Chess: Add win/draw conditions, and display them.

This commit is contained in:
Peter Elliott 2020-08-11 14:10:39 -06:00 committed by Andreas Kling
parent e05372cee2
commit f2c1782d86
Notes: sideshowbarker 2024-07-19 03:35:47 +09:00
4 changed files with 159 additions and 3 deletions

View File

@ -352,3 +352,21 @@ bool Chess::apply_illegal_move(const Move& move, Colour colour)
return true;
}
Chess::Result Chess::game_result() const
{
bool are_legal_moves = false;
generate_moves([&](Move m) {
(void)m;
are_legal_moves = true;
return IterationDecision::Break;
});
if (are_legal_moves)
return Result::NotFinished;
if (in_check(turn()))
return Result::CheckMate;
return Result::StaleMate;
}

View File

@ -104,9 +104,20 @@ public:
bool apply_move(const Move&, Colour colour = Colour::None);
Colour turn() const { return m_turn; };
enum class Result {
CheckMate,
StaleMate,
FiftyMoveRule,
ThreeFoldRepitition,
NotFinished,
};
template<typename Callback>
void generate_moves(Callback callback, Colour colour = Colour::None) const;
Result game_result() const;
Colour turn() const { return m_turn; };
private:
bool is_legal_no_check(const Move&, Colour colour) const;
bool apply_illegal_move(const Move&, Colour colour);
@ -127,3 +138,98 @@ struct AK::Traits<Chess::Piece> : public GenericTraits<Chess::Piece> {
return pair_int_hash(static_cast<u32>(piece.colour), static_cast<u32>(piece.type));
}
};
template<typename Callback>
void Chess::generate_moves(Callback callback, Colour colour) const
{
if (colour == Colour::None)
colour = turn();
auto try_move = [&](Move m) {
if (is_legal(m, colour)) {
if (callback(m) == IterationDecision::Break)
return false;
}
return true;
};
Square::for_each([&](Square sq) {
auto piece = get_piece(sq);
if (piece.colour != colour)
return IterationDecision::Continue;
bool keep_going = true;
if (piece.type == Type::Pawn) {
keep_going =
try_move({sq, {sq.rank+1, sq.file}}) &&
try_move({sq, {sq.rank+2, sq.file}}) &&
try_move({sq, {sq.rank-1, sq.file}}) &&
try_move({sq, {sq.rank-2, sq.file}}) &&
try_move({sq, {sq.rank+1, sq.file+1}}) &&
try_move({sq, {sq.rank+1, sq.file-1}}) &&
try_move({sq, {sq.rank-1, sq.file+1}}) &&
try_move({sq, {sq.rank-1, sq.file-1}});
} else if (piece.type == Type::Knight) {
keep_going =
try_move({sq, {sq.rank+2, sq.file+1}}) &&
try_move({sq, {sq.rank+2, sq.file-1}}) &&
try_move({sq, {sq.rank+1, sq.file+2}}) &&
try_move({sq, {sq.rank+1, sq.file-2}}) &&
try_move({sq, {sq.rank-2, sq.file+1}}) &&
try_move({sq, {sq.rank-2, sq.file-1}}) &&
try_move({sq, {sq.rank-1, sq.file+2}}) &&
try_move({sq, {sq.rank-1, sq.file-2}});
} else if (piece.type == Type::Bishop) {
for (int dr = -1; dr <= 1; dr += 2) {
for (int df = -1; df <= 1; df += 2) {
for (Square to = sq; to.in_bounds(); to = { to.rank + dr, to.file + df }) {
if (!try_move({ sq, to }))
return IterationDecision::Break;
}
}
}
} else if (piece.type == Type::Rook) {
for (int dr = -1; dr <= 1; dr++) {
for (int df = -1; df <= 1; df++) {
if ((dr == 0) != (df == 0)) {
for (Square to = sq; to.in_bounds(); to = { to.rank + dr, to.file + df }) {
if (!try_move({ sq, to }))
return IterationDecision::Break;
}
}
}
}
} else if (piece.type == Type::Queen) {
for (int dr = -1; dr <= 1; dr++) {
for (int df = -1; df <= 1; df++) {
if (dr != 0 || df != 0) {
for (Square to = sq; to.in_bounds(); to = { to.rank + dr, to.file + df }) {
if (!try_move({ sq, to }))
return IterationDecision::Break;
}
}
}
}
} else if (piece.type == Type::King) {
for (int dr = -1; dr <= 1; dr++) {
for (int df = -1; df <= 1; df++) {
if (!try_move({ sq, { sq.rank + dr, sq.file + df } }))
return IterationDecision::Break;
}
}
// Castling moves.
if (sq == Square("e1")) {
keep_going = try_move({ sq, Square("c1") }) && try_move({ sq, Square("g1") });
} else if (sq == Square("e8")) {
keep_going = try_move({ sq, Square("c8") }) && try_move({ sq, Square("g8") });
}
}
if (keep_going) {
return IterationDecision::Continue;
} else {
return IterationDecision::Break;
}
});
}

View File

@ -26,6 +26,7 @@
#include "ChessWidget.h"
#include <AK/String.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/Painter.h>
ChessWidget::ChessWidget(const StringView& set)
@ -92,7 +93,7 @@ void ChessWidget::mousedown_event(GUI::MouseEvent& event)
GUI::Widget::mousedown_event(event);
auto square = mouse_to_square(event);
auto piece = board().get_piece(square);
if (piece.colour == board().turn()) {
if (drag_enabled() && piece.colour == board().turn()) {
m_dragging_piece = true;
m_drag_point = event.position();
m_moving_square = square;
@ -110,7 +111,34 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event)
auto target_square = mouse_to_square(event);
board().apply_move({ m_moving_square, target_square });
if (board().apply_move({ m_moving_square, target_square }) && board().game_result() != Chess::Result::NotFinished) {
set_drag_enabled(false);
update();
String msg;
switch (board().game_result()) {
case Chess::Result::CheckMate:
if (board().turn() == Chess::Colour::White) {
msg = "Black wins by Checkmate.";
} else {
msg = "White wins by Checkmate.";
}
break;
case Chess::Result::StaleMate:
msg = "Draw by Stalemate.";
break;
case Chess::Result::FiftyMoveRule:
msg = "Draw by 50 move rule.";
break;
case Chess::Result::ThreeFoldRepitition:
msg = "Draw by threefold repitition.";
break;
default:
ASSERT_NOT_REACHED();
}
GUI::MessageBox::show(window(), msg, "Game Over");
}
update();
}

View File

@ -56,6 +56,9 @@ public:
Chess::Square mouse_to_square(GUI::MouseEvent& event) const;
bool drag_enabled() const { return m_drag_enabled; }
void set_drag_enabled(bool e) { m_drag_enabled = e; }
private:
Chess m_board;
Color m_dark_square_color { Color::from_rgb(0xb58863) };
@ -66,4 +69,5 @@ private:
Chess::Square m_moving_square { 50, 50 };
Gfx::IntPoint m_drag_point;
bool m_dragging_piece { false };
bool m_drag_enabled { true };
};