mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 19:27:45 +00:00
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:
parent
cf8fce368a
commit
4d9837d792
2 changed files with 99 additions and 6 deletions
|
@ -59,6 +59,8 @@ void ChessWidget::paint_event(GUI::PaintEvent& event)
|
||||||
size_t tile_height = height() / 8;
|
size_t tile_height = height() / 8;
|
||||||
unsigned coord_rank_file = (side() == Chess::Colour::White) ? 0 : 7;
|
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) {
|
Chess::Square::for_each([&](Chess::Square sq) {
|
||||||
Gfx::IntRect tile_rect;
|
Gfx::IntRect tile_rect;
|
||||||
if (side() == Chess::Colour::White) {
|
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);
|
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);
|
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)) {
|
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()) {
|
if (bmp.has_value()) {
|
||||||
painter.draw_scaled_bitmap(tile_rect, *bmp.value(), bmp.value()->rect());
|
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) {
|
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()) {
|
if (bmp.has_value()) {
|
||||||
auto center = m_drag_point - Gfx::IntPoint(tile_width / 2, tile_height / 2);
|
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());
|
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);
|
GUI::Widget::mousedown_event(event);
|
||||||
auto square = mouse_to_square(event);
|
auto square = mouse_to_square(event);
|
||||||
auto piece = board().get_piece(square);
|
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_dragging_piece = true;
|
||||||
m_drag_point = event.position();
|
m_drag_point = event.position();
|
||||||
m_moving_square = square;
|
m_moving_square = square;
|
||||||
|
@ -136,8 +138,11 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (board().apply_move(move)) {
|
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;
|
bool over = true;
|
||||||
String msg;
|
String msg;
|
||||||
switch (board().game_result()) {
|
switch (board().game_result()) {
|
||||||
|
@ -206,6 +211,33 @@ void ChessWidget::mousemove_event(GUI::MouseEvent& event)
|
||||||
update();
|
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 String set_path = String("/res/icons/chess/sets/");
|
||||||
|
|
||||||
static RefPtr<Gfx::Bitmap> get_piece(const StringView& set, const StringView& image)
|
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()
|
void ChessWidget::reset()
|
||||||
{
|
{
|
||||||
|
m_playback = false;
|
||||||
|
m_playback_move_number = 0;
|
||||||
|
m_board_playback = Chess::Board();
|
||||||
m_board = Chess::Board();
|
m_board = Chess::Board();
|
||||||
m_side = (arc4random() % 2) ? Chess::Colour::White : Chess::Colour::Black;
|
m_side = (arc4random() % 2) ? Chess::Colour::White : Chess::Colour::Black;
|
||||||
m_drag_enabled = true;
|
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) {
|
m_engine->get_best_move(board(), 4000, [this, drag_was_enabled](Chess::Move move) {
|
||||||
set_drag_enabled(drag_was_enabled);
|
set_drag_enabled(drag_was_enabled);
|
||||||
ASSERT(board().apply_move(move));
|
ASSERT(board().apply_move(move));
|
||||||
|
m_playback_move_number = m_board.moves().size();
|
||||||
|
m_playback = false;
|
||||||
update();
|
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
|
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
|
bool ChessWidget::export_pgn(const StringView& export_path) const
|
||||||
|
|
|
@ -46,10 +46,14 @@ public:
|
||||||
virtual void mousedown_event(GUI::MouseEvent&) override;
|
virtual void mousedown_event(GUI::MouseEvent&) override;
|
||||||
virtual void mouseup_event(GUI::MouseEvent&) override;
|
virtual void mouseup_event(GUI::MouseEvent&) override;
|
||||||
virtual void mousemove_event(GUI::MouseEvent&) override;
|
virtual void mousemove_event(GUI::MouseEvent&) override;
|
||||||
|
virtual void keydown_event(GUI::KeyEvent&) override;
|
||||||
|
|
||||||
Chess::Board& board() { return m_board; };
|
Chess::Board& board() { return m_board; };
|
||||||
const Chess::Board& board() const { 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; };
|
Chess::Colour side() const { return m_side; };
|
||||||
void set_side(Chess::Colour side) { m_side = 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 BoardTheme& theme) { m_board_theme = theme; }
|
||||||
void set_board_theme(const StringView& name);
|
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 set_engine(RefPtr<Engine> engine) { m_engine = engine; }
|
||||||
|
|
||||||
void maybe_input_engine_move();
|
void maybe_input_engine_move();
|
||||||
|
@ -88,6 +101,9 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Chess::Board m_board;
|
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) };
|
BoardTheme m_board_theme { "Beige", Color::from_rgb(0xb58863), Color::from_rgb(0xf0d9b5) };
|
||||||
Color m_move_highlight_color { Color::from_rgba(0x66ccee00) };
|
Color m_move_highlight_color { Color::from_rgba(0x66ccee00) };
|
||||||
Chess::Colour m_side { Chess::Colour::White };
|
Chess::Colour m_side { Chess::Colour::White };
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue