diff --git a/Userland/Games/Spider/Game.cpp b/Userland/Games/Spider/Game.cpp index af6c65ac47a..54356cb3840 100644 --- a/Userland/Games/Spider/Game.cpp +++ b/Userland/Games/Spider/Game.cpp @@ -37,6 +37,9 @@ void Game::setup(Mode mode) m_mode = mode; + if (on_undo_availability_change) + on_undo_availability_change(false); + if (on_game_end) on_game_end(GameOverReason::NewGame, m_score); @@ -74,6 +77,28 @@ void Game::setup(Mode mode) update(); } +void Game::perform_undo() +{ + if (m_last_move.type == LastMove::Type::Invalid) + return; + + if (!m_last_move.was_visible) + m_last_move.from->peek().set_upside_down(true); + + NonnullRefPtrVector cards; + for (size_t i = 0; i < m_last_move.card_count; i++) + cards.append(m_last_move.to->pop()); + for (ssize_t i = m_last_move.card_count - 1; i >= 0; i--) + m_last_move.from->push(cards[i]); + + update_score(-1); + + m_last_move = {}; + if (on_undo_availability_change) + on_undo_availability_change(false); + invalidate_layout(); +} + void Game::start_timer_if_necessary() { if (on_game_start && m_waiting_for_new_game) { @@ -141,6 +166,9 @@ void Game::detect_full_stacks() update(current_pile.peek().rect()); update_score(101); + + if (on_undo_availability_change) + on_undo_availability_change(false); } last_value = to_underlying(card.rank()); @@ -159,6 +187,9 @@ void Game::detect_victory() return; } + if (on_undo_availability_change) + on_undo_availability_change(false); + if (on_game_end) on_game_end(GameOverReason::Victory, m_score); } @@ -198,6 +229,17 @@ void Game::paint_event(GUI::PaintEvent& event) } } +void Game::remember_move_for_undo(CardStack& from, CardStack& to, size_t card_count, bool was_visible) +{ + m_last_move.type = LastMove::Type::MoveCards; + m_last_move.from = &from; + m_last_move.card_count = card_count; + m_last_move.to = &to; + m_last_move.was_visible = was_visible; + if (on_undo_availability_change) + on_undo_availability_change(true); +} + void Game::mousedown_event(GUI::MouseEvent& event) { GUI::Frame::mousedown_event(event); @@ -239,7 +281,10 @@ void Game::mousedown_event(GUI::MouseEvent& event) void Game::move_focused_cards(CardStack& stack) { + auto card_count = moving_cards().size(); drop_cards_on_stack(stack, Cards::CardStack::MovementRule::Any); + bool was_visible = moving_cards_source_stack()->is_empty() || !moving_cards_source_stack()->peek().is_upside_down(); + remember_move_for_undo(*moving_cards_source_stack(), stack, card_count, was_visible); update_score(-1); moving_cards_source_stack()->make_top_card_visible(); detect_full_stacks(); diff --git a/Userland/Games/Spider/Game.h b/Userland/Games/Spider/Game.h index e35a5a1db4d..1df7b50915a 100644 --- a/Userland/Games/Spider/Game.h +++ b/Userland/Games/Spider/Game.h @@ -40,13 +40,29 @@ public: Mode mode() const { return m_mode; } void setup(Mode); + void perform_undo(); + Function on_score_update; Function on_game_start; Function on_game_end; + Function on_undo_availability_change; private: Game(); + struct LastMove { + enum class Type { + Invalid, + MoveCards + }; + + Type type { Type::Invalid }; + CardStack* from { nullptr }; + size_t card_count; + bool was_visible; + CardStack* to { nullptr }; + }; + enum StackLocation { Completed, Stock, @@ -68,6 +84,7 @@ private: }; void start_timer_if_necessary(); + void remember_move_for_undo(CardStack&, CardStack&, size_t, bool); void update_score(int delta); void draw_cards(); void detect_full_stacks(); @@ -82,6 +99,7 @@ private: Mode m_mode { Mode::SingleSuit }; + LastMove m_last_move; NonnullRefPtrVector m_new_deck; Gfx::IntPoint m_mouse_down_location; diff --git a/Userland/Games/Spider/main.cpp b/Userland/Games/Spider/main.cpp index 8dca996dc17..a1277bb1fa4 100644 --- a/Userland/Games/Spider/main.cpp +++ b/Userland/Games/Spider/main.cpp @@ -239,6 +239,12 @@ ErrorOr serenity_main(Main::Arguments arguments) game.setup(mode); }))); TRY(game_menu->try_add_separator()); + auto undo_action = GUI::CommonActions::make_undo_action([&](auto&) { + game.perform_undo(); + }); + undo_action->set_enabled(false); + TRY(game_menu->try_add_action(undo_action)); + TRY(game_menu->try_add_separator()); TRY(game_menu->try_add_action(single_suit_action)); TRY(game_menu->try_add_action(two_suit_action)); TRY(game_menu->try_add_separator()); @@ -274,6 +280,10 @@ ErrorOr serenity_main(Main::Arguments arguments) window->set_icon(app_icon.bitmap_for_size(16)); window->show(); + game.on_undo_availability_change = [&](bool undo_available) { + undo_action->set_enabled(undo_available); + }; + game.setup(mode); return app->exec();