Игра 2048

Тема в разделе "WASM.ARTIFICIAL.INTELLIGENCE", создана пользователем Application, 12 авг 2026 в 10:05.

  1. Application

    Application Moderator Команда форума

    Публикаций:
    1
    Регистрация:
    8 дек 2007
    Сообщения:
    991
    Алгоритм оценивает какие ситуации в 2048 хорошие, а какие плохие.

    2026-08-12_10-08-45.png
    Код (Python):
    1. import pygame
    2. import random
    3. import sys
    4. import math
    5.  
    6. # ====================================================================
    7. # КЛАСС 1: GameCore
    8. # ====================================================================
    9. class GameCore:
    10.     """
    11.    Полностью независимый движок игры 2048.
    12.    """
    13.  
    14.     def __init__(self, size=4):
    15.         self.size = size
    16.         self.grid = [[0] * size for _ in range(size)]
    17.         self.score = 0
    18.         self.moves = 0
    19.         self._add_random_tile()
    20.         self._add_random_tile()
    21.  
    22.     def _add_random_tile(self):
    23.         empty = self.get_empty_cells()
    24.         if empty:
    25.             i, j = random.choice(empty)
    26.             self.grid[i][j] = 2 if random.random() < 0.9 else 4
    27.             return True
    28.         return False
    29.  
    30.     def _move_row_left(self, row):
    31.         row = [x for x in row if x != 0]
    32.         merged = []
    33.         i = 0
    34.         while i < len(row):
    35.             if i + 1 < len(row) and row[i] == row[i+1]:
    36.                 merged.append(row[i] * 2)
    37.                 self.score += row[i] * 2
    38.                 i += 2
    39.             else:
    40.                 merged.append(row[i])
    41.                 i += 1
    42.         merged += [0] * (self.size - len(merged))
    43.         return merged
    44.  
    45.     def get_empty_cells(self):
    46.         return [(i, j) for i in range(self.size)
    47.                 for j in range(self.size) if self.grid[i][j] == 0]
    48.  
    49.     def get_max_tile(self):
    50.         return max(max(row) for row in self.grid) if self.grid else 0
    51.  
    52.     def move_left(self):
    53.         moved = False
    54.         for i in range(self.size):
    55.             new_row = self._move_row_left(self.grid[i])
    56.             if self.grid[i] != new_row:
    57.                 moved = True
    58.             self.grid[i] = new_row
    59.         return moved
    60.  
    61.     def move_right(self):
    62.         for i in range(self.size):
    63.             self.grid[i] = self.grid[i][::-1]
    64.         moved = self.move_left()
    65.         for i in range(self.size):
    66.             self.grid[i] = self.grid[i][::-1]
    67.         return moved
    68.  
    69.     def move_up(self):
    70.         self.grid = [list(row) for row in zip(*self.grid)]
    71.         moved = self.move_left()
    72.         self.grid = [list(row) for row in zip(*self.grid)]
    73.         return moved
    74.  
    75.     def move_down(self):
    76.         self.grid = [list(row) for row in zip(*self.grid)]
    77.         moved = self.move_right()
    78.         self.grid = [list(row) for row in zip(*self.grid)]
    79.         return moved
    80.  
    81.     def move(self, direction):
    82.         moves = {
    83.             'up': self.move_up, 'down': self.move_down,
    84.             'left': self.move_left, 'right': self.move_right
    85.         }
    86.         if direction in moves:
    87.             moved = moves[direction]()
    88.             if moved:
    89.                 self.moves += 1
    90.                 self._add_random_tile()
    91.             return moved
    92.         return False
    93.  
    94.     def can_move(self, direction):
    95.         if direction == 'left':
    96.             for row in self.grid:
    97.                 for j in range(self.size - 1):
    98.                     if row[j] == 0 and row[j+1] != 0:
    99.                         return True
    100.                     if row[j] != 0 and row[j] == row[j+1]:
    101.                         return True
    102.         elif direction == 'right':
    103.             for row in self.grid:
    104.                 for j in range(self.size - 1, 0, -1):
    105.                     if row[j] == 0 and row[j-1] != 0:
    106.                         return True
    107.                     if row[j] != 0 and row[j] == row[j-1]:
    108.                         return True
    109.         elif direction == 'up':
    110.             for j in range(self.size):
    111.                 for i in range(self.size - 1):
    112.                     if self.grid[i][j] == 0 and self.grid[i+1][j] != 0:
    113.                         return True
    114.                     if self.grid[i][j] != 0 and self.grid[i][j] == self.grid[i+1][j]:
    115.                         return True
    116.         elif direction == 'down':
    117.             for j in range(self.size):
    118.                 for i in range(self.size - 1, 0, -1):
    119.                     if self.grid[i][j] == 0 and self.grid[i-1][j] != 0:
    120.                         return True
    121.                     if self.grid[i][j] != 0 and self.grid[i][j] == self.grid[i-1][j]:
    122.                         return True
    123.         return False
    124.  
    125.     def get_available_moves(self):
    126.         return [d for d in ['up', 'down', 'left', 'right'] if self.can_move(d)]
    127.  
    128.     def is_game_over(self):
    129.         if self.get_empty_cells():
    130.             return False
    131.         for i in range(self.size):
    132.             for j in range(self.size - 1):
    133.                 if self.grid[i][j] == self.grid[i][j+1]:
    134.                     return False
    135.         for i in range(self.size - 1):
    136.             for j in range(self.size):
    137.                 if self.grid[i][j] == self.grid[i+1][j]:
    138.                     return False
    139.         return True
    140.  
    141.     def clone(self):
    142.         new = object.__new__(GameCore)
    143.         new.size = self.size
    144.         new.grid = [row[:] for row in self.grid]
    145.         new.score = self.score
    146.         new.moves = self.moves
    147.         return new
    148.  
    149.     def get_state_hash(self):
    150.         return (tuple(tuple(row) for row in self.grid), self.score)
    151.  
    152.  
    153. # ====================================================================
    154. # КЛАСС 2: GameAi
    155. # ====================================================================
    156. class GameAi:
    157.     """
    158.    ИИ на основе EXPECTIMAX с глубиной 4
    159.    """
    160.  
    161.     def __init__(self):
    162.         self.depth = 4
    163.         self.nodes = 0
    164.         self.cache = {}
    165.         self.best_move = None
    166.  
    167.         # Веса для критериев
    168.         self.weights = {
    169.             'safety': 1.0,
    170.             'structure': 1.5,
    171.             'future': 0.8,
    172.             'progress': 0.5,
    173.         }
    174.  
    175.         # 4 матрицы градиента
    176.         self.gradient_matrices = self._create_gradient_matrices()
    177.  
    178.     def _create_gradient_matrices(self):
    179.         """4 матрицы градиента для разных углов"""
    180.         base = [
    181.             [100, 80, 60, 40],
    182.             [30, 20, 10, 5],
    183.             [4, 3, 2, 1],
    184.             [0, 0, 0, 0]
    185.         ]
    186.         return [
    187.             base,
    188.             [row[::-1] for row in base],
    189.             base[::-1],
    190.             [row[::-1] for row in base[::-1]]
    191.         ]
    192.  
    193.     def get_best_move(self, game):
    194.         """Главный метод: возвращает лучший ход"""
    195.         self.nodes = 0
    196.         self.cache = {}
    197.         self.best_move = None
    198.  
    199.         moves = game.get_available_moves()
    200.         if not moves:
    201.             return None
    202.  
    203.         best_score = -float('inf')
    204.  
    205.         # Сортируем ходы для лучшего отсечения
    206.         move_scores = []
    207.         for move in moves:
    208.             clone = game.clone()
    209.             clone.move(move)
    210.             move_scores.append((move, self._evaluate(clone)))
    211.         move_scores.sort(key=lambda x: x[1], reverse=True)
    212.  
    213.         for move, _ in move_scores:
    214.             clone = game.clone()
    215.             clone.move(move)
    216.             score = self._expectimax(clone, self.depth-1, -float('inf'), float('inf'), False)
    217.  
    218.             if score > best_score:
    219.                 best_score = score
    220.                 self.best_move = move
    221.  
    222.         return self.best_move
    223.  
    224.     def _expectimax(self, game, depth, alpha, beta, is_max):
    225.         """Expectimax с alpha-beta только на MAX узлах"""
    226.         self.nodes += 1
    227.  
    228.         key = (game.get_state_hash(), depth, is_max)
    229.         if key in self.cache:
    230.             return self.cache[key]
    231.  
    232.         if depth == 0 or game.is_game_over():
    233.             score = self._evaluate(game)
    234.             self.cache[key] = score
    235.             return score
    236.  
    237.         if is_max:
    238.             # MAX узел: ход игрока
    239.             max_score = -float('inf')
    240.             moves = game.get_available_moves()
    241.  
    242.             if not moves:
    243.                 score = self._evaluate(game)
    244.                 self.cache[key] = score
    245.                 return score
    246.  
    247.             # Сортируем ходы для отсечения
    248.             move_scores = []
    249.             for move in moves:
    250.                 clone = game.clone()
    251.                 clone.move(move)
    252.                 move_scores.append((move, self._evaluate(clone)))
    253.             move_scores.sort(key=lambda x: x[1], reverse=True)
    254.  
    255.             for move, _ in move_scores[:6]:
    256.                 clone = game.clone()
    257.                 clone.move(move)
    258.                 score = self._expectimax(clone, depth-1, alpha, beta, False)
    259.                 max_score = max(max_score, score)
    260.                 alpha = max(alpha, score)
    261.  
    262.                 if beta <= alpha:
    263.                     break
    264.  
    265.             self.cache[key] = max_score
    266.             return max_score
    267.  
    268.         else:
    269.             # EXPECT узел: случайное добавление плитки
    270.             expected_score = 0
    271.             empty = game.get_empty_cells()
    272.  
    273.             if not empty:
    274.                 score = self._evaluate(game)
    275.                 self.cache[key] = score
    276.                 return score
    277.  
    278.             # Рассматриваем ВСЕ пустые клетки
    279.             for pos in empty:
    280.                 for val, prob in [(2, 0.9), (4, 0.1)]:
    281.                     clone = game.clone()
    282.                     clone.grid[pos[0]][pos[1]] = val
    283.                     score = self._expectimax(clone, depth-1, alpha, beta, True)
    284.                     expected_score += score * prob / len(empty)
    285.  
    286.             self.cache[key] = expected_score
    287.             return expected_score
    288.  
    289.     def _evaluate(self, game):
    290.         """
    291.        ОЦЕНОЧНАЯ ФУНКЦИЯ - 4 независимых критерия
    292.        """
    293.         grid = game.grid
    294.         score = 0
    295.  
    296.         # 1. SAFETY - безопасность
    297.         score += self._safety(game) * self.weights['safety']
    298.  
    299.         # 2. STRUCTURE - структура поля
    300.         score += self._structure(grid) * self.weights['structure']
    301.  
    302.         # 3. FUTURE - будущий потенциал
    303.         score += self._future(game) * self.weights['future']
    304.  
    305.         # 4. PROGRESS - прогресс
    306.         score += self._progress(game) * self.weights['progress']
    307.  
    308.         return score
    309.  
    310.     # ==================== КРИТЕРИЙ 1: SAFETY ====================
    311.  
    312.     def _safety(self, game):
    313.         """
    314.        Безопасность = пустые клетки + штраф за заполненность
    315.        """
    316.         empty = len(game.get_empty_cells())
    317.  
    318.         # Пустые клетки - главный фактор
    319.         score = empty * 100
    320.  
    321.         # Нелинейный штраф за малое количество пустых клеток
    322.         if empty <= 2:
    323.             score -= 5000
    324.         elif empty <= 4:
    325.             score -= 2000
    326.         elif empty <= 6:
    327.             score -= 500
    328.  
    329.         return score
    330.  
    331.     # ==================== КРИТЕРИЙ 2: STRUCTURE ====================
    332.  
    333.     def _structure(self, grid):
    334.         """
    335.        Структура = градиент + цепочка + сглаженность
    336.        """
    337.         score = 0
    338.  
    339.         # 2.1 Градиент (выбираем лучший угол)
    340.         score += self._gradient(grid) * 100
    341.  
    342.         # 2.2 Цепочка (правильный порядок крупных плиток)
    343.         score += self._chain(grid) * 80
    344.  
    345.         # 2.3 Сглаженность (log2)
    346.         score += self._smoothness(grid) * 0.3
    347.  
    348.         return score
    349.  
    350.     def _gradient(self, grid):
    351.         """Выбираем лучший градиент из 4 углов"""
    352.         best = -float('inf')
    353.         for matrix in self.gradient_matrices:
    354.             g_score = 0
    355.             for i in range(4):
    356.                 for j in range(4):
    357.                     if grid[i][j]:
    358.                         g_score += grid[i][j] * matrix[i][j]
    359.             best = max(best, g_score)
    360.         return best / 1000.0
    361.  
    362.     def _chain(self, grid):
    363.         """
    364.        Проверяет, образуют ли крупные плитки правильную цепочку
    365.        """
    366.         tiles = []
    367.         for i in range(4):
    368.             for j in range(4):
    369.                 if grid[i][j] > 0:
    370.                     tiles.append((grid[i][j], i, j))
    371.  
    372.         if len(tiles) < 2:
    373.             return 0
    374.  
    375.         tiles.sort(reverse=True)
    376.  
    377.         chain_score = 0
    378.  
    379.         # Проверяем топ-6 плиток
    380.         for i in range(min(6, len(tiles)) - 1):
    381.             x1, y1 = tiles[i][1], tiles[i][2]
    382.             x2, y2 = tiles[i+1][1], tiles[i+1][2]
    383.  
    384.             # Расстояние Манхэттена
    385.             dist = abs(x1 - x2) + abs(y1 - y2)
    386.  
    387.             # Награда за близость
    388.             if dist == 1:
    389.                 chain_score += 10
    390.             elif dist == 2:
    391.                 chain_score += 5
    392.             elif dist <= 3:
    393.                 chain_score += 2
    394.  
    395.         return chain_score
    396.  
    397.     def _smoothness(self, grid):
    398.         """
    399.        Сглаженность с использованием log2
    400.        """
    401.         smooth = 0
    402.  
    403.         def log2_val(x):
    404.             return math.log2(x) if x > 0 else 0
    405.  
    406.         for i in range(4):
    407.             for j in range(4):
    408.                 if grid[i][j]:
    409.                     if j + 1 < 4 and grid[i][j+1]:
    410.                         diff = abs(log2_val(grid[i][j]) - log2_val(grid[i][j+1]))
    411.                         smooth -= diff
    412.                     if i + 1 < 4 and grid[i+1][j]:
    413.                         diff = abs(log2_val(grid[i][j]) - log2_val(grid[i+1][j]))
    414.                         smooth -= diff
    415.  
    416.         return smooth
    417.  
    418.     # ==================== КРИТЕРИЙ 3: FUTURE ====================
    419.  
    420.     def _future(self, game):
    421.         """
    422.        Будущий потенциал = объединения + мобильность
    423.        """
    424.         score = 0
    425.  
    426.         # 3.1 Потенциал объединений
    427.         score += self._merge_potential(game.grid) * 5
    428.  
    429.         # 3.2 Мобильность (доступные ходы)
    430.         score += len(game.get_available_moves()) * 50
    431.  
    432.         return score
    433.  
    434.     def _merge_potential(self, grid):
    435.         """
    436.        Оценка возможности объединений
    437.        """
    438.         potential = 0
    439.         for i in range(4):
    440.             for j in range(4):
    441.                 if grid[i][j]:
    442.                     if j+1 < 4 and grid[i][j] == grid[i][j+1]:
    443.                         potential += grid[i][j]
    444.                     if i+1 < 4 and grid[i][j] == grid[i+1][j]:
    445.                         potential += grid[i][j]
    446.         return potential
    447.  
    448.     # ==================== КРИТЕРИЙ 4: PROGRESS ====================
    449.  
    450.     def _progress(self, game):
    451.         """
    452.        Прогресс = максимальная плитка
    453.        """
    454.         max_tile = game.get_max_tile()
    455.  
    456.         score = max_tile * 2
    457.  
    458.         # Бонус за достижение высоких плиток
    459.         if max_tile >= 2048:
    460.             score += 5000
    461.         elif max_tile >= 1024:
    462.             score += 2000
    463.         elif max_tile >= 512:
    464.             score += 500
    465.  
    466.         return score
    467.  
    468.  
    469. # ====================================================================
    470. # ВИЗУАЛИЗАЦИЯ (pygame) - ОРИГИНАЛЬНЫЙ ВНЕШНИЙ ВИД
    471. # ====================================================================
    472. class Renderer:
    473.     def __init__(self):
    474.         pygame.init()
    475.         self.size = 4
    476.         self.cell = 100
    477.         self.pad = 10
    478.         self.w = self.size * (self.cell + self.pad) + self.pad + 40
    479.         self.h = self.w + 100
    480.         self.screen = pygame.display.set_mode((self.w, self.h))
    481.         pygame.display.set_caption("2048 AI")
    482.  
    483.         # Оригинальные цвета (светлая тема)
    484.         self.colors = {
    485.             0: (205,193,180), 2: (238,228,218), 4: (237,224,200),
    486.             8: (242,177,121), 16: (245,149,99), 32: (246,124,95),
    487.             64: (246,94,59), 128: (237,207,114), 256: (237,204,97),
    488.             512: (237,200,80), 1024: (237,197,63), 2048: (237,194,46)
    489.         }
    490.         self.fonts = {
    491.             'small': pygame.font.Font(None, 28),
    492.             'medium': pygame.font.Font(None, 36),
    493.             'large': pygame.font.Font(None, 48),
    494.             'huge': pygame.font.Font(None, 60)
    495.         }
    496.         self.clock = pygame.time.Clock()
    497.  
    498.     def draw(self, game):
    499.         # Оригинальный фон
    500.         self.screen.fill((187,173,160))
    501.  
    502.         # Заголовок
    503.         self.screen.blit(self.fonts['huge'].render("2048", True, (119,110,101)), (20, 10))
    504.         self.screen.blit(self.fonts['medium'].render(f"Score: {game.score}", True, (119,110,101)), (20, 75))
    505.  
    506.         # Информация об AI
    507.         self.screen.blit(self.fonts['small'].render(f"AI Depth: 4", True, (119,110,101)), (200, 80))
    508.         self.screen.blit(self.fonts['small'].render(f"Moves: {game.moves}", True, (119,110,101)), (200, 105))
    509.  
    510.         # Сетка
    511.         ox, oy = 20, 120
    512.         for i in range(self.size):
    513.             for j in range(self.size):
    514.                 x = ox + self.pad + j * (self.cell + self.pad)
    515.                 y = oy + self.pad + i * (self.cell + self.pad)
    516.                 val = game.grid[i][j]
    517.  
    518.                 rect = pygame.Rect(x, y, self.cell, self.cell)
    519.                 pygame.draw.rect(self.screen, self.colors.get(val, (60,58,50)), rect, border_radius=6)
    520.  
    521.                 if val:
    522.                     color = (249,246,242) if val > 4 else (119,110,101)
    523.                     font = self.fonts['large'] if val < 100 else self.fonts['medium']
    524.                     text = font.render(str(val), True, color)
    525.                     self.screen.blit(text, text.get_rect(center=rect.center))
    526.  
    527.         # Game Over
    528.         if game.is_game_over():
    529.             s = pygame.Surface((self.w-40, self.h-140))
    530.             s.set_alpha(180)
    531.             s.fill((0,0,0))
    532.             self.screen.blit(s, (20,120))
    533.             text = self.fonts['huge'].render("GAME OVER", True, (255,255,255))
    534.             self.screen.blit(text, text.get_rect(center=(self.w//2, self.h//2)))
    535.  
    536.             restart_text = self.fonts['medium'].render("Press R to restart", True, (200,200,200))
    537.             self.screen.blit(restart_text, restart_text.get_rect(center=(self.w//2, self.h//2 + 50)))
    538.  
    539.         # Управление
    540.         controls = ["R: Restart", "ESC: Exit"]
    541.         for i, control in enumerate(controls):
    542.             text = self.fonts['small'].render(control, True, (119,110,101))
    543.             self.screen.blit(text, (20 + i * 150, self.h - 30))
    544.  
    545.         pygame.display.flip()
    546.  
    547.  
    548. # ====================================================================
    549. # ЗАПУСК ИГРЫ - СРАЗУ AI РЕЖИМ С ГЛУБИНОЙ 4
    550. # ====================================================================
    551. def main():
    552.     game = GameCore()
    553.     ai = GameAi()  # глубина 4 по умолчанию
    554.     render = Renderer()
    555.     running = True
    556.  
    557.     print("\n" + "="*50)
    558.     print("2048 с ИСКУССТВЕННЫМ ИНТЕЛЛЕКТОМ")
    559.     print("ГЛУБИНА AI: 4")
    560.     print("УПРАВЛЕНИЕ: R - рестарт, ESC - выход")
    561.     print("="*50 + "\n")
    562.  
    563.     while running:
    564.         # Обработка событий
    565.         for event in pygame.event.get():
    566.             if event.type == pygame.QUIT:
    567.                 running = False
    568.                 break
    569.             if event.type == pygame.KEYDOWN:
    570.                 if event.key == pygame.K_ESCAPE:
    571.                     running = False
    572.                     break
    573.                 if event.key == pygame.K_r:
    574.                     game = GameCore()
    575.                     print("\n" + "="*50)
    576.                     print("ИГРА ПЕРЕЗАПУЩЕНА")
    577.                     print("="*50)
    578.                     continue
    579.  
    580.         # AI ход
    581.         if not game.is_game_over():
    582.             move = ai.get_best_move(game)
    583.             if move:
    584.                 game.move(move)
    585.                 if game.is_game_over():
    586.                     print("\n" + "!"*50)
    587.                     print("ИГРА ОКОНЧЕНА!")
    588.                     print(f"СЧЕТ: {game.score}")
    589.                     print(f"МАКС. ПЛИТКА: {game.get_max_tile()}")
    590.                     print(f"ХОДОВ: {game.moves}")
    591.                     print(f"УЗЛОВ: {ai.nodes}")
    592.                     print("!"*50)
    593.             pygame.time.wait(100)
    594.  
    595.         # Отрисовка
    596.         render.draw(game)
    597.         render.clock.tick(30)
    598.  
    599.     pygame.quit()
    600.     sys.exit()
    601.  
    602.  
    603. if __name__ == "__main__":
    604.     main()
     
  2. GRAFik

    GRAFik Active Member

    Публикаций:
    0
    Регистрация:
    14 мар 2020
    Сообщения:
    535
    Я только не понял, в чём смысл этой игры?

    Запускаешь и смотришь как компьютер сам с собой играет.
    Проверка загрузки процессора Питоновским кодом или что?
    Поясните профессор, для низших слоёв общества. :)
     
  3. Application

    Application Moderator Команда форума

    Публикаций:
    1
    Регистрация:
    8 дек 2007
    Сообщения:
    991
    В архиве версия без бота. Управление клавишами wasd, можно переназначить на стрелки.
    Если можешь чтото интереснее придумать -> выкладывай.
     

    Вложения:

    • 2048_2.rar
      Размер файла:
      2,7 КБ
      Просмотров:
      2
  4. Ahimov

    Ahimov Active Member

    Публикаций:
    0
    Регистрация:
    14 окт 2024
    Сообщения:
    802
    На 40k памяти такое работало:



    16 кнопок на рандоме это какая то деградация ниже некуда, значит что технологии неправильные или используются неверно.

    от чего независимый ?
     
  5. Application

    Application Moderator Команда форума

    Публикаций:
    1
    Регистрация:
    8 дек 2007
    Сообщения:
    991
    Движок отдельно, принятие решений отдельно. Очевидно же.
    Если ты писал что-то сложнее хелло-ворда, мог бы догадаться что смысл здесь в другом, например улучшить алгоритм принятия решений.