Xiangiqgame
AI engine for Xiangqi
Loading...
Searching...
No Matches
game.py
Go to the documentation of this file.
1"""
2Contains Game class.
3"""
4
5from datetime import datetime
6from typing import Dict, List
7
8import xiangqi_bindings as bindings
9
11import xiangqipy.terminal_output as msg
12from xiangqipy.enums import GameState
13from xiangqipy.game_interfaces import MoveReporter, Player
14from xiangqipy.game_summary import GameSummary, PlayerSummaries
15from xiangqipy.handlers.errors import handle_interactive_eof
16
17
18class Game:
19 """
20 Runs a game between two Players.
21
22 Tracks Game state, collects proposed moves, alternates Player turns,
23 maintains on gong GameSummary, and sends info to terminal.
24 """
26 self,
27 players: Dict[bindings.PieceColor, Player],
28 game_board: bindings.GameBoard,
29 status_reporter: MoveReporter = msg.TerminalStatusReporter(),
30 move_log: List[bindings.Move] = None,
31 ):
32 self._game_state = GameState.UNFINISHED
33 self._whose_turn = bindings.PieceColor.kRed
34 self._board = game_board
35 self._players = players
36 self._status_reporter = status_reporter
37 if move_log is None:
38 move_log = []
39 self._move_log = move_log
40 self._game_id = "".join(
41 char for char in str(datetime.now()) if char.isdigit()
42 )
43
44 @property
45 def summary(self) -> GameSummary:
46 return GameSummary(
47 game_id=self._game_id,
48 game_state=self._game_state,
49 whose_turn=self._whose_turn,
50 move_log=[
51 cdm.ExecutedMove.from_core_executed_move(
52 core_executed_move=item
53 )
54 for item in self._move_log
55 ],
56 player_summaries=PlayerSummaries(
57 kRed=self._players[bindings.PieceColor.kRed].summary,
58 kBlk=self._players[bindings.PieceColor.kBlk].summary,
59 ),
60 )
61
62 @property
63 def _move_count(self):
64 return len(self._move_log)
65
67 self._whose_turn = bindings.opponent_of(self._whose_turn)
68
69 @staticmethod
70 def is_valid_move(proposed_move: bindings.Move, avail_moves: List[bindings.Move]):
71 return proposed_move in avail_moves
72
73 def get_valid_move(self, avail_moves: bindings.MoveCollection):
74 valid_move = None
75 while not valid_move:
76 proposed_move = self._players[self._whose_turn].propose_move(
77 self._board, cur_moves=avail_moves
78 )
79 if avail_moves.ContainsMove(proposed_move):
80 valid_move = proposed_move
81 else:
82 self._players[self._whose_turn].illegal_move_notice_response(
83 illegal_move=proposed_move,
84 game_board=self._board,
85 cur_moves=avail_moves,
86 )
87 return valid_move
88
89 def player_turn(self, avail_moves: bindings.MoveCollection):
90 """
91 Selects and executes a move.
92
93 @param avail_moves: a bindings.MoveCollection of legal moves
94 """
95 try:
96 valid_move = self.get_valid_move(avail_moves=avail_moves)
97 except EOFError:
98 handle_interactive_eof()
99 executed_move = self._board.ExecuteMove(valid_move)
100 self._move_log.append(executed_move)
101
102 def set_game_state(self, game_state: GameState):
103 self._game_state = game_state
104
105 def set_winner(self, color: int):
106 if color == bindings.PieceColor.kRed:
107 self.set_game_state(GameState.RED_WON)
108 else:
109 self.set_game_state(GameState.BLACK_WON)
110
112 if self._move_log:
113 prev_move = self._move_log[-1].spaces
114 else:
115 prev_move = None
116 self._status_reporter.report_game_info(
117 red_player_summary=self._players[bindings.PieceColor.kRed].summary,
118 black_player_summary=self._players[bindings.PieceColor.kBlk].summary,
119 game_state=self._game_state,
120 game_board=self._board,
121 whose_turn=self._whose_turn,
122 is_in_check=self._board.IsInCheck(self._whose_turn),
123 move_count=self._move_count,
124 prev_move=prev_move,
125 )
126
127 def play(self) -> GameSummary:
128 while self._game_state == GameState.UNFINISHED:
130 avail_moves = self._board.CalcFinalMovesOf(self._whose_turn)
131 if avail_moves.size() == 0:
132 if self._board.is_draw:
133 self.set_game_state(GameState.DRAW)
134 else:
135 self.set_winner(bindings.opponent_of(self._whose_turn))
136 break
137 self.player_turn(avail_moves=avail_moves)
138 self.change_whose_turn()
139
141 return self.summary
Runs a game between two Players.
Definition: game.py:18
def set_game_state(self, GameState game_state)
Definition: game.py:102
def send_game_info_to_status_reporter(self)
Definition: game.py:111
def __init__(self, Dict[bindings.PieceColor, Player] players, bindings.GameBoard game_board, MoveReporter status_reporter=msg.TerminalStatusReporter(), List[bindings.Move] move_log=None)
Definition: game.py:31
def is_valid_move(bindings.Move proposed_move, List[bindings.Move] avail_moves)
Definition: game.py:70
def get_valid_move(self, bindings.MoveCollection avail_moves)
Definition: game.py:73
def _move_count(self)
Definition: game.py:63
GameSummary summary(self)
Definition: game.py:45
def player_turn(self, bindings.MoveCollection avail_moves)
Selects and executes a move.
Definition: game.py:89
GameSummary play(self)
Definition: game.py:127
def set_winner(self, int color)
Definition: game.py:105
def change_whose_turn(self)
Definition: game.py:66
Holds summary info of a xiangqipy.game.Game; implements msgspec.Struct for json IO.
Definition: game_summary.py:30
A data container for holding one PlayerSummary for each player in a Game.
Definition: game_summary.py:18
Contains classes that mirror the structure of some core C++ classes, primarily to facilitate easy IO ...
Enums that are only used on the Python side of the app.
Definition: enums.py:1
Python abstract classes used by a Game.
GameSummary class and its component classes.
Definition: game_summary.py:1
Handles case where ScriptedPlayer has no moves remaining in list but Game is not finished.
Definition: errors.py:1
Classes for terminal UI output including board representation and, messages requesting info,...