Chess.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. /*
  2. * Copyright (c) 2020, the SerenityOS developers.
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this
  9. * list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include <AK/Assertions.h>
  27. #include <AK/LogStream.h>
  28. #include <AK/String.h>
  29. #include <AK/StringBuilder.h>
  30. #include <AK/Vector.h>
  31. #include <LibChess/Chess.h>
  32. #include <stdlib.h>
  33. namespace Chess {
  34. String char_for_piece(Chess::Type type)
  35. {
  36. switch (type) {
  37. case Type::Knight:
  38. return "N";
  39. case Type::Bishop:
  40. return "B";
  41. case Type::Rook:
  42. return "R";
  43. case Type::Queen:
  44. return "Q";
  45. case Type::King:
  46. return "K";
  47. case Type::Pawn:
  48. default:
  49. return "";
  50. }
  51. }
  52. Colour opposing_colour(Colour colour)
  53. {
  54. return (colour == Colour::White) ? Colour::Black : Colour::White;
  55. }
  56. Square::Square(const StringView& name)
  57. {
  58. ASSERT(name.length() == 2);
  59. char filec = name[0];
  60. char rankc = name[1];
  61. if (filec >= 'a' && filec <= 'h') {
  62. file = filec - 'a';
  63. } else if (filec >= 'A' && filec <= 'H') {
  64. file = filec - 'A';
  65. } else {
  66. ASSERT_NOT_REACHED();
  67. }
  68. if (rankc >= '1' && rankc <= '8') {
  69. rank = rankc - '1';
  70. } else {
  71. ASSERT_NOT_REACHED();
  72. }
  73. }
  74. String Square::to_algebraic() const
  75. {
  76. StringBuilder builder;
  77. builder.append(file - 'a');
  78. builder.append(rank - '1');
  79. return builder.build();
  80. }
  81. String Move::to_long_algebraic() const
  82. {
  83. StringBuilder builder;
  84. builder.append(from.to_algebraic());
  85. builder.append(to.to_algebraic());
  86. builder.append(char_for_piece(promote_to).to_lowercase());
  87. return builder.build();
  88. }
  89. Board::Board()
  90. {
  91. // Fill empty spaces.
  92. for (unsigned rank = 2; rank < 6; ++rank) {
  93. for (unsigned file = 0; file < 8; ++file) {
  94. set_piece({ rank, file }, EmptyPiece);
  95. }
  96. }
  97. // Fill white pawns.
  98. for (unsigned file = 0; file < 8; ++file) {
  99. set_piece({ 1, file }, { Colour::White, Type::Pawn });
  100. }
  101. // Fill black pawns.
  102. for (unsigned file = 0; file < 8; ++file) {
  103. set_piece({ 6, file }, { Colour::Black, Type::Pawn });
  104. }
  105. // Fill while pieces.
  106. set_piece(Square("a1"), { Colour::White, Type::Rook });
  107. set_piece(Square("b1"), { Colour::White, Type::Knight });
  108. set_piece(Square("c1"), { Colour::White, Type::Bishop });
  109. set_piece(Square("d1"), { Colour::White, Type::Queen });
  110. set_piece(Square("e1"), { Colour::White, Type::King });
  111. set_piece(Square("f1"), { Colour::White, Type::Bishop });
  112. set_piece(Square("g1"), { Colour::White, Type::Knight });
  113. set_piece(Square("h1"), { Colour::White, Type::Rook });
  114. // Fill black pieces.
  115. set_piece(Square("a8"), { Colour::Black, Type::Rook });
  116. set_piece(Square("b8"), { Colour::Black, Type::Knight });
  117. set_piece(Square("c8"), { Colour::Black, Type::Bishop });
  118. set_piece(Square("d8"), { Colour::Black, Type::Queen });
  119. set_piece(Square("e8"), { Colour::Black, Type::King });
  120. set_piece(Square("f8"), { Colour::Black, Type::Bishop });
  121. set_piece(Square("g8"), { Colour::Black, Type::Knight });
  122. set_piece(Square("h8"), { Colour::Black, Type::Rook });
  123. }
  124. Piece Board::get_piece(const Square& square) const
  125. {
  126. ASSERT(square.rank < 8);
  127. ASSERT(square.file < 8);
  128. return m_board[square.rank][square.file];
  129. }
  130. Piece Board::set_piece(const Square& square, const Piece& piece)
  131. {
  132. ASSERT(square.rank < 8);
  133. ASSERT(square.file < 8);
  134. return m_board[square.rank][square.file] = piece;
  135. }
  136. bool Board::is_legal(const Move& move, Colour colour) const
  137. {
  138. if (colour == Colour::None)
  139. colour = turn();
  140. if (!is_legal_no_check(move, colour))
  141. return false;
  142. Board clone = *this;
  143. clone.apply_illegal_move(move, colour);
  144. if (clone.in_check(colour))
  145. return false;
  146. // Don't allow castling through check or out of check.
  147. Vector<Square> check_squares;
  148. if (colour == Colour::White && move.from == Square("e1") && get_piece(Square("e1")) == Piece(Colour::White, Type::King)) {
  149. if (move.to == Square("a1") || move.to == Square("c1")) {
  150. check_squares = { Square("e1"), Square("d1"), Square("c1") };
  151. } else if (move.to == Square("h1") || move.to == Square("g1")) {
  152. check_squares = { Square("e1"), Square("f1"), Square("g1") };
  153. }
  154. } else if (colour == Colour::Black && move.from == Square("e8") && get_piece(Square("e8")) == Piece(Colour::Black, Type::King)) {
  155. if (move.to == Square("a8") || move.to == Square("c8")) {
  156. check_squares = { Square("e8"), Square("d8"), Square("c8") };
  157. } else if (move.to == Square("h8") || move.to == Square("g8")) {
  158. check_squares = { Square("e8"), Square("f8"), Square("g8") };
  159. }
  160. }
  161. for (auto& square : check_squares) {
  162. Board clone = *this;
  163. clone.set_piece(move.from, EmptyPiece);
  164. clone.set_piece(square, { colour, Type::King });
  165. if (clone.in_check(colour))
  166. return false;
  167. }
  168. return true;
  169. }
  170. bool Board::is_legal_no_check(const Move& move, Colour colour) const
  171. {
  172. auto piece = get_piece(move.from);
  173. if (piece.colour != colour)
  174. return false;
  175. if (move.to.rank > 7 || move.to.file > 7)
  176. return false;
  177. if (piece.type != Type::Pawn && move.promote_to != Type::None)
  178. return false;
  179. if (move.promote_to == Type::Pawn || move.promote_to == Type::King)
  180. return false;
  181. if (piece.type == Type::Pawn) {
  182. int dir = (colour == Colour::White) ? +1 : -1;
  183. unsigned start_rank = (colour == Colour::White) ? 1 : 6;
  184. unsigned other_start_rank = (colour == Colour::White) ? 6 : 1;
  185. unsigned en_passant_rank = (colour == Colour::White) ? 4 : 3;
  186. unsigned promotion_rank = (colour == Colour::White) ? 7 : 0;
  187. if (move.to.rank == promotion_rank) {
  188. if (move.promote_to == Type::Pawn || move.promote_to == Type::King || move.promote_to == Type::None)
  189. return false;
  190. } else if (move.promote_to != Type::None) {
  191. return false;
  192. }
  193. if (move.to.rank == move.from.rank + dir && move.to.file == move.from.file && get_piece(move.to).type == Type::None) {
  194. // Regular pawn move.
  195. return true;
  196. } else if (move.to.rank == move.from.rank + dir && (move.to.file == move.from.file + 1 || move.to.file == move.from.file - 1)) {
  197. Move en_passant_last_move = { { other_start_rank, move.to.file }, { en_passant_rank, move.to.file } };
  198. if (get_piece(move.to).colour == opposing_colour(colour)) {
  199. // Pawn capture.
  200. return true;
  201. } else if (m_last_move.has_value() && move.from.rank == en_passant_rank && m_last_move.value() == en_passant_last_move
  202. && get_piece(en_passant_last_move.to) == Piece(opposing_colour(colour), Type::Pawn)) {
  203. // En passant.
  204. return true;
  205. }
  206. } else if (move.from.rank == start_rank && move.to.rank == move.from.rank + (2 * dir) && move.to.file == move.from.file
  207. && get_piece(move.to).type == Type::None && get_piece({ move.from.rank + dir, move.from.file }).type == Type::None) {
  208. // 2 square pawn move from initial position.
  209. return true;
  210. }
  211. return false;
  212. } else if (piece.type == Type::Knight) {
  213. int rank_delta = abs(move.to.rank - move.from.rank);
  214. int file_delta = abs(move.to.file - move.from.file);
  215. if (get_piece(move.to).colour != colour && max(rank_delta, file_delta) == 2 && min(rank_delta, file_delta) == 1) {
  216. return true;
  217. }
  218. } else if (piece.type == Type::Bishop) {
  219. int rank_delta = move.to.rank - move.from.rank;
  220. int file_delta = move.to.file - move.from.file;
  221. if (abs(rank_delta) == abs(file_delta)) {
  222. int dr = rank_delta / abs(rank_delta);
  223. int df = file_delta / abs(file_delta);
  224. for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
  225. if (get_piece(sq).type != Type::None && sq != move.from) {
  226. return false;
  227. }
  228. }
  229. if (get_piece(move.to).colour != colour) {
  230. return true;
  231. }
  232. }
  233. } else if (piece.type == Type::Rook) {
  234. int rank_delta = move.to.rank - move.from.rank;
  235. int file_delta = move.to.file - move.from.file;
  236. if (rank_delta == 0 || file_delta == 0) {
  237. int dr = (rank_delta) ? rank_delta / abs(rank_delta) : 0;
  238. int df = (file_delta) ? file_delta / abs(file_delta) : 0;
  239. for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
  240. if (get_piece(sq).type != Type::None && sq != move.from) {
  241. return false;
  242. }
  243. }
  244. if (get_piece(move.to).colour != colour) {
  245. return true;
  246. }
  247. }
  248. } else if (piece.type == Type::Queen) {
  249. int rank_delta = move.to.rank - move.from.rank;
  250. int file_delta = move.to.file - move.from.file;
  251. if (abs(rank_delta) == abs(file_delta) || rank_delta == 0 || file_delta == 0) {
  252. int dr = (rank_delta) ? rank_delta / abs(rank_delta) : 0;
  253. int df = (file_delta) ? file_delta / abs(file_delta) : 0;
  254. for (Square sq = move.from; sq != move.to; sq.rank += dr, sq.file += df) {
  255. if (get_piece(sq).type != Type::None && sq != move.from) {
  256. return false;
  257. }
  258. }
  259. if (get_piece(move.to).colour != colour) {
  260. return true;
  261. }
  262. }
  263. } else if (piece.type == Type::King) {
  264. int rank_delta = move.to.rank - move.from.rank;
  265. int file_delta = move.to.file - move.from.file;
  266. if (abs(rank_delta) <= 1 && abs(file_delta) <= 1) {
  267. if (get_piece(move.to).colour != colour) {
  268. return true;
  269. }
  270. }
  271. if (colour == Colour::White) {
  272. 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) {
  273. return true;
  274. } 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) {
  275. return true;
  276. }
  277. } else {
  278. 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) {
  279. return true;
  280. } 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) {
  281. return true;
  282. }
  283. }
  284. }
  285. return false;
  286. }
  287. bool Board::in_check(Colour colour) const
  288. {
  289. Square king_square = { 50, 50 };
  290. Square::for_each([&](const Square& square) {
  291. if (get_piece(square) == Piece(colour, Type::King)) {
  292. king_square = square;
  293. return IterationDecision::Break;
  294. }
  295. return IterationDecision::Continue;
  296. });
  297. bool check = false;
  298. Square::for_each([&](const Square& square) {
  299. if (is_legal({ square, king_square }, opposing_colour(colour))) {
  300. check = true;
  301. return IterationDecision::Break;
  302. } else if (get_piece(square) == Piece(opposing_colour(colour), Type::King) && is_legal_no_check({ square, king_square }, opposing_colour(colour))) {
  303. // The King is a special case, because it would be in check if it put the opposing king in check.
  304. check = true;
  305. return IterationDecision::Break;
  306. }
  307. return IterationDecision::Continue;
  308. });
  309. return check;
  310. }
  311. bool Board::apply_move(const Move& move, Colour colour)
  312. {
  313. if (colour == Colour::None)
  314. colour = turn();
  315. if (!is_legal(move, colour))
  316. return false;
  317. return apply_illegal_move(move, colour);
  318. }
  319. bool Board::apply_illegal_move(const Move& move, Colour colour)
  320. {
  321. Board clone = *this;
  322. clone.m_previous_states = {};
  323. auto state_count = 0;
  324. if (m_previous_states.contains(clone))
  325. state_count = m_previous_states.get(clone).value();
  326. m_previous_states.set(clone, state_count + 1);
  327. m_turn = opposing_colour(colour);
  328. m_last_move = move;
  329. m_moves_since_capture++;
  330. if (move.from == Square("a1") || move.to == Square("a1") || move.from == Square("e1"))
  331. m_white_can_castle_queenside = false;
  332. if (move.from == Square("h1") || move.to == Square("h1") || move.from == Square("e1"))
  333. m_white_can_castle_kingside = false;
  334. if (move.from == Square("a8") || move.to == Square("a8") || move.from == Square("e8"))
  335. m_black_can_castle_queenside = false;
  336. if (move.from == Square("h8") || move.to == Square("h8") || move.from == Square("e8"))
  337. m_black_can_castle_kingside = false;
  338. if (colour == Colour::White && move.from == Square("e1") && get_piece(Square("e1")) == Piece(Colour::White, Type::King)) {
  339. if (move.to == Square("a1") || move.to == Square("c1")) {
  340. set_piece(Square("e1"), EmptyPiece);
  341. set_piece(Square("a1"), EmptyPiece);
  342. set_piece(Square("c1"), { Colour::White, Type::King });
  343. set_piece(Square("d1"), { Colour::White, Type::Rook });
  344. return true;
  345. } else if (move.to == Square("h1") || move.to == Square("g1")) {
  346. set_piece(Square("e1"), EmptyPiece);
  347. set_piece(Square("h1"), EmptyPiece);
  348. set_piece(Square("g1"), { Colour::White, Type::King });
  349. set_piece(Square("f1"), { Colour::White, Type::Rook });
  350. return true;
  351. }
  352. } else if (colour == Colour::Black && move.from == Square("e8") && get_piece(Square("e8")) == Piece(Colour::Black, Type::King)) {
  353. if (move.to == Square("a8") || move.to == Square("c8")) {
  354. set_piece(Square("e8"), EmptyPiece);
  355. set_piece(Square("a8"), EmptyPiece);
  356. set_piece(Square("c8"), { Colour::White, Type::King });
  357. set_piece(Square("d8"), { Colour::White, Type::Rook });
  358. return true;
  359. } else if (move.to == Square("h8") || move.to == Square("g8")) {
  360. set_piece(Square("e8"), EmptyPiece);
  361. set_piece(Square("h8"), EmptyPiece);
  362. set_piece(Square("g8"), { Colour::Black, Type::King });
  363. set_piece(Square("f8"), { Colour::Black, Type::Rook });
  364. return true;
  365. }
  366. }
  367. if (get_piece(move.from).type == Type::Pawn && ((colour == Colour::Black && move.to.rank == 0) || (colour == Colour::White && move.to.rank == 7))) {
  368. // Pawn Promotion
  369. set_piece(move.to, { colour, move.promote_to });
  370. set_piece(move.from, EmptyPiece);
  371. return true;
  372. }
  373. if (get_piece(move.from).type == Type::Pawn && move.from.file != move.to.file && get_piece(move.to).type == Type::None) {
  374. // En passant.
  375. if (colour == Colour::White) {
  376. set_piece({ move.to.rank - 1, move.to.file }, EmptyPiece);
  377. } else {
  378. set_piece({ move.to.rank + 1, move.to.file }, EmptyPiece);
  379. }
  380. m_moves_since_capture = 0;
  381. }
  382. if (get_piece(move.to).colour != Colour::None)
  383. m_moves_since_capture = 0;
  384. set_piece(move.to, get_piece(move.from));
  385. set_piece(move.from, EmptyPiece);
  386. return true;
  387. }
  388. Board::Result Board::game_result() const
  389. {
  390. bool sufficient_material = false;
  391. bool no_more_pieces_allowed = false;
  392. Optional<Square> bishop;
  393. Square::for_each([&](Square sq) {
  394. if (get_piece(sq).type == Type::Queen || get_piece(sq).type == Type::Rook || get_piece(sq).type == Type::Pawn) {
  395. sufficient_material = true;
  396. return IterationDecision::Break;
  397. }
  398. if (get_piece(sq).type != Type::None && get_piece(sq).type != Type::King && no_more_pieces_allowed) {
  399. sufficient_material = true;
  400. return IterationDecision::Break;
  401. }
  402. if (get_piece(sq).type == Type::Knight)
  403. no_more_pieces_allowed = true;
  404. if (get_piece(sq).type == Type::Bishop) {
  405. if (bishop.has_value()) {
  406. if (get_piece(sq).colour == get_piece(bishop.value()).colour) {
  407. sufficient_material = true;
  408. return IterationDecision::Break;
  409. } else if (sq.is_light() != bishop.value().is_light()) {
  410. sufficient_material = true;
  411. return IterationDecision::Break;
  412. }
  413. no_more_pieces_allowed = true;
  414. } else {
  415. bishop = sq;
  416. }
  417. }
  418. return IterationDecision::Continue;
  419. });
  420. if (!sufficient_material)
  421. return Result::InsufficientMaterial;
  422. bool are_legal_moves = false;
  423. generate_moves([&](Move m) {
  424. (void)m;
  425. are_legal_moves = true;
  426. return IterationDecision::Break;
  427. });
  428. if (are_legal_moves) {
  429. if (m_moves_since_capture >= 75 * 2)
  430. return Result::SeventyFiveMoveRule;
  431. if (m_moves_since_capture == 50 * 2)
  432. return Result::FiftyMoveRule;
  433. auto repeats = m_previous_states.get(*this);
  434. if (repeats.has_value()) {
  435. if (repeats.value() == 3)
  436. return Result::ThreeFoldRepitition;
  437. if (repeats.value() >= 5)
  438. return Result::FiveFoldRepitition;
  439. }
  440. return Result::NotFinished;
  441. }
  442. if (in_check(turn()))
  443. return Result::CheckMate;
  444. return Result::StaleMate;
  445. }
  446. bool Board::is_promotion_move(const Move& move, Colour colour) const
  447. {
  448. if (colour == Colour::None)
  449. colour = turn();
  450. Move queen_move = move;
  451. queen_move.promote_to = Type::Queen;
  452. if (!is_legal(queen_move, colour))
  453. return false;
  454. if (get_piece(move.from).type == Type::Pawn && ((colour == Colour::Black && move.to.rank == 0) || (colour == Colour::White && move.to.rank == 7)))
  455. return true;
  456. return false;
  457. }
  458. bool Board::operator==(const Board& other) const
  459. {
  460. bool equal_squares = true;
  461. Square::for_each([&](Square sq) {
  462. if (get_piece(sq) != other.get_piece(sq)) {
  463. equal_squares = false;
  464. return IterationDecision::Break;
  465. }
  466. return IterationDecision::Continue;
  467. });
  468. if (!equal_squares)
  469. return false;
  470. if (m_white_can_castle_queenside != other.m_white_can_castle_queenside)
  471. return false;
  472. if (m_white_can_castle_kingside != other.m_white_can_castle_kingside)
  473. return false;
  474. if (m_black_can_castle_queenside != other.m_black_can_castle_queenside)
  475. return false;
  476. if (m_black_can_castle_kingside != other.m_black_can_castle_kingside)
  477. return false;
  478. return turn() == other.turn();
  479. }
  480. }