Chess.h 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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. #pragma once
  27. #include <AK/HashMap.h>
  28. #include <AK/IterationDecision.h>
  29. #include <AK/Optional.h>
  30. #include <AK/StringView.h>
  31. #include <AK/Traits.h>
  32. #include <AK/Vector.h>
  33. namespace Chess {
  34. enum class Type {
  35. Pawn,
  36. Knight,
  37. Bishop,
  38. Rook,
  39. Queen,
  40. King,
  41. None,
  42. };
  43. String char_for_piece(Type type);
  44. Chess::Type piece_for_char_promotion(const StringView& str);
  45. enum class Colour {
  46. White,
  47. Black,
  48. None,
  49. };
  50. Colour opposing_colour(Colour colour);
  51. struct Piece {
  52. constexpr Piece()
  53. : colour(Colour::None)
  54. , type(Type::None)
  55. {
  56. }
  57. constexpr Piece(Colour c, Type t)
  58. : colour(c)
  59. , type(t)
  60. {
  61. }
  62. Colour colour : 4;
  63. Type type : 4;
  64. bool operator==(const Piece& other) const { return colour == other.colour && type == other.type; }
  65. };
  66. constexpr Piece EmptyPiece = { Colour::None, Type::None };
  67. struct Square {
  68. unsigned rank; // zero indexed;
  69. unsigned file;
  70. Square(const StringView& name);
  71. Square(const unsigned& rank, const unsigned& file)
  72. : rank(rank)
  73. , file(file)
  74. {
  75. }
  76. bool operator==(const Square& other) const { return rank == other.rank && file == other.file; }
  77. template<typename Callback>
  78. static void for_each(Callback callback)
  79. {
  80. for (int rank = 0; rank < 8; ++rank) {
  81. for (int file = 0; file < 8; ++file) {
  82. if (callback(Square(rank, file)) == IterationDecision::Break)
  83. return;
  84. }
  85. }
  86. }
  87. bool in_bounds() const { return rank < 8 && file < 8; }
  88. bool is_light() const { return (rank % 2) != (file % 2); }
  89. String to_algebraic() const;
  90. };
  91. struct Move {
  92. Square from;
  93. Square to;
  94. Type promote_to;
  95. Move(const StringView& algebraic);
  96. Move(const Square& from, const Square& to, const Type& promote_to = Type::None)
  97. : from(from)
  98. , to(to)
  99. , promote_to(promote_to)
  100. {
  101. }
  102. bool operator==(const Move& other) const { return from == other.from && to == other.to && promote_to == other.promote_to; }
  103. String to_long_algebraic() const;
  104. };
  105. class Board {
  106. public:
  107. Board();
  108. Piece get_piece(const Square&) const;
  109. Piece set_piece(const Square&, const Piece&);
  110. bool is_legal(const Move&, Colour colour = Colour::None) const;
  111. bool in_check(Colour colour) const;
  112. bool is_promotion_move(const Move&, Colour colour = Colour::None) const;
  113. bool apply_move(const Move&, Colour colour = Colour::None);
  114. const Optional<Move>& last_move() const { return m_last_move; }
  115. enum class Result {
  116. CheckMate,
  117. StaleMate,
  118. FiftyMoveRule,
  119. SeventyFiveMoveRule,
  120. ThreeFoldRepetition,
  121. FiveFoldRepetition,
  122. InsufficientMaterial,
  123. NotFinished,
  124. };
  125. template<typename Callback>
  126. void generate_moves(Callback callback, Colour colour = Colour::None) const;
  127. Move random_move(Colour colour = Colour::None) const;
  128. Result game_result() const;
  129. Colour game_winner() const;
  130. int game_score() const;
  131. bool game_finished() const;
  132. int material_imbalance() const;
  133. Colour turn() const { return m_turn; }
  134. const Vector<Move>& moves() const { return m_moves; }
  135. bool operator==(const Board& other) const;
  136. private:
  137. bool is_legal_no_check(const Move&, Colour colour) const;
  138. bool apply_illegal_move(const Move&, Colour colour);
  139. Piece m_board[8][8];
  140. Colour m_turn { Colour::White };
  141. Optional<Move> m_last_move;
  142. int m_moves_since_capture { 0 };
  143. bool m_white_can_castle_kingside { true };
  144. bool m_white_can_castle_queenside { true };
  145. bool m_black_can_castle_kingside { true };
  146. bool m_black_can_castle_queenside { true };
  147. HashMap<Board, int> m_previous_states;
  148. Vector<Move> m_moves;
  149. friend struct AK::Traits<Board>;
  150. };
  151. template<typename Callback>
  152. void Board::generate_moves(Callback callback, Colour colour) const
  153. {
  154. if (colour == Colour::None)
  155. colour = turn();
  156. auto try_move = [&](Move m) {
  157. if (is_legal(m, colour)) {
  158. if (callback(m) == IterationDecision::Break)
  159. return false;
  160. }
  161. return true;
  162. };
  163. Square::for_each([&](Square sq) {
  164. auto piece = get_piece(sq);
  165. if (piece.colour != colour)
  166. return IterationDecision::Continue;
  167. bool keep_going = true;
  168. if (piece.type == Type::Pawn) {
  169. for (auto& piece : Vector({ Type::None, Type::Knight, Type::Bishop, Type::Rook, Type::Queen })) {
  170. keep_going = try_move({ sq, { sq.rank + 1, sq.file }, piece })
  171. && try_move({ sq, { sq.rank + 2, sq.file }, piece })
  172. && try_move({ sq, { sq.rank - 1, sq.file }, piece })
  173. && try_move({ sq, { sq.rank - 2, sq.file }, piece })
  174. && try_move({ sq, { sq.rank + 1, sq.file + 1 }, piece })
  175. && try_move({ sq, { sq.rank + 1, sq.file - 1 }, piece })
  176. && try_move({ sq, { sq.rank - 1, sq.file + 1 }, piece })
  177. && try_move({ sq, { sq.rank - 1, sq.file - 1 }, piece });
  178. }
  179. } else if (piece.type == Type::Knight) {
  180. keep_going = try_move({ sq, { sq.rank + 2, sq.file + 1 } })
  181. && try_move({ sq, { sq.rank + 2, sq.file - 1 } })
  182. && try_move({ sq, { sq.rank + 1, sq.file + 2 } })
  183. && try_move({ sq, { sq.rank + 1, sq.file - 2 } })
  184. && try_move({ sq, { sq.rank - 2, sq.file + 1 } })
  185. && try_move({ sq, { sq.rank - 2, sq.file - 1 } })
  186. && try_move({ sq, { sq.rank - 1, sq.file + 2 } })
  187. && try_move({ sq, { sq.rank - 1, sq.file - 2 } });
  188. } else if (piece.type == Type::Bishop) {
  189. for (int dr = -1; dr <= 1; dr += 2) {
  190. for (int df = -1; df <= 1; df += 2) {
  191. for (Square to = sq; to.in_bounds(); to = { to.rank + dr, to.file + df }) {
  192. if (!try_move({ sq, to }))
  193. return IterationDecision::Break;
  194. }
  195. }
  196. }
  197. } else if (piece.type == Type::Rook) {
  198. for (int dr = -1; dr <= 1; dr++) {
  199. for (int df = -1; df <= 1; df++) {
  200. if ((dr == 0) != (df == 0)) {
  201. for (Square to = sq; to.in_bounds(); to = { to.rank + dr, to.file + df }) {
  202. if (!try_move({ sq, to }))
  203. return IterationDecision::Break;
  204. }
  205. }
  206. }
  207. }
  208. } else if (piece.type == Type::Queen) {
  209. for (int dr = -1; dr <= 1; dr++) {
  210. for (int df = -1; df <= 1; df++) {
  211. if (dr != 0 || df != 0) {
  212. for (Square to = sq; to.in_bounds(); to = { to.rank + dr, to.file + df }) {
  213. if (!try_move({ sq, to }))
  214. return IterationDecision::Break;
  215. }
  216. }
  217. }
  218. }
  219. } else if (piece.type == Type::King) {
  220. for (int dr = -1; dr <= 1; dr++) {
  221. for (int df = -1; df <= 1; df++) {
  222. if (!try_move({ sq, { sq.rank + dr, sq.file + df } }))
  223. return IterationDecision::Break;
  224. }
  225. }
  226. // Castling moves.
  227. if (sq == Square("e1")) {
  228. keep_going = try_move({ sq, Square("c1") }) && try_move({ sq, Square("g1") });
  229. } else if (sq == Square("e8")) {
  230. keep_going = try_move({ sq, Square("c8") }) && try_move({ sq, Square("g8") });
  231. }
  232. }
  233. if (keep_going) {
  234. return IterationDecision::Continue;
  235. } else {
  236. return IterationDecision::Break;
  237. }
  238. });
  239. }
  240. }
  241. template<>
  242. struct AK::Traits<Chess::Piece> : public GenericTraits<Chess::Piece> {
  243. static unsigned hash(Chess::Piece piece)
  244. {
  245. return pair_int_hash(static_cast<u32>(piece.colour), static_cast<u32>(piece.type));
  246. }
  247. };
  248. template<>
  249. struct AK::Traits<Chess::Board> : public GenericTraits<Chess::Board> {
  250. static unsigned hash(Chess::Board chess)
  251. {
  252. unsigned hash = 0;
  253. hash = pair_int_hash(hash, static_cast<u32>(chess.m_white_can_castle_queenside));
  254. hash = pair_int_hash(hash, static_cast<u32>(chess.m_white_can_castle_kingside));
  255. hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_queenside));
  256. hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_kingside));
  257. hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_kingside));
  258. Chess::Square::for_each([&](Chess::Square sq) {
  259. hash = pair_int_hash(hash, Traits<Chess::Piece>::hash(chess.get_piece(sq)));
  260. return IterationDecision::Continue;
  261. });
  262. return hash;
  263. }
  264. };