play-ch0 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ch0/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """ch0 package."""
ch0/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
ch0/cli.py ADDED
@@ -0,0 +1,416 @@
1
+ #!/usr/bin/env -S uv run
2
+ import random
3
+ import subprocess
4
+ import io
5
+ import os
6
+ import shlex
7
+ from datetime import date
8
+
9
+ import chess
10
+ import chess.pgn
11
+ import chess.polyglot
12
+ import chess.engine
13
+
14
+ from .engines.andoma.movegeneration import next_move as andoma_gen
15
+ from .engines.sunfish import sunfish_uci
16
+ from .engines.sunfish.tools import uci
17
+
18
+
19
+ # --- Colors (ANSI) ------------------------------------------------------------
20
+ # Works in most terminals. On Windows, ANSI is supported in modern terminals;
21
+ # if you need broader support, install `colorama` and it will be used if present.
22
+ try:
23
+ import colorama # type: ignore
24
+ colorama.just_fix_windows_console()
25
+ except Exception:
26
+ pass
27
+
28
+
29
+ class Style:
30
+ RESET = "\033[0m"
31
+ BOLD = "\033[1m"
32
+ DIM = "\033[2m"
33
+
34
+ RED = "\033[31m"
35
+ GREEN = "\033[32m"
36
+ YELLOW = "\033[33m"
37
+ BLUE = "\033[34m"
38
+ MAGENTA = "\033[35m"
39
+ CYAN = "\033[36m"
40
+
41
+
42
+ def c(text: str, *styles: str) -> str:
43
+ return "".join(styles) + text + Style.RESET
44
+
45
+
46
+ # --- Game logic --------------------------------------------------------------
47
+ class Game:
48
+ def __init__(
49
+ self,
50
+ engine_kind: str,
51
+ engine_name: str,
52
+ player_color: chess.Color,
53
+ engine: chess.engine.SimpleEngine | None = None,
54
+ ):
55
+ self.board = chess.Board()
56
+ self.engine_kind = engine_kind # "random", "andoma", "sunfish", "uci"
57
+ self.engine_name = engine_name
58
+ self.player_color = player_color
59
+ self.engine = engine
60
+ self.turn = chess.WHITE # whose turn it is to move in our bookkeeping
61
+ self.count = 0 # move number (full moves)
62
+ self.pgn_text = ""
63
+ self.ended = False
64
+
65
+ def reset(self):
66
+ self.board.set_fen(chess.STARTING_FEN)
67
+ self.turn = chess.WHITE
68
+ self.count = 0
69
+ self.pgn_text = ""
70
+ self.ended = False
71
+
72
+ def close_engine(self):
73
+ if self.engine is None:
74
+ return
75
+ try:
76
+ self.engine.quit()
77
+ except Exception:
78
+ try:
79
+ self.engine.close()
80
+ except Exception:
81
+ pass
82
+ self.engine = None
83
+
84
+
85
+ def is_a_draw(board: chess.Board):
86
+ """Return (is_draw, message)."""
87
+ if board.is_stalemate():
88
+ return True, "Stalemate"
89
+ elif board.is_insufficient_material():
90
+ return True, "Insufficient Material"
91
+ elif board.can_claim_fifty_moves():
92
+ return True, "Fifty-move rule"
93
+ elif board.can_claim_threefold_repetition():
94
+ return True, "Threefold repetition"
95
+ return False, ""
96
+
97
+
98
+ def bool_color_to_string(color_b: chess.Color) -> str:
99
+ return "white" if color_b == chess.WHITE else "black"
100
+
101
+
102
+ def finalize_pgn(pgn_str: str, player_color: chess.Color, engine_name: str):
103
+ final_pgn = pgn_str + "\n\n"
104
+ game_pgn = chess.pgn.read_game(io.StringIO(final_pgn))
105
+ game_pgn.headers["Event"] = "Blind-chess match"
106
+ game_pgn.headers["Site"] = "Terminal"
107
+ if player_color == chess.WHITE:
108
+ game_pgn.headers["White"] = "Me"
109
+ game_pgn.headers["Black"] = f"{engine_name} Bot"
110
+ else:
111
+ game_pgn.headers["White"] = f"{engine_name} Bot"
112
+ game_pgn.headers["Black"] = "Me"
113
+ game_pgn.headers["Date"] = date.today().isoformat()
114
+ return game_pgn
115
+
116
+
117
+ def bot_makes_a_move(game: Game):
118
+ board = game.board
119
+ move = random.choice(list(board.legal_moves))
120
+
121
+ if game.engine_kind == "andoma":
122
+ move = andoma_gen(depth=4, board=board, debug=False)
123
+ elif game.engine_kind == "sunfish":
124
+ position = uci.from_fen(*board.fen().split())
125
+ current_hist = (
126
+ [position]
127
+ if uci.get_color(position) == uci.WHITE
128
+ else [position.rotate(), position]
129
+ )
130
+ total_time = random.randint(10, 60)
131
+ _, uci_move_str = sunfish_uci.generate_move(current_hist, total_time)
132
+ move = chess.Move.from_uci(uci_move_str)
133
+ elif game.engine_kind == "uci":
134
+ if game.engine is None:
135
+ raise RuntimeError("UCI engine is not initialized.")
136
+ think_time = random.uniform(0.1, 0.5)
137
+ result = game.engine.play(board, chess.engine.Limit(time=think_time))
138
+ move = result.move
139
+
140
+ # optional opening book
141
+ coin = random.randint(0, 1)
142
+ if game.count < 15 and coin == 1:
143
+ try:
144
+ with chess.polyglot.open_reader("book.bin") as reader:
145
+ move = reader.weighted_choice(board).move
146
+ except (IndexError, FileNotFoundError):
147
+ pass
148
+
149
+ move_san = board.san(move)
150
+ board.push(move)
151
+
152
+ if game.turn == chess.WHITE:
153
+ game.count += 1
154
+ game.pgn_text += f"\n{game.count}. {move_san}"
155
+ else:
156
+ game.pgn_text += f" {move_san}"
157
+
158
+ # Minimal engine output (colored, no label)
159
+ print(c(move_san, Style.MAGENTA, Style.BOLD))
160
+
161
+ # draw / checkmate handling
162
+ check_draw, draw_type = is_a_draw(board)
163
+ if check_draw:
164
+ print(c(f"Draw: {draw_type}", Style.YELLOW, Style.BOLD))
165
+ game.pgn_text += " { The game is a draw. } 1/2-1/2"
166
+ game.ended = True
167
+ return
168
+
169
+ if board.is_checkmate():
170
+ print(c("Checkmate.", Style.RED, Style.BOLD))
171
+ result = "0-1" if game.player_color == chess.WHITE else "1-0"
172
+ game.pgn_text += (
173
+ f" {{ {bool_color_to_string(not game.player_color)} wins by checkmate. }} "
174
+ f"{result}"
175
+ )
176
+ game.ended = True
177
+ return
178
+
179
+ game.turn = not game.turn
180
+
181
+
182
+ def _engine_display_name(command: str, engine: chess.engine.SimpleEngine) -> str:
183
+ name = engine.id.get("name")
184
+ if name:
185
+ return name
186
+ return os.path.basename(command.split()[0]) or "UCI"
187
+
188
+
189
+ def _spawn_uci_engine(command: str) -> chess.engine.SimpleEngine | None:
190
+ cmd = shlex.split(command)
191
+ if not cmd:
192
+ return None
193
+ try:
194
+ return chess.engine.SimpleEngine.popen_uci(cmd, stderr=subprocess.DEVNULL)
195
+ except (FileNotFoundError, PermissionError, OSError, chess.engine.EngineError):
196
+ return None
197
+
198
+
199
+ def choose_engine() -> tuple[str, str, chess.engine.SimpleEngine | None]:
200
+ options = ["random", "andoma", "sunfish", "uci"]
201
+ print(c("Choose engine:", Style.CYAN, Style.BOLD))
202
+ for i, name in enumerate(options, start=1):
203
+ print(f" {c(str(i) + '.', Style.DIM)} {c(name, Style.CYAN)}")
204
+ while True:
205
+ choice = input(c("engine> ", Style.DIM)).strip().lower()
206
+ if choice in {"1", "2", "3", "4"}:
207
+ choice = options[int(choice) - 1]
208
+ if choice in options:
209
+ if choice != "uci":
210
+ return choice, choice, None
211
+ while True:
212
+ cmd = input(c("uci engine path/command> ", Style.DIM)).strip()
213
+ engine = _spawn_uci_engine(cmd)
214
+ if engine is not None:
215
+ display_name = _engine_display_name(cmd, engine)
216
+ return "uci", display_name, engine
217
+ print(c("Could not start engine. Try again.", Style.RED))
218
+ print(c("Invalid choice.", Style.RED))
219
+
220
+
221
+ def choose_color():
222
+ print(c("Choose your color:", Style.CYAN, Style.BOLD))
223
+ print(f" {c('1.', Style.DIM)} {c('white', Style.CYAN)}")
224
+ print(f" {c('2.', Style.DIM)} {c('black', Style.CYAN)}")
225
+ print(f" {c('3.', Style.DIM)} {c('random', Style.CYAN)}")
226
+ while True:
227
+ choice = input(c("color> ", Style.DIM)).strip().lower()
228
+ if choice in {"1", "white"}:
229
+ return chess.WHITE
230
+ if choice in {"2", "black"}:
231
+ return chess.BLACK
232
+ if choice in {"3", "random"}:
233
+ return random.choice([chess.WHITE, chess.BLACK])
234
+ print(c("Invalid choice.", Style.RED))
235
+
236
+
237
+ def print_help():
238
+ print(c("Lobby:", Style.CYAN, Style.BOLD))
239
+ print(f" {c('start', Style.CYAN)} start a new game")
240
+ print(f" {c('help', Style.CYAN)} show this help")
241
+ print(f" {c('quit', Style.CYAN)} quit")
242
+ print()
243
+ print(c("In-game:", Style.CYAN, Style.BOLD))
244
+ print(f" {c('show', Style.CYAN)} show the board")
245
+ print(f" {c('moves', Style.CYAN)} show legal moves (SAN)")
246
+ print(f" {c('fen', Style.CYAN)} show FEN")
247
+ print(f" {c('pgn', Style.CYAN)} show PGN so far")
248
+ print(f" {c('resign', Style.CYAN)} resign the game")
249
+ print()
250
+ print(c("Or type a move in SAN, e.g. e4, Nf3, exd5, a8=Q.", Style.DIM))
251
+
252
+
253
+ def parse_command(s: str):
254
+ """Normalize commands: ':show' and 'show' both become 'show'."""
255
+ s = s.strip()
256
+ if not s:
257
+ return None
258
+ if s.startswith(":"):
259
+ s = s[1:]
260
+ return s.lower()
261
+
262
+
263
+ def ask_yes_no(prompt: str, default_no: bool = True) -> bool:
264
+ suffix = " [y/N]: " if default_no else " [Y/n]: "
265
+ while True:
266
+ ans = input(c(prompt + suffix, Style.DIM)).strip().lower()
267
+ if not ans:
268
+ return not default_no
269
+ if ans in {"y", "yes"}:
270
+ return True
271
+ if ans in {"n", "no"}:
272
+ return False
273
+ print(c("Please answer y or n.", Style.RED))
274
+
275
+
276
+ def main():
277
+ print(c("\nBlindfold Chess\n", Style.BOLD))
278
+ print_help()
279
+ print()
280
+
281
+ game: Game | None = None
282
+
283
+ while True:
284
+ # If a game ended, optionally print PGN, then return to lobby.
285
+ if game is not None and game.ended:
286
+ if game.pgn_text and ask_yes_no("Print final PGN?", default_no=True):
287
+ print()
288
+ print(c("Final PGN:", Style.CYAN, Style.BOLD))
289
+ print(finalize_pgn(game.pgn_text, game.player_color, game.engine_name))
290
+ game.close_engine()
291
+ game = None
292
+ print()
293
+ print(c("Lobby. Type 'start' to play.", Style.DIM))
294
+ continue
295
+
296
+ # No active game: only limited commands work.
297
+ if game is None:
298
+ user_in = input(c("> ", Style.DIM)).strip()
299
+ if not user_in:
300
+ continue
301
+
302
+ cmd = parse_command(user_in)
303
+ if cmd == "start":
304
+ engine_kind, engine_name, engine = choose_engine()
305
+ player_color = choose_color()
306
+ game = Game(engine_kind, engine_name, player_color, engine=engine)
307
+
308
+ print()
309
+ print(
310
+ c("You:", Style.DIM)
311
+ + " "
312
+ + c(bool_color_to_string(player_color), Style.CYAN, Style.BOLD)
313
+ + c(" vs ", Style.DIM)
314
+ + c(engine_name, Style.CYAN, Style.BOLD)
315
+ )
316
+ print(c("Tip: type 'show' to display the board.", Style.DIM))
317
+ print()
318
+
319
+ # If the engine is white, let it move first.
320
+ if player_color == chess.BLACK:
321
+ bot_makes_a_move(game)
322
+ continue
323
+
324
+ if cmd == "help":
325
+ print_help()
326
+ print()
327
+ continue
328
+
329
+ if cmd == "quit":
330
+ print(c("Goodbye.", Style.DIM))
331
+ if game is not None:
332
+ game.close_engine()
333
+ break
334
+
335
+ print(c("No active game. Type 'start' (or 'help', 'quit').", Style.RED))
336
+ continue
337
+
338
+ # If it's engine's turn, just let it move.
339
+ if game.board.turn != game.player_color:
340
+ bot_makes_a_move(game)
341
+ continue
342
+
343
+ user_in = input(c("> ", Style.DIM)).strip()
344
+ if not user_in:
345
+ continue
346
+
347
+ cmd = parse_command(user_in)
348
+
349
+ # Known in-game commands
350
+ if cmd in {"help", "show", "moves", "fen", "pgn", "resign", "quit", "start"}:
351
+ if cmd == "help":
352
+ print_help()
353
+ elif cmd == "show":
354
+ print(game.board)
355
+ elif cmd == "moves":
356
+ moves_in_uci = list(game.board.legal_moves)
357
+ moves_in_san = [game.board.san(m) for m in moves_in_uci]
358
+ print(" ".join(moves_in_san))
359
+ elif cmd == "fen":
360
+ print(game.board.fen())
361
+ elif cmd == "pgn":
362
+ print(finalize_pgn(game.pgn_text, game.player_color, game.engine_name))
363
+ elif cmd == "resign":
364
+ print(c("Resigned.", Style.YELLOW, Style.BOLD))
365
+ result = "0-1" if game.player_color == chess.WHITE else "1-0"
366
+ game.pgn_text += (
367
+ f" {{ {bool_color_to_string(game.player_color)} resigns. }} {result}"
368
+ )
369
+ game.ended = True
370
+ elif cmd == "quit":
371
+ print(c("Goodbye.", Style.DIM))
372
+ game.close_engine()
373
+ break
374
+ elif cmd == "start":
375
+ print(c("Game in progress. Finish or resign first.", Style.RED))
376
+ continue
377
+
378
+ # Otherwise, try to interpret it as a move in SAN
379
+ try:
380
+ game.board.push_san(user_in)
381
+ except ValueError:
382
+ print(c("Illegal move / unknown command.", Style.RED))
383
+ continue
384
+
385
+ # Optional: extremely subtle acknowledgement (comment out if you want *zero* noise)
386
+ # print(c("✓", Style.GREEN, Style.DIM))
387
+
388
+ if game.turn == chess.WHITE:
389
+ game.count += 1
390
+ game.pgn_text += f"\n{game.count}. {user_in}"
391
+ else:
392
+ game.pgn_text += f" {user_in}"
393
+
394
+ check_draw, draw_type = is_a_draw(game.board)
395
+ if check_draw:
396
+ print(c(f"Draw: {draw_type}", Style.YELLOW, Style.BOLD))
397
+ game.pgn_text += " { The game is a draw. } 1/2-1/2"
398
+ game.ended = True
399
+ continue
400
+
401
+ if game.board.is_checkmate():
402
+ print(c("Checkmate. You win.", Style.GREEN, Style.BOLD))
403
+ result = "1-0" if game.player_color == chess.WHITE else "0-1"
404
+ game.pgn_text += (
405
+ f" {{ {bool_color_to_string(game.player_color)} wins by checkmate. }} "
406
+ f"{result}"
407
+ )
408
+ game.ended = True
409
+ continue
410
+
411
+ game.turn = not game.turn
412
+ # engine will move in the next iteration
413
+
414
+
415
+ if __name__ == "__main__":
416
+ main()
@@ -0,0 +1 @@
1
+ """Engine package wrapper for distribution."""
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Andrew Healey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,114 @@
1
+ [![Unit tests and puzzles](https://github.com/healeycodes/andoma/workflows/Unit%20tests%20and%20puzzles/badge.svg)](https://github.com/healeycodes/andoma/actions/workflows/python-app.yml)
2
+
3
+ # ♟ Andoma
4
+ > My blog post: [Building My Own Chess Engine](https://healeycodes.com/building-my-own-chess-engine/)
5
+
6
+ <br>
7
+
8
+ A chess engine which implements:
9
+ - [Alpha-beta pruning](https://en.wikipedia.org/wiki/Alpha%E2%80%93beta_pruning) for move searching
10
+ - [Move ordering](https://www.chessprogramming.org/Move_Ordering) based off heuristics like captures and promotions
11
+ - Tomasz Michniewski's [Simplified Evaluation Function](https://www.chessprogramming.org/Simplified_Evaluation_Function) for board evaluation and piece-square tables
12
+ - A slice of the Universal Chess Interface (UCI) to allow challenges via lichess.org
13
+ - A command-line user interface
14
+
15
+ It uses Python 3.8 with Mypy type hints and unit + integration tests.
16
+
17
+ See [Contributing](#contributing) to help out!
18
+
19
+ <br>
20
+
21
+ ## Install
22
+
23
+ `pip install -r requirements.txt`
24
+
25
+ <br>
26
+
27
+ ## Use it via command-line
28
+
29
+ Start the engine with:
30
+
31
+ `python ui.py`
32
+
33
+ ```bash
34
+ Start as [w]hite or [b]lack:
35
+ w
36
+
37
+ 8 ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
38
+ 7 ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
39
+ 6 · · · · · · · ·
40
+ 5 · · · · · · · ·
41
+ 4 · · · · · · · ·
42
+ 3 · · · · · · · ·
43
+ 2 ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
44
+ 1 ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
45
+ a b c d e f g h
46
+
47
+ Enter a move like g1h3:
48
+ ```
49
+
50
+ <br>
51
+
52
+ ## Use it as a UCI engine
53
+
54
+ _The only interfaces that Andoma currently supports are [ShailChoksi/lichess-bot](https://github.com/ShailChoksi/lichess-bot) and the command-line UI (ui.py). Debug information and configuration options are minimal compared to a full UCI engine._
55
+
56
+ <br>
57
+
58
+ Start the engine with:
59
+
60
+ `python main.py`
61
+
62
+ An example interaction with the engine (responses have `#`):
63
+
64
+ ```bash
65
+ uci
66
+ # id name Andoma
67
+ # id author Andrew Healey & Roma Parramore
68
+ # uciok
69
+ position startpos moves e2e4
70
+ go
71
+ # bestmove g8f6
72
+ ```
73
+
74
+ Also accepts a FEN string:
75
+
76
+ `position fen rnbqk1nr/p1ppppbp/1p4p1/8/2P5/2Q5/PP1PPPPP/RNB1KBNR b KQkq - 0 1`
77
+
78
+ <br>
79
+
80
+ See the [UCI interface doc](https://github.com/healeycodes/andoma/blob/main/uci-interface.md) for more information on communicating with the engine.
81
+
82
+ <br>
83
+
84
+ ## Lichess.org
85
+
86
+ The UCI protocol slice that's implemented by this engine means you can play it via lichess.org by using [ShailChoksi/lichess-bot](https://github.com/ShailChoksi/lichess-bot) (a bridge between Lichess API and chess engines) and a BOT account.
87
+
88
+ The engine file required by `lichess-bot` may be generated using [pyinstaller](https://www.pyinstaller.org/).
89
+
90
+ <br>
91
+
92
+ ## Tests
93
+
94
+ There are unit tests for the engine, UI, and evaluation modules. Mate-in-two/mate-in-three puzzles are being added.
95
+
96
+ `python -m unittest discover test/`
97
+
98
+ Type hints:
99
+
100
+ `pip install -r requirements-dev.txt`
101
+
102
+ `mypy .`
103
+
104
+ <br>
105
+
106
+ ## Contributing
107
+
108
+ Raise an issue to propose a bug fix or feature (or pick up an existing one).
109
+
110
+ I ([@healeycodes](https://github.com/healeycodes)) am happy to help you along the way.
111
+
112
+ For coding style: look at the existing files, use Mypy types, use PEP8, and add a test for any change in functionality.
113
+
114
+ Please run the tests locally before submitting a PR.
File without changes
@@ -0,0 +1,83 @@
1
+ import sys
2
+ import chess
3
+ import argparse
4
+ from movegeneration import next_move
5
+
6
+
7
+ def talk():
8
+ """
9
+ The main input/output loop.
10
+ This implements a slice of the UCI protocol.
11
+ """
12
+ board = chess.Board()
13
+ depth = get_depth()
14
+
15
+ while True:
16
+ msg = input()
17
+ command(depth, board, msg)
18
+
19
+
20
+ def command(depth: int, board: chess.Board, msg: str):
21
+ """
22
+ Accept UCI commands and respond.
23
+ The board state is also updated.
24
+ """
25
+ msg = msg.strip()
26
+ tokens = msg.split(" ")
27
+ while "" in tokens:
28
+ tokens.remove("")
29
+
30
+ if msg == "quit":
31
+ sys.exit()
32
+
33
+ if msg == "uci":
34
+ print("id name Andoma") # Andrew/Roma -> And/oma
35
+ print("id author Andrew Healey & Roma Parramore")
36
+ print("uciok")
37
+ return
38
+
39
+ if msg == "isready":
40
+ print("readyok")
41
+ return
42
+
43
+ if msg == "ucinewgame":
44
+ return
45
+
46
+ if msg.startswith("position"):
47
+ if len(tokens) < 2:
48
+ return
49
+
50
+ # Set starting position
51
+ if tokens[1] == "startpos":
52
+ board.reset()
53
+ moves_start = 2
54
+ elif tokens[1] == "fen":
55
+ fen = " ".join(tokens[2:8])
56
+ board.set_fen(fen)
57
+ moves_start = 8
58
+ else:
59
+ return
60
+
61
+ # Apply moves
62
+ if len(tokens) <= moves_start or tokens[moves_start] != "moves":
63
+ return
64
+
65
+ for move in tokens[(moves_start+1):]:
66
+ board.push_uci(move)
67
+
68
+ if msg == "d":
69
+ # Non-standard command, but supported by Stockfish and helps debugging
70
+ print(board)
71
+ print(board.fen())
72
+
73
+ if msg[0:2] == "go":
74
+ _move = next_move(depth, board)
75
+ print(f"bestmove {_move}")
76
+ return
77
+
78
+
79
+ def get_depth() -> int:
80
+ parser = argparse.ArgumentParser()
81
+ parser.add_argument("--depth", default=3, help="provide an integer (default: 3)")
82
+ args = parser.parse_args()
83
+ return max([1, int(args.depth)])