Chess.h 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  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. String 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. String 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. String to_long_algebraic() const;
  96. String to_algebraic() const;
  97. };
  98. class Board {
  99. public:
  100. Board();
  101. Piece get_piece(Square const&) const;
  102. Piece set_piece(Square const&, Piece const&);
  103. bool is_legal(Move const&, Color color = Color::None) const;
  104. bool in_check(Color color) const;
  105. bool is_promotion_move(Move const&, Color color = Color::None) const;
  106. bool apply_move(Move const&, Color color = Color::None);
  107. Optional<Move> const& last_move() const { return m_last_move; }
  108. String to_fen() const;
  109. enum class Result {
  110. CheckMate,
  111. StaleMate,
  112. WhiteResign,
  113. BlackResign,
  114. FiftyMoveRule,
  115. SeventyFiveMoveRule,
  116. ThreeFoldRepetition,
  117. FiveFoldRepetition,
  118. InsufficientMaterial,
  119. NotFinished,
  120. };
  121. static String result_to_string(Result, Color turn);
  122. static String result_to_points(Result, Color turn);
  123. template<typename Callback>
  124. void generate_moves(Callback callback, Color color = Color::None) const;
  125. Move random_move(Color color = Color::None) const;
  126. Result game_result() const;
  127. Color game_winner() const;
  128. int game_score() const;
  129. bool game_finished() const;
  130. void set_resigned(Color);
  131. int material_imbalance() const;
  132. Color turn() const { return m_turn; }
  133. Vector<Move> const& moves() const { return m_moves; }
  134. bool operator==(Board const& other) const;
  135. private:
  136. bool is_legal_no_check(Move const&, Color color) const;
  137. bool is_legal_promotion(Move const&, Color color) const;
  138. bool apply_illegal_move(Move const&, Color color);
  139. Piece m_board[8][8];
  140. Optional<Move> m_last_move;
  141. short m_moves_since_capture { 0 };
  142. short m_moves_since_pawn_advance { 0 };
  143. Color m_turn : 2 { Color::White };
  144. Color m_resigned : 2 { Color::None };
  145. bool m_white_can_castle_kingside : 1 { true };
  146. bool m_white_can_castle_queenside : 1 { true };
  147. bool m_black_can_castle_kingside : 1 { true };
  148. bool m_black_can_castle_queenside : 1 { true };
  149. // We trust that hash collisions will not happen to save lots of memory and time.
  150. HashMap<unsigned, int> m_previous_states;
  151. Vector<Move> m_moves;
  152. friend struct Traits<Board>;
  153. };
  154. template<typename Callback>
  155. void Board::generate_moves(Callback callback, Color color) const
  156. {
  157. if (color == Color::None)
  158. color = turn();
  159. auto try_move = [&](Move m) {
  160. if (is_legal(m, color)) {
  161. if (callback(m) == IterationDecision::Break)
  162. return false;
  163. }
  164. return true;
  165. };
  166. Square::for_each([&](Square sq) {
  167. auto piece = get_piece(sq);
  168. if (piece.color != color)
  169. return IterationDecision::Continue;
  170. bool keep_going = true;
  171. if (piece.type == Type::Pawn) {
  172. for (auto& piece : Vector({ Type::None, Type::Knight, Type::Bishop, Type::Rook, Type::Queen })) {
  173. keep_going = try_move({ sq, { sq.rank + 1, sq.file }, piece })
  174. && try_move({ sq, { sq.rank + 2, sq.file }, piece })
  175. && try_move({ sq, { sq.rank - 1, sq.file }, piece })
  176. && try_move({ sq, { sq.rank - 2, sq.file }, piece })
  177. && try_move({ sq, { sq.rank + 1, sq.file + 1 }, 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. }
  182. } else if (piece.type == Type::Knight) {
  183. keep_going = try_move({ sq, { sq.rank + 2, sq.file + 1 } })
  184. && try_move({ sq, { sq.rank + 2, sq.file - 1 } })
  185. && try_move({ sq, { sq.rank + 1, sq.file + 2 } })
  186. && try_move({ sq, { sq.rank + 1, sq.file - 2 } })
  187. && try_move({ sq, { sq.rank - 2, sq.file + 1 } })
  188. && try_move({ sq, { sq.rank - 2, sq.file - 1 } })
  189. && try_move({ sq, { sq.rank - 1, sq.file + 2 } })
  190. && try_move({ sq, { sq.rank - 1, sq.file - 2 } });
  191. } else if (piece.type == Type::Bishop) {
  192. for (int dr = -1; dr <= 1; dr += 2) {
  193. for (int df = -1; df <= 1; df += 2) {
  194. for (Square to = sq; to.in_bounds(); to = { to.rank + dr, to.file + df }) {
  195. if (!try_move({ sq, to }))
  196. return IterationDecision::Break;
  197. }
  198. }
  199. }
  200. } else if (piece.type == Type::Rook) {
  201. for (int dr = -1; dr <= 1; dr++) {
  202. for (int df = -1; df <= 1; df++) {
  203. if ((dr == 0) != (df == 0)) {
  204. for (Square to = sq; to.in_bounds(); to = { to.rank + dr, to.file + df }) {
  205. if (!try_move({ sq, to }))
  206. return IterationDecision::Break;
  207. }
  208. }
  209. }
  210. }
  211. } else if (piece.type == Type::Queen) {
  212. for (int dr = -1; dr <= 1; dr++) {
  213. for (int df = -1; df <= 1; df++) {
  214. if (dr != 0 || df != 0) {
  215. for (Square to = sq; to.in_bounds(); to = { to.rank + dr, to.file + df }) {
  216. if (!try_move({ sq, to }))
  217. return IterationDecision::Break;
  218. }
  219. }
  220. }
  221. }
  222. } else if (piece.type == Type::King) {
  223. for (int dr = -1; dr <= 1; dr++) {
  224. for (int df = -1; df <= 1; df++) {
  225. if (!try_move({ sq, { sq.rank + dr, sq.file + df } }))
  226. return IterationDecision::Break;
  227. }
  228. }
  229. // Castling moves.
  230. if (sq == Square("e1")) {
  231. keep_going = try_move({ sq, Square("c1") }) && try_move({ sq, Square("g1") });
  232. } else if (sq == Square("e8")) {
  233. keep_going = try_move({ sq, Square("c8") }) && try_move({ sq, Square("g8") });
  234. }
  235. }
  236. if (keep_going) {
  237. return IterationDecision::Continue;
  238. } else {
  239. return IterationDecision::Break;
  240. }
  241. });
  242. }
  243. }
  244. template<>
  245. struct AK::Traits<Chess::Piece> : public GenericTraits<Chess::Piece> {
  246. static unsigned hash(Chess::Piece const& piece)
  247. {
  248. return pair_int_hash(static_cast<u32>(piece.color), static_cast<u32>(piece.type));
  249. }
  250. };
  251. template<>
  252. struct AK::Traits<Chess::Board> : public GenericTraits<Chess::Board> {
  253. static unsigned hash(Chess::Board const& chess)
  254. {
  255. unsigned hash = 0;
  256. hash = pair_int_hash(hash, static_cast<u32>(chess.m_white_can_castle_queenside));
  257. hash = pair_int_hash(hash, static_cast<u32>(chess.m_white_can_castle_kingside));
  258. hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_queenside));
  259. hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_kingside));
  260. Chess::Square::for_each([&](Chess::Square sq) {
  261. hash = pair_int_hash(hash, Traits<Chess::Piece>::hash(chess.get_piece(sq)));
  262. return IterationDecision::Continue;
  263. });
  264. return hash;
  265. }
  266. };