mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 05:27:43 +00:00
Chess: Add new ways to draw.
new ways: Insufficent material Threefold/Fivefold repitition 50 move/75 move rule
This commit is contained in:
parent
9a817270e8
commit
d90f8abe9d
3 changed files with 154 additions and 7 deletions
|
@ -305,9 +305,17 @@ bool Chess::apply_move(const Move& move, Colour colour)
|
||||||
|
|
||||||
bool Chess::apply_illegal_move(const Move& move, Colour colour)
|
bool Chess::apply_illegal_move(const Move& move, Colour colour)
|
||||||
{
|
{
|
||||||
|
Chess clone = *this;
|
||||||
|
clone.m_previous_states = {};
|
||||||
|
auto state_count = 0;
|
||||||
|
if (m_previous_states.contains(clone))
|
||||||
|
state_count = m_previous_states.get(clone).value();
|
||||||
|
m_previous_states.set(clone, state_count + 1);
|
||||||
|
|
||||||
m_turn = opposing_colour(colour);
|
m_turn = opposing_colour(colour);
|
||||||
|
|
||||||
m_last_move = move;
|
m_last_move = move;
|
||||||
|
m_moves_since_capture++;
|
||||||
|
|
||||||
if (move.from == Square("a1") || move.to == Square("a1") || move.from == Square("e1"))
|
if (move.from == Square("a1") || move.to == Square("a1") || move.from == Square("e1"))
|
||||||
m_white_can_castle_queenside = false;
|
m_white_can_castle_queenside = false;
|
||||||
|
@ -362,8 +370,12 @@ bool Chess::apply_illegal_move(const Move& move, Colour colour)
|
||||||
} else {
|
} else {
|
||||||
set_piece({ move.to.rank + 1, move.to.file }, EmptyPiece);
|
set_piece({ move.to.rank + 1, move.to.file }, EmptyPiece);
|
||||||
}
|
}
|
||||||
|
m_moves_since_capture = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (get_piece(move.to).colour != Colour::None)
|
||||||
|
m_moves_since_capture = 0;
|
||||||
|
|
||||||
set_piece(move.to, get_piece(move.from));
|
set_piece(move.to, get_piece(move.from));
|
||||||
set_piece(move.from, EmptyPiece);
|
set_piece(move.from, EmptyPiece);
|
||||||
|
|
||||||
|
@ -372,6 +384,44 @@ bool Chess::apply_illegal_move(const Move& move, Colour colour)
|
||||||
|
|
||||||
Chess::Result Chess::game_result() const
|
Chess::Result Chess::game_result() const
|
||||||
{
|
{
|
||||||
|
bool sufficient_material = false;
|
||||||
|
bool no_more_pieces_allowed = false;
|
||||||
|
Optional<Square> bishop;
|
||||||
|
Square::for_each([&](Square sq) {
|
||||||
|
if (get_piece(sq).type == Type::Queen || get_piece(sq).type == Type::Rook || get_piece(sq).type == Type::Pawn) {
|
||||||
|
sufficient_material = true;
|
||||||
|
return IterationDecision::Break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_piece(sq).type != Type::None && get_piece(sq).type != Type::King && no_more_pieces_allowed) {
|
||||||
|
sufficient_material = true;
|
||||||
|
return IterationDecision::Break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_piece(sq).type == Type::Knight)
|
||||||
|
no_more_pieces_allowed = true;
|
||||||
|
|
||||||
|
if (get_piece(sq).type == Type::Bishop) {
|
||||||
|
if (bishop.has_value()) {
|
||||||
|
if (get_piece(sq).colour == get_piece(bishop.value()).colour) {
|
||||||
|
sufficient_material = true;
|
||||||
|
return IterationDecision::Break;
|
||||||
|
} else if (sq.is_light() != bishop.value().is_light()) {
|
||||||
|
sufficient_material = true;
|
||||||
|
return IterationDecision::Break;
|
||||||
|
}
|
||||||
|
no_more_pieces_allowed = true;
|
||||||
|
} else {
|
||||||
|
bishop = sq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!sufficient_material)
|
||||||
|
return Result::InsufficientMaterial;
|
||||||
|
|
||||||
bool are_legal_moves = false;
|
bool are_legal_moves = false;
|
||||||
generate_moves([&](Move m) {
|
generate_moves([&](Move m) {
|
||||||
(void)m;
|
(void)m;
|
||||||
|
@ -379,8 +429,22 @@ Chess::Result Chess::game_result() const
|
||||||
return IterationDecision::Break;
|
return IterationDecision::Break;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (are_legal_moves)
|
if (are_legal_moves) {
|
||||||
|
if (m_moves_since_capture >= 75 * 2)
|
||||||
|
return Result::SeventyFiveMoveRule;
|
||||||
|
if (m_moves_since_capture == 50 * 2)
|
||||||
|
return Result::FiftyMoveRule;
|
||||||
|
|
||||||
|
auto repeats = m_previous_states.get(*this);
|
||||||
|
if (repeats.has_value()) {
|
||||||
|
if (repeats.value() == 3)
|
||||||
|
return Result::ThreeFoldRepitition;
|
||||||
|
if (repeats.value() >= 5)
|
||||||
|
return Result::FiveFoldRepitition;
|
||||||
|
}
|
||||||
|
|
||||||
return Result::NotFinished;
|
return Result::NotFinished;
|
||||||
|
}
|
||||||
|
|
||||||
if (in_check(turn()))
|
if (in_check(turn()))
|
||||||
return Result::CheckMate;
|
return Result::CheckMate;
|
||||||
|
@ -401,3 +465,28 @@ bool Chess::is_promotion_move(const Move& move, Colour colour) const
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Chess::operator==(const Chess& other) const
|
||||||
|
{
|
||||||
|
bool equal_squares = true;
|
||||||
|
Square::for_each([&](Square sq) {
|
||||||
|
if (get_piece(sq) != other.get_piece(sq)) {
|
||||||
|
equal_squares = false;
|
||||||
|
return IterationDecision::Break;
|
||||||
|
}
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
});
|
||||||
|
if (!equal_squares)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (m_white_can_castle_queenside != other.m_white_can_castle_queenside)
|
||||||
|
return false;
|
||||||
|
if (m_white_can_castle_kingside != other.m_white_can_castle_kingside)
|
||||||
|
return false;
|
||||||
|
if (m_black_can_castle_queenside != other.m_black_can_castle_queenside)
|
||||||
|
return false;
|
||||||
|
if (m_black_can_castle_kingside != other.m_black_can_castle_kingside)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return turn() == other.turn();
|
||||||
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/HashMap.h>
|
||||||
#include <AK/IterationDecision.h>
|
#include <AK/IterationDecision.h>
|
||||||
#include <AK/Optional.h>
|
#include <AK/Optional.h>
|
||||||
#include <AK/StringView.h>
|
#include <AK/StringView.h>
|
||||||
|
@ -82,6 +83,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
bool in_bounds() const { return rank < 8 && file < 8; }
|
bool in_bounds() const { return rank < 8 && file < 8; }
|
||||||
|
bool is_light() const { return (rank % 2) != (file % 2); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Move {
|
struct Move {
|
||||||
|
@ -115,7 +117,10 @@ public:
|
||||||
CheckMate,
|
CheckMate,
|
||||||
StaleMate,
|
StaleMate,
|
||||||
FiftyMoveRule,
|
FiftyMoveRule,
|
||||||
|
SeventyFiveMoveRule,
|
||||||
ThreeFoldRepitition,
|
ThreeFoldRepitition,
|
||||||
|
FiveFoldRepitition,
|
||||||
|
InsufficientMaterial,
|
||||||
NotFinished,
|
NotFinished,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -125,6 +130,8 @@ public:
|
||||||
|
|
||||||
Colour turn() const { return m_turn; };
|
Colour turn() const { return m_turn; };
|
||||||
|
|
||||||
|
bool operator==(const Chess& other) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool is_legal_no_check(const Move&, Colour colour) const;
|
bool is_legal_no_check(const Move&, Colour colour) const;
|
||||||
bool apply_illegal_move(const Move&, Colour colour);
|
bool apply_illegal_move(const Move&, Colour colour);
|
||||||
|
@ -132,11 +139,15 @@ private:
|
||||||
Piece m_board[8][8];
|
Piece m_board[8][8];
|
||||||
Colour m_turn { Colour::White };
|
Colour m_turn { Colour::White };
|
||||||
Optional<Move> m_last_move;
|
Optional<Move> m_last_move;
|
||||||
|
int m_moves_since_capture { 0 };
|
||||||
|
|
||||||
bool m_white_can_castle_kingside { true };
|
bool m_white_can_castle_kingside { true };
|
||||||
bool m_white_can_castle_queenside { true };
|
bool m_white_can_castle_queenside { true };
|
||||||
bool m_black_can_castle_kingside { true };
|
bool m_black_can_castle_kingside { true };
|
||||||
bool m_black_can_castle_queenside { true };
|
bool m_black_can_castle_queenside { true };
|
||||||
|
|
||||||
|
HashMap<Chess, int> m_previous_states;
|
||||||
|
friend struct AK::Traits<Chess>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -147,6 +158,27 @@ struct AK::Traits<Chess::Piece> : public GenericTraits<Chess::Piece> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct AK::Traits<Chess> : public GenericTraits<Chess> {
|
||||||
|
static unsigned hash(Chess chess)
|
||||||
|
{
|
||||||
|
unsigned hash = 0;
|
||||||
|
hash = pair_int_hash(hash, static_cast<u32>(chess.m_white_can_castle_queenside));
|
||||||
|
hash = pair_int_hash(hash, static_cast<u32>(chess.m_white_can_castle_kingside));
|
||||||
|
hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_queenside));
|
||||||
|
hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_kingside));
|
||||||
|
|
||||||
|
hash = pair_int_hash(hash, static_cast<u32>(chess.m_black_can_castle_kingside));
|
||||||
|
|
||||||
|
Chess::Square::for_each([&](Chess::Square sq) {
|
||||||
|
hash = pair_int_hash(hash, Traits<Chess::Piece>::hash(chess.get_piece(sq)));
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template<typename Callback>
|
template<typename Callback>
|
||||||
void Chess::generate_moves(Callback callback, Colour colour) const
|
void Chess::generate_moves(Callback callback, Colour colour) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -62,7 +62,7 @@ void ChessWidget::paint_event(GUI::PaintEvent& event)
|
||||||
tile_rect = { (7 - sq.file) * tile_width, sq.rank * tile_height, tile_width, tile_height };
|
tile_rect = { (7 - sq.file) * tile_width, sq.rank * tile_height, tile_width, tile_height };
|
||||||
}
|
}
|
||||||
|
|
||||||
painter.fill_rect(tile_rect, ((sq.rank % 2) == (sq.file % 2)) ? board_theme().dark_square_color : board_theme().light_square_color);
|
painter.fill_rect(tile_rect, (sq.is_light()) ? board_theme().light_square_color : board_theme().dark_square_color);
|
||||||
|
|
||||||
if (board().last_move().has_value() && (board().last_move().value().to == sq || board().last_move().value().from == sq)) {
|
if (board().last_move().has_value() && (board().last_move().value().to == sq || board().last_move().value().from == sq)) {
|
||||||
painter.fill_rect(tile_rect, m_move_highlight_color);
|
painter.fill_rect(tile_rect, m_move_highlight_color);
|
||||||
|
@ -124,9 +124,8 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event)
|
||||||
|
|
||||||
if (board().apply_move(move)) {
|
if (board().apply_move(move)) {
|
||||||
if (board().game_result() != Chess::Result::NotFinished) {
|
if (board().game_result() != Chess::Result::NotFinished) {
|
||||||
set_drag_enabled(false);
|
|
||||||
update();
|
|
||||||
|
|
||||||
|
bool over = true;
|
||||||
String msg;
|
String msg;
|
||||||
switch (board().game_result()) {
|
switch (board().game_result()) {
|
||||||
case Chess::Result::CheckMate:
|
case Chess::Result::CheckMate:
|
||||||
|
@ -140,15 +139,42 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event)
|
||||||
msg = "Draw by Stalemate.";
|
msg = "Draw by Stalemate.";
|
||||||
break;
|
break;
|
||||||
case Chess::Result::FiftyMoveRule:
|
case Chess::Result::FiftyMoveRule:
|
||||||
msg = "Draw by 50 move rule.";
|
update();
|
||||||
|
if (GUI::MessageBox::show(window(), "50 moves have elapsed without a capture. Claim Draw?", "Claim Draw?",
|
||||||
|
GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::YesNo)
|
||||||
|
== GUI::Dialog::ExecYes) {
|
||||||
|
msg = "Draw by 50 move rule.";
|
||||||
|
} else {
|
||||||
|
over = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Chess::Result::SeventyFiveMoveRule:
|
||||||
|
msg = "Draw by 75 move rule.";
|
||||||
break;
|
break;
|
||||||
case Chess::Result::ThreeFoldRepitition:
|
case Chess::Result::ThreeFoldRepitition:
|
||||||
msg = "Draw by threefold repitition.";
|
update();
|
||||||
|
if (GUI::MessageBox::show(window(), "The same board state has repeated three times. Claim Draw?", "Claim Draw?",
|
||||||
|
GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::YesNo)
|
||||||
|
== GUI::Dialog::ExecYes) {
|
||||||
|
msg = "Draw by threefold repitition.";
|
||||||
|
} else {
|
||||||
|
over = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Chess::Result::FiveFoldRepitition:
|
||||||
|
msg = "Draw by fivefold repitition.";
|
||||||
|
break;
|
||||||
|
case Chess::Result::InsufficientMaterial:
|
||||||
|
msg = "Draw by insufficient material.";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ASSERT_NOT_REACHED();
|
ASSERT_NOT_REACHED();
|
||||||
}
|
}
|
||||||
GUI::MessageBox::show(window(), msg, "Game Over", GUI::MessageBox::Type::Information);
|
if (over) {
|
||||||
|
set_drag_enabled(false);
|
||||||
|
update();
|
||||||
|
GUI::MessageBox::show(window(), msg, "Game Over", GUI::MessageBox::Type::Information);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue