From bb51bf45966c053abb93094ed69a13c397469b26 Mon Sep 17 00:00:00 2001 From: Casper Guo Date: Sat, 18 May 2024 13:00:44 -0400 Subject: [PATCH] progress --- royal_game/_exceptions.py | 8 ++++++- royal_game/modules/board.py | 20 ++++++++++++++++- royal_game/modules/game.py | 45 ++++++++++++++++++++++++++++++++++--- royal_game/modules/grid.py | 2 +- royal_game/modules/move.py | 18 ++++++++++++++- 5 files changed, 86 insertions(+), 7 deletions(-) diff --git a/royal_game/_exceptions.py b/royal_game/_exceptions.py index 6e858e4..20e3343 100644 --- a/royal_game/_exceptions.py +++ b/royal_game/_exceptions.py @@ -39,6 +39,12 @@ def __init__(self, player) -> None: class MoveError(Exception): pass +# ImpossibleMove denotes a move that is invalid in any circumstances +# InvalidMove denotes a move that is invalid on a specific board class ImpossibleMove(MoveError): def __init__(self) -> None: - super().__init__(f"The requested move is impossible.") \ No newline at end of file + super().__init__("The requested move is impossible.") + +class InvalidMove(MoveError): + def __init__(self) -> None: + super().__init__("This move is invalid.") \ No newline at end of file diff --git a/royal_game/modules/board.py b/royal_game/modules/board.py index 13882e8..6693cce 100644 --- a/royal_game/modules/board.py +++ b/royal_game/modules/board.py @@ -17,9 +17,10 @@ start_end_iter, white_iter, ) -from royal_game._exceptions import InvalidNumberofPieces +from royal_game._exceptions import InvalidMove, InvalidNumberofPieces from royal_game.modules.grid import Grid, StartEndGrid from royal_game.modules.grid_status import GridStatus +from royal_game.modules.move import Move class Board: @@ -159,3 +160,20 @@ def is_end_state(self): 1, Get available moves on the board, based on the dice roll 2, Execute a move and modify the board, including validity check """ + + def get_available_moves(self, white_turn: bool, dice_roll: int) -> tuple[Move]: + """Return a tuple of valid moves.""" + pass + + def make_move(self, move: Move) -> None: + """ + Modify the board based on the board. + + Limited validity check implemented. + """ + if move.is_onboard: + if self.board[move.grid2].status != GridStatus.empty: + raise InvalidMove + else: + if self.board[move.grid1].status == self.board[move.grid2].status: + raise InvalidMove diff --git a/royal_game/modules/game.py b/royal_game/modules/game.py index f762327..f9ed3c5 100644 --- a/royal_game/modules/game.py +++ b/royal_game/modules/game.py @@ -1,9 +1,15 @@ """Implements game loop.""" -from royal_game._exceptions import InvalidPlayer +import logging +from random import choices + +from royal_game._exceptions import InvalidMove, InvalidPlayer from royal_game.modules.board import Board from royal_game.modules.player import Player +logging.basicConfig(filename="games.log", filemode="w", format="%(levelname)s: %(message)s") +logger = logging.getLogger(__name__) + class Game: """ @@ -25,7 +31,7 @@ def __init__(self, player1: Player, player2: Player): self.board = Board() self.white_turn = True - def play(self) -> None: + def play(self) -> bool: """ Implement the game loop. @@ -34,6 +40,39 @@ def play(self) -> None: 3, player returns selected move 4, update board 5, determine who has the next turn + + Return true if white wins, and vice versa """ + # pre-game output, player names etc. + while not self.board.is_end_state(): - pass + dice_roll = choices( + [0, 1, 2, 3, 4], weights=[1 / 16, 1 / 4, 3 / 8, 1 / 4, 1 / 16], k=1 + ) + + if dice_roll == 0: + logger.info( + "%s rolled a zero. The turn is automatically passed", + self.player1 if self.white_turn else self.player2, + ) + self.white_turn = not self.white_turn + continue + + available_moves = self.board.get_available_moves(self.white_turn, dice_roll) + move_selected = ( + self.player1.select_move(self.board, available_moves) + if self.white_turn + else self.player2(self.board, available_moves) + ) + + try: + logger.info(move_selected) + self.board.make_move(move_selected) + except InvalidMove as e: + logger.critical(move_selected) + raise e + + if not move_selected.is_rosette: + self.white_turn = not self.white_turn + + # end of game output, results + any metadata diff --git a/royal_game/modules/grid.py b/royal_game/modules/grid.py index 91f3b18..2aa4ce9 100644 --- a/royal_game/modules/grid.py +++ b/royal_game/modules/grid.py @@ -11,9 +11,9 @@ class Grid: def __init__( self, name: str, is_rosette: bool, status: GridStatus = GridStatus.empty ) -> None: + self.status = status self.name = name self.is_rosette = is_rosette - self.status = status def __str__(self) -> str: symbol = " " diff --git a/royal_game/modules/move.py b/royal_game/modules/move.py index 439a3c6..4377d9a 100644 --- a/royal_game/modules/move.py +++ b/royal_game/modules/move.py @@ -8,17 +8,30 @@ class Move: """Move representation and benchmarking metadata.""" def __init__( - self, grid1: str, grid2: str, is_rosette: bool, is_capture: bool, is_ascension: bool + self, + grid1: str, + grid2: str, + is_rosette: bool, + is_capture: bool, + is_ascension: bool, + is_onboard: bool, ) -> None: self.grid1 = grid1 self.grid2 = grid2 self.is_rosette = is_rosette self.is_capture = is_capture self.is_ascension = is_ascension + self.is_onboard = is_onboard try: if int(is_rosette) + int(is_capture) + int(is_ascension) > 1: raise ImpossibleMove + if self.grid1 == self.grid2: + raise ImpossibleMove + if (self.grid1[0] == "W" and self.grid2[0] == "B") or ( + self.grid1[0] == "B" and self.grid2[0] == "W" + ): + raise ImpossibleMove except ImpossibleMove as e: print(self) raise e @@ -36,6 +49,9 @@ def __repr__(self) -> str: f"{capture if self.is_capture else ""}{ascension if self.is_ascension else ""}" ) + # It would be a nice QoL improvement to implement a full partial order + # so moves can be reasonably sorted + # Not urgent due to minimal player interaction and need for nice interface def __eq__(self, other: object) -> bool: if not isinstance(other, Move): return NotImplemented