Chess.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  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/String.h>
  8. #include <AK/StringBuilder.h>
  9. #include <AK/Vector.h>
  10. #include <LibChess/Chess.h>
  11. #include <stdlib.h>
  12. namespace Chess {
  13. String char_for_piece(Chess::Type type)
  14. {
  15. switch (type) {
  16. case Type::Knight:
  17. return "N";
  18. case Type::Bishop:
  19. return "B";
  20. case Type::Rook:
  21. return "R";
  22. case Type::Queen:
  23. return "Q";
  24. case Type::King:
  25. return "K";
  26. case Type::Pawn:
  27. default:
  28. return "";
  29. }
  30. }
  31. Chess::Type piece_for_char_promotion(const StringView& str)
  32. {
  33. String string = String(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(const 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. String Square::to_algebraic() const
  71. {
  72. StringBuilder builder;
  73. builder.append(file + 'a');
  74. builder.append(rank + '1');
  75. return builder.build();
  76. }
  77. Move::Move(const 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) : ""))
  81. {
  82. }
  83. String Move::to_long_algebraic() const
  84. {
  85. StringBuilder builder;
  86. builder.append(from.to_algebraic());
  87. builder.append(to.to_algebraic());
  88. builder.append(char_for_piece(promote_to).to_lowercase());
  89. return builder.build();
  90. }
  91. Move Move::from_algebraic(const StringView& algebraic, const Color turn, const Board& board)
  92. {
  93. String 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([&](const Square& 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), turn)) {
  148. move.from = square;
  149. return IterationDecision::Break;
  150. }
  151. return IterationDecision::Continue;
  152. }
  153. });
  154. return move;
  155. }
  156. String 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.build();
  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. String Board::to_fen() const
  226. {
  227. StringBuilder builder;
  228. // 1. Piece placement
  229. int empty = 0;
  230. for (int rank = 0; rank < 8; rank++) {
  231. for (int file = 0; file < 8; file++) {
  232. const Piece p(get_piece({ 7 - rank, file }));
  233. if (p.type == Type::None) {
  234. empty++;
  235. continue;
  236. }
  237. if (empty > 0) {
  238. builder.append(String::number(empty));
  239. empty = 0;
  240. }
  241. String piece = char_for_piece(p.type);
  242. if (piece == "")
  243. piece = "P";
  244. builder.append(p.color == Color::Black ? piece.to_lowercase() : piece);
  245. }
  246. if (empty > 0) {
  247. builder.append(String::number(empty));
  248. empty = 0;
  249. }
  250. if (rank < 7)
  251. builder.append("/");
  252. }
  253. // 2. Active color
  254. VERIFY(m_turn != Color::None);
  255. builder.append(m_turn == Color::White ? " w " : " b ");
  256. // 3. Castling availability
  257. builder.append(m_white_can_castle_kingside ? "K" : "");
  258. builder.append(m_white_can_castle_queenside ? "Q" : "");
  259. builder.append(m_black_can_castle_kingside ? "k" : "");
  260. builder.append(m_black_can_castle_queenside ? "q" : "");
  261. builder.append(" ");
  262. // 4. En passant target square
  263. if (!m_last_move.has_value())
  264. builder.append("-");
  265. else if (m_last_move.value().piece.type == Type::Pawn) {
  266. if (m_last_move.value().from.rank == 1 && m_last_move.value().to.rank == 3)
  267. builder.append(Square(m_last_move.value().to.rank - 1, m_last_move.value().to.file).to_algebraic());
  268. else if (m_last_move.value().from.rank == 6 && m_last_move.value().to.rank == 4)
  269. builder.append(Square(m_last_move.value().to.rank + 1, m_last_move.value().to.file).to_algebraic());
  270. else
  271. builder.append("-");
  272. } else {
  273. builder.append("-");
  274. }
  275. builder.append(" ");
  276. // 5. Halfmove clock
  277. builder.append(String::number(min(m_moves_since_capture, m_moves_since_pawn_advance)));
  278. builder.append(" ");
  279. // 6. Fullmove number
  280. builder.append(String::number(1 + m_moves.size() / 2));
  281. return builder.to_string();
  282. }
  283. Piece Board::get_piece(const Square& square) const
  284. {
  285. VERIFY(square.in_bounds());
  286. return m_board[square.rank][square.file];
  287. }
  288. Piece Board::set_piece(const Square& square, const Piece& piece)
  289. {
  290. VERIFY(square.in_bounds());
  291. return m_board[square.rank][square.file] = piece;
  292. }
  293. bool Board::is_legal_promotion(const Move& move, Color color) const
  294. {
  295. auto piece = get_piece(move.from);
  296. if (move.promote_to == Type::Pawn || move.promote_to == Type::King) {
  297. // attempted promotion to invalid piece
  298. return false;
  299. }
  300. if (piece.type != Type::Pawn && move.promote_to != Type::None) {
  301. // attempted promotion from invalid piece
  302. return false;
  303. }
  304. int promotion_rank = (color == Color::White) ? 7 : 0;
  305. if (move.to.rank != promotion_rank && move.promote_to != Type::None) {
  306. // attempted promotion from invalid rank
  307. return false;
  308. }
  309. if (piece.type == Type::Pawn && move.to.rank == promotion_rank && move.promote_to == Type::None) {
  310. // attempted move to promotion rank without promoting
  311. return false;
  312. }
  313. return true;
  314. }
  315. bool Board::is_legal(const Move& move, Color color) const
  316. {
  317. if (color == Color::None)
  318. color = turn();
  319. if (!is_legal_no_check(move, color))
  320. return false;
  321. if (!is_legal_promotion(move, color))
  322. return false;
  323. Board clone = *this;
  324. clone.apply_illegal_move(move, color);
  325. if (clone.in_check(color))
  326. return false;
  327. // Don't allow castling through check or out of check.
  328. Vector<Square> check_squares;
  329. if (color == Color::White && move.from == Square("e1") && get_piece(Square("e1")) == Piece(Color::White, Type::King)) {
  330. if (move.to == Square("a1") || move.to == Square("c1")) {
  331. check_squares = { Square("e1"), Square("d1"), Square("c1") };
  332. } else if (move.to == Square("h1") || move.to == Square("g1")) {
  333. check_squares = { Square("e1"), Square("f1"), Square("g1") };
  334. }
  335. } else if (color == Color::Black && move.from == Square("e8") && get_piece(Square("e8")) == Piece(Color::Black, Type::King)) {
  336. if (move.to == Square("a8") || move.to == Square("c8")) {
  337. check_squares = { Square("e8"), Square("d8"), Square("c8") };
  338. } else if (move.to == Square("h8") || move.to == Square("g8")) {
  339. check_squares = { Square("e8"), Square("f8"), Square("g8") };
  340. }
  341. }
  342. for (auto& square : check_squares) {
  343. Board clone = *this;
  344. clone.set_piece(move.from, EmptyPiece);
  345. clone.set_piece(square, { color, Type::King });
  346. if (clone.in_check(color))
  347. return false;
  348. }
  349. return true;
  350. }
  351. bool Board::is_legal_no_check(const Move& move, Color color) const
  352. {
  353. auto piece = get_piece(move.from);
  354. if (piece.color != color)
  355. // attempted move of opponent's piece
  356. return false;
  357. if (!move.to.in_bounds())
  358. // attempted move outside of board
  359. return false;
  360. // Check castling first to allow dragging king onto the rook.
  361. if (piece.type == Type::King) {
  362. if (color == Color::White) {
  363. 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) {
  364. return true;
  365. } 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) {
  366. return true;
  367. }
  368. } else {
  369. 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) {
  370. return true;
  371. } 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) {
  372. return true;
  373. }
  374. }
  375. }
  376. if (piece.color == get_piece(move.to).color)
  377. // Attempted move to a square occupied by a piece of the same color.
  378. return false;
  379. if (piece.type == Type::Pawn) {
  380. int dir = (color == Color::White) ? +1 : -1;
  381. int start_rank = (color == Color::White) ? 1 : 6;
  382. if (move.from.rank == start_rank && move.to.rank == move.from.rank + (2 * dir) && move.to.file == move.from.file
  383. && get_piece(move.to).type == Type::None && get_piece({ move.from.rank + dir, move.from.file }).type == Type::None) {
  384. // 2 square pawn move from initial position.
  385. return true;
  386. }
  387. if (move.to.rank != move.from.rank + dir)
  388. // attempted backwards or sideways move
  389. return false;
  390. if (move.to.file == move.from.file && get_piece(move.to).type == Type::None) {
  391. // Regular pawn move.
  392. return true;
  393. }
  394. if (move.to.file == move.from.file + 1 || move.to.file == move.from.file - 1) {
  395. int other_start_rank = (color == Color::White) ? 6 : 1;
  396. int en_passant_rank = (color == Color::White) ? 4 : 3;
  397. Move en_passant_last_move = { { other_start_rank, move.to.file }, { en_passant_rank, move.to.file } };
  398. if (get_piece(move.to).color == opposing_color(color)) {
  399. // Pawn capture.
  400. return true;
  401. }
  402. if (m_last_move.has_value() && move.from.rank == en_passant_rank && m_last_move.value() == en_passant_last_move
  403. && get_piece(en_passant_last_move.to) == Piece(opposing_color(color), Type::Pawn)) {
  404. // En passant.
  405. return true;
  406. }
  407. }
  408. return false;
  409. } else if (piece.type == Type::Knight) {
  410. int rank_delta = abs(move.to.rank - move.from.rank);
  411. int file_delta = abs(move.to.file - move.from.file);
  412. if (max(rank_delta, file_delta) == 2 && min(rank_delta, file_delta) == 1) {
  413. return true;
  414. }
  415. } else if (piece.type == Type::Bishop) {
  416. int rank_delta = move.to.rank - move.from.rank;
  417. int file_delta = move.to.file - move.from.file;
  418. if (abs(rank_delta) == abs(file_delta)) {
  419. int dr = rank_delta / abs(rank_delta);
  420. int df = file_delta / abs(file_delta);
  421. for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
  422. if (get_piece(sq).type != Type::None && sq != move.from) {
  423. return false;
  424. }
  425. }
  426. return true;
  427. }
  428. } else if (piece.type == Type::Rook) {
  429. int rank_delta = move.to.rank - move.from.rank;
  430. int file_delta = move.to.file - move.from.file;
  431. if (rank_delta == 0 || file_delta == 0) {
  432. int dr = (rank_delta) ? rank_delta / abs(rank_delta) : 0;
  433. int df = (file_delta) ? file_delta / abs(file_delta) : 0;
  434. for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
  435. if (get_piece(sq).type != Type::None && sq != move.from) {
  436. return false;
  437. }
  438. }
  439. return true;
  440. }
  441. } else if (piece.type == Type::Queen) {
  442. int rank_delta = move.to.rank - move.from.rank;
  443. int file_delta = move.to.file - move.from.file;
  444. if (abs(rank_delta) == abs(file_delta) || rank_delta == 0 || file_delta == 0) {
  445. int dr = (rank_delta) ? rank_delta / abs(rank_delta) : 0;
  446. int df = (file_delta) ? file_delta / abs(file_delta) : 0;
  447. for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
  448. if (get_piece(sq).type != Type::None && sq != move.from) {
  449. return false;
  450. }
  451. }
  452. return true;
  453. }
  454. } else if (piece.type == Type::King) {
  455. int rank_delta = move.to.rank - move.from.rank;
  456. int file_delta = move.to.file - move.from.file;
  457. if (abs(rank_delta) <= 1 && abs(file_delta) <= 1) {
  458. return true;
  459. }
  460. }
  461. return false;
  462. }
  463. bool Board::in_check(Color color) const
  464. {
  465. Square king_square = { 50, 50 };
  466. Square::for_each([&](const Square& square) {
  467. if (get_piece(square) == Piece(color, Type::King)) {
  468. king_square = square;
  469. return IterationDecision::Break;
  470. }
  471. return IterationDecision::Continue;
  472. });
  473. bool check = false;
  474. Square::for_each([&](const Square& square) {
  475. if (is_legal_no_check({ square, king_square }, opposing_color(color))) {
  476. check = true;
  477. return IterationDecision::Break;
  478. }
  479. return IterationDecision::Continue;
  480. });
  481. return check;
  482. }
  483. bool Board::apply_move(const Move& move, Color color)
  484. {
  485. if (color == Color::None)
  486. color = turn();
  487. if (!is_legal(move, color))
  488. return false;
  489. const_cast<Move&>(move).piece = get_piece(move.from);
  490. return apply_illegal_move(move, color);
  491. }
  492. bool Board::apply_illegal_move(const Move& move, Color color)
  493. {
  494. auto state = Traits<Board>::hash(*this);
  495. auto state_count = 0;
  496. if (m_previous_states.contains(state))
  497. state_count = m_previous_states.get(state).value();
  498. m_previous_states.set(state, state_count + 1);
  499. m_moves.append(move);
  500. m_turn = opposing_color(color);
  501. m_last_move = move;
  502. m_moves_since_capture++;
  503. m_moves_since_pawn_advance++;
  504. if (move.from == Square("a1") || move.to == Square("a1") || move.from == Square("e1"))
  505. m_white_can_castle_queenside = false;
  506. if (move.from == Square("h1") || move.to == Square("h1") || move.from == Square("e1"))
  507. m_white_can_castle_kingside = false;
  508. if (move.from == Square("a8") || move.to == Square("a8") || move.from == Square("e8"))
  509. m_black_can_castle_queenside = false;
  510. if (move.from == Square("h8") || move.to == Square("h8") || move.from == Square("e8"))
  511. m_black_can_castle_kingside = false;
  512. if (color == Color::White && move.from == Square("e1") && get_piece(Square("e1")) == Piece(Color::White, Type::King)) {
  513. if (move.to == Square("a1") || move.to == Square("c1")) {
  514. set_piece(Square("e1"), EmptyPiece);
  515. set_piece(Square("a1"), EmptyPiece);
  516. set_piece(Square("c1"), { Color::White, Type::King });
  517. set_piece(Square("d1"), { Color::White, Type::Rook });
  518. return true;
  519. } else if (move.to == Square("h1") || move.to == Square("g1")) {
  520. set_piece(Square("e1"), EmptyPiece);
  521. set_piece(Square("h1"), EmptyPiece);
  522. set_piece(Square("g1"), { Color::White, Type::King });
  523. set_piece(Square("f1"), { Color::White, Type::Rook });
  524. return true;
  525. }
  526. } else if (color == Color::Black && move.from == Square("e8") && get_piece(Square("e8")) == Piece(Color::Black, Type::King)) {
  527. if (move.to == Square("a8") || move.to == Square("c8")) {
  528. set_piece(Square("e8"), EmptyPiece);
  529. set_piece(Square("a8"), EmptyPiece);
  530. set_piece(Square("c8"), { Color::Black, Type::King });
  531. set_piece(Square("d8"), { Color::Black, Type::Rook });
  532. return true;
  533. } else if (move.to == Square("h8") || move.to == Square("g8")) {
  534. set_piece(Square("e8"), EmptyPiece);
  535. set_piece(Square("h8"), EmptyPiece);
  536. set_piece(Square("g8"), { Color::Black, Type::King });
  537. set_piece(Square("f8"), { Color::Black, Type::Rook });
  538. return true;
  539. }
  540. }
  541. if (move.piece.type == Type::Pawn)
  542. m_moves_since_pawn_advance = 0;
  543. if (get_piece(move.to).color != Color::None) {
  544. const_cast<Move&>(move).is_capture = true;
  545. m_moves_since_capture = 0;
  546. }
  547. if (get_piece(move.from).type == Type::Pawn && ((color == Color::Black && move.to.rank == 0) || (color == Color::White && move.to.rank == 7))) {
  548. // Pawn Promotion
  549. set_piece(move.to, { color, move.promote_to });
  550. set_piece(move.from, EmptyPiece);
  551. if (in_check(m_turn))
  552. const_cast<Move&>(move).is_check = true;
  553. return true;
  554. }
  555. if (get_piece(move.from).type == Type::Pawn && move.from.file != move.to.file && get_piece(move.to).type == Type::None) {
  556. // En passant.
  557. if (color == Color::White) {
  558. set_piece({ move.to.rank - 1, move.to.file }, EmptyPiece);
  559. } else {
  560. set_piece({ move.to.rank + 1, move.to.file }, EmptyPiece);
  561. }
  562. const_cast<Move&>(move).is_capture = true;
  563. m_moves_since_capture = 0;
  564. }
  565. Square::for_each([&](Square sq) {
  566. // Ambiguous Move
  567. if (sq != move.from && get_piece(sq).type == move.piece.type && get_piece(sq).color == move.piece.color) {
  568. if (is_legal(Move(sq, move.to), get_piece(sq).color)) {
  569. m_moves.last().is_ambiguous = true;
  570. m_moves.last().ambiguous = sq;
  571. return IterationDecision::Break;
  572. }
  573. }
  574. return IterationDecision::Continue;
  575. });
  576. set_piece(move.to, get_piece(move.from));
  577. set_piece(move.from, EmptyPiece);
  578. if (in_check(m_turn))
  579. const_cast<Move&>(move).is_check = true;
  580. return true;
  581. }
  582. Move Board::random_move(Color color) const
  583. {
  584. if (color == Color::None)
  585. color = turn();
  586. Move move = { { 50, 50 }, { 50, 50 } };
  587. int probability = 1;
  588. generate_moves([&](Move m) {
  589. if (rand() % probability == 0)
  590. move = m;
  591. ++probability;
  592. return IterationDecision::Continue;
  593. });
  594. return move;
  595. }
  596. Board::Result Board::game_result() const
  597. {
  598. if (m_resigned != Color::None)
  599. return (m_resigned == Color::White) ? Result::WhiteResign : Result::BlackResign;
  600. bool sufficient_material = false;
  601. bool no_more_pieces_allowed = false;
  602. Optional<Square> bishop;
  603. Square::for_each([&](Square sq) {
  604. if (get_piece(sq).type == Type::Queen || get_piece(sq).type == Type::Rook || get_piece(sq).type == Type::Pawn) {
  605. sufficient_material = true;
  606. return IterationDecision::Break;
  607. }
  608. if (get_piece(sq).type != Type::None && get_piece(sq).type != Type::King && no_more_pieces_allowed) {
  609. sufficient_material = true;
  610. return IterationDecision::Break;
  611. }
  612. if (get_piece(sq).type == Type::Knight)
  613. no_more_pieces_allowed = true;
  614. if (get_piece(sq).type == Type::Bishop) {
  615. if (bishop.has_value()) {
  616. if (get_piece(sq).color == get_piece(bishop.value()).color) {
  617. sufficient_material = true;
  618. return IterationDecision::Break;
  619. } else if (sq.is_light() != bishop.value().is_light()) {
  620. sufficient_material = true;
  621. return IterationDecision::Break;
  622. }
  623. no_more_pieces_allowed = true;
  624. } else {
  625. bishop = sq;
  626. }
  627. }
  628. return IterationDecision::Continue;
  629. });
  630. if (!sufficient_material)
  631. return Result::InsufficientMaterial;
  632. bool are_legal_moves = false;
  633. generate_moves([&]([[maybe_unused]] Move m) {
  634. are_legal_moves = true;
  635. return IterationDecision::Break;
  636. });
  637. if (are_legal_moves) {
  638. if (m_moves_since_capture >= 75 * 2)
  639. return Result::SeventyFiveMoveRule;
  640. if (m_moves_since_capture == 50 * 2)
  641. return Result::FiftyMoveRule;
  642. auto repeats = m_previous_states.get(Traits<Board>::hash(*this));
  643. if (repeats.has_value()) {
  644. if (repeats.value() == 3)
  645. return Result::ThreeFoldRepetition;
  646. if (repeats.value() >= 5)
  647. return Result::FiveFoldRepetition;
  648. }
  649. return Result::NotFinished;
  650. }
  651. if (in_check(turn())) {
  652. const_cast<Vector<Move>&>(m_moves).last().is_mate = true;
  653. return Result::CheckMate;
  654. }
  655. return Result::StaleMate;
  656. }
  657. Color Board::game_winner() const
  658. {
  659. if (game_result() == Result::CheckMate)
  660. return opposing_color(turn());
  661. return Color::None;
  662. }
  663. int Board::game_score() const
  664. {
  665. switch (game_winner()) {
  666. case Color::White:
  667. return +1;
  668. case Color::Black:
  669. return -1;
  670. case Color::None:
  671. return 0;
  672. }
  673. return 0;
  674. }
  675. bool Board::game_finished() const
  676. {
  677. return game_result() != Result::NotFinished;
  678. }
  679. int Board::material_imbalance() const
  680. {
  681. int imbalance = 0;
  682. Square::for_each([&](Square square) {
  683. int value = 0;
  684. switch (get_piece(square).type) {
  685. case Type::Pawn:
  686. value = 1;
  687. break;
  688. case Type::Knight:
  689. case Type::Bishop:
  690. value = 3;
  691. break;
  692. case Type::Rook:
  693. value = 5;
  694. break;
  695. case Type::Queen:
  696. value = 9;
  697. break;
  698. default:
  699. break;
  700. }
  701. if (get_piece(square).color == Color::White) {
  702. imbalance += value;
  703. } else {
  704. imbalance -= value;
  705. }
  706. return IterationDecision::Continue;
  707. });
  708. return imbalance;
  709. }
  710. bool Board::is_promotion_move(const Move& move, Color color) const
  711. {
  712. if (color == Color::None)
  713. color = turn();
  714. int promotion_rank = (color == Color::White) ? 7 : 0;
  715. if (move.to.rank != promotion_rank)
  716. return false;
  717. if (get_piece(move.from).type != Type::Pawn)
  718. return false;
  719. Move queen_move = move;
  720. queen_move.promote_to = Type::Queen;
  721. if (!is_legal(queen_move, color))
  722. return false;
  723. return true;
  724. }
  725. bool Board::operator==(const Board& other) const
  726. {
  727. bool equal_squares = true;
  728. Square::for_each([&](Square sq) {
  729. if (get_piece(sq) != other.get_piece(sq)) {
  730. equal_squares = false;
  731. return IterationDecision::Break;
  732. }
  733. return IterationDecision::Continue;
  734. });
  735. if (!equal_squares)
  736. return false;
  737. if (m_white_can_castle_queenside != other.m_white_can_castle_queenside)
  738. return false;
  739. if (m_white_can_castle_kingside != other.m_white_can_castle_kingside)
  740. return false;
  741. if (m_black_can_castle_queenside != other.m_black_can_castle_queenside)
  742. return false;
  743. if (m_black_can_castle_kingside != other.m_black_can_castle_kingside)
  744. return false;
  745. return turn() == other.turn();
  746. }
  747. void Board::set_resigned(Chess::Color c)
  748. {
  749. m_resigned = c;
  750. }
  751. String Board::result_to_string(Result result, Color turn)
  752. {
  753. switch (result) {
  754. case Result::CheckMate:
  755. VERIFY(turn != Chess::Color::None);
  756. return turn == Chess::Color::White ? "Black wins by Checkmate" : "White wins by Checkmate";
  757. case Result::WhiteResign:
  758. return "Black wins by Resignation";
  759. case Result::BlackResign:
  760. return "White wins by Resignation";
  761. case Result::StaleMate:
  762. return "Draw by Stalemate";
  763. case Chess::Board::Result::FiftyMoveRule:
  764. return "Draw by 50 move rule";
  765. case Chess::Board::Result::SeventyFiveMoveRule:
  766. return "Draw by 75 move rule";
  767. case Chess::Board::Result::ThreeFoldRepetition:
  768. return "Draw by threefold repetition";
  769. case Chess::Board::Result::FiveFoldRepetition:
  770. return "Draw by fivefold repetition";
  771. case Chess::Board::Result::InsufficientMaterial:
  772. return "Draw by insufficient material";
  773. case Chess::Board::Result::NotFinished:
  774. return "Game not finished";
  775. default:
  776. VERIFY_NOT_REACHED();
  777. }
  778. }
  779. String Board::result_to_points(Result result, Color turn)
  780. {
  781. switch (result) {
  782. case Result::CheckMate:
  783. VERIFY(turn != Chess::Color::None);
  784. return turn == Chess::Color::White ? "0-1" : "1-0";
  785. case Result::WhiteResign:
  786. return "0-1";
  787. case Result::BlackResign:
  788. return "1-0";
  789. case Result::StaleMate:
  790. return "1/2-1/2";
  791. case Chess::Board::Result::FiftyMoveRule:
  792. return "1/2-1/2";
  793. case Chess::Board::Result::SeventyFiveMoveRule:
  794. return "1/2-1/2";
  795. case Chess::Board::Result::ThreeFoldRepetition:
  796. return "1/2-1/2";
  797. case Chess::Board::Result::FiveFoldRepetition:
  798. return "1/2-1/2";
  799. case Chess::Board::Result::InsufficientMaterial:
  800. return "1/2-1/2";
  801. case Chess::Board::Result::NotFinished:
  802. return "*";
  803. default:
  804. VERIFY_NOT_REACHED();
  805. }
  806. }
  807. }