Compare commits

...

285 Commits

Author SHA1 Message Date
da858d9ed4 merge removed line readded 2025-12-04 14:32:46 +01:00
Hatvani Tamás
9efc7e3ba6 Merge branch 'master' into UI/event 2025-12-04 14:21:59 +01:00
568fdba7e7 added move history to ui and server 2025-12-03 22:06:11 +01:00
Varga Dávid Lajos
86b5f172ee added method to get notation of chessmove 2025-12-03 21:09:13 +01:00
5094a6e1fd fixed crashing when a wrong move was mode which was not in the legal moves vector 2025-12-03 20:44:45 +01:00
898ba3e92f fixed request moves event to have the correct fen string 2025-12-03 20:06:41 +01:00
Hatvani Tamás
683c392ab9 Merge pull request #27 from htamas1210/Engine/make-move
Engine/make move
2025-12-03 18:38:08 +01:00
5e85a59c29 fixed tests 2025-12-03 14:18:45 +01:00
dca37108d7 fixed game resignation 2025-12-03 13:37:49 +01:00
66c3f9652f Merge branch 'UI/Appearance' into UI/event 2025-12-02 19:23:53 +01:00
Bence
5e99034abe Added margin around chessboard 2025-12-02 19:17:52 +01:00
Bence
ac2fe4418c Added dark and light modes 2025-12-02 13:08:34 +01:00
Bence
9444441c07 Readded settings 2025-12-02 11:33:27 +01:00
ad2ad63e22 filtering positions correctly from av. moves, bug with fen ui update and fenstring from server 2025-12-01 13:27:53 +01:00
f6c9503965 requesting moves from engine, still needs proper checking 2025-11-30 16:57:25 +01:00
Bence
2f9a91cab8 Added move history to InGame appstate 2025-11-30 16:46:16 +01:00
bf490aa73d added legal moves message to send back available moves to clients 2025-11-30 16:32:14 +01:00
73a6cf004b deleted connection file 2025-11-30 16:31:39 +01:00
Bence
df28a16a55 Added "Return to main menu" button to Private Play 2025-11-30 15:26:32 +01:00
Bence
bfccdf1325 Dynamic button sizing 2025-11-30 14:35:04 +01:00
446413c1b2 added turn changing and checking before moving if the player is allowed to move 2025-11-30 11:58:42 +01:00
d94c088ae9 can now go back to the main menu before starting a match 2025-11-29 20:21:52 +01:00
bc03cead82 added clone to engine structs, added private session launch with starting server from ui 2025-11-29 18:55:10 +01:00
Hatvani Tamás
2fd04238f5 Remove checkout step from release.yml
Removed checkout step from release workflow.
2025-11-28 11:47:54 +01:00
Hatvani Tamás
c7bad7665f Update release job dependencies in dispatcher.yml 2025-11-28 11:44:39 +01:00
Hatvani Tamás
b2d1d3f809 Merge pull request #26 from htamas1210/Engine/make-move
Engine/make move
2025-11-28 11:27:38 +01:00
Varga Dávid Lajos
fe8f8621d7 implemented library function get_board_after_move 2025-11-28 00:26:12 +02:00
Varga Dávid Lajos
767e7d14fd added and used method bitboard::makemove::castles::make_castle 2025-11-28 00:15:37 +02:00
Varga Dávid Lajos
074f181791 added file and module structure for bitboard::makemove::castles 2025-11-27 23:57:31 +02:00
Varga Dávid Lajos
542dd39aa6 added and used method bitboard::makemove::captures::make_capture 2025-11-27 23:56:27 +02:00
Varga Dávid Lajos
6db4ae6d07 added file and module structure for bitboard::makemove::captures 2025-11-27 23:50:53 +02:00
Varga Dávid Lajos
171a7b8020 added and used method bitboard::makemove::quiets::make_quiet 2025-11-27 23:49:18 +02:00
Varga Dávid Lajos
205e8811d5 added file and module structure for bitboard::makemove::quiets 2025-11-27 23:28:46 +02:00
Varga Dávid Lajos
2394da84ce added skeleton for method bitboard::makemove::make_move 2025-11-27 23:26:59 +02:00
Varga Dávid Lajos
e7c7743682 added file and module structure for bitboard::makemove 2025-11-27 23:08:39 +02:00
Varga Dávid Lajos
2375f28ee3 added getter method bitboard::board::fen 2025-11-27 23:05:01 +02:00
Bence
036f57daa0 Merge pull request #25 from htamas1210/UI/event
UI/event
2025-11-27 21:55:30 +01:00
a0bca32733 fixed test conditions 2025-11-27 21:45:38 +01:00
Varga Dávid Lajos
5b6318442e added part of getter bitboard::board::fen 2025-11-27 22:31:06 +02:00
Varga Dávid Lajos
24ddc74573 added specialized getter method get_piece_character to bitboard/board.rs 2025-11-27 22:18:04 +02:00
bdbe93932d fixed unicode characters, fixed queen and king position, new min windows size 2025-11-27 21:17:53 +01:00
75c39ccbe4 added new tests for the new code 2025-11-27 16:45:40 +01:00
df02c2eee1 updated ui code with partial rewriting, now if there are two players they will join a match and the board will be drawn 2025-11-27 16:33:08 +01:00
5763716848 fixed error: sending both match notification to the same player and removed old server message struct 2025-11-27 16:31:48 +01:00
2112109470 added optional parameters that the ui will receive from server 2025-11-26 17:53:12 +01:00
Bence
933bb46f17 Board squares flipping 2025-11-26 15:22:23 +01:00
5deebb0621 connecting with websocket to local hosted server instance 2025-11-26 13:55:39 +01:00
60911f93f6 removed unused stuff from test client 2025-11-26 13:55:16 +01:00
7820555fa8 added new packages to ui and removed unused stuff from server 2025-11-26 11:18:58 +01:00
b5c18419d8 readded dependency for workflow 2025-11-25 19:03:38 +01:00
Hatvani Tamás
32ab155ff5 Merge pull request #24 from htamas1210/Engine/API-logic 2025-11-24 15:53:45 +01:00
1f3fff3501 removed test upload dependenci, issue with sheets api 2025-11-24 15:51:37 +01:00
vargadavidlajos
b8113b8cc4 Merge branch 'master' into Engine/API-logic 2025-11-24 15:05:58 +01:00
vargadavidlajos
3795087b3c Merge pull request #23 from htamas1210/Server/logging
Server/logging
2025-11-24 13:30:22 +01:00
Hatvani Tamás
7fdd7fd65c Merge branch 'master' into Server/logging 2025-11-24 13:20:16 +01:00
vargadavidlajos
90a59305ac Merge pull request #22 from htamas1210/Server/event
Server/event
2025-11-24 13:17:29 +01:00
5ccca3ab8e added resign event and deleting finished matches 2025-11-24 13:14:38 +01:00
f2adc14ce2 fixed error, not removing reference to deleted crate 2025-11-24 12:10:29 +01:00
7ed9ae3799 refactored println statements into formatted logs with log crate 2025-11-24 12:05:07 +01:00
622bef963f new serevr message enum for easier message handling 2025-11-24 10:07:20 +01:00
Varga Dávid Lajos
499b0fd152 removed unneeded log message 2025-11-21 23:43:57 +02:00
Varga Dávid Lajos
eba8076ea9 implemented api function is_game_over 2025-11-21 23:41:33 +02:00
Varga Dávid Lajos
f05b1c80da added test for api function is_game_over 2025-11-21 23:26:15 +02:00
Varga Dávid Lajos
068f5248cd implemented api function get_available_moves 2025-11-21 22:56:02 +02:00
Varga Dávid Lajos
a63954fbfe implemented traits needed for testing in lib.rs 2025-11-21 21:52:46 +02:00
Varga Dávid Lajos
2b7faf80ab removed unused variable from conversion method from_bitmove from chessmove.rs 2025-11-21 20:36:22 +02:00
Hatvani Tamás
890fab38cb Merge pull request #21 from htamas1210/Engine/api-conversions 2025-11-21 14:54:51 +01:00
Varga Dávid Lajos
ebcc447092 removed unused imports from chessmove.rs 2025-11-21 14:19:33 +01:00
Varga Dávid Lajos
a4982c0fd3 implemented conversion method to_bitmove for enum ChessMove 2025-11-21 14:18:23 +01:00
Varga Dávid Lajos
c414485891 implemented conversion method to_index for struct BoardSquare 2025-11-21 14:09:02 +01:00
Varga Dávid Lajos
f29298731b implemented conversion method to_index for enum PieceType 2025-11-21 14:07:27 +01:00
Varga Dávid Lajos
e1d51fe291 implemented conversion method to_bitmovetype for enum MoveType 2025-11-21 14:04:37 +01:00
Varga Dávid Lajos
f841fba080 implemented constructor from_bitmove for enum ChessMove 2025-11-21 13:55:07 +01:00
Varga Dávid Lajos
821746d7cf changed implementation of ChessMove from struct to enum 2025-11-21 13:29:01 +01:00
Varga Dávid Lajos
5d108602ea implemented constructor by square_index for struct BoardSquare 2025-11-21 13:08:53 +01:00
Varga Dávid Lajos
b137e6ceab implemented constructor by piece_index for enum PieceType 2025-11-21 13:01:38 +01:00
Varga Dávid Lajos
d649afdcfd limited visibility of method from_bitmovetype from enum MoveType to pub(in super) 2025-11-21 12:57:38 +01:00
Varga Dávid Lajos
5e65bcf5ef changed method from_index for enum MoveType to from_bitmovetype 2025-11-21 12:55:18 +01:00
Varga Dávid Lajos
82a6bd8449 implemented constructor for enum MoveType from in-engine move_type index 2025-11-21 12:40:24 +01:00
Hatvani Tamás
23c890f329 Merge pull request #20 from htamas1210/Engine/move-gen 2025-11-20 21:26:58 +01:00
vargadavidlajos
b91777a64f Merge branch 'master' into Engine/move-gen 2025-11-20 21:19:41 +01:00
Varga Dávid Lajos
775083fde7 fixed wrong promotion_piece indexes being saved in bitboard::movegen::pawns 2025-11-20 21:11:41 +01:00
Varga Dávid Lajos
84121fbeba allowed use of move generation methods in case of check 2025-11-20 20:45:26 +01:00
Varga Dávid Lajos
f3d316d3e3 implemented method collect_king_evasion in bitboard::movegen used in move generation 2025-11-20 20:43:35 +01:00
Varga Dávid Lajos
fad1a6d51b implemented method collect_moves_single_check in bitboard::movegen used in move generation 2025-11-20 20:42:44 +01:00
Varga Dávid Lajos
9b43c9ce9a implemented method collect_all_moves in bitboard::movegen used in move generation 2025-11-20 20:39:47 +01:00
Varga Dávid Lajos
dba7aca2cf added method get_safe_king_squares to bitboard::attacks for king safety checks 2025-11-20 20:35:44 +01:00
Varga Dávid Lajos
47a22dbbaa added method get_pseudo_rook_moves_ignore_king in bitboard::attacks for king safety checks 2025-11-20 20:29:27 +01:00
Varga Dávid Lajos
1bbe33f3aa added method get_pseudo_bishop_moves_ignore_king in bitboard::attacks for king safety checks 2025-11-20 20:28:16 +01:00
Varga Dávid Lajos
8c0fa09f1e added LUT KING_SAFETY_BISHOP_MASK in bitboard::attackmaps for king safety checks 2025-11-20 20:25:14 +01:00
Varga Dávid Lajos
d8eae9177b added LUT KING_SAFETY_ROOK_MASK in bitboard::attackmaps for king safety checks 2025-11-20 20:23:40 +01:00
Varga Dávid Lajos
71ea2a05d0 added LUT BISHOP_MOVE_MASK in bitboard::attackmaps for king safety checks 2025-11-20 20:22:08 +01:00
Varga Dávid Lajos
33ca535547 added LUT ROOK_MOVE_MASK in bitboard::attackmaps for king safety checks 2025-11-20 20:21:28 +01:00
6fc1b6c3fd fixed test function, removed messages because we do not use it 2025-11-20 17:23:30 +01:00
a985182c99 rewrote the message function to use an Option value and check if the playerconnection has value before trying to send message 2025-11-20 16:36:02 +01:00
3b78a8e925 Merge branch 'master' into Server/event 2025-11-20 14:46:33 +01:00
ab369f179c updated move event to use chessmove struct from engine 2025-11-20 14:46:01 +01:00
be6a86bebf handling client move event 2025-11-20 13:38:39 +01:00
Varga Dávid Lajos
08ba7ee436 implemented move generation method for castling moves 2025-11-20 12:41:20 +01:00
Varga Dávid Lajos
5b3e84dab0 implemented move generation method or king capture moves 2025-11-20 12:35:46 +01:00
Varga Dávid Lajos
21d26902f0 implemented general move generation methdod for kings 2025-11-20 12:33:58 +01:00
Varga Dávid Lajos
e9f7663488 added file and module structure for bitboard::movegen::kings 2025-11-20 12:28:46 +01:00
Varga Dávid Lajos
c2abc08206 implemented move generation method for queen capture moves 2025-11-20 12:27:31 +01:00
Varga Dávid Lajos
9fa298ed51 implemented general move generation method for queens 2025-11-20 12:26:16 +01:00
Varga Dávid Lajos
a51d5e2652 added file and module structure for bitboard::movegen::queens 2025-11-20 12:24:40 +01:00
Varga Dávid Lajos
0dd9a3d047 implemented move generation method for rook capture moves 2025-11-20 12:23:14 +01:00
Varga Dávid Lajos
e896aba4ec implemented general move generation method for rooks 2025-11-20 12:18:25 +01:00
Varga Dávid Lajos
9fb3d15bed added file and module structure for bitboard::movegen::rooks 2025-11-20 12:14:31 +01:00
Varga Dávid Lajos
35cb1eeb73 implemented move generation method for bishop capture moves 2025-11-19 21:13:05 +01:00
Varga Dávid Lajos
8165749845 implemented general move generation method for bishops 2025-11-19 21:11:39 +01:00
Varga Dávid Lajos
06cb2b88c2 added file and module structure for bitboard::movegen::bishops 2025-11-19 21:08:45 +01:00
Varga Dávid Lajos
2d646f0ad0 implemented move generation method for knight capture moves 2025-11-19 21:07:18 +01:00
Varga Dávid Lajos
40e2c50209 implemeneted general move generation method for knight moves 2025-11-19 21:05:58 +01:00
Varga Dávid Lajos
2e82a52f27 added file and module structure for bitboard::movegen::knights 2025-11-19 21:03:27 +01:00
Varga Dávid Lajos
372d86b451 changed signature utility functions utils::pop_lsb and utils::pop_msb to reduce unnecessary type conversions 2025-11-19 20:58:56 +01:00
Varga Dávid Lajos
d66787f51d implemented method to collect both quiet and capture moves for pawns 2025-11-19 20:54:01 +01:00
Varga Dávid Lajos
a4c63f19e5 implemented move generation method for pawn capture moves 2025-11-19 20:52:28 +01:00
Varga Dávid Lajos
a2f261778b implemented move generation method for pawn quiet moves 2025-11-19 20:47:44 +01:00
Varga Dávid Lajos
3f7cc2df4d added file and module structure for bitboard::movegen::pawns 2025-11-19 20:39:00 +01:00
Varga Dávid Lajos
48272d6d70 added skeleton for main move generation method to bitboard::movegen 2025-11-19 20:35:46 +01:00
Varga Dávid Lajos
d172afc50c re-added missing code for check tests from past branch 2025-11-19 20:32:12 +01:00
5336aadf97 fixed bug in findmatch, added a new command in test client, refactored how the server sends messages to the client 2025-11-19 20:16:55 +01:00
Varga Dávid Lajos
ee61528891 added file and module structure for bitboard::movegen.rs 2025-11-19 20:16:48 +01:00
Hatvani Tamás
0ace485bb6 Merge pull request #19 from htamas1210/Engine/movebuffer
Engine/movebuffer
2025-11-19 20:14:45 +01:00
Hatvani Tamás
1c1b9a96d9 Import time module and add sleep in data upload
Added time.sleep to control row insertion rate.
2025-11-19 17:34:44 +01:00
Hatvani Tamás
e99b2defa3 Merge pull request #18 from htamas1210/Engine/bitmove
Engine/bitmove
2025-11-19 17:10:03 +01:00
Varga Dávid Lajos
f39c113ef9 implemented method append for struct MoveBuffer 2025-11-19 16:13:34 +01:00
Varga Dávid Lajos
f417a7e47c implemented getter for field buffer i struct MoveBuffer 2025-11-19 16:09:31 +01:00
Varga Dávid Lajos
547b0e51cb corrected annotation for method clear in struct MoveBuffer 2025-11-19 16:00:09 +01:00
Varga Dávid Lajos
5340744abe implemented method clear for struct MoveBuffer 2025-11-19 15:56:57 +01:00
Varga Dávid Lajos
f64ebfa47f implemented method get for struct MoveBuffer 2025-11-19 15:53:39 +01:00
Varga Dávid Lajos
4e7ac2a195 added getter for field count in struct MoveBuffer 2025-11-19 15:50:03 +01:00
Varga Dávid Lajos
dc176c103b implemented method add for struct MoveBuffer 2025-11-19 15:40:11 +01:00
Varga Dávid Lajos
85a7fa37ef added parameterless constructor for struct MoveBuffer 2025-11-19 15:22:15 +01:00
Varga Dávid Lajos
a60658763d defined shape of struct MoveBuffer 2025-11-19 15:06:37 +01:00
Varga Dávid Lajos
b76a009e4e added file and module structure for movebuffer.rs 2025-11-19 14:57:36 +01:00
Varga Dávid Lajos
db333a693f added method uci_notation to struct BitMove 2025-11-18 17:38:48 +01:00
Varga Dávid Lajos
fd0d26486b switched from public fields to getters for struct BitMove 2025-11-18 17:30:04 +01:00
8d559f4b11 added request legal moves to testing client 2025-11-18 17:24:49 +01:00
Varga Dávid Lajos
e1f4ae717e added constructor for castling moves in bitboard::bitmove.rs 2025-11-18 17:24:11 +01:00
Varga Dávid Lajos
7b9c1edbab added constructor for capture moves in bitboard::bitmove.rs 2025-11-18 17:23:07 +01:00
Varga Dávid Lajos
05294a7736 changed name of value of enum BitMoveType to align with Rust naming conventions 2025-11-18 17:20:40 +01:00
Varga Dávid Lajos
d8da808580 added enum BitMoveType to bitboard::bitmove.rs for readability 2025-11-18 17:19:31 +01:00
Varga Dávid Lajos
c420d8b3dd added constructor for quiet moves in bitboard::bitmove.rs 2025-11-18 17:17:11 +01:00
Varga Dávid Lajos
57af3aaae3 defined shape of struct bitboard::bitmove::BitMove 2025-11-18 17:09:39 +01:00
Varga Dávid Lajos
887b9abed8 adde file and module structure for bitboard::bitmove.rs 2025-11-18 17:02:07 +01:00
vargadavidlajos
a862d31c7d Merge pull request #17 from htamas1210/Server/event
Server/event
2025-11-18 16:58:29 +01:00
48f66aac75 added engine as a crate into server project, implemented request moves event 2025-11-18 16:43:14 +01:00
Hatvani Tamás
431c65e075 Merge pull request #16 from htamas1210/Engine/check-test
Engine/check test
2025-11-18 16:41:49 +01:00
Varga Dávid Lajos
7e64a7ca16 fixed incorrect test parameter in bitboard::legality::tests::check_test_test 2025-11-18 16:20:42 +01:00
Varga Dávid Lajos
6e0efc76f3 added missing method place_piece to struct bitboard::Board 2025-11-18 16:18:19 +01:00
Varga Dávid Lajos
1a9d7ad460 added tests for method bitboard::legality::check_test 2025-11-18 16:15:28 +01:00
6dfe92f211 Merge branch 'master' into Server/event 2025-11-18 16:12:07 +01:00
Varga Dávid Lajos
9209f1c4e0 implemented method check_test in bitboard::checktest.rs 2025-11-18 12:14:22 +01:00
4bb485e833 added findmatch event 2025-11-18 09:28:07 +01:00
Bence
3ebfc61ac7 Merge pull request #15 from htamas1210/Engine/library-functions
Engine/library functions
2025-11-17 15:04:28 +01:00
Hatvani Tamás
d8d8b58ed7 Uploaded presentation for class 2025-11-17 12:24:42 +01:00
Varga Dávid Lajos
a8970053f9 added constructor for castling moves in chessmove.rs 2025-11-16 19:05:52 +01:00
vargadavidlajos
7cd77ab904 Merge pull request #14 from htamas1210/Docs/kovspec
Added point 3 to kovspec.md
2025-11-16 19:05:07 +01:00
Varga Dávid Lajos
fa0a93172d added constructore for captures in chessmove.rs 2025-11-16 19:01:53 +01:00
Varga Dávid Lajos
bb74a486d8 added constructor for quiet moves in chessmove.rs 2025-11-16 19:00:00 +01:00
vargadavidlajos
10c176a0f5 Added point 3 to kovspec.md 2025-11-16 18:39:48 +01:00
Varga Dávid Lajos
d1932762f9 removed field is_promotion from struct ChessMove and added an Option for field promotion_piece 2025-11-16 18:33:04 +01:00
Varga Dávid Lajos
9181b6c4ca added all arguments constructor for struct BoardSquare 2025-11-16 13:51:10 +01:00
Varga Dávid Lajos
b703d8b9a1 added parameterless constructor for struct BoardSquare 2025-11-16 13:44:41 +01:00
Varga Dávid Lajos
acaa7d078a added new library function: get_board_after_move 2025-11-15 20:40:58 +01:00
Varga Dávid Lajos
30448b5f3d switched return type of is_game_over from bool to GameEnd 2025-11-15 20:37:06 +01:00
Varga Dávid Lajos
ff38c5c475 added struct GameEnd 2025-11-15 20:28:33 +01:00
Varga Dávid Lajos
42daeb9971 added file and module structure for gameend.rs 2025-11-15 20:15:17 +01:00
Bence
fc7ece9e94 Fixed app not starting in fullscreen 2025-11-15 20:09:26 +01:00
Varga Dávid Lajos
c8b6bc35d5 added library functions: get_available_moves and is_game_over 2025-11-15 20:08:54 +01:00
Varga Dávid Lajos
5f2a4e1721 added struct ChessMove 2025-11-15 19:57:11 +01:00
c77e534ed8 removed event system file as it was unused and we do not neet it anymore 2025-11-15 19:41:48 +01:00
Varga Dávid Lajos
5c42b6e52a added enum MoveType 2025-11-15 19:37:30 +01:00
Varga Dávid Lajos
f7364e7939 added file and module structure for movetype.rs 2025-11-15 19:35:17 +01:00
1c92918692 finished join event 2025-11-15 19:33:40 +01:00
Varga Dávid Lajos
b4a1f0820f added struct BoardSquare 2025-11-15 19:26:21 +01:00
Varga Dávid Lajos
6d9d5c49ae added file and module structure for boardsquare.rs 2025-11-15 19:24:37 +01:00
Varga Dávid Lajos
1ca44b472b added enum PieceType 2025-11-15 19:21:46 +01:00
Varga Dávid Lajos
79aabeac8c added file and module structure for piecetype.rs 2025-11-15 19:17:50 +01:00
Varga Dávid Lajos
5eb85dab89 added file and module structure for file: chessmove.rs 2025-11-15 19:15:30 +01:00
Varga Dávid Lajos
291b7e71a2 added crate root file lib.rs for library crate 2025-11-15 19:14:08 +01:00
vargadavidlajos
2aaa2aca70 Merge pull request #13 from htamas1210/UI/SettingsAndGame
Created a new branch due to a bug
2025-11-15 19:11:39 +01:00
Bence
456d386551 Created a new branch due to a bug
Previous pull request didn't work (didnt change ui/src/main.rs), created new branch to see if the problem persists
2025-11-15 19:06:25 +01:00
cca852d466 Working on event system, join implemented 2025-11-15 18:32:43 +01:00
Hatvani Tamás
e663d61b9e Merge pull request #11 from htamas1210/Engine/pseudo-moves
Engine/pseudo moves
2025-11-15 18:05:17 +01:00
a098c8051a planning done on how the event system works 2025-11-15 16:41:11 +01:00
Hatvani Tamás
c3ed181b07 Merge pull request #10 from htamas1210/Engine/legality-checks
Engine/legality checks
2025-11-15 15:03:38 +01:00
Hatvani Tamás
5b3f2488d4 Merge pull request #9 from htamas1210/Engine/board-repr
Engine/board repr
2025-11-15 14:53:07 +01:00
e599722e45 added client for testing connection and events 2025-11-15 14:44:44 +01:00
Varga Dávid Lajos
659413ca31 implemented pseudo-move generation for queens 2025-11-15 13:47:46 +01:00
Varga Dávid Lajos
4de505bc21 implemented pseudo-move generation for rooks 2025-11-15 13:47:03 +01:00
Varga Dávid Lajos
7c58f0d508 implemented pseudo-move generation for bishops 2025-11-15 13:46:11 +01:00
Varga Dávid Lajos
c9246d1342 added helper function: get_raycast_from_square_in_direction 2025-11-15 13:45:01 +01:00
Varga Dávid Lajos
eab795b6df implemented pseudo-move generation for pawn captures 2025-11-15 13:43:37 +01:00
Varga Dávid Lajos
f8ab14a026 implemented pseudo-move generation for kings 2025-11-15 13:42:41 +01:00
Varga Dávid Lajos
274897a070 implemented pseudo-move generation for knights 2025-11-15 13:41:40 +01:00
Varga Dávid Lajos
8ecbeb9c41 implemented pseudo-move generation for pawn moves 2025-11-15 13:40:40 +01:00
Varga Dávid Lajos
a1482d11f2 added file and module structure for bitboard::attacks.rs 2025-11-15 13:38:48 +01:00
Varga Dávid Lajos
f9a302c9a0 implemented method add_checker for CheckInfo in bitboard::checkinfo.rs 2025-11-15 12:40:52 +01:00
Varga Dávid Lajos
ad530e9155 added constructor for struct CheckInfo in bitboard::checkinfo.rs 2025-11-15 12:39:35 +01:00
Varga Dávid Lajos
182aa59ee1 defined shape of struct CheckInfo in bitboard::checkinfo.rs 2025-11-15 12:37:47 +01:00
Varga Dávid Lajos
296f1f6c3a added file and module structure for checkinfo.rs 2025-11-15 12:36:11 +01:00
Varga Dávid Lajos
f7355f8e74 implemented use of calc_pinned_squares in board.rs 2025-11-15 12:33:18 +01:00
Varga Dávid Lajos
cd58a7a321 added methods for detecting pinned pieces 2025-11-15 12:30:01 +01:00
Varga Dávid Lajos
2beb8ab303 added file and module structure for bitboard::legality.rs 2025-11-15 10:31:02 +01:00
Varga Dávid Lajos
5425ccc5cd added partial constructor for starting from a fen position 2025-11-15 10:26:31 +01:00
Varga Dávid Lajos
1104c8e6c5 added helper method for fen parsing to utils.rs 2025-11-15 10:21:00 +01:00
Varga Dávid Lajos
274ffcf5ec added method to get the current square of a king 2025-11-15 10:14:37 +01:00
Varga Dávid Lajos
5854dbc20b set each field to pub(in super) for later use 2025-11-15 10:12:52 +01:00
Varga Dávid Lajos
f36a196b2f added getters for fields 2025-11-15 10:11:34 +01:00
Varga Dávid Lajos
11f26809df used calc_occupancy and calc_piece_board methods in the initial state constructor 2025-11-15 10:07:23 +01:00
Varga Dávid Lajos
c88fbe68e3 added and used necessary methods the initial state constructor 2025-11-15 10:06:12 +01:00
Varga Dávid Lajos
7f4c53ddb7 added partial constructor for initial board state to board.rs 2025-11-15 10:03:24 +01:00
Varga Dávid Lajos
38b38845d6 added constructor for clear board to board.rs 2025-11-15 09:58:38 +01:00
Varga Dávid Lajos
66dd2877b2 defined board representation shape in board.rs 2025-11-15 09:56:17 +01:00
Varga Dávid Lajos
3cd53c7d70 added file and module structure for board representation 2025-11-15 09:51:07 +01:00
Bence
5b3bdc750f Merge pull request #8 from htamas1210/Server/websocket
Server/websocket
2025-11-13 19:12:00 +01:00
83a73ed34c Added tests 2025-11-13 15:19:26 +01:00
Hatvani Tamás
7c27d47935 Merge branch 'master' into Server/websocket 2025-11-12 19:31:55 +01:00
Hatvani Tamás
4ffec0f05d Add name to checkout step in dispatcher.yml 2025-11-12 19:27:29 +01:00
Hatvani Tamás
da3b1c8c00 Add checkout action to dispatcher workflow 2025-11-12 19:13:14 +01:00
Hatvani Tamás
a497f8554f Add checkout action to dispatcher workflow 2025-11-12 19:12:47 +01:00
94b4d727a6 Merge branch 'master' into Server/websocket 2025-11-12 19:04:41 +01:00
39273908ac removed checkout to potentially fix an error with test_data.log 2025-11-12 18:58:36 +01:00
Hatvani Tamás
7be3c928b7 Display test_data.log in workflow script
Added command to display contents of test_data.log
2025-11-12 18:54:06 +01:00
Hatvani Tamás
f8c14cc268 Enhance logging for data upload process
Added print statements to log data being uploaded to each tab.
2025-11-12 18:50:01 +01:00
Hatvani Tamás
566d72c2d6 Merge pull request #7 from htamas1210/Workflow
Workflow
2025-11-12 18:45:08 +01:00
ad26d22281 indentation error in python script 2025-11-12 18:44:00 +01:00
352b4e57d7 rewrote the python script
the script now checks for project name on every line read from the log
file and appends it to a list named by the project, if the branch is
master then it appends all lines into one list, then it checks if there
is any data in the list and uploads them to the correct tab that matches
the project name or master
2025-11-12 18:42:17 +01:00
5696128ac9 handling differenk kinds of server messages, and matchmaking basics 2025-11-12 17:11:23 +01:00
c2e29415c9 added new event system to handle different client events in game and on connection 2025-11-12 15:54:03 +01:00
ff68d9d2d9 new server messages 2025-11-12 15:52:55 +01:00
281f496c83 removed these files, ready to start working on event system messages 2025-11-12 15:14:07 +01:00
532c5a42ea added anyhow and rand crate 2025-11-12 15:13:01 +01:00
73624baf90 added server event enum 2025-11-12 14:01:13 +01:00
d8861b66b2 using serde feature from uuid, so i can add an id to a struct definition 2025-11-12 13:25:40 +01:00
4123a710cf first phase of rewrite done, server stores connections with an id, currently works as a hub, next phase to handle custom message types 2025-11-12 12:49:42 +01:00
4daa21e8bf Started to rewrite the project to handle connection storing and message types, added v4 for uuid crate 2025-11-12 11:28:14 +01:00
Hatvani Tamás
cdd8b45da8 Merge pull request #6 from htamas1210/Engine/movegen-utils
Engine/movegen utils
2025-11-12 11:08:36 +01:00
503cb23015 accepted connection and sending data both ways 2025-11-11 15:25:42 +01:00
Varga Dávid Lajos
6006442f90 added '#[inline(always)]' annotation to hot functions: utils::pop_lsb, utils::pop_msb 2025-11-11 14:23:30 +01:00
Varga Dávid Lajos
eebdfdbee2 implemented utility function: bitboard::utils::notation_from_square_number 2025-11-11 14:21:11 +01:00
Varga Dávid Lajos
a6aba8801e implemented utility function: bitboard::utils::pop_msb 2025-11-11 14:16:33 +01:00
Varga Dávid Lajos
8d1300d7e2 implemented utility function: bitboard::utils::pop_lsb 2025-11-11 14:14:39 +01:00
Varga Dávid Lajos
d0e6ce81ce fixed bad test values in test utils::tests::pop_lsb_test 2025-11-11 14:06:43 +01:00
Varga Dávid Lajos
f3dea86ded fixed typo in test utils::tests::pop_msb_test 2025-11-11 14:05:31 +01:00
Varga Dávid Lajos
092ed19104 added frame and tests for function: bitboard::util::notation_from_square_number 2025-11-11 13:59:53 +01:00
Varga Dávid Lajos
f8894bbdff added frame and tests for function: bitboard::util::pop_msb 2025-11-11 13:51:14 +01:00
Varga Dávid Lajos
258a8a0da9 added frame and tests for function: bitboard::util::pop_lsb 2025-11-11 13:39:26 +01:00
8ffbfdc63f basic listener implemented, addedd modules, working on handling connections 2025-11-11 13:35:34 +01:00
Varga Dávid Lajos
dca6eac3ba added file bitboard/utils.rs 2025-11-11 12:50:53 +01:00
8f9a48fa96 rendszerterv v1 2025-11-11 12:40:29 +01:00
Hatvani Tamás
5ee797e1f8 Add conditional logging for master branch 2025-11-11 12:32:03 +01:00
Hatvani Tamás
eaf3bfa192 Merge pull request #5 from htamas1210/Engine/movegen-lut
Engine/movegen lut
2025-11-11 12:17:48 +01:00
Hatvani Tamás
9c73ca6838 Fix formatting of conditional checks in dispatcher.yml 2025-11-11 11:34:05 +01:00
Hatvani Tamás
0f50f31b13 Update branch checks to be case-sensitive
Refactor branch checks to be case-sensitive for Engine, Server, and UI.
2025-11-11 11:27:42 +01:00
Hatvani Tamás
b6f0b6ee5e Fix conditional syntax in dispatcher.yml 2025-11-11 11:22:21 +01:00
4ae9eea7e2 removed pull_request dispatcher from release workflow 2025-11-11 11:13:45 +01:00
1f368551c1 removed pull_request dispatch from the test files 2025-11-11 11:12:39 +01:00
Hatvani Tamás
4e9f222ddc Allow case-insensitive branch checks in dispatcher.yml 2025-11-11 11:01:38 +01:00
Varga Dávid Lajos
061795a039 implemented the generation of LUT: RAY_TABLE 2025-11-11 10:39:51 +01:00
Varga Dávid Lajos
1af497f063 implemented the generation of LUT: KNIGHT_ATTACK_MAP 2025-11-11 10:33:51 +01:00
Varga Dávid Lajos
b6cdf5b778 implemented the generation of LUT: PAWN_ATTACK_MAP 2025-11-11 10:32:41 +01:00
Varga Dávid Lajos
4eb4bc1348 implemented the generation of LUT: KING_ATTACK_MAPS 2025-11-11 10:25:23 +01:00
Varga Dávid Lajos
c2fe2e968a added masks for use against wrap-around 2025-11-11 10:22:05 +01:00
Varga Dávid Lajos
21f2890b92 added frame and tests for table: RAY_TABLE 2025-11-10 17:17:30 +01:00
Varga Dávid Lajos
5d748b07d2 added frame and tests for table: KNIGHT_ATTACK_MAP 2025-11-10 16:54:14 +01:00
Varga Dávid Lajos
b252f16b2d added frame and tests for table: PAWN_ATTACK_MAP 2025-11-10 15:50:58 +01:00
Varga Dávid Lajos
18f4060df0 added frame and tests for table: KING_ATTACK_MAP 2025-11-10 15:28:15 +01:00
Varga Dávid Lajos
033440fbac added dependency to Cargo.toml: once_cell 2025-11-10 13:18:48 +01:00
Varga Dávid Lajos
bfb2a6db23 added file and module structure for attackmaps.rs 2025-11-10 13:13:03 +01:00
Bence
70d10719e6 Dokumentumok feltöltése a master branch-re 2025-11-10 12:21:27 +01:00
Hatvani Tamás
85f7c6e690 Remove checkout step from upload_data workflow
Removed checkout step from upload job in workflow.
2025-11-09 15:05:10 +01:00
Hatvani Tamás
2ac2f17d1a Fix subprocess by adding decode to file path in upload_data.yml 2025-11-09 15:01:42 +01:00
Hatvani Tamás
7cd4bb6b09 Refactor subprocess call and print statement
Updated subprocess command to use list format and modified print statement for clarity.
2025-11-09 14:55:23 +01:00
1bf4d875af Workflow: added subprocess run to have the correct path for the test log file 2025-11-09 14:48:04 +01:00
Hatvani Tamás
8a01bba644 Update pip install command to break system packages 2025-11-09 14:34:18 +01:00
Hatvani Tamás
cf2edab297 Remove Python setup from upload_data workflow
Removed Python setup step for Google Sheets API.
2025-11-09 14:31:18 +01:00
Hatvani Tamás
5443002c5f Workflow: removed runs-on: self-hosted from test upload job 2025-11-09 14:26:28 +01:00
e19a5aac9a Workflow: Re added test-upload job, added workflow_call trigger 2025-11-09 14:24:24 +01:00
Hatvani Tamás
3515703a90 Modify zip commands in release workflow to include files in the folder with the wildcard
Update zip commands to include all files in the directories.
2025-11-09 13:12:27 +01:00
Hatvani Tamás
a6966e055d Compress build folders for upload
Added compression step for build folders before release.
2025-11-09 13:05:23 +01:00
Bence
1788cce42d Kövspec áttekintés, vágyálom rendszer hozzáadása 2025-11-04 12:01:25 +01:00
Hatvani Tamás
49f4ffc380 Document system requirements for chess application
Added system requirements and specifications for a two-player chess application, detailing functional, client-side, server-side, non-functional requirements, and components.
2025-11-03 18:41:29 +01:00
49 changed files with 11382 additions and 54 deletions

