├── Blog1.ipynb
└── Blog2.ipynb
/Blog1.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {
7 | "collapsed": true
8 | },
9 | "outputs": [],
10 | "source": [
11 | "%load_ext autotime"
12 | ]
13 | },
14 | {
15 | "cell_type": "markdown",
16 | "metadata": {},
17 | "source": [
18 | "# Writing a chess program in one day"
19 | ]
20 | },
21 | {
22 | "cell_type": "markdown",
23 | "metadata": {},
24 | "source": [
25 | "The post is about how to write a simple computer chess program within one day with only a few lines of code. The program will be written in Python and contains all main parts of a chess engine. It will be the basis of refinements and enhancements which I will show in future postings.\n",
26 | "\n",
27 | "Every chess program has 3 important parts:\n",
28 | " - The representation of the board\n",
29 | " - The board evaluation\n",
30 | " - The search\n",
31 | "\n",
32 | "As a starting point I use the Python package \"chess\" [https://python-chess.readthedocs.io](https://python-chess.readthedocs.io) which is a library for move generation, move validation, support for printing the board and more."
33 | ]
34 | },
35 | {
36 | "cell_type": "markdown",
37 | "metadata": {},
38 | "source": [
39 | "## Board representation"
40 | ]
41 | },
42 | {
43 | "cell_type": "code",
44 | "execution_count": 2,
45 | "metadata": {},
46 | "outputs": [
47 | {
48 | "data": {
49 | "image/svg+xml": [
50 | ""
51 | ],
52 | "text/plain": [
53 | ""
54 | ]
55 | },
56 | "execution_count": 2,
57 | "metadata": {},
58 | "output_type": "execute_result"
59 | },
60 | {
61 | "name": "stdout",
62 | "output_type": "stream",
63 | "text": [
64 | "time: 143 ms\n"
65 | ]
66 | }
67 | ],
68 | "source": [
69 | "import chess\n",
70 | "import chess.svg\n",
71 | "\n",
72 | "from IPython.display import SVG\n",
73 | "\n",
74 | "board = chess.Board()\n",
75 | "SVG(chess.svg.board(board=board,size=400)) "
76 | ]
77 | },
78 | {
79 | "cell_type": "markdown",
80 | "metadata": {},
81 | "source": [
82 | "The main component of the chess library is a \"Board\"-object which represents the pieces on the chess board, and has methods for move-generation and checking the status of the board (for example checking for mate). The object has a move-stack on which you can push and pop moves, for making a move and taking back a move.\n",
83 | "\n",
84 | "An SVG component can be used to display a graphical representation of the board as above in a \"Jupyter\"-notebook."
85 | ]
86 | },
87 | {
88 | "cell_type": "markdown",
89 | "metadata": {},
90 | "source": [
91 | "## Board evaluation\n",
92 | "\n",
93 | "A position on the chess board can be evaluated, if it is won by one side or it is a draw. If none of this conditions is satisfied we need an estimate of how likely it is that a player wins. In this simple implementation it is done by two factors the \"material\" (pieces on board) and the positions of the pieces. For each sort of pieces different values are calculated depending on the squares the pieces are located. This is done with so called \"piece-square\" tables. See below for the implementation."
94 | ]
95 | },
96 | {
97 | "cell_type": "code",
98 | "execution_count": 8,
99 | "metadata": {},
100 | "outputs": [
101 | {
102 | "name": "stdout",
103 | "output_type": "stream",
104 | "text": [
105 | "time: 115 ms\n"
106 | ]
107 | }
108 | ],
109 | "source": [
110 | "def evaluate_board():\n",
111 | " \n",
112 | " if board.is_checkmate():\n",
113 | " if board.turn:\n",
114 | " return -9999\n",
115 | " else:\n",
116 | " return 9999\n",
117 | " if board.is_stalemate():\n",
118 | " return 0\n",
119 | " if board.is_insufficient_material():\n",
120 | " return 0\n",
121 | " \n",
122 | " wp = len(board.pieces(chess.PAWN, chess.WHITE))\n",
123 | " bp = len(board.pieces(chess.PAWN, chess.BLACK))\n",
124 | " wn = len(board.pieces(chess.KNIGHT, chess.WHITE))\n",
125 | " bn = len(board.pieces(chess.KNIGHT, chess.BLACK))\n",
126 | " wb = len(board.pieces(chess.BISHOP, chess.WHITE))\n",
127 | " bb = len(board.pieces(chess.BISHOP, chess.BLACK))\n",
128 | " wr = len(board.pieces(chess.ROOK, chess.WHITE))\n",
129 | " br = len(board.pieces(chess.ROOK, chess.BLACK))\n",
130 | " wq = len(board.pieces(chess.QUEEN, chess.WHITE))\n",
131 | " bq = len(board.pieces(chess.QUEEN, chess.BLACK))\n",
132 | " \n",
133 | " material = 100*(wp-bp)+320*(wn-bn)+330*(wb-bb)+500*(wr-br)+900*(wq-bq)\n",
134 | " \n",
135 | " pawnsq = sum([pawntable[i] for i in board.pieces(chess.PAWN, chess.WHITE)])\n",
136 | " pawnsq= pawnsq + sum([-pawntable[chess.square_mirror(i)] \n",
137 | " for i in board.pieces(chess.PAWN, chess.BLACK)])\n",
138 | " knightsq = sum([knightstable[i] for i in board.pieces(chess.KNIGHT, chess.WHITE)])\n",
139 | " knightsq = knightsq + sum([-knightstable[chess.square_mirror(i)] \n",
140 | " for i in board.pieces(chess.KNIGHT, chess.BLACK)])\n",
141 | " bishopsq= sum([bishopstable[i] for i in board.pieces(chess.BISHOP, chess.WHITE)])\n",
142 | " bishopsq= bishopsq + sum([-bishopstable[chess.square_mirror(i)] \n",
143 | " for i in board.pieces(chess.BISHOP, chess.BLACK)])\n",
144 | " rooksq = sum([rookstable[i] for i in board.pieces(chess.ROOK, chess.WHITE)]) \n",
145 | " rooksq = rooksq + sum([-rookstable[chess.square_mirror(i)] \n",
146 | " for i in board.pieces(chess.ROOK, chess.BLACK)])\n",
147 | " queensq = sum([queenstable[i] for i in board.pieces(chess.QUEEN, chess.WHITE)]) \n",
148 | " queensq = queensq + sum([-queenstable[chess.square_mirror(i)] \n",
149 | " for i in board.pieces(chess.QUEEN, chess.BLACK)])\n",
150 | " kingsq = sum([kingstable[i] for i in board.pieces(chess.KING, chess.WHITE)]) \n",
151 | " kingsq = kingsq + sum([-kingstable[chess.square_mirror(i)] \n",
152 | " for i in board.pieces(chess.KING, chess.BLACK)])\n",
153 | " \n",
154 | " eval = material + pawnsq + knightsq + bishopsq+ rooksq+ queensq + kingsq\n",
155 | " if board.turn:\n",
156 | " return eval\n",
157 | " else:\n",
158 | " return -eval"
159 | ]
160 | },
161 | {
162 | "cell_type": "markdown",
163 | "metadata": {},
164 | "source": [
165 | "The function returns -9999 if white is mated, 9999 if black is mated and 0 for a draw. In all other situations it returns an evaluation as the sum of the material and the sum of postion values via piece-square tables. If black is in turn then the negative value is returned as needed by the negamax implementation of the search (see below)."
166 | ]
167 | },
168 | {
169 | "cell_type": "markdown",
170 | "metadata": {},
171 | "source": [
172 | "### Piece-square tables"
173 | ]
174 | },
175 | {
176 | "cell_type": "markdown",
177 | "metadata": {},
178 | "source": [
179 | "I used the piece-squre tables from [https://www.chessprogramming.org/Simplified_Evaluation_Function](https://www.chessprogramming.org/Simplified_Evaluation_Function). For each sort of piece a different table is defined. If the value on a square is positive then the program tries to place a piece on that square if the value is negative it avoids to move to that square. The value of the whole position is calculated by summing over all pieces of both sides.\n",
180 | "\n",
181 | "For pawns the program is encouraged to advance the pawns. Additionally we try to discourage the engine from leaving central pawns unmoved. Pawns on f2, g2 or c2 and b2 should not move zu f3 etc.\n",
182 | "\n",
183 | "Knights are simply encouraged to go to the center. Standing on the edge is a bad idea.\n",
184 | "\n",
185 | "Bishops should avoid corners and borders.\n",
186 | "\n",
187 | "Rooks should occupy the 7th rank and avoid a, h columns\n",
188 | "\n",
189 | "Queens should avoid corners and borders and stay in the center.\n",
190 | "\n",
191 | "Kings should stand behind the pawn shelter. This is only good for opening and middle game. The endgame needs a different table. I will do this in a future enhancement of the program."
192 | ]
193 | },
194 | {
195 | "cell_type": "code",
196 | "execution_count": 9,
197 | "metadata": {},
198 | "outputs": [
199 | {
200 | "name": "stdout",
201 | "output_type": "stream",
202 | "text": [
203 | "time: 73 ms\n"
204 | ]
205 | }
206 | ],
207 | "source": [
208 | "pawntable = [\n",
209 | " 0, 0, 0, 0, 0, 0, 0, 0,\n",
210 | " 5, 10, 10,-20,-20, 10, 10, 5,\n",
211 | " 5, -5,-10, 0, 0,-10, -5, 5,\n",
212 | " 0, 0, 0, 20, 20, 0, 0, 0,\n",
213 | " 5, 5, 10, 25, 25, 10, 5, 5,\n",
214 | "10, 10, 20, 30, 30, 20, 10, 10,\n",
215 | "50, 50, 50, 50, 50, 50, 50, 50,\n",
216 | " 0, 0, 0, 0, 0, 0, 0, 0]\n",
217 | "\n",
218 | "knightstable = [\n",
219 | "-50,-40,-30,-30,-30,-30,-40,-50,\n",
220 | "-40,-20, 0, 5, 5, 0,-20,-40,\n",
221 | "-30, 5, 10, 15, 15, 10, 5,-30,\n",
222 | "-30, 0, 15, 20, 20, 15, 0,-30,\n",
223 | "-30, 5, 15, 20, 20, 15, 5,-30,\n",
224 | "-30, 0, 10, 15, 15, 10, 0,-30,\n",
225 | "-40,-20, 0, 0, 0, 0,-20,-40,\n",
226 | "-50,-40,-30,-30,-30,-30,-40,-50]\n",
227 | "\n",
228 | "bishopstable = [\n",
229 | "-20,-10,-10,-10,-10,-10,-10,-20,\n",
230 | "-10, 5, 0, 0, 0, 0, 5,-10,\n",
231 | "-10, 10, 10, 10, 10, 10, 10,-10,\n",
232 | "-10, 0, 10, 10, 10, 10, 0,-10,\n",
233 | "-10, 5, 5, 10, 10, 5, 5,-10,\n",
234 | "-10, 0, 5, 10, 10, 5, 0,-10,\n",
235 | "-10, 0, 0, 0, 0, 0, 0,-10,\n",
236 | "-20,-10,-10,-10,-10,-10,-10,-20]\n",
237 | "\n",
238 | "rookstable = [\n",
239 | " 0, 0, 0, 5, 5, 0, 0, 0,\n",
240 | " -5, 0, 0, 0, 0, 0, 0, -5,\n",
241 | " -5, 0, 0, 0, 0, 0, 0, -5,\n",
242 | " -5, 0, 0, 0, 0, 0, 0, -5,\n",
243 | " -5, 0, 0, 0, 0, 0, 0, -5,\n",
244 | " -5, 0, 0, 0, 0, 0, 0, -5,\n",
245 | " 5, 10, 10, 10, 10, 10, 10, 5,\n",
246 | " 0, 0, 0, 0, 0, 0, 0, 0]\n",
247 | "\n",
248 | "queenstable = [\n",
249 | "-20,-10,-10, -5, -5,-10,-10,-20,\n",
250 | "-10, 0, 0, 0, 0, 0, 0,-10,\n",
251 | "-10, 5, 5, 5, 5, 5, 0,-10,\n",
252 | " 0, 0, 5, 5, 5, 5, 0, -5,\n",
253 | " -5, 0, 5, 5, 5, 5, 0, -5,\n",
254 | "-10, 0, 5, 5, 5, 5, 0,-10,\n",
255 | "-10, 0, 0, 0, 0, 0, 0,-10,\n",
256 | "-20,-10,-10, -5, -5,-10,-10,-20]\n",
257 | "\n",
258 | "kingstable = [\n",
259 | " 20, 30, 10, 0, 0, 10, 30, 20,\n",
260 | " 20, 20, 0, 0, 0, 0, 20, 20,\n",
261 | "-10,-20,-20,-20,-20,-20,-20,-10,\n",
262 | "-20,-30,-30,-40,-40,-30,-30,-20,\n",
263 | "-30,-40,-40,-50,-50,-40,-40,-30,\n",
264 | "-30,-40,-40,-50,-50,-40,-40,-30,\n",
265 | "-30,-40,-40,-50,-50,-40,-40,-30,\n",
266 | "-30,-40,-40,-50,-50,-40,-40,-30]"
267 | ]
268 | },
269 | {
270 | "cell_type": "markdown",
271 | "metadata": {},
272 | "source": [
273 | "## Search\n",
274 | "\n",
275 | "For lookahead I use Depth-First search that starts at the root and explores up to a fixed depth along each branch before backtracking. The value of the position is calculated via [Minimax](https://www.chessprogramming.org/Minimax) and [Alphabeta pruning](https://www.chessprogramming.org/Alpha-Beta) is used with the Negamax implementation. "
276 | ]
277 | },
278 | {
279 | "cell_type": "code",
280 | "execution_count": 10,
281 | "metadata": {},
282 | "outputs": [
283 | {
284 | "name": "stdout",
285 | "output_type": "stream",
286 | "text": [
287 | "time: 27 ms\n"
288 | ]
289 | }
290 | ],
291 | "source": [
292 | "def alphabeta( alpha, beta, depthleft ):\n",
293 | " bestscore = -9999\n",
294 | " if( depthleft == 0 ):\n",
295 | " return quiesce( alpha, beta )\n",
296 | " for move in board.legal_moves:\n",
297 | " board.push(move) \n",
298 | " score = -alphabeta( -beta, -alpha, depthleft - 1 )\n",
299 | " board.pop()\n",
300 | " if( score >= beta ):\n",
301 | " return score\n",
302 | " if( score > bestscore ):\n",
303 | " bestscore = score\n",
304 | " if( score > alpha ):\n",
305 | " alpha = score \n",
306 | " return bestscore"
307 | ]
308 | },
309 | {
310 | "cell_type": "markdown",
311 | "metadata": {},
312 | "source": [
313 | "At the maximun search depth the search is extended by searching all capture moves, the so called [quiescence search](https://www.chessprogramming.org/Quiescence_Search) to avoid the [\"Horizon Effect\"](https://www.chessprogramming.org/Horizon_Effect)."
314 | ]
315 | },
316 | {
317 | "cell_type": "code",
318 | "execution_count": 11,
319 | "metadata": {},
320 | "outputs": [
321 | {
322 | "name": "stdout",
323 | "output_type": "stream",
324 | "text": [
325 | "time: 37 ms\n"
326 | ]
327 | }
328 | ],
329 | "source": [
330 | "def quiesce( alpha, beta ):\n",
331 | " stand_pat = evaluate_board()\n",
332 | " if( stand_pat >= beta ):\n",
333 | " return beta\n",
334 | " if( alpha < stand_pat ):\n",
335 | " alpha = stand_pat\n",
336 | "\n",
337 | " for move in board.legal_moves:\n",
338 | " if board.is_capture(move):\n",
339 | " board.push(move) \n",
340 | " score = -quiesce( -beta, -alpha )\n",
341 | " board.pop()\n",
342 | "\n",
343 | " if( score >= beta ):\n",
344 | " return beta\n",
345 | " if( score > alpha ):\n",
346 | " alpha = score \n",
347 | " return alpha"
348 | ]
349 | },
350 | {
351 | "cell_type": "markdown",
352 | "metadata": {},
353 | "source": [
354 | "The function which implements the move selection in the root position consists of two parts. The first part tries to find a move in the opening book and gives it back. The \"chess\" library has a function to access opening books in the [\"Polyglot\" format](https://www.chessprogramming.org/PolyGlot). I used the \"bookfish\" opening book which I downloaded from [http://rebel13.nl/download/books.html](http://rebel13.nl/download/books.html). A random weighted move is selected from all possible moves of the book in this position.\n",
355 | "\n",
356 | "The second part calculates the move if the book is empty. For each move in the position the search (alphabeta) is conducted and the best move is choosen."
357 | ]
358 | },
359 | {
360 | "cell_type": "code",
361 | "execution_count": 12,
362 | "metadata": {},
363 | "outputs": [
364 | {
365 | "name": "stdout",
366 | "output_type": "stream",
367 | "text": [
368 | "time: 20 ms\n"
369 | ]
370 | }
371 | ],
372 | "source": [
373 | "import chess.polyglot\n",
374 | "\n",
375 | "def selectmove(depth):\n",
376 | " try:\n",
377 | " move = chess.polyglot.MemoryMappedReader(\"bookfish.bin\").weighted_choice(board).move()\n",
378 | " movehistory.append(move)\n",
379 | " return move\n",
380 | " except:\n",
381 | " bestMove = chess.Move.null()\n",
382 | " bestValue = -99999\n",
383 | " alpha = -100000\n",
384 | " beta = 100000\n",
385 | " for move in board.legal_moves:\n",
386 | " board.push(move)\n",
387 | " boardValue = -alphabeta(-beta, -alpha, depth-1)\n",
388 | " if boardValue > bestValue:\n",
389 | " bestValue = boardValue;\n",
390 | " bestMove = move\n",
391 | " if( boardValue > alpha ):\n",
392 | " alpha = boardValue\n",
393 | " board.pop()\n",
394 | " movehistory.append(bestMove)\n",
395 | " return bestMove"
396 | ]
397 | },
398 | {
399 | "cell_type": "markdown",
400 | "metadata": {},
401 | "source": [
402 | "To play against the program inside a Jupyter notebook you can evalute the following cell to compute a computer move (with a search depth of 3), and display the board."
403 | ]
404 | },
405 | {
406 | "cell_type": "code",
407 | "execution_count": 14,
408 | "metadata": {},
409 | "outputs": [
410 | {
411 | "data": {
412 | "image/svg+xml": [
413 | ""
414 | ],
415 | "text/plain": [
416 | ""
417 | ]
418 | },
419 | "execution_count": 14,
420 | "metadata": {},
421 | "output_type": "execute_result"
422 | },
423 | {
424 | "name": "stdout",
425 | "output_type": "stream",
426 | "text": [
427 | "time: 32 ms\n"
428 | ]
429 | }
430 | ],
431 | "source": [
432 | "movehistory =[]\n",
433 | "board = chess.Board()\n",
434 | "mov = selectmove(3)\n",
435 | "board.push(mov)\n",
436 | "SVG(chess.svg.board(board=board,size=400))"
437 | ]
438 | },
439 | {
440 | "cell_type": "markdown",
441 | "metadata": {},
442 | "source": [
443 | "To make a human move you can use:"
444 | ]
445 | },
446 | {
447 | "cell_type": "code",
448 | "execution_count": 15,
449 | "metadata": {},
450 | "outputs": [
451 | {
452 | "data": {
453 | "image/svg+xml": [
454 | ""
455 | ],
456 | "text/plain": [
457 | ""
458 | ]
459 | },
460 | "execution_count": 15,
461 | "metadata": {},
462 | "output_type": "execute_result"
463 | },
464 | {
465 | "name": "stdout",
466 | "output_type": "stream",
467 | "text": [
468 | "time: 27 ms\n"
469 | ]
470 | }
471 | ],
472 | "source": [
473 | "board.push_san(\"d5\")\n",
474 | "SVG(chess.svg.board(board=board,size=400))"
475 | ]
476 | },
477 | {
478 | "cell_type": "markdown",
479 | "metadata": {},
480 | "source": [
481 | "## Match against Stockfish\n",
482 | "\n",
483 | "Stockfish [https://stockfishchess.org/](https://stockfishchess.org/) is one of the strongest chess-engines in this days. I use it to test my programm with a search depth of 3.\n",
484 | "\n",
485 | "After loading the Stockfish engine with the [UCI](https://www.chessprogramming.org/UCI) component of the library I wrote some code to collect all the moves of the game in a list (\"movehistory\") and write them to a [portable game notation file](https://en.wikipedia.org/wiki/Portable_Game_Notation). \n",
486 | "\n",
487 | "At the end of the game the final board position is displayed."
488 | ]
489 | },
490 | {
491 | "cell_type": "code",
492 | "execution_count": 19,
493 | "metadata": {},
494 | "outputs": [
495 | {
496 | "name": "stdout",
497 | "output_type": "stream",
498 | "text": [
499 | "[Event \"Example\"]\n",
500 | "[Site \"Linz\"]\n",
501 | "[Date \"2018-12-24\"]\n",
502 | "[Round \"1\"]\n",
503 | "[White \"MyChess\"]\n",
504 | "[Black \"Stockfish9\"]\n",
505 | "[Result \"0-1\"]\n",
506 | "\n",
507 | "1. d4 d5 2. c4 e6 3. Nc3 c5 4. cxd5 exd5 5. Nf3 Nf6 6. Bg5 Be7 7. dxc5 Nc6 8. Bxf6 Bxf6 9. Nxd5 Bxb2 10. Rb1 Ba3 11. e4 O-O 12. Rb5 Re8 13. Nc3 Qxd1+ 14. Kxd1 a6 15. Rb3 Bxc5 16. Ke1 Be6 17. Rxb7 Bb4 18. Kd2 Rac8 19. Bxa6 Bxc3+ 20. Kxc3 Nb8+ 21. Kb2 Nxa6 22. Ra7 Nc5 23. e5 Nd3+ 24. Kb1 Nxf2 25. Re1 Red8 26. Re7 Bf5+ 27. Ka1 Rd1+ 28. Rxd1 Nxd1 29. a3 Kf8 30. Rb7 Rc1+ 31. Ka2 Be6+ 32. Rb3 Rc2+ 33. Kb1 Bxb3 34. Ng5 Nc3+ 35. Ka1 Ra2# 0-1\n"
508 | ]
509 | },
510 | {
511 | "data": {
512 | "image/svg+xml": [
513 | ""
514 | ],
515 | "text/plain": [
516 | ""
517 | ]
518 | },
519 | "execution_count": 19,
520 | "metadata": {},
521 | "output_type": "execute_result"
522 | },
523 | {
524 | "name": "stdout",
525 | "output_type": "stream",
526 | "text": [
527 | "time: 20min 18s\n"
528 | ]
529 | }
530 | ],
531 | "source": [
532 | "import chess.pgn\n",
533 | "import datetime\n",
534 | "import chess.uci\n",
535 | "\n",
536 | "engine = chess.uci.popen_engine(\"C:/Users/p20133/Documents/pychess/stockfish/Windows/stockfish_9_x64.exe\")\n",
537 | "engine.uci()\n",
538 | "engine.name\n",
539 | "\n",
540 | "movehistory =[]\n",
541 | "game = chess.pgn.Game()\n",
542 | "game.headers[\"Event\"] = \"Example\"\n",
543 | "game.headers[\"Site\"] = \"Linz\"\n",
544 | "game.headers[\"Date\"] = str(datetime.datetime.now().date())\n",
545 | "game.headers[\"Round\"] = 1\n",
546 | "game.headers[\"White\"] = \"MyChess\"\n",
547 | "game.headers[\"Black\"] = \"Stockfish9\"\n",
548 | "board = chess.Board()\n",
549 | "while not board.is_game_over(claim_draw=True):\n",
550 | " if board.turn:\n",
551 | " move = selectmove(4)\n",
552 | " board.push(move) \n",
553 | " else:\n",
554 | " engine.position(board)\n",
555 | " move = engine.go(movetime=1000).bestmove\n",
556 | " movehistory.append(move)\n",
557 | " board.push(move)\n",
558 | " \n",
559 | "game.add_line(movehistory)\n",
560 | "game.headers[\"Result\"] = str(board.result(claim_draw=True))\n",
561 | "print(game)\n",
562 | "print(game, file=open(\"test.pgn\", \"w\"), end=\"\\n\\n\")\n",
563 | "\n",
564 | "SVG(chess.svg.board(board=board,size=400))"
565 | ]
566 | },
567 | {
568 | "cell_type": "markdown",
569 | "metadata": {},
570 | "source": [
571 | "In the opening phase both programs used the opening books. Considering the limeted amount of knowledge of my program it played some solid moves. The end of the game showed the negative effects of the limited search depth of my engine. \n",
572 | "\n",
573 | "There are a lot of things that can be done to improve the stength of the program. For example a more sophisticated evaluation function or a better search procedure. I will cover this in future posts."
574 | ]
575 | },
576 | {
577 | "cell_type": "markdown",
578 | "metadata": {},
579 | "source": [
580 | "## Selfplay"
581 | ]
582 | },
583 | {
584 | "cell_type": "code",
585 | "execution_count": 20,
586 | "metadata": {},
587 | "outputs": [
588 | {
589 | "name": "stdout",
590 | "output_type": "stream",
591 | "text": [
592 | "[Event \"Example\"]\n",
593 | "[Site \"Linz\"]\n",
594 | "[Date \"2018-12-24\"]\n",
595 | "[Round \"1\"]\n",
596 | "[White \"MyChess\"]\n",
597 | "[Black \"MyChess\"]\n",
598 | "[Result \"1-0\"]\n",
599 | "\n",
600 | "1. c4 c5 2. g3 Nc6 3. Bg2 g6 4. Nc3 Bg7 5. a3 e6 6. b4 Nxb4 7. axb4 cxb4 8. d4 bxc3 9. e3 Ne7 10. Ne2 O-O 11. Nxc3 Qc7 12. Qb3 a6 13. Ba3 d6 14. O-O e5 15. c5 dxc5 16. Bxc5 Re8 17. Nb5 Qb8 18. Nd6 Be6 19. d5 Bf5 20. Nxe8 Qxe8 21. Qxb7 Bf6 22. Qb6 Bg7 23. Rxa6 Rxa6 24. Qxa6 g5 25. Qd6 Nc8 26. Qc7 e4 27. Re1 Be5 28. d6 g4 29. Rd1 Qe6 30. Qd8+ Kg7 31. Qg5+ Bg6 32. d7 f6 33. Qxg6+ hxg6 34. d8=Q Qc6 35. Qf8+ Kh7 36. Bxe4 Qxe4 37. Rd7+ Ne7 38. Rxe7# 1-0\n"
601 | ]
602 | },
603 | {
604 | "data": {
605 | "image/svg+xml": [
606 | ""
607 | ],
608 | "text/plain": [
609 | ""
610 | ]
611 | },
612 | "execution_count": 20,
613 | "metadata": {},
614 | "output_type": "execute_result"
615 | },
616 | {
617 | "name": "stdout",
618 | "output_type": "stream",
619 | "text": [
620 | "time: 32min 32s\n"
621 | ]
622 | }
623 | ],
624 | "source": [
625 | "import chess.pgn\n",
626 | "import datetime\n",
627 | "\n",
628 | "movehistory =[]\n",
629 | "game = chess.pgn.Game()\n",
630 | "game.headers[\"Event\"] = \"Example\"\n",
631 | "game.headers[\"Site\"] = \"Linz\"\n",
632 | "game.headers[\"Date\"] = str(datetime.datetime.now().date())\n",
633 | "game.headers[\"Round\"] = 1\n",
634 | "game.headers[\"White\"] = \"MyChess\"\n",
635 | "game.headers[\"Black\"] = \"MyChess\"\n",
636 | "board = chess.Board()\n",
637 | "while not board.is_game_over(claim_draw=True):\n",
638 | " if board.turn:\n",
639 | " move = selectmove(3)\n",
640 | " board.push(move) \n",
641 | " else:\n",
642 | " move = selectmove(3)\n",
643 | " board.push(move) \n",
644 | " \n",
645 | "game.add_line(movehistory)\n",
646 | "game.headers[\"Result\"] = str(board.result(claim_draw=True))\n",
647 | "print(game)\n",
648 | "print(game, file=open(\"selftest.pgn\", \"w\"), end=\"\\n\\n\")\n",
649 | "\n",
650 | "SVG(chess.svg.board(board=board,size=400))"
651 | ]
652 | }
653 | ],
654 | "metadata": {
655 | "kernelspec": {
656 | "display_name": "Python 3",
657 | "language": "python",
658 | "name": "python3"
659 | },
660 | "language_info": {
661 | "codemirror_mode": {
662 | "name": "ipython",
663 | "version": 3
664 | },
665 | "file_extension": ".py",
666 | "mimetype": "text/x-python",
667 | "name": "python",
668 | "nbconvert_exporter": "python",
669 | "pygments_lexer": "ipython3",
670 | "version": "3.6.3"
671 | }
672 | },
673 | "nbformat": 4,
674 | "nbformat_minor": 2
675 | }
676 |
--------------------------------------------------------------------------------
/Blog2.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 18,
6 | "metadata": {
7 | "collapsed": true
8 | },
9 | "outputs": [],
10 | "source": [
11 | "%load_ext autotime"
12 | ]
13 | },
14 | {
15 | "cell_type": "markdown",
16 | "metadata": {},
17 | "source": [
18 | "# An incremental evaluation function and a testsuite for computer chess\n",
19 | "\n",
20 | "This article is a continuation of the post\n",
21 | "https://medium.com/@andreasstckl/writing-a-chess-program-in-one-day-30daff4610ec\n",
22 | "\n",
23 | "It shows how to write a simple computer chess program with only a few lines of code in Python, and contains all main parts of a chess engine. In this article and future postings I will show how to enhance the program. \n",
24 | "\n",
25 | "The code can be found at:\n",
26 | "https://github.com/astoeckl/mediumchess/\n",
27 | "\n",
28 | "The simple program in the previous post has a very simple evaluation function, but evaluation and therefore the whole search process is very slow because each node in the search is evaluated from scratch. In the search process the positions only differ by one move, and so it is a good idea to initialize the evaluation at the begin of the search and then incrementally update the evalutation after each move. "
29 | ]
30 | },
31 | {
32 | "cell_type": "code",
33 | "execution_count": 1,
34 | "metadata": {},
35 | "outputs": [],
36 | "source": [
37 | "import chess\n",
38 | "import chess.svg\n",
39 | "from IPython.display import SVG"
40 | ]
41 | },
42 | {
43 | "cell_type": "markdown",
44 | "metadata": {},
45 | "source": [
46 | "## Incremental evaluation\n",
47 | "\n",
48 | "I define a function to evaluate the actual position and store the value in a global variable. This funcion is called once at the beginning of a search."
49 | ]
50 | },
51 | {
52 | "cell_type": "code",
53 | "execution_count": 2,
54 | "metadata": {},
55 | "outputs": [],
56 | "source": [
57 | "def init_evaluate_board():\n",
58 | " global boardvalue\n",
59 | " \n",
60 | " wp = len(board.pieces(chess.PAWN, chess.WHITE))\n",
61 | " bp = len(board.pieces(chess.PAWN, chess.BLACK))\n",
62 | " wn = len(board.pieces(chess.KNIGHT, chess.WHITE))\n",
63 | " bn = len(board.pieces(chess.KNIGHT, chess.BLACK))\n",
64 | " wb = len(board.pieces(chess.BISHOP, chess.WHITE))\n",
65 | " bb = len(board.pieces(chess.BISHOP, chess.BLACK))\n",
66 | " wr = len(board.pieces(chess.ROOK, chess.WHITE))\n",
67 | " br = len(board.pieces(chess.ROOK, chess.BLACK))\n",
68 | " wq = len(board.pieces(chess.QUEEN, chess.WHITE))\n",
69 | " bq = len(board.pieces(chess.QUEEN, chess.BLACK))\n",
70 | " \n",
71 | " material = 100*(wp-bp)+320*(wn-bn)+330*(wb-bb)+500*(wr-br)+900*(wq-bq)\n",
72 | " \n",
73 | " pawnsq = sum([pawntable[i] for i in board.pieces(chess.PAWN, chess.WHITE)])\n",
74 | " pawnsq= pawnsq + sum([-pawntable[chess.square_mirror(i)] \n",
75 | " for i in board.pieces(chess.PAWN, chess.BLACK)])\n",
76 | " knightsq = sum([knightstable[i] for i in board.pieces(chess.KNIGHT, chess.WHITE)])\n",
77 | " knightsq = knightsq + sum([-knightstable[chess.square_mirror(i)] \n",
78 | " for i in board.pieces(chess.KNIGHT, chess.BLACK)])\n",
79 | " bishopsq= sum([bishopstable[i] for i in board.pieces(chess.BISHOP, chess.WHITE)])\n",
80 | " bishopsq= bishopsq + sum([-bishopstable[chess.square_mirror(i)] \n",
81 | " for i in board.pieces(chess.BISHOP, chess.BLACK)])\n",
82 | " rooksq = sum([rookstable[i] for i in board.pieces(chess.ROOK, chess.WHITE)]) \n",
83 | " rooksq = rooksq + sum([-rookstable[chess.square_mirror(i)] \n",
84 | " for i in board.pieces(chess.ROOK, chess.BLACK)])\n",
85 | " queensq = sum([queenstable[i] for i in board.pieces(chess.QUEEN, chess.WHITE)]) \n",
86 | " queensq = queensq + sum([-queenstable[chess.square_mirror(i)] \n",
87 | " for i in board.pieces(chess.QUEEN, chess.BLACK)])\n",
88 | " kingsq = sum([kingstable[i] for i in board.pieces(chess.KING, chess.WHITE)]) \n",
89 | " kingsq = kingsq + sum([-kingstable[chess.square_mirror(i)] \n",
90 | " for i in board.pieces(chess.KING, chess.BLACK)])\n",
91 | " \n",
92 | " boardvalue = material + pawnsq + knightsq + bishopsq + rooksq + queensq + kingsq\n",
93 | " \n",
94 | " return boardvalue"
95 | ]
96 | },
97 | {
98 | "cell_type": "markdown",
99 | "metadata": {},
100 | "source": [
101 | "The evaluation function now simply checks for mate or draw and reads the board value from the global variable."
102 | ]
103 | },
104 | {
105 | "cell_type": "code",
106 | "execution_count": 13,
107 | "metadata": {},
108 | "outputs": [],
109 | "source": [
110 | "def evaluate_board():\n",
111 | " \n",
112 | " if board.is_checkmate():\n",
113 | " if board.turn:\n",
114 | " return -9999\n",
115 | " else:\n",
116 | " return 9999\n",
117 | " if board.is_stalemate():\n",
118 | " return 0\n",
119 | " if board.is_insufficient_material():\n",
120 | " return 0\n",
121 | " \n",
122 | " eval = boardvalue\n",
123 | " if board.turn:\n",
124 | " return eval\n",
125 | " else:\n",
126 | " return -eval"
127 | ]
128 | },
129 | {
130 | "cell_type": "markdown",
131 | "metadata": {},
132 | "source": [
133 | "The definition of the piece square tables remains unchanged."
134 | ]
135 | },
136 | {
137 | "cell_type": "code",
138 | "execution_count": 14,
139 | "metadata": {},
140 | "outputs": [],
141 | "source": [
142 | "pawntable = [\n",
143 | " 0, 0, 0, 0, 0, 0, 0, 0,\n",
144 | " 5, 10, 10,-20,-20, 10, 10, 5,\n",
145 | " 5, -5,-10, 0, 0,-10, -5, 5,\n",
146 | " 0, 0, 0, 20, 20, 0, 0, 0,\n",
147 | " 5, 5, 10, 25, 25, 10, 5, 5,\n",
148 | "10, 10, 20, 30, 30, 20, 10, 10,\n",
149 | "50, 50, 50, 50, 50, 50, 50, 50,\n",
150 | " 0, 0, 0, 0, 0, 0, 0, 0]\n",
151 | "\n",
152 | "knightstable = [\n",
153 | "-50,-40,-30,-30,-30,-30,-40,-50,\n",
154 | "-40,-20, 0, 5, 5, 0,-20,-40,\n",
155 | "-30, 5, 10, 15, 15, 10, 5,-30,\n",
156 | "-30, 0, 15, 20, 20, 15, 0,-30,\n",
157 | "-30, 5, 15, 20, 20, 15, 5,-30,\n",
158 | "-30, 0, 10, 15, 15, 10, 0,-30,\n",
159 | "-40,-20, 0, 0, 0, 0,-20,-40,\n",
160 | "-50,-40,-30,-30,-30,-30,-40,-50]\n",
161 | "\n",
162 | "bishopstable = [\n",
163 | "-20,-10,-10,-10,-10,-10,-10,-20,\n",
164 | "-10, 5, 0, 0, 0, 0, 5,-10,\n",
165 | "-10, 10, 10, 10, 10, 10, 10,-10,\n",
166 | "-10, 0, 10, 10, 10, 10, 0,-10,\n",
167 | "-10, 5, 5, 10, 10, 5, 5,-10,\n",
168 | "-10, 0, 5, 10, 10, 5, 0,-10,\n",
169 | "-10, 0, 0, 0, 0, 0, 0,-10,\n",
170 | "-20,-10,-10,-10,-10,-10,-10,-20]\n",
171 | "\n",
172 | "rookstable = [\n",
173 | " 0, 0, 0, 5, 5, 0, 0, 0,\n",
174 | " -5, 0, 0, 0, 0, 0, 0, -5,\n",
175 | " -5, 0, 0, 0, 0, 0, 0, -5,\n",
176 | " -5, 0, 0, 0, 0, 0, 0, -5,\n",
177 | " -5, 0, 0, 0, 0, 0, 0, -5,\n",
178 | " -5, 0, 0, 0, 0, 0, 0, -5,\n",
179 | " 5, 10, 10, 10, 10, 10, 10, 5,\n",
180 | " 0, 0, 0, 0, 0, 0, 0, 0]\n",
181 | "\n",
182 | "queenstable = [\n",
183 | "-20,-10,-10, -5, -5,-10,-10,-20,\n",
184 | "-10, 0, 0, 0, 0, 0, 0,-10,\n",
185 | "-10, 5, 5, 5, 5, 5, 0,-10,\n",
186 | " 0, 0, 5, 5, 5, 5, 0, -5,\n",
187 | " -5, 0, 5, 5, 5, 5, 0, -5,\n",
188 | "-10, 0, 5, 5, 5, 5, 0,-10,\n",
189 | "-10, 0, 0, 0, 0, 0, 0,-10,\n",
190 | "-20,-10,-10, -5, -5,-10,-10,-20]\n",
191 | "\n",
192 | "kingstable = [\n",
193 | " 20, 30, 10, 0, 0, 10, 30, 20,\n",
194 | " 20, 20, 0, 0, 0, 0, 20, 20,\n",
195 | "-10,-20,-20,-20,-20,-20,-20,-10,\n",
196 | "-20,-30,-30,-40,-40,-30,-30,-20,\n",
197 | "-30,-40,-40,-50,-50,-40,-40,-30,\n",
198 | "-30,-40,-40,-50,-50,-40,-40,-30,\n",
199 | "-30,-40,-40,-50,-50,-40,-40,-30,\n",
200 | "-30,-40,-40,-50,-50,-40,-40,-30]"
201 | ]
202 | },
203 | {
204 | "cell_type": "markdown",
205 | "metadata": {},
206 | "source": [
207 | "Now I have to define a function to update the global boardvalue after each move and after a move has takten back during search."
208 | ]
209 | },
210 | {
211 | "cell_type": "code",
212 | "execution_count": 28,
213 | "metadata": {},
214 | "outputs": [
215 | {
216 | "name": "stdout",
217 | "output_type": "stream",
218 | "text": [
219 | "time: 155 ms\n"
220 | ]
221 | }
222 | ],
223 | "source": [
224 | "piecetypes = [chess.PAWN, chess.KNIGHT, chess.BISHOP, chess.ROOK, chess.QUEEN, chess.KING ]\n",
225 | "tables = [pawntable, knightstable, bishopstable, rookstable, queenstable, kingstable]\n",
226 | "piecevalues = [100,320,330,500,900]\n",
227 | "\n",
228 | "def update_eval(mov, side):\n",
229 | " global boardvalue\n",
230 | " \n",
231 | " #update piecequares\n",
232 | " movingpiece = board.piece_type_at(mov.from_square)\n",
233 | " if side:\n",
234 | " boardvalue = boardvalue - tables[movingpiece - 1][mov.from_square]\n",
235 | " #update castling\n",
236 | " if (mov.from_square == chess.E1) and (mov.to_square == chess.G1):\n",
237 | " boardvalue = boardvalue - rookstable[chess.H1]\n",
238 | " boardvalue = boardvalue + rookstable[chess.F1]\n",
239 | " elif (mov.from_square == chess.E1) and (mov.to_square == chess.C1):\n",
240 | " boardvalue = boardvalue - rookstable[chess.A1]\n",
241 | " boardvalue = boardvalue + rookstable[chess.D1]\n",
242 | " else:\n",
243 | " boardvalue = boardvalue + tables[movingpiece - 1][mov.from_square]\n",
244 | " #update castling\n",
245 | " if (mov.from_square == chess.E8) and (mov.to_square == chess.G8):\n",
246 | " boardvalue = boardvalue + rookstable[chess.H8]\n",
247 | " boardvalue = boardvalue - rookstable[chess.F8]\n",
248 | " elif (mov.from_square == chess.E8) and (mov.to_square == chess.C8):\n",
249 | " boardvalue = boardvalue + rookstable[chess.A8]\n",
250 | " boardvalue = boardvalue - rookstable[chess.D8]\n",
251 | " \n",
252 | " if side:\n",
253 | " boardvalue = boardvalue + tables[movingpiece - 1][mov.to_square]\n",
254 | " else:\n",
255 | " boardvalue = boardvalue - tables[movingpiece - 1][mov.to_square]\n",
256 | " \n",
257 | " \n",
258 | " #update material\n",
259 | " if mov.drop != None:\n",
260 | " if side:\n",
261 | " boardvalue = boardvalue + piecevalues[mov.drop-1]\n",
262 | " else:\n",
263 | " boardvalue = boardvalue - piecevalues[mov.drop-1]\n",
264 | " \n",
265 | " #update promotion\n",
266 | " if mov.promotion != None:\n",
267 | " if side:\n",
268 | " boardvalue = boardvalue + piecevalues[mov.promotion-1] - piecevalues[movingpiece-1]\n",
269 | " boardvalue = boardvalue - tables[movingpiece - 1][mov.to_square] \\\n",
270 | " + tables[mov.promotion - 1][mov.to_square]\n",
271 | " else:\n",
272 | " boardvalue = boardvalue - piecevalues[mov.promotion-1] + piecevalues[movingpiece-1]\n",
273 | " boardvalue = boardvalue + tables[movingpiece - 1][mov.to_square] \\\n",
274 | " - tables[mov.promotion - 1][mov.to_square]\n",
275 | " \n",
276 | " \n",
277 | " return mov\n",
278 | "\n",
279 | "def make_move(mov):\n",
280 | " update_eval(mov, board.turn)\n",
281 | " board.push(mov)\n",
282 | " \n",
283 | " return mov\n",
284 | "\n",
285 | "def unmake_move():\n",
286 | " mov = board.pop()\n",
287 | " update_eval(mov, not board.turn)\n",
288 | " \n",
289 | " return mov"
290 | ]
291 | },
292 | {
293 | "cell_type": "markdown",
294 | "metadata": {},
295 | "source": [
296 | "The functions make_move(mov) and unmake_move() are used insted of using board.push(mov) board.pop() directly. The search function are modified with this functions."
297 | ]
298 | },
299 | {
300 | "cell_type": "code",
301 | "execution_count": 16,
302 | "metadata": {},
303 | "outputs": [],
304 | "source": [
305 | "def quiesce( alpha, beta ):\n",
306 | " stand_pat = evaluate_board()\n",
307 | " if( stand_pat >= beta ):\n",
308 | " return beta\n",
309 | " if( alpha < stand_pat ):\n",
310 | " alpha = stand_pat\n",
311 | "\n",
312 | " for move in board.legal_moves:\n",
313 | " if board.is_capture(move):\n",
314 | " make_move(move) \n",
315 | " score = -quiesce( -beta, -alpha )\n",
316 | " unmake_move()\n",
317 | "\n",
318 | " if( score >= beta ):\n",
319 | " return beta\n",
320 | " if( score > alpha ):\n",
321 | " alpha = score \n",
322 | " return alpha\n",
323 | "\n",
324 | "def alphabeta( alpha, beta, depthleft ):\n",
325 | " bestscore = -9999\n",
326 | " if( depthleft == 0 ):\n",
327 | " return quiesce( alpha, beta )\n",
328 | " for move in board.legal_moves:\n",
329 | " make_move(move) \n",
330 | " score = -alphabeta( -beta, -alpha, depthleft - 1 )\n",
331 | " unmake_move()\n",
332 | " if( score >= beta ):\n",
333 | " return score\n",
334 | " if( score > bestscore ):\n",
335 | " bestscore = score\n",
336 | " if( score > alpha ):\n",
337 | " alpha = score \n",
338 | " return bestscore\n",
339 | "\n",
340 | "import chess.polyglot\n",
341 | "\n",
342 | "def selectmove(depth):\n",
343 | " try:\n",
344 | " move = chess.polyglot.MemoryMappedReader(\"bookfish.bin\").weighted_choice(board).move()\n",
345 | " movehistory.append(move)\n",
346 | " return move\n",
347 | " except:\n",
348 | " bestMove = chess.Move.null()\n",
349 | " bestValue = -99999\n",
350 | " alpha = -100000\n",
351 | " beta = 100000\n",
352 | " for move in board.legal_moves:\n",
353 | " make_move(move)\n",
354 | " boardValue = -alphabeta(-beta, -alpha, depth-1)\n",
355 | " if boardValue > bestValue:\n",
356 | " bestValue = boardValue;\n",
357 | " bestMove = move\n",
358 | " if( boardValue > alpha ):\n",
359 | " alpha = boardValue\n",
360 | " unmake_move()\n",
361 | " movehistory.append(bestMove)\n",
362 | " return bestMove"
363 | ]
364 | },
365 | {
366 | "cell_type": "markdown",
367 | "metadata": {},
368 | "source": [
369 | "Testing the search on a middle game postion with the new evaluation shows faster search process with about 2 Min for a search with depth 5. With the old version the same search took more the an half hour."
370 | ]
371 | },
372 | {
373 | "cell_type": "code",
374 | "execution_count": 19,
375 | "metadata": {},
376 | "outputs": [
377 | {
378 | "data": {
379 | "image/svg+xml": [
380 | ""
381 | ],
382 | "text/plain": [
383 | ""
384 | ]
385 | },
386 | "execution_count": 19,
387 | "metadata": {},
388 | "output_type": "execute_result"
389 | },
390 | {
391 | "name": "stdout",
392 | "output_type": "stream",
393 | "text": [
394 | "time: 2min 8s\n"
395 | ]
396 | }
397 | ],
398 | "source": [
399 | "movehistory =[]\n",
400 | "board = chess.Board(\"1k1r4/pp1b1R2/3q2pp/4p3/2B5/4Q3/PPP2B2/2K5 b - - 0 1\")\n",
401 | "boardvalue = init_evaluate_board()\n",
402 | "\n",
403 | "mov = selectmove(5)\n",
404 | "make_move(mov)\n",
405 | "SVG(chess.svg.board(board=board,size=400,lastmove=mov))"
406 | ]
407 | },
408 | {
409 | "cell_type": "markdown",
410 | "metadata": {},
411 | "source": [
412 | "## A suite of testpostions\n",
413 | "\n",
414 | "I want to use a set of positions which is good for testing the ability of a chess engine to solve tactical positions and so is able to test the quality of the search. A collection of such problems is the Bratko-Kopec Test (https://www.chessprogramming.org/Bratko-Kopec_Test) from the 80s. This test has been a standard for nearly 20 years in computer chess and in our days it is good to test a weak engine like mine.\n",
415 | "\n",
416 | "The position I used for the speed test above is position 1 from this set.\n",
417 | "\n",
418 | "I import the 24 postions in [FEN](https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation) notation."
419 | ]
420 | },
421 | {
422 | "cell_type": "code",
423 | "execution_count": 20,
424 | "metadata": {},
425 | "outputs": [
426 | {
427 | "name": "stdout",
428 | "output_type": "stream",
429 | "text": [
430 | "time: 6 ms\n"
431 | ]
432 | }
433 | ],
434 | "source": [
435 | "positions = [\n",
436 | "\"1k1r4/pp1b1R2/3q2pp/4p3/2B5/4Q3/PPP2B2/2K5 b - - 0 1\",\n",
437 | "\"3r1k2/4npp1/1ppr3p/p6P/P2PPPP1/1NR5/5K2/2R5 w - - 0 1\",\n",
438 | "\"2q1rr1k/3bbnnp/p2p1pp1/2pPp3/PpP1P1P1/1P2BNNP/2BQ1PRK/7R b - - 0 1\",\n",
439 | "\"rnbqkb1r/p3pppp/1p6/2ppP3/3N4/2P5/PPP1QPPP/R1B1KB1R w KQkq - 0 1\",\n",
440 | "\"r1b2rk1/2q1b1pp/p2ppn2/1p6/3QP3/1BN1B3/PPP3PP/R4RK1 w - - 0 1\",\n",
441 | "\"2r3k1/pppR1pp1/4p3/4P1P1/5P2/1P4K1/P1P5/8 w - - 0 1\",\n",
442 | "\"1nk1r1r1/pp2n1pp/4p3/q2pPp1N/b1pP1P2/B1P2R2/2P1B1PP/R2Q2K1 w - - 0 1\",\n",
443 | "\"4b3/p3kp2/6p1/3pP2p/2pP1P2/4K1P1/P3N2P/8 w - - 0 1\",\n",
444 | "\"2kr1bnr/pbpq4/2n1pp2/3p3p/3P1P1B/2N2N1Q/PPP3PP/2KR1B1R w - - 0 1\",\n",
445 | "\"3rr1k1/pp3pp1/1qn2np1/8/3p4/PP1R1P2/2P1NQPP/R1B3K1 b - - 0 1\",\n",
446 | "\"2r1nrk1/p2q1ppp/bp1p4/n1pPp3/P1P1P3/2PBB1N1/4QPPP/R4RK1 w - - 0 1\",\n",
447 | "\"r3r1k1/ppqb1ppp/8/4p1NQ/8/2P5/PP3PPP/R3R1K1 b - - 0 1\",\n",
448 | "\"r2q1rk1/4bppp/p2p4/2pP4/3pP3/3Q4/PP1B1PPP/R3R1K1 w - - 0 1\",\n",
449 | "\"rnb2r1k/pp2p2p/2pp2p1/q2P1p2/8/1Pb2NP1/PB2PPBP/R2Q1RK1 w - - 0 1\",\n",
450 | "\"2r3k1/1p2q1pp/2b1pr2/p1pp4/6Q1/1P1PP1R1/P1PN2PP/5RK1 w - - 0 1\",\n",
451 | "\"r1bqkb1r/4npp1/p1p4p/1p1pP1B1/8/1B6/PPPN1PPP/R2Q1RK1 w kq - 0 1\",\n",
452 | "\"r2q1rk1/1ppnbppp/p2p1nb1/3Pp3/2P1P1P1/2N2N1P/PPB1QP2/R1B2RK1 b - - 0 1\",\n",
453 | "\"r1bq1rk1/pp2ppbp/2np2p1/2n5/P3PP2/N1P2N2/1PB3PP/R1B1QRK1 b - - 0 1\",\n",
454 | "\"3rr3/2pq2pk/p2p1pnp/8/2QBPP2/1P6/P5PP/4RRK1 b - - 0 1\",\n",
455 | "\"r4k2/pb2bp1r/1p1qp2p/3pNp2/3P1P2/2N3P1/PPP1Q2P/2KRR3 w - - 0 1\",\n",
456 | "\"3rn2k/ppb2rpp/2ppqp2/5N2/2P1P3/1P5Q/PB3PPP/3RR1K1 w - - 0 1\",\n",
457 | "\"2r2rk1/1bqnbpp1/1p1ppn1p/pP6/N1P1P3/P2B1N1P/1B2QPP1/R2R2K1 b - - 0 1\",\n",
458 | "\"r1bqk2r/pp2bppp/2p5/3pP3/P2Q1P2/2N1B3/1PP3PP/R4RK1 b kq - 0 1\",\n",
459 | "\"r2qnrnk/p2b2b1/1p1p2pp/2pPpp2/1PP1P3/PRNBB3/3QNPPP/5RK1 w - - 0 1\",\n",
460 | "]\n",
461 | "solutions = [\"Qd1+\",\"d5\",\"f5\",\"e6\",\"a4\",\"g6\",\"Nf6\",\"f5\",\"f5\",\"Ne5\",\"f4\",\"Bf5\",\"b4\",\n",
462 | " \"Qd2 Qe1\",\"Qxg7+\",\"Ne4\",\"h5\",\"Nb3\",\"Rxe4\",\"g4\",\"Nh6\",\"Bxe4\",\"f6\",\"f4\"]"
463 | ]
464 | },
465 | {
466 | "cell_type": "markdown",
467 | "metadata": {},
468 | "source": [
469 | "We can compute the search for each position and compare with the solution. For example position 9."
470 | ]
471 | },
472 | {
473 | "cell_type": "code",
474 | "execution_count": 21,
475 | "metadata": {},
476 | "outputs": [
477 | {
478 | "name": "stdout",
479 | "output_type": "stream",
480 | "text": [
481 | "d5\n",
482 | "d5\n"
483 | ]
484 | },
485 | {
486 | "data": {
487 | "image/svg+xml": [
488 | ""
489 | ],
490 | "text/plain": [
491 | ""
492 | ]
493 | },
494 | "execution_count": 21,
495 | "metadata": {},
496 | "output_type": "execute_result"
497 | },
498 | {
499 | "name": "stdout",
500 | "output_type": "stream",
501 | "text": [
502 | "time: 2min 50s\n"
503 | ]
504 | }
505 | ],
506 | "source": [
507 | "movehistory =[]\n",
508 | "board = chess.Board(positions[1])\n",
509 | "boardvalue = init_evaluate_board()\n",
510 | "\n",
511 | "mov = selectmove(6)\n",
512 | "print(solutions[1])\n",
513 | "print(board.san(mov))\n",
514 | "\n",
515 | "make_move(mov)\n",
516 | "SVG(chess.svg.board(board=board,size=400,lastmove=mov))"
517 | ]
518 | },
519 | {
520 | "cell_type": "markdown",
521 | "metadata": {},
522 | "source": [
523 | "Now I loop over all positions and count the number of correct solutions for different search depth."
524 | ]
525 | },
526 | {
527 | "cell_type": "code",
528 | "execution_count": 24,
529 | "metadata": {},
530 | "outputs": [
531 | {
532 | "name": "stdout",
533 | "output_type": "stream",
534 | "text": [
535 | "Move problem 0 Qd1+ / Solution: Qd1+\n",
536 | "OK\n",
537 | "Move problem 1 d5 / Solution: d5\n",
538 | "OK\n",
539 | "Move problem 2 Kg8 / Solution: f5\n",
540 | "wrong\n",
541 | "Move problem 3 a4 / Solution: e6\n",
542 | "wrong\n",
543 | "Move problem 4 a4 / Solution: a4\n",
544 | "OK\n",
545 | "Move problem 5 g6 / Solution: g6\n",
546 | "OK\n",
547 | "Move problem 6 g4 / Solution: Nf6\n",
548 | "wrong\n",
549 | "Move problem 7 Kf2 / Solution: f5\n",
550 | "wrong\n",
551 | "Move problem 8 Kb1 / Solution: f5\n",
552 | "wrong\n",
553 | "Move problem 9 Re7 / Solution: Ne5\n",
554 | "wrong\n",
555 | "Move problem 10 f4 / Solution: f4\n",
556 | "OK\n",
557 | "Move problem 11 Red8 / Solution: Bf5\n",
558 | "wrong\n",
559 | "Move problem 12 Bc3 / Solution: b4\n",
560 | "wrong\n",
561 | "Move problem 13 a4 / Solution: Qd2 Qe1\n",
562 | "wrong\n",
563 | "Move problem 14 a4 / Solution: Qxg7+\n",
564 | "wrong\n",
565 | "Move problem 15 a4 / Solution: Ne4\n",
566 | "wrong\n",
567 | "Move problem 16 Nc5 / Solution: h5\n",
568 | "wrong\n",
569 | "Move problem 17 Kh8 / Solution: Nb3\n",
570 | "wrong\n",
571 | "Move problem 18 Rh8 / Solution: Rxe4\n",
572 | "wrong\n",
573 | "Move problem 19 g4 / Solution: g4\n",
574 | "OK\n",
575 | "Move problem 20 a4 / Solution: Nh6\n",
576 | "wrong\n",
577 | "Move problem 21 Kh8 / Solution: Bxe4\n",
578 | "wrong\n",
579 | "Move problem 22 Rg8 / Solution: f6\n",
580 | "wrong\n",
581 | "Move problem 23 f4 / Solution: f4\n",
582 | "OK\n",
583 | "Number of correct solved: 7\n",
584 | "time: 29min 12s\n"
585 | ]
586 | }
587 | ],
588 | "source": [
589 | "depth = 5\n",
590 | "solved = 0\n",
591 | "for i in range(24):\n",
592 | " movehistory =[]\n",
593 | " board = chess.Board(positions[i])\n",
594 | " boardvalue = init_evaluate_board()\n",
595 | "\n",
596 | " mov = selectmove(depth)\n",
597 | " print(\"Move problem \" + str(i+1) + \" \" + board.san(mov) + \" / Solution: \" + solutions[i])\n",
598 | "\n",
599 | " if str(board.san(mov)) in str(solutions[i]):\n",
600 | " solved = solved + 1\n",
601 | " print(\"OK\")\n",
602 | " else:\n",
603 | " print(\"wrong\")\n",
604 | "\n",
605 | "print(\"Number of correct solved: \" + str(solved))"
606 | ]
607 | },
608 | {
609 | "cell_type": "markdown",
610 | "metadata": {},
611 | "source": [
612 | "For a search depth of 3 it solves 4 positions in 15 sec.\n",
613 | "\n",
614 | "For a search depth of 4 it solves 5 positions in 2 min 14 sec.\n",
615 | "\n",
616 | "For a search depth of 5 it solves 7 positions in 29 min 12 sec.\n",
617 | "\n",
618 | "The program is now able to solve some of the 24 test positions in several minutes. In the next articles I will try to improve the speed and so the quality of the search."
619 | ]
620 | }
621 | ],
622 | "metadata": {
623 | "kernelspec": {
624 | "display_name": "Python 3",
625 | "language": "python",
626 | "name": "python3"
627 | },
628 | "language_info": {
629 | "codemirror_mode": {
630 | "name": "ipython",
631 | "version": 3
632 | },
633 | "file_extension": ".py",
634 | "mimetype": "text/x-python",
635 | "name": "python",
636 | "nbconvert_exporter": "python",
637 | "pygments_lexer": "ipython3",
638 | "version": "3.6.3"
639 | }
640 | },
641 | "nbformat": 4,
642 | "nbformat_minor": 2
643 | }
644 |
--------------------------------------------------------------------------------