Chess: Add display widget for moves

Adds a TextEditor widget to the chess application to display move
history. It is updated whenever Board::apply_move is called to reflect
the current board state.
This commit is contained in:
dgaston 2024-05-18 20:54:45 -04:00 committed by Andrew Kaster
parent 6a4938a524
commit 2d5cb1e02d
Notes: sideshowbarker 2024-07-16 23:51:07 +09:00
4 changed files with 63 additions and 12 deletions

View File

@ -2,7 +2,34 @@
fill_with_background_color: true
layout: @GUI::VerticalBoxLayout {}
@Chess::ChessWidget {
name: "chess_widget"
@GUI::HorizontalSplitter {
@GUI::Frame {
name: "chess_widget_frame"
min_width: 508
min_height: 508
layout: @GUI::HorizontalBoxLayout {}
@Chess::ChessWidget {
name: "chess_widget"
}
}
@GUI::Frame {
layout: @GUI::VerticalBoxLayout {}
@GUI::Label {
text: "Moves"
text_alignment: "Center"
font_weight: "Bold"
fixed_height: 24
name: "moves_display_widget_label"
}
@GUI::TextEditor {
name: "move_display_widget"
mode: "DisplayOnly"
focus_policy: "NoFocus"
}
}
}
}

View File

@ -9,6 +9,7 @@
#include "ChessWidget.h"
#include "PromotionDialog.h"
#include <AK/Enumerate.h>
#include <AK/GenericLexer.h>
#include <AK/Random.h>
#include <AK/String.h>
@ -37,7 +38,6 @@ ErrorOr<NonnullRefPtr<ChessWidget>> ChessWidget::try_create()
void ChessWidget::paint_event(GUI::PaintEvent& event)
{
int const min_size = min(width(), height());
int const widget_offset_x = (window()->width() - min_size) / 2;
int const widget_offset_y = (window()->height() - min_size) / 2;
GUI::Frame::paint_event(event);
@ -47,7 +47,7 @@ void ChessWidget::paint_event(GUI::PaintEvent& event)
painter.fill_rect(frame_inner_rect(), Gfx::Color::Black);
painter.translate(frame_thickness() + widget_offset_x, frame_thickness() + widget_offset_y);
painter.translate(frame_thickness(), frame_thickness() + widget_offset_y);
auto square_width = min_size / 8;
auto square_height = min_size / 8;
@ -211,7 +211,6 @@ void ChessWidget::paint_event(GUI::PaintEvent& event)
void ChessWidget::mousedown_event(GUI::MouseEvent& event)
{
int const min_size = min(width(), height());
int const widget_offset_x = (window()->width() - min_size) / 2;
int const widget_offset_y = (window()->height() - min_size) / 2;
if (!frame_inner_rect().contains(event.position()))
@ -237,7 +236,7 @@ void ChessWidget::mousedown_event(GUI::MouseEvent& event)
if (drag_enabled() && piece.color == board().turn() && !m_playback) {
m_dragging_piece = true;
set_override_cursor(Gfx::StandardCursor::Drag);
m_drag_point = { event.position().x() - widget_offset_x, event.position().y() - widget_offset_y };
m_drag_point = { event.position().x(), event.position().y() - widget_offset_y };
m_moving_square = square.value();
m_board.generate_moves([&](Chess::Move move) {
@ -295,6 +294,7 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event)
}
if (board().apply_move(move)) {
update_move_display_widget(m_board);
m_playback_move_number = board().moves().size();
m_playback = false;
m_board_playback = m_board;
@ -310,7 +310,6 @@ void ChessWidget::mouseup_event(GUI::MouseEvent& event)
void ChessWidget::mousemove_event(GUI::MouseEvent& event)
{
int const min_size = min(width(), height());
int const widget_offset_x = (window()->width() - min_size) / 2;
int const widget_offset_y = (window()->height() - min_size) / 2;
if (!frame_inner_rect().contains(event.position()))
@ -334,7 +333,7 @@ void ChessWidget::mousemove_event(GUI::MouseEvent& event)
return;
}
m_drag_point = { event.position().x() - widget_offset_x, event.position().y() - widget_offset_y };
m_drag_point = { event.position().x(), event.position().y() - widget_offset_y };
update();
}
@ -399,10 +398,9 @@ void ChessWidget::set_piece_set(StringView set)
Optional<Chess::Square> ChessWidget::mouse_to_square(GUI::MouseEvent& event) const
{
int const min_size = min(width(), height());
int const widget_offset_x = (window()->width() - min_size) / 2;
int const widget_offset_y = (window()->height() - min_size) / 2;
auto x = event.x() - widget_offset_x;
auto x = event.x();
auto y = event.y() - widget_offset_y;
if (x < 0 || y < 0 || x > min_size || y > min_size)
return {};
@ -433,6 +431,7 @@ void ChessWidget::reset()
m_playback_move_number = 0;
m_board_playback = Chess::Board();
m_board = Chess::Board();
update_move_display_widget(m_board);
m_side = (get_random<u32>() % 2) ? Chess::Color::White : Chess::Color::Black;
m_drag_enabled = true;
if (m_engine)
@ -483,10 +482,10 @@ void ChessWidget::input_engine_move()
set_drag_enabled(drag_was_enabled);
if (!move.is_error()) {
VERIFY(board().apply_move(move.release_value()));
update_move_display_widget(board());
if (check_game_over(ClaimDrawBehavior::Prompt))
return;
}
m_playback_move_number = m_board.moves().size();
m_playback = false;
m_board_markings.clear();
@ -509,6 +508,7 @@ void ChessWidget::playback_move(PlaybackDirection direction)
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));
update_move_display_widget(m_board_playback);
m_playback_move_number--;
break;
case PlaybackDirection::Forward:
@ -517,6 +517,7 @@ void ChessWidget::playback_move(PlaybackDirection direction)
return;
}
m_board_playback.apply_move(m_board.moves().at(m_playback_move_number++));
update_move_display_widget(m_board_playback);
if (m_playback_move_number == m_board.moves().size())
m_playback = false;
break;
@ -535,6 +536,21 @@ void ChessWidget::playback_move(PlaybackDirection direction)
update();
}
void ChessWidget::update_move_display_widget(Chess::Board& board)
{
size_t turn = 1;
StringBuilder sb;
for (auto [i, move] : enumerate(board.moves())) {
if (i % 2 == 0) {
sb.append(MUST(String::formatted("{}. {}", turn, MUST(move.to_algebraic()))));
} else {
sb.append(MUST(String::formatted(" {}\n", MUST(move.to_algebraic()))));
turn++;
}
}
m_move_display_widget->set_text(sb.string_view());
}
ErrorOr<String> ChessWidget::get_fen() const
{
return TRY(m_playback ? m_board_playback.to_fen() : m_board.to_fen());
@ -740,6 +756,7 @@ ErrorOr<void, PGNParseError> ChessWidget::import_pgn(Core::File& file)
m_board_playback = m_board;
m_playback_move_number = m_board_playback.moves().size();
m_playback = true;
update_move_display_widget(m_board_playback);
update();
return {};

View File

@ -16,6 +16,7 @@
#include <LibChess/Chess.h>
#include <LibConfig/Listener.h>
#include <LibGUI/Frame.h>
#include <LibGUI/TextEditor.h>
#include <LibGfx/Bitmap.h>
namespace Chess {
@ -81,6 +82,7 @@ public:
ErrorOr<String> get_fen() const;
ErrorOr<void, PGNParseError> import_pgn(Core::File&);
ErrorOr<void> export_pgn(Core::File&) const;
void update_move_display_widget(Chess::Board&);
int resign();
void flip_board();
@ -106,6 +108,7 @@ public:
void playback_move(PlaybackDirection);
void set_engine(RefPtr<Engine> engine) { m_engine = engine; }
void set_move_display_widget(RefPtr<GUI::TextEditor> move_display_widget) { m_move_display_widget = move_display_widget; }
void input_engine_move();
bool want_engine_move();
@ -174,6 +177,7 @@ private:
RefPtr<Engine> m_engine;
bool m_coordinates { true };
bool m_highlight_checks { true };
RefPtr<GUI::TextEditor> m_move_display_widget;
};
}

View File

@ -66,7 +66,10 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
auto window = GUI::Window::construct();
auto main_widget = TRY(Chess::MainWidget::try_create());
auto& chess_widget = *main_widget->find_descendant_of_type_named<Chess::ChessWidget>("chess_widget");
auto& move_display_widget = *main_widget->find_descendant_of_type_named<GUI::TextEditor>("move_display_widget");
chess_widget.set_move_display_widget(move(move_display_widget));
window->set_main_widget(main_widget);
window->set_focused_widget(&chess_widget);
@ -85,7 +88,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
window->set_title("Chess");
window->set_base_size({ 4, 4 });
window->set_size_increment({ 8, 8 });
window->resize(508, 508);
window->resize(668, 508);
window->set_icon(app_icon.bitmap_for_size(16));