Разработка игрового движка для настольных игр — увлекательная задача, которая часто требует глубокого понимания правил, стратегий и нюансов игры. Однако что, если я скажу вам, что вы можете создать игровой движок, не разбираясь в тонкостях самой игры? В этой статье мы рассмотрим реализацию кода, демонстрирующую эту концепцию, на примере движка для игры в шашки (шашки).

Сила абстракции

Ключ к разработке игрового движка без глубоких знаний об играх лежит в силе абстракции. Абстрагируя правила и механику игры в набор общих функций, мы можем создать гибкий движок, который можно адаптировать к различным настольным играм. Существует более 20 вариантов шашек. Движок может воспроизвести их все.

Предоставленный код демонстрирует реализацию механизма, основанную на алгоритме сокращения альфа-бета. Этот алгоритм широко используется в игровых системах ИИ для определения наилучшего хода в данном игровом состоянии. Разработав движок для взаимодействия с игровым сервером через стандартизированный интерфейс, мы можем отделить специфические для игры детали от логики движка.

Окружающая среда

Для среды шашек я буду использовать библиотеку py-draughts. Это позволяет играть в разные варианты шашек, используя один и тот же интерфейс.

Доска представлена ​​в виде n квадратов со значениями:

  • -2 — белый род
  • -1 — белый человек
  • 0 — пустой квадрат
  • 1 — черный человек
  • 2 — черный король

Реализация

Для оценки того, как получился наш двигатель, нам понадобится пара двигателей.

Функция оценки

Функция оценки отвечает за присвоение значения данной позиции на доске, указывая, насколько она благоприятна для игрока. В коде функция оценки реализована следующим образом:

def evaluate(self, board: Board) -> int:
  return -board._pos.sum()

Случайный движок

Этот движок выбирает случайный ход из списка допустимых ходов, доступных для текущего состояния доски:

class RandomEngine:
  def get_best_move(self, board: Board = None) -> tuple:
    return np.random.choice(list(board.legal_moves))

Этот движок можно использовать в качестве основы для тестирования или в качестве отправной точки для разработки более сложных движков.

Минимаксный движок

Класс, реализующий минимаксный алгоритм, широко используемый подход к игровому ИИ. Он исследует игровое дерево до указанной глубины, оценивая конечные узлы с помощью предоставленной функции оценки. Затем движок выбирает ход, который приводит к наилучшему результату оценки:

class MiniMaxEngine:
    def __init__(self, depth):
        self.depth = depth

    def get_best_move(self, board: Board = None) -> tuple:
        best_move = None
        best_evaluation = -100 if board.turn == Color.WHITE else 100
        for move in board.legal_moves:
            board.push(move)
            evaluation = self.__minimax(board, self.depth)
            board.pop()
            if best_move is None or evaluation > best_evaluation:
                best_move = move
                best_evaluation = evaluation
        return move

    def __minimax(self, board: Board, depth: int) -> float:
        if board.game_over:
            return -100 if board.turn == Color.WHITE else 100
        if depth == 0:
            return self.evaluate(board)
        if board.turn == Color.WHITE:
            best_evaluation = -100
            for move in board.legal_moves:
                board.push(move)
                evaluation = self.__minimax(board, depth - 1)
                board.pop()
                best_evaluation = max(best_evaluation, evaluation)
            return best_evaluation
        else:
            best_evaluation = 100
            for move in board.legal_moves:
                board.push(move)
                evaluation = self.__minimax(board, depth - 1)
                board.pop()
                best_evaluation = min(best_evaluation, evaluation)
            return best_evaluationp

Минимаксный движок рекурсивно исследует дерево игры, чередуя максимизацию и минимизацию игроков. Он отслеживает лучшее значение оценки, найденное на данный момент, и выбирает ход, связанный с этим значением.

Альфа-бета-движок

Оптимизированный минимаксный движок. Он позволяет избежать изучения определенных ветвей дерева игры, которые гарантированно будут хуже ранее изученных ветвей, уменьшая количество оценок:

class AlphaBetaEngine:
    def __init__(self, depth):
        self.depth = depth
        self.inspected_nodes = 0

    def get_best_move(self, board: Board = None) -> tuple:
        self.inspected_nodes = 0
        move, evaluation = self.__get_engine_move(board)
        return move

    def __get_engine_move(self, board: Board) -> tuple:
        depth = self.depth
        legal_moves = list(board.legal_moves)
        legal_moves.sort(key=lambda move: board.is_capture(move), reverse=True)
        evals = []
        alpha, beta = -100, 100
        for move in legal_moves:
            board.push(move)
            evals.append(
                self.__alpha_beta_pruning(
                    board,
                    depth - 1,
                    alpha,
                    beta,
                )
            )
            board.pop()
            if board.turn == Color.WHITE:
                alpha = max(alpha, evals[-1])
            else:
                beta = min(beta, evals[-1])
        index = (
            evals.index(max(evals))
            if board.turn == Color.WHITE
            else evals.index(min(evals))
        )
        return legal_moves[index], evals[index]

    def __alpha_beta_pruning(
        self, board: Board, depth: int, alpha: float, beta: float
    ) -> float:
        if board.game_over:
            return -100 if board.turn == Color.WHITE else 100
        if depth == 0:
            self.inspected_nodes += 1
            return self.evaluate(board)
        legal_moves = list(board.legal_moves)
        legal_moves.sort(key=lambda move: board.is_capture(move), reverse=True)
        for move in legal_moves:
            board.push(move)
            evaluation = self.__alpha_beta_pruning(board, depth - 1, alpha, beta)
            board.pop()
            if board.turn == Color.WHITE:
                alpha = max(alpha, evaluation)
            else:
                beta = min(beta, evaluation)
            if beta <= alpha:
                break
        return alpha if board.turn == Color.WHITE else beta

Результаты:

Библиотека py-draughts предоставляет пользовательский интерфейс для тестирования движков:

вот весь код, необходимый для его запуска.

board = Board()
engine = AlphaBetaEngine(4)
server = Server(board, get_best_move_method=engine.get_best_move)
server.run()

Статистика для всех двигателей:

Заключение

Создание игровых движков, не требующих глубоких знаний об играх, возможно благодаря абстракции, стандартизированным интерфейсам и алгоритмам, таким как Alpha-Beta Pruning. Предоставленный код для шашек иллюстрирует потенциал таких движков, которые можно применять к различным настольным играм, придерживаясь интерфейса движка и реализуя алгоритмы оценки ходов. Разработка этих движков открывает захватывающие возможности как для разработчиков игр, так и для энтузиастов ИИ, позволяя улучшать игры и исследовать искусственный интеллект и теорию игр. Используйте абстракцию, алгоритмическое мышление и стандартизированные интерфейсы, чтобы привнести новые измерения в мир настольных игр с вашими игровыми движками.

Потенциал игровых движков

Разработка игровых движков, не требующих глубокого понимания правил игры, открывает захватывающие возможности. Разработчики игр и энтузиасты теперь могут создавать ИИ-противников для своих игр, не разбираясь в тонкостях каждой отдельной игры. Это ускоряет процесс разработки и позволяет более широкому кругу игр получать выгоду от сложных противников с искусственным интеллектом.

Кроме того, эти игровые движки можно использовать в качестве учебных инструментов. Абстрагируясь от игровой механики и сосредоточив внимание на общих стратегиях и алгоритмах, начинающие разработчики игр и энтузиасты ИИ могут экспериментировать и узнавать больше о различных игровых приемах. Он становится воротами для изучения мира искусственного интеллекта и теории игр.

Исходный код:

https://github.com/michalskibinski109/py-draughts/blob/main/examples/engine.py