View File

@@ -16,6 +16,9 @@ jobs:
server: ${{ steps.check.outputs.server }}
ui: ${{ steps.check.outputs.ui }}
steps:
- name: checkout repository
uses: actions/checkout@v4
- name: Determine which tests to run
id: check
run: |
@@ -26,13 +29,13 @@ jobs:
SERVER=false
UI=false
if [[ "$BRANCH" == *"Engine"* ]]; then
if [[ "$BRANCH" == *"Engine"* ]] ; then
ENGINE=true
fi
if [[ "$BRANCH" == *"Server"* ]]; then
if [[ "$BRANCH" == *"Server"* ]] ; then
SERVER=true
fi
if [[ "$BRANCH" == *"UI"* ]]; then
if [[ "$BRANCH" == *"UI"* ]] ; then
UI=true
fi
@@ -68,16 +71,13 @@ jobs:
test-data-upload:
needs: [engine, server, ui]
runs-on: self-hosted
if: always()
#uses: ./.github/workflows/upload_data.yml
steps:
- name: Temp turn off
run: |
echo "Uploading data to table"
uses: ./.github/workflows/upload_data.yml
secrets: inherit
release:
needs: test-data-upload
needs: [engine, server, ui]
if: github.ref == 'refs/heads/master'
uses: ./.github/workflows/release.yml
secrets: inherit

View File

@@ -1,7 +1,6 @@
name: Engine Tests
on:
pull_request:
workflow_dispatch:
workflow_call:
@@ -10,8 +9,6 @@ jobs:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Run Engine tests
run: |
bash .github/workflows/test.sh engine/

View File

