Chess.cpp 30 KB

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