На этом шаге мы рассмотрим основные компоненты и гры и приведем их реализацию.
Как и при рассмотрении большинства более сложных задач в предыдущих шагах, постараемся сделать решение как можно более обобщенным. В случае состязательного поиска это означает, что наши алгоритмы поиска не должны зависеть от игры. Начнем с определения нескольких простых базовых классов, которые выявляют состояние, необходимое алгоритмам поиска. Затем создадим подклассы базовых классов для конкретных игр (крестики-нолики и Connect Four) и введем эти подклассы в алгоритмы поиска, чтобы они могли "играть" в эти игры. Вот базовые классы (файл board.ру).
from __future__ import annotations from typing import NewType, List from abc import ABC, abstractmethod Move = NewType('Move', int) class Piece: @property def opposite(self) -> Piece: raise NotImplementedError("Должно быть реализовано подклассами.") class Board(ABC): @property @abstractmethod def turn(self) -> Piece: ... @abstractmethod def move(self, location: Move) -> Board: ... @property @abstractmethod def legal_moves(self) -> List[Move]: ... @property @abstractmethod def is_win(self) -> bool: ... @property def is_draw(self) -> bool: return (not self.is_win) and (len(self.legal_moves) == 0) @abstractmethod def evaluate(self, player: Piece) -> float: ...
Тип Move будет представлять ход в игре. По сути, это просто целое число. В таких играх, как крестики-нолики и Connect Four, целое число может описывать ход, определяя квадрат или столбец, в котором должна быть размещена фигура. Piece - это базовый класс для фигуры на доске в игре. Его другая роль - индикатор хода. Именно для этого необходимо свойство opposite. Нам нужно знать, чей ход следует за текущим ходом.
Абстрактный базовый класс Board является фактическим хранилищем состояния. Для любой конкретной игры, которую будут выполнять алгоритмы поиска, они должны уметь ответить на следующие вопросы.
Последний вопрос, касающийся ничьих, на самом деле является комбинацией двух предыдущих вопросов для многих игр. Если игра не выиграна, но возможных ходов нет, то это ничья. Вот почему в абстрактном базовом классе Game сразу можно создать конкретную реализацию свойства is_draw. Кроме того, есть еще несколько действий, которые необходимо реализовать.
Каждый метод и свойство класса Board являются реализацией одного из предыдущих вопросов или действий. На языке игры класс Board можно было бы назвать Position, но мы используем это имя для чего-то более конкретного в каждом из подклассов.
Со следующего шага мы начнем реализовывать игру "крестики-нолики".