@@ -1,7 +1,6 @@
name: Release build
on:
pull_request:
workflow_dispatch:
workflow_call:
@@ -13,9 +12,6 @@ jobs:
name: Build and release (master only)
runs-on: self-hosted
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build Engine for Linux
run: |
cd $(git rev-parse --show-toplevel)
@@ -82,16 +78,24 @@ jobs:
cargo build --release --target x86_64-pc-windows-gnu
pwd
cp target/x86_64-pc-windows-gnu/release/ui.exe $(git rev-parse --show-toplevel)/release/windows/ui.exe
- name: Compress build folders for upload
run: |
cd $(git rev-parse --show-toplevel)
pwd
cd release/
zip linux.zip linux/*
zip windows.zip windows/*
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: "v${{ github.run_number }}"
name: "Release v${{ github.run_number }}"
generate_release_notes: true
files: |
release/linux/
release/windows/
release/linux.zip
release/windows.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,7 +1,6 @@
name: Server Tests
on:
pull_request:
workflow_dispatch:
workflow_call:
@@ -10,8 +9,6 @@ jobs:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Run Server tests
run: |
bash .github/workflows/test.sh server/

View File

@@ -30,8 +30,13 @@ echo "$PROJECT_NAME" > "$LOG_FILE"
awk '/^running [0-9]+ test[s]?$/,/^$/' full_test_output.log >> "$LOG_FILE"
# --- APPEND TO GLOBAL LOG (in repo root) ---
if [[ $(git rev-parse --abbrev-ref HEAD) == "master" ]]; then
echo "master" >> $FINAL_LOG
fi
cat "$LOG_FILE" >> "$FINAL_LOG"
# --- SUMMARY ---
echo ">>> Test output extracted to $PROJECT_PATH/$LOG_FILE"
echo ">>> Appended to $FINAL_LOG"
cat $(git rev-parse --show-toplevel)/test_data.log

View File

@@ -1,7 +1,6 @@
name: UI Tests
on:
pull_request:
workflow_dispatch:
workflow_call:
@@ -10,8 +9,6 @@ jobs:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Run UI tests
run: |
bash .github/workflows/test.sh ui/

View File

@@ -2,22 +2,15 @@ name: Upload Test Results to Google Sheets
on:
workflow_dispatch:
workflow_call:
jobs:
upload:
runs-on: self-hosted
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup Python (for Google Sheets API)
uses: actions/setup-python@v5
with:
python-version: '3.x'
steps:
- name: Install dependencies
run: |
pip install gspread google-auth
pip install gspread google-auth --break-system-packages
- name: Upload test_data.log to Google Sheets
env:
@@ -27,31 +20,78 @@ jobs:
echo "$GOOGLE_SERVICE_ACCOUNT_JSON" > service_account.json
python <<'PYCODE'
import gspread, json, time
import gspread, json, subprocess, time
# credentials
creds = json.load(open("service_account.json"))
gc = gspread.service_account_from_dict(creds)
sh = gc.open_by_key("${{ secrets.SPREADSHEET_ID }}")
v = subprocess.run(['git','rev-parse','--show-toplevel'], capture_output=True).stdout.decode().strip()
print(f"{v}/test_data.log")
with open("test_data.log", "r") as f:
def writeRowsToSpreadsheet(data_list, worksheet):
existing_rows = len(worksheet.get_all_values())
start_row = existing_rows + 3
rows_to_append = [row.split() for row in data_list]
print("rows to append")
print(f"{rows_to_append}")
for i, row in enumerate(rows_to_append):
worksheet.insert_row(row, start_row + i)
time.sleep(1)
with open(f"{v}/test_data.log", "r") as f:
lines = [line.strip() for line in f if line.strip()]
isMaster = False
project = lines[0].lower()
worksheet = sh.worksheet(project)
if project == "master":
isMaster = True
engine_data = []
server_data = []
ui_data = []
master_data = []
# project name
data = lines[1:]
for entry in lines:
if not isMaster and entry == "engine":
project = "engine"
elif not isMaster and entry == "server":
project = "server"
elif not isMaster and entry == "ui":
project = "ui"
#blank rows
existing_rows = len(worksheet.get_all_values())
start_row = existing_rows + 3
if project == "engine" and entry != "engine":
engine_data.append(entry)
elif project == "server" and entry != "server":
server_data.append(entry)
elif project == "ui" and entry != "ui":
ui_data.append(entry)
elif project == "master" and entry != "master":
master_data.append(entry)
# Split data into columns (by spaces)
rows_to_append = [row.split() for row in data]
print("PRINTING FILTERED DATA\n\n")
print(f"engine\n{engine_data}")
print(f"server\n{server_data}")
print(f"ui\n{ui_data}")
print(f"master\n{master_data}")
print("\n\n\n")
for i, row in enumerate(rows_to_append):
worksheet.insert_row(row, start_row + i)
if isMaster and len(master_data) != 0:
print("uploading to master tab")
worksheet = sh.worksheet("master")
writeRowsToSpreadsheet(master_data, worksheet)
exit(0)
if len(engine_data) != 0:
print("uploading to engine tab")
writeRowsToSpreadsheet(engine_data, sh.worksheet("engine"))
if len(server_data) != 0:
print("uploading to server tab")
writeRowsToSpreadsheet(server_data, sh.worksheet("server"))
if len(ui_data) != 0:
print("uploading to ui tab")
writeRowsToSpreadsheet(ui_data, sh.worksheet("ui"))
print(f"Uploaded {len(rows_to_append)} rows to '{project}' tab.")
PYCODE

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

View File

@@ -0,0 +1,287 @@
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0" version="28.2.9">
<diagram name="Page-1" id="9d-IwT1pk2NoWoP7MTJv">
<mxGraphModel dx="1534" dy="751" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="9999" pageHeight="9999" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-1" target="IaA3IaIvg4qVMEhu7ya9-36" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-1" value="App megnyitása" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="120" y="50" width="140" height="40" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-10" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-6" target="IaA3IaIvg4qVMEhu7ya9-9" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="540" y="120" />
<mxPoint x="370" y="120" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-12" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-6" target="IaA3IaIvg4qVMEhu7ya9-11" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-13" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-6" target="IaA3IaIvg4qVMEhu7ya9-11" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-14" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-6" target="IaA3IaIvg4qVMEhu7ya9-11" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-15" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-6" target="IaA3IaIvg4qVMEhu7ya9-11" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-6" target="IaA3IaIvg4qVMEhu7ya9-18" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="670" y="180" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-21" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-6" target="IaA3IaIvg4qVMEhu7ya9-20" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-6" value="&lt;div&gt;főmenü&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="480" y="40" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-24" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-9" target="IaA3IaIvg4qVMEhu7ya9-23" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-9" value="Lokális játék hosztolása" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="310" y="210" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-32" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-11" target="IaA3IaIvg4qVMEhu7ya9-31" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-11" value="&lt;div&gt;Lokális játékba csatlakozni&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="460" y="220" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-13" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-18" target="4rzJeeoM2DMZDjvMm2AJ-12" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-18" value="Online jatek&lt;br&gt;(sorba allas)" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="695" y="200" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0;exitDx=0;exitDy=0;entryX=1;entryY=0;entryDx=0;entryDy=0;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-20" target="IaA3IaIvg4qVMEhu7ya9-6" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-28" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-20" target="IaA3IaIvg4qVMEhu7ya9-27" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-3" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="IaA3IaIvg4qVMEhu7ya9-20" target="IaA3IaIvg4qVMEhu7ya9-27">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="IaA3IaIvg4qVMEhu7ya9-20" target="iJwoCWMJ6fIoSaqHo3aH-4">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-6" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="IaA3IaIvg4qVMEhu7ya9-20" target="iJwoCWMJ6fIoSaqHo3aH-4">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="IaA3IaIvg4qVMEhu7ya9-20" target="iJwoCWMJ6fIoSaqHo3aH-7">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="770" y="80" />
<mxPoint x="1000" y="80" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="IaA3IaIvg4qVMEhu7ya9-20" target="iJwoCWMJ6fIoSaqHo3aH-9">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-20" value="&lt;div&gt;Beallitasok&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="650" y="40" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-23" target="4rzJeeoM2DMZDjvMm2AJ-4" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-23" value="&lt;div&gt;Szerver peldany elinditasa&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="130" y="150" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-31" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-25" target="IaA3IaIvg4qVMEhu7ya9-29" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-16" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="IaA3IaIvg4qVMEhu7ya9-25" target="iJwoCWMJ6fIoSaqHo3aH-15">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-25" value="varakozas az ellenfelre" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="130" y="390" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-27" value="lokalis jatekhoz port megvaltoztatasa" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="830" y="10" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-33" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-29" target="4rzJeeoM2DMZDjvMm2AJ-32" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-29" value="&lt;div&gt;jatek elkezdése&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="400" y="850" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-34" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-31" target="IaA3IaIvg4qVMEhu7ya9-33" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-31" value="&lt;div&gt;lokalis halozaton szerver ip:port megadasa&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="460" y="360" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-11" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-33" target="IaA3IaIvg4qVMEhu7ya9-29" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-33" value="csatlakozás" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="290" y="360" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-38" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="IaA3IaIvg4qVMEhu7ya9-36" target="IaA3IaIvg4qVMEhu7ya9-6" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="IaA3IaIvg4qVMEhu7ya9-36" value="user struct letrehozasa a app-ban" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="340" y="40" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-4" target="4rzJeeoM2DMZDjvMm2AJ-7" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-4" value="hostolo kapcsolodik a szerverehez" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="130" y="230" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-7" target="IaA3IaIvg4qVMEhu7ya9-25" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-7" value="eszköz ip cím lekérése a csatlakozáshoz" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="130" y="310" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-21" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-12" target="4rzJeeoM2DMZDjvMm2AJ-20" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-12" value="&lt;div&gt;csatlakozás a központi szerverhez&lt;/div&gt;(tudjuk az ip cimet)" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="690" y="300" width="130" height="70" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-24" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-16" target="4rzJeeoM2DMZDjvMm2AJ-23" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-27" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-16" target="4rzJeeoM2DMZDjvMm2AJ-26" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-16" value="sorban állás szabad játékra várva" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="695" y="480" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-20" target="4rzJeeoM2DMZDjvMm2AJ-16" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-20" value="a szerver eltárolja az uj jatekost mint aktiv felhasznalo a listaban" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="700" y="390" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-23" value="a szerveren bekerultunk a varolistaba" style="ellipse;whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="850" y="465" width="155" height="90" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-29" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-26" target="4rzJeeoM2DMZDjvMm2AJ-28" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-26" value="&lt;div&gt;megjelent meg egy jatekos a varolistan&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="695" y="570" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-30" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-28" target="IaA3IaIvg4qVMEhu7ya9-29" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-28" value="ket jatekos ossze vonasa egy meccsbe. Kivesszuk oket a varolistabol es egy Match structba osszevonjuk oket" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="682.5" y="660" width="145" height="120" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-35" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-32" target="4rzJeeoM2DMZDjvMm2AJ-34" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-32" value="&lt;div&gt;fehér és fekete oldal eldöntése&lt;/div&gt;&lt;div&gt;(szerver által, érme dobással)&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="390" y="950" width="140" height="80" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-37" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-34" target="4rzJeeoM2DMZDjvMm2AJ-36" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-34" value="játék ui megjelenítése" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="400" y="1060" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-39" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-36" target="4rzJeeoM2DMZDjvMm2AJ-38" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-36" value="lehetséges lépések lekérése a tábla állapot alapján a szerver / engine-től" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="390" y="1160" width="140" height="90" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-41" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-38" target="4rzJeeoM2DMZDjvMm2AJ-40" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-38" value="a ui lokalisan eltarolja ezt az adot a következő körig" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="400" y="1280" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-2" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-40" target="iJwoCWMJ6fIoSaqHo3aH-1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-19" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="4rzJeeoM2DMZDjvMm2AJ-40" target="iJwoCWMJ6fIoSaqHo3aH-18">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="4rzJeeoM2DMZDjvMm2AJ-40" value="a jatekos lep a egy babual. Ekkor ellenorizzuk hogy szabad e volt ezt a léppést megtennie" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="390" y="1370" width="140" height="90" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-12" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-1" target="iJwoCWMJ6fIoSaqHo3aH-11">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-14" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-1" target="iJwoCWMJ6fIoSaqHo3aH-13">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-1" value="ha szabad volt elküldjük a lépést a szervernek" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="400" y="1490" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-4" value="felbontás megváltoztatása" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="830" y="90" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-7" value="teljes képernyő" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="1000" y="50" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-9" value="ai api kulcs megadása" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="1000" y="140" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-21" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-11" target="iJwoCWMJ6fIoSaqHo3aH-20">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-11" value="&lt;div&gt;ellenfél ui frissítése&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="400" y="1580" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-13" value="&lt;div&gt;{&lt;/div&gt;&lt;div&gt;from:str,&lt;/div&gt;&lt;div&gt;to: str&lt;/div&gt;&lt;div&gt;}&lt;/div&gt;" style="rhombus;whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="560" y="1455" width="140" height="130" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-15" value="Ip cím megjelenítése a könnyebb csatlakozás érdekében" style="ellipse;whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="20" y="490" width="120" height="150" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-18" value="a ui végig megy az adatokon, amit visszakapott a szervertől a kör elején" style="ellipse;whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="590" y="1290" width="170" height="110" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-23" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-20" target="iJwoCWMJ6fIoSaqHo3aH-22">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-25" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-20" target="iJwoCWMJ6fIoSaqHo3aH-24">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-27" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-20">
<mxGeometry relative="1" as="geometry">
<mxPoint x="640" y="1860" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-20" value="ellenőrizzük, hogy nyert-e a játékos vagy döntetlen lett-e" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="395" y="1670" width="130" height="70" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-22" value="a szerver megkapja a tábla állást és elküldi az enginnek ami ad egy választ" style="ellipse;whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="560" y="1647.5" width="200" height="115" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-26" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-24" target="4rzJeeoM2DMZDjvMm2AJ-36">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="360" y="1820" />
<mxPoint x="360" y="1205" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-24" value="kör átadása az ellenfélnek" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="400" y="1790" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-30" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="iJwoCWMJ6fIoSaqHo3aH-28" target="iJwoCWMJ6fIoSaqHo3aH-29">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-28" value="játék befejezése, nyertes megjelnítése,&amp;nbsp;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="580" y="1860" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="iJwoCWMJ6fIoSaqHo3aH-29" value="a játékosokat vissza küldjük a menübe, a szerver oldalon töröljük a meccset a listából, és töröljük&amp;nbsp; a játékosokat a meccs közben lévő listából" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="560" y="1940" width="160" height="120" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

187
Docs/kovspec.md Normal file
View File

@@ -0,0 +1,187 @@
# Követelmény-specifikáció
## 1. Áttekintés
A jelen dokumentum célja, hogy bemutassa a CastlingCreations megrendelésére készülő Knightly nevű alkalmazás alapvető céljait, funkcionális és nem funkcionális követelményeinek áttekintését, valamint a fejlesztés kontextusát.
A Knightly egy modern, digitális sakkalkalmazás, amely kezdetben helyi hálózaton (LAN) keresztül teszi lehetővé két játékos számára, hogy valós időben mérkőzzenek meg egymással. A rendszer egy grafikus felhasználói felületen keresztül biztosítja a játék indítását, a szerver futtatását és a másik félhez történő csatlakozást.
A projekt hosszú távú célja, hogy a Knightly egy online platformmá fejlődjön, amely hasonló módon működik, mint a népszerű sakkportálok (pl. chess.com): a felhasználók fiókot hozhatnak létre, bejelentkezhetnek, és egy központi szerveren keresztül kereshetnek, illetve indíthatnak mérkőzéseket.
A fejlesztés első szakasza azonban a LAN-alapú verzió megvalósítására koncentrál, amely a sakkjátszma logikai alapjainak, a játékállapot kezelésének és a hálózati kommunikáció modelljének megvalósítását célozza. A későbbi online verzió ezekre az alapokra építkezve bővíthető tovább.
## 2. Vágyálom rendszer
A vágyálom rendszer célja egy teljes körű, modern online sakkplatform létrehozása, amely nemcsak a klasszikus sakkjáték digitális megvalósítását kínálja, hanem egy közösségi, versenyképes és kényelmes felhasználói élményt is biztosít. A hosszú távú cél, hogy a rendszer működése és felépítése a nagyobb nemzetközi sakkoldalakhoz (például a chess.com-hoz vagy a lichess.org-hoz) hasonló legyen, de saját, könnyen kezelhető és letisztult felülettel.
A felhasználók a rendszerben saját profilt hozhatnak létre, amellyel be tudnak jelentkezni, és részt vehetnek online mérkőzéseken más játékosok ellen. A rendszer automatikusan párosítaná őket ellenfelekkel, de lehetőséget adna arra is, hogy barátokat hívjanak meg privát meccsekre. A lejátszott mérkőzések mentésre kerülnének, így a játékosok bármikor visszanézhetnék vagy elemezhetnék azokat.
A játék mellett a felhasználók statisztikákat is láthatnának magukról, például nyerési arányt, aktuális értékszámot (rating), leggyakoribb megnyitásokat, illetve fejlődési tendenciát az idő során. A rendszer ezen felül egy egyszerű chat funkciót is tartalmazna, hogy a játékosok kommunikálhassanak egymással a játszmák közben vagy akár azokon kívül is.
A vágyálom rendszer alapját egy központi szerver képezné, amely kezeli a felhasználói fiókokat, a bejelentkezéseket, a matchmaking folyamatot, valamint a játékok futását és szinkronizálását. A szerver a kliensalkalmazásokkal valós idejű adatkapcsolatot tartana fenn, így a játék során minden lépés azonnal megjelenne a másik játékosnál is.
A platform célja a megbízható és folyamatos működés, akár nagyobb terhelés mellett is. A rendszer fejlesztése során kiemelt szempont lenne a biztonság (adatvédelem, csalás elleni védelem), a stabil hálózati kommunikáció, valamint a bővíthetőség például ranglisták, versenyek vagy mobilalkalmazás későbbi integrálásának lehetősége.
Összességében a vágyálom rendszer egy minden szempontból teljes értékű, közösségorientált sakkalkalmazás lenne, amely a mostani, helyi hálózaton működő változatból fejlődne tovább egy interneten keresztül bárhonnan elérhető platformmá.
## 3. Igényelt funkciók
### Alapok
- Két játékos közti sakkjátszma lebonyolítása.
- Teljes szabályrendszer megvalósítása (érvényes lépések, sakk/sakkmatt/patt felismerése).
- Új játék indítása.
- Játékosok nevének megadása a játszma elején.
- Felhasználóbarát grafikus felület (UI) látható tábla, figurák, órák, státuszjelzések.
- Játékoslépések vizuális kiemelése (pl. kijelölt mező, lehetséges lépések megjelenítése).
- A játék állapotának kijelzése (folyamatban, sakk, matt, döntetlen).
### LAN és hálózati funkciók
- „Szerver indítása” funkció a játékos hostként indíthat egy helyi szervert.
- „Csatlakozás” funkció másik játékos IP-cím alapján tud csatlakozni.
- Helyi hálózaton keresztüli valós idejű kommunikáció.
- LAN játék automatikus felfedezése (broadcast keresés).
- Játék mentése és visszatöltése hálózati módban.
### Online vágyálom funkciók
- Felhasználói regisztráció és bejelentkezés.
- Jelszóval védett fiókok, email- vagy OAuth-alapú hitelesítés (Google, GitHub stb.).
- Profiloldal megtekintése (név, avatar, statisztikák, értékszám).
- Automatikus matchmaking rendszer.
- Kézi játékindítás meghívó küldése barátnak.
- Játszmák mentése és visszajátszása.
- Játszmaelemzés lépések listázása, hibák kiemelése.
- Webes felület vagy mobilalkalmazás támogatása.
- Játék előzményeinek és statisztikáinak megtekintése (győzelmek, vereségek, döntetlenek).
- Automatikus szervermentés és adatbázis szinkronizáció.
### Felhasználói felület
- Letisztult, reszponzív, platformfüggetlen felület (asztali és webes verzió).
- Sötét/világos téma lehetősége.
- Egyéni figurakészlet vagy tábla kinézet választása.
- Animált figuramozgások.
- Egérrel és billentyűzettel is vezérelhető játék.
- Hangjelzések a lépésekhez és az idő lejártához.
- Lépéselőzmények (move list) megjelenítése oldalt.
- Tábla forgatásának lehetősége (pl. a fehér vagy fekete nézőpontból).
- Állapotjelzők (sakk, matt, döntetlen, várakozás az ellenfélre).
- Teljes képernyős mód.
### Technikai / fejlesztői funkciók
- Kliensszerver architektúra.
- REST API vagy WebSocket alapú kommunikáció.
- Adatbázis a felhasználói adatok és meccsek tárolására (pl. SQLite, PostgreSQL, MongoDB).
- Logolási és hibakezelési rendszer.
- Automatikus mentés és adatvisszaállítás.
- Verziókezelés (Git).
- Tesztelhető, moduláris kódszerkezet (külön modulok: logika, UI, hálózat, adat).
- Cross-platform működés (Windows, Linux, esetleg web).
### További funkciók
- Egyszemélyes mód (ember vs. gép, AI-bot).
- Több nehézségi szintű AI.
- Oktató mód (javasolt lépések, hibák magyarázata).
- Hivatalos FEN/PGN formátum export/import.
- Beépített sakkfeladványok, kihívások.
- Érintéses vezérlés mobilon.
- Többnyelvű felület (pl. magyar, angol).
## 4. Rendszer követelmények
A rendszer célja egy kétjátékos sakkalkalmazás megvalósítása, amely alapvetően hálózati kapcsolat (LAN vagy internet) segítségével biztosítja a valós idejű játékot. A rendszer kliensszerver architektúrán alapul, ahol az egyes komponensek jól elkülönülten, meghatározott feladatokat látnak el.
### 4.1 Kötelező funkcionális követelmények
A rendszernek az alábbi alapvető funkciókat mindenképpen biztosítania kell:
- Két játékos közötti sakkjátszma lebonyolítása, a hivatalos sakk szabályai alapján.
- A játékosok felváltva tehetnek lépéseket, a lépések érvényességét a kliens oldali logika ellenőrzi.
- A rendszer valós időben szinkronizálja a két kliens állapotát (mindkét fél ugyanazt a táblát látja).
- A játék vége (sakkmatt, patt, idő lejárta) automatikusan felismerésre kerül.
- A szerver egyidejűleg több játékot is képes kezelni (külön szobákban vagy sessionökben).
- A játékosok elindíthatnak új meccset, illetve befejezett játék után visszatérhetnek a főmenübe.
- A rendszer minden játékban egyedi azonosítót (Game ID) használ a játékállapot nyomon követéséhez.
- A kliens értesítéseket kap az ellenfél lépéseiről és a játék állapotváltozásairól.
### 4.2 Kliens oldali követelmények
A kliens felelős a játékos felhasználói élményéért, a grafikus megjelenítésért és a játéklogika helyi működéséért.
A kliensnek tudnia kell:
- A sakk tábla és a figurák megjelenítése, lépések kezelése (egérkattintás vagy billentyűparancsok).
- Lépések érvényesítése és elküldése a szervernek.
- A szervertől érkező események (ellenfél lépése, állapotváltozás) feldolgozása és megjelenítése.
- Hibák és megszakadt kapcsolat kezelése (újracsatlakozási lehetőség).
- Saját IP vagy szerver cím megadása LAN esetén.
- Alapvető menürendszer (csatlakozás, szerverindítás, új játék, kilépés).
- A hálózati kommunikáció egységes formátumban történjen (pl. JSON alapú üzenetek).
### 4.3 Szerver oldali követelmények
A szerver feladata a kliensek közti kommunikáció kezelése, az állapotok szinkronizálása és a játék logikai integritásának megőrzése.
A szervernek tudnia kell:
- Kapcsolatok fogadása és kezelése több kliens esetén is.
- Új játék (session) létrehozása és azonosító kiosztása.
- Üzenetek továbbítása a kliensek között (pl. lépés, visszajelzés, játék vége).
- A játékállapot naprakészen tartása és küldése mindkét félnek.
- Kapcsolat megszakadása esetén az érintett játék szüneteltetése vagy lezárása.
- Üzenetformátumok ellenőrzése és hibás adatok elutasítása.
- Kliensazonosítás és egyszerű hitelesítés (pl. játékosnév alapján).
- A kommunikáció biztonságos kezelése (üzenetduplikáció, szinkronizációs hibák elkerülése).
### 4.4 A komponensek közti kommunikáció (szerződések)
A rendszer komponensei egy meghatározott üzenetprotokollon keresztül kommunikálnak egymással.
A kommunikáció kétirányú, valós idejű, és az alábbi szerződések szerint zajlik:
A kliens minden lépést csak akkor hajt végre a felhasználói felületen, ha a szervertől visszaigazolást kapott az érvényességéről.
A szerver az üzeneteket sorosítva, FIFO-elv szerint dolgozza fel, és broadcastolja a változásokat az adott játékhoz tartozó összes kliensnek.
### 4.5 Nem funkcionális követelmények
- Megbízhatóság: a rendszernek stabilan kell működnie hálózati késleltetés és csomagvesztés esetén is.
- Teljesítmény: a szerver legalább 10 párhuzamos játékot képes kezelni érezhető lassulás nélkül.
- Biztonság: a kliens csak a szerver által engedélyezett parancsokat hajthatja végre.
- Bővíthetőség: a rendszer felépítése moduláris legyen, hogy később könnyen kiterjeszthető legyen (pl. online matchmaking).
- Platformfüggetlenség: a kliens és a szerver futtatható legyen Windows, Linux és esetleg webes környezetben.
- Karbantarthatóság: kódmodulok (logika, hálózat, UI) elkülönítése, jól dokumentált interfészekkel.
### 4.6 Minimális technikai elvárások
- Programozási nyelv: Rust, C#, Python vagy más, hálózati alkalmazásokra alkalmas nyelv.
- Kommunikációs protokoll: TCP vagy WebSocket alapú kapcsolat.
- Adatcsere formátum: JSON.
- Grafikus felület: desktop GUI (pl. egérvezérlés, drag & drop lépés).
- Követelmény kliensoldalon: legalább 4 GB RAM, modern operációs rendszer.
- Követelmény szerveroldalon: 1 CPU mag, 512 MB RAM, állandó hálózati kapcsolat.
## 5. Követelménylista
### Szerver
| Név | Verzió | Leírás |
| --- | ------ | ------ |
| **WebSocket** | 1.0 | A szerver és a kliens között folyamatos kétirányú kommunikációt biztosít. A kapcsolat létrejötte után a szerver valós időben képes fogadni és továbbítani az eseményeket (pl. lépés végrehajtása, állapotfrissítés). Hiba esetén a kapcsolat automatikusan újraépül. |
| **Kapcsolatok csoportosítása** | 1.0 | A szerver figyeli az elérhető, szabad klienseket, majd két szabad kapcsolatot automatikusan összerendel egy meccsbe. A csoportosítás után a játékosok azonos „room”-ba kerülnek, és a szerver biztosítja az egymás közötti adatkommunikációt. |
| **Kommunikáció az engine-nel** | 1.0 | A szerver a játékosoktól érkező lépéseket és játékinformációkat továbbítja az engine-nek feldolgozásra. Az engine válasza után a szerver visszaküldi az eredményt a klienseknek (pl. érvényes lépés, matt, patt). A kommunikáció aszinkron módon zajlik, válaszidő-ellenőrzéssel. |
| **Kommunikáció a UI-al** | 1.0 | A szerver WebSocket-en keresztül adatokat továbbít a felhasználói felület és az engine között. A UI által kért műveletek (pl. új meccs létrehozása, állapotlekérés) feldolgozását a szerver közvetíti.|
### Engine
| Név | Verzió | Leírás |
| --- | ------ | ------ |
| **Bitboard** | 1.0 | A játék táblaállapotát bitműveletekkel reprezentálja a hatékonyság érdekében. Minden bábu típus és szín külön bitmask-on kerül tárolásra, lehetővé téve a gyors lekérdezéseket és lépésellenőrzéseket. |
| **Lépésgenerálás LUT** | 1.0 | Előre kiszámított lookup táblák segítségével gyorsítja a lépésgenerálást és szabályellenőrzést. Ez csökkenti a számítási időt, és optimalizálja az engine teljesítményét. |
| **Lépésgenerálás** | 1.0 | A különböző bábutípusok (gyalog, bástya, futó, stb.) lépési logikáját valósítja meg. A függvények ellenőrzik a lépés érvényességét, figyelembe véve az aktuális állást, sakkhelyzetet és speciális szabályokat (pl. sáncolás, en passant). |
| **Util függvények** | 1.0 | Segédfüggvények az engine belső működéséhez, például raycast műveletek, bitműveleti maszkok kezelése, valamint logikai ellenőrzések a lépések és ütések számításához. |
### UI
| Név | Verzió | Leírás |
| --- | ------ | ------ |
| **Belépés** | 1.0 | A felhasználó a kezdőképernyőn keresztül adhatja meg a nevét lokális játékhoz, vagy hitelesítheti magát online játékmód esetén. Hibás adatok esetén a rendszer figyelmeztetést küld. |
| **Főmenü** | 1.0 | Az alkalmazás központi navigációs felülete, ahol a felhasználó meccset kereshet, új játékot indíthat lokálisan, vagy beállításokat módosíthat. A menü megjeleníti az aktuális státuszt (online/offline). |
| **Játék** | 1.0 | A játékfelület megjeleníti a táblát, bábukat, lépéseket, és az aktuális játékállást. Támogatja mind az online, mind a lokális módot. A felület kezeli az interakciókat (lépéskattintás, visszavonás, végeredmény kijelzés). |
| **Kommunikáció a szerverrel** | 1.0 | A kliens a szerveren keresztül kommunikál az engine-nel. A UI felel az üzenetek küldéséért (lépés, új játék, visszajelzés), valamint a szervertől kapott események vizuális megjelenítéséért. |
### GitHub Actions (CI/CD)
| Név | Leírás |
| --- | ------ |
| Folyamatos tesztelés | A projekt minden commit után automatikusan tesztelődik. A pipeline lefuttatja a teszteket, és értesítést küld hibás build esetén. |
| Folyamatos integráció | Az új funkciók beolvadásakor a rendszer automatikusan integrálja a változtatásokat, új buildet hoz létre, és frissíti a fejlesztői környezetet. |
| Tesztadatok | A tesztadatok legyenek elérhetőek egy táblázatban, dátummal ellátva. (Google Sheets) |

View File

@@ -4,3 +4,6 @@ version = "0.1.0"
edition = "2024"
[dependencies]
once_cell = "1.19"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

11
engine/src/bitboard.rs Normal file
View File

@@ -0,0 +1,11 @@
mod attackmaps;
mod utils;
mod legality;
mod checkinfo;
mod attacks;
mod movegen;
pub mod board;
pub(in super) mod bitmove;
pub(in super) mod movebuffer;
pub(in super) mod makemove;

View File

@@ -0,0 +1,310 @@
use once_cell::sync::Lazy;
const A_FILE: u64 = 0x0101_0101_0101_0101;
const H_FILE: u64 = 0x8080_8080_8080_8080;
const AB_FILE: u64 = 0x0303_0303_0303_0303;
const GH_FILE: u64 = 0xC0C0_C0C0_C0C0_C0C0;
/*
EXPLANATIONS:
> square_index: 8 * rank number + file number (a-h = 0-7)
> side: white = 0, black = 1
> direction_index: 0..8 = [E, NE, N, NW, W, SW, S, SE]
*/
// KING_ATTACK_MAP[<square_index>]
pub static KING_ATTACK_MAP: Lazy<[u64; 64]> = Lazy::new(|| {
let mut table: [u64; 64] = [0u64; 64];
for sq in 0..64 {
let king: u64 = 1 << sq;
let left_attacks: u64 = king << 7 | king >> 1 | king >> 9;
let right_attacks: u64 = king << 1 | king << 9 | king >> 7;
table[sq] = (left_attacks & !H_FILE) | (right_attacks & !A_FILE) | king << 8 | king >> 8;
}
return table;
});
// PAWN_ATTACK_MAP[<square_index>][<side>]
pub static PAWN_ATTACK_MAP: Lazy<[[u64; 2]; 64]> = Lazy::new(|| {
let mut table: [[u64; 2]; 64] = [[0u64; 2]; 64];
for sq in 0..64 {
let pawn: u64 = 1 << sq;
table[sq][0] |= (pawn << 9) & !A_FILE;
table[sq][0] |= (pawn << 7) & !H_FILE;
}
for sq in 0..64 {
let pawn: u64 = 1 << sq;
table[sq][1] |= (pawn >> 9) & !H_FILE;
table[sq][1] |= (pawn >> 7) & !A_FILE;
}
return table;
});
// KNIGHT_ATTACK_MAP[<square_index>]
pub static KNIGHT_ATTACK_MAP: Lazy<[u64; 64]> = Lazy::new(|| {
let mut table: [u64; 64] = [0u64; 64];
for sq in 0..64 {
let knight: u64 = 1 << sq;
let far_left_attacks: u64 = knight << 6 | knight >> 10;
let near_left_attacks: u64 = knight << 15 | knight >> 17;
let far_right_attacks: u64 = knight << 10 | knight >> 6;
let near_right_attacks: u64 = knight << 17 | knight >> 15;
table[sq] = (far_left_attacks & !GH_FILE) | (far_right_attacks & !AB_FILE) | (near_left_attacks & !H_FILE) | (near_right_attacks & !A_FILE);
}
return table;
});
// RAY_TABLE[<square_index>][<direction_index>]
pub static RAY_TABLE: Lazy<[[u64; 8]; 64]> = Lazy::new(|| {
let mut table: [[u64; 8]; 64] = [[0u64; 8]; 64];
let dirs: [i8; 8] = [1, 9, 8, 7, -1, -9, -8, -7];
for sq in 0..64 {
for d in 0..8 {
let mut ray: u64 = 0u64;
let origin: u64 = 1 << sq;
let mut new_target: u64 = if dirs[d] > 0 {origin << dirs[d]} else {origin >> -dirs[d]};
if [0, 1, 7].contains(&d) {
new_target &= !A_FILE;
}
else if [3, 4, 5].contains(&d) {
new_target &= !H_FILE;
}
while new_target != 0 {
ray |= new_target;
new_target = if dirs[d] > 0 {new_target << dirs[d]} else {new_target >> -dirs[d]};
if [0, 1, 7].contains(&d) {
new_target &= !A_FILE;
}
else if [3, 4, 5].contains(&d) {
new_target &= !H_FILE;
}
}
table[sq][d] = ray;
}
}
return table;
});
// ROOK_MOVE_MASK[<square_index>]
pub static ROOK_MOVE_MASK: Lazy<[u64; 64]> = Lazy::new(|| {
let mut table = [0u64; 64];
for sq in 0..64 {
for dir in [0, 2, 4, 6] {
table[sq] |= RAY_TABLE[sq][dir];
}
}
table
});
// BISHOP_MOVE_MASK[<square_index>]
pub static BISHOP_MOVE_MASK: Lazy<[u64; 64]> = Lazy::new(|| {
let mut table = [0u64; 64];
for sq in 0..64 {
for dir in [1, 3, 5, 7] {
table[sq] |= RAY_TABLE[sq][dir];
}
}
table
});
// KING_SAFETY_ROOK_MASK[<square_index>]
pub static KING_SAFETY_ROOK_MASK: Lazy<[u64; 64]> = Lazy::new(|| {
let mut table = [0u64; 64];
for sq in 0..64 {
let mut mask = KING_ATTACK_MAP[sq];
while mask != 0 {
let next_sq = mask.trailing_zeros();
table[sq] |= ROOK_MOVE_MASK[next_sq as usize];
mask &= !(1 << next_sq);
}
}
table
});
// KING_SAFETY_BISHOP_MASK[<square_index>]
pub static KING_SAFETY_BISHOP_MASK: Lazy<[u64; 64]> = Lazy::new(|| {
let mut table = [0u64; 64];
for sq in 0..64 {
let mut mask = KING_ATTACK_MAP[sq];
while mask != 0 {
let next_sq = mask.trailing_zeros();
table[sq] |= BISHOP_MOVE_MASK[next_sq as usize];
mask &= !(1 << next_sq);
}
}
table
});
// <----- TESTS ----->
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_king_attack_map() {
// test setup for corners [SW, SE, NW, NE]
let corner_indexes: [usize; 4] = [0, 7, 56, 63];
let corner_attack_maps: [u64; 4] = [
(1u64 << 1) | (1u64 << 8) | (1u64 << 9),
(1u64 << 6) | (1u64 << 14) | (1u64 << 15),
(1u64 << 48) | (1u64 << 49) | (1u64 << 57),
(1u64 << 54) | (1u64 << 55) | (1u64 << 62)
];
// tests for corners
for index in 0..4 {
assert_eq!(KING_ATTACK_MAP[corner_indexes[index]], corner_attack_maps[index]);
}
// test setup for sides [S, E, W, N]
let side_indexes: [usize; 4] = [3, 31, 32, 60];
let side_attack_maps: [u64; 4] = [
(1 << 2) | (1 << 4) | (1 << 10) | (1 << 11) | (1 << 12),
(1 << 22) | (1 << 23) | (1 << 30) | (1 << 38) | (1 << 39),
(1 << 24) | (1 << 25) | (1 << 33) | (1 << 40) | (1 << 41),
(1 << 51) | (1 << 52) | (1 << 53) | (1 << 59) | (1 << 61)
];
// tests for sides
for index in 0..4 {
assert_eq!(KING_ATTACK_MAP[side_indexes[index]], side_attack_maps[index]);
}
// test setup for center
let center_index: usize = 27;
let center_attack_map: u64 = (1 << 18) | (1 << 19) | (1 << 20) | (1 << 26) | (1 << 28) | (1 << 34) | (1 << 35) | (1 << 36);
// test for center
assert_eq!(KING_ATTACK_MAP[center_index], center_attack_map);
}
#[test]
fn test_pawn_attack_map() {
// test setup for white sides
let white_side_indexes: [usize; 2] = [24, 31];
let white_side_attack_maps: [u64; 2] = [
(1 << 33),
(1 << 38)
];
// tests for white sides
for index in 0..2 {
assert_eq!(PAWN_ATTACK_MAP[white_side_indexes[index]][0], white_side_attack_maps[index])
}
// test setup for black sides
let black_side_indexes: [usize; 2] = [32, 39];
let black_side_attack_maps: [u64; 2] = [
(1 << 25),
(1 << 30)
];
// tests for black sides
for index in 0..2 {
assert_eq!(PAWN_ATTACK_MAP[black_side_indexes[index]][1], black_side_attack_maps[index])
}
// test setup for white center
let white_center_indexes: [usize; 2] = [11, 12];
let white_center_attack_maps: [u64; 2] = [
(1 << 18) | (1 << 20),
(1 << 19) | (1 << 21)
];
// tests for white center
for index in 0..2 {
assert_eq!(PAWN_ATTACK_MAP[white_center_indexes[index]][0], white_center_attack_maps[index])
}
// test setup for black center
let black_center_indexes: [usize; 2] = [51, 52];
let black_center_attack_maps: [u64; 2] = [
(1 << 42) | (1 << 44),
(1 << 43) | (1 << 45)
];
// tests for black center
for index in 0..2 {
assert_eq!(PAWN_ATTACK_MAP[black_center_indexes[index]][1], black_center_attack_maps[index])
}
}
#[test]
fn test_knight_attack_map() {
// test setup for corners [SW, SE, NW, NE]
let corner_indexes: [usize; 4] = [0, 7, 56, 63];
let corner_attack_maps: [u64; 4] = [
(1 << 17) | (1 << 10),
(1 << 13) | (1 << 22),
(1 << 41) | (1 << 50),
(1 << 46) | (1 << 53)
];
// tests for corners
for index in 0..4 {
assert_eq!(KNIGHT_ATTACK_MAP[corner_indexes[index]], corner_attack_maps[index]);
}
// test setup for sides [S, E, W, N]
let side_indexes: [usize; 4] = [3, 31, 32, 60];
let side_attack_maps: [u64; 4] = [
(1 << 9) | (1 << 13) | (1 << 18) | (1 << 20),
(1 << 14) | (1 << 21) | (1 << 37) | (1 << 46),
(1 << 17) | (1 << 26) | (1 << 42) | (1 << 49),
(1 << 43) | (1 << 45) | (1 << 50) | (1 << 54)
];
// tests for sides
for index in 0..4 {
assert_eq!(KNIGHT_ATTACK_MAP[side_indexes[index]], side_attack_maps[index]);
}
// test setup for center
let center_index: usize = 27;
let center_attack_map: u64 = (1 << 10) | (1 << 12) | (1 << 17) | (1 << 21) | (1 << 33) | (1 << 37) | (1 << 42) | (1 << 44);
// test for center
assert_eq!(KNIGHT_ATTACK_MAP[center_index], center_attack_map);
}
#[test]
fn test_ray_table() {
// test setup for all directions from center
let starting_square_index: usize = 27;
let ray_masks: [u64; 8] = [
(1 << 28) | (1 << 29) | (1 << 30) | (1 << 31),
(1 << 36) | (1 << 45) | (1 << 54) | (1 << 63),
(1 << 35) | (1 << 43) | (1 << 51) | (1 << 59),
(1 << 34) | (1 << 41) | (1 << 48),
(1 << 26) | (1 << 25) | (1 << 24),
(1 << 18) | (1 << 9) | (1 << 0),
(1 << 19) | (1 << 11) | (1 << 3),
(1 << 20) | (1 << 13) | (1 << 6)
];
// tests for all directions from starting_square
for direction in 0..8 {
assert_eq!(RAY_TABLE[starting_square_index][direction], ray_masks[direction]);
}
}
}

