Алгоритм оценивает какие ситуации в 2048 хорошие, а какие плохие. Код (Python): import pygame import random import sys import math # ==================================================================== # КЛАСС 1: GameCore # ==================================================================== class GameCore: """ Полностью независимый движок игры 2048. """ def __init__(self, size=4): self.size = size self.grid = [[0] * size for _ in range(size)] self.score = 0 self.moves = 0 self._add_random_tile() self._add_random_tile() def _add_random_tile(self): empty = self.get_empty_cells() if empty: i, j = random.choice(empty) self.grid[i][j] = 2 if random.random() < 0.9 else 4 return True return False def _move_row_left(self, row): row = [x for x in row if x != 0] merged = [] i = 0 while i < len(row): if i + 1 < len(row) and row[i] == row[i+1]: merged.append(row[i] * 2) self.score += row[i] * 2 i += 2 else: merged.append(row[i]) i += 1 merged += [0] * (self.size - len(merged)) return merged def get_empty_cells(self): return [(i, j) for i in range(self.size) for j in range(self.size) if self.grid[i][j] == 0] def get_max_tile(self): return max(max(row) for row in self.grid) if self.grid else 0 def move_left(self): moved = False for i in range(self.size): new_row = self._move_row_left(self.grid[i]) if self.grid[i] != new_row: moved = True self.grid[i] = new_row return moved def move_right(self): for i in range(self.size): self.grid[i] = self.grid[i][::-1] moved = self.move_left() for i in range(self.size): self.grid[i] = self.grid[i][::-1] return moved def move_up(self): self.grid = [list(row) for row in zip(*self.grid)] moved = self.move_left() self.grid = [list(row) for row in zip(*self.grid)] return moved def move_down(self): self.grid = [list(row) for row in zip(*self.grid)] moved = self.move_right() self.grid = [list(row) for row in zip(*self.grid)] return moved def move(self, direction): moves = { 'up': self.move_up, 'down': self.move_down, 'left': self.move_left, 'right': self.move_right } if direction in moves: moved = moves[direction]() if moved: self.moves += 1 self._add_random_tile() return moved return False def can_move(self, direction): if direction == 'left': for row in self.grid: for j in range(self.size - 1): if row[j] == 0 and row[j+1] != 0: return True if row[j] != 0 and row[j] == row[j+1]: return True elif direction == 'right': for row in self.grid: for j in range(self.size - 1, 0, -1): if row[j] == 0 and row[j-1] != 0: return True if row[j] != 0 and row[j] == row[j-1]: return True elif direction == 'up': for j in range(self.size): for i in range(self.size - 1): if self.grid[i][j] == 0 and self.grid[i+1][j] != 0: return True if self.grid[i][j] != 0 and self.grid[i][j] == self.grid[i+1][j]: return True elif direction == 'down': for j in range(self.size): for i in range(self.size - 1, 0, -1): if self.grid[i][j] == 0 and self.grid[i-1][j] != 0: return True if self.grid[i][j] != 0 and self.grid[i][j] == self.grid[i-1][j]: return True return False def get_available_moves(self): return [d for d in ['up', 'down', 'left', 'right'] if self.can_move(d)] def is_game_over(self): if self.get_empty_cells(): return False for i in range(self.size): for j in range(self.size - 1): if self.grid[i][j] == self.grid[i][j+1]: return False for i in range(self.size - 1): for j in range(self.size): if self.grid[i][j] == self.grid[i+1][j]: return False return True def clone(self): new = object.__new__(GameCore) new.size = self.size new.grid = [row[:] for row in self.grid] new.score = self.score new.moves = self.moves return new def get_state_hash(self): return (tuple(tuple(row) for row in self.grid), self.score) # ==================================================================== # КЛАСС 2: GameAi # ==================================================================== class GameAi: """ ИИ на основе EXPECTIMAX с глубиной 4 """ def __init__(self): self.depth = 4 self.nodes = 0 self.cache = {} self.best_move = None # Веса для критериев self.weights = { 'safety': 1.0, 'structure': 1.5, 'future': 0.8, 'progress': 0.5, } # 4 матрицы градиента self.gradient_matrices = self._create_gradient_matrices() def _create_gradient_matrices(self): """4 матрицы градиента для разных углов""" base = [ [100, 80, 60, 40], [30, 20, 10, 5], [4, 3, 2, 1], [0, 0, 0, 0] ] return [ base, [row[::-1] for row in base], base[::-1], [row[::-1] for row in base[::-1]] ] def get_best_move(self, game): """Главный метод: возвращает лучший ход""" self.nodes = 0 self.cache = {} self.best_move = None moves = game.get_available_moves() if not moves: return None best_score = -float('inf') # Сортируем ходы для лучшего отсечения move_scores = [] for move in moves: clone = game.clone() clone.move(move) move_scores.append((move, self._evaluate(clone))) move_scores.sort(key=lambda x: x[1], reverse=True) for move, _ in move_scores: clone = game.clone() clone.move(move) score = self._expectimax(clone, self.depth-1, -float('inf'), float('inf'), False) if score > best_score: best_score = score self.best_move = move return self.best_move def _expectimax(self, game, depth, alpha, beta, is_max): """Expectimax с alpha-beta только на MAX узлах""" self.nodes += 1 key = (game.get_state_hash(), depth, is_max) if key in self.cache: return self.cache[key] if depth == 0 or game.is_game_over(): score = self._evaluate(game) self.cache[key] = score return score if is_max: # MAX узел: ход игрока max_score = -float('inf') moves = game.get_available_moves() if not moves: score = self._evaluate(game) self.cache[key] = score return score # Сортируем ходы для отсечения move_scores = [] for move in moves: clone = game.clone() clone.move(move) move_scores.append((move, self._evaluate(clone))) move_scores.sort(key=lambda x: x[1], reverse=True) for move, _ in move_scores[:6]: clone = game.clone() clone.move(move) score = self._expectimax(clone, depth-1, alpha, beta, False) max_score = max(max_score, score) alpha = max(alpha, score) if beta <= alpha: break self.cache[key] = max_score return max_score else: # EXPECT узел: случайное добавление плитки expected_score = 0 empty = game.get_empty_cells() if not empty: score = self._evaluate(game) self.cache[key] = score return score # Рассматриваем ВСЕ пустые клетки for pos in empty: for val, prob in [(2, 0.9), (4, 0.1)]: clone = game.clone() clone.grid[pos[0]][pos[1]] = val score = self._expectimax(clone, depth-1, alpha, beta, True) expected_score += score * prob / len(empty) self.cache[key] = expected_score return expected_score def _evaluate(self, game): """ ОЦЕНОЧНАЯ ФУНКЦИЯ - 4 независимых критерия """ grid = game.grid score = 0 # 1. SAFETY - безопасность score += self._safety(game) * self.weights['safety'] # 2. STRUCTURE - структура поля score += self._structure(grid) * self.weights['structure'] # 3. FUTURE - будущий потенциал score += self._future(game) * self.weights['future'] # 4. PROGRESS - прогресс score += self._progress(game) * self.weights['progress'] return score # ==================== КРИТЕРИЙ 1: SAFETY ==================== def _safety(self, game): """ Безопасность = пустые клетки + штраф за заполненность """ empty = len(game.get_empty_cells()) # Пустые клетки - главный фактор score = empty * 100 # Нелинейный штраф за малое количество пустых клеток if empty <= 2: score -= 5000 elif empty <= 4: score -= 2000 elif empty <= 6: score -= 500 return score # ==================== КРИТЕРИЙ 2: STRUCTURE ==================== def _structure(self, grid): """ Структура = градиент + цепочка + сглаженность """ score = 0 # 2.1 Градиент (выбираем лучший угол) score += self._gradient(grid) * 100 # 2.2 Цепочка (правильный порядок крупных плиток) score += self._chain(grid) * 80 # 2.3 Сглаженность (log2) score += self._smoothness(grid) * 0.3 return score def _gradient(self, grid): """Выбираем лучший градиент из 4 углов""" best = -float('inf') for matrix in self.gradient_matrices: g_score = 0 for i in range(4): for j in range(4): if grid[i][j]: g_score += grid[i][j] * matrix[i][j] best = max(best, g_score) return best / 1000.0 def _chain(self, grid): """ Проверяет, образуют ли крупные плитки правильную цепочку """ tiles = [] for i in range(4): for j in range(4): if grid[i][j] > 0: tiles.append((grid[i][j], i, j)) if len(tiles) < 2: return 0 tiles.sort(reverse=True) chain_score = 0 # Проверяем топ-6 плиток for i in range(min(6, len(tiles)) - 1): x1, y1 = tiles[i][1], tiles[i][2] x2, y2 = tiles[i+1][1], tiles[i+1][2] # Расстояние Манхэттена dist = abs(x1 - x2) + abs(y1 - y2) # Награда за близость if dist == 1: chain_score += 10 elif dist == 2: chain_score += 5 elif dist <= 3: chain_score += 2 return chain_score def _smoothness(self, grid): """ Сглаженность с использованием log2 """ smooth = 0 def log2_val(x): return math.log2(x) if x > 0 else 0 for i in range(4): for j in range(4): if grid[i][j]: if j + 1 < 4 and grid[i][j+1]: diff = abs(log2_val(grid[i][j]) - log2_val(grid[i][j+1])) smooth -= diff if i + 1 < 4 and grid[i+1][j]: diff = abs(log2_val(grid[i][j]) - log2_val(grid[i+1][j])) smooth -= diff return smooth # ==================== КРИТЕРИЙ 3: FUTURE ==================== def _future(self, game): """ Будущий потенциал = объединения + мобильность """ score = 0 # 3.1 Потенциал объединений score += self._merge_potential(game.grid) * 5 # 3.2 Мобильность (доступные ходы) score += len(game.get_available_moves()) * 50 return score def _merge_potential(self, grid): """ Оценка возможности объединений """ potential = 0 for i in range(4): for j in range(4): if grid[i][j]: if j+1 < 4 and grid[i][j] == grid[i][j+1]: potential += grid[i][j] if i+1 < 4 and grid[i][j] == grid[i+1][j]: potential += grid[i][j] return potential # ==================== КРИТЕРИЙ 4: PROGRESS ==================== def _progress(self, game): """ Прогресс = максимальная плитка """ max_tile = game.get_max_tile() score = max_tile * 2 # Бонус за достижение высоких плиток if max_tile >= 2048: score += 5000 elif max_tile >= 1024: score += 2000 elif max_tile >= 512: score += 500 return score # ==================================================================== # ВИЗУАЛИЗАЦИЯ (pygame) - ОРИГИНАЛЬНЫЙ ВНЕШНИЙ ВИД # ==================================================================== class Renderer: def __init__(self): pygame.init() self.size = 4 self.cell = 100 self.pad = 10 self.w = self.size * (self.cell + self.pad) + self.pad + 40 self.h = self.w + 100 self.screen = pygame.display.set_mode((self.w, self.h)) pygame.display.set_caption("2048 AI") # Оригинальные цвета (светлая тема) self.colors = { 0: (205,193,180), 2: (238,228,218), 4: (237,224,200), 8: (242,177,121), 16: (245,149,99), 32: (246,124,95), 64: (246,94,59), 128: (237,207,114), 256: (237,204,97), 512: (237,200,80), 1024: (237,197,63), 2048: (237,194,46) } self.fonts = { 'small': pygame.font.Font(None, 28), 'medium': pygame.font.Font(None, 36), 'large': pygame.font.Font(None, 48), 'huge': pygame.font.Font(None, 60) } self.clock = pygame.time.Clock() def draw(self, game): # Оригинальный фон self.screen.fill((187,173,160)) # Заголовок self.screen.blit(self.fonts['huge'].render("2048", True, (119,110,101)), (20, 10)) self.screen.blit(self.fonts['medium'].render(f"Score: {game.score}", True, (119,110,101)), (20, 75)) # Информация об AI self.screen.blit(self.fonts['small'].render(f"AI Depth: 4", True, (119,110,101)), (200, 80)) self.screen.blit(self.fonts['small'].render(f"Moves: {game.moves}", True, (119,110,101)), (200, 105)) # Сетка ox, oy = 20, 120 for i in range(self.size): for j in range(self.size): x = ox + self.pad + j * (self.cell + self.pad) y = oy + self.pad + i * (self.cell + self.pad) val = game.grid[i][j] rect = pygame.Rect(x, y, self.cell, self.cell) pygame.draw.rect(self.screen, self.colors.get(val, (60,58,50)), rect, border_radius=6) if val: color = (249,246,242) if val > 4 else (119,110,101) font = self.fonts['large'] if val < 100 else self.fonts['medium'] text = font.render(str(val), True, color) self.screen.blit(text, text.get_rect(center=rect.center)) # Game Over if game.is_game_over(): s = pygame.Surface((self.w-40, self.h-140)) s.set_alpha(180) s.fill((0,0,0)) self.screen.blit(s, (20,120)) text = self.fonts['huge'].render("GAME OVER", True, (255,255,255)) self.screen.blit(text, text.get_rect(center=(self.w//2, self.h//2))) restart_text = self.fonts['medium'].render("Press R to restart", True, (200,200,200)) self.screen.blit(restart_text, restart_text.get_rect(center=(self.w//2, self.h//2 + 50))) # Управление controls = ["R: Restart", "ESC: Exit"] for i, control in enumerate(controls): text = self.fonts['small'].render(control, True, (119,110,101)) self.screen.blit(text, (20 + i * 150, self.h - 30)) pygame.display.flip() # ==================================================================== # ЗАПУСК ИГРЫ - СРАЗУ AI РЕЖИМ С ГЛУБИНОЙ 4 # ==================================================================== def main(): game = GameCore() ai = GameAi() # глубина 4 по умолчанию render = Renderer() running = True print("\n" + "="*50) print("2048 с ИСКУССТВЕННЫМ ИНТЕЛЛЕКТОМ") print("ГЛУБИНА AI: 4") print("УПРАВЛЕНИЕ: R - рестарт, ESC - выход") print("="*50 + "\n") while running: # Обработка событий for event in pygame.event.get(): if event.type == pygame.QUIT: running = False break if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: running = False break if event.key == pygame.K_r: game = GameCore() print("\n" + "="*50) print("ИГРА ПЕРЕЗАПУЩЕНА") print("="*50) continue # AI ход if not game.is_game_over(): move = ai.get_best_move(game) if move: game.move(move) if game.is_game_over(): print("\n" + "!"*50) print("ИГРА ОКОНЧЕНА!") print(f"СЧЕТ: {game.score}") print(f"МАКС. ПЛИТКА: {game.get_max_tile()}") print(f"ХОДОВ: {game.moves}") print(f"УЗЛОВ: {ai.nodes}") print("!"*50) pygame.time.wait(100) # Отрисовка render.draw(game) render.clock.tick(30) pygame.quit() sys.exit() if __name__ == "__main__": main()
Я только не понял, в чём смысл этой игры? Запускаешь и смотришь как компьютер сам с собой играет. Проверка загрузки процессора Питоновским кодом или что? Поясните профессор, для низших слоёв общества.
В архиве версия без бота. Управление клавишами wasd, можно переназначить на стрелки. Если можешь чтото интереснее придумать -> выкладывай.
На 40k памяти такое работало: 16 кнопок на рандоме это какая то деградация ниже некуда, значит что технологии неправильные или используются неверно. от чего независимый ?
Движок отдельно, принятие решений отдельно. Очевидно же. Если ты писал что-то сложнее хелло-ворда, мог бы догадаться что смысл здесь в другом, например улучшить алгоритм принятия решений.