Chess.h 9.7 KB

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