Chess.cpp 31 KB

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