Chess: Add ability to replay moves

This patch allows the user to go back and forward in move history
to replay moves from the game. This is view-only however, and as soon
as a move is made the board returns to it's current state. This will
work well for replaying games loaded in with PGN files, once that's
implemented.
This commit is contained in:
AnicJov 2020-12-10 17:07:10 +01:00 committed by Andreas Kling
parent cf8fce368a
commit 4d9837d792
Notes: sideshowbarker 2024-07-19 17:31:24 +09:00
2 changed files with 99 additions and 6 deletions

View file

@ -59,6 +59,8 @@ void ChessWidget::paint_event(GUI::PaintEvent& event)
size_t tile_height = height() / 8;
unsigned coord_rank_file = (side() == Chess::Colour::White) ? 0 : 7;
Chess::Board& active_board = (m_playback ? board_playback() : board());
Chess::Square::for_each([&](Chess::Square sq) {
Gfx::IntRect tile_rect;
if (side() == Chess::Colour::White) {
@ -69,7 +71,7 @@ void ChessWidget::paint_event(GUI::PaintEvent& event)
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 (active_board.last_move().has_value() && (active_board.last_move().value().to == sq || active_board.last_move().value().from == sq)) {
painter.fill_rect(tile_rect, m_move_highlight_color);
}
@ -87,7 +89,7 @@ void ChessWidget::paint_event(GUI::PaintEvent& event)
}
if (!(m_dragging_piece && sq == m_moving_square)) {
auto bmp = m_pieces.get(board().get_piece(sq));
auto bmp = m_pieces.get(active_board.get_piece(sq));
if (bmp.has_value()) {
painter.draw_scaled_bitmap(tile_rect, *bmp.value(), bmp.value()->rect());
}
@ -97,7 +99,7 @@ void ChessWidget::paint_event(GUI::PaintEvent& event)
});
if (m_dragging_piece) {
auto bmp = m_pieces.get(board().get_piece(m_moving_square));
auto bmp = m_pieces.get(active_board.get_piece(m_moving_square));
if (bmp.has_value()) {
auto center = m_drag_point - Gfx::IntPoint(tile_width / 2, tile_height / 2);
painter.draw_scaled_bitmap({ center, { tile_width, tile_height } }, *bmp.value(), bmp.value()->rect());
@ -110,7 +112,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 (drag_enabled() && piece.colour == board().turn()) {
if (drag_enabled() && piece.colour == board().turn() && !m_playback) {
m_dragging_piece = true;
m_drag_point = event.position();
m_moving_square = square;
@ -136,8 +138,11 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event)
}
if (board().apply_move(move)) {
if (board().game_result() != Chess::Board::Result::NotFinished) {
m_playback_move_number = board().moves().size();
m_playback = false;
m_board_playback = m_board;
if (board().game_result() != Chess::Board::Result::NotFinished) {
bool over = true;
String msg;
switch (board().game_result()) {
@ -206,6 +211,33 @@ void ChessWidget::mousemove_event(GUI::MouseEvent& event)
update();
}
void ChessWidget::keydown_event(GUI::KeyEvent& event)
{
switch (event.key()) {
case KeyCode::Key_Left:
playback_move(PlaybackDirection::Backward);
break;
case KeyCode::Key_Right:
playback_move(PlaybackDirection::Forward);
break;
case KeyCode::Key_Up:
playback_move(PlaybackDirection::Last);
break;
case KeyCode::Key_Down:
playback_move(PlaybackDirection::First);
break;
case KeyCode::Key_Home:
playback_move(PlaybackDirection::First);
break;
case KeyCode::Key_End:
playback_move(PlaybackDirection::Last);
break;
default:
return;
}
update();
}
static String set_path = String("/res/icons/chess/sets/");
static RefPtr<Gfx::Bitmap> get_piece(const StringView& set, const StringView& image)
@ -254,6 +286,9 @@ RefPtr<Gfx::Bitmap> ChessWidget::get_piece_graphic(const Chess::Piece& piece) co
void ChessWidget::reset()
{
m_playback = false;
m_playback_move_number = 0;
m_board_playback = Chess::Board();
m_board = Chess::Board();
m_side = (arc4random() % 2) ? Chess::Colour::White : Chess::Colour::Black;
m_drag_enabled = true;
@ -288,13 +323,55 @@ void ChessWidget::maybe_input_engine_move()
m_engine->get_best_move(board(), 4000, [this, drag_was_enabled](Chess::Move move) {
set_drag_enabled(drag_was_enabled);
ASSERT(board().apply_move(move));
m_playback_move_number = m_board.moves().size();
m_playback = false;
update();
});
}
void ChessWidget::playback_move(PlaybackDirection direction)
{
if (m_board.moves().is_empty())
return;
m_playback = true;
switch (direction) {
case PlaybackDirection::Backward:
if (m_playback_move_number == 0)
return;
m_board_playback = Chess::Board();
for (size_t i = 0; i < m_playback_move_number - 1; i++)
m_board_playback.apply_move(m_board.moves().at(i));
m_playback_move_number--;
break;
case PlaybackDirection::Forward:
if (m_playback_move_number + 1 > m_board.moves().size()) {
m_playback = false;
return;
}
m_board_playback.apply_move(m_board.moves().at(m_playback_move_number++));
if (m_playback_move_number == m_board.moves().size())
m_playback = false;
break;
case PlaybackDirection::First:
m_board_playback = Chess::Board();
m_playback_move_number = 0;
break;
case PlaybackDirection::Last:
while (m_playback) {
playback_move(PlaybackDirection::Forward);
}
break;
default:
ASSERT_NOT_REACHED();
}
update();
}
String ChessWidget::get_fen() const
{
return m_board.to_fen();
return m_playback ? m_board_playback.to_fen() : m_board.to_fen();
}
bool ChessWidget::export_pgn(const StringView& export_path) const

View file

@ -46,10 +46,14 @@ public:
virtual void mousedown_event(GUI::MouseEvent&) override;
virtual void mouseup_event(GUI::MouseEvent&) override;
virtual void mousemove_event(GUI::MouseEvent&) override;
virtual void keydown_event(GUI::KeyEvent&) override;
Chess::Board& board() { return m_board; };
const Chess::Board& board() const { return m_board; };
Chess::Board& board_playback() { return m_board_playback; };
const Chess::Board& board_playback() const { return m_board_playback; };
Chess::Colour side() const { return m_side; };
void set_side(Chess::Colour side) { m_side = side; };
@ -79,6 +83,15 @@ public:
void set_board_theme(const BoardTheme& theme) { m_board_theme = theme; }
void set_board_theme(const StringView& name);
enum class PlaybackDirection {
First,
Backward,
Forward,
Last
};
void playback_move(PlaybackDirection);
void set_engine(RefPtr<Engine> engine) { m_engine = engine; }
void maybe_input_engine_move();
@ -88,6 +101,9 @@ public:
private:
Chess::Board m_board;
Chess::Board m_board_playback;
bool m_playback { false };
size_t m_playback_move_number { 0 };
BoardTheme m_board_theme { "Beige", Color::from_rgb(0xb58863), Color::from_rgb(0xf0d9b5) };
Color m_move_highlight_color { Color::from_rgba(0x66ccee00) };
Chess::Colour m_side { Chess::Colour::White };