diff options
author | Tim Ledbetter <timledbetter@gmail.com> | 2023-04-02 21:14:16 +0100 |
---|---|---|
committer | Sam Atkins <atkinssj@gmail.com> | 2023-04-20 09:59:18 +0100 |
commit | 8b6c538f2a6c0d3b1597a1a2c86f634594cc2584 (patch) | |
tree | fa06c822eb46b84397d76bb74455dbf9070168d4 /Userland | |
parent | 55347ed6a59d51edd7a1b1867682cf751fb900be (diff) | |
download | serenity-8b6c538f2a6c0d3b1597a1a2c86f634594cc2584.zip |
Chess: Gracefully handle ChessEngine disconnections
The GUI now tracks when it becomes disconnected from ChessEngine.
If not currently waiting for a move from ChessEngine, it will
automatically reconnect on the next engine move. If a disconnection
occurs while waiting for a move, the player is asked whether they
want to try again or not.
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Games/Chess/ChessWidget.cpp | 6 | ||||
-rw-r--r-- | Userland/Games/Chess/Engine.cpp | 28 | ||||
-rw-r--r-- | Userland/Games/Chess/Engine.h | 11 | ||||
-rw-r--r-- | Userland/Games/Chess/main.cpp | 38 |
4 files changed, 63 insertions, 20 deletions
diff --git a/Userland/Games/Chess/ChessWidget.cpp b/Userland/Games/Chess/ChessWidget.cpp index 753bb91f66..bb2bd05900 100644 --- a/Userland/Games/Chess/ChessWidget.cpp +++ b/Userland/Games/Chess/ChessWidget.cpp @@ -473,12 +473,14 @@ void ChessWidget::input_engine_move() set_drag_enabled(false); set_override_cursor(Gfx::StandardCursor::Wait); - 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](ErrorOr<Chess::Move> move) { set_override_cursor(Gfx::StandardCursor::None); if (!want_engine_move()) return; set_drag_enabled(drag_was_enabled); - VERIFY(board().apply_move(move)); + if (!move.is_error()) + VERIFY(board().apply_move(move.release_value())); + m_playback_move_number = m_board.moves().size(); m_playback = false; m_board_markings.clear(); diff --git a/Userland/Games/Chess/Engine.cpp b/Userland/Games/Chess/Engine.cpp index cbfcf40d1d..8f8b95227b 100644 --- a/Userland/Games/Chess/Engine.cpp +++ b/Userland/Games/Chess/Engine.cpp @@ -17,6 +17,11 @@ Engine::~Engine() } Engine::Engine(StringView command) + : m_command(command) +{ +} + +void Engine::connect_to_engine_service() { int wpipefds[2]; int rpipefds[2]; @@ -35,9 +40,9 @@ Engine::Engine(StringView command) posix_spawn_file_actions_adddup2(&file_actions, wpipefds[0], STDIN_FILENO); posix_spawn_file_actions_adddup2(&file_actions, rpipefds[1], STDOUT_FILENO); - DeprecatedString cstr(command); - char const* argv[] = { cstr.characters(), nullptr }; - if (posix_spawnp(&m_pid, cstr.characters(), &file_actions, nullptr, const_cast<char**>(argv), environ) < 0) { + char const* argv[] = { m_command.characters(), nullptr }; + pid_t pid = -1; + if (posix_spawnp(&pid, m_command.characters(), &file_actions, nullptr, const_cast<char**>(argv), environ) < 0) { perror("posix_spawnp"); VERIFY_NOT_REACHED(); } @@ -56,6 +61,7 @@ Engine::Engine(StringView command) set_out(outfile); send_command(Chess::UCI::UCICommand()); + m_connected = true; } void Engine::handle_bestmove(Chess::UCI::BestMoveCommand const& command) @@ -68,5 +74,21 @@ void Engine::handle_bestmove(Chess::UCI::BestMoveCommand const& command) void Engine::quit() { + if (!m_connected) + return; + send_command(Chess::UCI::QuitCommand()); + m_connected = false; +} + +void Engine::handle_unexpected_eof() +{ + m_connected = false; + if (m_bestmove_callback) + m_bestmove_callback(Error::from_errno(EPIPE)); + + m_bestmove_callback = nullptr; + + if (on_connection_lost) + on_connection_lost(); } diff --git a/Userland/Games/Chess/Engine.h b/Userland/Games/Chess/Engine.h index dd5906cf84..efbfd5b463 100644 --- a/Userland/Games/Chess/Engine.h +++ b/Userland/Games/Chess/Engine.h @@ -20,11 +20,17 @@ public: Engine(Engine const&) = delete; Engine& operator=(Engine const&) = delete; + Function<void()> on_connection_lost; + virtual void handle_bestmove(Chess::UCI::BestMoveCommand const&) override; + virtual void handle_unexpected_eof() override; template<typename Callback> void get_best_move(Chess::Board const& board, int time_limit, Callback&& callback) { + if (!m_connected) + connect_to_engine_service(); + send_command(Chess::UCI::PositionCommand({}, board.moves())); Chess::UCI::GoCommand go_command; go_command.movetime = time_limit; @@ -34,6 +40,9 @@ public: private: void quit(); + void connect_to_engine_service(); - Function<void(Chess::Move)> m_bestmove_callback; + DeprecatedString m_command; + Function<void(ErrorOr<Chess::Move>)> m_bestmove_callback; + bool m_connected { false }; }; diff --git a/Userland/Games/Chess/main.cpp b/Userland/Games/Chess/main.cpp index f777277598..62db52d1d2 100644 --- a/Userland/Games/Chess/main.cpp +++ b/Userland/Games/Chess/main.cpp @@ -128,21 +128,31 @@ ErrorOr<int> serenity_main(Main::Arguments arguments) GUI::ActionGroup engines_action_group; engines_action_group.set_exclusive(true); auto engine_submenu = TRY(engine_menu->try_add_submenu("&Engine"_short_string)); - for (auto const& engine : { "Human", "ChessEngine" }) { - auto action = GUI::Action::create_checkable(engine, [&](auto& action) { - if (action.text() == "Human") { - widget->set_engine(nullptr); - } else { - widget->set_engine(Engine::construct(action.text())); + auto human_engine_checkbox = GUI::Action::create_checkable("Human", [&](auto&) { + widget->set_engine(nullptr); + }); + human_engine_checkbox->set_checked(true); + engines_action_group.add_action(human_engine_checkbox); + TRY(engine_submenu->try_add_action(human_engine_checkbox)); + + auto action = GUI::Action::create_checkable("ChessEngine", [&](auto& action) { + auto new_engine = Engine::construct(action.text()); + new_engine->on_connection_lost = [&]() { + if (!widget->want_engine_move()) + return; + + auto rc = GUI::MessageBox::show(window, "Connection to the chess engine was lost while waiting for a move. Do you want to try again?"sv, "Chess"sv, GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo); + if (rc == GUI::Dialog::ExecResult::Yes) widget->input_engine_move(); - } - }); - engines_action_group.add_action(*action); - if (engine == DeprecatedString("Human")) - action->set_checked(true); - - TRY(engine_submenu->try_add_action(*action)); - } + else + human_engine_checkbox->activate(); + }; + widget->set_engine(move(new_engine)); + widget->input_engine_move(); + }); + engines_action_group.add_action(*action); + + TRY(engine_submenu->try_add_action(*action)); auto help_menu = TRY(window->try_add_menu("&Help"_short_string)); TRY(help_menu->try_add_action(GUI::CommonActions::make_command_palette_action(window))); |