View File

@@ -0,0 +1,196 @@
use super::board::Board;
use super::attackmaps::*;
impl Board {
const RANK_2: u64 = 0x0000_0000_0000_FF00;
const RANK_7: u64 = 0x00FF_0000_0000_0000;
const A_FILE: u64 = 0x0101_0101_0101_0101;
const H_FILE: u64 = 0x8080_8080_8080_8080;
pub fn get_pseudo_pawn_moves(&self, sq: u32) -> u64 {
let pawn: u64 = 1 << sq;
let mut move_mask: u64 = 0u64;
let move_offset: i8 = 8 - 16 * self.side_to_move as i8;
let next_sq: u64 = if move_offset > 0 {pawn << move_offset} else {pawn >> -move_offset};
if (self.occupancy[2] & next_sq) == 0 {
move_mask |= next_sq;
if (self.side_to_move == 0 && pawn & Self::RANK_2 != 0)
|| (self.side_to_move == 1 && pawn & Self::RANK_7 != 0) {
let next_sq: u64 = if move_offset > 0 {next_sq << move_offset} else {next_sq >> -move_offset};
if (self.occupancy[2] & next_sq) == 0 {
move_mask |= next_sq;
}
}
}
return move_mask;
}
#[inline]
pub fn get_pseudo_knight_moves(&self, sq: u32) -> u64 {
return KNIGHT_ATTACK_MAP[sq as usize];
}
#[inline]
pub fn get_pseudo_king_moves(&self, sq: u32) -> u64 {
return KING_ATTACK_MAP[sq as usize];
}
#[inline]
pub fn get_pseudo_pawn_captures(&self, sq: u32) -> u64 {
return PAWN_ATTACK_MAP[sq as usize][self.side_to_move as usize];
}
#[inline]
pub fn get_pseudo_opponent_pawn_captures(&self, sq: u32) -> u64 {
return PAWN_ATTACK_MAP[sq as usize][1 - self.side_to_move as usize];
}
#[inline]
pub fn get_pseudo_bishop_moves(&self, sq: u32) -> u64 {
let mut moves = 0u64;
let sq = sq as usize;
let occupancy = self.occupancy[2];
moves |= get_raycast_from_square_in_direction(occupancy, sq, 1);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 3);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 5);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 7);
return moves;
}
#[inline]
pub fn get_pseudo_rook_moves(&self, sq: u32) -> u64 {
let mut moves: u64 = 0u64;
let occupancy = self.occupancy[2];
let sq = sq as usize;
moves |= get_raycast_from_square_in_direction(occupancy, sq, 0);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 2);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 4);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 6);
return moves;
}
#[inline(always)]
pub fn get_pseudo_queen_moves(&self, sq: u32) -> u64 {
return self.get_pseudo_bishop_moves(sq) | self.get_pseudo_rook_moves(sq);
}
#[inline]
pub fn get_pseudo_bishop_moves_ignore_king(&self, sq: u32) -> u64 {
let mut moves = 0u64;
let sq = sq as usize;
let king = self.bitboards[5 + 6*self.side_to_move as usize];
let occupancy = self.occupancy[2] & !king;
moves |= get_raycast_from_square_in_direction(occupancy, sq, 1);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 3);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 5);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 7);
return moves;
}
#[inline]
pub fn get_pseudo_rook_moves_ignore_king(&self, sq: u32) -> u64 {
let mut moves: u64 = 0u64;
let sq = sq as usize;
let king = self.bitboards[5 + 6*self.side_to_move as usize];
let occupancy = self.occupancy[2] & !king;
moves |= get_raycast_from_square_in_direction(occupancy, sq, 0);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 2);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 4);
moves |= get_raycast_from_square_in_direction(occupancy, sq, 6);
return moves;
}
#[inline]
pub fn is_square_attacked(&self, king_sq: u32) -> bool {
let offset: usize = 6 * self.side_to_move as usize;
// rook-queen checks (+)
let mut threat_mask: u64 = self.get_pseudo_rook_moves(king_sq);
let mut attacker_mask: u64 = self.bitboards[10 - offset] | self.bitboards[9 - offset];
if threat_mask & attacker_mask != 0 { return true; }
// bishop-queen checks (x)
threat_mask = self.get_pseudo_bishop_moves(king_sq);
attacker_mask = self.bitboards[10 - offset] | self.bitboards[8 - offset];
if threat_mask & attacker_mask != 0 { return true; }
// knight checks (L)
threat_mask = KNIGHT_ATTACK_MAP[king_sq as usize];
attacker_mask = self.bitboards[7 - offset];
if threat_mask & attacker_mask != 0 { return true; }
// pawn checks (v)
threat_mask = PAWN_ATTACK_MAP[king_sq as usize][self.side_to_move as usize];
attacker_mask = self.bitboards[6 - offset];
return threat_mask & attacker_mask != 0;
}
pub fn get_safe_king_squares(&self) -> u64 {
let offset: usize = 6 * (1 - self.side_to_move as usize);
let king_sq = self.bitboards[11 - offset].trailing_zeros() as usize;
let bishop_mask = KING_SAFETY_BISHOP_MASK[king_sq];
let rook_mask = KING_SAFETY_ROOK_MASK[king_sq];
let mut attack_map: u64 = 0u64;
let mut board: u64 = self.bitboards[offset];
if self.side_to_move() == 0 {
attack_map |= (board >> 9 & !Self::H_FILE) | (board >> 7 & !Self::A_FILE);
}
else {
attack_map |= (board << 9 & !Self::A_FILE) | (board << 7 & !Self::H_FILE);
}
board = self.bitboards[offset + 1];
while board != 0 {
let piece_sq: u32 = board.trailing_zeros();
board &= !(1 << piece_sq);
attack_map |= self.get_pseudo_knight_moves(piece_sq);
}
board = self.bitboards[offset + 2] & bishop_mask;
while board != 0 {
let piece_sq: u32 = board.trailing_zeros();
board &= !(1 << piece_sq);
attack_map |= self.get_pseudo_bishop_moves_ignore_king(piece_sq);
}
board = self.bitboards[offset + 3] & rook_mask;
while board != 0 {
let piece_sq: u32 = board.trailing_zeros();
board &= !(1 << piece_sq);
attack_map |= self.get_pseudo_rook_moves_ignore_king(piece_sq);
}
board = self.bitboards[offset + 4] & (bishop_mask | rook_mask);
while board != 0 {
let piece_sq: u32 = board.trailing_zeros();
board &= !(1 << piece_sq);
attack_map |= self.get_pseudo_rook_moves_ignore_king(piece_sq) | self.get_pseudo_bishop_moves_ignore_king(piece_sq);
}
board = self.bitboards[offset + 5];
let piece_sq: u32 = board.trailing_zeros();
attack_map |= self.get_pseudo_king_moves(piece_sq);
return !attack_map;
}
}
#[inline(always)]
pub fn get_raycast_from_square_in_direction(occupancy: u64, sq: usize, dir: usize) -> u64 {
let is_up: bool = dir / 4 == 0;
let mut ray: u64 = RAY_TABLE[sq][dir];
let blockers: u64 = occupancy & ray;
if blockers != 0 {
let first_blocker: u32 = if is_up { blockers.trailing_zeros() } else { 63 - blockers.leading_zeros() };
ray &= !RAY_TABLE[first_blocker as usize][dir];
}
return ray;
}

View File

