Chess.h 9.9 KB

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