Source code for engine.sub_board

from copy import deepcopy

from .cell import Cell
from .errors import MoveOutsideSubBoardError, MoveInPlayedCellError, \
    MoveInFinishedBoardError, BoardNotFinishedError
from .gameplay import Player, SubBoardCoords
from .gameplay import did_move_win


[docs]class SubBoard(object): """A single game of TicTacToe (not ultimate). Several of these make up the Ultimate TTT game. Instances of this class behave in a functional manner, with no method call modifying the state of the original object. State changing operations (such as :code:`add_my_move`) return a new SubBoard object, which the calling function must replace. The returned SubBoard object has all properties (e.g. is_finished) calculated. .. highlight:: python Example: :: SubBoard(3) #Initialises a board of size 3 .add_my_move(SubBoardCoords(1, 1)) #Adds a move at 1, 1 and returns a SubBoard .add_opponent_move(SubBoardCoords(2, 1)) #Adds a move to the last returned board Call :code:`str(SubBoard())` to get a pretty-printed representation of this board Todo: * Use a :code:`@classmethod` to initialize SubBoard and make it immutable internally """ def __init__(self, board_size: int = 3): if not board_size == 3: raise ValueError("Size must be integer of size 3 (for now)") self._board_size = board_size self._board = [ [Cell() for board_col in range(board_size)] for board_row in range(board_size) ] self._max_moves = board_size * board_size self._moves_so_far = 0 self._is_finished = False self._winner = Player.NONE @property def is_finished(self) -> bool: """Whether the board is finished (tied, won or lost)""" return self._is_finished @property def winner(self) -> Player: """The winner of the board if finished. Exception otherwise""" if not self._is_finished: raise BoardNotFinishedError return self._winner
[docs] def add_my_move(self, sub_board_coords: SubBoardCoords) -> 'SubBoard': """Adds a move for the current player Args: sub_board_coords: Move co-ordinates Returns: A new SubBoard instance with the move applied """ return self.add_move(sub_board_coords, Player.ME)
[docs] def add_opponent_move(self, sub_board_coords: SubBoardCoords) -> 'SubBoard': """Adds a move for the opponent Args: sub_board_coords: Move co-ordinates Returns: A new SubBoard instance with the move applied """ return self.add_move(sub_board_coords, Player.OPPONENT)
def __str__(self): """Returns a pretty printed representation of this board""" pretty_printed = '' for row in self._board: for cell in row: pretty_printed += str(cell) + ' ' pretty_printed += '\n' return pretty_printed
[docs] def add_move(self, sub_board_coords: SubBoardCoords, player: Player) -> 'SubBoard': """Adds a move by a ultimate_ttt_player to a deep copy of the board, returning the copy Player may find it easier to use the :func:`~add_my_move` and :func:`~add_opponent_move` functions Args: sub_board_coords: The co-ordinates to make a move in on this sub-board player: The player that made the move Returns: A new SubBoard instance with the move applied and all properties calculated """ if self.is_finished: raise MoveInFinishedBoardError(sub_board_coords, player) if not (self._is_move_in_bounds(sub_board_coords)): raise MoveOutsideSubBoardError(sub_board_coords) if self._is_move_already_played(sub_board_coords): raise MoveInPlayedCellError(player, sub_board_coords) # Copy the board so we can update it # Maybe this should all go in the constructor/classmethod updated_sub_board = deepcopy(self) updated_sub_board._board[sub_board_coords.row][sub_board_coords.col] = Cell(player) updated_sub_board._moves_so_far += 1 if did_move_win(updated_sub_board._board, sub_board_coords, player): updated_sub_board._is_finished = True updated_sub_board._winner = player elif updated_sub_board._moves_so_far == updated_sub_board._max_moves: updated_sub_board._is_finished = True return updated_sub_board
[docs] def get_playable_coords(self) -> [SubBoardCoords]: """ Returns: All valid SubBoardCoords that can be played (have not been played Empty if board is finished. """ if self.is_finished: return [] valid_coords = [] for row_index in range(0, self._board_size): for col_index in range(0, self._board_size): if not self._board[row_index][col_index].is_played(): valid_coords.append(SubBoardCoords(row_index, col_index)) return valid_coords
# Private functions def _is_move_in_bounds(self, sub_board_coords: SubBoardCoords) -> bool: """Checks whether the given move is inside the boundaries of this board Args: sub_board_coords: The intended move Returns: True if the move is within the bounds of this board, False otherwise """ if 0 <= sub_board_coords.row < len(self._board) and 0 <= sub_board_coords.col < len(self._board): return True return False def _is_move_already_played(self, sub_board_coords: SubBoardCoords) -> bool: """Checks whether the given move is already played in this board Args: sub_board_coords: The intended move Returns: True if the cell referenced by the move is already played, False otherwise """ return self._board[sub_board_coords.row][sub_board_coords.col].is_played() def __iter__(self): return self._board.__iter__() def __getitem__(self, key): return self._board[key]