@@ -0,0 +1,77 @@
use super::utils::*;
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct BitMove {
move_type: BitMoveType,
from_square: u8,
to_square: u8,
promotion_piece: Option<u8>
}
impl BitMove {
#[inline]
pub fn quiet(from: u8, to: u8, promotion_piece: Option<u8>) -> Self {
return Self {
move_type: BitMoveType::Quiet,
from_square: from,
to_square: to,
promotion_piece: promotion_piece
};
}
#[inline]
pub fn capture(from: u8, to: u8, promotion_piece: Option<u8>) -> Self {
return Self {
move_type: BitMoveType::Capture,
from_square: from,
to_square: to,
promotion_piece: promotion_piece
};
}
#[inline]
pub fn castle(from: u8, to: u8) -> Self {
return Self {
move_type: BitMoveType::Castle,
from_square: from,
to_square: to,
promotion_piece: None
};
}
#[inline(always)]
pub fn move_type(&self) -> BitMoveType {
return self.move_type;
}
#[inline(always)]
pub fn from_square(&self) -> u8 {
return self.from_square;
}
#[inline(always)]
pub fn to_square(&self) -> u8 {
return self.to_square;
}
#[inline(always)]
pub fn promotion_piece(&self) -> Option<u8> {
return self.promotion_piece;
}
pub fn uci_notation(&self) -> String {
let mut notation = notation_from_square_number(self.from_square());
notation.push_str(&notation_from_square_number(self.to_square()));
if let Some(promotion_piece) = self.promotion_piece {
notation.push(get_character_by_piece_id(promotion_piece).to_ascii_lowercase());
}
return notation;
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum BitMoveType {
Quiet,
Capture,
Castle,
EnPassant
}

View File

@@ -0,0 +1,295 @@
use crate::bitboard::utils::notation_from_square_number;
use super::utils::try_get_square_number_from_notation;
pub struct Board {
pub(in super) bitboards: [u64; 12], // 0-5 -> white pieces (P, N, B, R, Q, K), 6-11 -> black pieces (p, n, b, r, q, k)
pub(in super) piece_board: [u8; 64], // same as board indexes, 12 -> empty square
pub(in super) occupancy: [u64; 3], // 0 -> white, 1 -> black, 2 -> combined
pub(in super) castling_rights: u8, // 0b0000_KQkq
pub(in super) pinned_squares: [u8; 64], // 0 -> E-W, 1 -> NE-SW, 2 -> N-S, 3 -> SE-NW, 4 -> no pin
pub(in super) pin_mask: u64, // 1 -> pin, 0 -> no pin
pub(in super) en_passant_square: u64, // 1 -> ep square, 0 -> no ep square
pub(in super) side_to_move: u8 // 0 -> white to play, 1 -> black to play
}
impl Board {
pub const EMPTY_SQUARE: u8 = 12;
pub fn new_clear() -> Self {
let mut bit_board: Self = Self {
bitboards: [0x0000_0000_0000_0000; 12],
piece_board: [12; 64],
occupancy: [0x0000_0000_0000_0000; 3],
castling_rights: 0b0000_0000,
pinned_squares: [4; 64],
pin_mask: 0u64,
en_passant_square: 0x0000_0000_0000_0000,
side_to_move: 0
};
return bit_board;
}
pub fn new() -> Self {
let mut bit_board: Board = Self {
bitboards: [0x0000_0000_0000_FF00,
0x0000_0000_0000_0042,
0x0000_0000_0000_0024,
0x0000_0000_0000_0081,
0x0000_0000_0000_0008,
0x0000_0000_0000_0010,
0x00FF_0000_0000_0000,
0x4200_0000_0000_0000,
0x2400_0000_0000_0000,
0x8100_0000_0000_0000,
0x0800_0000_0000_0000,
0x1000_0000_0000_0000],
piece_board: [12; 64],
occupancy: [0; 3],
castling_rights: 0b0000_1111,
pinned_squares: [4; 64],
pin_mask: 0u64,
en_passant_square: 0x0000_0000_0000_0000,
side_to_move: 0
};
bit_board.calc_occupancy();
bit_board.calc_piece_board();
return bit_board;
}
pub fn build(fen: &str) -> Self {
let mut board: Board = Board::new_clear();
let mut col: i32 = 0;
let mut row: i32 = 7;
let pieces: [char; 12] = ['p', 'n', 'b', 'r', 'q', 'k', 'P', 'N', 'B', 'R', 'Q', 'K'];
let mut coming_up: &str = fen;
for (i, c) in coming_up.chars().enumerate() {
if pieces.contains(&c) {
board.place_piece(row*8 + col, c);
col += 1;
}
else if ('1'..='8').contains(&c) {
col += c.to_string().parse::<i32>().unwrap();
}
else if c == '/' {
row -= 1;
col = 0;
}
else {
coming_up = &coming_up[i+1..];
break;
}
}
board.calc_occupancy();
match coming_up.chars().next().unwrap() {
'w' => board.side_to_move = 0,
'b' => board.side_to_move = 1,
_ => panic!("invalid fen notation / to be handled later")
}
coming_up = &coming_up[2..];
for (i, c) in coming_up.chars().enumerate() {
match c {
'K' => board.castling_rights |= 1 << 3,
'Q' => board.castling_rights |= 1 << 2,
'k' => board.castling_rights |= 1 << 1,
'q' => board.castling_rights |= 1,
'-' => {
coming_up = &coming_up[i+2..];
break;
}
_ => {
coming_up = &coming_up[i+1..];
break;
}
}
}
match coming_up.chars().next().unwrap() {
'-' => {
coming_up = &coming_up[1..];
}
_ => {
let notation = coming_up.split(' ').next().unwrap();
if let Ok(epsq_index) = try_get_square_number_from_notation(notation) {
board.en_passant_square = 1 << epsq_index;
}
}
}
board.calc_pinned_squares();
board.calc_piece_board();
return board;
}
#[inline(always)]
pub fn bitboards(&self, index: usize) -> u64 {
return self.bitboards[index];
}
#[inline(always)]
pub fn piece_board(&self, sq: u8) -> u8 {
return self.piece_board[sq as usize];
}
#[inline(always)]
pub fn occupancy(&self, side: usize) -> u64 {
return self.occupancy[side];
}
#[inline(always)]
pub fn castling_rights(&self) -> u8 {
return self.castling_rights;
}
#[inline(always)]
pub fn pinned_squares(&self, sq: usize) -> u8 {
return self.pinned_squares[sq];
}
#[inline(always)]
pub fn pin_mask(&self) -> u64 {
return self.pin_mask;
}
#[inline(always)]
pub fn en_passant_square(&self) -> u64 {
return self.en_passant_square;
}
#[inline(always)]
pub fn side_to_move(&self) -> u8 {
return self.side_to_move;
}
#[inline(always)]
pub fn current_king_square(&self) -> u32 {
return if self.side_to_move == 0 { self.bitboards[5].trailing_zeros() } else { self.bitboards[11].trailing_zeros() };
}
pub fn fen(&self) -> String {
let mut fen = String::new();
for row in (0..8).rev() {
let mut empty = 0;
for col in 0..8 {
let sq = row * 8 + col;
if let Some(piece) = self.get_piece_character(sq) {
if empty > 0 {
fen.push_str(&empty.to_string());
empty = 0;
}
fen.push(piece);
} else {
empty += 1;
if col == 7 {
fen.push_str(&empty.to_string());
}
}
}
if row > 0 {
fen.push('/');
}
}
fen.push(' ');
if self.side_to_move() == 0 { fen.push('w'); } else { fen.push('b'); }
fen.push(' ');
if self.castling_rights() == 0 {
fen.push('-');
} else {
if self.castling_rights() & (1 << 3) != 0 { fen.push('K'); }
if self.castling_rights() & (1 << 2) != 0 { fen.push('Q'); }
if self.castling_rights() & (1 << 1) != 0 { fen.push('k'); }
if self.castling_rights() & (1 << 0) != 0 { fen.push('q'); }
}
fen.push(' ');
if self.en_passant_square() == 0 {
fen.push('-');
} else {
let sq = self.en_passant_square().trailing_zeros();
fen.push_str(&notation_from_square_number(sq as u8));
}
fen.push_str(" 0 1");
return fen;
}
fn calc_occupancy(&mut self) {
self.occupancy = [0u64; 3];
for b in 0..6 {
self.occupancy[0] |= self.bitboards[b];
}
for b in 6..12 {
self.occupancy[1] |= self.bitboards[b];
}
self.occupancy[2] = self.occupancy[0] | self.occupancy[1];
}
fn calc_piece_board(&mut self) {
for sq in 0..64 {
for b in 0..12 {
if (self.bitboards[b as usize] & 1 << sq) != 0 {
self.piece_board[sq] = b;
}
}
}
}
pub fn place_piece(&mut self, sq: i32, piece: char) {
match piece {
'p' => {self.bitboards[6] |= 1 << sq}
'n' => {self.bitboards[7] |= 1 << sq}
'b' => {self.bitboards[8] |= 1 << sq}
'r' => {self.bitboards[9] |= 1 << sq}
'q' => {self.bitboards[10] |= 1 << sq}
'k' => {self.bitboards[11] |= 1 << sq}
'P' => {self.bitboards[0] |= 1 << sq}
'N' => {self.bitboards[1] |= 1 << sq}
'B' => {self.bitboards[2] |= 1 << sq}
'R' => {self.bitboards[3] |= 1 << sq}
'Q' => {self.bitboards[4] |= 1 << sq}
'K' => {self.bitboards[5] |= 1 << sq}
_ => ()
}
}
pub fn get_piece_character(&self, index: i32) -> Option<char> {
let sq = 1 << index;
if (self.bitboards[0] & sq) != 0 {
return Some('P');
}
if (self.bitboards[1] & sq) != 0 {
return Some('N');
}
if (self.bitboards[2] & sq) != 0 {
return Some('B');
}
if (self.bitboards[3] & sq) != 0 {
return Some('R');
}
if (self.bitboards[4] & sq) != 0 {
return Some('Q');
}
if (self.bitboards[5] & sq) != 0 {
return Some('K');
}
if (self.bitboards[6] & sq) != 0 {
return Some('p');
}
if (self.bitboards[7] & sq) != 0 {
return Some('n');
}
if (self.bitboards[8] & sq) != 0 {
return Some('b');
}
if (self.bitboards[9] & sq) != 0 {
return Some('r');
}
if (self.bitboards[10] & sq) != 0 {
return Some('q');
}
if (self.bitboards[11] & sq) != 0 {
return Some('k');
}
return None;
}
}

View File

@@ -0,0 +1,21 @@
pub struct CheckInfo {
pub check_count: u8,
pub move_mask: u64
}
impl CheckInfo {
pub fn new() -> Self {
return Self {
check_count: 0,
move_mask: 0xFFFF_FFFF_FFFF_FFFF
}
}
#[inline(always)]
pub fn add_checker(&mut self, move_mask: u64) {
self.move_mask &= move_mask;
self.check_count += 1;
}
}

View File

@@ -0,0 +1,134 @@
use super::board::Board;
use super::attackmaps::RAY_TABLE;
use super::checkinfo::CheckInfo;
use super::attacks::get_raycast_from_square_in_direction;
impl Board {
pub fn check_test(&self) -> CheckInfo {
let mut check_info: CheckInfo = CheckInfo::new();
let offset: usize = 6 * self.side_to_move as usize;
let king: u64 = self.bitboards[5 + offset];
let king_sq = king.trailing_zeros() as usize;
let occupancy = self.occupancy[2];
// queen-rook checks (+)
let attacker_mask = self.bitboards[10 - offset] | self.bitboards[9 - offset];
for dir in [0, 2, 4, 6] {
let threat_mask: u64 = get_raycast_from_square_in_direction(occupancy, king_sq, dir);
if threat_mask & attacker_mask != 0 {
check_info.add_checker(threat_mask);
}
}
// queen-bishop checks (x)
let attacker_mask = self.bitboards[10 - offset] | self.bitboards[8 - offset];
for dir in [1, 3, 5, 7] {
let threat_mask = get_raycast_from_square_in_direction(occupancy, king_sq, dir);
if threat_mask & attacker_mask != 0 {
check_info.add_checker(threat_mask);
}
}
// knight checks (L)
let attacker_mask = self.bitboards[7 - offset];
let threat_mask = self.get_pseudo_knight_moves(king_sq as u32);
let checker = threat_mask & attacker_mask;
if checker != 0 {
check_info.add_checker(checker);
}
// pawn checks (v)
let attacker_mask = self.bitboards[6 - offset];
let threat_mask = self.get_pseudo_pawn_captures(king_sq as u32);
let checker = threat_mask & attacker_mask;
if checker != 0 {
check_info.add_checker(checker);
}
return check_info;
}
pub(in super) fn calc_pinned_squares(&mut self) {
self.pinned_squares = [4; 64];
self.pin_mask = 0u64;
let friendly_pieces: u64 = self.occupancy[self.side_to_move as usize];
let offset: usize = 6 * self.side_to_move as usize;
let king_board: u64 = self.bitboards[5 + offset];
let king_sq: u32 = king_board.trailing_zeros();
let opponent_queen_bishop_mask: u64 = self.bitboards[8 - offset] | self.bitboards[10 - offset];
let opponent_queen_rook_mask: u64 = self.bitboards[9 - offset] | self.bitboards[10 - offset];
// Queen-Rook directions
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_rook_mask, 0);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_rook_mask, 2);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_rook_mask, 4);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_rook_mask, 6);
// Queen-Bishop directions
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_bishop_mask, 1);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_bishop_mask, 3);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_bishop_mask, 5);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_bishop_mask, 7);
}
pub(in super) fn set_pinned_in_ray_direction(&mut self, king_sq: u32, friendly_pieces: u64, attackers: u64, dir: u8) {
let is_up: bool = dir / 4 == 0;
let mask: u64 = RAY_TABLE[king_sq as usize][dir as usize];
let blockers: u64 = self.occupancy[2] & mask;
if blockers == 0 { return; }
let first_blocker_sq: u32 = if is_up { blockers.trailing_zeros() } else { 63 - blockers.leading_zeros() };
if (friendly_pieces & 1 << first_blocker_sq) != 0 {
let blockers: u64 = blockers & !(1 << first_blocker_sq);
if blockers == 0 { return; }
let second_blocker_sq: u32 = if is_up { blockers.trailing_zeros() } else { 63 - blockers.leading_zeros() };
if (attackers & 1 << second_blocker_sq) != 0 {
self.pinned_squares[first_blocker_sq as usize] = dir % 4;
self.pin_mask |= 1 << first_blocker_sq;
}
}
}
#[inline]
pub(in super) fn get_pin_masked_moves(&self, moves: u64, sq: u32) -> u64 {
let sq: usize = sq as usize;
if self.pinned_squares[sq] == 4 { return moves; }
let dir: u8 = self.pinned_squares[sq];
return moves & (RAY_TABLE[sq][dir as usize] | RAY_TABLE[sq][4 + dir as usize]);
}
}
// <----- TESTS ----->
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_test_test() {
let fens = [
"rnb1k1nr/pppppppp/4q3/8/1b6/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", // no check
"rnb1k1nr/pppppppp/4q3/8/1b1P4/8/PPP1PPPP/RNBQKBNR w KQkq d3 0 1", // single check
"rnb1k1nr/ppp1p2p/3pq1p1/8/1b1P1P2/8/PPP2PPP/RNBQKBNR w KQkq - 0 1" // double check
];
let expected_results = [
CheckInfo { check_count: 0, move_mask: 0xFFFF_FFFF_FFFF_FFFF },
CheckInfo { check_count: 1, move_mask: 0x0000_0000_0204_0800 },
CheckInfo { check_count: 2, move_mask: 0x0000_0000_0000_0000 }
];
for test_nr in 0..3 {
let fen = fens[test_nr];
let board = Board::build(fen);
let check_test_actual = board.check_test();
assert_eq!(check_test_actual.check_count, expected_results[test_nr].check_count);
assert_eq!(check_test_actual.move_mask, expected_results[test_nr].move_mask);
}
}
}

View File

@@ -0,0 +1,38 @@
mod quiets;
mod captures;
mod castles;
use super::bitmove::BitMoveType;
use super::bitmove::BitMove;
use super::board::Board;
impl Board {
#[inline]
pub fn make_move(&mut self, played_move: &BitMove) {
let move_type = played_move.move_type();
match move_type {
BitMoveType::Quiet => {
self.make_quiet(played_move);
}
BitMoveType::Capture => {
self.make_capture(played_move);
}
BitMoveType::Castle => {
self.make_castle(played_move);
}
BitMoveType::EnPassant => {
}
}
self.occupancy[2] = self.occupancy[0] | self.occupancy[1];
if self.en_passant_square != 0 {
self.en_passant_square = 0u64;
}
self.side_to_move = 1 - self.side_to_move;
}
}

View File

@@ -0,0 +1,82 @@
use super::*;
impl Board {
pub fn make_capture(&mut self, played_move: &BitMove) {
let main_from: usize = played_move.from_square() as usize;
let main_to: usize = played_move.to_square() as usize;
let main_piece: usize = self.piece_board(main_from as u8) as usize;
let friendly_occupancy = main_piece/6;
let color_offset = self.side_to_move * 6;
let castling_offset = 2 - 2 * self.side_to_move as usize;
let castling_rights = self.castling_rights >> castling_offset;
let mut taken_piece = 0u8;
taken_piece = self.piece_board(main_to as u8);
let secondary_piece = taken_piece as usize;
let secondary_from = main_to;
let opponent_castling_offset = 2 * self.side_to_move as usize;
let opponent_castling_rights = self.castling_rights >> opponent_castling_offset;
let opponent_occupancy = 1 - self.side_to_move as usize;
self.bitboards[main_piece] &= !(1 << main_from);
self.occupancy[friendly_occupancy] &= !(1 << main_from);
self.piece_board[main_from] = Self::EMPTY_SQUARE;
self.bitboards[secondary_piece] &= !(1 << secondary_from);
self.occupancy[opponent_occupancy] &= !(1 << secondary_from);
self.piece_board[secondary_from] = Self::EMPTY_SQUARE;
if opponent_castling_rights != 0
&& secondary_piece == 9 - color_offset as usize{
let back_rank_offset = 56 - 56 * self.side_to_move as usize;
if opponent_castling_rights & 0b01 != 0
&& secondary_from == back_rank_offset {
self.castling_rights &= !(1 << opponent_castling_offset);
}
else if opponent_castling_rights & 0b10 != 0
&& secondary_from == 7 + back_rank_offset {
self.castling_rights &= !(2 << opponent_castling_offset);
}
}
if let Some(promotion_piece) = played_move.promotion_piece() {
let promotion_piece = (color_offset + promotion_piece) as usize;
self.bitboards[promotion_piece] |= 1 << main_to;
self.occupancy[friendly_occupancy] |= 1 << main_to;
self.piece_board[main_to] = promotion_piece as u8;
}
else {
self.bitboards[main_piece] |= 1 << main_to;
self.occupancy[friendly_occupancy] |= 1 << main_to;
self.piece_board[main_to] = main_piece as u8;
if main_piece == 5 + color_offset as usize
&& castling_rights != 0 {
if castling_rights & 0b1 != 0 {
self.castling_rights &= !(1 << castling_offset);
}
if castling_rights & 0b10 != 0 {
self.castling_rights &= !(2 << castling_offset);
}
}
else if main_piece == 3 + color_offset as usize
&& castling_rights != 0 {
let back_rank_offset = 56 * self.side_to_move as usize;
if castling_rights & 0b10 != 0
&& main_from == 7 + back_rank_offset {
self.castling_rights &= !(2 << castling_offset);
}
else if castling_rights & 0b1 != 0
&& main_from == back_rank_offset {
self.castling_rights &= !(1 << castling_offset);
}
}
}
}
}

View File

@@ -0,0 +1,35 @@
use super::*;
impl Board {
pub fn make_castle(&mut self, played_move: &BitMove) {
let main_from: usize = played_move.from_square() as usize;
let main_to: usize = played_move.to_square() as usize;
let main_piece: usize = self.piece_board(main_from as u8) as usize;
let friendly_occupancy = main_piece/6;
let castling_offset = 2 - 2 * self.side_to_move as usize;
let secondary_piece: usize = main_piece - 2;
let is_kingside = main_to%8 > 4;
let secondary_from: usize = if is_kingside { main_to + 1 } else { main_to - 2 };
let secondary_to: usize = if is_kingside { main_to - 1 } else { main_to + 1 };
self.bitboards[main_piece] |= 1 << main_to;
self.occupancy[friendly_occupancy] |= 1 << main_to;
self.piece_board[main_to] = main_piece as u8;
self.bitboards[main_piece] &= !(1 << main_from);
self.occupancy[friendly_occupancy] &= !(1 << main_from);
self.piece_board[main_from] = Self::EMPTY_SQUARE;
self.bitboards[secondary_piece] |= 1 << secondary_to;
self.occupancy[friendly_occupancy] |= 1 << secondary_to;
self.piece_board[secondary_to] = secondary_piece as u8;
self.bitboards[secondary_piece] &= !(1 << secondary_from);
self.occupancy[friendly_occupancy] &= !(1 << secondary_from);
self.piece_board[secondary_from] = Self::EMPTY_SQUARE;
self.castling_rights &= !(3 << castling_offset);
}
}

View File

@@ -0,0 +1,61 @@
use super::*;
impl Board {
pub fn make_quiet(&mut self, played_move: &BitMove) {
let main_from: usize = played_move.from_square() as usize;
let main_to: usize = played_move.to_square() as usize;
let main_piece: usize = self.piece_board(main_from as u8) as usize;
let friendly_occupancy = main_piece/6;
let color_offset = self.side_to_move * 6;
let castling_offset = 2 - 2 * self.side_to_move as usize;
let castling_rights = self.castling_rights >> castling_offset;
self.bitboards[main_piece] &= !(1 << main_from);
self.occupancy[friendly_occupancy] &= !(1 << main_from);
self.piece_board[main_from] = Self::EMPTY_SQUARE;
if let Some(promotion_piece) = played_move.promotion_piece() {
let promotion_piece = (color_offset + promotion_piece) as usize;
self.bitboards[promotion_piece] |= 1 << main_to;
self.occupancy[friendly_occupancy] |= 1 << main_to;
self.piece_board[main_to] = promotion_piece as u8;
}
else {
self.bitboards[main_piece] |= 1 << main_to;
self.occupancy[friendly_occupancy] |= 1 << main_to;
self.piece_board[main_to] = main_piece as u8;
if main_piece == 0 && (main_to - main_from) == 16 {
let new_en_passant = main_to - 8;
self.en_passant_square = 1 << new_en_passant;
}
else if main_piece == 6 && (main_from - main_to) == 16 {
let new_en_passant = main_to + 8;
self.en_passant_square = 1 << new_en_passant;
}
else if main_piece == 5 + color_offset as usize
&& castling_rights != 0 {
if castling_rights & 0b1 != 0 {
self.castling_rights &= !(1 << castling_offset);
}
if castling_rights & 0b10 != 0 {
self.castling_rights &= !(2 << castling_offset);
}
}
else if main_piece == 3 + color_offset as usize
&& castling_rights != 0 {
let back_rank_offset = 56 * self.side_to_move as usize;
if castling_rights & 0b10 != 0
&& main_from == 7 + back_rank_offset {
self.castling_rights &= !(2 << castling_offset);
}
else if castling_rights & 0b1 != 0
&& main_from == back_rank_offset {
self.castling_rights &= !(1 << castling_offset);
}
}
}
}
}

View File

