Chess.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965
  1. /*
  2. * Copyright (c) 2020, the SerenityOS developers.
  3. * Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/Assertions.h>
  8. #include <AK/CharacterTypes.h>
  9. #include <AK/String.h>
  10. #include <AK/StringBuilder.h>
  11. #include <AK/Vector.h>
  12. #include <LibChess/Chess.h>
  13. #include <stdlib.h>
  14. namespace Chess {
  15. Optional<char> char_for_piece(Type type, Notation notation)
  16. {
  17. switch (type) {
  18. case Type::Knight:
  19. return 'N';
  20. case Type::Bishop:
  21. return 'B';
  22. case Type::Rook:
  23. return 'R';
  24. case Type::Queen:
  25. return 'Q';
  26. case Type::King:
  27. return 'K';
  28. case Type::Pawn:
  29. if (notation == Notation::FEN)
  30. return 'P';
  31. return {};
  32. case Type::None:
  33. return {};
  34. }
  35. VERIFY_NOT_REACHED();
  36. }
  37. Type piece_from_char(char c)
  38. {
  39. switch (to_ascii_lowercase(c)) {
  40. case 'n':
  41. return Type::Knight;
  42. case 'b':
  43. return Type::Bishop;
  44. case 'r':
  45. return Type::Rook;
  46. case 'q':
  47. return Type::Queen;
  48. case 'k':
  49. return Type::King;
  50. case 'p':
  51. return Type::Pawn;
  52. }
  53. return Type::None;
  54. }
  55. Color opposing_color(Color color)
  56. {
  57. return (color == Color::White) ? Color::Black : Color::White;
  58. }
  59. Square::Square(StringView name)
  60. {
  61. VERIFY(name.length() == 2);
  62. char filec = name[0];
  63. char rankc = name[1];
  64. if (filec >= 'a' && filec <= 'h') {
  65. file = filec - 'a';
  66. } else if (filec >= 'A' && filec <= 'H') {
  67. file = filec - 'A';
  68. } else {
  69. VERIFY_NOT_REACHED();
  70. }
  71. if (rankc >= '1' && rankc <= '8') {
  72. rank = rankc - '1';
  73. } else {
  74. VERIFY_NOT_REACHED();
  75. }
  76. }
  77. char Square::file_char() const
  78. {
  79. return file + 'a';
  80. }
  81. char Square::rank_char() const
  82. {
  83. return rank + '1';
  84. }
  85. ErrorOr<String> Square::to_algebraic() const
  86. {
  87. return String::formatted("{}{}", file_char(), rank_char());
  88. }
  89. Move::Move(StringView long_algebraic)
  90. : from(long_algebraic.substring_view(0, 2))
  91. , to(long_algebraic.substring_view(2, 2))
  92. , promote_to((long_algebraic.length() >= 5) ? piece_from_char(long_algebraic[4]) : Type::None)
  93. {
  94. }
  95. ErrorOr<String> Move::to_long_algebraic() const
  96. {
  97. StringBuilder builder;
  98. TRY(builder.try_append(TRY(from.to_algebraic())));
  99. TRY(builder.try_append(TRY(to.to_algebraic())));
  100. if (auto promoted_char = char_for_piece(promote_to, Notation::Algebraic); promoted_char.has_value())
  101. TRY(builder.try_append(to_ascii_lowercase(promoted_char.value())));
  102. return builder.to_string();
  103. }
  104. Move Move::from_algebraic(StringView algebraic, Color const turn, Board const& board)
  105. {
  106. auto move_string = algebraic;
  107. Move move({ 50, 50 }, { 50, 50 });
  108. if (move_string.contains('-')) {
  109. move.from = Square(turn == Color::White ? 0 : 7, 4);
  110. move.to = Square(turn == Color::White ? 0 : 7, move_string == "O-O" ? 6 : 2);
  111. move.promote_to = Type::None;
  112. move.piece = { turn, Type::King };
  113. return move;
  114. }
  115. if (algebraic.contains('#')) {
  116. move.is_mate = true;
  117. move_string = move_string.substring_view(0, move_string.length() - 1);
  118. } else if (algebraic.contains('+')) {
  119. move.is_check = true;
  120. move_string = move_string.substring_view(0, move_string.length() - 1);
  121. }
  122. if (algebraic.contains('=')) {
  123. auto parts = move_string.split_view('=');
  124. move.promote_to = piece_from_char(parts[1][0]);
  125. move_string = parts[0];
  126. }
  127. move.to = Square(move_string.substring_view(move_string.length() - 2, 2));
  128. move_string = move_string.substring_view(0, move_string.length() - 2);
  129. if (move_string.contains('x')) {
  130. move.is_capture = true;
  131. move_string = move_string.substring_view(0, move_string.length() - 1);
  132. }
  133. if (move_string.is_empty() || move_string[0] >= 'a') {
  134. move.piece = Piece(turn, Type::Pawn);
  135. } else {
  136. move.piece = Piece(turn, piece_from_char(move_string[0]));
  137. move_string = move_string.substring_view(1, move_string.length() - 1);
  138. }
  139. Square::for_each([&](Square const& square) {
  140. if (!move_string.is_empty()) {
  141. if (board.get_piece(square).type == move.piece.type && board.is_legal(Move(square, move.to), turn)) {
  142. if (move_string.length() >= 2) {
  143. if (square == Square(move_string.substring_view(0, 2))) {
  144. move.from = square;
  145. return IterationDecision::Break;
  146. }
  147. } else if (move_string[0] <= 57) {
  148. if (square.rank == (move_string[0] - '0')) {
  149. move.from = square;
  150. return IterationDecision::Break;
  151. }
  152. } else {
  153. if (square.file == (move_string[0] - 'a')) {
  154. move.from = square;
  155. return IterationDecision::Break;
  156. }
  157. }
  158. }
  159. return IterationDecision::Continue;
  160. } else {
  161. if (board.get_piece(square).type == move.piece.type && board.is_legal(Move(square, move.to, move.promote_to), turn)) {
  162. move.from = square;
  163. return IterationDecision::Break;
  164. }
  165. return IterationDecision::Continue;
  166. }
  167. });
  168. return move;
  169. }
  170. ErrorOr<String> Move::to_algebraic() const
  171. {
  172. if (piece.type == Type::King && from.file == 4) {
  173. if (to.file == 2)
  174. return "O-O-O"_string;
  175. if (to.file == 6)
  176. return "O-O"_string;
  177. }
  178. StringBuilder builder;
  179. if (auto piece_char = char_for_piece(piece.type, Notation::Algebraic); piece_char.has_value())
  180. TRY(builder.try_append(*piece_char));
  181. if (is_ambiguous) {
  182. if (from.file != ambiguous.file)
  183. TRY(builder.try_append(from.file_char()));
  184. else if (from.rank != ambiguous.rank)
  185. TRY(builder.try_append(from.rank_char()));
  186. else
  187. TRY(builder.try_append(TRY(from.to_algebraic())));
  188. }
  189. if (is_capture) {
  190. if (piece.type == Type::Pawn && !is_ambiguous)
  191. TRY(builder.try_append(from.file_char()));
  192. TRY(builder.try_append('x'));
  193. }
  194. TRY(builder.try_append(TRY(to.to_algebraic())));
  195. if (promote_to != Type::None && promote_to != Type::Pawn) {
  196. TRY(builder.try_append('='));
  197. TRY(builder.try_append(char_for_piece(promote_to, Notation::Algebraic).value()));
  198. }
  199. if (is_mate)
  200. TRY(builder.try_append('#'));
  201. else if (is_check)
  202. TRY(builder.try_append('+'));
  203. return builder.to_string();
  204. }
  205. Board::Board()
  206. {
  207. // Fill empty spaces.
  208. for (int rank = 2; rank < 6; ++rank) {
  209. for (int file = 0; file < 8; ++file) {
  210. set_piece({ rank, file }, EmptyPiece);
  211. }
  212. }
  213. // Fill white pawns.
  214. for (int file = 0; file < 8; ++file) {
  215. set_piece({ 1, file }, { Color::White, Type::Pawn });
  216. }
  217. // Fill black pawns.
  218. for (int file = 0; file < 8; ++file) {
  219. set_piece({ 6, file }, { Color::Black, Type::Pawn });
  220. }
  221. // Fill while pieces.
  222. set_piece(Square("a1"), { Color::White, Type::Rook });
  223. set_piece(Square("b1"), { Color::White, Type::Knight });
  224. set_piece(Square("c1"), { Color::White, Type::Bishop });
  225. set_piece(Square("d1"), { Color::White, Type::Queen });
  226. set_piece(Square("e1"), { Color::White, Type::King });
  227. set_piece(Square("f1"), { Color::White, Type::Bishop });
  228. set_piece(Square("g1"), { Color::White, Type::Knight });
  229. set_piece(Square("h1"), { Color::White, Type::Rook });
  230. // Fill black pieces.
  231. set_piece(Square("a8"), { Color::Black, Type::Rook });
  232. set_piece(Square("b8"), { Color::Black, Type::Knight });
  233. set_piece(Square("c8"), { Color::Black, Type::Bishop });
  234. set_piece(Square("d8"), { Color::Black, Type::Queen });
  235. set_piece(Square("e8"), { Color::Black, Type::King });
  236. set_piece(Square("f8"), { Color::Black, Type::Bishop });
  237. set_piece(Square("g8"), { Color::Black, Type::Knight });
  238. set_piece(Square("h8"), { Color::Black, Type::Rook });
  239. }
  240. Board Board::clone_without_history() const
  241. {
  242. // Note: When used in the MCTSTree, the board doesn't need to have all information about previous states.
  243. // It spares a huge amount of memory.
  244. auto result = *this;
  245. result.m_moves.clear();
  246. result.m_previous_states.clear();
  247. return result;
  248. }
  249. ErrorOr<String> Board::to_fen() const
  250. {
  251. StringBuilder builder;
  252. // 1. Piece placement
  253. int empty = 0;
  254. for (int rank = 0; rank < 8; rank++) {
  255. for (int file = 0; file < 8; file++) {
  256. Piece const p(get_piece({ 7 - rank, file }));
  257. if (p.type == Type::None) {
  258. empty++;
  259. continue;
  260. }
  261. if (empty > 0) {
  262. TRY(builder.try_appendff("{}", empty));
  263. empty = 0;
  264. }
  265. auto const piece = char_for_piece(p.type, Notation::FEN).value();
  266. if (p.color == Color::Black)
  267. TRY(builder.try_append(to_ascii_lowercase(piece)));
  268. else
  269. TRY(builder.try_append(piece));
  270. }
  271. if (empty > 0) {
  272. TRY(builder.try_appendff("{}", empty));
  273. empty = 0;
  274. }
  275. if (rank < 7)
  276. TRY(builder.try_append('/'));
  277. }
  278. // 2. Active color
  279. VERIFY(m_turn != Color::None);
  280. TRY(builder.try_append(m_turn == Color::White ? " w "sv : " b "sv));
  281. // 3. Castling availability
  282. if (m_white_can_castle_kingside)
  283. TRY(builder.try_append('K'));
  284. if (m_white_can_castle_queenside)
  285. TRY(builder.try_append('Q'));
  286. if (m_black_can_castle_kingside)
  287. TRY(builder.try_append('k'));
  288. if (m_black_can_castle_queenside)
  289. TRY(builder.try_append('q'));
  290. TRY(builder.try_append(' '));
  291. // 4. En passant target square
  292. if (!m_last_move.has_value())
  293. TRY(builder.try_append('-'));
  294. else if (m_last_move.value().piece.type == Type::Pawn) {
  295. if (m_last_move.value().from.rank == 1 && m_last_move.value().to.rank == 3)
  296. TRY(builder.try_append(TRY(Square(m_last_move.value().to.rank - 1, m_last_move.value().to.file).to_algebraic())));
  297. else if (m_last_move.value().from.rank == 6 && m_last_move.value().to.rank == 4)
  298. TRY(builder.try_append(TRY(Square(m_last_move.value().to.rank + 1, m_last_move.value().to.file).to_algebraic())));
  299. else
  300. TRY(builder.try_append('-'));
  301. } else {
  302. TRY(builder.try_append('-'));
  303. }
  304. TRY(builder.try_append(' '));
  305. // 5. Halfmove clock
  306. TRY(builder.try_appendff("{}", (min(m_moves_since_capture, m_moves_since_pawn_advance))));
  307. TRY(builder.try_append(' '));
  308. // 6. Fullmove number
  309. TRY(builder.try_appendff("{}", (1 + m_moves.size() / 2)));
  310. return builder.to_string();
  311. }
  312. Piece Board::get_piece(Square const& square) const
  313. {
  314. VERIFY(square.in_bounds());
  315. return m_board[square.rank][square.file];
  316. }
  317. Piece Board::set_piece(Square const& square, Piece const& piece)
  318. {
  319. VERIFY(square.in_bounds());
  320. return m_board[square.rank][square.file] = piece;
  321. }
  322. bool Board::is_legal_promotion(Move const& move, Color color) const
  323. {
  324. auto piece = get_piece(move.from);
  325. if (move.promote_to == Type::Pawn || move.promote_to == Type::King) {
  326. // attempted promotion to invalid piece
  327. return false;
  328. }
  329. if (piece.type != Type::Pawn && move.promote_to != Type::None) {
  330. // attempted promotion from invalid piece
  331. return false;
  332. }
  333. int promotion_rank = (color == Color::White) ? 7 : 0;
  334. if (move.to.rank != promotion_rank && move.promote_to != Type::None) {
  335. // attempted promotion from invalid rank
  336. return false;
  337. }
  338. if (piece.type == Type::Pawn && move.to.rank == promotion_rank && move.promote_to == Type::None) {
  339. // attempted move to promotion rank without promoting
  340. return false;
  341. }
  342. return true;
  343. }
  344. bool Board::is_legal(Move const& move, Color color) const
  345. {
  346. if (color == Color::None)
  347. color = turn();
  348. if (!is_legal_no_check(move, color))
  349. return false;
  350. if (!is_legal_promotion(move, color))
  351. return false;
  352. Board clone = *this;
  353. clone.apply_illegal_move(move, color);
  354. if (clone.in_check(color))
  355. return false;
  356. // Don't allow castling through check or out of check.
  357. Vector<Square> check_squares;
  358. if (color == Color::White && move.from == Square("e1") && get_piece(Square("e1")) == Piece(Color::White, Type::King)) {
  359. if (move.to == Square("a1") || move.to == Square("c1")) {
  360. check_squares = { Square("e1"), Square("d1"), Square("c1") };
  361. } else if (move.to == Square("h1") || move.to == Square("g1")) {
  362. check_squares = { Square("e1"), Square("f1"), Square("g1") };
  363. }
  364. } else if (color == Color::Black && move.from == Square("e8") && get_piece(Square("e8")) == Piece(Color::Black, Type::King)) {
  365. if (move.to == Square("a8") || move.to == Square("c8")) {
  366. check_squares = { Square("e8"), Square("d8"), Square("c8") };
  367. } else if (move.to == Square("h8") || move.to == Square("g8")) {
  368. check_squares = { Square("e8"), Square("f8"), Square("g8") };
  369. }
  370. }
  371. for (auto& square : check_squares) {
  372. Board clone = *this;
  373. clone.set_piece(move.from, EmptyPiece);
  374. clone.set_piece(square, { color, Type::King });
  375. if (clone.in_check(color))
  376. return false;
  377. }
  378. return true;
  379. }
  380. bool Board::is_legal_no_check(Move const& move, Color color) const
  381. {
  382. auto piece = get_piece(move.from);
  383. if (piece.color != color)
  384. // attempted move of opponent's piece
  385. return false;
  386. if (!move.to.in_bounds())
  387. // attempted move outside of board
  388. return false;
  389. // Check castling first to allow dragging king onto the rook.
  390. if (piece.type == Type::King) {
  391. if (color == Color::White) {
  392. if ((move.to == Square("a1") || move.to == Square("c1")) && m_white_can_castle_queenside && get_piece(Square("b1")).type == Type::None && get_piece(Square("c1")).type == Type::None && get_piece(Square("d1")).type == Type::None) {
  393. return true;
  394. } else if ((move.to == Square("h1") || move.to == Square("g1")) && m_white_can_castle_kingside && get_piece(Square("f1")).type == Type::None && get_piece(Square("g1")).type == Type::None) {
  395. return true;
  396. }
  397. } else {
  398. if ((move.to == Square("a8") || move.to == Square("c8")) && m_black_can_castle_queenside && get_piece(Square("b8")).type == Type::None && get_piece(Square("c8")).type == Type::None && get_piece(Square("d8")).type == Type::None) {
  399. return true;
  400. } else if ((move.to == Square("h8") || move.to == Square("g8")) && m_black_can_castle_kingside && get_piece(Square("f8")).type == Type::None && get_piece(Square("g8")).type == Type::None) {
  401. return true;
  402. }
  403. }
  404. }
  405. if (piece.color == get_piece(move.to).color)
  406. // Attempted move to a square occupied by a piece of the same color.
  407. return false;
  408. if (piece.type == Type::Pawn) {
  409. int dir = (color == Color::White) ? +1 : -1;
  410. int start_rank = (color == Color::White) ? 1 : 6;
  411. if (move.from.rank == start_rank && move.to.rank == move.from.rank + (2 * dir) && move.to.file == move.from.file
  412. && get_piece(move.to).type == Type::None && get_piece({ move.from.rank + dir, move.from.file }).type == Type::None) {
  413. // 2 square pawn move from initial position.
  414. return true;
  415. }
  416. if (move.to.rank != move.from.rank + dir)
  417. // attempted backwards or sideways move
  418. return false;
  419. if (move.to.file == move.from.file && get_piece(move.to).type == Type::None) {
  420. // Regular pawn move.
  421. return true;
  422. }
  423. if (move.to.file == move.from.file + 1 || move.to.file == move.from.file - 1) {
  424. int other_start_rank = (color == Color::White) ? 6 : 1;
  425. int en_passant_rank = (color == Color::White) ? 4 : 3;
  426. Move en_passant_last_move = { { other_start_rank, move.to.file }, { en_passant_rank, move.to.file } };
  427. if (get_piece(move.to).color == opposing_color(color)) {
  428. // Pawn capture.
  429. return true;
  430. }
  431. if (m_last_move.has_value() && move.from.rank == en_passant_rank && m_last_move.value() == en_passant_last_move
  432. && get_piece(en_passant_last_move.to) == Piece(opposing_color(color), Type::Pawn)) {
  433. // En passant.
  434. return true;
  435. }
  436. }
  437. return false;
  438. } else if (piece.type == Type::Knight) {
  439. int rank_delta = abs(move.to.rank - move.from.rank);
  440. int file_delta = abs(move.to.file - move.from.file);
  441. if (max(rank_delta, file_delta) == 2 && min(rank_delta, file_delta) == 1) {
  442. return true;
  443. }
  444. } else if (piece.type == Type::Bishop) {
  445. int rank_delta = move.to.rank - move.from.rank;
  446. int file_delta = move.to.file - move.from.file;
  447. if (abs(rank_delta) == abs(file_delta)) {
  448. int dr = rank_delta / abs(rank_delta);
  449. int df = file_delta / abs(file_delta);
  450. for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
  451. if (get_piece(sq).type != Type::None && sq != move.from) {
  452. return false;
  453. }
  454. }
  455. return true;
  456. }
  457. } else if (piece.type == Type::Rook) {
  458. int rank_delta = move.to.rank - move.from.rank;
  459. int file_delta = move.to.file - move.from.file;
  460. if (rank_delta == 0 || file_delta == 0) {
  461. int dr = (rank_delta) ? rank_delta / abs(rank_delta) : 0;
  462. int df = (file_delta) ? file_delta / abs(file_delta) : 0;
  463. for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
  464. if (get_piece(sq).type != Type::None && sq != move.from) {
  465. return false;
  466. }
  467. }
  468. return true;
  469. }
  470. } else if (piece.type == Type::Queen) {
  471. int rank_delta = move.to.rank - move.from.rank;
  472. int file_delta = move.to.file - move.from.file;
  473. if (abs(rank_delta) == abs(file_delta) || rank_delta == 0 || file_delta == 0) {
  474. int dr = (rank_delta) ? rank_delta / abs(rank_delta) : 0;
  475. int df = (file_delta) ? file_delta / abs(file_delta) : 0;
  476. for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
  477. if (get_piece(sq).type != Type::None && sq != move.from) {
  478. return false;
  479. }
  480. }
  481. return true;
  482. }
  483. } else if (piece.type == Type::King) {
  484. int rank_delta = move.to.rank - move.from.rank;
  485. int file_delta = move.to.file - move.from.file;
  486. if (abs(rank_delta) <= 1 && abs(file_delta) <= 1) {
  487. return true;
  488. }
  489. }
  490. return false;
  491. }
  492. bool Board::in_check(Color color) const
  493. {
  494. Square king_square = { 50, 50 };
  495. Square::for_each([&](Square const& square) {
  496. if (get_piece(square) == Piece(color, Type::King)) {
  497. king_square = square;
  498. return IterationDecision::Break;
  499. }
  500. return IterationDecision::Continue;
  501. });
  502. bool check = false;
  503. Square::for_each([&](Square const& square) {
  504. if (is_legal_no_check({ square, king_square }, opposing_color(color))) {
  505. check = true;
  506. return IterationDecision::Break;
  507. }
  508. return IterationDecision::Continue;
  509. });
  510. return check;
  511. }
  512. bool Board::apply_move(Move const& move, Color color)
  513. {
  514. if (color == Color::None)
  515. color = turn();
  516. if (!is_legal(move, color))
  517. return false;
  518. const_cast<Move&>(move).piece = get_piece(move.from);
  519. return apply_illegal_move(move, color);
  520. }
  521. bool Board::apply_illegal_move(Move const& move, Color color)
  522. {
  523. auto state = Traits<Board>::hash(*this);
  524. auto state_count = 0;
  525. if (m_previous_states.contains(state))
  526. state_count = m_previous_states.get(state).value();
  527. m_previous_states.set(state, state_count + 1);
  528. m_moves.append(move);
  529. m_turn = opposing_color(color);
  530. m_last_move = move;
  531. m_moves_since_capture++;
  532. m_moves_since_pawn_advance++;
  533. if (move.from == Square("a1") || move.to == Square("a1") || move.from == Square("e1"))
  534. m_white_can_castle_queenside = false;
  535. if (move.from == Square("h1") || move.to == Square("h1") || move.from == Square("e1"))
  536. m_white_can_castle_kingside = false;
  537. if (move.from == Square("a8") || move.to == Square("a8") || move.from == Square("e8"))
  538. m_black_can_castle_queenside = false;
  539. if (move.from == Square("h8") || move.to == Square("h8") || move.from == Square("e8"))
  540. m_black_can_castle_kingside = false;
  541. if (color == Color::White && move.from == Square("e1") && get_piece(Square("e1")) == Piece(Color::White, Type::King)) {
  542. if (move.to == Square("a1") || move.to == Square("c1")) {
  543. set_piece(Square("e1"), EmptyPiece);
  544. set_piece(Square("a1"), EmptyPiece);
  545. set_piece(Square("c1"), { Color::White, Type::King });
  546. set_piece(Square("d1"), { Color::White, Type::Rook });
  547. return true;
  548. } else if (move.to == Square("h1") || move.to == Square("g1")) {
  549. set_piece(Square("e1"), EmptyPiece);
  550. set_piece(Square("h1"), EmptyPiece);
  551. set_piece(Square("g1"), { Color::White, Type::King });
  552. set_piece(Square("f1"), { Color::White, Type::Rook });
  553. return true;
  554. }
  555. } else if (color == Color::Black && move.from == Square("e8") && get_piece(Square("e8")) == Piece(Color::Black, Type::King)) {
  556. if (move.to == Square("a8") || move.to == Square("c8")) {
  557. set_piece(Square("e8"), EmptyPiece);
  558. set_piece(Square("a8"), EmptyPiece);
  559. set_piece(Square("c8"), { Color::Black, Type::King });
  560. set_piece(Square("d8"), { Color::Black, Type::Rook });
  561. return true;
  562. } else if (move.to == Square("h8") || move.to == Square("g8")) {
  563. set_piece(Square("e8"), EmptyPiece);
  564. set_piece(Square("h8"), EmptyPiece);
  565. set_piece(Square("g8"), { Color::Black, Type::King });
  566. set_piece(Square("f8"), { Color::Black, Type::Rook });
  567. return true;
  568. }
  569. }
  570. if (move.piece.type == Type::Pawn)
  571. m_moves_since_pawn_advance = 0;
  572. if (get_piece(move.to).color != Color::None) {
  573. const_cast<Move&>(move).is_capture = true;
  574. m_moves_since_capture = 0;
  575. }
  576. if (get_piece(move.from).type == Type::Pawn && ((color == Color::Black && move.to.rank == 0) || (color == Color::White && move.to.rank == 7))) {
  577. // Pawn Promotion
  578. set_piece(move.to, { color, move.promote_to });
  579. set_piece(move.from, EmptyPiece);
  580. if (in_check(m_turn))
  581. const_cast<Move&>(move).is_check = true;
  582. return true;
  583. }
  584. if (get_piece(move.from).type == Type::Pawn && move.from.file != move.to.file && get_piece(move.to).type == Type::None) {
  585. // En passant.
  586. if (color == Color::White) {
  587. set_piece({ move.to.rank - 1, move.to.file }, EmptyPiece);
  588. } else {
  589. set_piece({ move.to.rank + 1, move.to.file }, EmptyPiece);
  590. }
  591. const_cast<Move&>(move).is_capture = true;
  592. m_moves_since_capture = 0;
  593. }
  594. Square::for_each([&](Square sq) {
  595. // Ambiguous Move
  596. if (sq != move.from && get_piece(sq).type == move.piece.type && get_piece(sq).color == move.piece.color) {
  597. if (is_legal(Move(sq, move.to), get_piece(sq).color)) {
  598. m_moves.last().is_ambiguous = true;
  599. m_moves.last().ambiguous = sq;
  600. return IterationDecision::Break;
  601. }
  602. }
  603. return IterationDecision::Continue;
  604. });
  605. set_piece(move.to, get_piece(move.from));
  606. set_piece(move.from, EmptyPiece);
  607. if (in_check(m_turn))
  608. const_cast<Move&>(move).is_check = true;
  609. return true;
  610. }
  611. Move Board::random_move(Color color) const
  612. {
  613. if (color == Color::None)
  614. color = turn();
  615. Move move = { { 50, 50 }, { 50, 50 } };
  616. int probability = 1;
  617. generate_moves([&](Move m) {
  618. if (rand() % probability == 0)
  619. move = m;
  620. ++probability;
  621. return IterationDecision::Continue;
  622. });
  623. return move;
  624. }
  625. Board::Result Board::game_result() const
  626. {
  627. if (m_resigned != Color::None)
  628. return (m_resigned == Color::White) ? Result::WhiteResign : Result::BlackResign;
  629. bool sufficient_material = false;
  630. bool no_more_pieces_allowed = false;
  631. Optional<Square> bishop;
  632. Square::for_each([&](Square sq) {
  633. if (get_piece(sq).type == Type::Queen || get_piece(sq).type == Type::Rook || get_piece(sq).type == Type::Pawn) {
  634. sufficient_material = true;
  635. return IterationDecision::Break;
  636. }
  637. if (get_piece(sq).type != Type::None && get_piece(sq).type != Type::King && no_more_pieces_allowed) {
  638. sufficient_material = true;
  639. return IterationDecision::Break;
  640. }
  641. if (get_piece(sq).type == Type::Knight)
  642. no_more_pieces_allowed = true;
  643. if (get_piece(sq).type == Type::Bishop) {
  644. if (bishop.has_value()) {
  645. if (get_piece(sq).color == get_piece(bishop.value()).color) {
  646. sufficient_material = true;
  647. return IterationDecision::Break;
  648. } else if (sq.is_light() != bishop.value().is_light()) {
  649. sufficient_material = true;
  650. return IterationDecision::Break;
  651. }
  652. no_more_pieces_allowed = true;
  653. } else {
  654. bishop = sq;
  655. }
  656. }
  657. return IterationDecision::Continue;
  658. });
  659. if (!sufficient_material)
  660. return Result::InsufficientMaterial;
  661. bool are_legal_moves = false;
  662. generate_moves([&]([[maybe_unused]] Move m) {
  663. are_legal_moves = true;
  664. return IterationDecision::Break;
  665. });
  666. if (are_legal_moves) {
  667. if (m_moves_since_capture >= 75 * 2 && m_moves_since_pawn_advance >= 75 * 2)
  668. return Result::SeventyFiveMoveRule;
  669. if ((m_moves_since_capture >= 50 * 2 && m_moves_since_pawn_advance == 50 * 2)
  670. || (m_moves_since_pawn_advance >= 50 * 2 && m_moves_since_capture == 50 * 2))
  671. return Result::FiftyMoveRule;
  672. auto repeats = m_previous_states.get(Traits<Board>::hash(*this));
  673. if (repeats.has_value()) {
  674. if (repeats.value() == 3)
  675. return Result::ThreeFoldRepetition;
  676. if (repeats.value() >= 5)
  677. return Result::FiveFoldRepetition;
  678. }
  679. return Result::NotFinished;
  680. }
  681. if (in_check(turn())) {
  682. const_cast<Vector<Move>&>(m_moves).last().is_mate = true;
  683. return Result::CheckMate;
  684. }
  685. return Result::StaleMate;
  686. }
  687. Color Board::game_winner() const
  688. {
  689. if (game_result() == Result::CheckMate)
  690. return opposing_color(turn());
  691. return Color::None;
  692. }
  693. int Board::game_score() const
  694. {
  695. switch (game_winner()) {
  696. case Color::White:
  697. return +1;
  698. case Color::Black:
  699. return -1;
  700. case Color::None:
  701. return 0;
  702. }
  703. return 0;
  704. }
  705. bool Board::game_finished() const
  706. {
  707. return game_result() != Result::NotFinished;
  708. }
  709. int Board::material_imbalance() const
  710. {
  711. int imbalance = 0;
  712. Square::for_each([&](Square square) {
  713. int value = 0;
  714. switch (get_piece(square).type) {
  715. case Type::Pawn:
  716. value = 1;
  717. break;
  718. case Type::Knight:
  719. case Type::Bishop:
  720. value = 3;
  721. break;
  722. case Type::Rook:
  723. value = 5;
  724. break;
  725. case Type::Queen:
  726. value = 9;
  727. break;
  728. default:
  729. break;
  730. }
  731. if (get_piece(square).color == Color::White) {
  732. imbalance += value;
  733. } else {
  734. imbalance -= value;
  735. }
  736. return IterationDecision::Continue;
  737. });
  738. return imbalance;
  739. }
  740. bool Board::is_promotion_move(Move const& move, Color color) const
  741. {
  742. if (color == Color::None)
  743. color = turn();
  744. int promotion_rank = (color == Color::White) ? 7 : 0;
  745. if (move.to.rank != promotion_rank)
  746. return false;
  747. if (get_piece(move.from).type != Type::Pawn)
  748. return false;
  749. Move queen_move = move;
  750. queen_move.promote_to = Type::Queen;
  751. if (!is_legal(queen_move, color))
  752. return false;
  753. return true;
  754. }
  755. bool Board::operator==(Board const& other) const
  756. {
  757. bool equal_squares = true;
  758. Square::for_each([&](Square sq) {
  759. if (get_piece(sq) != other.get_piece(sq)) {
  760. equal_squares = false;
  761. return IterationDecision::Break;
  762. }
  763. return IterationDecision::Continue;
  764. });
  765. if (!equal_squares)
  766. return false;
  767. if (m_white_can_castle_queenside != other.m_white_can_castle_queenside)
  768. return false;
  769. if (m_white_can_castle_kingside != other.m_white_can_castle_kingside)
  770. return false;
  771. if (m_black_can_castle_queenside != other.m_black_can_castle_queenside)
  772. return false;
  773. if (m_black_can_castle_kingside != other.m_black_can_castle_kingside)
  774. return false;
  775. return turn() == other.turn();
  776. }
  777. void Board::set_resigned(Chess::Color c)
  778. {
  779. m_resigned = c;
  780. }
  781. StringView Board::result_to_string(Result result, Color turn)
  782. {
  783. switch (result) {
  784. case Result::CheckMate:
  785. VERIFY(turn != Chess::Color::None);
  786. return turn == Chess::Color::White ? "Black wins by Checkmate"sv : "White wins by Checkmate"sv;
  787. case Result::WhiteResign:
  788. return "Black wins by Resignation"sv;
  789. case Result::BlackResign:
  790. return "White wins by Resignation"sv;
  791. case Result::StaleMate:
  792. return "Draw by Stalemate"sv;
  793. case Chess::Board::Result::FiftyMoveRule:
  794. return "Draw by 50 move rule"sv;
  795. case Chess::Board::Result::SeventyFiveMoveRule:
  796. return "Draw by 75 move rule"sv;
  797. case Chess::Board::Result::ThreeFoldRepetition:
  798. return "Draw by threefold repetition"sv;
  799. case Chess::Board::Result::FiveFoldRepetition:
  800. return "Draw by fivefold repetition"sv;
  801. case Chess::Board::Result::InsufficientMaterial:
  802. return "Draw by insufficient material"sv;
  803. case Chess::Board::Result::NotFinished:
  804. return "Game not finished"sv;
  805. default:
  806. VERIFY_NOT_REACHED();
  807. }
  808. }
  809. StringView Board::result_to_points_string(Result result, Color turn)
  810. {
  811. switch (result) {
  812. case Result::CheckMate:
  813. VERIFY(turn != Chess::Color::None);
  814. return turn == Chess::Color::White ? "0-1"sv : "1-0"sv;
  815. case Result::WhiteResign:
  816. return "0-1"sv;
  817. case Result::BlackResign:
  818. return "1-0"sv;
  819. case Result::StaleMate:
  820. case Chess::Board::Result::FiftyMoveRule:
  821. case Chess::Board::Result::SeventyFiveMoveRule:
  822. case Chess::Board::Result::ThreeFoldRepetition:
  823. case Chess::Board::Result::FiveFoldRepetition:
  824. case Chess::Board::Result::InsufficientMaterial:
  825. return "1/2-1/2"sv;
  826. case Chess::Board::Result::NotFinished:
  827. return "*"sv;
  828. default:
  829. VERIFY_NOT_REACHED();
  830. }
  831. }
  832. }