Xiangiqgame
AI engine for Xiangqi
Loading...
Searching...
No Matches
command_input.py
Go to the documentation of this file.
1"""
2Contains classes for collecting command line input, and converting to a form
3that will be convenient for instantiating all of the objects needed to play a
4Game.
5"""
6
7import argparse
8import random
9from dataclasses import dataclass
10from typing import Any, Literal
11
12import xiangqi_bindings as xb
13
14from xiangqipy.enums import EvaluatorType, PlayerType
15
16
17@dataclass
19 color: xb.PieceColor
20 player_type: Literal[None, "ai", "person"]
21 algo: Literal[None, "minimax", "random"]
22 strength: int = None
23 key_size: int = None
24 number_zobrist_states: int = None
25 zkeys_seed: int = None
26
27 @property
28 def evaluator_type(self) -> xb.EvaluatorType:
29 if self.player_type in [None, "ai"]:
30 if self.algoalgo in ["minimax", None]:
31 return xb.EvaluatorType.kMinimax
32 if self.algoalgo == "random":
33 return xb.EvaluatorType.kRandom
34 else:
35 return xb.EvaluatorType.kHuman
36
37 @property
38 def minimax_search_depth(self) -> int:
40 xb.EvaluatorType.kRandom,
41 xb.EvaluatorType.kHuman,
42 ]:
43 return 0
44 elif self.evaluator_typeevaluator_type == xb.EvaluatorType.kMinimax:
45 if self.strength is None:
46 return 4
47 else:
48 return self.strength
49
50 @property
51 def zobrist_key_size_bits(self) -> int:
53 xb.EvaluatorType.kRandom,
54 xb.EvaluatorType.kHuman,
55 ]:
56 return 0
57 elif self.evaluator_typeevaluator_type == xb.EvaluatorType.kMinimax:
58 return self.key_size if self.key_size else 64
59
60 @property
61 def zobrist_calculator_count(self) -> int:
63 xb.EvaluatorType.kRandom,
64 xb.EvaluatorType.kHuman,
65 ]:
66 return 0
67 elif self.evaluator_typeevaluator_type == xb.EvaluatorType.kMinimax:
68 return (
70 )
71
72 @property
73 def zobrist_seed(self) -> int:
74 if self.zkeys_seed is not None:
75 return self.zkeys_seed
76 else:
77 return random.randint(a=0, b=2**32 -1)
78
79 def to_player_spec(self) -> xb.PlayerSpec:
80 return xb.PlayerSpec(
81 color=self.color,
82 evaluator_type=self.evaluator_typeevaluator_type,
83 zobrist_key_size_bits=self.zobrist_key_size_bits,
84 zobrist_calculator_count=self.zobrist_calculator_count,
85 minimax_search_depth=self.minimax_search_depth,
86 zkeys_seed=self.zobrist_seed,
87 )
88
89
91 run_kwargs: dict[str, Any], color_enum: xb.PieceColor, color_prefix: str
92) -> xb.PlayerSpec:
93 player_info = {"color": color_enum} | {
94 key.removeprefix(color_prefix): val
95 for key, val in run_kwargs.items()
96 if key.startswith(color_prefix)
97 }
98 return RawPlayerInput(**player_info).to_player_spec()
99
100
101def build_game_runner(run_kwargs: dict[str, Any]) -> xb.GameRunner:
102 red_player_spec = build_player_spec(
103 run_kwargs=run_kwargs,
104 color_enum=xb.PieceColor.kRed,
105 color_prefix="red_",
106 )
107 black_player_spec = build_player_spec(
108 run_kwargs=run_kwargs,
109 color_enum=xb.PieceColor.kBlk,
110 color_prefix="black_",
111 )
112
113 game_runner = xb.GameRunner(
114 red_player_spec=red_player_spec, black_player_spec=black_player_spec
115 )
116
117 return game_runner
118
119
120@dataclass
122 save_summary: bool = False
123 output_dir_suffix: str = ""
124
125
126def get_output_file_info(run_kwargs: dict[str, Any]) -> OutputFileInfo:
127 return OutputFileInfo(
128 save_summary=run_kwargs["save_summary"],
129 output_dir_suffix=run_kwargs["output_dir_suffix"],
130 )
131
132
133@dataclass
135 """
136 Container for info collected from command line for specific player.
137 """
138
139 player_type: PlayerType
140 algo: EvaluatorType
141 strength: int
142 key_size: int
143 num_zobrist_states: int
144 zkeys_seed: int | None = None
145
146
148 """
149 Converts command line input related to a player into PlayerInput object.
150 """
151
152 _player_input_dispatch = {
153 "ai": PlayerType.AI,
154 "person": PlayerType.HUMAN,
155 None: PlayerType.AI,
156 }
157
158 _minimax_key_size_dispatch = {
159 32: xb.MinimaxMoveEvaluator32,
160 64: xb.MinimaxMoveEvaluator64,
161 128: xb.MinimaxMoveEvaluator128,
162 }
163
164 _default_key_size = 64
165
166 _default_strength = 4
167
168 _default_number_zobrist_states = 1
169
171 self,
172 player_input: str,
173 algo_input: str,
174 strength_input: int,
175 key_size_input: int,
176 zkeys_seed: int,
177 number_zobrist_states: int,
178 ):
179 self.player_input = player_input
180 self.algo_input = algo_input
181 self.strength_input = strength_input
182 self.key_size_input = key_size_input
183 self.zkeys_seed = zkeys_seed
184 self.number_zobrist_states = number_zobrist_states
185
186 def _get_key_size(self) -> int:
187 if self.key_size_input is None:
188 return self._default_key_size
189 else:
190 return self.key_size_input
191
192 def _get_strength(self) -> int:
193 if self.strength_input is None:
194 return self._default_strength
195 else:
196 return self.strength_input
197
199 if self.number_zobrist_states is None:
201 else:
202 return self.number_zobrist_states
203
204 def interpret_command(self) -> PlayerInput:
205 player_type = self._player_input_dispatch[self.player_input]
206 if player_type == PlayerType.HUMAN:
207 algo = strength = key_size = num_zobrist_states = None
208 else:
209 if self.algo_input == "random":
210 algo = EvaluatorType.RANDOM
211 strength = key_size = num_zobrist_states = None
212 else:
213 key_size = self._get_key_size()
214 algo = EvaluatorType.MINIMAX
215 strength = self._get_strength()
216 num_zobrist_states = self._get_number_zobrist_states()
217 return PlayerInput(
218 player_type=player_type,
219 algo=algo,
220 strength=strength,
221 key_size=key_size,
222 zkeys_seed=self.zkeys_seed,
223 num_zobrist_states=num_zobrist_states,
224 )
225
226
227@dataclass
229 """
230 Data container with all the info needed to instantiate a Game.
231 """
232
233 red_player_input: PlayerInput
234 black_player_input: PlayerInput
235 save_summary: bool = False
236 output_dir_suffix: str = ""
237
238
239@dataclass
241 """
242 Converts dictionary output by XiangqiGameCommandLine into a
243 XiangqiGameCommand.
244 """
245
246 run_kwargs: dict[str, Any]
247
248 def interpret_command(self) -> XiangqiGameCommand:
249 red_interpreter = PlayerCommandInterpreter(
250 player_input=self.run_kwargs["red_player_type"],
251 algo_input=self.run_kwargs["red_algo"],
252 strength_input=self.run_kwargs["red_strength"],
253 key_size_input=self.run_kwargs["red_key_size"],
254 zkeys_seed=self.run_kwargs["red_zkeys_seed"],
255 number_zobrist_states=self.run_kwargs["red_number_zobrist_states"],
256 )
257 black_interpreter = PlayerCommandInterpreter(
258 player_input=self.run_kwargs["black_player_type"],
259 algo_input=self.run_kwargs["black_algo"],
260 strength_input=self.run_kwargs["black_strength"],
261 key_size_input=self.run_kwargs["black_key_size"],
262 zkeys_seed=self.run_kwargs["black_zkeys_seed"],
263 number_zobrist_states=self.run_kwargs[
264 "black_number_zobrist_states"
265 ],
266 )
267
268 return XiangqiGameCommand(
269 red_player_input=red_interpreter.interpret_command(),
270 black_player_input=black_interpreter.interpret_command(),
271 save_summary=self.run_kwargs["save_summary"],
272 output_dir_suffix=self.run_kwargs["output_dir_suffix"],
273 )
274
275
277 """
278 Collects info from command line args out outputs as a dictionary.
279
280 Includes default values for any items not specified in command line args.
281 """
282
283 def __init__(self):
284 self._parser = argparse.ArgumentParser(
285 prog="play_xiangqi",
286 description="A Xiangqi (a.k.a. Chinese Chess) game that can be "
287 "played as Human vs. Human, AI vs. AI, or Human vs. AI",
288 epilog="Note: If all default parameters are used, both players will be AI and "
289 "use Minimax with search depth = 4, and 64 bit Zobrist keys.",
290 )
291
292 def _attach_args(self):
293 self._parser.add_argument(
294 "-rt",
295 "--red_player_type",
296 choices=["person", "ai"],
297 help="Can be 'person', or 'ai'. Default is 'ai'.",
298 )
299
300 self._parser.add_argument(
301 "-ra",
302 "--red_algo",
303 choices=["random", "minimax"],
304 required=False,
305 help="Search algorithm to use for red player (if player type is "
306 "'ai'). Can be 'random' or 'minimax'. Default is minimax.",
307 )
308
309 self._parser.add_argument(
310 "-rst",
311 "--red_strength",
312 type=int,
313 choices=range(1, 10),
314 required=False,
315 help="Search depth to user for red AI player with minimax algo"
316 "Default is 4.",
317 )
318
319 self._parser.add_argument(
320 "-rk",
321 "--red_key_size",
322 type=int,
323 choices=[32, 64, 128],
324 required=False,
325 help="Key size (in bits) used for red AI player Zobrist hashing",
326 )
327
328 self._parser.add_argument(
329 "-rn",
330 "--red_number_zobrist_states",
331 type=int,
332 choices=[1, 2],
333 required=False,
334 help="Number of Zobrist state values to maintain for red.",
335 )
336
337 self._parser.add_argument(
338 "-rz",
339 "--red_zkeys_seed",
340 type=int,
341 required=False,
342 help="Seed for red player Zobrist Keys generator. "
343 "32-bit unsigned int.",
344 )
345
346 self._parser.add_argument(
347 "-bt",
348 "--black_player_type",
349 choices=["person", "ai"],
350 help="Can be 'person', or 'ai'. Default is 'ai'.",
351 )
352
353 self._parser.add_argument(
354 "-ba",
355 "--black_algo",
356 choices=["random", "minimax"],
357 required=False,
358 help="Search depth to user for black AI player with minimax algo"
359 "Default is 4.",
360 )
361
362 self._parser.add_argument(
363 "-bst",
364 "--black_strength",
365 type=int,
366 choices=range(1, 10),
367 required=False,
368 help="Search depth to user for red player when red is 'ai' with "
369 "'minimax.' Default is 4.",
370 )
371
372 self._parser.add_argument(
373 "-bk",
374 "--black_key_size",
375 type=int,
376 choices=[32, 64, 128],
377 required=False,
378 help="Key size (in bits) used for black AI player Zobrist hashing",
379 )
380
381 self._parser.add_argument(
382 "-bn",
383 "--black_number_zobrist_states",
384 type=int,
385 choices=[1, 2],
386 required=False,
387 help="Number of Zobrist state values to maintain for black.",
388 )
389
390 self._parser.add_argument(
391 "-bz",
392 "--black_zkeys_seed",
393 type=int,
394 required=False,
395 help="Seed for black player Zobrist Keys generator. "
396 "32-bit unsigned int.",
397 )
398
399 self._parser.add_argument(
400 "-s",
401 "--save_summary",
402 action="store_true",
403 help="Save GameSummary as .json",
404 )
405
406 self._parser.add_argument(
407 "-d",
408 "--output_dir_suffix",
409 type=str,
410 required=False,
411 help="String to append to end of output directory name. Output dir "
412 "relative to cwd will be "
413 "./data/game_summaries/<timestamp><optional-suffix>",
414 )
415
416 def get_args(self) -> dict[str, Any]:
417 self._attach_args()
418 args_namespace = self._parser.parse_args()
419 return vars(args_namespace)
420
421
422def main() -> dict[str, Any]:
423 command_retriever = XiangqiGameCommandLine()
424 my_command = command_retriever.get_args()
425 return my_command
426
427
428if __name__ == "__main__":
429 result = main()
430 print(result)
Converts command line input related to a player into PlayerInput object.
def __init__(self, str player_input, str algo_input, int strength_input, int key_size_input, int zkeys_seed, int number_zobrist_states)
Container for info collected from command line for specific player.
xb.EvaluatorType evaluator_type(self)
Converts dictionary output by XiangqiGameCommandLine into a XiangqiGameCommand.
Collects info from command line args out outputs as a dictionary.
Data container with all the info needed to instantiate a Game.
dict[str, Any] main()
xb.PlayerSpec build_player_spec(dict[str, Any] run_kwargs, xb.PieceColor color_enum, str color_prefix)
Enums that are only used on the Python side of the app.
Definition: enums.py:1