@@ -0,0 +1,44 @@
use super::bitmove::BitMove;
pub struct MoveBuffer {
buffer: [BitMove; 256],
count: usize
}
impl MoveBuffer {
pub fn new() -> Self {
return Self {
buffer: [BitMove::quiet(0, 0, None); 256],
count: 0
};
}
#[inline]
pub fn add(&mut self, bitmove: BitMove) {
self.buffer[self.count] = bitmove;
self.count += 1;
}
#[inline]
pub fn append(&mut self, other: &MoveBuffer) {
self.buffer[self.count..self.count + other.count()].copy_from_slice(other.contents());
self.count += other.count();
}
#[inline(always)]
pub fn clear(&mut self) {
self.count = 0;
}
#[inline(always)]
pub fn count(&self) -> usize{
return self.count;
}
#[inline(always)]
pub fn get(&self, idx: usize) -> &BitMove {
return &self.buffer[idx];
}
#[inline(always)]
pub fn contents(&self) -> &[BitMove] {
return &self.buffer[0..self.count];
}
}

View File

@@ -0,0 +1,67 @@
mod pawns;
mod knights;
mod bishops;
mod rooks;
mod queens;
mod kings;
use super::board::Board;
use super::movebuffer::MoveBuffer;
use super::bitmove::BitMove;
use super::checkinfo::CheckInfo;
use super::utils::*;
impl Board {
const NO_FILTER: u64 = 0xFFFF_FFFF_FFFF_FFFF;
pub fn collect_moves(&mut self, buffer: &mut MoveBuffer, temp_buffer: &mut MoveBuffer) -> bool {
buffer.clear();
self.calc_pinned_squares();
let check_info = self.check_test();
match check_info.check_count {
0 => self.collect_all_moves(buffer, temp_buffer),
1 => self.collect_moves_single_check(buffer, temp_buffer, &check_info),
2 => self.collect_king_evasion(buffer, temp_buffer),
_ => panic!("More than 2 checking pieces found as the same time!")
}
return check_info.check_count > 0;
}
pub(in super) fn collect_all_moves(&self, buffer: &mut MoveBuffer, temp_buffer: &mut MoveBuffer) {
let safe_squares = self.get_safe_king_squares();
self.add_pawn_moves(buffer, temp_buffer, Self::NO_FILTER);
self.add_knight_moves(buffer, temp_buffer, Self::NO_FILTER);
self.add_bishop_moves(buffer, temp_buffer, Self::NO_FILTER);
self.add_rook_moves(buffer, temp_buffer, Self::NO_FILTER);
self.add_queen_moves(buffer, temp_buffer, Self::NO_FILTER);
self.add_king_moves(buffer, temp_buffer, safe_squares);
self.add_king_castles(buffer, safe_squares);
buffer.append(temp_buffer);
temp_buffer.clear();
}
pub(in super) fn collect_moves_single_check(&self, buffer: &mut MoveBuffer, temp_buffer: &mut MoveBuffer, check_info: &CheckInfo) {
let safe_squares = self.get_safe_king_squares();
self.add_pawn_moves(buffer, temp_buffer, check_info.move_mask);
self.add_knight_moves(buffer, temp_buffer, check_info.move_mask);
self.add_bishop_moves(buffer, temp_buffer, check_info.move_mask);
self.add_rook_moves(buffer, temp_buffer, check_info.move_mask);
self.add_queen_moves(buffer, temp_buffer, check_info.move_mask);
self.add_king_moves(buffer, temp_buffer, safe_squares);
buffer.append(temp_buffer);
temp_buffer.clear();
}
pub(in super) fn collect_king_evasion(&self, buffer: &mut MoveBuffer, temp_buffer: &mut MoveBuffer) {
let safe_squares = self.get_safe_king_squares();
self.add_king_moves(buffer, temp_buffer, safe_squares);
buffer.append(&temp_buffer);
temp_buffer.clear();
}
}

View File

@@ -0,0 +1,55 @@
use super::*;
impl Board {
pub fn add_bishop_moves(&self, capture_buffer: &mut MoveBuffer, quiet_buffer: &mut MoveBuffer, move_mask: u64) {
let piece_index = 2 + self.side_to_move * 6;
let mut bishops = self.bitboards[piece_index as usize];
let empty = !self.occupancy[2];
let opponents = self.occupancy[1 - self.side_to_move as usize];
while bishops != 0 {
let from_sq = pop_lsb(&mut bishops);
let raw_move_map = self.get_pseudo_bishop_moves(from_sq) & move_mask;
let move_map = self.get_pin_masked_moves(raw_move_map, from_sq);
let mut quiet_map = move_map & empty;
let mut capture_map = move_map & opponents;
while quiet_map != 0 {
let to_sq = pop_lsb(&mut quiet_map);
quiet_buffer.add(BitMove::quiet(
from_sq as u8,
to_sq as u8,
None
));
}
while capture_map != 0 {
let to_sq = pop_lsb(&mut capture_map);
capture_buffer.add(BitMove::capture(
from_sq as u8,
to_sq as u8,
None
));
}
}
}
pub fn add_bishop_captures(&self, buffer: &mut MoveBuffer, move_mask: u64) {
let offset = 6 * self.side_to_move as usize;
let mut bishops: u64 = self.bitboards[2 + offset];
let opponents = self.occupancy[1 - self.side_to_move as usize];
while bishops != 0 {
let next_sq: u32 = pop_lsb(&mut bishops);
let mut attacks: u64 = self.get_pseudo_bishop_moves(next_sq) & opponents & move_mask;
attacks = self.get_pin_masked_moves(attacks, next_sq);
while attacks != 0 {
let to_sq = pop_lsb(&mut attacks);
buffer.add(BitMove::capture(
next_sq as u8,
to_sq as u8,
None
));
}
}
}
}

View File

@@ -0,0 +1,88 @@
use super::*;
impl Board {
pub fn add_king_moves(&self, capture_buffer: &mut MoveBuffer, quiet_buffer: &mut MoveBuffer, move_mask: u64) {
let piece_index = 5 + self.side_to_move * 6;
let mut kings = self.bitboards[piece_index as usize];
let empty = !self.occupancy[2];
let opponents = self.occupancy[1 - self.side_to_move as usize];
while kings != 0 {
let from_sq = pop_lsb(&mut kings);
let move_map = self.get_pseudo_king_moves(from_sq) & move_mask;
let mut quiet_map = move_map & empty;
let mut capture_map = move_map & opponents;
while quiet_map != 0 {
let to_sq = pop_lsb(&mut quiet_map);
quiet_buffer.add(BitMove::quiet(
from_sq as u8,
to_sq as u8,
None
));
}
while capture_map != 0 {
let to_sq = pop_lsb(&mut capture_map);
capture_buffer.add(BitMove::capture(
from_sq as u8,
to_sq as u8,
None
));
}
}
}
pub fn add_king_captures(&self, buffer: &mut MoveBuffer, move_mask: u64) {
let offset = 6 * self.side_to_move as usize;
let mut kings: u64 = self.bitboards[5 + offset];
let opponents = self.occupancy[1 - self.side_to_move as usize];
while kings != 0 {
let next_sq: u32 = pop_lsb(&mut kings);
let mut attacks: u64 = self.get_pseudo_king_moves(next_sq) & opponents & move_mask;
attacks = self.get_pin_masked_moves(attacks, next_sq);
while attacks != 0 {
let to_sq = pop_lsb(&mut attacks);
buffer.add(BitMove::capture(
next_sq as u8,
to_sq as u8,
None
));
}
}
}
pub fn add_king_castles(&self, buffer: &mut MoveBuffer, move_mask: u64) {
if self.castling_rights & (0b11 << (2 - 2 * self.side_to_move)) == 0 {
return;
}
let offset = 5 + 6 * self.side_to_move as u8;
let castle_offset = 2 - 2 * self.side_to_move as u8;
let castling_rights = self.castling_rights & 3 << castle_offset;
let occupied = self.occupancy[2];
let king_sq = self.bitboards[offset as usize].trailing_zeros();
let queenside_mask = 0b111 << (king_sq - 3);
let kingside_mask = 0b11 << (king_sq + 1);
if (castling_rights & 1 << castle_offset) != 0
&& queenside_mask & occupied == 0
&& !move_mask & 0b11 << (king_sq - 2) == 0
&& !self.is_square_attacked(king_sq - 2) {
buffer.add(BitMove::castle(
king_sq as u8,
(king_sq - 2) as u8
));
}
if (castling_rights & 2 << castle_offset) != 0
&& kingside_mask & occupied == 0
&& !move_mask & 0b11 << (king_sq + 1) == 0
&& !self.is_square_attacked(king_sq + 2) {
buffer.add(BitMove::castle(
king_sq as u8,
(king_sq + 2) as u8
));
}
}
}

View File

@@ -0,0 +1,55 @@
use super::*;
impl Board {
pub fn add_knight_moves(&self, capture_buffer: &mut MoveBuffer, quiet_buffer: &mut MoveBuffer, move_mask: u64) {
let piece_index = 1 + self.side_to_move * 6;
let mut knights = self.bitboards[piece_index as usize];
let empty = !self.occupancy[2];
let opponents = self.occupancy[1 - self.side_to_move as usize];
while knights != 0 {
let from_sq = pop_lsb(&mut knights);
let raw_move_map = self.get_pseudo_knight_moves(from_sq) & move_mask;
let move_map = self.get_pin_masked_moves(raw_move_map, from_sq);
let mut quiet_map = move_map & empty;
let mut capture_map = move_map & opponents;
while quiet_map != 0 {
let to_sq = pop_lsb(&mut quiet_map);
quiet_buffer.add(BitMove::quiet(
from_sq as u8,
to_sq as u8,
None
));
}
while capture_map != 0 {
let to_sq = pop_lsb(&mut capture_map);
capture_buffer.add(BitMove::capture(
from_sq as u8,
to_sq as u8,
None
));
}
}
}
pub fn add_knight_captures(&self, buffer: &mut MoveBuffer, move_mask: u64) {
let offset = 6 * self.side_to_move as usize;
let mut knights: u64 = self.bitboards[1 + offset];
let opponents = self.occupancy[1 - self.side_to_move as usize];
while knights != 0 {
let next_sq: u32 = pop_lsb(&mut knights);
let mut attacks: u64 = self.get_pseudo_knight_moves(next_sq) & opponents & move_mask;
attacks = self.get_pin_masked_moves(attacks, next_sq);
while attacks != 0 {
let to_sq = pop_lsb(&mut attacks);
buffer.add(BitMove::capture(
next_sq as u8,
to_sq as u8,
None
));
}
}
}
}

View File

@@ -0,0 +1,76 @@
use super::*;
impl Board {
pub fn add_pawn_quiets(&self, buffer: &mut MoveBuffer, move_mask: u64) {
let offset: u8 = self.side_to_move * 6;
let mut pawns: u64 = self.bitboards[offset as usize];
while pawns != 0 {
let next_sq = pop_lsb(&mut pawns);
let mut quiets: u64 = self.get_pseudo_pawn_moves(next_sq) & move_mask;
quiets = self.get_pin_masked_moves(quiets, next_sq);
while quiets != 0 {
let to_sq = quiets.trailing_zeros();
if (self.side_to_move == 0 && quiets.trailing_zeros() / 8 == 7)
|| (self.side_to_move == 1 && quiets.trailing_zeros() / 8 == 0) {
for piece_type in [4, 3, 2, 1] {
buffer.add(BitMove::quiet(
next_sq as u8,
to_sq as u8,
Some(piece_type)
));
}
}
else {
buffer.add(BitMove::quiet(
next_sq as u8,
to_sq as u8,
None
));
}
quiets &= !(1 << to_sq);
}
}
}
pub fn add_pawn_captures(&self, buffer: &mut MoveBuffer, move_mask: u64) {
let offset = 6 * self.side_to_move as usize;
let mut pawns: u64 = self.bitboards[offset];
let opponents = self.occupancy[1 - self.side_to_move as usize];
while pawns != 0 {
let next_sq = pop_lsb(&mut pawns);
let mut attacks: u64 = self.get_pseudo_pawn_captures(next_sq) & move_mask;
attacks = self.get_pin_masked_moves(attacks, next_sq);
attacks &= opponents;
while attacks != 0 {
let to_sq = attacks.trailing_zeros();
if (self.side_to_move == 0 && attacks.trailing_zeros() / 8 == 7)
|| (self.side_to_move == 1 && attacks.trailing_zeros() / 8 == 0) {
for piece_type in [4, 3, 2, 1] {
buffer.add(BitMove::capture(
next_sq as u8,
to_sq as u8,
Some(piece_type)
));
}
}
else {
buffer.add(BitMove::capture(
next_sq as u8,
to_sq as u8,
None
));
}
attacks &= !(1 << to_sq);
}
}
}
pub fn add_pawn_moves(&self, capture_buffer: &mut MoveBuffer, quiet_buffer: &mut MoveBuffer, move_mask: u64) {
self.add_pawn_captures(capture_buffer, move_mask);
self.add_pawn_quiets(quiet_buffer, move_mask);
}
}

View File

@@ -0,0 +1,55 @@
use super::*;
impl Board {
pub fn add_queen_moves(&self, capture_buffer: &mut MoveBuffer, quiet_buffer: &mut MoveBuffer, move_mask: u64) {
let piece_index = 4 + self.side_to_move * 6;
let mut queens = self.bitboards[piece_index as usize];
let empty = !self.occupancy[2];
let opponents = self.occupancy[1 - self.side_to_move as usize];
while queens != 0 {
let from_sq = pop_lsb(&mut queens);
let raw_move_map = self.get_pseudo_queen_moves(from_sq) & move_mask;
let move_map = self.get_pin_masked_moves(raw_move_map, from_sq);
let mut quiet_map = move_map & empty;
let mut capture_map = move_map & opponents;
while quiet_map != 0 {
let to_sq = pop_lsb(&mut quiet_map);
quiet_buffer.add(BitMove::quiet(
from_sq as u8,
to_sq as u8,
None
));
}
while capture_map != 0 {
let to_sq = pop_lsb(&mut capture_map);
capture_buffer.add(BitMove::capture(
from_sq as u8,
to_sq as u8,
None
));
}
}
}
pub fn add_queen_captures(&self, buffer: &mut MoveBuffer, move_mask: u64) {
let offset = 6 * self.side_to_move as usize;
let mut queens: u64 = self.bitboards[4 + offset];
let opponents = self.occupancy[1 - self.side_to_move as usize];
while queens != 0 {
let next_sq: u32 = pop_lsb(&mut queens);
let mut attacks: u64 = self.get_pseudo_queen_moves(next_sq) & opponents & move_mask;
attacks = self.get_pin_masked_moves(attacks, next_sq);
while attacks != 0 {
let to_sq = pop_lsb(&mut attacks);
buffer.add(BitMove::capture(
next_sq as u8,
to_sq as u8,
None
));
}
}
}
}

View File

@@ -0,0 +1,55 @@
use super::*;
impl Board {
pub fn add_rook_moves(&self, capture_buffer: &mut MoveBuffer, quiet_buffer: &mut MoveBuffer, move_mask: u64) {
let piece_index = 3 + self.side_to_move * 6;
let mut rooks = self.bitboards[piece_index as usize];
let empty = !self.occupancy[2];
let opponents = self.occupancy[1 - self.side_to_move as usize];
while rooks != 0 {
let from_sq = pop_lsb(&mut rooks);
let raw_move_map = self.get_pseudo_rook_moves(from_sq) & move_mask;
let move_map = self.get_pin_masked_moves(raw_move_map, from_sq);
let mut quiet_map = move_map & empty;
let mut capture_map = move_map & opponents;
while quiet_map != 0 {
let to_sq = pop_lsb(&mut quiet_map);
quiet_buffer.add(BitMove::quiet(
from_sq as u8,
to_sq as u8,
None
));
}
while capture_map != 0 {
let to_sq = pop_lsb(&mut capture_map);
capture_buffer.add(BitMove::capture(
from_sq as u8,
to_sq as u8,
None
));
}
}
}
pub fn add_rook_captures(&self, buffer: &mut MoveBuffer, move_mask: u64) {
let offset = 6 * self.side_to_move as usize;
let mut rooks: u64 = self.bitboards[3 + offset];
let opponents = self.occupancy[1 - self.side_to_move as usize];
while rooks != 0 {
let next_sq: u32 = pop_lsb(&mut rooks);
let mut attacks: u64 = self.get_pseudo_rook_moves(next_sq) & opponents & move_mask;
attacks = self.get_pin_masked_moves(attacks, next_sq);
while attacks != 0 {
let to_sq = pop_lsb(&mut attacks);
buffer.add(BitMove::capture(
next_sq as u8,
to_sq as u8,
None
));
}
}
}
}

View File

@@ -0,0 +1,125 @@
#[inline(always)]
pub fn pop_lsb(value: &mut u64) -> u32 {
let idx = value.trailing_zeros();
*value &= !(1 << idx);
return idx;
}
#[inline(always)]
pub fn pop_msb(value: &mut u64) -> u32 {
let idx = 63 - value.leading_zeros();
*value &= !(1 << idx);
return idx;
}
const RANK_NUMBERS: [char; 8] = ['1', '2', '3', '4', '5', '6', '7', '8'];
const FILE_LETTERS: [char; 8] = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
pub fn notation_from_square_number(sq: u8) -> String {
let row = sq / 8;
let col = sq % 8;
let mut notation = String::new();
let row_not = RANK_NUMBERS[row as usize];
let col_not = FILE_LETTERS[col as usize];
notation.push(col_not);
notation.push(row_not);
return notation;
}
pub fn try_get_square_number_from_notation(notation: &str) -> Result<u8, ()> {
let file = match notation.chars().nth(0).unwrap() {
'a' => 0,
'b' => 1,
'c' => 2,
'd' => 3,
'e' => 4,
'f' => 5,
'g' => 6,
'h' => 7,
_ => { return Result::Err(()); }
};
if let Some(rank) = notation.chars().nth(1) {
return Result::Ok(file + 8 * (rank.to_digit(10).unwrap() as u8) - 8);
}
else {
return Result::Err(());
}
}
const PIECE_CHARACTERS: [char; 12] = ['P', 'N', 'B', 'R', 'Q', 'K', 'p', 'n', 'b', 'r', 'q', 'k'];
pub fn get_character_by_piece_id(id: u8) -> char {
return PIECE_CHARACTERS[id as usize];
}
// <----- TESTS ----->
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pop_lsb_test() {
// test setup
let test_values: [u64; 6] = [
0x8000_0000_0000_0000,
0x4E91_CF05_713E_451B,
0xD588_2D58_6962_34B0,
0x9581_3335_DCAB_1DD4,
0xBEAC_DBE0_903A_AC00,
0x01E8_C895_A6F0_0000
];
let expected_values: [u32; 6] = [63, 0, 4, 2, 10, 20];
// tests
for index in 0..6 {
let mut test_value = test_values[index];
assert_eq!(pop_lsb(&mut test_value), expected_values[index])
}
}
#[test]
fn pop_msb_test() {
// test setup
let test_values: [u64; 6] = [
0x86D6_8EB0_96A8_8D1C,
0x0000_0000_0000_0001,
0x3809_24AF_A7AE_8129,
0x0277_DA36_3B31_86D9,
0x0000_C1C3_201C_0DB1,
0x0000_0203_0DE4_E944
];
let expected_values: [u32; 6] = [63, 0, 61, 57, 47, 41];
// tests
for index in 0..6 {
let mut test_value = test_values[index];
assert_eq!(pop_msb(&mut test_value), expected_values[index])
}
}
#[test]
fn notation_from_square_number_test() {
// test setup
let square_indices: [u8; 8] = [1, 12, 22, 27, 32, 47, 53, 58];
let notations: [String; 8] = [
String::from("b1"),
String::from("e2"),
String::from("g3"),
String::from("d4"),
String::from("a5"),
String::from("h6"),
String::from("f7"),
String::from("c8")
];
// tests
for index in 0..8 {
let notation = notation_from_square_number(square_indices[index].clone());
assert_eq!(notation, notations[index]);
}
}
}

50
engine/src/boardsquare.rs Normal file
View File

@@ -0,0 +1,50 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BoardSquare {
pub x: usize,
pub y: usize,
}
impl BoardSquare {
pub fn new() -> Self {
return Self { x: 0, y: 0 };
}
pub fn from_coord(x: usize, y: usize) -> Self {
#[cfg(debug_assertions)]
{
if x > 7 {
println!(
"Warning: x coordinate of square is bigger than 7, it might not be on the board!"
);
}
if y > 7 {
println!(
"Warning: y coordinate of square is bigger than 7, it might not be on the board!"
);
}
}
return Self { x: x, y: y };
}
pub(super) fn from_index(idx: u8) -> Self {
let file = idx % 8;
let rank = idx / 8;
#[cfg(debug_assertions)]
{
if !(0..8).contains(&rank) {
println!("Warning: internal engine issue, given index is not on the board!");
}
}
return Self {
x: file as usize,
y: rank as usize,
};
}
pub(super) fn to_index(&self) -> u8 {
return (8 * self.y + self.x) as u8;
}
}

238
engine/src/chessmove.rs Normal file
View File

