Chess.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
  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. if (piece.type == Type::Pawn) {
  361. int dir = (color == Color::White) ? +1 : -1;
  362. int start_rank = (color == Color::White) ? 1 : 6;
  363. if (move.from.rank == start_rank && move.to.rank == move.from.rank + (2 * dir) && move.to.file == move.from.file
  364. && get_piece(move.to).type == Type::None && get_piece({ move.from.rank + dir, move.from.file }).type == Type::None) {
  365. // 2 square pawn move from initial position.
  366. return true;
  367. }
  368. if (move.to.rank != move.from.rank + dir)
  369. // attempted backwards or sideways move
  370. return false;
  371. if (move.to.file == move.from.file && get_piece(move.to).type == Type::None) {
  372. // Regular pawn move.
  373. return true;
  374. }
  375. if (move.to.file == move.from.file + 1 || move.to.file == move.from.file - 1) {
  376. int other_start_rank = (color == Color::White) ? 6 : 1;
  377. int en_passant_rank = (color == Color::White) ? 4 : 3;
  378. Move en_passant_last_move = { { other_start_rank, move.to.file }, { en_passant_rank, move.to.file } };
  379. if (get_piece(move.to).color == opposing_color(color)) {
  380. // Pawn capture.
  381. return true;
  382. }
  383. if (m_last_move.has_value() && move.from.rank == en_passant_rank && m_last_move.value() == en_passant_last_move
  384. && get_piece(en_passant_last_move.to) == Piece(opposing_color(color), Type::Pawn)) {
  385. // En passant.
  386. return true;
  387. }
  388. }
  389. return false;
  390. } else if (piece.type == Type::Knight) {
  391. int rank_delta = abs(move.to.rank - move.from.rank);
  392. int file_delta = abs(move.to.file - move.from.file);
  393. if (get_piece(move.to).color != color && max(rank_delta, file_delta) == 2 && min(rank_delta, file_delta) == 1) {
  394. return true;
  395. }
  396. } else if (piece.type == Type::Bishop) {
  397. int rank_delta = move.to.rank - move.from.rank;
  398. int file_delta = move.to.file - move.from.file;
  399. if (abs(rank_delta) == abs(file_delta)) {
  400. int dr = rank_delta / abs(rank_delta);
  401. int df = file_delta / abs(file_delta);
  402. for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
  403. if (get_piece(sq).type != Type::None && sq != move.from) {
  404. return false;
  405. }
  406. }
  407. if (get_piece(move.to).color != color) {
  408. return true;
  409. }
  410. }
  411. } else if (piece.type == Type::Rook) {
  412. int rank_delta = move.to.rank - move.from.rank;
  413. int file_delta = move.to.file - move.from.file;
  414. if (rank_delta == 0 || file_delta == 0) {
  415. int dr = (rank_delta) ? rank_delta / abs(rank_delta) : 0;
  416. int df = (file_delta) ? file_delta / abs(file_delta) : 0;
  417. for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
  418. if (get_piece(sq).type != Type::None && sq != move.from) {
  419. return false;
  420. }
  421. }
  422. if (get_piece(move.to).color != color) {
  423. return true;
  424. }
  425. }
  426. } else if (piece.type == Type::Queen) {
  427. int rank_delta = move.to.rank - move.from.rank;
  428. int file_delta = move.to.file - move.from.file;
  429. if (abs(rank_delta) == abs(file_delta) || rank_delta == 0 || file_delta == 0) {
  430. int dr = (rank_delta) ? rank_delta / abs(rank_delta) : 0;
  431. int df = (file_delta) ? file_delta / abs(file_delta) : 0;
  432. for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
  433. if (get_piece(sq).type != Type::None && sq != move.from) {
  434. return false;
  435. }
  436. }
  437. if (get_piece(move.to).color != color) {
  438. return true;
  439. }
  440. }
  441. } else if (piece.type == Type::King) {
  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) <= 1 && abs(file_delta) <= 1) {
  445. if (get_piece(move.to).color != color) {
  446. return true;
  447. }
  448. }
  449. if (color == Color::White) {
  450. 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) {
  451. return true;
  452. } 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) {
  453. return true;
  454. }
  455. } else {
  456. 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) {
  457. return true;
  458. } 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) {
  459. return true;
  460. }
  461. }
  462. }
  463. return false;
  464. }
  465. bool Board::in_check(Color color) const
  466. {
  467. Square king_square = { 50, 50 };
  468. Square::for_each([&](const Square& square) {
  469. if (get_piece(square) == Piece(color, Type::King)) {
  470. king_square = square;
  471. return IterationDecision::Break;
  472. }
  473. return IterationDecision::Continue;
  474. });
  475. bool check = false;
  476. Square::for_each([&](const Square& square) {
  477. if (is_legal_no_check({ square, king_square }, opposing_color(color))) {
  478. check = true;
  479. return IterationDecision::Break;
  480. }
  481. return IterationDecision::Continue;
  482. });
  483. return check;
  484. }
  485. bool Board::apply_move(const Move& move, Color color)
  486. {
  487. if (color == Color::None)
  488. color = turn();
  489. if (!is_legal(move, color))
  490. return false;
  491. const_cast<Move&>(move).piece = get_piece(move.from);
  492. return apply_illegal_move(move, color);
  493. }
  494. bool Board::apply_illegal_move(const Move& move, Color color)
  495. {
  496. Board clone = *this;
  497. clone.m_previous_states = {};
  498. clone.m_moves = {};
  499. auto state_count = 0;
  500. if (m_previous_states.contains(clone))
  501. state_count = m_previous_states.get(clone).value();
  502. m_previous_states.set(clone, state_count + 1);
  503. m_moves.append(move);
  504. m_turn = opposing_color(color);
  505. m_last_move = move;
  506. m_moves_since_capture++;
  507. m_moves_since_pawn_advance++;
  508. if (move.from == Square("a1") || move.to == Square("a1") || move.from == Square("e1"))
  509. m_white_can_castle_queenside = false;
  510. if (move.from == Square("h1") || move.to == Square("h1") || move.from == Square("e1"))
  511. m_white_can_castle_kingside = false;
  512. if (move.from == Square("a8") || move.to == Square("a8") || move.from == Square("e8"))
  513. m_black_can_castle_queenside = false;
  514. if (move.from == Square("h8") || move.to == Square("h8") || move.from == Square("e8"))
  515. m_black_can_castle_kingside = false;
  516. if (color == Color::White && move.from == Square("e1") && get_piece(Square("e1")) == Piece(Color::White, Type::King)) {
  517. if (move.to == Square("a1") || move.to == Square("c1")) {
  518. set_piece(Square("e1"), EmptyPiece);
  519. set_piece(Square("a1"), EmptyPiece);
  520. set_piece(Square("c1"), { Color::White, Type::King });
  521. set_piece(Square("d1"), { Color::White, Type::Rook });
  522. return true;
  523. } else if (move.to == Square("h1") || move.to == Square("g1")) {
  524. set_piece(Square("e1"), EmptyPiece);
  525. set_piece(Square("h1"), EmptyPiece);
  526. set_piece(Square("g1"), { Color::White, Type::King });
  527. set_piece(Square("f1"), { Color::White, Type::Rook });
  528. return true;
  529. }
  530. } else if (color == Color::Black && move.from == Square("e8") && get_piece(Square("e8")) == Piece(Color::Black, Type::King)) {
  531. if (move.to == Square("a8") || move.to == Square("c8")) {
  532. set_piece(Square("e8"), EmptyPiece);
  533. set_piece(Square("a8"), EmptyPiece);
  534. set_piece(Square("c8"), { Color::Black, Type::King });
  535. set_piece(Square("d8"), { Color::Black, Type::Rook });
  536. return true;
  537. } else if (move.to == Square("h8") || move.to == Square("g8")) {
  538. set_piece(Square("e8"), EmptyPiece);
  539. set_piece(Square("h8"), EmptyPiece);
  540. set_piece(Square("g8"), { Color::Black, Type::King });
  541. set_piece(Square("f8"), { Color::Black, Type::Rook });
  542. return true;
  543. }
  544. }
  545. if (move.piece.type == Type::Pawn)
  546. m_moves_since_pawn_advance = 0;
  547. if (get_piece(move.to).color != Color::None) {
  548. const_cast<Move&>(move).is_capture = true;
  549. m_moves_since_capture = 0;
  550. }
  551. if (get_piece(move.from).type == Type::Pawn && ((color == Color::Black && move.to.rank == 0) || (color == Color::White && move.to.rank == 7))) {
  552. // Pawn Promotion
  553. set_piece(move.to, { color, move.promote_to });
  554. set_piece(move.from, EmptyPiece);
  555. if (in_check(m_turn))
  556. const_cast<Move&>(move).is_check = true;
  557. return true;
  558. }
  559. if (get_piece(move.from).type == Type::Pawn && move.from.file != move.to.file && get_piece(move.to).type == Type::None) {
  560. // En passant.
  561. if (color == Color::White) {
  562. set_piece({ move.to.rank - 1, move.to.file }, EmptyPiece);
  563. } else {
  564. set_piece({ move.to.rank + 1, move.to.file }, EmptyPiece);
  565. }
  566. const_cast<Move&>(move).is_capture = true;
  567. m_moves_since_capture = 0;
  568. }
  569. Square::for_each([&](Square sq) {
  570. // Ambiguous Move
  571. if (sq != move.from && get_piece(sq).type == move.piece.type && get_piece(sq).color == move.piece.color) {
  572. if (is_legal(Move(sq, move.to), get_piece(sq).color)) {
  573. m_moves.last().is_ambiguous = true;
  574. m_moves.last().ambiguous = sq;
  575. return IterationDecision::Break;
  576. }
  577. }
  578. return IterationDecision::Continue;
  579. });
  580. set_piece(move.to, get_piece(move.from));
  581. set_piece(move.from, EmptyPiece);
  582. if (in_check(m_turn))
  583. const_cast<Move&>(move).is_check = true;
  584. return true;
  585. }
  586. Move Board::random_move(Color color) const
  587. {
  588. if (color == Color::None)
  589. color = turn();
  590. Move move = { { 50, 50 }, { 50, 50 } };
  591. int probability = 1;
  592. generate_moves([&](Move m) {
  593. if (rand() % probability == 0)
  594. move = m;
  595. ++probability;
  596. return IterationDecision::Continue;
  597. });
  598. return move;
  599. }
  600. Board::Result Board::game_result() const
  601. {
  602. if (m_resigned != Color::None)
  603. return (m_resigned == Color::White) ? Result::WhiteResign : Result::BlackResign;
  604. bool sufficient_material = false;
  605. bool no_more_pieces_allowed = false;
  606. Optional<Square> bishop;
  607. Square::for_each([&](Square sq) {
  608. if (get_piece(sq).type == Type::Queen || get_piece(sq).type == Type::Rook || get_piece(sq).type == Type::Pawn) {
  609. sufficient_material = true;
  610. return IterationDecision::Break;
  611. }
  612. if (get_piece(sq).type != Type::None && get_piece(sq).type != Type::King && no_more_pieces_allowed) {
  613. sufficient_material = true;
  614. return IterationDecision::Break;
  615. }
  616. if (get_piece(sq).type == Type::Knight)
  617. no_more_pieces_allowed = true;
  618. if (get_piece(sq).type == Type::Bishop) {
  619. if (bishop.has_value()) {
  620. if (get_piece(sq).color == get_piece(bishop.value()).color) {
  621. sufficient_material = true;
  622. return IterationDecision::Break;
  623. } else if (sq.is_light() != bishop.value().is_light()) {
  624. sufficient_material = true;
  625. return IterationDecision::Break;
  626. }
  627. no_more_pieces_allowed = true;
  628. } else {
  629. bishop = sq;
  630. }
  631. }
  632. return IterationDecision::Continue;
  633. });
  634. if (!sufficient_material)
  635. return Result::InsufficientMaterial;
  636. bool are_legal_moves = false;
  637. generate_moves([&]([[maybe_unused]] Move m) {
  638. are_legal_moves = true;
  639. return IterationDecision::Break;
  640. });
  641. if (are_legal_moves) {
  642. if (m_moves_since_capture >= 75 * 2)
  643. return Result::SeventyFiveMoveRule;
  644. if (m_moves_since_capture == 50 * 2)
  645. return Result::FiftyMoveRule;
  646. auto repeats = m_previous_states.get(*this);
  647. if (repeats.has_value()) {
  648. if (repeats.value() == 3)
  649. return Result::ThreeFoldRepetition;
  650. if (repeats.value() >= 5)
  651. return Result::FiveFoldRepetition;
  652. }
  653. return Result::NotFinished;
  654. }
  655. if (in_check(turn())) {
  656. const_cast<Vector<Move>&>(m_moves).last().is_mate = true;
  657. return Result::CheckMate;
  658. }
  659. return Result::StaleMate;
  660. }
  661. Color Board::game_winner() const
  662. {
  663. if (game_result() == Result::CheckMate)
  664. return opposing_color(turn());
  665. return Color::None;
  666. }
  667. int Board::game_score() const
  668. {
  669. switch (game_winner()) {
  670. case Color::White:
  671. return +1;
  672. case Color::Black:
  673. return -1;
  674. case Color::None:
  675. return 0;
  676. }
  677. return 0;
  678. }
  679. bool Board::game_finished() const
  680. {
  681. return game_result() != Result::NotFinished;
  682. }
  683. int Board::material_imbalance() const
  684. {
  685. int imbalance = 0;
  686. Square::for_each([&](Square square) {
  687. int value = 0;
  688. switch (get_piece(square).type) {
  689. case Type::Pawn:
  690. value = 1;
  691. break;
  692. case Type::Knight:
  693. case Type::Bishop:
  694. value = 3;
  695. break;
  696. case Type::Rook:
  697. value = 5;
  698. break;
  699. case Type::Queen:
  700. value = 9;
  701. break;
  702. default:
  703. break;
  704. }
  705. if (get_piece(square).color == Color::White) {
  706. imbalance += value;
  707. } else {
  708. imbalance -= value;
  709. }
  710. return IterationDecision::Continue;
  711. });
  712. return imbalance;
  713. }
  714. bool Board::is_promotion_move(const Move& move, Color color) const
  715. {
  716. if (color == Color::None)
  717. color = turn();
  718. int promotion_rank = (color == Color::White) ? 7 : 0;
  719. if (move.to.rank != promotion_rank)
  720. return false;
  721. if (get_piece(move.from).type != Type::Pawn)
  722. return false;
  723. Move queen_move = move;
  724. queen_move.promote_to = Type::Queen;
  725. if (!is_legal(queen_move, color))
  726. return false;
  727. return true;
  728. }
  729. bool Board::operator==(const Board& other) const
  730. {
  731. bool equal_squares = true;
  732. Square::for_each([&](Square sq) {
  733. if (get_piece(sq) != other.get_piece(sq)) {
  734. equal_squares = false;
  735. return IterationDecision::Break;
  736. }
  737. return IterationDecision::Continue;
  738. });
  739. if (!equal_squares)
  740. return false;
  741. if (m_white_can_castle_queenside != other.m_white_can_castle_queenside)
  742. return false;
  743. if (m_white_can_castle_kingside != other.m_white_can_castle_kingside)
  744. return false;
  745. if (m_black_can_castle_queenside != other.m_black_can_castle_queenside)
  746. return false;
  747. if (m_black_can_castle_kingside != other.m_black_can_castle_kingside)
  748. return false;
  749. return turn() == other.turn();
  750. }
  751. void Board::set_resigned(Chess::Color c)
  752. {
  753. m_resigned = c;
  754. }
  755. String Board::result_to_string(Result result, Color turn)
  756. {
  757. switch (result) {
  758. case Result::CheckMate:
  759. VERIFY(turn != Chess::Color::None);
  760. return turn == Chess::Color::White ? "Black wins by Checkmate" : "White wins by Checkmate";
  761. case Result::WhiteResign:
  762. return "Black wins by Resignation";
  763. case Result::BlackResign:
  764. return "White wins by Resignation";
  765. case Result::StaleMate:
  766. return "Draw by Stalemate";
  767. case Chess::Board::Result::FiftyMoveRule:
  768. return "Draw by 50 move rule";
  769. case Chess::Board::Result::SeventyFiveMoveRule:
  770. return "Draw by 75 move rule";
  771. case Chess::Board::Result::ThreeFoldRepetition:
  772. return "Draw by threefold repetition";
  773. case Chess::Board::Result::FiveFoldRepetition:
  774. return "Draw by fivefold repetition";
  775. case Chess::Board::Result::InsufficientMaterial:
  776. return "Draw by insufficient material";
  777. case Chess::Board::Result::NotFinished:
  778. return "Game not finished";
  779. default:
  780. VERIFY_NOT_REACHED();
  781. }
  782. }
  783. String Board::result_to_points(Result result, Color turn)
  784. {
  785. switch (result) {
  786. case Result::CheckMate:
  787. VERIFY(turn != Chess::Color::None);
  788. return turn == Chess::Color::White ? "0-1" : "1-0";
  789. case Result::WhiteResign:
  790. return "0-1";
  791. case Result::BlackResign:
  792. return "1-0";
  793. case Result::StaleMate:
  794. return "1/2-1/2";
  795. case Chess::Board::Result::FiftyMoveRule:
  796. return "1/2-1/2";
  797. case Chess::Board::Result::SeventyFiveMoveRule:
  798. return "1/2-1/2";
  799. case Chess::Board::Result::ThreeFoldRepetition:
  800. return "1/2-1/2";
  801. case Chess::Board::Result::FiveFoldRepetition:
  802. return "1/2-1/2";
  803. case Chess::Board::Result::InsufficientMaterial:
  804. return "1/2-1/2";
  805. case Chess::Board::Result::NotFinished:
  806. return "*";
  807. default:
  808. VERIFY_NOT_REACHED();
  809. }
  810. }
  811. }