Compare commits

..

203 Commits
v0.1.0 ... main

Author SHA1 Message Date
Karma Riuk
ef25455ac0 added gif for readme
Some checks failed
pre-release / Pre Release (push) Has been cancelled
2025-02-19 15:00:37 +01:00
Karma Riuk
4aa3d4ba33 updated readme
Some checks are pending
pre-release / Pre Release (push) Waiting to run
2025-02-19 13:56:14 +01:00
Karma Riuk
cb1a1274a0 updated readme
Some checks are pending
pre-release / Pre Release (push) Waiting to run
2025-02-19 13:54:30 +01:00
Karma Riuk
1ff9b33bda updated makefile and readme
Some checks are pending
pre-release / Pre Release (push) Waiting to run
2025-02-19 13:48:22 +01:00
Karma Riuk
f7733c1317 updated main 2025-02-19 13:44:17 +01:00
Karma Riuk
0aa8b7a16f updated README
Some checks are pending
pre-release / Pre Release (push) Waiting to run
2025-02-19 13:39:15 +01:00
Karma Riuk
ebf5934909 added possibility to make moves from string (with tests) 2025-02-16 12:44:58 +01:00
Karma Riuk
ecba8e3c6e minor fix 2025-02-16 11:37:52 +01:00
Karma Riuk
1834b3b8ce fixed the ordering of the moves 2025-02-16 11:13:59 +01:00
Karma Riuk
ec4cc85ca3 logging depth reached with iterative deepening
Some checks failed
tagged-release / Tagged Release (push) Has been cancelled
2025-02-16 10:59:04 +01:00
Karma Riuk
97f9def306 generalized the position counter
Some checks failed
pre-release / Pre Release (push) Has been cancelled
tagged-release / Tagged Release (push) Has been cancelled
2025-02-16 10:42:30 +01:00
Karma Riuk
f68dedeb20 made v6, does iterative deepening until the
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
thinking time runs out
2025-02-16 10:32:15 +01:00
Karma Riuk
4eae988999 fixed warnings 2025-02-16 10:32:09 +01:00
Karma Riuk
62a35ecafd made ai better at endgames (it's just a start)
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
2025-02-16 10:12:06 +01:00
Karma Riuk
5cbe57dc55 Removed the python version and kept just the c++ one
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
2025-02-16 09:36:04 +01:00
Karma Riuk
0145664567 minor fixes
Some checks are pending
pre-release / Pre Release (push) Waiting to run
2025-02-16 09:30:59 +01:00
Karma Riuk
959ecdeb7d added the opponenent pawn attack map move better ordering 2025-02-16 09:29:50 +01:00
Karma Riuk
adf21ee021 minor stuff 2025-02-09 21:18:51 +01:00
Karma Riuk
989225c2d7 fixed bugs 2025-02-07 20:08:25 +01:00
Karma Riuk
aef6fc39e3 fixed slight bug 2025-02-07 19:39:52 +01:00
Karma Riuk
2260cd918a implemented promotion for white 2025-02-07 19:39:51 +01:00
Karma Riuk
3bc5b75f1e implemented v4 that resolves all the captures
after looking at the next two moves (all of them)
2025-02-07 19:14:09 +01:00
Karma Riuk
377e5dfdcc implemented move ordering to improve on alpha_beta
pruning
2025-02-07 18:24:57 +01:00
Karma Riuk
10de9828be implemented alpha beta pruning 2025-02-07 17:49:08 +01:00
Karma Riuk
9942832b18 caching check and nlm 2025-02-07 17:35:14 +01:00
Karma Riuk
5b45fc32c0 annoying 2025-02-07 16:18:11 +01:00
Karma Riuk
d28008d913 committing to stash and compare 2025-02-07 15:08:53 +01:00
Karma Riuk
86beb9cc85 renamed v1_simple to v1_pure_minimax 2025-02-07 15:08:10 +01:00
Karma Riuk
8edde1a8b1 caching checks and no legal moves 2025-02-07 14:15:10 +01:00
Karma Riuk
182d183247 committing to stash and compare 2025-02-07 14:02:56 +01:00
Karma Riuk
1b14ed693d made small opti 2025-02-07 12:04:20 +01:00
Karma Riuk
a80fc9482d base for testing optimization 2025-02-07 11:08:59 +01:00
Karma Riuk
88b2d01cf8 made v1_simple multithreaded again 2025-02-07 10:03:37 +01:00
Karma Riuk
979d44fad0 fixed small bug 2025-02-07 10:03:32 +01:00
Karma Riuk
db87c2ec8b minor stuff 2025-02-07 09:52:37 +01:00
Karma Riuk
f3e28d2646 found better terminal condition for v1_simple 2025-02-07 09:52:09 +01:00
Karma Riuk
7e888bba61 made v1_simple single threaded because it was
getting messy
2025-02-07 09:51:49 +01:00
Karma Riuk
1d83c066f6 made v0_random thinking longer 2025-02-07 09:51:22 +01:00
Karma Riuk
2764787e63 made v1_simple look 4 moves into the future, it's
parallelized
2025-02-07 00:34:22 +01:00
Karma Riuk
c71cabb3cd fixed tests 2025-02-07 00:29:49 +01:00
Karma Riuk
0525bd565e minor fixes 2025-02-07 00:29:39 +01:00
Karma Riuk
695cef33df fixed test execution 2025-02-07 00:29:17 +01:00
Karma Riuk
75b6c35cc5 if the ai found the move before the thinking_time
is over, stop the thread to return
2025-02-06 23:46:34 +01:00
Karma Riuk
74fa99fe7b faking some thought for the random player 2025-02-06 23:46:06 +01:00
Karma Riuk
0274f44647 minor fix 2025-02-06 23:46:00 +01:00
Karma Riuk
a5abe39aa4 added a position to perft 2025-02-06 23:37:55 +01:00
Karma Riuk
e056ef0805 minor modifications to ai v1 2025-02-06 22:57:06 +01:00
Karma Riuk
dc6631f79c made AIs aware what colour they were playing 2025-02-06 22:56:41 +01:00
Karma Riuk
d6aa977a15 added the 50-move draw rule 2025-02-06 22:39:11 +01:00
Karma Riuk
6e567f2f11 added the check for insufficient material 2025-02-06 22:34:48 +01:00
Karma Riuk
fced9757c2 made figuring whether the board is terminal easier 2025-02-06 22:10:47 +01:00
Karma Riuk
04134f013c implemented the ai vs ai controller
Some checks failed
pre-release / Pre Release (push) Has been cancelled
2025-02-06 21:21:18 +01:00
Karma Riuk
47cf8b3539 unified controller interface 2025-02-06 20:59:05 +01:00
Karma Riuk
a47130e5d0 fixed variable name 2025-02-06 20:41:17 +01:00
Karma Riuk
e0a52f57b7 made the thinking time of AIs common to all of them 2025-02-06 20:38:27 +01:00
Karma Riuk
74e4d596a7 very easily implemented the human_vs_ai controller
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
(god i love a good design)
2025-02-06 20:12:12 +01:00
Karma Riuk
60e1a77fcb fixed slight bug 2025-02-06 20:11:58 +01:00
Karma Riuk
46a835df4b moved some code around because it made more sense 2025-02-06 20:11:11 +01:00
Karma Riuk
10257351cb created a no-op gui (to run the games without
showing them, can be useful)
2025-02-06 19:53:11 +01:00
Karma Riuk
96f0ec5399 implemented full manual controller 2025-02-06 19:42:48 +01:00
Karma Riuk
e376d67c83 added to string for piece and colour 2025-02-06 19:42:30 +01:00
Karma Riuk
d8557d02df now showing target squares of legal moves 2025-02-06 19:11:30 +01:00
Karma Riuk
d08a5cca39 extracted a function to utils 2025-02-06 19:11:03 +01:00
Karma Riuk
058616fa89 extracted the drawing of the annotation in it's
own function
2025-02-06 18:45:11 +01:00
Karma Riuk
b8627ace14 quick little refactoring 2025-02-06 18:34:54 +01:00
Karma Riuk
77f77ac04e now we draw the annotations for the squares 2025-02-06 18:23:44 +01:00
Karma Riuk
56b74318c1 moved colours 2025-02-06 18:23:19 +01:00
Karma Riuk
811acaa479 drawing pieces correctly 2025-02-06 17:58:49 +01:00
Karma Riuk
1ae0cad802 fixed colours 2025-02-06 17:34:37 +01:00
Karma Riuk
f540246817 we're starting to get somewhere here 2025-02-06 17:31:16 +01:00
Karma Riuk
42484f4217 small refactoring to get rid of compiler warning 2025-02-06 16:18:13 +01:00
Karma Riuk
72f3431418 big refactoring: put everything in model to allow
for better mvc structure (yes i'm doing view in cpp)
2025-02-06 16:15:56 +01:00
Karma Riuk
1231e4da92 added the less than operator to store the objects
in maps
2025-02-06 15:57:21 +01:00
Karma Riuk
32c7832001 cleaned up the ais a bit 2025-02-06 15:57:06 +01:00
Karma Riuk
5f093907f3 small refactoring 2025-02-06 15:45:32 +01:00
Karma Riuk
6d8a3f7dc0 made lambda capture values by reference, not by
value
2025-02-06 10:55:25 +01:00
Karma Riuk
108c0c3b90 added 500 equal positions to start from for the
AIs to fight
2025-02-06 10:44:54 +01:00
Karma Riuk
e821814cd1 updated the code of main
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
2025-02-06 10:43:28 +01:00
Karma Riuk
2899820bcc created a very stupid player that plays random
moves
2025-02-06 10:43:07 +01:00
Karma Riuk
6fd9b15261 moved stickfosh to the ais folder, since we want
to create multiple versions and make them fight
2025-02-06 10:42:42 +01:00
Karma Riuk
97b3eeb984 first version of stickfosh, simply evaluates a
Some checks failed
tagged-release / Tagged Release (push) Has been cancelled
position based on the material (no AB-pruning)
2025-02-05 17:11:09 +01:00
Karma Riuk
982f933698 removed useless include 2025-02-05 17:11:00 +01:00
Karma Riuk
d21fcde21c renamed stickfosh to perft, it's more appropriate 2025-02-05 16:23:16 +01:00
Karma Riuk
a47dc9b695 Merge branch 'main' into opti-2-compacting-move
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
2025-02-05 15:37:57 +01:00
Karma Riuk
e8ca1db8d6 using the thread pool for each move in the top
Some checks failed
tagged-release / Tagged Release (push) Has been cancelled
level of the search (x4~ improvement)
2025-02-05 15:30:55 +01:00
Karma Riuk
73b2a5b51b created threadpool 2025-02-05 15:17:10 +01:00
Karma Riuk
3f3017a389 removed useless comments 2025-02-05 14:41:23 +01:00
Karma Riuk
f768d16a7e removed en passant from move 2025-02-05 14:41:16 +01:00
Karma Riuk
a658673d2b remove castle side from move 2025-02-05 14:36:00 +01:00
Karma Riuk
222adbb8f0 removed the is_capturing flag of move 2025-02-05 14:25:35 +01:00
Karma Riuk
c55645ad73 using global peft
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
2025-02-04 21:53:07 +01:00
Karma Riuk
1b0af3b486 annotated each depths with its time (to then
compare with the different optimizations)
2025-02-04 21:52:07 +01:00
Karma Riuk
df20060075 added global peft 2025-02-04 21:51:58 +01:00
Karma Riuk
151a50fba9 made move str better (includes promotion) 2025-02-04 21:51:14 +01:00
Karma Riuk
609d5f8c98 fixed castle right rook capture coords 2025-02-04 21:50:43 +01:00
Karma Riuk
e9c96e83da fixed en passant offset 2025-02-04 21:50:30 +01:00
Karma Riuk
76310400e8 added empty line before move counts to ease copying 2025-02-03 23:41:27 +01:00
Karma Riuk
e5bb2d1a5f printing moves normally 2025-02-03 23:41:16 +01:00
Karma Riuk
4c86d5e815 fixed castling rights on capture 2025-02-03 23:40:30 +01:00
Karma Riuk
b1812f1df6 removed the inclusion of move in piece 2025-02-03 19:10:49 +01:00
Karma Riuk
9d21141d3c fixed pawn capture + promotion action 2025-02-03 19:10:07 +01:00
Karma Riuk
974af5d19f we can now print a move (stockfish stile) 2025-02-03 19:09:21 +01:00
Karma Riuk
df07b4399d minor fix 2025-02-03 19:09:13 +01:00
Karma Riuk
fa59784230 fixed castling rights that were not removed
properly
2025-02-03 19:08:26 +01:00
Karma Riuk
be34045f92 minor fix 2025-02-03 19:08:21 +01:00
Karma Riuk
4eaed699c0 fixed en passant capture 2025-02-03 16:26:31 +01:00
Karma Riuk
310abb0611 fixed slight mistake 2025-02-03 16:12:20 +01:00
Karma Riuk
b70ca5302a put back good code 2025-02-03 15:36:11 +01:00
Karma Riuk
83af5f0b97 removed problemaic code 2025-02-03 15:35:43 +01:00
Karma Riuk
72ba6a80ae made types behave 2025-02-03 15:35:03 +01:00
Karma Riuk
1723356f62 removed useless stuff 2025-02-03 15:34:52 +01:00
Karma Riuk
75adb4b1ba removed already existing variable 2025-02-03 15:34:12 +01:00
Karma Riuk
6951b860da forgot to push the normal moves for the pawn, fixed it now 2025-02-03 14:05:32 +01:00
Karma Riuk
805d9fa95c fixed implicit type casting (warning) 2025-02-03 14:05:16 +01:00
Karma Riuk
4df7cbf01a fixed piece recognition 2025-02-03 14:04:52 +01:00
Karma Riuk
00137e022a fixed move for position initial guard 2025-02-03 14:04:27 +01:00
Karma Riuk
570b8df4a7 fixed look_direction 2025-02-03 14:04:18 +01:00
Karma Riuk
9a66a71c38 handling better the output of perft 2025-02-03 13:59:33 +01:00
Karma Riuk
41e4119468 reordered members of enum 2025-02-03 13:51:43 +01:00
Karma Riuk
e7fc1b06de fixed the is_within_bounds check 2025-02-03 13:50:48 +01:00
Karma Riuk
5f79b81ce4 added validity check when creating coords from index 2025-02-03 13:50:34 +01:00
Karma Riuk
e5819ee83b minor fix 2025-02-03 13:50:15 +01:00
Karma Riuk
58109c5120 fixed condition 2025-02-03 13:49:40 +01:00
Karma Riuk
6478176a7d fixed check condition 2025-02-03 13:47:27 +01:00
Karma Riuk
ab088da33c removed useless this-> 2025-02-03 13:47:17 +01:00
Karma Riuk
31799cb0ec added optimization and warning flags to the compiler 2025-02-03 13:44:53 +01:00
Karma Riuk
96dcbc0a6c actually counting milliseconds now 2025-02-03 13:41:45 +01:00
Karma Riuk
ce06097063 i forgot to actually implement the root legal_moves haha 2025-02-03 01:12:30 +01:00
Karma Riuk
3fc2aa73ad actually counting milliseconds now 2025-02-03 01:06:07 +01:00
Karma Riuk
de6a03ce08 fixed mistake 2025-02-03 01:05:59 +01:00
Karma Riuk
dc3031ddc1 we can finally print pieces and colours properly 2025-02-03 01:05:37 +01:00
Karma Riuk
5e4b880aad checking perft to see if my cpp implementation is
correct
2025-02-03 00:33:12 +01:00
Karma Riuk
540ffa4fb3 fixed slight mistake 2025-02-03 00:33:04 +01:00
Karma Riuk
21136a26f0 implemented the last details to make everything
work (hopefully)
2025-02-03 00:08:37 +01:00
Karma Riuk
fa57dcfc30 apparently the default value of a paramter goes in
the declaration and not the defintion, i didn't know that
2025-02-03 00:08:13 +01:00
Karma Riuk
8da349579d make_move now checks castle rights after each move 2025-02-02 23:52:24 +01:00
Karma Riuk
f0467bc516 make_move now handles castling 2025-02-02 23:34:10 +01:00
Karma Riuk
a7cf8e0c21 make_move now handles the setting of en passant
targets
2025-02-02 23:33:52 +01:00
Karma Riuk
2128159914 make_move now handles promotion 2025-02-02 23:33:29 +01:00
Karma Riuk
c564add509 removed useless variable 2025-02-02 23:33:20 +01:00
Karma Riuk
31b0656332 fixed promotion missing colour 2025-02-02 23:16:53 +01:00
Karma Riuk
24165bb5bb made comment more accurate 2025-02-02 23:14:13 +01:00
Karma Riuk
4c8fdfa3b4 implemented en passant in legal moves 2025-02-02 23:13:37 +01:00
Karma Riuk
84c3af2bb1 fully support FEN strings now
Some checks failed
pre-release / Pre Release (push) Has been cancelled
tagged-release / Tagged Release (push) Has been cancelled
2025-02-02 23:05:42 +01:00
Karma Riuk
22acfc5027 fixed slight bug 2025-02-02 23:05:26 +01:00
Karma Riuk
1ff4b6b1f4 implemented the en passant target support for FEN
strings
2025-02-02 22:52:46 +01:00
Karma Riuk
2493892b05 added algebraic support for coordinates 2025-02-02 22:39:06 +01:00
Karma Riuk
bdd8011577 now support the castling sides of the FEN string 2025-02-02 22:05:33 +01:00
Karma Riuk
5549c77177 updated testing library 2025-02-02 22:04:41 +01:00
Karma Riuk
be016dcbcc fixed type mismatch 2025-02-02 22:04:31 +01:00
Karma Riuk
efd7bf6794 gave default values to the board fields because
they were causing undefinied behaviour
2025-02-02 22:04:04 +01:00
Karma Riuk
81b24d3082 extended FEN support to include who's turn it is 2025-02-02 21:39:33 +01:00
Karma Riuk
497da2de8e updated assert equals to fail fast 2025-02-02 21:39:02 +01:00
Karma Riuk
0574a11b32 fixed minor bug 2025-02-02 21:37:43 +01:00
Karma Riuk
472f9e4c7c made CastleSide a normal enum again to use &
Some checks failed
tagged-release / Tagged Release (push) Has been cancelled
2025-02-02 21:30:37 +01:00
Karma Riuk
f5292fa6d7 added small comment 2025-02-02 21:22:38 +01:00
Karma Riuk
8a3a92f80f minor change 2025-02-02 21:14:31 +01:00
Karma Riuk
b7ad7b3111 implemented king legal moves 2025-02-02 21:14:14 +01:00
Karma Riuk
b329c41bea got rid of magic numbers 2025-02-02 21:13:30 +01:00
Karma Riuk
1625345f08 fixed board.cpp 2025-02-02 20:28:50 +01:00
Karma Riuk
a790677bb7 implemented queen legal moves 2025-02-02 20:28:41 +01:00
Karma Riuk
703dcef59f implemented knight legal moves 2025-02-02 20:24:42 +01:00
Karma Riuk
d74222c2f4 implemented rook legal moves 2025-02-02 20:19:07 +01:00
Karma Riuk
01c912435b implemented bishop legal moves 2025-02-02 20:17:36 +01:00
Karma Riuk
8bf164cb05 extracted coords to its own hpp 2025-02-02 20:17:27 +01:00
Karma Riuk
e08dbb913e fixed board and pawn moves 2025-02-02 19:49:21 +01:00
Karma Riuk
fea3f6c98a fixed signature 2025-02-02 18:24:23 +01:00
Karma Riuk
da55f0085f implemented (partially) the pawn move 2025-02-02 18:17:37 +01:00
Karma Riuk
95327ec653 cleaned up main 2025-02-02 18:17:19 +01:00
Karma Riuk
1a4e33201e when loading a fen ref, i return the object and
not a pointer
2025-02-02 18:17:02 +01:00
Karma Riuk
d6baf1ee53 added main to gitignore 2025-02-02 18:16:45 +01:00
Karma Riuk
67e377bfde made some seperation of concerns 2025-02-02 17:16:37 +01:00
Karma Riuk
29453fbb14 implemented some stuff 2025-02-02 17:13:55 +01:00
Karma Riuk
585e392b6a created move struct 2025-02-02 16:49:24 +01:00
Karma Riuk
d436c5a032 made stuff a tiny bit more compact 2025-02-02 16:49:10 +01:00
Karma Riuk
08c0a3b50b added test support to the makefile 2025-02-02 16:37:59 +01:00
Karma Riuk
0ae37a3eba did some stuff to make makefile work... it was
painful
2025-02-02 16:26:46 +01:00
Karma Riuk
c83129a0d5 added cpp gitignore 2025-02-02 15:31:45 +01:00
Karma Riuk
acfa27c83e removed binary files 2025-02-02 15:21:04 +01:00
Karma Riuk
85a5bfa328 moved some stuff around 2025-02-02 15:15:14 +01:00
Karma Riuk
139a5c7d8f fixed missing imports
Some checks are pending
pre-release / Pre Release (push) Waiting to run
2025-02-02 14:54:17 +01:00
Karma Riuk
3a2988d351 fixed permissions of fields 2025-02-02 14:53:17 +01:00
Karma Riuk
c758d1854f removed useless imports 2025-02-02 14:52:47 +01:00
Karma Riuk
53a0755547 extracted the assert equals to another file to
have mulitple test files
2025-02-02 14:52:13 +01:00
Karma Riuk
4792daf127 cpp board can go back and forth from FEN
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
2025-02-02 14:50:44 +01:00
Karma Riuk
947114877b moved everything related to python in the python
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
folder
2025-02-02 13:20:59 +01:00
Karma Riuk
166e1c7664 added some positions to test the move generation 2025-02-02 13:18:25 +01:00
Karma Riuk
84d73511d2 fixed a bunch of issues with the move generation 2025-02-02 12:58:05 +01:00
Karma Riuk
4bb068b2a5 added complete FEN support both for reading and writing 2025-02-01 18:34:41 +01:00
Karma Riuk
92e1ff26fc added pawn promotion 2025-02-01 16:43:02 +01:00
Karma Riuk
c7884e227b made the initial board a memeber of the board
Some checks failed
pre-release / Pre Release (push) Has been cancelled
module
2025-02-01 10:37:02 +01:00
Karma Riuk
e6fafc8081 implemented en passant
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
2025-01-31 18:55:02 +01:00
Karma Riuk
1be71bf203 fixed giga spelling mistake (it's castling right, not castling write) 2025-01-31 18:27:44 +01:00
Karma Riuk
e862ab6b0b added comments to the make_move function 2025-01-31 18:24:52 +01:00
Karma Riuk
6c0819428e implemented checkmate
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
2025-01-31 18:20:12 +01:00
Karma Riuk
496207861e implemented castling
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
2025-01-31 16:35:41 +01:00
Karma Riuk
87e8e75c04 simplified moves, made them algebraic 2025-01-31 16:35:29 +01:00
Karma Riuk
13e3675665 fixed FEN reading for castling writes 2025-01-31 16:33:56 +01:00
Karma Riuk
2e27e7b703 implemented king moves (missing castles) 2025-01-31 15:22:49 +01:00
Karma Riuk
d7863e0d81 added capturing circle around possible capture for
legal moves
2025-01-31 14:34:54 +01:00
Karma Riuk
a3b7df4e4c minor fix again 2025-01-31 14:28:18 +01:00
Karma Riuk
806a4a7f65 minor fix 2025-01-31 14:09:35 +01:00
Karma Riuk
052f815ee1 we have nice looing gui now
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
2025-01-31 14:08:21 +01:00
79 changed files with 3413 additions and 792 deletions

198
.gitignore vendored
View File

@ -1,174 +1,34 @@
# Byte-compiled / optimized / DLL files # Prerequisites
__pycache__/ *.d
*.py[cod]
*$py.class
# C extensions # Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so *.so
*.dylib
*.dll
# Distribution / packaging # Fortran module files
.Python *.mod
build/ *.smod
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller # Compiled Static libraries
# Usually these files are written by a python script from a template *.lai
# before PyInstaller builds the exe, so as to inject date/other infos into it. *.la
*.manifest *.a
*.spec *.lib
# Installer logs # Executables
pip-log.txt *.exe
pip-delete-this-directory.txt *.out
*.appobj/
# Unit test / coverage reports test_bin/
htmlcov/ main
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc

59
Makefile Normal file
View File

@ -0,0 +1,59 @@
CXXFLAGS += -O3 -Wall
# CXXFLAGS += -pg
# Add .d to Make's recognized suffixes.
SUFFIXES += .d
#We don't need to clean up when we're making these targets
NODEPS:=clean tags svn
#Find all the C++ files in the src/ directory
SOURCES:=$(shell find src/ -name "*.cpp")
OBJFILES := $(patsubst src/%.cpp,obj/%.o,$(SOURCES))
#These are the dependency files, which make will clean up after it creates them
DEPFILES:=$(patsubst %.cpp,%.d,$(SOURCES))
#Don't create dependencies when we're cleaning, for instance
ifeq (0, $(words $(findstring $(MAKECMDGOALS), $(NODEPS))))
#Chances are, these files don't exist. GMake will create them and
#clean up automatically afterwards
-include $(DEPFILES)
endif
#This is the rule for creating the dependency files
src/%.d: src/%.cpp
$(CXX) $(CXXFLAGS) -MM -MT '$(patsubst src/%.cpp,obj/%.o,$<)' $< -MF $@
#This rule does the compilation
obj/%.o:
@mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) -o $@ -c $<
stickfosh: $(OBJFILES)
$(CXX) $(CXXFLAGS) $(OBJFILES) $(LOADLIBES) $(LDLIBS) -o stickfosh -lsfml-graphics -lsfml-window -lsfml-system
clean:
rm -rf obj/* $(DEPFILES) test_bin/
# --- Test Support ---
# Find all test source files in the tests directory
TESTS := $(shell find tests -name "*.cpp")
# Define corresponding test executable names, e.g. tests/foo.cpp -> test_bin/foo
TEST_BIN := $(patsubst tests/%.cpp,test_bin/%,$(TESTS))
LIBS := $(filter-out obj/main.o,$(OBJFILES))
# Pattern rule: how to build a test executable from a test source file.
# You can adjust CXXFLAGS or add include directories if needed.
test_bin/%: tests/%.cpp $(LIBS)
@echo $(LIBS)
@mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) -o $@ $< $(LIBS) -lsfml-graphics -lsfml-window -lsfml-system
# The 'test' target builds all tests and then runs each one.
.PHONY: test
test: $(TEST_BIN)
@echo "Running all tests..."
@for t in $(TEST_BIN); do \
echo "---- Running $$t ----"; \
./$$t || exit 1; \
done

View File

@ -1,3 +1,86 @@
# Stickfosh # Stickfosh
> Stockfish, but worse :) > [Stockfish](https://stockfishchess.org), but worse :)
## Overview
This project is a **Chess AI** built using the **Model-View-Controller (MVC)
pattern**. It provides a modular framework for playing chess, allowing both
**human vs AI** and **AI vs AI** matches. The AI has undergone several
iterations, improving its decision-making capabilities through enhancements like
**alpha-beta pruning**, **move ordering**, **iterative deepening**, and
**transposition tables**.
## Features
- **MVC Architecture**: The project follows the MVC pattern for clean separation of concerns:
- **Model**: Handles chess rules, board state, and AI logic.
- **View**: GUI and NoOp (that shows nothing, but is useful for debugging the AIs) rendering options.
- **Controller**: Manages interactions between players and the game.
- **Multiple AI Versions**: Several AI versions with increasing complexity have been implemented.
- **AI vs AI Matches**: A dedicated mode to watch different AI versions compete.
- **Human vs AI Mode**: Play against the AI using a graphical interface.
- **FEN Support**: Load chess positions using FEN notation.
- **Performance Testing (Perft)**: Built-in performance testing for move generation.
## AI Iterations
This project has undergone multiple AI improvements, including:
1. **v0 Random AI**: Selects moves randomly.
1. **v1 Pure Minimax**: Implements basic minimax search.
1. **v2 Alpha-Beta Pruning**: Optimizes minimax with pruning.
1. **v3 Move Ordering**: Prioritizes moves to improve search efficiency.
1. **v4 Search Captures**: Enhances move ordering by focusing on captures.
1. **v5 Better Endgame**: Introduces heuristics for endgame play.
1. **v6 Iterative Deepening**: Dynamically adjusts search depth for better performance.
1. **v7 Transposition Tables**: Caches board states to reduce redundant computations.
## Installation & Usage
### Prerequisites
- C++ Compiler (C++17 or later)
- `make`
- SFML (for GUI rendering)
### Build Instructions
1. Clone the repository:
```sh
git clone https://github.com/karma-riuk/stickfosh.git
cd stickfosh
```
1. Create a build directory and compile:
```sh
make stickfosh
```
1. Run the program:
```sh
./stickfosh
```
## Running the Application
Stickfosh provides multiple execution modes, selectable via command-line arguments:
| Mode | Description | Example Command |
|------|------------|----------------|
| **Human vs AI** | Play against the AI | `./stickfosh --mode human_vs_ai` |
| **AI vs AI** | Watch two AI versions compete | `./stickfosh --mode ai_vs_ai --ai1 v3_AB_ordering --ai2 v6_iterative_deepening` |
| **Human vs Human** | Manually input moves for both sides | `./stickfosh --mode human_vs_human` |
| **Perft Testing** | Performance test move generation | `./stickfosh --mode perft` |
| **Custom FEN** | Start from a custom position | `./stickfosh --mode ai_vs_ai --fen "rnbqkb1r/pppppppp/8/8/8/8/PPPPPPPP/RNBQKB1R w KQkq - 0 1"` |
## Video Demo
<!-- [![AI vs AI Chess Match](https://img.youtube.com/vi/XXXXXXXXXX/0.jpg)](https://www.youtube.com/watch?v=XXXXXXXXXX) -->
<!-- *Click the image above to watch a video of two AI versions competing!* -->
## Future Improvements
- Implement **opening book** for better early-game decisions.
- Enhance **evaluation function** with more advanced heuristics.
- Introduce **neural network-based AI** for machine-learning-driven play.
- Introduce **Monte-Carlo Tree Search** for stochastic driven play.

BIN
res/arial.ttf Normal file

Binary file not shown.

500
res/equal_positions.fen Normal file
View File

@ -0,0 +1,500 @@
r1bq1rk1/pp3pbp/2n1pnp1/2pp4/2P5/2NPP1P1/PP2NPBP/R1BQ1RK1 w - - 0 9
2kr2nr/ppp1b1pp/2n5/2N1p3/P5q1/3PQN2/1PP2PP1/R3KB1R w KQ - 0 16
r4rk1/pp1b1ppp/4pn2/2b5/2P5/2N5/PP2BPPP/R1BR2K1 w - - 0 13
r1bq1rk1/1p3ppp/p1n1pn2/2b5/2B5/2N1PN2/PP1B1PPP/R2Q1RK1 w - - 0 10
1q2rrk1/pp1n1ppp/2pbpn2/3p3b/2PP4/1P1BPN1P/PB1NQPP1/2RR2K1 w - - 7 14
r2qr1k1/pp3ppp/4b1n1/2pNb3/2P5/1P5P/PB2BPP1/R2Q1RK1 w - - 0 17
3q1rk1/3nbpp1/r1p1pn1p/2Pp2B1/3P4/2N1PN2/5PPP/2RQ1RK1 w - - 0 15
rnbq1rk1/pp4pp/3b4/2pPpp2/2P1p3/4P1P1/PP1N1PBP/R1BQ1RK1 w - - 0 12
r2qkb1r/pp4pp/2npbn2/2p1p3/7N/2PP4/PP2BPPP/RNBQ1RK1 w kq - 3 9
r4rk1/pbpn2pp/1p2p1q1/4Pp2/2P5/5PP1/PPQB2BP/R3R1K1 w - - 1 18
rn2k2r/pp1b1ppp/1qpp1b2/3Pp3/2P1P3/2N2N2/PP3PPP/R2QKB1R w KQkq - 0 9
r2qr1k1/p1p2pp1/2pp1n1p/2b3B1/4P1b1/2NB4/PPPQ1PPP/R4RK1 w - - 0 12
r1bqr1k1/ppp2p1p/3p1pn1/3P4/2PQ4/5NP1/P4PBP/R4RK1 w - - 0 15
r3k1nr/pp3bpp/2pb1n2/5p1P/8/2NP3N/PPP2PB1/R1B1K2R w KQkq - 10 17
r2qk1nr/pp3pp1/2nb2b1/2ppp3/6P1/P2PP3/1PPNQ1BP/R1B1K1NR w KQkq - 1 10
r1bqk2r/p4ppp/2p2n2/3pp3/4P3/P1b2P2/1PPB2PP/R2QKB1R w KQkq - 0 10
r2q1rk1/pn1bbppp/2p2n2/4p1N1/1P6/1BP5/P2P1PPP/RNBQK2R w KQ - 3 12
r1bq1rk1/1p2bp1p/p2ppnpP/2p1n3/4P3/1BPP2B1/PP2QPP1/RN2K1NR w KQ - 0 13
r1bq1rk1/4bpp1/p3pn1p/1p2n3/2pP1B2/2N1P2P/PPB2PP1/R2Q1RK1 w - - 0 14
2kr2nr/1ppn1pp1/p2p3p/3Pp1q1/4P3/2N2B2/PPP2PPP/R1Q2RK1 w - - 2 12
r1b2rk1/1pp3p1/p1nb1q1p/3p1p2/1P1P4/P3PN1P/1B1n1PP1/RB1Q1RK1 w - - 0 14
1k1r3r/pppnqpp1/3b2p1/3B2P1/8/4P1PP/PPPNQ3/2KR3R w - - 1 18
r3k2r/ppp2ppp/1nn5/4P3/1bP3b1/5N2/PP2BPPP/R1BNK2R w KQkq - 5 10
r2q1rk1/1pp1ppbp/p2p1np1/2nP1b2/2P5/1PN1PN2/PB2BPPP/R2QK2R w KQ - 0 10
r2qk2r/pp3ppp/2n1bn2/2bp4/8/2N1P1P1/PP3PBP/R1BQK1NR w KQkq - 3 9
1r3rk1/p1q2ppp/2pb1n2/2pp4/5P2/3P1Q1P/PPP1N1P1/R1B1R1K1 w - - 5 15
2r2rk1/1p1nqpbp/pn4p1/Q1pp4/8/2PPB2P/PP1NNPP1/R4RK1 w - - 2 16
3q1rk1/4ppbp/3p1np1/1r6/1p1BP3/1P3P2/P2Q2PP/2RN1RK1 w - - 0 18
r2q1rk1/4bppp/pp2p3/2p2n2/8/1PBPP1P1/P4PNP/2RQ1RK1 w - - 1 16
r2qkb1r/2p1pppp/p1n1b3/1p6/B2P4/2P1P3/P4PPP/R1BQK1NR w KQkq - 0 9
r2q1rk1/1p2bppp/p1n1pn2/3p4/3P4/2N1PB2/PPQ2PPP/R1B1R1K1 w - - 2 12
r1b1kbnr/p1p2ppp/2p5/8/4q3/5N2/PPP2PPP/RNBQK2R w KQkq - 0 9
r1b1kbnr/4pppp/p7/1P6/1np5/8/1P2PPPP/R1BNKBNR w KQkq - 1 9
2kr1br1/pp1b2pp/2n1p3/5p2/8/1PB1P1P1/P3NP1P/R3KB1R w KQ - 1 14
rn1q1rk1/pb2bppp/1p2pn2/3p4/3P4/2N1PN1P/PP2BPP1/R1BQ1RK1 w - - 2 10
rnb1k2r/pp2n1pp/2pb1p2/3pN3/3P4/2N5/PPP2PPP/R1B1KB1R w KQkq - 0 9
rn1qr1k1/pb3ppp/1p2pn2/2ppN3/2PP4/P1b1P3/1P1BBPPP/R2Q1RK1 w - - 0 11
r1b2rk1/2qn1ppp/p2bpn2/1p1p4/1P1P4/P2B1NP1/2P1NP1P/R1BQ1RK1 w - - 0 12
rn1qk2r/pbp2pp1/1p2pb1p/3p4/3P4/2N1PNP1/PPP2PBP/R2QK2R w KQkq - 0 9
r4rk1/pp2ppbp/2n3p1/2qp1b2/3P4/1P2P3/PB1NBPPP/2R2RK1 w - - 0 15
r4rk1/ppBq1ppp/2n5/2nN3b/1P6/P4N1P/4BPP1/b2Q1RK1 w - - 0 17
r2qk2r/p2n1ppp/3bpn2/3p1b2/3P4/2N5/PP2PPPP/R1BQKBNR w KQkq - 5 9
r2q1rk1/ppp1bppp/1nn1p3/8/3PP1b1/P1N2N2/1P2BPPP/R1BQ1RK1 w - - 5 10
r2q1rk1/pppb1ppn/2np3p/4p3/1P2P1P1/P2PbN1P/2P1QPB1/RN3RK1 w - - 0 12
2kr3r/ppp1nppp/4b3/3n4/2B5/2P2N2/PP3PPP/RN3RK1 w - - 0 12
r3kb1r/pp1bnppp/2n1p3/q2pP3/N2P1P2/3B1N2/PP4PP/R1BQK2R w KQkq - 9 11
2kr2nr/pp1b4/2n1p3/P2pPpqp/2pP2p1/2P1N1P1/1P3PBP/RN1QK2R w KQ f6 0 15
r2q1rk1/1p1bbppp/p1n1p3/8/4P3/2P1BN2/P2QBPPP/R3K2R w KQ - 4 13
r3r3/pp1kbppp/2bp4/3n4/3N1B2/2N5/PPP2PPP/R3R1K1 w - - 6 15
r2qk2r/pp3ppp/2npbn2/4p1B1/1b2P3/2N2P2/PPPQN1PP/R3KB1R w KQkq - 1 10
rnbq1rk1/pp2b1pp/2p5/3p1p2/3Pn2N/2N3P1/PP2PPBP/R1BQ1RK1 w - - 7 10
r3kb1r/1pp1qpp1/p1n4p/3pPb2/3P1P2/5N2/PP4PP/RNBQ1RK1 w kq - 2 12
r4bnr/1pkb2pp/p1n2p2/2p1p3/2N5/2P1P1P1/PP2NPBP/R1B1K2R w KQ - 2 14
2kr3r/ppp1nppp/6q1/5b2/3p4/3P1P2/PPPQ1KPP/R1B2B1R w - - 0 15
r3k2r/1p1qbppp/p3pn2/3p2B1/8/2N2Q2/PPP2PPP/2KRR3 w kq - 2 17
r3kb1r/pp3p1p/1qn1bpp1/1B1p4/3P4/1QN1P3/PP3PPP/R3K1NR w KQkq - 3 10
rnbq1rk1/5ppp/1p1b1n2/3pp3/1P6/P1P1P3/1B3PPP/RN1QKBNR w KQ - 1 9
r1bq1rk1/ppp2pp1/2n2p1p/8/1bBP4/2N1PN2/PP3PPP/R2QK2R w KQ - 1 9
r3k3/pp1n1ppr/3bb2p/2p5/8/4BN2/PPP2PPP/RN2K2R w KQq - 0 13
r1bqr1k1/pp1nppbp/2pp2p1/3P4/2PP4/1P1BB3/P2Q1PPP/R3K1NR w KQ - 2 12
r3kb1r/1p1n1ppp/p1p1p3/2Pp1bB1/3P4/1PN2P2/1P2P1PP/R3KB1R w KQkq - 0 12
r3k2r/ppp1n1pp/2nppq2/4p3/1P1bP3/P1N2N1P/2PP1PP1/R1BQ1RK1 w kq - 1 10
rn1qr1k1/1p3pbp/p2p1np1/2pP1bB1/2P5/2N2N2/PP2BPPP/R2Q1RK1 w - - 2 12
1k1rr3/1ppnqppp/4b3/1N1pP3/8/3B4/PPPbQPPP/1K1R3R w - - 0 16
3rk2r/ppp1bppp/5nb1/4p3/2B1P3/5P1P/PPP3P1/RNB2RK1 w k - 1 12
r2qk2r/p1pn1pp1/2pbpn1p/3p4/3P1B2/P3PN1P/1PP1NPP1/R2QK2R w KQkq - 0 11
r1bq1rk1/pp3pbp/2np1np1/2p1p3/4P3/P1NP1N1P/1PPBBPP1/R2QK2R w KQ - 4 9
r1bq1r2/pp2ppkp/3p1np1/8/3nP3/2N5/PPPQBPPP/R3K2R w KQ - 0 11
rn1q1rk1/pb3ppp/1p2p3/2pn4/8/1P1P1NP1/Pb1NPPBP/R1Q2RK1 w - - 0 12
r4b1r/pppkp1p1/2nq1nQ1/3p3p/3P3P/4PP2/PPP5/RNB1K1NR w KQ - 3 12
r2qk1nr/1bp2ppp/p3p3/1p2P3/1P1P4/1B6/P2N1PPP/R2Q1RK1 w kq - 4 18
r1bqk2r/p1p2ppp/2p5/3p4/3Pn3/3Q1N2/PP3PPP/RN3RK1 w kq - 0 12
r2q1rk1/pbp1bppn/1p1p3p/8/4P2B/2N1QN2/PPP2PPP/R2R2K1 w - - 4 14
r2q1rk1/p2bbppp/2pp1n2/4p1B1/Q3P3/2N2N2/PPP2PPP/R4RK1 w - - 8 11
r1bqk2r/2p1bpp1/p1np1n1p/1p2p3/2B1P3/2NPBN1P/PPPQ1PP1/R3K2R w KQkq - 0 9
r2q1rk1/1bp1bppp/p1np1n2/1p2p3/4P3/PB1P1N1P/1PP2PP1/RNBQ1RK1 w - - 1 10
rn1q1rk1/pp2p1bp/3ppnpB/2p5/3PP3/5P2/PPPQ2PP/RN2K1NR w KQ - 0 9
rn3rk1/3pqppp/b1pb4/1p2p3/2P1P3/BB1P1N2/5PPP/R2Q1RK1 w - - 1 16
r1bq1rk1/1pp1npbp/p1np2p1/4p3/1PP1P3/P2PBN2/4BPPP/RN1QK2R w KQ - 2 9
r1b2rk1/1pq1ppbp/p2p1np1/8/3NP3/2NQ1P2/PPP3PP/R1B2RK1 w - - 0 13
rnb2rk1/4qppp/p2pp3/2n5/1p1N4/1B4N1/PPP2PPP/R2QR1K1 w - - 4 14
r1q1kb1r/1p1npppp/p1p3b1/P1Pp4/1P1PnB2/2N4P/2Q1PPP1/R3KBNR w KQkq - 5 11
r2qk1nr/p2bbppp/2p1p3/3pP3/3P1P2/5N2/PP4PP/RNBQK2R w KQkq - 2 10
r1b2rk1/1pq1bppp/p1nppn2/6B1/P2NP3/1BN5/1PP2PPP/R2Q1RK1 w - - 7 11
r2qk2r/1bpn1pb1/pp1p2pp/8/1PPNP3/P2nBN1P/5PP1/1R1Q1RK1 w kq - 0 16
r1bqnrk1/pp1nbp1p/2p3p1/3p2B1/3P4/2NBP3/PPQ1NPPP/R4RK1 w - - 4 11
r2q1rk1/pbpnnppp/1p2p3/8/3PPP2/2NB1N2/PP4PP/R2QK2R w KQ - 4 11
r1b1k2r/1pp1bppp/p1p2n2/8/8/5N2/PPPPKPPP/RNB1R3 w kq - 3 10
r2qk2r/1b2nppp/p3p3/3pP3/8/1N1Qb3/PP3PPP/RN3RK1 w kq - 0 14
r3kb1r/ppp2ppp/2n2n2/4p3/2P5/3BPb1P/PP3PP1/RNBK3R w kq - 0 9
r3kb1r/ppp2pp1/2n3qp/4p3/2P3b1/P2P1N2/1P2BPPP/R1BQ1RK1 w kq - 1 11
rnb1k3/ppq1b2r/2p1pnN1/3p4/3P1P1P/2N5/PPPB2P1/R2QK2R w KQq - 1 14
r2qrbk1/pp1bnppp/3p4/3Pp3/8/1N4P1/PP2PPBP/R1BQ1RK1 w - - 1 14
2kr3r/pppbbppp/3q4/4n3/2B2B2/2P2Q2/PP3PPP/RN3RK1 w - - 2 13
rn2kbnr/pp2pppp/8/2p1P3/2Pp1B2/3P1q1P/PP3PP1/RN2KB1R w KQkq - 0 9
r4rk1/5ppp/p4n2/2bp1qB1/8/2N5/PP3PPP/R2Q1RK1 w - - 4 16
r1b2rk1/pppnqppp/8/4p3/4P2N/1P1P3P/1PP3P1/RN1QK2R w KQ - 0 13
r1b1kb1r/p4ppp/2p5/2pp2B1/6n1/3P1N2/PPP1KPPP/RN5R w kq - 1 11
rnbq1rk1/pp2bpp1/4pn1p/2p5/2BPP2B/2N2N2/PP3PPP/R2QK2R w KQ - 0 9
r3kb1r/2Bn1ppp/2ppp1n1/8/2B1P3/2P5/PP3PPP/RN2K2R w KQkq - 3 13
r1bq1rk1/ppp1bppp/1n6/4p3/8/1PN4P/1PPPNPP1/R1BQ1RK1 w - - 0 12
r1bqk2r/np3ppp/1bpp1n2/pP2p3/P3P1P1/3P1N1P/2P2P2/RNBQKB1R w KQkq - 0 10
r3k2r/2p1bpp1/p1nqp2p/1p1p1b2/1P1Pn3/PNPBPN1P/1B3PP1/R2QK2R w KQkq - 4 12
r1bqk2r/1pp2pb1/p2p1npp/3Pp2n/1P2P3/2PB1PP1/P2NN2P/R1BQK2R w KQkq - 1 12
r4rk1/pp2npp1/1qpNb2p/4P3/4P3/4N3/PPP3PP/R2Q1RK1 w - - 3 16
1k1rr3/1pp3pp/p1nbRp1n/8/3P4/P1N2N1P/1P3PP1/R1B3K1 w - - 1 18
r3k1nr/p3bpp1/1pqp3p/3Np3/4P2B/3Q1b2/PPP2PPP/R4RK1 w kq - 0 13
2bqr1k1/r3bppp/2n1pn2/p1pp4/PpP5/1P1PPNP1/2QN1PBP/1RB2RK1 w - - 1 14
r2qkb1r/2p2ppp/p1np4/1p2p2n/1P2P3/P7/R1PP1PPP/1NBQ1RK1 w kq - 0 12
r1b1k1nr/1p4pp/1pn1pp2/3pP3/1b1P1P2/5N2/PP4PP/RNB1KB1R w KQkq - 1 10
2rq1rk1/pp1bbppp/4pn2/2p1N1B1/8/2NP4/PPP2PPP/R2Q1RK1 w - - 5 11
r2q1rk1/pp2bppp/2np1n2/2p1p3/4P3/1PNP1bPP/PBP2PB1/R2QK2R w KQ - 0 10
r1bqkbnr/3p1pp1/n3p2p/1p1P4/p1p1P3/P1P2N2/BP1N1PPP/R1BQK2R w KQkq - 0 11
r3k2r/pbqnbppp/1p2pn2/2pp4/Q1P5/P2P1NP1/1P1NPPBP/R1B2RK1 w kq - 1 10
r3kb1r/pp1nppp1/2p2n1p/8/5q2/2NP1B2/PPP2PPP/R1B2RK1 w kq - 0 11
r2q1rk1/ppp1ppbp/2np2p1/7n/2P1PP2/P1NPB2P/1P4P1/R2QKB1R w KQ - 1 12
r2q1rk1/p1n2p2/3p2pp/1ppPp3/4P2b/2PB2NP/PP3PP1/R2Q1RK1 w - - 0 18
r2q1rk1/p4ppp/1bp1p1n1/3pP3/1P1P4/P1N1BQ1P/5PP1/R3K2R w KQ - 3 15
r2qk2r/pppb1ppp/3p1n2/2b1n3/3NPB2/2P4P/PP2BPP1/RN1QK2R w KQkq - 3 10
rnbr2k1/pp3ppp/2p2bn1/4p3/2P5/1P2PNP1/PB3PBP/RN3RK1 w - - 0 12
r1br2k1/pppnbpp1/4p2p/3q4/8/P2PBN1P/1PP1BPP1/R2Q1RK1 w - - 0 13
r2qk1nr/ppp3bp/2n1bpp1/3p2P1/1P1Pp2P/4P3/P1PB1P2/RN1QKBNR w KQkq - 0 9
2kr1b1r/p1p1pppp/2p1bn2/8/3P4/2N5/PPP2PPP/R1B1K1NR w KQ - 0 10
r3kb1r/p4ppp/npp1pn2/2Pq4/N2P1B2/P7/1P3NPP/R2QK2R w KQkq - 2 15
r2qk2r/pp3ppp/3b1n2/2np4/6b1/P3PN2/1P2BPPP/RNBQK2R w KQkq - 4 10
r2qk2r/pp2bppp/3p4/2pnp3/3BP3/PP4P1/2PP1P1P/R2QK1NR w KQkq - 0 14
rn1q1rk1/pbp1bppp/1p1pp1n1/8/2B1P3/P1NP1N1P/1PP2PP1/R1BQ1RK1 w - - 0 9
r1b1k2r/pp4p1/1nppBn2/6Bp/3P4/P7/P4PPP/R3K1NR w KQkq - 4 14
2k1r2r/1ppn4/p2qppbb/1P1p1n1p/P2P1P1P/2N1P2B/2P1N3/R1BQK2R w KQ - 1 16
r1bq1rk1/p3b1pp/1p2p3/nBp1Pp2/3P4/2P1BN2/P1PQ2PP/R4RK1 w - - 4 14
rnbqk2r/1p3ppp/p3pb2/8/2BP4/2N2N2/PP3PPP/R2QK2R w KQkq - 0 10
r1bqk2r/pp1nbpp1/2p1pn1p/2Pp4/3P3B/2N1P3/PP3PPP/R2QKBNR w KQkq - 1 9
r2qk2r/pp4bp/n1pp1ppn/4p1B1/4P2P/P1NP1QP1/1PPK1PB1/R6R w kq - 0 13
r2qkb1r/1p3pp1/p1npbn1p/2p1p3/4P3/2NPBNP1/PPPQ1PBP/2KR3R w kq - 8 10
2rqk2r/pp1nbpp1/2n1p1p1/3p2B1/3P2P1/2P2PN1/PP5P/RN1Q1RK1 w k - 2 15
r1bq1rk1/pp1nppbp/2np2p1/8/2B1PB2/2N2N1P/PP2QPP1/R4RK1 w - - 1 11
r2qkb1r/pp2pppp/2b2n2/1B2n3/4p3/P1N5/1PP1QPPP/R1B1K1NR w KQkq - 0 9
r2qk1nr/pp1n2pp/2p1bp2/4b3/P7/1PB2N2/2PP2PP/RN1QKB1R w KQkq - 0 11
2r1r1k1/ppqn1ppp/3b4/3Pp3/7N/1P4P1/P3QP1P/R1B2RK1 w - - 1 18
rnbq1rk1/p3ppbp/1ppp2p1/4P3/3P1P2/2P3P1/PP4BP/RN1QK1NR w KQ - 0 10
r3k2r/ppp2ppp/2n1p3/3pP1q1/3P3n/5B1P/PPP1NPP1/R2Q1RK1 w kq - 6 12
rn2r1k1/ppp1bp2/3pbn1p/6p1/2N5/2NP2B1/PPP2PPP/2KR1B1R w - - 2 13
1r1q1rk1/pb1pppbp/2n2np1/2p5/1p2P1P1/6NP/PPPPNPB1/R1BQ1RK1 w - - 2 11
1rbq1rk1/b1p2pp1/2n4p/4p3/NnB5/3P1N1P/1P3PP1/R1BQ1R1K w - - 0 18
r2q1rk1/pp1n1ppp/2p1bn2/3pp3/2P1P3/P1PPBN1P/4BPP1/R2QK2R w KQ - 0 11
r2q1rk1/5ppp/p1p2nb1/3p4/1P2p3/4PPN1/1PP1N1PP/R2QK2R w KQ - 0 16
r1bq1rk1/1p2nppp/p1npp3/3p4/3P4/2NBPN2/PPP2PPP/R2Q1RK1 w - - 2 9
r2q1rk1/1p1n1ppp/p2pbb2/3Np3/4P3/1N6/PPP1BPPP/R2Q1RK1 w - - 2 12
r4rk1/pp3ppp/5n2/2bq4/8/5N2/PP3PPP/R1BQ1RK1 w - - 0 15
rn1qk2r/pp2ppbp/1n1p2p1/8/2PP2b1/5N2/PP2BPPP/RNBQ1RK1 w kq - 1 9
rn1q1rk1/pbp1bppp/1p3n2/3p2B1/3P4/P1N1PN2/1P3PPP/R2QKB1R w KQ - 1 9
rnbqk2r/1pp3pp/p2npp2/3p4/1P1P4/P3PN2/2P2PPP/RN1QKB1R w KQkq - 0 9
r1b1q1k1/1p3pbp/p4np1/3pr3/3Q4/N1P1BB1P/PP3PP1/R3K2R w KQ - 0 15
2rr2k1/pp2bpp1/1qp1pnp1/8/2PP1B2/1Q4P1/PP3PBP/3RR1K1 w - - 3 18
rnbq1rk1/pp2bpp1/3p1n1p/2p3B1/2B5/5N2/PPPN1PPP/R2QR1K1 w - - 0 11
r3kb1r/1p1b1pp1/pqn1p1p1/3pP3/P2P4/5N2/1P3PPP/RNBQ1RK1 w kq - 1 12
r1b2rk1/1pp1qppn/p1n1p2p/3p4/3P1P2/NPPBPNP1/P6P/R2QK2R w KQ - 1 12
r4rk1/ppp2ppp/2nq1b2/4p3/1PP3b1/P2P1N2/4BPPP/R1BQ1RK1 w - - 1 12
r3kb1r/3n1pp1/2ppbn2/1p2p3/p3P2p/P1PPBP2/1PB1N1PP/RN3RK1 w kq - 2 14
r1bq1rk1/p3ppbp/6p1/3p4/8/4BP2/PPP1B1PP/R2Q1RK1 w - - 0 13
r2q1rk1/pp1bppbp/2np1np1/1B6/3NP3/2P1B3/PP1N1PPP/R2Q1RK1 w - - 6 10
1r1qk1nr/p1pb1ppp/6n1/4P3/1b1PB3/2N5/PPP2PPP/R1BQK2R w KQk - 5 12
r2qkb1r/pbpp2pp/1pn2p2/4p3/1P1P1Pn1/2P1PNP1/P2Q3P/R1B1KBNR w KQkq - 1 10
r3kbnr/ppp2ppp/4b3/8/3p1P2/2N5/PPPP2PP/R1B1KB1R w KQkq - 0 9
rnbq1rk1/1p2bpp1/p1p1pn1p/3pB3/1P1P4/P1P2N2/3NPPPP/R2QKB1R w KQ - 3 9
r1b1k2r/pp1n1pp1/2pp1n2/4p1Bp/2B1P3/2PP4/P1P1NPPP/R3K2R w KQkq - 2 10
r3r1k1/pp3pb1/3p1qpp/3Pp3/4P1b1/2NQ1N2/PP3PPP/R4RK1 w - - 0 16
r1b1k1nr/1pq1bppp/p1n5/1Bpp4/P7/1P3N2/2PP1PPP/RNBQ1RK1 w kq - 0 9
r2qk2r/1p1nbppp/p1p1pn2/3pB3/1P1P4/P2PPN2/5PPP/RN1QK2R w KQkq - 1 10
r4rk1/pp3ppp/2n1bn2/3qp3/8/1NP1P3/P3BPPP/R1BQ1RK1 w - - 0 12
rnbqr1k1/ppp2pp1/3p1b1p/P7/2PP4/3B1NP1/1P3PP1/RN1Q1RK1 w - - 3 15
2kr1bnr/ppp1pppp/6b1/q5N1/3P4/2NnB3/PPP2PPP/R2Q1RK1 w - - 0 11
r3k2r/pp2ppbp/2p3p1/3n1b2/N2n1N2/3P4/PPPB1PPP/R2BK2R w KQkq - 14 15
r1bq1rk1/pp3ppp/8/4p2Q/1b1pN2P/1P1P2N1/1PP2PP1/R3K2R w KQ - 1 16
rn1qkb1r/ppp2p2/5n1p/4p1p1/2BP1pb1/2N2N2/PPP3PP/R1BQ1RK1 w kq - 0 9
rnb2k1r/ppp1q1pp/8/4N3/6n1/2Q5/PP3PPP/RNB1K2R w KQ - 1 12
r1bq1rk1/ppp1bppp/3p1n2/4p1B1/4P3/1PNP1N2/1PP2PPP/R2QK2R w KQ - 3 9
r2qkb1r/1p1n1ppp/p3pn2/1B1p1b2/3P1P2/2N1PN2/PP4PP/R1BQK2R w KQkq - 0 9
r1b1k1nr/1p1p1ppp/p3p3/q1b5/4P3/3B1N2/PPP1NPPP/R2Q1RK1 w kq - 2 13
rnbq1rk1/ppp3pp/1n6/3Ppp2/4P3/2N2P2/PP1Q2PP/R3KBNR w KQ - 0 10
rn1qk1nr/1p3ppp/p2bp3/3pN3/3P4/2N5/PPP1bPPP/R1BQ1RK1 w kq - 0 9
3r1rk1/4qppp/1pnp1n2/p1p1p3/P1P1P1b1/1P1P1NP1/2QN1PBP/R3R1K1 w - - 1 15
r1bqk2r/1p1pbppp/p1n1p3/4P3/2B3n1/2N1BN2/PP3PPP/R2Q1RK1 w kq - 1 10
r2qk2r/1pp2p2/p1np1n1p/2b1p1p1/2B1P1b1/P1NP1NB1/1PP2PPP/R2QK2R w KQkq - 2 10
rnbq1rk1/1pp1bppp/p3p3/8/2pP4/2N1PN2/PP2BPPP/R2Q1RK1 w - - 0 10
r1b2rk1/4q1pp/p1pp1p2/np2p1B1/4P3/3P1N1P/PPP2PP1/R2Q1RK1 w - - 0 14
r4rk1/1p2qpp1/p2pnn1p/4p2b/1PB1P2B/2PP1N1P/5PP1/R2Q1RK1 w - - 1 16
r2q1rk1/pb1nn1bp/1p4p1/4pp2/3pP3/1N1P1P1P/PP2BBPN/2RQ1RK1 w - - 0 16
r3k2r/1pp1bp2/p1np2pp/P3p2n/1PB1P2P/2PP3P/1B3P2/RN2K1R1 w Qkq - 1 16
rnbqkb1r/pp4pp/5p2/3np3/4P3/2N5/PP3PPP/R1BQK1NR w KQkq - 0 9
rn1q1rk1/ppp1bpp1/7p/3p4/3P2b1/3BPN2/PP3PPP/R1BQ1RK1 w - - 2 10
r1b1k2r/ppp2ppp/4q3/4n3/2P1Q3/3P4/P2B2PP/R3KB1R w KQkq - 0 16
2kr3r/pppbqpbp/1n4p1/3Pp3/4Pp2/1PNB4/PBPQ2PP/1K2R2R w - - 2 16
r2qkb1r/1p1n1ppn/p3p2p/3p1b2/8/PBNP1P1P/1PP1N1P1/R1BQK2R w KQkq - 4 11
rn2k1r1/pp2p2p/3p2p1/2pP1p1P/b1P1n3/2P1BN2/P3PPP1/2R1KB1R w Kq - 1 14
r2qk2r/ppp2ppp/2nbp3/3p4/3P1B2/3PPN2/PP1Q1PPP/R4RK1 w kq - 1 10
r2qrbk1/1ppb1ppp/p2p1n2/8/4PB2/1PNP3P/1PP3PN/R2Q1R1K w - - 3 14
r2qr1k1/pp1bnppp/2pb1n2/3pp3/P3P3/3P1NPP/1PPNQPB1/R1B2RK1 w - - 1 11
r2q1rk1/1ppbb1pp/p3p3/3pBp2/1P1PnP2/2PBP1P1/P6P/RN2QRK1 w - - 1 16
r1bq1rk1/b1p2pp1/p1n2n1p/1p6/1PNp1B2/P2B1N1P/2P2PP1/R2QR1K1 w - - 0 15
rnbq1rk1/pp2bppp/2p2n2/3p4/3P4/2NB1N2/PPP2PPP/R1BQ1RK1 w - - 2 10
r1bqk2r/1p1nn1p1/2pp3p/p3p3/4P3/2NB1N1P/PPP2PP1/R2Q1RK1 w kq - 0 11
r2qk2r/1p2b1pp/p1np1n2/2p1pb2/8/2NPBN2/PPPQBPPP/R4RK1 w kq - 2 10
rn1qk2r/pp2nppp/1b2p3/3pP3/3P1P2/5N2/PP4PP/RNBQK2R w KQkq - 3 10
r1bqk2r/p3npbp/1pn1p1p1/3p4/B7/2N2N2/PPPP1PPP/R1BQR1K1 w kq - 0 10
r2q1rk1/pp3ppp/2nbpn2/3p4/3P2b1/P1N2NP1/1P2PPBP/R1BQ1RK1 w - - 3 10
rn2k2r/pp2bppp/2p1pn2/q7/3P4/2NbBN2/PPP2PPP/R2Q1RK1 w kq - 0 10
2r1r1k1/pp2bpp1/2ppq1np/8/3PPQN1/2N4P/PPP3P1/3R1RK1 w - - 1 18
r1bq1rk1/pp2nppp/2pp4/1B1Pp3/1bP5/3P1N2/PP3PPP/R1BQ1RK1 w - - 0 10
rn1q1rk1/pp2bppp/2p1bn2/2Pp4/BP1Pp3/P1N1P3/5PPP/R1BQK1NR w KQ - 2 10
r1bq1rk1/2p2pp1/p2p1b1p/1p1P4/8/PPPB3P/5PP1/R1BQ1RK1 w - - 1 15
r2q1rk1/4bpp1/pp2pn1p/2pp4/P2P1P2/2PBPQ2/1P1B2PP/R4RK1 w - - 0 17
rnbqk2r/ppb2ppp/2p1pn2/P2p4/1P1P3P/2P1PN2/5PP1/RNBQKB1R w KQkq - 1 9
rn3b1r/ppp1k1pp/3p1nb1/6N1/2BPp3/8/PPP2PPP/RNB1K2R w KQ - 0 10
r1bqr1k1/1p2bppp/p1pp1n2/8/3pP3/1PN1BN1P/1PPQ1PP1/R4RK1 w - - 0 13
2kr1b2/ppq5/3p2pn/3p1b2/3PpP2/1B2B3/PP4PP/RN3RK1 w - - 2 17
r2q1rk1/ppp2ppp/3b1n2/3pNb2/3P1B2/2PB3P/PP1n1PP1/R2Q1RK1 w - - 0 13
r2r2k1/ppp2pbp/2n1p1p1/4P3/3P4/Pb2BN2/1P2BPPP/R4RK1 w - - 0 18
2kr2nr/pp3ppp/4p3/8/1bBnP3/2N2P2/PP3P1P/R1B2RK1 w - - 4 16
r3k1nr/1bp1qpp1/p1pp3p/2b1p3/Q3P3/2PP1N2/PP3PPP/RNB2RK1 w kq - 3 10
r1bq1rk1/pp2b1pp/2np4/2p1pp2/2P2Bn1/P1NP1NP1/1P2PPBP/R2Q1RK1 w - - 0 11
r3k1nr/pp3ppp/1q2p3/3p4/3b4/P4PP1/1P3PBP/1RBQ1RK1 w kq - 0 14
r2qk2r/ppp1bppp/3p1nb1/3Np3/2B1P1P1/5Q1P/PPPP1P2/R1B1R1K1 w kq - 3 11
2rqkb1r/pp3ppp/2n1pn2/3p3b/3P4/2NBP2P/PPQ1NPP1/R1B1K2R w KQk - 3 10
r2qk2r/ppp1bppn/2npb2p/8/4P1P1/2N2P1N/PPP4P/R1BQKBR1 w Qkq - 6 10
r1bq1rk1/ppp2pp1/1bnp1n1p/4p3/2B1P3/P1NPBN1P/1PP2PP1/R2QK2R w KQ - 3 9
r1bqkb1r/p4ppp/2p5/n2np1N1/8/3B4/PPPP1PPP/RNBQK2R w KQkq - 2 9
2kr1b1r/1ppqnpp1/p3p2p/P2pP3/1PnP2P1/2P2N2/5P1P/RNBQ1R1K w - - 0 14
r2qkb1r/1pp3pp/p2p1n2/8/4Pp2/3Q4/PPP3PP/RNB2RK1 w kq - 0 12
r4rk1/1p2bppp/p4n2/4p3/PnB1P1b1/2N2N2/1P3PPP/R1BR2K1 w - - 1 15
r1bnk1nr/pp2b1p1/7p/2p2pN1/4pB2/2N3P1/PPP1PPBP/R4RK1 w kq - 0 11
r3k1nr/1p1n1pp1/2p1bq1p/p1b1p3/P3P3/2NBBN2/1PP2PPP/R2Q1RK1 w kq - 5 11
r2q1rk1/pp1bppbp/2p3p1/8/3P4/2B1PN1P/PP2QPP1/2R1K2R w K - 1 15
rn1qk2r/ppp1bppp/2b1p3/4P3/3P4/1Q6/PP3PPP/R1B1KBNR w KQkq - 4 11
r2qkb1r/1b2nppp/p3p3/1pn1P3/8/P2B1N2/1P1N1PPP/R1BQ1RK1 w kq - 0 12
r1bq1rk1/ppp2ppp/2n5/2bnp3/8/3P1NN1/PPP1BPPP/R1BQ1RK1 w - - 7 9
rnbq1rk1/1p1p2bp/2p1p1p1/p2n4/4p3/1PP2PP1/PB1PN1BP/RN1Q1RK1 w - - 0 11
r4rk1/3qbppp/p1n1pn2/1p1p4/1P4b1/P4NP1/1BPNPPBP/2RQ1RK1 w - - 2 14
rn2k2r/pp6/2p3pp/7n/3PpB1P/5bP1/PPP5/RN2KB1R w KQkq - 2 18
r3k1nr/p4p1p/2bp1b2/nBp2P2/8/2P5/PP2N2P/RNB1KR2 w Qkq - 4 17
r1bq1rk1/1pp2pp1/2np3p/p2Np3/2P1P1n1/P2PPN2/1P2B1PP/R2QK2R w KQ - 0 11
r1bq1rk1/4bppp/p2p1n2/npp1p3/4P3/2PP1N1P/PPB2PP1/RNBQ1RK1 w - - 0 11
r1bq1rk1/ppp1npp1/1b3n1p/1P1p4/2B1P3/2PP1N2/P4PPP/RNBQK2R w KQ - 0 10
r2q1rk1/p4ppp/1ppp1b2/8/P1B1P3/1QP2PPb/1P1N3P/R3R1K1 w - - 1 18
r4rk1/1b2qpbp/p3p1p1/np1p4/3P1N2/N1P3P1/PP3PBP/R2Q1RK1 w - - 4 16
r2qk2r/pp1b2pp/2pbp3/5p2/3Pp3/1P2P1P1/PBP1NP1P/R2Q1RK1 w kq - 0 13
r2qkb1r/1ppb3p/p1n1pp2/3p4/3P3B/5N2/PPP2PPP/RN1QK2R w KQkq - 2 10
r2qk2r/p1pbbppn/2pp3p/8/3NP2B/2N5/PPP2PPP/R2Q1RK1 w kq - 2 11
r2q1rk1/1p3pp1/p4b1p/2pp4/3n4/PB1P1QNP/1PP2PP1/R4RK1 w - - 1 16
3r1rk1/ppp1bppp/5n2/3p4/1n1P4/2NPBN1P/PP3PP1/4RRK1 w - - 3 15
r1b2rk1/1ppn1ppp/p4q2/4p3/2BP4/P3PN2/1P3PPP/R2Q1RK1 w - - 0 13
rn2kbnr/p1p1pp1p/1p4p1/8/3P4/2PB1P2/PP5P/RNB1K2R w KQkq - 0 10
2r2rk1/pp3ppp/1n1qpn2/8/5P2/1PR1PN2/PB2Q1PP/5RK1 w - - 1 18
3q1rk1/p1pn1ppp/4rn2/2b1p3/8/2P1p2N/PPQP1PPP/RNB1K2R w KQ - 0 13
rnbqk2r/2p1bpp1/p2p1n1p/1p6/2BNP3/P4P2/1PP3PP/RNBQ1RK1 w kq - 0 9
rn1qk2r/pp3pp1/4bn1p/2bpp3/7B/2PP4/PPB2PPP/RN1QK1NR w KQkq - 0 9
r3k2r/p1pp1ppp/np2p3/3nP3/3P4/2P2N2/P1PB1PPP/R3K2R w KQkq - 0 13
r2q1rk1/pp1bbppp/2nppn2/2p3B1/2P1P3/2NP1N2/PP1QBPPP/R3K2R w KQ - 8 9
r2q1rk1/ppp1bppp/2n1bn2/3pp3/4P3/1PNP1N1P/PBP1BPP1/R2QK2R w KQ - 0 9
r1bq1rk1/pp1nppbp/3p2p1/2p5/2BPPPn1/2N1BN2/PPPQ2PP/R3K2R w KQ - 2 9
r1b1kr2/pppq2pp/4pp2/4P3/1Q2p3/P3P3/2P2PPP/R1B1K2R w KQq - 0 15
r4rk1/pp2nppp/1qn1p3/3pP3/3P4/N4N2/PP1Q1PPP/R4RK1 w - - 3 13
r1bq1rk1/pp1nb1pp/2p1p3/2Pp1p2/3PnB2/2NBPN1P/PP3PP1/R2QK2R w KQ - 3 10
r1bq1rk1/1p1pppbp/p1n2np1/8/4P3/1NN5/PPP1BPPP/R1BQ1RK1 w - - 0 9
rnbq1rk1/ppppp1bp/6p1/6N1/3P4/5N2/PPP2PPP/R2QKB1R w KQ - 3 9
r3k2r/pppqnpbp/2npb1p1/4p3/2P1P3/PPN2N1P/1B1P1PP1/R2QKB1R w KQkq - 3 9
r3k2r/pppq1pp1/2bp1b2/4p2p/4P3/2NP1N1P/PPPQ1PP1/R3K2R w KQkq - 2 11
r3kb1r/pp1nnpp1/2p1p2p/3pP3/3P4/2NP1N2/PP1B1PPP/2R2RK1 w kq - 0 12
r2qkb1r/ppp2ppp/5n2/4p3/3Q4/7P/PPP1NPP1/R1B1K2R w KQkq - 0 11
2kr3r/1pp1qppn/p1bp4/4p2p/4P3/2NP1N1P/PPP1QPP1/R2R2K1 w - - 0 14
r4rk1/1bp2ppp/np1qpn2/p2p4/P2P4/NPP1P1P1/4NPBP/R2QK2R w KQ - 5 11
r1br2k1/1pp1nppp/p1p5/2b5/3NP3/2P5/PP1N1PPP/R1B2RK1 w - - 5 11
1k1r3r/bpp2pp1/p2q1n2/3Pp2p/4P1b1/1BN1B3/PP3PPP/R3QRK1 w - - 2 17
r2q1rk1/1pp2ppp/p1bbpn2/3pN3/5P2/1P1PP3/PBP3PP/RN1Q1RK1 w - - 2 11
rn3rk1/1b1qppbp/3p1np1/1pp5/4P3/BPNB1P2/2PPN1PP/R2QK2R w KQ - 0 13
1rb1nrk1/p2n2qp/3p2pQ/1pp5/4N1P1/5P2/PPP1P3/R3KBNR w KQ - 4 18
r2q1rk1/pbppn1bp/1p2p1p1/8/4P3/1PNB1Q2/P1PB1PPP/2R1R1K1 w - - 2 13
r1bqk2r/1n4pp/2pp1b2/pp2pp2/1P2P3/P1NP3P/B1P1NPP1/R2QK2R w KQkq - 0 13
rnbq1k1r/ppp5/3p1n1p/3p2p1/8/B1P2Q2/P1P2PPP/R3KBNR w KQ - 0 11
rn1q1rk1/pp3pp1/2pb1n1p/3p4/2PP4/1P3B2/PB1N1PPP/R2Q1RK1 w - - 5 12
r2qk2r/pb1nnpbp/1p1pp1p1/2p5/3PPP2/1BN1B3/PPPQN1PP/R3K2R w KQkq - 0 10
rn2k2r/p1p2ppp/1p2bn2/P3p3/4P3/5P2/P1PB2PP/R2K1BNR w q - 2 11
r3k1nr/pp3p2/2ppq2p/2n1p1p1/4P3/P2P1N1P/1PP2PP1/R1BQ1RK1 w kq - 0 12
r1b1k2r/pp1qbpp1/3p1n1p/2pBp3/Q1P1P3/3P1N2/PP3PPP/R1B2RK1 w kq - 3 11
r1b2rk1/p4ppp/2p1pn2/q7/2p5/1PNBP3/P4PPP/2RQK2R w K - 0 13
r4rk1/p4ppp/1q2pn2/1bbp4/8/P1P1PQ2/3NBPPP/R1B2RK1 w - - 6 15
r4rk1/2qbbp1p/p2p1np1/1pnPp3/P1p1P3/2P2NNP/1PB2PP1/R1BQR1K1 w - - 0 17
r3kb1r/p4p1p/2pp1np1/1pn5/4P3/1BN2P2/PPP2P1P/R1B1K1R1 w Qkq - 2 13
r3nrk1/p1p2p1p/3bp1pP/1N1p1b2/8/P3PN2/1PP2PP1/R1B1K2R w KQ - 4 14
r1b2rk1/ppp2ppp/1b6/1q2P3/2N5/5N2/PPP1Q1PP/2KR3R w - - 2 15
r3kbnr/pb3ppp/2p1p3/8/8/4P1P1/PP3P1P/RNBK1B1R w kq - 1 10
r2qkb1r/pp1nppp1/2p4p/3n3b/3P4/2N1PN1P/PP2BPP1/R1BQK2R w KQkq - 1 9
r1b2rk1/1p2bppp/2n1pn2/2qp4/2P5/P3PNP1/1B1N1PBP/R1Q2RK1 w - - 1 15
r1bq2nr/pppk3p/2nb4/3p1p1B/3PpNp1/4P3/PPP2PPP/RNBQ1RK1 w - - 2 11
r1bq1rk1/ppp2pp1/1bnp1n1p/4p3/2B1P2B/1N1P1N2/PPP2PPP/R2QK2R w KQ - 2 9
r4rk1/4qppp/1pnp1b2/p1p1p3/2P1P3/PP1P4/1B1NbPPP/R2Q1RK1 w - - 0 14
rnbqkb1r/p3pppp/2p5/3nP3/P2PN3/2p2N2/5PPP/R1BQKB1R w KQkq - 0 10
rn2k2r/1bppqpbp/pp2pnp1/6N1/2B1P3/2PP1Q2/PP3PPP/RNB2RK1 w kq - 0 9
r3r1k1/pp1qbpp1/2n1bn1p/3p4/2pP4/P1N2NB1/1PPQBPPP/R4RK1 w - - 8 14
r1bq1rk1/1p2bpp1/p1n1pn1p/8/2p4B/P1N1PN2/1P3PPP/2RQKB1R w K - 0 12
r3k2r/2pqppbp/ppbp1np1/8/3PP3/2N1BN2/PPPQ1PPP/R4RK1 w kq - 2 11
r1bq1rk1/1p2bppp/pn6/2pP4/P7/2N5/BP3PPP/R2QK1NR w KQ - 2 12
r1bq1rk1/pp2ppbp/5np1/3P4/1nBN4/2N1BP2/PPP3PP/R2QK2R w KQ - 1 10
rnb1k1nr/pp2bppp/2p1p3/3pP3/3P1B2/1NP5/PP3PPP/R3KBNR w KQkq - 0 9
rnb1k2r/pp2q1pp/3b4/3n1p2/3Pp3/1Q2P3/PP1NBPPP/R1B1K2R w KQkq - 0 11
r1bq1rk1/pp3ppp/n1pbp3/8/3P4/4PN2/PPP3PP/RNBQ1RK1 w - - 3 11
1r1qr1k1/p2nbppp/5n2/2p4b/2Pp4/1P3N1P/P1B2PP1/RNBQR1K1 w - - 1 16
2kr3r/1pp2ppp/p1pbbn2/4N3/8/8/PPPP1PPP/RNB1RK2 w - - 7 12
r2qkb1r/pppnpp1p/6p1/3pP3/5P2/4P3/PPP1Q1PP/RNB1K2R w KQkq - 0 9
r2qkb1r/ppp3pp/5n2/3p1b2/3n4/P3P3/1PP1BPPP/RNBQK2R w KQkq - 0 9
r2q1rk1/pp1bppbp/2np1np1/2p5/4PP2/3P3P/PPP1B1PN/RNBQ1RK1 w - - 1 9
2kr3r/pppqbpp1/5n1p/3pn3/3P2b1/1P4P1/PB1NPPBP/R2Q1RK1 w - - 0 12
rn1qk2r/pp2npp1/1bp1p2p/3pP3/3P1Pb1/2P2NP1/PP4BP/RNBQK2R w KQkq - 1 10
rnb2r1k/1p3ppp/2pp3n/p3p1q1/P1B1P2N/2NPP2P/1PP3PK/R2Q1R2 w - - 7 13
r3kb1r/pp3ppp/2n1p3/2p5/4nP2/2P1Bb1P/PP2K1P1/RN3B1R w kq - 0 11
r2qr1k1/pp2ppbp/2p2np1/6B1/3P4/2P2Q1P/PPB2PP1/R4RK1 w - - 3 14
3r1rk1/1bq1bppp/p1n1pn2/1p1p2B1/4P3/PNNB4/1PPQ1PPP/3R1R1K w - - 0 15
r1bq1rk1/pppp2bp/2n1p1p1/5n2/2PP4/2N1BN2/PP2BPPP/R2QK2R w KQ - 2 9
rnb1k2r/pp1p1pp1/2p2n1p/2b1p3/2B1P2P/2NP1P2/PPP1NP2/R1B1K2R w KQkq - 1 9
rn1qk2r/1b3pp1/p3pb1p/1pp5/8/PB1P1N2/1PP1QPPP/R1B2RK1 w kq - 0 13
r2qk2r/ppp2pp1/3b3p/n2Bn3/4P1b1/1QP2N2/PP1N1PPP/R1B1K2R w KQkq - 2 11
r1b1k1nr/5ppp/p2p1q2/1pp1p3/2PbP3/1B6/PP1PQPPP/RNB1R1K1 w kq - 0 11
r4rk1/ppq1bppp/3ppn2/2p5/4PP2/2PPBQP1/PP1N3P/R4RK1 w - - 6 13
rn2kbnr/p1p2ppp/2ppp3/2q3B1/6P1/2NP1N2/PPP1PP1P/R2QK2R w KQkq - 1 9
r1bq1rk1/5ppp/p1nbpn2/1p1p4/8/2PP1NP1/PP1N1PBP/R1BQR1K1 w - - 0 11
r1bq1k1r/1p1nbppp/p1n1p3/3p2B1/5P1P/2N4N/PPPQ2P1/2KR1B1R w - - 1 13
r2q1rk1/pp3ppp/2n2n2/2bp4/5Bb1/3B1N2/PPPN1PPP/R2Q1RK1 w - - 8 10
r1b1kbnr/2pn1pp1/p2p3p/1p1Pp3/4P3/1B3P1P/PPP1NP2/RNB1K2R w KQkq - 1 10
r2q1rk1/ppp1nppp/3p4/3Np1b1/4P1B1/3P4/PPP2PPP/R2Q1RK1 w - - 6 15
r1b1k2r/pp3ppp/2nqpn2/3p4/3P4/2N1PN2/PP3PPP/R2QKB1R w KQkq - 0 9
rnbq1rk1/pp3pb1/2p1pn1p/4p1p1/3P3B/2NB1N2/PPP2PPP/R2Q1RK1 w - - 0 11
r4rk1/pp2ppbp/2n3p1/2pqP3/6b1/2PB1N2/PP3PPP/R1BQR1K1 w - - 1 12
2rqkb1r/pp1n1ppp/2p1pn2/3p4/3P4/1P2PB2/PBP2PPP/RN1Q1RK1 w k - 2 9
4r1k1/ppp2ppp/1q1b1n2/8/2P1r1b1/1P1Q1N2/P2B1PPP/RN3RK1 w - - 2 17
r2qr1k1/pp1n1ppp/3bbn2/2pp4/3P4/2NBBN1P/PPP2PP1/R2QR1K1 w - - 0 11
r1n2rk1/1p1b1pp1/p1p2q1p/2bp4/3P4/P3PN2/1P2BPPP/2RQ1RK1 w - - 0 17
r3k2r/1ppq1ppp/p1nbpnb1/1N1p2B1/3P2P1/5N1P/PPP1PPB1/R2Q1RK1 w kq - 0 11
r4rk1/pppq1p1p/2np1bp1/3Bp2b/4P3/2PP2QN/PP3PPP/RN2K2R w KQ - 0 12
r1b1rnk1/ppq2ppp/2p2n2/3p4/3P4/3BPN2/PPQN1PPP/2R2RK1 w - - 0 14
r1b1k1nr/1p1p1pp1/4pq2/p1b2P1p/2P1P1n1/1Q5N/PP2B1PP/RNB1K2R w KQkq - 0 11
r1b2rk1/1ppp1pp1/2n2q1p/p1b1p3/4P3/2PP1N2/PP2BPPP/RN1Q1RK1 w - - 0 9
r2qkbnr/pp1n2pp/2p2p2/3p3b/Q2P4/2N2N2/PP2PPPP/R1B1KB1R w KQkq - 2 9
r1bq1rk1/pp2npbp/2n1p1p1/2p1P3/3p1P2/3P1N2/PPP1BBPP/RN1Q1RK1 w - - 0 10
4rrk1/ppqb2pp/2pbp3/3pN3/3PpP2/PP2P3/1BP3PP/R2Q1R1K w - - 0 17
r1bq1rk1/2p1bppp/p1np1n2/1p2p3/4P3/1BPP1N1P/PP3PP1/RNBQK2R w KQ - 1 9
r1b1k2r/1p1pnpbp/p1n1p3/5p2/8/2NBPN1P/PPP2PP1/R1B2RK1 w kq - 1 12
4k1nr/2ppq1pp/rpn2p2/4P3/8/P3PN2/1B1Q1PPP/R3K2R w KQk - 0 13
rnb1kbnr/pp2q1pp/3p4/4p3/4p3/2P1BPP1/PP5P/RN1QKBNR w KQkq - 0 9
r4rk1/ppp2ppp/3pb3/4p3/1PB1P1nq/P2P1n1P/2P1NPP1/R1BQ1R1K w - - 3 13
rnbqk2r/pp2bpp1/2p4p/3p4/3P3B/5N2/PP2PPPP/R2QKB1R w KQkq - 0 9
r1b1r1k1/4ppbp/pq4p1/npp5/4N3/P1PPn1NP/1PB2PP1/R3QRK1 w - - 0 17
r3kbnr/1pp4p/p2q2p1/3pp2Q/1P6/P3P1P1/2P2P1P/RN2KB1R w KQkq - 0 12
r3k2r/2pq1ppp/p1nbpn2/1p1p4/3P1P2/2P1P2P/PPB2P2/RNBQK2R w KQkq - 1 11
r2qkb1r/p1pb1ppp/3p1n2/2p5/4P3/1N6/PPP2PPP/RNBQ1RK1 w kq - 2 9
2kr3r/pppq2p1/2nppb2/7p/P3P3/2P1BP2/1P1N2PP/R2Q1RK1 w - - 0 14
r4rk1/4nppp/pqp1p3/3pP3/QP4P1/2Pb1N1P/P4P2/R1B2RK1 w - - 2 18
r1b1k2r/ppp2ppp/2n2n2/2b1pP2/4P3/2N2N2/PPP3PP/R1BK1B1R w kq - 1 9
r1b1qrk1/p1p3pp/1p1b1n2/5p2/2P1p3/1P2P1P1/PBQ1NPBP/R4RK1 w - - 0 16
r1bqk1nr/1pp2ppp/1p1p4/8/2BnP3/8/PPP2PPP/R1BQK2R w KQkq - 0 9
rnbq1rk1/ppp2pp1/4pn1p/b7/2p5/PP4PN/1B1PPPBP/RN1Q1RK1 w - - 0 9
r2q1rk1/pp1n1ppp/2p1p3/3n1b2/2BPP3/2b2N2/PP1B1PPP/R2Q1RK1 w - - 0 11
r2q2k1/pp3pp1/2pbb2p/3n4/3P4/3B1N1P/PP1B1PP1/R3Q1K1 w - - 1 17
r1bq1rk1/bpp3pp/p1np2n1/4pp2/1PP1P3/P4NP1/1B1P1PBP/RN1Q1RK1 w - - 0 11
rnbq1rk1/pp3pp1/3p1n1p/2pPp1B1/2P1P3/2P2N2/P1Q2PPP/R3KB1R w KQ - 0 10
r1b1k2r/ppq2ppp/2nb1n2/1B1pN3/3P4/2N5/PP3PPP/R1BQK2R w KQkq - 8 10
3r1rk1/1pq1bppp/p1npbn2/8/2P5/1PN2N2/PB2BPPP/R2Q1RK1 w - - 4 13
r3kbnr/pp3ppp/2q1p3/2ppP3/3P4/P1N2b1P/1PP2PP1/R1BQ1RK1 w kq - 0 11
rn1qk2r/pp2bpp1/2n1p3/1B1pPb1p/Q2P4/2N2N2/PP3PPP/R1B1K2R w KQkq - 4 10
r2q1rk1/1ppnppbp/p2p1np1/8/1P1P2b1/P3PN2/1BPNBPPP/R2QK2R w KQ - 0 9
r1bq1rk1/2p1bppp/p1np1n2/1p2p3/4P3/1BNP1N1P/PPP2PP1/R1BQK2R w KQ - 3 9
r3r1k1/p1p2ppp/1pn5/2q5/2P3b1/3P1N2/PQPB2PP/R4R1K w - - 2 17
r3k1nr/pp3pp1/2n1p2p/2qpP3/8/2PB1Q1P/PP3PP1/RN2K2R w KQkq - 0 12
r3kbnr/ppp2ppp/5q2/4p3/3P2b1/1P1P1pP1/P2N1PBP/R1BQK2R w KQkq - 0 10
r1bq1rk1/pp3ppp/1bn2n2/3p4/8/1N1B1N2/PPP2PPP/R1BQ1RK1 w - - 6 10
r1b2rk1/p2p1ppp/2p1pn2/2q5/2P5/P1N1P3/3QBPPP/RR4K1 w - - 6 15
r2q1rk1/pb2ppbp/1p4p1/n1p1P3/2P5/1P3N2/PBQNRPPP/R5K1 w - - 0 16
rn1qk2r/pp2nppp/4p3/3pP3/8/3QbN2/PPP2PPP/RN3RK1 w kq - 0 10
2kr3r/pppqbppp/3p4/4nP2/2B1P3/2P5/PP4PP/R1BQ1RK1 w - - 0 15
r1bq1rk1/pp1nppbp/2np2p1/2p5/4P1P1/2NPB2P/PPPQNP2/R3KB1R w KQ - 1 9
r1b1k3/ppq1bp1r/2pp1n2/4p1pp/2BPPn2/1QP1B1N1/PP1N1PPP/R4RK1 w q - 0 15
r2q1rk1/2pbb1pp/p2p1n2/3Ppp2/2Q1P3/5N2/PP1N1PPP/R1B1R1K1 w - - 1 15
r2qkb1r/1p3ppp/p1n1b3/3pPp2/3P4/2N2N2/PP3PPP/R1BQ1RK1 w kq - 0 11
r1b1k2r/3p2pp/pp1bp3/2p2n2/P1P5/1PN2N1P/2PB1PP1/R3K2R w KQkq - 0 15
r2qk2r/1p1bnppp/p3p3/2bpP3/8/2N2N2/PPP2PPP/R1BQ1RK1 w kq - 2 10
r1bq1rk1/pp2ppbp/2n2np1/1B1p4/3PP3/5N2/PP3PPP/RNBQR1K1 w - - 0 9
5rk1/4bp1p/2p2np1/2Pp1b2/QP6/B3P2N/5PPP/qN3RK1 w - - 2 17
r2q1rk1/2p1bppp/p1p2n2/3p1PB1/8/2N2Q2/PPP2PPP/R4RK1 w - - 0 13
r1b1k2r/pp2bpp1/2qppn1p/2p5/4P3/2NP1N1P/PPP2PP1/R1BQK2R w KQkq - 1 10
r1b2rk1/ppqn1ppp/2pb1n2/3pp3/3PP3/2NQ2P1/PPP1NPBP/R1B1K2R w KQ - 1 9
r2q1rk1/pbpnppbp/1p1p1np1/3P4/4P3/2NB1N2/PPP1QPPP/R1B2RK1 w - - 6 9
r1bq1rk1/ppp1nppp/1bnp4/1B6/3PP3/5N1P/PP3PP1/RNBQ1RK1 w - - 1 9
2rbk2r/p3npp1/2n1p1b1/1p1pP2p/q1pP1PP1/2P1BN1P/PP1NQ1B1/R3R1K1 w k - 0 17
rn1qr1k1/pp2ppbp/2p3p1/8/2pP4/1P1QPN1P/PB3PP1/R4RK1 w - - 0 14
rn2kb1r/1b1p1ppp/pq2p1n1/1pp5/4P3/P2P1N2/NPPBBPPP/R2Q1RK1 w kq - 10 12
r5k1/ppr2ppp/4p3/3pPn2/3P4/qP3N1P/P1bQBPP1/R4RK1 w - - 9 18
2kr3r/pp1bq2p/1np1p1p1/3pPp2/3Pn2P/2PBPNP1/PP4Q1/R1B1K2R w KQ - 1 15
r2q1rk1/3nbpp1/2pp1n1p/1p2p3/4P2B/1Q1P1N2/1PPN1PPP/R4RK1 w - - 0 14
rnb2rk1/pp3ppp/5n2/3p4/P2Pp3/NP2PP2/3q2PP/2R1KBNR w K - 0 12
r3k2r/pp3ppp/2p2n2/q3p3/3n2b1/PPb2N2/1BPPBPPP/R2Q1RK1 w kq - 0 12
3r1rk1/ppp1ppbp/6p1/8/2Pnq3/1Q2BN2/PP3PPP/2R2RK1 w - - 0 14
r2qkb1r/5ppp/bpn1pn2/p2p4/3P4/P2B1P2/1PP1N1PP/RNBQ1RK1 w kq - 1 10
r1b1kb1r/3q2p1/p1n4p/1pp5/3ppPn1/NP4PN/PBPQP1BP/R4RK1 w kq - 0 16
r2q1rk1/ppp2pp1/2n2b1p/3p3b/3P4/2PB1N1P/PP3PP1/RN1QR1K1 w - - 0 12
r1q2rk1/1b1n1pbp/p3pnp1/1p1p4/3P1PPB/P2BPN2/1P5P/RN1Q1RK1 w - - 1 14
r1b1k2r/ppq1bp2/2p2nnp/4p1p1/2B1P3/2N1BN2/PPPRQPPP/3R2K1 w kq - 2 13
r1bq1rk1/p1p1bppn/2pp3p/8/3PP3/2N2N2/PP3PPP/R1BQR1K1 w - - 2 11
r1bq1rk1/pppn1pbp/3p1np1/3Pp1B1/4P3/2N2N2/PPP2PPP/R1Q1KB1R w KQ - 3 10
rn2k2r/5p2/2pp2pp/ppb1p3/4P3/PB1P1P2/1PP2P1P/R1B1K1R1 w Qkq - 0 14
r2qk2r/1p3pp1/1np1p1p1/p2nP1b1/P1pP4/2N2B1P/1P3PP1/R1BQ1RK1 w kq - 0 15
2rq1rk1/1p3pp1/p2pbn1p/4p3/2P1P3/5P2/n1PNBBPP/1R1QK2R w K - 0 16
3q1rk1/2pp1pbp/2n3p1/1r1Bp3/8/4P3/1P1P1PPP/R1BQ1RK1 w - - 0 17
r2q1rk1/pppb1ppp/2nbpn2/3p4/8/P1NPPN2/1PPBBPPP/R2Q1RK1 w - - 3 9
r3k1nr/pb1qpp1p/1ppp2p1/1B6/3bP3/PPN5/1BPPQPPP/R3K2R w KQkq - 0 11
3rr1k1/pppq1ppp/2n2b2/3np3/8/2PPBNNP/PP3PP1/R2Q1RK1 w - - 4 15
r2q1rk1/pp2bppp/3pbn2/2p3B1/3QP3/2NB4/PPP2PPP/R4RK1 w - - 0 11
r2q1rk1/ppp1b1pn/2np3p/1B2pb2/8/2PP1N1P/PP1N1PP1/R1BQR1K1 w - - 0 11
rnb2rk1/2q2ppp/p4n2/2pp4/8/2PBBN2/P1Q2PPP/R3R1K1 w - - 0 18
r1bqk2r/bppp1ppp/p1n2n2/3Np3/1P2P3/P2B1N2/2PP1PPP/R1BQ1RK1 w kq - 1 9
rn2k1nr/pp2q1pp/2p1p3/2Pp1p2/4bP2/3BP3/PPP3PP/RNBQ1RK1 w kq - 1 11
r2q1rk1/pp2bpp1/4bn1p/3pn3/3P4/2N3P1/PP3PBP/R1BQ1R1K w - - 0 13
r4rk1/ppp1npp1/5q1p/2bpp3/PPB1P3/2PP1P2/5P1P/R1BQ1RK1 w - - 0 13
2kr3r/pp2np2/1q2p2b/n2pP2p/2pP2pN/P1P5/1P2QPPP/R1B1RNK1 w - - 4 17
r2qk2r/pbppbppp/1pn1p3/4P3/2BP4/2P2N2/P1P2PPP/R1BQ1RK1 w kq - 2 9
r1b2rk1/pp3ppp/2n2B2/8/8/2P2q2/P1N1B1PP/R3K2R w KQ - 0 15
rn1q1rk1/ppp1bpp1/5n1p/3p2B1/3P2b1/3B1N2/PPP2PPP/RN1QR1K1 w - - 0 9
r1bqkb1r/ppp4p/2np4/4pp2/8/3PBP2/PPP1N1PP/R2QKB1R w KQkq - 0 9
r3kbnr/ppp3p1/3pp2p/8/3qP3/2N1B3/PPP2PPP/2KR3R w kq - 0 11
r2q1rk1/pp3ppp/2n1pn2/8/2P5/P4N2/P2PQPPP/R1B1K2R w KQ - 1 10
r1q2r2/pp2ppbk/3p2pp/2pP4/3n4/P2P1NPb/1PPB1PBP/R1Q2RK1 w - - 1 15
rn1qk2r/1bp1bppp/p3pn2/1p6/3P4/1BN1PN2/PP3PPP/R1BQ1RK1 w kq - 6 9
r1bq1rk1/pp1n1pbp/3p1np1/2p5/1P1B4/P3PNP1/2PN1P1P/R2QKB1R w KQ - 0 10
r1bqkb1r/5p1p/p1np1p2/1p2p3/4P3/N1N5/PPP2PPP/R2QKB1R w KQkq - 0 10
rn1qkb1r/1p3ppp/p3pn2/3p4/N2P4/3Q4/PPP2PPP/R1B1K1NR w KQkq - 0 9
r1bqk2r/3n1ppp/p2b1n2/2pp4/1p1P4/2N1PN2/PPB2PPP/R1BQK2R w KQkq - 0 11
r4rk1/1p2bppp/p1np1n2/2p5/8/2NP1N1P/PPP2PP1/R1B1K2R w KQ - 0 12
r1bq1rk1/3n1pp1/1p2pn1p/p2p4/1b1P4/1P2PN2/PBQNBPPP/R4RK1 w - - 0 12
r1b2rk1/pp1np1bp/2p2np1/3pNpB1/3P1P1P/q2BPN2/2P3P1/1R1Q1RK1 w - - 1 13
r1b2rk1/ppqn1pbp/2pp1np1/3Pp3/2P5/2N1PNP1/PP3PBP/R1BQ1RK1 w - - 1 10
r2q1r2/3n1pkp/p3p1p1/1ppnN3/8/2NP4/PPP2PPP/R1Q1R1K1 w - - 1 15
rn1q1rk1/pp2bppp/2p1pn2/5bB1/3P4/5B2/PPP1NPPP/RN1Q1RK1 w - - 4 9
r2q1rk1/1p2ppbp/p1np1np1/2p5/4PPb1/2PP1NP1/PP2Q1BP/RNB2RK1 w - - 2 10
r1b2rk1/1p3p1p/p3pnp1/4q3/P2n4/2N2Q2/BP1P1PPP/R1B2RK1 w - - 2 16
r2qk2r/pb1pnpbp/1pn1p1p1/2p5/2P1P3/2NP2PP/PP2NPB1/R1BQK2R w KQkq - 2 9
r1bq1rk1/3nppbp/p2p1np1/2p5/3P4/P3PNP1/1BPN1PBP/R2QK2R w KQ - 0 11
r3k2r/5ppp/p1npbb2/qp1Np3/2P1P3/N7/PP3PPP/R2QKB1R w KQkq - 1 13
r2qkb1r/ppp2ppp/2np4/8/4P1b1/2P2N2/PP3PPP/RNBQK2R w KQkq - 2 9
r1bqk2r/1p3pbp/2n1pnp1/p2p4/3P4/BP2PN2/P3BPPP/RN1Q1RK1 w kq - 0 10
r1b2rk1/ppq2pbp/1n1p1np1/2pPp3/QPP1P3/3B1N2/P2N1PPP/R1B2RK1 w - - 1 12
r1b2rk1/pp1nqppp/2p1p3/8/2BPn3/5N2/PPP2PPP/R2QR1K1 w - - 0 12
r2qr1k1/pbpp1p1p/1p1b1npB/8/4P3/2NB1P1P/PPP2P2/R2QK1R1 w Q - 2 12
r2qk2r/pbpn1pbp/1p1ppnp1/8/2PP1B2/2NBPN2/PPQ2PPP/R3K2R w KQkq - 2 9
rnb2rk1/ppq3pp/2pbpn2/3p1pB1/3P4/2P2NP1/PP2PPBP/RN1QR1K1 w - - 5 9
r1b2rk1/ppq3pp/2nb1n2/3p4/2PPpp2/1P4P1/PB1NNPBP/R2Q1RK1 w - - 0 13
r1bq1rk1/2b3pp/p2p1p2/1p1Pp1P1/3pP2P/P2P4/1P1B1P2/R2QKB1R w KQ - 0 16
r1bq1rk1/1p1nbppp/p1p1pn2/3p4/1P1P4/P3PNP1/1BPN1P1P/R2QKB1R w KQ - 1 9
r4rk1/1pbn1pp1/p1p1pn1p/2Pp1bB1/1P1P4/2N1PN2/1P1K1PPP/2R2B1R w - - 0 14
r2q1rk1/ppp1npb1/3p1npp/3Pp1B1/4P1b1/3B1N2/PPPN1PPP/R3QRK1 w - - 0 11
rnb2rk1/6pp/1npbpp2/pp6/3PB3/P3BN2/1PPN1PPP/R3R1K1 w - - 7 14
rnb2rk1/ppp2ppp/8/8/1bPN4/4B3/PP2BPPP/R3K2R w KQ - 5 12
r1b1k1nr/pppp1ppp/8/3N4/5b2/8/PPP1PPPP/R3KB1R w KQkq - 0 9
r2qkb1r/pQ4pp/3p1n2/3pp3/4P3/2P2P2/PP5P/RNB1K2R w KQkq - 0 13
r1b1k2r/pp1p2pp/2n1p3/q1p2p2/2PP4/2P3P1/P2QPPBP/R3K1NR w KQkq - 1 11
r1b1kbnr/p4ppp/2p1q3/2pp4/4P3/2N2NR1/PPPP1P1P/R1BQK3 w Qkq - 0 10
2kr3r/p1pbqpb1/Npn1p1pp/2P1P3/3P4/3n1N2/PP4PP/R1BQK2R w KQ - 0 16
r2q1rk1/ppp2ppp/2nb4/3pp3/1P1P2n1/P1P1PN2/5PPP/RNBQ1RK1 w - - 0 11
r2qk2r/1pp2pp1/p2p1n1p/2b1p3/2bnP3/P1NPBN1P/1PPQ1PP1/2KR3R w kq - 0 11
r2qkbnr/1p2ppp1/p1n3bp/3p4/1P4P1/P1NP1N1P/4PP2/R1BQKB1R w KQkq - 0 10
r3k1r1/1p1nbp2/p1p1pp1p/2Pp4/1P1P3N/2N5/1Pb1PPPP/R3KB1R w KQq - 3 15
r4rk1/pbp2pp1/1p2pq1p/3n4/3P4/P1PB1N2/2PQ1PPP/R3R1K1 w - - 1 15
rn1qk2r/pp5p/2p1pnp1/3pp3/4P3/3PQ3/PPP2PPP/RN2K1NR w KQkq - 0 11
rn1qk1nr/4bppp/p1b1p3/1pp5/2P5/P2P1N2/BP3PPP/RNBQ1RK1 w kq - 3 10
rn1q1rk1/pp2npbp/2p1b1p1/3p4/3P4/1PN1PN2/PB2BPPP/R2Q1RK1 w - - 3 11
r2qk1nr/pp2bppp/2pp4/4n3/4P3/1BN5/PPP1QP1P/R1B2K1R w kq - 2 14
r1bqk2r/pp3pp1/5n1p/2bpN3/2Bn4/2NP4/PPP2PPP/R1BQK2R w KQkq - 0 10
rnb2rk1/pp3ppp/2p1p3/q7/2BP4/2b1PN2/PPQ2PPP/R4RK1 w - - 0 12
rnbq1rk1/ppp3bp/4ppp1/3pPn2/3P4/2P1BN1P/PP1Q1PP1/RN2KB1R w KQ - 2 9
r2q1rk1/ppp2pbp/2np1np1/4p3/3PP1b1/3B1N2/PPP1NPPP/R1BQR1K1 w - - 2 9
r2q1rk1/1p1n1pp1/p1pbpn1p/5b2/1PBP4/PN2PN1P/1B3PP1/R2QK2R w KQ - 5 12
r2q1rk1/pppb1ppn/3p3p/8/4Pb2/1PNP2QP/1PP3PN/R4RK1 w - - 0 15
r1bqr1k1/pp3pp1/3p1nnp/2pP4/2P1PP2/2PN4/P5BP/R1BQ1RK1 w - - 1 15
r3k1nr/pbpn1pbp/1p1qp1p1/8/2BP4/5N2/PPP2PPP/RNBQ1RK1 w kq - 0 9
r1bqk2r/pp2bppp/2n1p3/3n4/2BP4/2N2N2/PP3PPP/R1BQK2R w KQkq - 2 9
r3k1nr/ppp2ppp/2bp4/8/4P3/2qB1P2/PPP3PP/RN3RK1 w kq - 0 12
rnb1kb1r/pppq3p/3ppp2/8/2P1P3/3B4/PP3PPP/RN1QK1NR w KQkq - 0 9
r2qkb1r/pp3ppp/2n1pn2/1B1p2B1/3P4/2NQ1b2/PPP2PPP/R3K2R w KQkq - 0 9
r2qk2r/pp3ppp/2nbpn2/3p1b2/3P4/1BP2N2/PP3PPP/RNBQ1RK1 w kq - 4 9
r4rk1/p2b1pp1/1p1qpn1p/3p4/3P4/4P3/PP1B1PPP/R2QKB1R w KQ - 0 13
r1bqk2r/pp3ppp/2n2n2/3p4/1b1N4/1P2P3/PB3PPP/RN1QKB1R w KQkq - 1 9
r4rk1/1p2bppp/p1b1p3/3n4/3P4/2N2B1P/PP3PP1/R1B2RK1 w - - 6 16
1rbq1rk1/pp3ppp/3p1b2/3Bp1B1/3pP3/3P1Q1P/PPP2PP1/R3R1K1 w - - 5 14
1rbq1rk1/2pn1ppp/p4n2/4p3/P1Pp4/R2P1NP1/3NPPBP/3Q1RK1 w - - 0 14
r4rk1/1pqbbppp/p1nppn2/8/3NP3/2PB1N1P/PP2QPP1/R1B2RK1 w - - 1 12
r1b1k2r/ppppnppp/5q2/8/3bP3/2P5/PP2BPPP/RN1QK2R w KQkq - 0 9
r2qk2r/3n1ppp/p2bpn2/1p1p4/3P1Pb1/P1NBPN2/1P4PP/R1BQ1RK1 w kq - 2 11
r3k2r/2p1q1p1/p2ppn1p/1p2p3/1P1bP3/P1NP2QP/2P1KPP1/1RB4R w kq - 1 17
rnb1kb1r/3n1p2/2pp2pp/pp2p3/1P2P3/1B1P4/PBP2PPP/RN2K1NR w KQkq - 0 11
r1b1k2r/ppp1qppp/4p3/4n3/3P4/2nB3P/PP1Q1PP1/R1B1R1K1 w kq - 0 16
r2q1rk1/pppb1pp1/2nbpn1p/3p4/1P1P4/P1NBPN2/2PB1PPP/R2QK2R w KQ - 1 9
r2qk2r/1b3ppp/p3pn2/1pb5/8/3BPN2/PP3PPP/R1BQ1RK1 w kq - 0 13
1rbq1rk1/pp1pnpbp/2n1p1p1/2p5/2P5/2NPP1P1/PP2NPBP/R1BQ1RK1 w - - 3 9

BIN
res/pieces/black-bishop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
res/pieces/black-king.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
res/pieces/black-knight.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
res/pieces/black-pawn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 B

BIN
res/pieces/black-queen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
res/pieces/black-rook.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1014 B

BIN
res/pieces/trimmed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
res/pieces/white-bishop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
res/pieces/white-king.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
res/pieces/white-knight.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
res/pieces/white-pawn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
res/pieces/white-queen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
res/pieces/white-rook.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
res/stickfosh.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

View File

@ -0,0 +1,38 @@
#include "ai_vs_ai.hpp"
#include "controller.hpp"
#include <thread>
AIvsAIController::AIvsAIController(Board b, View& v, ai::AI& p1, ai::AI& p2)
: Controller(b, v),
p1(p1),
p2(p2) {}
void AIvsAIController::start() {
std::thread view_thread([&]() { view.show(); });
ai::AI* current_player;
while (!board.is_terminal()) {
current_player = board.white_to_play ? &p1 : &p2;
std::cout << typeid(*current_player).name() << " to play" << std::endl;
Move move = current_player->search(board);
make_move(move);
}
view_thread.join();
}
void AIvsAIController::make_move(Move move) {
board = board.make_move(move);
std::cout << "Made move: " << move << std::endl;
view.update_board(board, -1, {});
Colour current_colour = board.white_to_play ? White : Black;
if (board.is_checkmate())
view.notify_checkmate(current_colour);
if (board.is_stalemate())
view.notify_stalemate(current_colour);
}
void AIvsAIController::on_tile_selected(int, int) {}

View File

@ -0,0 +1,19 @@
#pragma once
#include "../model/ais/ai.hpp"
#include "../model/utils/coords.hpp"
#include "../model/utils/move.hpp"
#include "../view/view.hpp"
#include "controller.hpp"
class AIvsAIController : public Controller {
ai::AI &p1, &p2;
protected:
void make_move(Move) override;
public:
AIvsAIController(Board, View&, ai::AI&, ai::AI&);
void on_tile_selected(int, int) override;
void start() override;
};

View File

@ -0,0 +1,7 @@
#include "controller.hpp"
#include "../view/view.hpp"
Controller::Controller(Board b, View& v): board(b), view(v) {
v.set_controller(this);
}

View File

@ -0,0 +1,17 @@
#pragma once
#include "../model/board/board.hpp"
class View;
class Controller {
protected:
Board board;
View& view;
virtual void make_move(Move) = 0;
public:
Controller(Board, View&);
virtual void start() = 0;
virtual void on_tile_selected(int, int) = 0;
};

View File

@ -1,57 +0,0 @@
from logic.board import Board
from logic.move import Move
from logic.pieces.piece import Piece
from logic.position import Position
from view.view import View
class Controller:
def __init__(self, board: Board, view: View) -> None:
self._board = board
self._view = view
self._view.set_controller(self)
self._reset_selection()
self._selected_piece: Piece = None
self._legal_moves: list[Move] = []
def _reset_selection(self):
self._selected_piece = None
self._legal_moves = []
self._view.update_board(self._board, self._selected_piece, self._legal_moves)
def _show_legal_moves(self, pos: Position):
piece = self._board.piece_at(pos.x, pos.y)
if piece:
if piece.colour != self._board._turn:
return
self._selected_piece = piece
self._legal_moves = piece.legal_moves(self._board)
self._view.update_board(self._board, self._selected_piece, self._legal_moves)
else:
self._reset_selection()
def _make_move(self, move: Move) -> None:
self._board = self._board.make_move(move)
self._reset_selection()
def on_tile_selected(self, x: int, y: int) -> None:
pos = Position(x, y)
print(f"Clicked on {pos.to_algebraic()}")
piece = self._board.piece_at(x, y)
if self._selected_piece is None or (piece is not None and piece != self._selected_piece):
self._show_legal_moves(pos)
else:
legal_moves_positions = [move for move in self._legal_moves if move.pos == pos]
assert len(legal_moves_positions) <= 1, f"Apparently we can make multiple moves towards {pos.to_algebraic()} with {type(self._selected_piece)}, which doesn't make sense..."
if len(legal_moves_positions) == 0: # click on a square outside of the possible moves
self._reset_selection()
else:
move = legal_moves_positions[0]
self._make_move(move)

View File

@ -0,0 +1,29 @@
#include "human_vs_ai.hpp"
#include <algorithm>
HumanVsAIController::HumanVsAIController(Board b, View& v, ai::AI& ai)
: ManualController(b, v),
ai(ai) {}
void HumanVsAIController::on_tile_selected(int x, int y) {
Coords c{x, y};
Piece piece = board.piece_at(c);
if (selected_index == -1
|| (piece != Piece::None && piece != selected_piece
&& (piece & 0b11000) != (selected_piece & 0b11000))) {
show_legal_moves(c);
} else {
auto boh = std::find(targets.begin(), targets.end(), c.to_index());
if (boh != targets.end()) {
make_move(Move{selected_index, c.to_index()});
if (board.is_terminal())
return;
Move ai_move = ai.search(board);
make_move(ai_move);
} else
reset_selection();
}
}

View File

@ -0,0 +1,14 @@
#pragma once
#include "../model/ais/ai.hpp"
#include "../view/view.hpp"
#include "manual.hpp"
class HumanVsAIController : public ManualController {
protected:
ai::AI& ai;
public:
HumanVsAIController(Board, View&, ai::AI&);
void on_tile_selected(int, int) override;
};

74
src/controller/manual.cpp Normal file
View File

@ -0,0 +1,74 @@
#include "manual.hpp"
#include "../model/utils/utils.hpp"
#include <algorithm>
ManualController::ManualController(Board b, View& v): Controller(b, v) {
reset_selection();
}
void ManualController::start() {
reset_selection();
view.show();
}
void ManualController::on_tile_selected(int x, int y) {
Coords c{x, y};
Piece piece = board.piece_at(c);
if (selected_index == -1
|| (piece != Piece::None && piece != selected_piece
&& (piece & 0b11000) != (selected_piece & 0b11000))) {
show_legal_moves(c);
} else {
auto boh = std::find(targets.begin(), targets.end(), c.to_index());
if (boh != targets.end())
make_move(Move{selected_index, c.to_index()});
else
reset_selection();
}
}
void ManualController::reset_selection() {
selected_index = -1;
selected_piece = Piece::None;
targets = {};
view.update_board(board, selected_index, targets);
}
void ManualController::show_legal_moves(Coords xy) {
Colour colour = board.colour_at(xy);
if (colour) {
Colour to_play = board.white_to_play ? White : Black;
if (colour != to_play)
return;
selected_index = xy.to_index();
selected_piece = board.piece_at(xy);
targets = to_target_square(legal_moves(selected_piece, board, xy));
view.update_board(board, xy.to_index(), targets);
}
}
void ManualController::make_move(Move move) {
// handle promotion before making the move
Colour colour = board.white_to_play ? White : Black;
Coords source = Coords::from_index(move.source_square);
if (board.piece_at(move.source_square) == Piece::Pawn
&& board.colour_at(move.source_square) == White && source.y == 6) {
Piece promotion_piece = (Piece) (colour | view.ask_about_promotion());
move.promoting_to = promotion_piece;
}
std::cout << "Move made: " << move << std::endl;
board = board.make_move(move);
reset_selection();
Colour current_colour = board.white_to_play ? White : Black;
if (board.is_checkmate())
view.notify_checkmate(current_colour);
if (board.is_stalemate())
view.notify_stalemate(current_colour);
}

22
src/controller/manual.hpp Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include "../model/utils/coords.hpp"
#include "../model/utils/move.hpp"
#include "../view/view.hpp"
#include "controller.hpp"
class ManualController : public Controller {
protected:
int8_t selected_index;
Piece selected_piece;
std::vector<int8_t> targets;
void reset_selection();
void show_legal_moves(Coords);
void make_move(Move) override;
public:
ManualController(Board, View&);
void on_tile_selected(int, int) override;
void start() override;
};

View File

@ -1,143 +0,0 @@
from logic.move import Move
from logic.pieces.bishop import Bishop
from logic.pieces.king import King
from logic.pieces.knight import Knight
from logic.pieces.queen import Queen
from logic.pieces.rook import Rook
from logic.pieces.pawn import Pawn
from logic.pieces.piece import Colour, Piece
from logic.position import Position
from typing import Type
class Board:
def __init__(self) -> None:
self._white: dict[Position, Piece] = {}
self._black: dict[Position, Piece] = {}
self._turn = None
self._white_castling_write = set()
self._black_castling_write = set()
self._en_passant_target = None
@staticmethod
def _piece_class_from_char(c: str) -> Type[Piece]:
assert len(c) == 1, f"The piece {c} isn't denoted by 1 character"
c = c.lower()
if c == "p":
return Pawn
if c == "r":
return Rook
if c == "n":
return Knight
if c == "b":
return Bishop
if c == "q":
return Queen
if c == "k":
return King
raise ValueError(f"Unknown piece '{c}'")
@staticmethod
def setup_FEN_position(position: str) -> "Board":
ret = Board()
index = 0
# -- Pieces
pieces = "prnbqk" # possible pieces
numbers = "12345678" # possible number of empty squares
x = 0
y = 7 # FEN starts from the top left, so 8th rank
for c in position:
index += 1
if c == " ":
break
if c in pieces or c in pieces.upper():
pos = Position(x, y)
piece = Board._piece_class_from_char(c)
if c.isupper():
ret._white[pos] = piece(pos, Colour.WHITE)
else:
ret._black[pos] = piece(pos, Colour.BLACK)
x += 1
continue
if c in numbers:
x += int(c)
if c == '/':
x = 0
y -= 1
# -- Active colour
if position[index] == "w":
ret._turn = Colour.WHITE
elif position[index] == "b":
ret._turn = Colour.BLACK
else:
raise ValueError(f"The FEN position is malformed, the active colour should be either 'w' or 'b', but is '{position[index]}'")
index += 1
# -- Castling Rights
for c in position[index:]:
index += 1
if c == "-" or c == " ":
break
sides = "kq"
assert c in sides or c in sides.upper(), f"The FEN position is malformed, the castling rights should be either k or q (both either lower- or upper-case), instead is '{c}'"
if c == "K":
ret._white_castling_write.add(Board.KING_SIDE_CASTLE)
if c == "Q":
ret._white_castling_write.add(Board.QUEEN_SIDE_CASTLE)
if c == "k":
ret._black_castling_write.add(Board.KING_SIDE_CASTLE)
if c == "q":
ret._black_castling_write.add(Board.QUEEN_SIDE_CASTLE)
# -- En passant target
if position[index] != "-":
ret._en_passant_target = position[index:index+2]
return ret
def piece_at(self, x: int, y: int) -> Piece | None:
pos = Position(x, y)
white_piece = self._white.get(pos, None)
black_piece = self._black.get(pos, None)
assert white_piece == None or black_piece == None, f"There are two pieces at the same position {pos}, this shouldn't happen!"
if white_piece != None:
return white_piece
return black_piece
def make_move(self, move: Move) -> "Board":
dest_piece = self.piece_at(move.pos.x, move.pos.y)
if dest_piece:
assert dest_piece.colour != move.piece.colour, "A piece cannot cannot eat another piece of the same colour"
ret = Board()
ret._white = self._white.copy()
ret._black = self._black.copy()
ret._turn = Colour.WHITE if self._turn == Colour.BLACK else Colour.BLACK
ret._white_castling_write = self._white_castling_write.copy()
ret._black_castling_write = self._black_castling_write.copy()
ret._en_passant_target = self._en_passant_target
piece = move.piece
if piece.colour == Colour.WHITE:
del ret._white[piece.pos]
ret._white[move.pos] = piece.move_to(move.pos)
if move.pos in ret._black:
del ret._black[move.pos]
else:
del ret._black[piece.pos]
ret._black[move.pos] = piece.move_to(move.pos)
if move.pos in ret._white:
del ret._white[move.pos]
return ret

View File

@ -1,25 +0,0 @@
# from logic.pieces.piece import Piece
from logic.position import Position
from enum import Enum
class Move:
def __init__(self, is_capturing: bool) -> None:
self.is_capturing = is_capturing
def to_algebraic(self) -> str:
raise NotImplementedError("The move can't be translated to algbraic notation, as it was not implemented")
@staticmethod
def from_algebraic(move: str) -> "Move":
raise NotImplementedError("The move can't be translated from algbraic notation, as it was not implemented")
class PieceMove(Move):
def __init__(self, piece: "Piece", pos: Position,/, is_capturing: bool = False) -> None:
super().__init__(is_capturing)
self.piece = piece
self.pos = pos
class Castle(Move, Enum):
KING_SIDE_CASTLE = False
QUEEN_SIDE_CASTLE = False

View File

@ -1,21 +0,0 @@
from logic.move import Move
from .piece import Piece
class Bishop(Piece):
def legal_moves(self, board: "Board") -> list[Move]:
ret = []
# looking north east
ret.extend(self._look_direction(board, 1, 1))
# looking south east
ret.extend(self._look_direction(board, 1, -1))
# looking south west
ret.extend(self._look_direction(board, -1, -1))
# looking north west
ret.extend(self._look_direction(board, -1, 1))
return ret

View File

@ -1,5 +0,0 @@
from .piece import Piece
class King(Piece):
pass

View File

@ -1,16 +0,0 @@
from .piece import Piece
class Knight(Piece):
def legal_moves(self, board: "Board") -> list["Move"]:
ret = []
for dx, dy in [
(+2, +1), (+1, +2), # north east
(+2, -1), (+1, -2), # south east
(-2, -1), (-1, -2), # south west
(-2, +1), (-1, +2), # north west
]:
move = self._move_for_position(board, self.pos.x + dx, self.pos.y + dy)
if move is not None:
ret.append(move)
return ret

View File

@ -1,40 +0,0 @@
from logic.move import Move, PieceMove
from logic.pieces.piece import Colour, Piece
from logic.position import Position
class Pawn(Piece):
def legal_moves(self, board) -> list[Move]:
ret = []
# can we capture to the left?
if self.pos.x > 0 and (
(self.colour == Colour.WHITE and (capturable_piece := board.piece_at(self.pos.x - 1, self.pos.y + 1)))
or
(self.colour == Colour.BLACK and (capturable_piece := board.piece_at(self.pos.x - 1, self.pos.y - 1)))
):
if capturable_piece.colour != self.colour:
ret.append(PieceMove(self, capturable_piece.pos, is_capturing = True))
# can we capture to the right?
if self.pos.x < 7 and (
(self.colour == Colour.WHITE and (capturable_piece := board.piece_at(self.pos.x + 1, self.pos.y + 1)))
or
(self.colour == Colour.BLACK and (capturable_piece := board.piece_at(self.pos.x + 1, self.pos.y - 1)))
):
if capturable_piece.colour != self.colour:
ret.append(PieceMove(self, capturable_piece.pos, is_capturing = True))
if self.colour == Colour.WHITE:
for dy in range(1, 3 if self.pos.y == 1 else 2):
if self.pos.y + dy > 7 or board.piece_at(self.pos.x, self.pos.y + dy):
break
pos = Position(self.pos.x, self.pos.y + dy)
ret.append(PieceMove(self, pos))
else:
for dy in range(1, 3 if self.pos.y == 6 else 2):
if self.pos.y - dy < 0 or board.piece_at(self.pos.x, self.pos.y - dy):
break
pos = Position(self.pos.x, self.pos.y - dy)
ret.append(PieceMove(self, pos))
return ret

View File

@ -1,51 +0,0 @@
from logic.move import Move, PieceMove
from logic.position import Position
from enum import Enum
class Colour(Enum):
WHITE = "white"
BLACK = "black"
class Piece:
def __init__(self, pos: Position, colour: Colour) -> None:
self.pos = pos
assert colour == Colour.WHITE or colour == Colour.BLACK, "The colour of the piece must be either Piece.WHITE or Piece.BLACK"
self.colour = colour
def _look_direction(self, board: "Board", mult_dx: int, mult_dy: int):
ret = []
for d in range(1, 8):
dx = mult_dx * d
dy = mult_dy * d
move = self._move_for_position(board, self.pos.x + dx, self.pos.y + dy)
if move is None:
break
ret.append(move)
if move.is_capturing:
break
return ret
def _move_for_position(self, board: "Board", x: int, y: int) -> Move | None:
if not Position.is_within_bounds(x, y):
return None
piece = board.piece_at(x, y)
if piece is None:
return PieceMove(self, Position(x, y))
if piece.colour != self.colour:
return PieceMove(self, Position(x, y), is_capturing=True)
return None
def position(self) -> Position:
return self.pos
def move_to(self, pos: Position) -> "Piece":
ret = type(self)(pos, self.colour)
return ret
def legal_moves(self, board: "Board") -> list["Move"]:
raise NotImplementedError(f"Can't say what the legal moves are for {type(self).__name__}, the method hasn't been implemented yet")

View File

@ -1,32 +0,0 @@
from logic.move import Move
from .piece import Piece
class Queen(Piece):
def legal_moves(self, board: "Board") -> list[Move]:
ret = []
# looking north east
ret.extend(self._look_direction(board, 1, 1))
# looking south east
ret.extend(self._look_direction(board, 1, -1))
# looking south west
ret.extend(self._look_direction(board, -1, -1))
# looking north west
ret.extend(self._look_direction(board, -1, 1))
# looking east
ret.extend(self._look_direction(board, 1, 0))
# looking south
ret.extend(self._look_direction(board, 0, -1))
# looking west
ret.extend(self._look_direction(board, -1, 0))
# looking north
ret.extend(self._look_direction(board, 0, 1))
return ret

View File

@ -1,20 +0,0 @@
from logic.move import Move
from .piece import Piece
class Rook(Piece):
def legal_moves(self, board: "Board") -> list[Move]:
ret = []
# looking east
ret.extend(self._look_direction(board, 1, 0))
# looking south
ret.extend(self._look_direction(board, 0, -1))
# looking west
ret.extend(self._look_direction(board, -1, 0))
# looking north
ret.extend(self._look_direction(board, 0, 1))
return ret

View File

@ -1,36 +0,0 @@
class Position:
_RANKS = range(1, 9)
_FILES = "abcdefgh"
_MIN_POS = 0
_MAX_POS = 7
def __init__(self, x, y) -> None:
assert x >= self._MIN_POS and x <= self._MAX_POS, f"Invalid argument: x should be between {self._MIN_POS} and {self._MAX_POS}, but is {x}"
assert y >= self._MIN_POS and y <= self._MAX_POS, f"Invalid argument: y should be between {self._MIN_POS} and {self._MAX_POS}, but is {y}"
self.x = x
self.y = y
@staticmethod
def is_within_bounds(x: int, y: int) -> bool:
return x >= Position._MIN_POS and x <= Position._MAX_POS \
and y >= Position._MIN_POS and y <= Position._MAX_POS
def to_algebraic(self) -> str:
return f"{Position._FILES[self.x]}{Position._RANKS[self.y]}"
def __eq__(self, value: object, /) -> bool:
if type(value) != type(self):
return False
return value.x == self.x and value.y == self.y
def __hash__(self) -> int:
return hash((self.x, self.y))
def __str__(self) -> str:
return f"{self.x, self.y}"
def __repr__(self) -> str:
return str(self)

81
src/main.cpp Normal file
View File

@ -0,0 +1,81 @@
#include "controller/ai_vs_ai.hpp"
#include "controller/controller.hpp"
#include "controller/human_vs_ai.hpp"
#include "controller/manual.hpp"
#include "model/ais/ai.hpp"
#include "model/perft/perft.hpp"
#include "view/gui.hpp"
#include "view/noop.hpp"
#include "view/view.hpp"
#include <chrono>
#include <iostream>
#include <string>
void print_usage() {
std::cout << "Usage: chess_ai [OPTIONS]\n";
std::cout << "Options:\n";
std::cout
<< " --mode <human_vs_ai|ai_vs_ai|human_vs_human|perft> Choose the "
"game mode.\n";
std::cout << " --ai1 <version> Choose the first AI version (for ai_vs_ai "
"mode).\n";
std::cout << " --ai2 <version> Choose the second AI version (for "
"ai_vs_ai mode).\n";
std::cout << " --fen <FEN_STRING> Set a custom FEN position.\n";
std::cout << " --help Show this help message.\n";
}
int main(int argc, char* argv[]) {
std::string mode = "human_vs_ai";
std::string ai1_version = "v0_random";
std::string ai2_version = "v6_iterative_deepening";
std::string fen =
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--mode" && i + 1 < argc) {
mode = argv[++i];
} else if (arg == "--ai1" && i + 1 < argc) {
ai1_version = argv[++i];
} else if (arg == "--ai2" && i + 1 < argc) {
ai2_version = argv[++i];
} else if (arg == "--fen" && i + 1 < argc) {
fen = argv[++i];
} else if (arg == "--help") {
print_usage();
return 0;
} else {
std::cerr << "Unknown option: " << arg << "\n";
print_usage();
return 1;
}
}
Board board = Board::setup_fen_position(fen);
GUI gui;
Controller* controller = nullptr;
if (mode == "human_vs_ai") {
ai::v6_iterative_deepening ai(false, std::chrono::milliseconds(2000));
controller = new HumanVsAIController(board, gui, ai);
} else if (mode == "ai_vs_ai") {
ai::v0_random p1(true, std::chrono::milliseconds(1000));
ai::v6_iterative_deepening p2(false, std::chrono::milliseconds(2000));
controller = new AIvsAIController(board, gui, p1, p2);
} else if (mode == "human_vs_human") {
controller = new ManualController(board, gui);
} else if (mode == "perft") {
perft();
return 0;
} else {
std::cerr << "Invalid mode selected!\n";
print_usage();
return 1;
}
controller->start();
delete controller;
return 0;
}

View File

@ -1,14 +0,0 @@
from controller.controller import Controller
from logic.board import Board
from view.gui import GUI
from view.tui import TUI
if __name__ == "__main__":
initial_board_position = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
board = Board.setup_FEN_position(initial_board_position)
view = GUI()
controller = Controller(board, view)
view.show()

62
src/model/ais/ai.cpp Normal file
View File

@ -0,0 +1,62 @@
#include "ai.hpp"
#include <condition_variable>
#include <ostream>
#include <thread>
static long int position_counter = 0;
Move ai::AI::search(const Board& b) {
position_counter = 0;
Move result;
std::condition_variable cv;
std::mutex cv_mutex;
double elapsed;
stop_computation = false;
// Start computation in a separate thread
std::thread computation_thread([&]() {
auto start = std::chrono::steady_clock::now();
result = _search(b);
auto end = std::chrono::steady_clock::now();
elapsed =
std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
.count();
// Notify the timer thread that computation is done
{
std::lock_guard<std::mutex> lock(cv_mutex);
stop_computation = true;
cv.notify_one();
}
});
// Start a timer thread to stop computation after given time
std::thread timer_thread([&]() {
std::unique_lock<std::mutex> lock(cv_mutex);
if (!cv.wait_for(lock, thinking_time, [&] {
return stop_computation.load();
})) {
// Timeout reached; set stop flag
stop_computation = true;
}
});
// Wait for computation thread to finish
computation_thread.join();
// Ensure timer thread is also stopped
timer_thread.join();
std::cout << "Took " << elapsed << " ms, " << "Looked at "
<< position_counter << " positions" << std::endl;
return result;
}
int ai::AI::eval(const Board& b) {
int ret = _eval(b);
position_counter++;
return ret;
}

104
src/model/ais/ai.hpp Normal file
View File

@ -0,0 +1,104 @@
#pragma once
#include "../board/board.hpp"
#include <atomic>
#include <chrono>
namespace ai {
class AI {
protected:
bool am_white;
std::chrono::milliseconds thinking_time;
virtual Move _search(const Board&) = 0;
public:
AI(bool am_white, std::chrono::milliseconds tt)
: am_white(am_white),
thinking_time(tt) {}
std::atomic<bool> stop_computation = false;
Move search(const Board& b);
int eval(const Board&);
virtual int _eval(const Board&) = 0;
};
struct v0_random : public AI {
v0_random(bool w, std::chrono::milliseconds tt): AI(w, tt) {}
Move _search(const Board&) override;
int _eval(const Board&) override {
return 0;
};
};
class v1_pure_minimax : public AI { // looks two moves ahead
int _search(const Board&, int);
public:
v1_pure_minimax(bool w, std::chrono::milliseconds tt): AI(w, tt) {}
Move _search(const Board&) override;
int _eval(const Board&) override;
};
class v2_alpha_beta : public AI {
// looks two moves ahead, with alpha-beta pruning (no move ordering)
virtual int _search(const Board&, int, int, int);
public:
v2_alpha_beta(bool w, std::chrono::milliseconds tt): AI(w, tt) {}
virtual Move _search(const Board&) override;
virtual int _eval(const Board&) override;
};
class v3_AB_ordering : public AI {
// looks two moves ahead, with alpha-beta pruning, with move ordering
virtual int _ab_search(const Board&, int, int, int);
public:
v3_AB_ordering(bool w, std::chrono::milliseconds tt): AI(w, tt) {}
virtual Move _search(const Board&) override;
virtual int _eval(const Board&) override;
};
class v4_search_captures : public v3_AB_ordering {
protected:
// same as v3, but looking at only at captures when leaf is reached,
// until no captures are left
virtual int _ab_search(const Board&, int, int, int) override;
virtual int _search_captures(const Board&, int, int);
public:
v4_search_captures(bool w, std::chrono::milliseconds tt)
: v3_AB_ordering(w, tt) {}
};
class v5_better_endgame : public v4_search_captures {
// same as v4, but with a better evaluation function, that forces the
// king towards the corner of the board for endgames
public:
v5_better_endgame(bool w, std::chrono::milliseconds tt)
: v4_search_captures(w, tt) {}
virtual int _eval(const Board&) override;
};
class v6_iterative_deepening : public v5_better_endgame {
// same as v5, but instead of just looking 2 moves ahead, it does
// iterative depening until and keeps on searching until the thinking
// time runs out
public:
v6_iterative_deepening(bool w, std::chrono::milliseconds tt)
: v5_better_endgame(w, tt) {}
virtual Move _search(const Board&) override;
};
} // namespace ai

View File

@ -0,0 +1,13 @@
#include "ai.hpp"
#include <thread>
Move ai::v0_random::_search(const Board& b) {
std::vector<Move> moves = b.all_legal_moves();
std::this_thread::sleep_for(std::chrono::milliseconds(thinking_time)
); // Simulate work
return moves[rand() % moves.size()];
}

View File

@ -0,0 +1,86 @@
#include "../pieces/piece.hpp"
#include "../utils/threadpool.hpp"
#include "../utils/utils.hpp"
#include "ai.hpp"
#include <map>
#define MULTITHREADED 1
Move ai::v1_pure_minimax::_search(const Board& b) {
std::vector<Move> moves = b.all_legal_moves();
Move best_move;
int best_eval = -INFINITY;
#if MULTITHREADED
ThreadPool pool(std::thread::hardware_concurrency());
std::cout << "Have to look at " << moves.size() << " moves" << std::endl;
std::map<Move, std::future<int>> futures;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
futures.insert({move, pool.enqueue([&, tmp_board]() {
return _search(tmp_board, 3);
})});
}
int counter = 0;
for (auto& [move, future] : futures) {
int eval = future.get();
counter++;
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#else
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
std::cout << "Looking at " << move << std::endl;
int eval = _search(tmp_board, 3);
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#endif
return best_move;
}
int ai::v1_pure_minimax::_search(const Board& b, int depth) {
if (depth == 0 || stop_computation)
return eval(b);
if (b.no_legal_moves()) {
if (b.is_check())
return -INFINITY;
return 0;
}
std::vector<Move> moves = b.all_legal_moves();
int best_evaluation = -INFINITY;
Move best_move;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_search(tmp_board, depth - 1);
best_evaluation = std::max(best_evaluation, tmp_eval);
}
return best_evaluation;
}
int ai::v1_pure_minimax::_eval(const Board& b) {
int white_eval = count_material(b, Colour::White);
int black_eval = count_material(b, Colour::Black);
int evaluation = white_eval - black_eval;
int perspective = b.white_to_play ? 1 : -1;
return perspective * evaluation;
}

View File

@ -0,0 +1,86 @@
#include "../pieces/piece.hpp"
#include "../utils/threadpool.hpp"
#include "../utils/utils.hpp"
#include "ai.hpp"
#include <map>
#define MULTITHREADED 1
Move ai::v2_alpha_beta::_search(const Board& b) {
std::vector<Move> moves = b.all_legal_moves();
Move best_move;
int best_eval = -INFINITY;
#if MULTITHREADED
ThreadPool pool(std::thread::hardware_concurrency());
std::cout << "Have to look at " << moves.size() << " moves" << std::endl;
std::map<Move, std::future<int>> futures;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
futures.insert({move, pool.enqueue([&, tmp_board]() {
return _search(tmp_board, 3, -INFINITY, INFINITY);
})});
}
int counter = 0;
for (auto& [move, future] : futures) {
int eval = future.get();
counter++;
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#else
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
std::cout << "Looking at " << move << std::endl;
int eval = _search(tmp_board, 3);
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#endif
return best_move;
}
int ai::v2_alpha_beta::_search(const Board& b, int depth, int alpha, int beta) {
if (depth == 0 || stop_computation)
return eval(b);
if (b.no_legal_moves()) {
if (b.is_check())
return -INFINITY;
return 0;
}
std::vector<Move> moves = b.all_legal_moves();
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_search(tmp_board, depth - 1, -beta, -alpha);
if (tmp_eval >= beta)
return beta;
alpha = std::max(alpha, tmp_eval);
}
return alpha;
}
int ai::v2_alpha_beta::_eval(const Board& b) {
int white_eval = count_material(b, Colour::White);
int black_eval = count_material(b, Colour::Black);
int evaluation = white_eval - black_eval;
int perspective = b.white_to_play ? 1 : -1;
return perspective * evaluation;
}

View File

@ -0,0 +1,104 @@
#include "../pieces/piece.hpp"
#include "../utils/threadpool.hpp"
#include "../utils/utils.hpp"
#include "ai.hpp"
#include <algorithm>
#include <map>
#define MULTITHREADED 1
Move ai::v3_AB_ordering::_search(const Board& b) {
std::vector<Move> moves = b.all_legal_moves();
std::sort(moves.begin(), moves.end(), [&](Move& m1, Move& m2) {
int score = m1.score_guess(b) - m2.score_guess(b);
if (!am_white)
score *= -1;
return score < 0;
});
Move best_move;
int best_eval = -INFINITY;
#if MULTITHREADED
ThreadPool pool(std::thread::hardware_concurrency());
std::cout << "Have to look at " << moves.size() << " moves" << std::endl;
std::map<Move, std::future<int>> futures;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
futures.insert(
{move, pool.enqueue([&, tmp_board]() {
return _ab_search(tmp_board, 3, -INFINITY, INFINITY);
})}
);
}
int counter = 0;
for (auto& [move, future] : futures) {
int eval = future.get();
counter++;
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#else
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
std::cout << "Looking at " << move << std::endl;
int eval = _search(tmp_board, 3);
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#endif
return best_move;
}
int ai::v3_AB_ordering::_ab_search(
const Board& b, int depth, int alpha, int beta
) {
if (depth == 0 || stop_computation)
return eval(b);
if (b.no_legal_moves()) {
if (b.is_check())
return -INFINITY;
return 0;
}
std::vector<Move> moves = b.all_legal_moves();
std::sort(moves.begin(), moves.end(), [&](Move& m1, Move& m2) {
int score = m1.score_guess(b) - m2.score_guess(b);
if (!am_white)
score *= -1;
return score < 0;
});
Move best_move;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_ab_search(tmp_board, depth - 1, -beta, -alpha);
if (tmp_eval >= beta)
return beta;
alpha = std::max(alpha, tmp_eval);
}
return alpha;
}
int ai::v3_AB_ordering::_eval(const Board& b) {
int white_eval = count_material(b, Colour::White);
int black_eval = count_material(b, Colour::Black);
int evaluation = white_eval - black_eval;
int perspective = b.white_to_play ? 1 : -1;
return perspective * evaluation;
}

View File

@ -0,0 +1,58 @@
#include "../pieces/piece.hpp"
#include "../utils/utils.hpp"
#include "ai.hpp"
#include <algorithm>
#define MULTITHREADED 1
int ai::v4_search_captures::_ab_search(
const Board& b, int depth, int alpha, int beta
) {
if (depth == 0 || stop_computation)
return _search_captures(b, alpha, beta);
if (b.no_legal_moves()) {
if (b.is_check())
return -INFINITY;
return 0;
}
std::vector<Move> moves = b.all_legal_moves();
std::sort(moves.begin(), moves.end(), [&](Move& m1, Move& m2) {
return m1.score_guess(b) > m2.score_guess(b);
});
Move best_move;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_ab_search(tmp_board, depth - 1, -beta, -alpha);
if (tmp_eval >= beta)
return beta;
alpha = std::max(alpha, tmp_eval);
}
return alpha;
}
int ai::v4_search_captures::_search_captures(
const Board& b, int alpha, int beta
) {
int evaluation = eval(b);
if (evaluation >= beta)
return beta;
alpha = std::max(evaluation, alpha);
std::vector<Move> moves = b.all_capturing_moves();
std::sort(moves.begin(), moves.end(), [&](Move& m1, Move& m2) {
return m1.score_guess(b) > m2.score_guess(b);
});
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_search_captures(tmp_board, -beta, -alpha);
if (tmp_eval >= beta)
return beta;
alpha = std::max(alpha, tmp_eval);
}
return alpha;
}

View File

@ -0,0 +1,52 @@
#include "../pieces/piece.hpp"
#include "../utils/utils.hpp"
#include "ai.hpp"
#include <algorithm>
static int force_king_to_corner(
int8_t attacking_king, int8_t defending_king, float endgame_weight
) {
int eval = 0;
Coords def_xy = Coords::from_index(defending_king);
Coords def_dist_to_center{
std::max(3 - def_xy.x, def_xy.x - 4),
std::max(3 - def_xy.y, def_xy.y - 4)
};
eval += def_dist_to_center.x + def_dist_to_center.y;
// make attacking king go closer to defending king to cut off escape routes
Coords attack_xy = Coords::from_index(attacking_king);
Coords dist_between_kings{
std::abs(attack_xy.x - def_xy.x),
std::abs(attack_xy.y - def_xy.y)
};
int distance = dist_between_kings.x + dist_between_kings.y;
eval += 14 - distance;
return (int) (eval * 10 * endgame_weight);
}
static float endgame_phase_weight(int material_count_no_pawns) {
static int endgame_material_start =
RookValue * 2 + BishopValue + KnightValue;
float multiplier = 1.f / endgame_material_start;
return 1.f - std::min(1.f, material_count_no_pawns * multiplier);
}
int ai::v5_better_endgame::_eval(const Board& b) {
int old_eval = v4_search_captures::_eval(b);
Colour attacking_colour = b.white_to_play ? White : Black;
Colour defending_colour = b.white_to_play ? Black : White;
return old_eval
+ force_king_to_corner(
b.get_king_of(attacking_colour),
b.get_king_of(defending_colour),
endgame_phase_weight(
count_material(b, attacking_colour, false)
+ count_material(b, defending_colour, false)
)
);
}

View File

@ -0,0 +1,43 @@
#include "../pieces/piece.hpp"
#include "../utils/threadpool.hpp"
#include "../utils/utils.hpp"
#include "ai.hpp"
#include <map>
Move ai::v6_iterative_deepening::_search(const Board& b) {
ThreadPool pool(std::thread::hardware_concurrency());
std::vector<Move> moves = b.all_legal_moves();
Move best_move;
int best_eval = -INFINITY;
std::map<Move, std::future<int>> futures;
int depth;
for (depth = 1; !stop_computation; depth++) {
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
futures.insert(
{move, pool.enqueue([&, tmp_board]() {
return _ab_search(tmp_board, depth, -INFINITY, INFINITY);
})}
);
}
int counter = 0;
for (auto& [move, future] : futures) {
int eval = future.get();
counter++;
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
futures.clear();
}
std::cout << "Went up until depth: " << depth << std::endl;
return best_move;
}

437
src/model/board/board.cpp Normal file
View File

@ -0,0 +1,437 @@
#include "board.hpp"
#include "../pieces/piece.hpp"
#include "../utils/coords.hpp"
#include "../utils/move.hpp"
#include "../utils/utils.hpp"
#include <SFML/Graphics/BlendMode.hpp>
#include <algorithm>
#include <cctype>
#include <map>
#include <sstream>
#include <stdexcept>
Board Board::setup_fen_position(std::string fen) {
Board board;
std::map<char, Piece> c2p{
{'k', Piece::King},
{'p', Piece::Pawn},
{'n', Piece::Knigt},
{'b', Piece::Bishop},
{'r', Piece::Rook},
{'q', Piece::Queen},
};
// -- Pieces
std::string fen_board = fen.substr(0, fen.find(' '));
int rank = 7, file = 0;
for (char symbol : fen_board) {
if (symbol == '/') {
file = 0;
rank--;
continue;
}
if (std::isdigit(symbol))
file += symbol - '0';
else {
Colour colour =
std::isupper(symbol) ? Colour::White : Colour::Black;
Piece piece = c2p[std::tolower(symbol)];
board.squares[rank * 8 + file] = colour | piece;
file++;
}
}
// -- Active colour
int index = fen.find(' ');
index++;
board.white_to_play = fen[index] == 'w';
// Castling Rights
index += 2;
for (char symbol : fen.substr(index, fen.find(' ', index + 1))) {
index++;
if (symbol == ' ' || symbol == '-') {
if (symbol == '-')
index++;
break;
}
switch (symbol) {
case 'K':
board.w_castle_rights |= CastleSide::KingSide;
break;
case 'Q':
board.w_castle_rights |= CastleSide::QueenSide;
break;
case 'k':
board.b_castle_rights |= CastleSide::KingSide;
break;
case 'q':
board.b_castle_rights |= CastleSide::QueenSide;
break;
}
}
// -- En passant target
if (fen[index] != '-') {
Coords c = Coords::from_algebraic(fen.substr(index, 2));
index += 2;
board.en_passant_target = c.to_index();
} else {
index++;
}
// -- Half move clock
index = fen.find(' ', index) + 1;
board.n_half_moves =
std::stoi(fen.substr(index, fen.find(' ', index + 1) - index));
// -- Full move number
index = fen.find(' ', index) + 1;
board.n_full_moves = std::stoi(fen.substr(index));
board.check = board._is_check_for(board.white_to_play ? White : Black);
board.nlm = board._no_legal_moves_for(board.white_to_play ? White : Black);
return board;
}
std::string Board::to_fen() const {
std::map<int, char> p2c{
{Piece::King, 'k'},
{Piece::Pawn, 'p'},
{Piece::Knigt, 'n'},
{Piece::Bishop, 'b'},
{Piece::Rook, 'r'},
{Piece::Queen, 'q'},
};
std::string ret;
// -- Pieces
for (int rank = 7; rank >= 0; rank--) {
int empty_cell_counter = 0;
for (int file = 0; file < 8; file++) {
if (piece_at({file, rank}) == Piece::None) {
empty_cell_counter++;
continue;
}
int full_piece = squares[rank * 8 + file];
char piece = p2c[full_piece & 0b111];
Colour colour = colour_at({file, rank});
if (empty_cell_counter > 0) {
ret += std::to_string(empty_cell_counter);
empty_cell_counter = 0;
}
ret += colour == Colour::White ? std::toupper(piece) : piece;
}
if (empty_cell_counter > 0)
ret += std::to_string(empty_cell_counter);
if (rank > 0)
ret += "/";
}
ret += " ";
// -- Active colour
ret += white_to_play ? 'w' : 'b';
ret += " ";
// -- Castling Rights
if (w_castle_rights == CastleSide::NeitherSide
&& b_castle_rights == CastleSide::NeitherSide)
ret += '-';
else {
if (w_castle_rights & CastleSide::KingSide)
ret += 'K';
if (w_castle_rights & CastleSide::QueenSide)
ret += 'Q';
if (b_castle_rights & CastleSide::KingSide)
ret += 'k';
if (b_castle_rights & CastleSide::QueenSide)
ret += 'q';
}
ret += ' ';
// -- En passant target
ret += en_passant_target == -1
? "-"
: Coords::from_index(en_passant_target).to_algebraic();
ret += ' ';
// -- Half move clock
ret += std::to_string(n_half_moves);
ret += ' ';
// -- Full moves number
ret += std::to_string(n_full_moves);
return ret;
}
Board Board::skip_turn() const {
Board ret = *this;
ret.white_to_play = !ret.white_to_play;
ret.check = ret._is_check_for(ret.white_to_play ? White : Black);
return ret;
}
Board Board::make_move(Move move, bool recurse_call) const {
Board ret;
std::copy(
std::begin(this->squares),
std::end(this->squares),
std::begin(ret.squares)
);
ret.white_to_play = !this->white_to_play;
// -- Actually make the move
ret.squares[move.source_square] = Piece::None;
ret.squares[move.target_square] = this->squares[move.source_square];
Piece source_piece = piece_at(move.source_square);
Piece target_piece = piece_at(move.target_square);
// -- Handle en passant target being eaten
if (en_passant_target != -1 && source_piece == Piece::Pawn
&& target_piece == Piece::None)
ret.squares[move.target_square + (white_to_play ? -8 : 8)] =
Piece::None;
// -- Handle promotion
if (move.promoting_to != Piece::None)
ret.squares[move.target_square] = move.promoting_to;
// -- Set en passant target if need
if (source_piece == Piece::Pawn
&& std::abs(move.target_square - move.source_square) == 16) {
if (white_to_play)
ret.en_passant_target = move.target_square - 8;
else
ret.en_passant_target = move.target_square + 8;
} else {
ret.en_passant_target = -1;
}
// -- Handle castling (just move the rook over)
Coords c = Coords::from_index(move.source_square);
if (source_piece == Piece::King) {
if (move.target_square - move.source_square == 2) { // king side castle
Coords rook_source{7, c.y};
int8_t old_rook = ret.squares[rook_source.to_index()];
ret.squares[rook_source.to_index()] = Piece::None;
Coords rook_dest{5, c.y};
ret.squares[rook_dest.to_index()] = old_rook;
} else if (move.target_square - move.source_square == -2) { // queen
Coords rook_source{0, c.y};
int8_t old_rook = ret.squares[rook_source.to_index()];
ret.squares[rook_source.to_index()] = Piece::None;
Coords rook_dest{3, c.y};
ret.squares[rook_dest.to_index()] = old_rook;
}
}
// -- Check for castling rights
ret.w_castle_rights = w_castle_rights;
ret.b_castle_rights = b_castle_rights;
bool is_capturing = squares[move.target_square] != Piece::None;
if (white_to_play) {
if (source_piece == King)
ret.w_castle_rights = NeitherSide;
if (source_piece == Rook) {
if (c.x == 0 && (ret.w_castle_rights & QueenSide))
ret.w_castle_rights &= ~(QueenSide);
if (c.x == 7 && (ret.w_castle_rights & KingSide))
ret.w_castle_rights &= ~(KingSide);
}
Coords target = Coords::from_index(move.target_square);
if (is_capturing && target.y == 7 && target_piece == Rook) {
if (target.x == 0 && (ret.b_castle_rights & QueenSide))
ret.b_castle_rights &= ~(QueenSide);
if (target.x == 7 && (ret.b_castle_rights & KingSide))
ret.b_castle_rights &= ~(KingSide);
}
} else {
if (source_piece == King)
ret.b_castle_rights = NeitherSide;
if (source_piece == Rook) {
if (c.x == 0 && (ret.b_castle_rights & QueenSide))
ret.b_castle_rights &= ~(QueenSide);
if (c.x == 7 && (ret.b_castle_rights & KingSide))
ret.b_castle_rights &= ~(KingSide);
}
Coords target = Coords::from_index(move.target_square);
if (is_capturing && target.y == 0 && target_piece == Rook) {
if (target.x == 0 && (ret.w_castle_rights & QueenSide))
ret.w_castle_rights &= ~(QueenSide);
if (target.x == 7 && (ret.w_castle_rights & KingSide))
ret.w_castle_rights &= ~(KingSide);
}
}
ret.n_half_moves = n_half_moves + 1;
if (is_capturing || piece_at(move.source_square) == Piece::Pawn)
ret.n_half_moves = 0;
if (!white_to_play)
ret.n_full_moves = n_full_moves + 1;
if (ret.n_half_moves > 150) {
std::cerr << "too many recursions" << std::endl;
exit(1);
}
if (recurse_call) {
ret.check = ret._is_check_for(ret.white_to_play ? White : Black);
ret.nlm = ret._no_legal_moves_for(ret.white_to_play ? White : Black);
}
return ret;
}
bool Board::insufficient_material_for(Colour current_colour) const {
int n_bishop = 0, n_knight = 0;
for (int i = 0; i < 64; i++) {
Colour colour = colour_at(i);
if (colour != current_colour)
continue;
Piece piece = piece_at(i);
if (piece == Piece::Pawn || piece == Piece::Queen
|| piece == Piece::Rook)
return false;
if (piece == Piece::Bishop)
n_bishop++;
if (piece == Piece::Knigt && colour == Colour::White)
n_knight++;
}
return (n_bishop == 0 && n_knight == 0) || (n_bishop == 1 && n_knight == 0)
|| (n_bishop == 0 && n_knight == 1);
}
int8_t Board::get_king_of(int8_t colour) const {
for (int i = 0; i < 64; i++)
if (squares[i] == (colour | Piece::King))
return i;
std::stringstream ss;
ss << "Apparently there no kings of the such color in this board: "
<< std::endl;
ss << to_fen();
throw std::domain_error(ss.str());
}
bool Board::_is_check_for(Colour colour) const {
int8_t king_idx = this->get_king_of(colour);
std::vector<Move> all_moves;
all_moves.reserve(50);
for (int8_t i = 0; i < 64; i++) {
if (this->squares[i] == Piece::None || colour_at(i) == colour)
continue;
if (piece_at(i) == King) {
// special case for the king, because it creates infinite recursion
// (since he looks if he's walking into a check)
Coords king_pos = Coords::from_index(i);
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
Coords c{king_pos.x + dx, king_pos.y + dy};
if (c.is_within_bounds())
all_moves.push_back(Move{i, c.to_index()});
}
}
} else {
std::vector<Move> moves = legal_moves(
this->squares[i],
*this,
Coords::from_index(i),
true
);
all_moves.insert(all_moves.end(), moves.begin(), moves.end());
}
for (const Move& move : all_moves)
if (move.target_square == king_idx)
return true;
all_moves.clear();
}
return false;
}
bool Board::_no_legal_moves_for(Colour colour) const {
for (int i = 0; i < 64; i++) {
if (squares[i] == Piece::None || colour_at(i) != colour)
continue;
std::vector<Move> moves;
moves = legal_moves(squares[i], *this, Coords::from_index(i));
if (moves.size() > 0)
return false;
}
return true;
}
bool Board::no_legal_moves() const {
return nlm;
}
bool Board::is_check() const {
return check;
}
bool Board::is_checkmate() const {
return check && nlm;
}
bool Board::is_stalemate() const {
return !check && nlm;
}
bool Board::is_terminal() const {
return n_half_moves == 100 || insufficient_material() || is_checkmate()
|| is_stalemate();
}
std::vector<Move> Board::all_legal_moves() const {
std::vector<Move> ret;
for (int i = 0; i < 64; i++) {
if ((colour_at(i) == White && white_to_play)
|| (colour_at(i) == Black && !white_to_play)) {
std::vector<Move> moves =
legal_moves(squares[i], *this, Coords::from_index(i));
ret.insert(ret.end(), moves.begin(), moves.end());
}
}
return ret;
}
std::vector<Move> Board::all_capturing_moves() const {
std::vector<Move> moves = all_legal_moves();
std::vector<Move> ret;
ret.reserve(moves.size());
for (const Move& move : moves)
if (piece_at(move.target_square) != Piece::None)
ret.push_back(move);
return ret;
}
std::vector<int8_t> Board::opponent_pawn_attack_map() const {
std::vector<int8_t> ret;
for (int i = 0; i < 64; i++) {
if (piece_at(i) == Piece::Pawn
&& ((colour_at(i) == White && !white_to_play)
|| (colour_at(i) == Black && white_to_play))) {
std::vector<int8_t> attack_map =
pawn_attack_map(*this, Coords::from_index(i));
ret.insert(ret.end(), attack_map.begin(), attack_map.end());
}
}
return ret;
}

70
src/model/board/board.hpp Normal file
View File

@ -0,0 +1,70 @@
#pragma once
#include "../pieces/piece.hpp"
#include "../utils/coords.hpp"
#include "../utils/move.hpp"
#include <string>
struct Board {
private:
bool _no_legal_moves_for(Colour) const;
bool _is_check_for(Colour) const;
bool nlm = false, check = false;
public:
int8_t squares[64] = {Piece::None};
bool white_to_play = true;
int8_t w_castle_rights = CastleSide::NeitherSide;
int8_t b_castle_rights = CastleSide::NeitherSide;
int8_t en_passant_target = -1;
int n_half_moves = 0;
int n_full_moves = 0;
static Board setup_fen_position(std::string fen);
int8_t get_king_of(int8_t) const;
Board skip_turn() const;
Board make_move(Move, bool = true) const;
std::string to_fen() const;
bool no_legal_moves() const;
bool is_check() const;
bool insufficient_material_for(Colour) const;
bool insufficient_material() const {
return insufficient_material_for(White)
&& insufficient_material_for(Black);
};
std::vector<Move> all_legal_moves() const;
std::vector<Move> all_capturing_moves() const;
std::vector<int8_t> opponent_pawn_attack_map() const;
bool is_checkmate() const;
bool is_stalemate() const;
bool is_terminal() const;
inline Piece piece_at(int8_t idx) const {
return (Piece) (squares[idx] & 0b00111);
}
inline Piece piece_at(Coords xy) const {
return piece_at(xy.to_index());
}
inline Colour colour_at(int8_t idx) const {
return (Colour) (squares[idx] & 0b11000);
}
inline Colour colour_at(Coords xy) const {
return colour_at(xy.to_index());
}
};
inline bool operator<(const Board& m1, const Board& m2) {
return m1.to_fen() < m2.to_fen(
); // TODO: make this the comparison between the hash of the board
}

View File

@ -0,0 +1,7 @@
#include <cstdint>
enum CastleSide : int8_t {
NeitherSide = 0,
KingSide = 1,
QueenSide = 2,
};

188
src/model/perft/perft.cpp Normal file
View File

@ -0,0 +1,188 @@
#include "perft.hpp"
#include "../board/board.hpp"
#include "../utils/move.hpp"
#include "../utils/threadpool.hpp"
#include <chrono>
#include <map>
#include <ostream>
#include <sstream>
#include <string>
#include <vector>
static std::string tick = "\033[92m✔\033[0m"; // Green Tick;
static std::string cross = "\033[91m❌\033[0m"; // Red Cross
static std::map<std::string, std::map<int, int>> pos2expected{
// -- Position 1
{
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
{
{1, 20}, // 0
{2, 400}, // 1
{3, 8902}, // 38
{4, 197281}, // 971
// {5, 4865609}, // 23032
},
},
// -- Position 2
{
"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 2",
{
{1, 48}, // 0
{2, 2039}, // 16
{3, 97862}, // 602
// {4, 4085603}, // 26612
},
},
// -- Position 3
{
"8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 3",
{
{1, 14}, // 0
{2, 191}, // 1
{3, 2812}, // 11
{4, 43238}, // 157
{5, 674624}, // 2199
{6, 11030083},
},
},
// -- Position 4a
{
"r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 4",
{
{1, 6}, // 0
{2, 264}, // 1
{3, 9467}, // 69
{4, 422333}, // 3085
{5, 15833292}, // 124452
},
},
// -- Position 4b
{
"r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 5",
{
{1, 6}, // 0
{2, 264}, // 2
{3, 9467}, // 104
{4, 422333}, // 3742
{5, 15833292}, // 136784
},
},
// -- Position 5
{
"rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 6",
{
{1, 44}, // 0
{2, 1486}, // 12
{3, 62379}, // 357
{4, 2103487}, // 13804
// {5, 89941194}, // 1230428
},
},
// -- Position 6
{
"r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 "
"7",
{
{1, 46}, // 0
{2, 2079}, // 16
{3, 89890}, // 602
{4, 3894594}, // 26612
// {5, 164075551}, // 1230428
},
},
};
static std::stringstream res;
int move_generation_test(
const Board& b, int depth, int max_depth, ThreadPool& pool
) {
if (depth == max_depth) {
res.str("");
res.clear();
}
if (b.is_terminal())
return 0;
if (depth == 0)
return 1;
std::vector<Move> moves = b.all_legal_moves();
if (depth == 1)
return moves.size();
int num_pos = 0;
if (depth == max_depth) {
// Parallel execution at the top level
std::vector<std::future<int>> futures;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
futures.push_back(pool.enqueue(
move_generation_test,
tmp_board,
depth - 1,
max_depth,
std::ref(pool)
));
}
for (auto& future : futures)
num_pos += future.get(); // Retrieve the result of each task
} else {
// Regular sequential execution
for (const Move& move : moves) {
// std::cout << "Looking at " << move << std::endl;
Board tmp_board = b.make_move(move);
int n = move_generation_test(tmp_board, depth - 1, max_depth, pool);
if (depth == max_depth)
res << move << ": " << n << std::endl;
num_pos += n;
}
}
return num_pos;
}
void perft() {
for (auto& [key, value] : pos2expected)
perft(key);
}
void perft(std::string pos) {
std::cout << pos << std::endl;
std::map<int, int> expected = pos2expected[pos];
Board b = Board::setup_fen_position(pos);
ThreadPool pool(std::thread::hardware_concurrency());
for (const auto& [depth, expected_n_moves] : expected) {
std::cout << "Depth: " << depth << " " << std::flush;
auto start = std::chrono::steady_clock::now();
int moves = move_generation_test(b, depth, depth, pool);
auto end = std::chrono::steady_clock::now();
auto elapsed =
std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
.count();
std::cout << "Results: " << moves << " ";
if (moves == expected_n_moves)
std::cout << tick << " ";
else
std::cout << cross << " (expected " << expected_n_moves << ") ";
std::cout << "positions Time: " << elapsed << " milliseconds"
<< std::endl;
if (moves != expected_n_moves) {
std::cout << std::endl << res.str();
exit(1);
}
}
}

View File

@ -0,0 +1,6 @@
#pragma once
#include <string>
void perft(std::string pos);
void perft();

View File

@ -0,0 +1,23 @@
#include "../board/board.hpp"
#include "../utils/coords.hpp"
#include "../utils/move.hpp"
#include "piece.hpp"
#include <vector>
std::vector<Move> bishop_moves(const Board& b, const Coords xy) {
std::vector<Move> ret;
auto ne = look_direction(b, xy, 1, 1);
ret.insert(ret.end(), ne.begin(), ne.end());
auto se = look_direction(b, xy, 1, -1);
ret.insert(ret.end(), se.begin(), se.end());
auto sw = look_direction(b, xy, -1, -1);
ret.insert(ret.end(), sw.begin(), sw.end());
auto nw = look_direction(b, xy, -1, 1);
ret.insert(ret.end(), nw.begin(), nw.end());
return ret;
}

78
src/model/pieces/king.cpp Normal file
View File

@ -0,0 +1,78 @@
#include "../board/board.hpp"
#include "../utils/coords.hpp"
#include "../utils/move.hpp"
#include "piece.hpp"
static bool is_clear_king_side(const Board& b, const Coords xy) {
for (int dx = 1; dx < 3; dx++) {
Coords c{xy.x + dx, xy.y};
if (b.squares[c.to_index()] != Piece::None)
return false;
std::optional<Move> move = move_for_position(b, xy, c);
Board board_after_move = b.make_move(move.value(), false);
board_after_move = board_after_move.skip_turn();
if (board_after_move.is_check())
return false;
}
return true;
}
static bool is_clear_queen_side(const Board& b, const Coords xy) {
for (int dx = 1; dx < 4; dx++) {
Coords c{xy.x - dx, xy.y};
if (b.squares[c.to_index()] != Piece::None)
return false;
std::optional<Move> move = move_for_position(b, xy, c);
Board board_after_move = b.make_move(move.value(), false);
board_after_move = board_after_move.skip_turn();
if (dx < 3 && board_after_move.is_check())
return false;
}
return true;
}
std::vector<Move> king_moves(const Board& b, const Coords xy) {
std::vector<Move> ret;
// -- Regular moves
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
if (dx == 0 && dy == 0) // skip staying in the same position
continue;
Coords c{xy.x + dx, xy.y + dy};
std::optional<Move> move = move_for_position(b, xy, c);
if (move.has_value())
ret.push_back(move.value());
}
}
if (b.is_check())
return keep_only_blocking(ret, b);
// -- Castles
int8_t castling_rights = b.colour_at(xy) == Colour::White
? b.w_castle_rights
: b.b_castle_rights;
if (castling_rights == CastleSide::NeitherSide)
return ret;
if (castling_rights & CastleSide::KingSide && is_clear_king_side(b, xy)) {
ret.push_back(Move{
xy.to_index(),
Coords{6, xy.y}.to_index(),
});
}
if (castling_rights & CastleSide::QueenSide && is_clear_queen_side(b, xy)) {
ret.push_back(Move{
xy.to_index(),
Coords{2, xy.y}.to_index(),
});
}
return ret;
}

View File

@ -0,0 +1,28 @@
#include "../board/board.hpp"
#include "../utils/coords.hpp"
#include "../utils/move.hpp"
#include "piece.hpp"
#include <vector>
std::vector<Move> knight_moves(const Board& b, const Coords xy) {
std::vector<Move> ret;
std::vector<std::pair<int, int>> moves = {
{+2, +1},
{+1, +2}, // north east
{+2, -1},
{+1, -2}, // south east
{-2, -1},
{-1, -2}, // south west
{-2, +1},
{-1, +2} // north west
};
for (const auto& [dx, dy] : moves) {
std::optional<Move> move =
move_for_position(b, xy, Coords{xy.x + dx, xy.y + dy});
if (move.has_value())
ret.push_back(move.value());
}
return ret;
}

102
src/model/pieces/pawn.cpp Normal file
View File

@ -0,0 +1,102 @@
#include "../board/board.hpp"
#include "../utils/coords.hpp"
#include "../utils/move.hpp"
#include "piece.hpp"
std::vector<Move> pawn_moves(const Board& b, const Coords xy) {
std::vector<Move> ret{};
Colour my_colour = b.colour_at(xy);
// -- Capture to the left
if (xy.x > 0) {
int dy = my_colour == Colour::White ? 1 : -1;
Coords left{xy.x - 1, xy.y + dy};
int8_t capturable_piece = b.squares[left.to_index()];
if (capturable_piece != 0 && my_colour != b.colour_at(left)) {
if ((my_colour == White && left.y == 7)
|| (my_colour == Black && left.y == 0))
for (auto piece : {Rook, Knigt, Bishop, Queen})
ret.push_back(Move{
xy.to_index(),
left.to_index(),
(Piece) (my_colour | piece)
});
else
ret.push_back(Move{xy.to_index(), left.to_index()});
}
}
// -- Capture to the right
if (xy.x < 7) {
int dy = my_colour == Colour::White ? 1 : -1;
Coords right{xy.x + 1, xy.y + dy};
int8_t capturable_piece = b.squares[right.to_index()];
if (capturable_piece != 0 && my_colour != b.colour_at(right)) {
if ((my_colour == White && right.y == 7)
|| (my_colour == Black && right.y == 0))
for (auto piece : {Rook, Knigt, Bishop, Queen})
ret.push_back(Move{
xy.to_index(),
right.to_index(),
(Piece) (my_colour | piece)
});
else
ret.push_back(Move{xy.to_index(), right.to_index()});
}
}
// -- Capture en passant
if (b.en_passant_target != -1) {
Coords c = Coords::from_index(b.en_passant_target);
int dy = my_colour == Colour::White ? 1 : -1;
if (c.y == xy.y + dy && (c.x == xy.x - 1 || c.x == xy.x + 1))
ret.push_back(Move{xy.to_index(), c.to_index()});
}
// -- Normal move + promotion
bool is_on_starting_rank =
my_colour == Colour::White ? xy.y == 1 : xy.y == 6;
int max_dy = is_on_starting_rank ? 3 : 2;
for (int dy = 1; dy < max_dy; dy++) {
int actual_dy = my_colour == Colour::White ? dy : -dy;
Coords new_xy{xy.x, xy.y + actual_dy};
if (b.squares[new_xy.to_index()] != Piece::None)
break;
if (new_xy.y == 7 || new_xy.y == 0)
for (auto piece : {Rook, Knigt, Bishop, Queen})
ret.push_back(Move{
xy.to_index(),
new_xy.to_index(),
(Piece) (my_colour | piece)
});
else
ret.push_back(Move{
xy.to_index(),
new_xy.to_index(),
});
}
return ret;
}
std::vector<int8_t> pawn_attack_map(const Board& b, Coords xy) {
std::vector<int8_t> ret{};
Colour my_colour = b.colour_at(xy);
// -- Capture to the left
if (xy.x > 0) {
int dy = my_colour == Colour::White ? 1 : -1;
Coords left{xy.x - 1, xy.y + dy};
ret.push_back(left.to_index());
}
// -- Capture to the right
if (xy.x < 7) {
int dy = my_colour == Colour::White ? 1 : -1;
Coords right{xy.x + 1, xy.y + dy};
ret.push_back(right.to_index());
}
return ret;
}

View File

@ -0,0 +1,83 @@
#include "piece.hpp"
#include "../board/board.hpp"
#include "../utils/coords.hpp"
#include "../utils/move.hpp"
std::vector<Move>
keep_only_blocking(const std::vector<Move> candidates, const Board& board) {
if (candidates.size() == 0)
return {};
std::vector<Move> ret;
for (Move move : candidates) {
Board board_after_move = board.make_move(move, false);
board_after_move = board_after_move.skip_turn();
if (!board_after_move.is_check())
ret.push_back(move);
}
return ret;
}
std::vector<Move>
legal_moves(int8_t p, const Board& b, const Coords xy, bool looking_for_check) {
std::vector<Move> ret;
int8_t simple_piece = p & 0b00111;
switch (simple_piece) {
case Piece::Pawn:
ret = pawn_moves(b, xy);
break;
case Piece::Bishop:
ret = bishop_moves(b, xy);
break;
case Piece::Rook:
ret = rook_moves(b, xy);
break;
case Piece::Knigt:
ret = knight_moves(b, xy);
break;
case Piece::Queen:
ret = queen_moves(b, xy);
break;
case Piece::King:
ret = king_moves(b, xy);
break;
default:
break;
}
if (!looking_for_check)
return keep_only_blocking(ret, b);
return ret;
}
std::optional<Move>
move_for_position(const Board& board, const Coords source, const Coords dest) {
if (!dest.is_within_bounds()
|| board.colour_at(source) == board.colour_at(dest))
return {};
return Move{source.to_index(), dest.to_index()};
}
std::vector<Move>
look_direction(const Board& board, const Coords xy, int mult_dx, int mult_dy) {
std::vector<Move> ret;
for (int d = 1; d < 8; d++) {
int dx = mult_dx * d;
int dy = mult_dy * d;
Coords target{xy.x + dx, xy.y + dy};
std::optional<Move> move = move_for_position(board, xy, target);
if (move.has_value()) {
ret.push_back(move.value());
if (board.squares[target.to_index()] != Piece::None)
break;
} else {
break;
}
}
return ret;
}

View File

@ -0,0 +1,78 @@
#pragma once
#include <cstdint>
#include <optional>
#include <ostream>
#include <vector>
enum Piece : int8_t {
None = 0,
Rook = 1,
Knigt = 2,
Bishop = 3,
Queen = 4,
King = 5,
Pawn = 6,
};
enum Colour : int8_t {
White = 8,
Black = 16,
};
inline const char* to_string(Colour c) {
switch (c) {
case White:
return "White";
case Black:
return "Black";
default:
return "[Unknown Colour]";
}
}
inline const char* to_string(Piece c) {
switch (c) {
case Pawn:
return "Pawn";
case Rook:
return "Rook";
case Bishop:
return "Bishop";
case Knigt:
return "Knight";
case Queen:
return "Queen";
case King:
return "King";
default:
return "[Unknown Colour]";
}
}
inline std::ostream& operator<<(std::ostream& os, const Colour& i) {
os << std::to_string(i);
return os;
}
inline std::ostream& operator<<(std::ostream& os, const Piece& i) {
os << std::to_string(i);
return os;
}
class Board;
struct Coords;
struct Move;
std::vector<Move> legal_moves(int8_t, const Board&, const Coords, bool = false);
std::vector<Move> keep_only_blocking(const std::vector<Move>, const Board&);
std::optional<Move> move_for_position(const Board&, const Coords, const Coords);
std::vector<Move> look_direction(const Board&, const Coords, int, int);
std::vector<int8_t> pawn_attack_map(const Board&, const Coords);
std::vector<Move> pawn_moves(const Board&, const Coords);
std::vector<Move> rook_moves(const Board&, const Coords);
std::vector<Move> knight_moves(const Board&, const Coords);
std::vector<Move> bishop_moves(const Board&, const Coords);
std::vector<Move> queen_moves(const Board&, const Coords);
std::vector<Move> king_moves(const Board&, const Coords);

View File

@ -0,0 +1,35 @@
#include "../board/board.hpp"
#include "../utils/coords.hpp"
#include "../utils/move.hpp"
#include "piece.hpp"
#include <vector>
std::vector<Move> queen_moves(const Board& b, const Coords xy) {
std::vector<Move> ret;
auto e = look_direction(b, xy, 1, 0);
ret.insert(ret.end(), e.begin(), e.end());
auto s = look_direction(b, xy, 0, -1);
ret.insert(ret.end(), s.begin(), s.end());
auto w = look_direction(b, xy, -1, 0);
ret.insert(ret.end(), w.begin(), w.end());
auto n = look_direction(b, xy, 0, 1);
ret.insert(ret.end(), n.begin(), n.end());
auto ne = look_direction(b, xy, 1, 1);
ret.insert(ret.end(), ne.begin(), ne.end());
auto se = look_direction(b, xy, 1, -1);
ret.insert(ret.end(), se.begin(), se.end());
auto sw = look_direction(b, xy, -1, -1);
ret.insert(ret.end(), sw.begin(), sw.end());
auto nw = look_direction(b, xy, -1, 1);
ret.insert(ret.end(), nw.begin(), nw.end());
return ret;
}

23
src/model/pieces/rook.cpp Normal file
View File

@ -0,0 +1,23 @@
#include "../board/board.hpp"
#include "../utils/coords.hpp"
#include "../utils/move.hpp"
#include "piece.hpp"
#include <vector>
std::vector<Move> rook_moves(const Board& b, const Coords xy) {
std::vector<Move> ret;
auto e = look_direction(b, xy, 1, 0);
ret.insert(ret.end(), e.begin(), e.end());
auto s = look_direction(b, xy, 0, -1);
ret.insert(ret.end(), s.begin(), s.end());
auto w = look_direction(b, xy, -1, 0);
ret.insert(ret.end(), w.begin(), w.end());
auto n = look_direction(b, xy, 0, 1);
ret.insert(ret.end(), n.begin(), n.end());
return ret;
}

View File

@ -0,0 +1,71 @@
#pragma once
#include <iostream>
#include <stdexcept>
#include <string>
static std::string _FILES = "abcdefgh";
static std::string _RANKS = "12345678";
struct Coords {
int x, y;
Coords(int x, int y): x(x), y(y) {}
int8_t to_index() const {
return this->y * 8 + this->x;
}
static Coords from_index(int idx) {
if (idx < 0 || idx > 63)
throw std::invalid_argument("The index is outside the board...");
return {idx % 8, idx / 8};
}
static Coords from_algebraic(std::string pos) {
if (pos.size() != 2)
throw std::invalid_argument(
"An algebraic coordinate should only have two characters"
);
size_t x = _FILES.find(pos[0]);
if (x == std::string::npos)
throw std::invalid_argument("The first character of the given "
"algebraic coordinate is invalid");
size_t y = _RANKS.find(pos[1]);
if (y == std::string::npos)
throw std::invalid_argument("The second character of the given "
"algebraic coordinate is invalid");
return Coords{(int) x, (int) y};
}
std::string to_algebraic() const {
std::string ret;
if (x > 7 || y > 7)
throw std::invalid_argument(
"Can't give the algebraic vesion of an invalid coord"
);
ret += _FILES[x];
ret += _RANKS[y];
return ret;
}
bool is_within_bounds() const {
return 0 <= x && x < 8 && 0 <= y && y < 8;
}
};
inline bool operator==(const Coords& a, const Coords& b) {
return a.x == b.x && a.y == b.y;
}
inline bool operator!=(const Coords& a, const Coords& b) {
return !(a == b);
}
inline std::ostream& operator<<(std::ostream& os, const Coords& coords) {
os << coords.to_algebraic();
return os;
}

54
src/model/utils/move.cpp Normal file
View File

@ -0,0 +1,54 @@
#include "move.hpp"
#include "../board/board.hpp"
#include "utils.hpp"
#include <algorithm>
int Move::score_guess(const Board& b) const {
int ret = 0;
Piece me_piece = b.piece_at(source_square);
Piece captured_piece = b.piece_at(target_square);
if (captured_piece != Piece::None)
ret += 10 * piece_value(captured_piece) - piece_value(me_piece);
if (promoting_to != Piece::None)
ret += piece_value(promoting_to);
std::vector<int8_t> pawn_attack_map = b.opponent_pawn_attack_map();
if (std::find(pawn_attack_map.begin(), pawn_attack_map.end(), target_square)
!= pawn_attack_map.end())
ret -= me_piece;
return ret;
}
Move Move::from_string(std::string move) {
if (!(4 <= move.size() && move.size() <= 5))
throw std::invalid_argument("Move must be 4 or 5 characters long");
Move ret;
ret.source_square = Coords::from_algebraic(move.substr(0, 2)).to_index();
ret.target_square = Coords::from_algebraic(move.substr(2, 2)).to_index();
if (move.size() == 5)
switch (move[4]) {
case 'n':
ret.promoting_to = Knigt;
break;
case 'b':
ret.promoting_to = Bishop;
break;
case 'r':
ret.promoting_to = Rook;
break;
case 'q':
ret.promoting_to = Queen;
break;
default:
throw std::invalid_argument("Promotion piece must be one of 'nbrq'"
);
}
ret.target_square = Coords::from_algebraic(move.substr(2, 2)).to_index();
return ret;
}

53
src/model/utils/move.hpp Normal file
View File

@ -0,0 +1,53 @@
#pragma once
#include "../board/castle_side.hpp"
#include "../pieces/piece.hpp"
#include "coords.hpp"
#include <cstdint>
#include <sstream>
struct Move {
int8_t source_square;
int8_t target_square;
Piece promoting_to = Piece::None;
int score_guess(const Board&) const;
static Move from_string(std::string);
std::string to_string() const {
std::stringstream ss;
ss << Coords::from_index(source_square)
<< Coords::from_index(target_square);
if (promoting_to != Piece::None) {
switch (promoting_to & 0b00111) {
case Queen:
ss << 'q';
break;
case Bishop:
ss << 'b';
break;
case Knigt:
ss << 'n';
break;
case Rook:
ss << 'r';
break;
default:
break;
}
}
return ss.str();
}
};
inline bool operator<(const Move& m1, const Move& m2) {
return m1.to_string() < m2.to_string();
}
inline std::ostream& operator<<(std::ostream& os, const Move& m) {
os << m.to_string();
return os;
}

View File

@ -0,0 +1,73 @@
#pragma once
#include <condition_variable>
#include <functional>
#include <future>
#include <iostream>
#include <queue>
#include <thread>
#include <vector>
class ThreadPool {
public:
ThreadPool(size_t numThreads) {
for (size_t i = 0; i < numThreads; ++i) {
workers.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queueMutex);
condition.wait(lock, [this] {
return stop || !tasks.empty();
});
if (stop && tasks.empty())
return;
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
template <class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::invoke_result<F, Args...>::type> {
using return_type = typename std::invoke_result<F, Args...>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queueMutex);
tasks.emplace([task]() { (*task)(); });
}
condition.notify_one();
return res;
}
void waitAll() {
std::unique_lock<std::mutex> lock(queueMutex);
condition.wait(lock, [this] { return tasks.empty(); });
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queueMutex);
stop = true;
}
condition.notify_all();
for (std::thread& worker : workers)
worker.join();
}
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queueMutex;
std::condition_variable condition;
bool stop = false;
};

38
src/model/utils/utils.cpp Normal file
View File

@ -0,0 +1,38 @@
#include "utils.hpp"
#include "../board/board.hpp"
std::vector<int8_t> to_target_square(std::vector<Move> moves) {
std::vector<int8_t> ret;
for (Move move : moves)
ret.push_back(move.target_square);
return ret;
}
int piece_value(Piece p) {
switch (p) {
case Piece::Pawn:
return PawnValue;
case Piece::Knigt:
return KnightValue;
case Piece::Bishop:
return BishopValue;
case Piece::Rook:
return RookValue;
case Piece::Queen:
return QueenValue;
default:
return 0;
}
}
int count_material(const Board& b, int8_t colour, bool count_pawns) {
int ret = 0;
for (int i = 0; i < 64; i++) {
if (b.piece_at(i) == Pawn && !count_pawns)
continue;
if (b.colour_at(i) == colour)
ret += piece_value(b.piece_at(i));
}
return ret;
}

19
src/model/utils/utils.hpp Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include "move.hpp"
#include <cstdint>
#include <limits>
#include <vector>
std::vector<int8_t> to_target_square(std::vector<Move>);
int count_material(const Board&, int8_t, bool = true);
int piece_value(Piece);
const int INFINITY = std::numeric_limits<int>::max();
const int PawnValue = 100;
const int KnightValue = 300;
const int BishopValue = 320;
const int RookValue = 500;
const int QueenValue = 900;

215
src/view/gui.cpp Normal file
View File

@ -0,0 +1,215 @@
#include "gui.hpp"
#include "../model/utils/utils.hpp"
#include <SFML/Graphics/Color.hpp>
#include <SFML/System/Vector2.hpp>
GUI::GUI() {
window.create(sf::VideoMode(WINDOW_SIZE, WINDOW_SIZE), "Chess Board");
load_textures();
font.loadFromFile("res/arial.ttf");
}
void GUI::update_board(
const Board& b, int8_t selected_square, std::vector<int8_t> targets
) {
window.clear();
draw_board(selected_square, targets);
draw_pieces(b);
window.display();
}
void GUI::notify_stalemate(Colour col) {
std::cout << "Stalemate for " << to_string(col) << std::endl;
}
void GUI::notify_checkmate(Colour col) {
std::cout << "Checkmate for " << to_string(col) << std::endl;
}
void GUI::handle_events() {
sf::Event event;
while (window.pollEvent(event))
if (event.type == sf::Event::Closed)
window.close();
else if (event.type == sf::Event::MouseButtonPressed)
handle_click(event.mouseButton.x, event.mouseButton.y);
}
void GUI::load_textures() {
const std::string names[6] =
{"rook", "knight", "bishop", "queen", "king", "pawn"
}; // don't touch the order, it's reflecting the one in the Piece enum
for (int i = 0; i < 6; ++i) {
textures[i][0].loadFromFile("res/pieces/white-" + names[i] + ".png");
textures[i][1].loadFromFile("res/pieces/black-" + names[i] + ".png");
}
}
void GUI::handle_click(int x, int y) {
int file = x / TILE_SIZE;
int rank = 7 - (y / TILE_SIZE);
controller->on_tile_selected(file, rank);
}
void GUI::draw_annotation(int file, int rank) {
if (file == 0) {
sf::Text annotation(std::to_string(rank + 1), font);
annotation.setStyle(sf::Text::Bold);
annotation.setCharacterSize(16);
annotation.setFillColor(rank % 2 == 0 ? colours[1] : colours[0]);
annotation.setPosition(
(file + .05) * TILE_SIZE,
(7 - rank + .05) * TILE_SIZE
);
window.draw(annotation);
}
if (rank == 0) {
sf::Text annotation("abcdefgh"[file], font);
annotation.setCharacterSize(16);
annotation.setOrigin(16, 16);
annotation.setStyle(sf::Text::Bold);
annotation.setFillColor(file % 2 == 0 ? colours[1] : colours[0]);
annotation.setPosition(
(file + 1) * TILE_SIZE,
(7 - rank + .95) * TILE_SIZE
);
window.draw(annotation);
}
}
void GUI::draw_board(int selected_square, std::vector<int8_t> targets) {
sf::RectangleShape square(sf::Vector2f(TILE_SIZE, TILE_SIZE));
for (int rank = 0; rank < 8; ++rank) {
for (int file = 0; file < 8; ++file) {
int8_t index = Coords{file, rank}.to_index();
square.setPosition(file * TILE_SIZE, (7 - rank) * TILE_SIZE);
if (index == selected_square)
square.setFillColor(
(file + rank) % 2 == 0 ? alt_colours[0] : alt_colours[1]
);
else
square.setFillColor(
(file + rank) % 2 == 0 ? colours[0] : colours[1]
);
window.draw(square);
draw_annotation(file, rank);
if (std::find(targets.begin(), targets.end(), index)
!= targets.end()) {
float r = .15 * TILE_SIZE;
sf::CircleShape circle{r};
sf::Color c(0x00000055);
circle.setFillColor(c);
circle.setOrigin(r, r);
circle.setPosition(
(file + .5) * TILE_SIZE,
(7 - rank + .5) * TILE_SIZE
);
window.draw(circle);
}
}
}
}
void GUI::draw_pieces(const Board& board) {
for (int i = 0; i < 64; ++i) {
int piece = board.piece_at(i);
if (piece != Piece::None) {
int colour = board.colour_at(i) == Colour::White ? 0 : 1;
pieces[i].setTexture(textures[piece - 1][colour]);
sf::Vector2 center = textures[piece - 1][colour].getSize() / 2u;
pieces[i].setOrigin(center.x, center.y);
pieces[i].setPosition(
(i % 8 + .5) * TILE_SIZE,
(7 - (int) (i / 8) + .5) * TILE_SIZE
);
window.draw(pieces[i]);
}
}
}
int GUI::show_popup(
const std::string& message, const std::vector<std::string>& options
) {
sf::RenderWindow popup(sf::VideoMode(300, 200), "Choice");
sf::Font font;
if (!font.loadFromFile("res/arial.ttf")) {
std::cerr << "Error: Could not load font!" << std::endl;
return -1;
}
sf::Text text(message, font, 20);
text.setPosition(20, 20);
text.setFillColor(sf::Color::Black);
std::vector<sf::RectangleShape> buttonShapes;
std::vector<sf::Text> buttonTexts;
for (size_t i = 0; i < options.size(); ++i) {
sf::RectangleShape button(sf::Vector2f(200, 30));
button.setPosition(50, 70 + i * 40);
button.setFillColor(sf::Color(150, 150, 150));
buttonShapes.push_back(button);
sf::Text buttonText(options[i], font, 18);
buttonText.setPosition(60, 75 + i * 40);
buttonText.setFillColor(sf::Color::Black);
buttonTexts.push_back(buttonText);
}
while (popup.isOpen()) {
sf::Event event;
while (popup.pollEvent(event)) {
if (event.type == sf::Event::Closed)
popup.close();
else if (event.type == sf::Event::MouseButtonPressed) {
for (size_t i = 0; i < buttonShapes.size(); ++i) {
if (buttonShapes[i].getGlobalBounds().contains(
event.mouseButton.x,
event.mouseButton.y
)) {
popup.close();
return i;
}
}
}
}
popup.clear(sf::Color::White);
popup.draw(text);
for (size_t i = 0; i < buttonShapes.size(); ++i) {
popup.draw(buttonShapes[i]);
popup.draw(buttonTexts[i]);
}
popup.display();
}
return -1;
}
Piece GUI::ask_about_promotion() {
std::vector<std::string> options = {"Queen", "Rook", "Bishop", "Knight"};
int idx = show_popup("Please choose a promotion for your pawn", options);
switch (idx) {
case 0:
return Queen;
case 1:
return Rook;
case 2:
return Bishop;
case 3:
return Knigt;
};
return Piece::None;
}
void GUI::show() {
while (window.isOpen())
handle_events();
}

41
src/view/gui.hpp Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include "../model/board/board.hpp"
#include "view.hpp"
#include <SFML/Graphics.hpp>
const int TILE_SIZE = 80;
const int BOARD_SIZE = 8;
const int WINDOW_SIZE = TILE_SIZE * BOARD_SIZE;
class GUI : public View {
public:
GUI();
void show() override;
Piece ask_about_promotion();
void update_board(const Board&, int8_t, std::vector<int8_t>) override;
void notify_checkmate(Colour) override;
void notify_stalemate(Colour) override;
private:
sf::RenderWindow window;
sf::Texture textures[6][2];
sf::Sprite pieces[64];
sf::Font font;
sf::Color colours[2] = {sf::Color(0xB88762FF), sf::Color(0xEDD6B0FF)};
sf::Color alt_colours[2] = {sf::Color(0xDCC34BFF), sf::Color(0xF6EB72FF)};
int show_popup(
const std::string& message, const std::vector<std::string>& options
);
void load_textures();
void handle_events();
void handle_click(int, int);
void draw_board(int, std::vector<int8_t>);
void draw_pieces(const Board&);
void draw_annotation(int, int);
};

View File

@ -1,87 +0,0 @@
import tkinter as tk
from logic.board import Board
from logic.move import Move
from logic.pieces.piece import Colour, Piece
from logic.position import Position
from view.view import View
class GUI(View):
def __init__(self) -> None:
super().__init__()
self.root = tk.Tk()
self.root.title("Chess Board")
self.tile_size = 80
board_size = self.tile_size * 8
self.canvas = tk.Canvas(self.root, width=board_size, height=board_size)
self.canvas.pack()
self.canvas.bind("<Button-1>", self._on_canvas_click)
def _on_canvas_click(self, event):
x, y = event.x // self.tile_size, event.y // self.tile_size
y = 7 - y
self._controller.on_tile_selected(x, y)
def update_board(self, board: Board, selected_piece: Piece, legal_moves: list[Move]) -> None:
self.canvas.delete("all")
self._draw_chess_board(board, selected_piece, legal_moves)
def _draw_chess_board(self, board, selected_piece = None, legal_moves = []):
colours = ["#F0D9B5", "#B58863"] # Light and dark squares
for y in range(8):
for x in range(8):
colour = colours[(x + y) % 2]
if selected_piece is not None:
possible_positions = [move.pos for move in legal_moves]
if Position(x, 7-y) in possible_positions:
colour = "#ADD8E6" # Highlight legal moves
self.canvas.create_rectangle(
x * self.tile_size,
y * self.tile_size,
(x + 1) * self.tile_size,
(y + 1) * self.tile_size,
fill=colour,
outline=colour,
)
piece = board.piece_at(x, 7-y)
if piece:
text_colour = "white" if piece.colour == Colour.WHITE else "black"
self.canvas.create_text(
(x + 0.5) * self.tile_size,
(y + 0.5) * self.tile_size,
text=piece.__class__.__name__[0],
fill=text_colour,
font=("Arial", 32, "bold")
)
# Cell annotations
text_colour = colours[(x + y + 1) % 2] # the other colour
if x == 0: # numbers in the top left of the first column
self.canvas.create_text(
(x + 0.15) * self.tile_size,
(y + 0.15) * self.tile_size,
text=8-y,
fill=text_colour,
font=("Arial", 10, "bold")
)
if y == 7: # numbers in the top left of the first column
self.canvas.create_text(
(x + 0.85) * self.tile_size,
(y + 0.85) * self.tile_size,
text="abcdefgh"[x],
fill=text_colour,
font=("Arial", 10, "bold")
)
def show(self) -> None:
self.root.mainloop()

16
src/view/noop.hpp Normal file
View File

@ -0,0 +1,16 @@
#include "../model/board/board.hpp"
#include "view.hpp"
class NoOpView : public View {
public:
NoOpView() {};
void show() override {};
void update_board(const Board&, int8_t, std::vector<int8_t>) override {};
void notify_checkmate(Colour) override{};
void notify_stalemate(Colour) override{};
Piece ask_about_promotion() override {
return Queen;
};
};

View File

@ -1,57 +0,0 @@
from logic.board import Board
from logic.pieces.bishop import Bishop
from logic.pieces.king import King
from logic.pieces.knight import Knight
from logic.pieces.pawn import Pawn
from logic.pieces.piece import Piece
from logic.pieces.queen import Queen
from logic.pieces.rook import Rook
from view.view import View
class TUI(View):
def __init__(self, board: Board) -> None:
super().__init__(board)
def show(self) -> None:
board_view = [
[" " for _ in range(0, 8)]
for _ in range(0, 8)
]
for pos, piece in self.board._white.items():
board_view[pos.y][pos.x] = self.string_of(piece).upper()
for pos, piece in self.board._black.items():
board_view[pos.y][pos.x] = self.string_of(piece)
# we reverse the board because (0, 0) in in the bottom left, not top left
board_view.reverse()
print(self.to_string(board_view))
def to_string(self, board_view: list[list[str]]) -> str:
VER_SEP = "|"
HOR_SEP = "-"
ROW_SEP = HOR_SEP * (2*len(board_view[0]) + 1)
ret = ROW_SEP + "\n"
for row_view in board_view:
row_str = VER_SEP + VER_SEP.join(row_view) + VER_SEP
ret += row_str + "\n"
ret += ROW_SEP + "\n"
return ret
def string_of(self, piece: Piece) -> str:
type_ = type(piece)
if type_ == Pawn:
return "p"
if type_ == Queen:
return "q"
if type_ == Bishop:
return "b"
if type_ == Knight:
return "n"
if type_ == Rook:
return "r"
if type_ == King:
return "k"
raise ValueError(f"Unknown piece type {type(piece)}")

22
src/view/view.hpp Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include "../controller/controller.hpp"
#include "../model/board/board.hpp"
#include "../model/pieces/piece.hpp"
#include "../model/utils/move.hpp"
class View {
protected:
Controller* controller;
public:
void set_controller(Controller* c) {
controller = c;
}
virtual void show() = 0;
virtual Piece ask_about_promotion() = 0;
virtual void update_board(const Board&, int8_t, std::vector<int8_t>) = 0;
virtual void notify_checkmate(Colour) = 0;
virtual void notify_stalemate(Colour) = 0;
};

View File

@ -1,18 +0,0 @@
from logic.board import Board
from logic.move import Move
from logic.pieces.piece import Piece
class View:
def __init__(self) -> None:
self._controller: "Controller" = None
def show(self) -> None:
raise NotImplementedError(f"Can't show the board, the show() method of {type(self)} is not implemented")
def update_board(self, board: Board, selected_piece: Piece, legal_moves: list[Move]) -> None:
raise NotImplementedError(f"Can't update the board, the update_board() method of {type(self)} is not implemented")
def set_controller(self, controller: "Controller") -> None:
self._controller = controller

23
tests/fen.cpp Normal file
View File

@ -0,0 +1,23 @@
#include "../src/model/board/board.hpp"
#include "lib.hpp"
int main() {
std::string pos =
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
ASSERT_EQUALS(pos, Board::setup_fen_position(pos).to_fen());
pos = "r1bk3r/p2pBpNp/n4n2/1p1NP2P/6P1/3P4/P1P1K3/q5b1 b Qk - 0 1";
ASSERT_EQUALS(pos, Board::setup_fen_position(pos).to_fen());
pos = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1";
ASSERT_EQUALS(pos, Board::setup_fen_position(pos).to_fen());
pos = "4k2r/6r1/8/8/8/8/3R4/R3K3 w Qk - 0 1";
ASSERT_EQUALS(pos, Board::setup_fen_position(pos).to_fen());
pos = "8/8/8/4p1K1/2k1P3/8/8/8 b - - 0 1";
ASSERT_EQUALS(pos, Board::setup_fen_position(pos).to_fen());
pos = "8/5k2/3p4/1p1Pp2p/pP2Pp1P/P4P1K/8/8 b - - 99 50";
ASSERT_EQUALS(pos, Board::setup_fen_position(pos).to_fen());
}

16
tests/lib.hpp Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include <iostream>
#define ASSERT_EQUALS(x, y) \
{ \
auto expected = x; \
auto actual = y; \
if (expected != actual) { \
std::cerr << "Expected: " << std::endl \
<< '\t' << expected << std::endl \
<< "Got: " << std::endl \
<< '\t' << actual << std::endl; \
exit(1); \
} \
}

13
tests/move.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "../src/model/board/board.hpp"
#include "lib.hpp"
int main() {
std::string str_move = "a2a3";
ASSERT_EQUALS(str_move, Move::from_string(str_move).to_string());
str_move = "b2f4";
ASSERT_EQUALS(str_move, Move::from_string(str_move).to_string());
str_move = "a2a1r";
ASSERT_EQUALS(str_move, Move::from_string(str_move).to_string());
}

20
tests/positions.cpp Normal file
View File

@ -0,0 +1,20 @@
#include "../src/model/utils/coords.hpp"
#include "lib.hpp"
int main() {
ASSERT_EQUALS(Coords(0, 0).to_algebraic(), "a1");
ASSERT_EQUALS(Coords(1, 0).to_algebraic(), "b1");
ASSERT_EQUALS(Coords(2, 1).to_algebraic(), "c2");
ASSERT_EQUALS(Coords(4, 2).to_algebraic(), "e3");
ASSERT_EQUALS(Coords(7, 7).to_algebraic(), "h8");
ASSERT_EQUALS(Coords::from_algebraic("a1"), Coords(0, 0));
ASSERT_EQUALS(Coords::from_algebraic("b1"), Coords(1, 0));
ASSERT_EQUALS(Coords::from_algebraic("c2"), Coords(2, 1));
ASSERT_EQUALS(Coords::from_algebraic("e3"), Coords(4, 2));
ASSERT_EQUALS(Coords::from_algebraic("h8"), Coords(7, 7));
}