@@ -0,0 +1,238 @@
use crate::bitboard::{
bitmove::{BitMove, BitMoveType},
board::Board,
};
use super::boardsquare::BoardSquare;
use super::piecetype::PieceType;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
/*pub struct ChessMove {
pub move_type: MoveType,
pub piece_type: PieceType,
pub from_square: BoardSquare,
pub to_square: BoardSquare,
pub rook_from: BoardSquare,
pub rook_to: BoardSquare,
pub promotion_piece: Option<PieceType>,
}*/
pub enum ChessMove {
Quiet {
piece_type: PieceType,
from_square: BoardSquare,
to_square: BoardSquare,
promotion_piece: Option<PieceType>,
},
Capture {
piece_type: PieceType,
from_square: BoardSquare,
to_square: BoardSquare,
captured_piece: PieceType,
promotion_piece: Option<PieceType>,
},
Castle {
king_type: PieceType,
king_from: BoardSquare,
king_to: BoardSquare,
rook_type: PieceType,
rook_from: BoardSquare,
rook_to: BoardSquare,
},
EnPassant {
pawn_type: PieceType,
from_square: BoardSquare,
to_square: BoardSquare,
captured_piece: PieceType,
captured_from: BoardSquare,
},
}
impl ChessMove {
pub fn quiet(
piece_type: PieceType,
from_square: BoardSquare,
to_square: BoardSquare,
promotion_piece: Option<PieceType>,
) -> Self {
return Self::Quiet {
piece_type,
from_square,
to_square,
promotion_piece,
};
}
pub fn capture(
piece_type: PieceType,
from_square: BoardSquare,
to_square: BoardSquare,
captured_piece: PieceType,
promotion_piece: Option<PieceType>,
) -> Self {
return Self::Capture {
piece_type,
from_square,
to_square,
captured_piece,
promotion_piece,
};
}
pub fn castle(
king_type: PieceType,
king_from: BoardSquare,
king_to: BoardSquare,
rook_type: PieceType,
rook_from: BoardSquare,
rook_to: BoardSquare,
) -> Self {
return Self::Castle {
king_type,
king_from,
king_to,
rook_type,
rook_from,
rook_to,
};
}
pub(super) fn from_bitmove(bitmove: &BitMove, board: &Board) -> Self {
match bitmove.move_type() {
BitMoveType::Quiet => {
let from_square_index = bitmove.from_square();
let piece_type = PieceType::from_index(board.piece_board(from_square_index));
let from_square = BoardSquare::from_index(from_square_index);
let to_square = BoardSquare::from_index(bitmove.to_square());
let promotion_piece = match bitmove.promotion_piece() {
Some(piece) => Some(PieceType::from_index(piece)),
None => None,
};
return ChessMove::Quiet {
piece_type,
from_square,
to_square,
promotion_piece,
};
}
BitMoveType::Capture => {
let from_square_index = bitmove.from_square();
let to_square_index = bitmove.to_square();
let piece_type = PieceType::from_index(board.piece_board(from_square_index));
let from_square = BoardSquare::from_index(from_square_index);
let to_square = BoardSquare::from_index(to_square_index);
let captured_piece = PieceType::from_index(board.piece_board(to_square_index));
let promotion_piece = match bitmove.promotion_piece() {
Some(piece) => Some(PieceType::from_index(piece)),
None => None,
};
return ChessMove::Capture {
piece_type,
from_square,
to_square,
captured_piece,
promotion_piece,
};
}
BitMoveType::Castle => {
let from_square_index = bitmove.from_square();
let to_square_index = bitmove.to_square();
let king_type = PieceType::from_index(board.piece_board(from_square_index));
let king_from = BoardSquare::from_index(from_square_index);
let king_to = BoardSquare::from_index(to_square_index);
let rook_type = if bitmove.from_square() < 32 {
PieceType::WhiteRook
} else {
PieceType::BlackRook
};
let rook_from_index = if bitmove.to_square() > bitmove.from_square() {
bitmove.from_square() + 3
} else {
bitmove.from_square() - 4
};
let rook_from = BoardSquare::from_index(rook_from_index);
let rook_to_index = if bitmove.to_square() > bitmove.from_square() {
bitmove.from_square() + 1
} else {
bitmove.from_square() - 1
};
let rook_to = BoardSquare::from_index(rook_to_index);
return ChessMove::Castle {
king_type,
king_from,
king_to,
rook_type,
rook_from,
rook_to,
};
}
BitMoveType::EnPassant => {
panic!("ChessMove::from_bitmove was left unimplemented");
}
}
}
pub(super) fn to_bitmove(&self) -> BitMove {
let bitmove = match self {
ChessMove::Quiet {
piece_type,
from_square,
to_square,
promotion_piece,
} => {
let promotion_piece = match promotion_piece {
Some(piece) => Some(piece.to_index()),
None => None,
};
return BitMove::quiet(
from_square.to_index(),
to_square.to_index(),
promotion_piece,
);
}
ChessMove::Capture {
piece_type,
from_square,
to_square,
captured_piece,
promotion_piece,
} => {
let promotion_piece = match promotion_piece {
Some(piece) => Some(piece.to_index()),
None => None,
};
return BitMove::capture(
from_square.to_index(),
to_square.to_index(),
promotion_piece,
);
}
ChessMove::Castle {
king_type,
king_from,
king_to,
rook_type,
rook_from,
rook_to,
} => {
return BitMove::castle(king_from.to_index(), king_to.to_index());
}
ChessMove::EnPassant {
pawn_type,
from_square,
to_square,
captured_piece,
captured_from,
} => {
panic!("ChessMove::to_bitmove was left unimplemented");
}
};
return bitmove;
}
pub fn notation(&self) -> String {
return self.to_bitmove().uci_notation();
}
}

8
engine/src/gameend.rs Normal file
View File

@@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
pub enum GameEnd {
WhiteWon(String),
BlackWon(String),
Draw(String),
}

538
engine/src/lib.rs Normal file
View File

@@ -0,0 +1,538 @@
mod bitboard;
pub mod boardsquare;
pub mod chessmove;
pub mod gameend;
pub mod movetype;
pub mod piecetype;
use bitboard::board::Board;
use bitboard::movebuffer::MoveBuffer;
use chessmove::ChessMove;
use gameend::GameEnd;
pub fn get_available_moves(fen: &str) -> Vec<ChessMove> {
let mut board = Board::build(fen);
let mut buffer = MoveBuffer::new();
let mut temp_buffer = MoveBuffer::new();
let mut generated_moves: Vec<ChessMove> = vec![];
board.collect_moves(&mut buffer, &mut temp_buffer);
for idx in 0..buffer.count() {
generated_moves.push(ChessMove::from_bitmove(buffer.get(idx), &board));
}
println!(
"get_available_moves resulted in {} moves",
generated_moves.len()
);
return generated_moves;
}
pub fn is_game_over(fen: &str) -> Option<GameEnd> {
let mut board = Board::build(fen);
let mut buffer = MoveBuffer::new();
let mut temp_buffer = MoveBuffer::new();
let in_check = board.collect_moves(&mut buffer, &mut temp_buffer);
println!("is_game_over answered");
if buffer.count() > 0 {
return None;
}
if !in_check {
return Some(GameEnd::Draw("".to_string()));
}
return if board.side_to_move() == 0 {
Some(GameEnd::BlackWon("".to_string()))
} else {
Some(GameEnd::WhiteWon("".to_string()))
};
}
pub fn get_board_after_move(fen: &str, chess_move: &ChessMove) -> String {
let mut board = Board::build(fen);
let played_move = chess_move.to_bitmove();
println!("get_board_after_move answered");
board.make_move(&played_move);
return board.fen();
}
#[cfg(test)]
mod tests {
use crate::boardsquare::BoardSquare;
use crate::gameend::GameEnd;
use crate::piecetype::PieceType::*;
use super::*;
impl PartialEq for ChessMove {
fn eq(&self, other: &Self) -> bool {
canonical(self) == canonical(other)
}
}
impl Eq for ChessMove {}
impl Ord for ChessMove {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let lhs = canonical(self);
let rhs = canonical(other);
lhs.cmp(&rhs)
}
}
impl PartialOrd for ChessMove {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
fn canonical(m: &ChessMove) -> (u8, u8, u8) {
match m {
ChessMove::Quiet {
piece_type,
from_square,
to_square,
promotion_piece,
} => (0, from_square.to_index(), to_square.to_index()),
ChessMove::Capture {
piece_type,
from_square,
to_square,
captured_piece,
promotion_piece,
} => (1, from_square.to_index(), to_square.to_index()),
ChessMove::Castle {
king_type,
king_from,
king_to,
rook_type,
rook_from,
rook_to,
} => (2, king_from.to_index(), king_to.to_index()),
ChessMove::EnPassant {
pawn_type,
from_square,
to_square,
captured_piece,
captured_from,
} => (3, from_square.to_index(), to_square.to_index()),
}
}
#[test]
fn get_available_moves_test() {
let boards: [&str; 2] = [
"rnbqkbnr/pppppppp/8/1B6/4P3/5P1N/PPPP2PP/RNBQK2R w KQkq e6 0 1",
"6Bn/B2Pk3/8/p1r3NK/3p4/b6P/3p2n1/2R5 w - - 0 1",
];
let mut expected_moves: Vec<Vec<ChessMove>> = vec![
vec![
ChessMove::capture(
WhiteBishop,
BoardSquare::from_coord(1, 4),
BoardSquare::from_coord(3, 6),
BlackPawn,
None,
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(0, 1),
BoardSquare::from_coord(0, 2),
None,
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(0, 1),
BoardSquare::from_coord(0, 3),
None,
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(1, 1),
BoardSquare::from_coord(1, 2),
None,
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(1, 1),
BoardSquare::from_coord(1, 3),
None,
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(2, 1),
BoardSquare::from_coord(2, 2),
None,
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(2, 1),
BoardSquare::from_coord(2, 3),
None,
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(3, 1),
BoardSquare::from_coord(3, 2),
None,
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(3, 1),
BoardSquare::from_coord(3, 3),
None,
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(4, 3),
BoardSquare::from_coord(4, 4),
None,
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(5, 2),
BoardSquare::from_coord(5, 3),
None,
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(6, 1),
BoardSquare::from_coord(6, 2),
None,
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(6, 1),
BoardSquare::from_coord(6, 3),
None,
),
ChessMove::quiet(
WhiteKnight,
BoardSquare::from_coord(1, 0),
BoardSquare::from_coord(0, 2),
None,
),
ChessMove::quiet(
WhiteKnight,
BoardSquare::from_coord(1, 0),
BoardSquare::from_coord(2, 2),
None,
),
ChessMove::quiet(
WhiteKnight,
BoardSquare::from_coord(7, 2),
BoardSquare::from_coord(6, 0),
None,
),
ChessMove::quiet(
WhiteKnight,
BoardSquare::from_coord(7, 2),
BoardSquare::from_coord(5, 1),
None,
),
ChessMove::quiet(
WhiteKnight,
BoardSquare::from_coord(7, 2),
BoardSquare::from_coord(5, 3),
None,
),
ChessMove::quiet(
WhiteKnight,
BoardSquare::from_coord(7, 2),
BoardSquare::from_coord(6, 4),
None,
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(1, 4),
BoardSquare::from_coord(5, 0),
None,
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(1, 4),
BoardSquare::from_coord(4, 1),
None,
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(1, 4),
BoardSquare::from_coord(3, 2),
None,
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(1, 4),
BoardSquare::from_coord(2, 3),
None,
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(1, 4),
BoardSquare::from_coord(0, 3),
None,
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(1, 4),
BoardSquare::from_coord(0, 5),
None,
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(1, 4),
BoardSquare::from_coord(2, 5),
None,
),
ChessMove::quiet(
WhiteRook,
BoardSquare::from_coord(7, 0),
BoardSquare::from_coord(6, 0),
None,
),
ChessMove::quiet(
WhiteRook,
BoardSquare::from_coord(7, 0),
BoardSquare::from_coord(5, 0),
None,
),
ChessMove::quiet(
WhiteQueen,
BoardSquare::from_coord(3, 0),
BoardSquare::from_coord(4, 1),
None,
),
ChessMove::quiet(
WhiteKing,
BoardSquare::from_coord(4, 0),
BoardSquare::from_coord(4, 1),
None,
),
ChessMove::quiet(
WhiteKing,
BoardSquare::from_coord(4, 0),
BoardSquare::from_coord(5, 1),
None,
),
ChessMove::quiet(
WhiteKing,
BoardSquare::from_coord(4, 0),
BoardSquare::from_coord(5, 0),
None,
),
ChessMove::castle(
WhiteKing,
BoardSquare::from_coord(4, 0),
BoardSquare::from_coord(6, 0),
WhiteRook,
BoardSquare::from_coord(7, 0),
BoardSquare::from_coord(5, 0),
),
],
vec![
ChessMove::capture(
WhiteBishop,
BoardSquare::from_coord(0, 6),
BoardSquare::from_coord(2, 4),
BlackRook,
None,
),
ChessMove::capture(
WhiteRook,
BoardSquare::from_coord(2, 0),
BoardSquare::from_coord(2, 4),
BlackRook,
None,
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(7, 2),
BoardSquare::from_coord(7, 3),
None,
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(3, 6),
BoardSquare::from_coord(3, 7),
Some(WhiteQueen),
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(3, 6),
BoardSquare::from_coord(3, 7),
Some(WhiteRook),
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(3, 6),
BoardSquare::from_coord(3, 7),
Some(WhiteBishop),
),
ChessMove::quiet(
WhitePawn,
BoardSquare::from_coord(3, 6),
BoardSquare::from_coord(3, 7),
Some(WhiteKnight),
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(0, 6),
BoardSquare::from_coord(1, 5),
None,
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(0, 6),
BoardSquare::from_coord(1, 7),
None,
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(6, 7),
BoardSquare::from_coord(7, 6),
None,
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(6, 7),
BoardSquare::from_coord(5, 6),
None,
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(6, 7),
BoardSquare::from_coord(4, 5),
None,
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(6, 7),
BoardSquare::from_coord(3, 4),
None,
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(6, 7),
BoardSquare::from_coord(2, 3),
None,
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(6, 7),
BoardSquare::from_coord(1, 2),
None,
),
ChessMove::quiet(
WhiteBishop,
BoardSquare::from_coord(6, 7),
BoardSquare::from_coord(0, 1),
None,
),
ChessMove::quiet(
WhiteRook,
BoardSquare::from_coord(2, 0),
BoardSquare::from_coord(0, 0),
None,
),
ChessMove::quiet(
WhiteRook,
BoardSquare::from_coord(2, 0),
BoardSquare::from_coord(1, 0),
None,
),
ChessMove::quiet(
WhiteRook,
BoardSquare::from_coord(2, 0),
BoardSquare::from_coord(3, 0),
None,
),
ChessMove::quiet(
WhiteRook,
BoardSquare::from_coord(2, 0),
BoardSquare::from_coord(4, 0),
None,
),
ChessMove::quiet(
WhiteRook,
BoardSquare::from_coord(2, 0),
BoardSquare::from_coord(5, 0),
None,
),
ChessMove::quiet(
WhiteRook,
BoardSquare::from_coord(2, 0),
BoardSquare::from_coord(6, 0),
None,
),
ChessMove::quiet(
WhiteRook,
BoardSquare::from_coord(2, 0),
BoardSquare::from_coord(7, 0),
None,
),
ChessMove::quiet(
WhiteRook,
BoardSquare::from_coord(2, 0),
BoardSquare::from_coord(2, 1),
None,
),
ChessMove::quiet(
WhiteRook,
BoardSquare::from_coord(2, 0),
BoardSquare::from_coord(2, 2),
None,
),
ChessMove::quiet(
WhiteRook,
BoardSquare::from_coord(2, 0),
BoardSquare::from_coord(2, 3),
None,
),
ChessMove::quiet(
WhiteKing,
BoardSquare::from_coord(7, 4),
BoardSquare::from_coord(6, 3),
None,
),
ChessMove::quiet(
WhiteKing,
BoardSquare::from_coord(7, 4),
BoardSquare::from_coord(7, 5),
None,
),
],
];
for case in 0..2 {
let mut generated_moves = get_available_moves(boards[case]);
generated_moves.sort();
expected_moves[case].sort();
assert_eq!(generated_moves.len(), expected_moves[case].len());
assert_eq!(generated_moves, expected_moves[case]);
}
}
#[test]
fn is_game_over_test() {
let boards: [&str; 4] = [
"2k5/3pn3/2pP4/1R1P3B/1Np5/3RPp2/1B6/6Kb w - - 0 1",
"2K3B1/4P3/8/7p/4pPn1/1N1P1p1p/4bp2/2Rk4 b - - 0 1",
"6N1/B2PP3/pR1b4/3P2nb/6P1/3P1k2/2p5/4r1K1 w - - 0 1",
"3n1K2/p2k1p2/5P2/b1p2P2/P7/8/3p2r1/8 w - - 0 1",
];
let expected_results: [Option<GameEnd>; 4] = [
None,
Some(GameEnd::WhiteWon("".to_string())),
Some(GameEnd::BlackWon("".to_string())),
Some(GameEnd::Draw("".to_string())),
];
for case in 0..4 {
let fen = boards[case];
let actual = is_game_over(fen);
assert_eq!(actual, expected_results[case]);
}
}
}

View File

@@ -1,3 +1,5 @@
mod bitboard;
fn main() {
println!("Hello, world!");
}

31
engine/src/movetype.rs Normal file
View File

@@ -0,0 +1,31 @@
use serde::Deserialize;
use serde::Serialize;
use super::bitboard::bitmove::BitMoveType;
#[derive(Serialize, Deserialize)]
pub enum MoveType {
Quiet,
Capture,
Castle,
EnPassant,
}
impl MoveType {
pub(in super) fn from_bitmovetype(move_type: BitMoveType) -> Self {
return match move_type {
BitMoveType::Quiet => Self::Quiet,
BitMoveType::Capture => Self::Capture,
BitMoveType::Castle => Self::Castle,
BitMoveType::EnPassant => Self::EnPassant,
_ => panic!("invalid move_type index! should NEVER appear")
}
}
pub(in super) fn to_bitmoveType(&self) -> BitMoveType {
return match self {
&MoveType::Quiet => BitMoveType::Quiet,
&MoveType::Capture => BitMoveType::Capture,
&MoveType::Castle => BitMoveType::Castle,
&MoveType::EnPassant => BitMoveType::EnPassant
};
}
}

54
engine/src/piecetype.rs Normal file
View File

@@ -0,0 +1,54 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum PieceType {
WhitePawn,
WhiteKnight,
WhiteBishop,
WhiteRook,
WhiteQueen,
WhiteKing,
BlackPawn,
BlackKnight,
BlackBishop,
BlackRook,
BlackQueen,
BlackKing,
}
impl PieceType {
pub(super) fn from_index(idx: u8) -> Self {
return match idx {
0 => PieceType::WhitePawn,
1 => PieceType::WhiteKnight,
2 => PieceType::WhiteBishop,
3 => PieceType::WhiteRook,
4 => PieceType::WhiteQueen,
5 => PieceType::WhiteKing,
6 => PieceType::BlackPawn,
7 => PieceType::BlackKnight,
8 => PieceType::BlackBishop,
9 => PieceType::BlackRook,
10 => PieceType::BlackQueen,
11 => PieceType::BlackKing,
_ => panic!("invalid piece index! should NEVER appear"),
};
}
pub(super) fn to_index(&self) -> u8 {
return match self {
&PieceType::WhitePawn => 0,
&PieceType::WhiteKnight => 1,
&PieceType::WhiteBishop => 2,
&PieceType::WhiteRook => 3,
&PieceType::WhiteQueen => 4,
&PieceType::WhiteKing => 5,
&PieceType::BlackPawn => 6,
&PieceType::BlackKnight => 7,
&PieceType::BlackBishop => 8,
&PieceType::BlackRook => 9,
&PieceType::BlackQueen => 10,
&PieceType::BlackKing => 11,
};
}
}

1256
server/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,3 +4,21 @@ version = "0.1.0"
edition = "2024"
[dependencies]
tokio = { version = "1", features = ["full"] }
tokio-tungstenite = "0.21"
tungstenite = "0.21"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
futures-util = "0.3.31"
url = "2.5.7"
uuid = {version = "1.18.1", features = ["v4", "serde"] }
anyhow = "1.0.100"
rand = "0.9.2"
engine = {path = "../engine/"}
log = {version = "0.4.28"}
env_logger = "0.11.8"
[[bin]]
name = "client"
path = "src/bin/client.rs"

228
server/src/bin/client.rs Normal file
View File

