Chess.h 9.7 KB

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