@@ -0,0 +1,228 @@
use engine::gameend::GameEnd;
use engine::{boardsquare::BoardSquare, chessmove::ChessMove, piecetype::PieceType};
use futures_util::{SinkExt, StreamExt};
use serde::{Deserialize, Serialize};
use std::io::{self, Write};
use tokio_tungstenite::{connect_async, tungstenite::Message};
use url::Url;
use uuid::Uuid;
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
enum ClientMessage {
Join { username: String },
FindMatch,
Move { step: ChessMove, fen: String },
Resign,
Chat { text: String },
RequestLegalMoves { fen: String },
}
#[derive(Serialize, Deserialize)]
pub enum ServerMessage2 {
GameEnd {
winner: GameEnd,
},
UIUpdate {
fen: String,
},
MatchFound {
match_id: Uuid,
color: String,
opponent_name: String,
},
Ok {
response: Result<(), String>,
},
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Knightly Chess Client");
println!("========================");
// Get server address from user
print!("Enter server address [ws://127.0.0.1:9001]: ");
io::stdout().flush()?;
let mut server_addr = String::new();
io::stdin().read_line(&mut server_addr)?;
let server_addr = server_addr.trim();
let server_addr = if server_addr.is_empty() {
"ws://127.0.0.1:9001".to_string()
} else {
server_addr.to_string()
};
// Connect to server
println!("Connecting to {}...", server_addr);
let url = Url::parse(&server_addr)?;
let (ws_stream, _) = connect_async(url).await?;
println!("Connected to server!");
let (mut write, mut read) = ws_stream.split();
// Spawn a task to handle incoming messages
let read_handle = tokio::spawn(async move {
while let Some(message) = read.next().await {
match message {
Ok(msg) => {
if msg.is_text() {
let text = msg.to_text().unwrap();
println!("\nServer: {}", text);
// Try to parse as structured message
if let Ok(parsed) = serde_json::from_str::<ServerMessage2>(text) {
match parsed {
ServerMessage2::MatchFound {
match_id,
color,
opponent_name,
} => {
println!(
"opponent: {}, match_id: {}, color: {}",
opponent_name, match_id, color
);
}
_ => {
println!("cucc");
}
}
}
}
}
Err(e) => {
eprintln!("Error receiving message: {}", e);
break;
}
}
}
});
// Main loop for sending messages
println!("\nAvailable commands:");
println!(" join <username> - Join the server");
println!(" findmatch - Find a match");
println!(" move <from> <to> - Make a move (e.g., move e2 e4)");
println!(" chat <message> - Send chat message");
println!(" resign - Resign from current game");
println!(" quit - Exit client");
println!();
loop {
print!("➡️ Enter command: ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim();
if input.is_empty() {
continue;
}
let parts: Vec<&str> = input.split_whitespace().collect();
let command = parts[0].to_lowercase();
match command.as_str() {
"quit" | "exit" => {
println!("👋 Goodbye!");
break;
}
"join" => {
if parts.len() >= 2 {
let username = parts[1..].join(" ");
let message = ClientMessage::Join { username };
send_message(&mut write, &message).await?;
} else {
println!("Usage: join <username>");
}
}
"findmatch" | "find" => {
let message = ClientMessage::FindMatch;
send_message(&mut write, &message).await?;
println!("🔍 Searching for a match...");
}
"move" => {
if parts.len() >= 3 {
//let from = parts[1].to_string();
//let to = parts[2].to_string();
let fen = parts[1].to_string();
let step = ChessMove::quiet(
engine::piecetype::PieceType::WhiteBishop,
BoardSquare::new(),
BoardSquare { x: 1, y: 1 },
None,
);
let message = ClientMessage::Move { step, fen };
send_message(&mut write, &message).await?;
//println!("♟️ Sent move: {} -> {}", parts[1], parts[2]);
} else {
println!("Usage: move <from> <to> (e.g., move e2 e4)");
}
}
"chat" => {
if parts.len() >= 2 {
let text = parts[1..].join(" ");
let message = ClientMessage::Chat { text };
send_message(&mut write, &message).await?;
} else {
println!("Usage: chat <message>");
}
}
"resign" => {
let message = ClientMessage::Resign;
send_message(&mut write, &message).await?;
println!("Resigned from current game");
}
"help" => {
print_help();
}
"requestmoves" => {
if parts.len() >= 2 {
let fen = parts[1..].join(" ");
let message = ClientMessage::RequestLegalMoves { fen };
send_message(&mut write, &message).await?;
}
}
_ => {
println!(
"Unknown command: {}. Type 'help' for available commands.",
command
);
}
}
}
// Cleanup
read_handle.abort();
Ok(())
}
async fn send_message(
write: &mut futures_util::stream::SplitSink<
tokio_tungstenite::WebSocketStream<
tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
>,
Message,
>,
message: &ClientMessage,
) -> Result<(), Box<dyn std::error::Error>> {
let json = serde_json::to_string(message)?;
write.send(Message::Text(json)).await?;
Ok(())
}
fn print_help() {
println!("\n📖 Available Commands:");
println!(" join <username> - Register with a username");
println!(" findmatch - Enter matchmaking queue");
println!(" move <from> <to> - Make a chess move");
println!(" chat <message> - Send chat to opponent");
println!(" resign - Resign from current game");
println!(" help - Show this help");
println!(" quit - Exit the client");
println!(" requestmoves - Request the legal moves");
println!();
}

470
server/src/connection.rs Normal file
View File

@@ -0,0 +1,470 @@
use crate::connection::ClientEvent::*;
use crate::matchmaking;
use engine::chessmove::ChessMove;
use engine::gameend::GameEnd::{self, *};
use engine::{get_available_moves, is_game_over};
use futures_util::{SinkExt, StreamExt};
use log::{error, info, warn};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio::sync::Mutex;
use tokio_tungstenite::{WebSocketStream, tungstenite::Message};
use uuid::Uuid;
// Type definitions
pub type Tx = futures_util::stream::SplitSink<WebSocketStream<TcpStream>, Message>;
pub type ConnectionMap = Arc<Mutex<HashMap<Uuid, PlayerConnection>>>;
pub type MatchMap = Arc<Mutex<HashMap<Uuid, GameMatch>>>;
pub type WaitingQueue = Arc<Mutex<VecDeque<Uuid>>>;
pub async fn clean_up_match(matches: &MatchMap, match_id: &Uuid) {
matches.lock().await.remove(&match_id);
}
// Helper functions to create new instances
pub fn new_connection_map() -> ConnectionMap {
warn!("Created new connection map");
Arc::new(Mutex::new(HashMap::new()))
}
pub fn new_match_map() -> MatchMap {
warn!("Created new match map");
Arc::new(Mutex::new(HashMap::new()))
}
pub fn new_waiting_queue() -> WaitingQueue {
warn!("Created new waiting queue");
Arc::new(Mutex::new(VecDeque::new()))
}
#[derive(Serialize, Deserialize)]
pub enum ServerMessage2 {
GameEnd {
winner: GameEnd,
},
UIUpdate {
fen: String,
turn_player: String,
move_history: Vec<String>,
},
MatchFound {
match_id: Uuid,
color: String,
opponent_name: String,
},
LegalMoves {
moves: Vec<ChessMove>,
},
Ok {
response: Result<(), String>,
},
}
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
enum ClientEvent {
Join {
username: String,
},
FindMatch,
Move {
step: ChessMove,
turn_player: String,
},
Resign,
Chat {
text: String,
},
RequestLegalMoves {
fen: String,
},
CloseConnection,
}
#[derive(Debug)]
pub struct PlayerConnection {
pub id: Uuid,
pub username: Option<String>,
pub tx: Tx,
pub current_match: Option<Uuid>,
}
#[derive(Debug, Clone)]
pub struct GameMatch {
pub id: Uuid,
pub player_white: Uuid,
pub player_black: Uuid,
pub board_state: String,
pub move_history: Vec<String>,
}
// Message sending utilities
pub async fn send_message_to_player_connection(
connection: Option<&mut PlayerConnection>,
message: &str,
) -> Result<(), tokio_tungstenite::tungstenite::Error> {
match connection {
Some(connection) => {
info!("sending message to: {}", connection.id);
connection.tx.send(Message::Text(message.to_string())).await
}
None => {
error!("No connection provided");
Err(tokio_tungstenite::tungstenite::Error::ConnectionClosed)
}
}
}
pub async fn broadcast_to_all(connections: &ConnectionMap, message: &str) {
let mut connections_lock = connections.lock().await;
let mut dead_connections = Vec::new();
for (id, connection) in connections_lock.iter_mut() {
if let Err(e) = connection.tx.send(Message::Text(message.to_string())).await {
error!("Failed to send to {}: {}", id, e);
dead_connections.push(*id);
}
}
// Clean up dead connections
for dead_id in dead_connections {
connections_lock.remove(&dead_id);
}
}
pub async fn broadcast_to_match(
connections: &ConnectionMap,
matches: &MatchMap,
match_id: Uuid,
message: &str,
) -> Result<(), Box<dyn std::error::Error>> {
info!("Broadcasting data to match: {}", &match_id);
let matches_lock = matches.lock().await;
if let Some(game_match) = matches_lock.get(&match_id) {
send_message_to_player_connection(
connections.lock().await.get_mut(&game_match.player_white),
message,
)
.await?;
send_message_to_player_connection(
connections.lock().await.get_mut(&game_match.player_black),
message,
)
.await?;
}
Ok(())
}
// Connection handler
pub async fn handle_connection(
stream: TcpStream,
connections: ConnectionMap,
matches: MatchMap,
waiting_queue: WaitingQueue,
) -> anyhow::Result<()> {
use tokio_tungstenite::accept_async;
let ws_stream = accept_async(stream).await?;
let (write, mut read) = ws_stream.split();
warn!("Accepted new connection");
let player_id = Uuid::new_v4();
// Store the connection
{
let mut conn_map = connections.lock().await;
conn_map.insert(
player_id,
PlayerConnection {
id: player_id,
username: None,
tx: write,
current_match: None,
},
);
}
info!("id: {}", &player_id);
println!("\n\n\n");
println!(
"{:?}",
engine::get_available_moves("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
);
println!("\n\n\n");
// Message processing loop
while let Some(Ok(message)) = read.next().await {
if message.is_text() {
let text = message.to_text()?;
info!("Received from {}: {}", player_id, text);
let client_data: ClientEvent = serde_json::from_str(text)
.expect("Failed to convert data into json at handle_connection");
match client_data {
Join { username } => {
{
let mut conn_map = connections.lock().await;
let player = conn_map.get_mut(&player_id).unwrap();
player.username = Some(username.clone());
info!("player: {}, set username: {}", &player_id, username);
}
//respone to client
let response = ServerMessage2::Ok { response: Ok(()) };
let mut conn_map = connections.lock().await;
let _ = send_message_to_player_connection(
conn_map.get_mut(&player_id),
&serde_json::to_string(&response).unwrap(),
)
.await;
}
FindMatch => {
let mut wait_queue = waiting_queue.lock().await;
wait_queue.push_back(player_id.clone());
info!("Appended {} to the waiting queue", player_id);
info!("queue {:?}", wait_queue);
}
Move { step, turn_player } => {
let match_id = connections
.lock()
.await
.get(&player_id)
.unwrap()
.current_match
.unwrap();
println!("\n\nstep: {:?}\n", step);
{
info!("updating board state in match: {}", &match_id);
let mut matches = matches.lock().await;
matches.get_mut(&match_id).unwrap().board_state =
engine::get_board_after_move(
&matches.get(&match_id).unwrap().board_state,
&step,
);
info!(
"board after engine fn: {}",
matches.get_mut(&match_id).unwrap().board_state.clone()
);
matches
.get_mut(&match_id)
.unwrap()
.move_history
.push(step.clone().notation());
}
let message = ServerMessage2::UIUpdate {
fen: {
let mut matches = matches.lock().await;
matches.get(&match_id).unwrap().board_state.clone()
},
turn_player: turn_player,
move_history: {
let mut matches = matches.lock().await;
matches.get(&match_id).unwrap().move_history.clone()
},
};
let _ = broadcast_to_match(
&connections,
&matches,
match_id,
&serde_json::to_string(&message).unwrap(),
)
.await;
{
let is_game_end = engine::is_game_over(
&matches.lock().await.get(&match_id).unwrap().board_state,
);
match is_game_end {
Some(res) => {
warn!("A player won the match: {}", &match_id);
let message = ServerMessage2::GameEnd { winner: res };
let _ = broadcast_to_match(
&connections,
&matches,
match_id,
&serde_json::to_string(&message).unwrap(),
)
.await;
clean_up_match(&matches, &match_id).await;
}
None => {
info!("No winner match continues. Id: {}", &match_id);
}
}
}
}
RequestLegalMoves { fen } => {
info!("Requesting legal moves player: {}", &player_id);
let moves = get_available_moves(&fen);
let message = ServerMessage2::LegalMoves { moves };
let _ = send_message_to_player_connection(
connections.lock().await.get_mut(&player_id),
&serde_json::to_string(&message).unwrap(),
)
.await;
info!("Sent moves to player: {}", player_id);
}
Resign => {
warn!("Resigned!");
let (fuck, fuck_id): (ServerMessage2, &Uuid) = {
let matches = matches.lock().await;
let curr_match = matches
.get(
&connections
.lock()
.await
.get(&player_id)
.unwrap()
.current_match
.unwrap(),
)
.unwrap();
if player_id == curr_match.player_white {
(
ServerMessage2::GameEnd {
winner: GameEnd::BlackWon("Resigned".to_string()),
},
&connections
.lock()
.await
.get(&player_id)
.unwrap()
.current_match
.unwrap(),
)
} else {
(
ServerMessage2::GameEnd {
winner: GameEnd::WhiteWon("Resigned".to_string()),
},
&connections
.lock()
.await
.get(&player_id)
.unwrap()
.current_match
.unwrap(),
)
}
};
let _ = broadcast_to_match(
&connections,
&matches,
fuck_id.clone(),
&serde_json::to_string(&fuck).unwrap(),
)
.await;
clean_up_match(&matches, fuck_id).await;
}
CloseConnection => {
warn!("Closing connection for: {}", &player_id);
break;
}
_ => {
warn!("Not known client event");
}
}
}
}
// Cleanup on disconnect
cleanup_player(player_id, &connections, &matches, &waiting_queue).await;
warn!("Connection {} closed", player_id);
Ok(())
}
async fn cleanup_player(
player_id: Uuid,
connections: &ConnectionMap,
_matches: &MatchMap,
waiting_queue: &WaitingQueue,
) {
// Remove from waiting queue
waiting_queue.lock().await.retain(|&id| id != player_id);
// Remove from connections
connections.lock().await.remove(&player_id);
warn!("Cleaned up player {}", player_id);
}
#[cfg(test)]
mod tests {
use super::*;
use uuid::Uuid;
#[tokio::test]
async fn test_send_message_to_nonexistent_player() {
let connections = new_connection_map();
let player_id = Uuid::new_v4();
// Test 1: Pass None directly (non-existent player)
let result = send_message_to_player_connection(None, "test message").await;
assert!(result.is_err(), "Should return error for None connection");
println!("Test passed: Handles None connection correctly");
// Test 2: Try to get non-existent player from map
let mut conn = connections.lock().await;
let non_existent_connection = conn.get_mut(&player_id); // This will be None
let result2 =
send_message_to_player_connection(non_existent_connection, "test message").await;
assert!(
result2.is_err(),
"Should return error for non-existent player"
);
println!("Test passed: Handles non-existent player in map correctly");
}
#[tokio::test]
async fn test_broadcast_to_empty_connections() {
let connections = new_connection_map();
broadcast_to_all(&connections, "test broadcast").await;
let conn_map = connections.lock().await;
assert!(conn_map.is_empty(), "Connections should still be empty");
}
#[tokio::test]
async fn test_connection_cleanup() {
let connections = new_connection_map();
let matches = new_match_map();
let waiting_queue = new_waiting_queue();
let player_id = Uuid::new_v4();
{
waiting_queue.lock().await.push_back(player_id);
assert_eq!(waiting_queue.lock().await.len(), 1);
}
cleanup_player(player_id, &connections, &matches, &waiting_queue).await;
{
let queue = waiting_queue.lock().await;
assert!(
!queue.contains(&player_id),
"Player should be removed from waiting queue"
);
}
}
}

View File

@@ -1,3 +1,47 @@
fn main() {
println!("Hello, world!");
mod connection;
mod matchmaking;
use env_logger::Env;
use log::{error, info};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let env = Env::default().filter_or("MY_LOG_LEVEL", "INFO");
env_logger::init_from_env(env);
let address = "0.0.0.0:9001";
let listener = TcpListener::bind(address).await?;
info!("Server running on ws://{}", address);
// Shared state initialization using the new helper functions
let connections = connection::new_connection_map();
let matches = connection::new_match_map();
let waiting_queue = connection::new_waiting_queue();
// Start matchmaking background task
let matchmaker = matchmaking::MatchmakingSystem::new(
connections.clone(),
matches.clone(),
waiting_queue.clone(),
);
tokio::spawn(async move {
matchmaker.run().await;
});
// Main connection loop
while let Ok((stream, _)) = listener.accept().await {
let connections = connections.clone();
let matches = matches.clone();
let waiting_queue = waiting_queue.clone();
tokio::spawn(async move {
if let Err(e) =
connection::handle_connection(stream, connections, matches, waiting_queue).await
{
error!("Connection error: {}", e);
}
});
}
Ok(())
}

209
server/src/matchmaking.rs Normal file
View File

@@ -0,0 +1,209 @@
use crate::connection::ServerMessage2;
use crate::connection::{ConnectionMap, GameMatch, MatchMap, WaitingQueue};
use log::{error, info, warn};
use rand::random;
use uuid::Uuid;
pub struct MatchmakingSystem {
connections: ConnectionMap,
matches: MatchMap,
waiting_queue: WaitingQueue,
}
impl MatchmakingSystem {
pub fn new(connections: ConnectionMap, matches: MatchMap, waiting_queue: WaitingQueue) -> Self {
Self {
connections,
matches,
waiting_queue,
}
}
pub async fn run(&self) {
loop {
self.try_create_match().await;
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
}
}
async fn try_create_match(&self) {
//info!("Checking for new matches!");
let mut queue = self.waiting_queue.lock().await;
while queue.len() >= 2 {
let player1 = queue.pop_front().unwrap();
let player2 = queue.pop_front().unwrap();
info!("Creating new match. Players: {}, {}", &player1, &player2);
let match_id = Uuid::new_v4();
let (white_player, black_player) = if random::<bool>() {
info!("player1 is white, player2 is black");
(player1, player2)
} else {
info!("player2 is white, player1 is black");
(player2, player1)
};
let game_match = GameMatch {
id: match_id,
player_white: white_player,
player_black: black_player,
board_state: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1".to_string(),
move_history: Vec::new(),
};
info!("Match id: {}", &game_match.id);
// Store the match
self.matches.lock().await.insert(match_id, game_match);
// Update player connections
{
let mut conn_map = self.connections.lock().await;
if let Some(player) = conn_map.get_mut(&white_player) {
player.current_match = Some(match_id);
} else {
error!("Could not store match id for white player");
}
if let Some(player) = conn_map.get_mut(&black_player) {
player.current_match = Some(match_id);
//TODO: at the end of a match delete this from player
} else {
error!("Could not store match id for black player");
}
}
// Notify players
info!(
"Notifying player for a match: {:?} | {:?}",
white_player, black_player
);
self.notify_players(white_player, black_player, match_id)
.await;
}
}
async fn notify_players(&self, white: Uuid, black: Uuid, match_id: Uuid) {
let mut conn_map = self.connections.lock().await;
// Notify white player
if let Some(_) = conn_map.get(&white) {
let message = ServerMessage2::MatchFound {
match_id: match_id.clone(),
color: String::from("white"),
opponent_name: conn_map
.get(&white)
.and_then(|c| c.username.as_deref())
.unwrap_or("Opponent")
.to_string(),
};
let _ = crate::connection::send_message_to_player_connection(
conn_map.get_mut(&white),
&serde_json::to_string(&message).unwrap(),
)
.await;
}
// Notify black player
if let Some(_) = conn_map.get(&black) {
let message = ServerMessage2::MatchFound {
match_id: match_id.clone(),
color: String::from("black"),
opponent_name: conn_map
.get(&black)
.and_then(|c| c.username.as_deref())
.unwrap_or("Opponent")
.to_string(),
};
let _ = crate::connection::send_message_to_player_connection(
conn_map.get_mut(&black),
&serde_json::to_string(&message).unwrap(),
)
.await;
}
info!("Match created: {} (white) vs {} (black)", white, black);
}
}
#[cfg(test)]
mod tests {
use super::*;
use uuid::Uuid;
use crate::connection::new_connection_map;
use crate::connection::new_match_map;
use crate::connection::new_waiting_queue;
#[tokio::test]
async fn test_matchmaking_creates_matches() {
let connections = new_connection_map();
let matches = new_match_map();
let waiting_queue = new_waiting_queue();
let matchmaking =
MatchmakingSystem::new(connections.clone(), matches.clone(), waiting_queue.clone());
let player1 = Uuid::new_v4();
let player2 = Uuid::new_v4();
{
waiting_queue.lock().await.push_back(player1);
waiting_queue.lock().await.push_back(player2);
}
matchmaking.try_create_match().await;
{
let matches_map = matches.lock().await;
assert_eq!(matches_map.len(), 1, "Should create one match");
let game_match = matches_map.values().next().unwrap();
assert!(game_match.player_white == player1 || game_match.player_white == player2);
assert!(game_match.player_black == player1 || game_match.player_black == player2);
assert_ne!(
game_match.player_white, game_match.player_black,
"Players should be different"
);
}
{
let queue = waiting_queue.lock().await;
assert!(
queue.is_empty(),
"Waiting queue should be empty after matchmaking"
);
}
}
#[tokio::test]
async fn test_matchmaking_with_odd_players() {
let connections = new_connection_map();
let matches = new_match_map();
let waiting_queue = new_waiting_queue();
let matchmaking =
MatchmakingSystem::new(connections.clone(), matches.clone(), waiting_queue.clone());
let player1 = Uuid::new_v4();
{
waiting_queue.lock().await.push_back(player1);
}
matchmaking.try_create_match().await;
{
let matches_map = matches.lock().await;
assert!(
matches_map.is_empty(),
"Should not create match with only one player"
);
let queue = waiting_queue.lock().await;
assert_eq!(queue.len(), 1, "Should keep single player in queue");
}
}
}

4371
ui/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,3 +4,19 @@ version = "0.1.0"
edition = "2024"
[dependencies]
eframe = "0.33.0"
egui = "0.33.0"
winit = "0.30.12"
tokio = { version = "1", features = ["full"] }
tokio-tungstenite = "0.21"
tungstenite = "0.21"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
futures-util = "0.3.31"
url = "2.5.7"
uuid = {version = "1.18.1", features = ["v4", "serde"] }
engine = {path = "../engine/"}
log = {version = "0.4.28"}
env_logger = "0.11.8"
local-ip-address = "0.6.5"
anyhow = "1.0.100"

BIN
ui/fonts/DejaVuSans.ttf Normal file

Binary file not shown.

117
ui/src/connection.rs Normal file
View File

@@ -0,0 +1,117 @@
use engine::{chessmove::ChessMove, gameend::GameEnd};
use futures_util::{SinkExt, StreamExt};
use local_ip_address::local_ip;
use log::{error, info, warn};
use serde::{Deserialize, Serialize};
use std::{
net::{IpAddr, Ipv4Addr},
sync::{Arc, Mutex},
};
use tokio_tungstenite::connect_async;
use tungstenite::Message;
use url::Url;
use uuid::Uuid;
use crate::{ChessApp, ClientEvent, SharedGameState};
#[derive(Serialize, Deserialize)]
pub enum ServerMessage2 {
GameEnd {
winner: GameEnd,
},
UIUpdate {
fen: String,
},
MatchFound {
match_id: Uuid,
color: String,
opponent_name: String,
},
Ok {
response: Result<(), String>,
},
}
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
enum ClientMessage {
Join { username: String },
FindMatch,
Move { step: ChessMove, fen: String },
Resign,
Chat { text: String },
RequestLegalMoves { fen: String },
}
fn get_ip_address() -> IpAddr {
let ip = local_ip().unwrap_or(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
ip
}
pub async fn handle_connection(
server_port: &str,
shared_state: SharedGameState,
ui_events: Arc<Mutex<Vec<ClientEvent>>>,
) -> anyhow::Result<()> {
let address = get_ip_address();
//start main loop
let server_address = String::from("ws://") + &address.to_string() + ":" + server_port;
warn!(
"Machine IpAddress is bound for listener. Ip: {}",
server_address
);
let url = Url::parse(&server_address)?;
let (ws_stream, _) = connect_async(url).await?;
let (mut write, mut read) = ws_stream.split();
while let Some(message) = read.next().await {
info!("connection");
match message {
Ok(msg) => {
if msg.is_text() {
let text = msg.to_text().unwrap();
info!("text: {}", text);
/*if let Ok(parsed) = serde_json::from_str::<ServerMessage2>(text) {
match parsed {
ServerMessage2::GameEnd { winner } => {}
ServerMessage2::UIUpdate { fen } => {}
ServerMessage2::MatchFound {
match_id,
color,
opponent_name,
} => {
//chess_app.player_color = Some(color);
}
ServerMessage2::Ok { response } => {}
_ => {
error!("Received unkown servermessage2");
}
}
}*/
if let Ok(parsed) = serde_json::from_str::<ServerMessage2>(text) {
// Update shared state with server message
shared_state.update_from_server_message(parsed);
}
// Send UI events to server
let events = ui_events.lock().unwrap().drain(..).collect::<Vec<_>>();
for event in events {
let message = serde_json::to_string(&event)?;
write.send(Message::Text(message)).await?;
}
}
}
Err(e) => {
error!("Error receiving message: {}", e);
}
}
}
Ok(())
}

File diff suppressed because it is too large Load Diff