diff options
author | Matthew B. Jones <matthewbjones85@gmail.com> | 2021-06-02 18:16:49 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-03 01:16:49 +0100 |
commit | ab4f4ddc3c7375a758925c3ff66a553a835f706d (patch) | |
tree | b9bdd5468fc4a41a46720f18002285e56155af37 /Userland | |
parent | 9444272ba02cc0a1815d79c22ef2fb0cc3c79548 (diff) | |
download | serenity-ab4f4ddc3c7375a758925c3ff66a553a835f706d.zip |
Solitaire: Add undo functionality
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Games/Solitaire/Game.cpp | 105 | ||||
-rw-r--r-- | Userland/Games/Solitaire/Game.h | 19 | ||||
-rw-r--r-- | Userland/Games/Solitaire/main.cpp | 11 |
3 files changed, 125 insertions, 10 deletions
diff --git a/Userland/Games/Solitaire/Game.cpp b/Userland/Games/Solitaire/Game.cpp index b9ae0914dc..bf1b0c51f4 100644 --- a/Userland/Games/Solitaire/Game.cpp +++ b/Userland/Games/Solitaire/Game.cpp @@ -113,6 +113,8 @@ void Game::setup(Mode mode) m_passes_left_before_punishment = recycle_rules().passes_allowed_before_punishment; m_score = 0; update_score(0); + if (on_undo_availability_change) + on_undo_availability_change(false); for (int i = 0; i < Card::card_count; ++i) { m_new_deck.append(Card::construct(Card::Type::Clubs, i)); @@ -129,6 +131,19 @@ void Game::setup(Mode mode) update(); } +void Game::score_move(CardStack& from, CardStack& to, bool inverse = false) +{ + if (from.type() == CardStack::Type::Play && to.type() == CardStack::Type::Normal) { + update_score(5 * (inverse ? -1 : 1)); + } else if (from.type() == CardStack::Type::Play && to.type() == CardStack::Type::Foundation) { + update_score(10 * (inverse ? -1 : 1)); + } else if (from.type() == CardStack::Type::Normal && to.type() == CardStack::Type::Foundation) { + update_score(10 * (inverse ? -1 : 1)); + } else if (from.type() == CardStack::Type::Foundation && to.type() == CardStack::Type::Normal) { + update_score(-15 * (inverse ? -1 : 1)); + } +} + void Game::update_score(int to_add) { m_score = max(static_cast<int>(m_score) + to_add, 0); @@ -213,11 +228,15 @@ void Game::mousedown_event(GUI::MouseEvent& event) update(stock.bounding_box()); + NonnullRefPtrVector<Card> cards_drawn; for (size_t i = 0; (i < cards_to_draw) && !stock.is_empty(); ++i) { auto card = stock.pop(); + cards_drawn.prepend(card); play.push(move(card)); } + remember_move_for_undo(stock, play, cards_drawn); + if (play.bounding_box().size().width() > play_bounding_box.size().width()) update(play.bounding_box()); else @@ -231,6 +250,7 @@ void Game::mousedown_event(GUI::MouseEvent& event) top_card.set_upside_down(false); update_score(5); update(top_card.rect()); + remember_flip_for_undo(top_card); } } else if (m_focused_cards.is_empty()) { to_check.add_all_grabbed_cards(click_location, m_focused_cards); @@ -266,11 +286,13 @@ void Game::mouseup_event(GUI::MouseEvent& event) m_focused_stack->pop(); } + remember_move_for_undo(*m_focused_stack, stack, m_focused_cards); + if (m_focused_stack->type() == CardStack::Type::Play) { auto& waste = this->stack(Waste); if (m_focused_stack->is_empty() && !waste.is_empty()) { auto card = waste.pop(); - m_focused_cards.append(card); + m_focused_cards.prepend(card); m_focused_stack->push(move(card)); } } @@ -278,15 +300,7 @@ void Game::mouseup_event(GUI::MouseEvent& event) update(m_focused_stack->bounding_box()); update(stack.bounding_box()); - if (m_focused_stack->type() == CardStack::Type::Play && stack.type() == CardStack::Type::Normal) { - update_score(5); - } else if (m_focused_stack->type() == CardStack::Type::Play && stack.type() == CardStack::Type::Foundation) { - update_score(10); - } else if (m_focused_stack->type() == CardStack::Type::Normal && stack.type() == CardStack::Type::Foundation) { - update_score(10); - } else if (m_focused_stack->type() == CardStack::Type::Foundation && stack.type() == CardStack::Type::Normal) { - update_score(-15); - } + score_move(*m_focused_stack, stack); rebound = false; break; @@ -397,6 +411,8 @@ void Game::move_card(CardStack& from, CardStack& to) mark_intersecting_stacks_dirty(card); to.push(card); + remember_move_for_undo(from, to, m_focused_cards); + update(to.bounding_box()); } @@ -482,6 +498,75 @@ void Game::paint_event(GUI::PaintEvent& event) } } +void Game::remember_move_for_undo(CardStack& from, CardStack& to, NonnullRefPtrVector<Card> moved_cards) +{ + m_last_move.type = LastMove::Type::MoveCards; + m_last_move.from = &from; + m_last_move.cards = moved_cards; + m_last_move.to = &to; + if (on_undo_availability_change) + on_undo_availability_change(true); +} + +void Game::remember_flip_for_undo(Card& card) +{ + NonnullRefPtrVector<Card> cards; + cards.append(card); + m_last_move.type = LastMove::Type::FlipCard; + m_last_move.cards = cards; + if (on_undo_availability_change) + on_undo_availability_change(true); +} + +void Game::perform_undo() +{ + if (m_last_move.type == LastMove::Type::Invalid) + return; + + if (m_last_move.type == LastMove::Type::FlipCard) { + m_last_move.cards.at(0).set_upside_down(true); + if (on_undo_availability_change) + on_undo_availability_change(false); + invalidate_layout(); + return; + } + + if (m_last_move.from->type() == CardStack::Type::Play && m_mode == Mode::SingleCardDraw) { + auto& waste = stack(Waste); + if (!m_last_move.from->is_empty()) + waste.push(m_last_move.from->pop()); + } + + for (auto& to_intersect : m_last_move.cards) { + mark_intersecting_stacks_dirty(to_intersect); + m_last_move.from->push(to_intersect); + m_last_move.to->pop(); + } + + if (m_last_move.from->type() == CardStack::Type::Stock) { + auto& waste = this->stack(Waste); + auto& play = this->stack(Play); + NonnullRefPtrVector<Card> cards_popped; + for (size_t i = 0; i < m_last_move.cards.size(); i++) { + if (!waste.is_empty()) { + auto card = waste.pop(); + cards_popped.prepend(card); + } + } + for (auto& card : cards_popped) { + m_focused_cards.append(card); + play.push(move(card)); + } + } + + score_move(*m_last_move.from, *m_last_move.to, true); + + m_last_move = {}; + if (on_undo_availability_change) + on_undo_availability_change(false); + invalidate_layout(); +} + void Game::dump_layout() const { if constexpr (SOLITAIRE_DEBUG) { diff --git a/Userland/Games/Solitaire/Game.h b/Userland/Games/Solitaire/Game.h index a77c9d943c..1e59919691 100644 --- a/Userland/Games/Solitaire/Game.h +++ b/Userland/Games/Solitaire/Game.h @@ -37,10 +37,12 @@ public: Mode mode() const { return m_mode; } void setup(Mode); + void perform_undo(); Function<void(uint32_t)> on_score_update; Function<void()> on_game_start; Function<void(GameOverReason, uint32_t)> on_game_end; + Function<void(bool)> on_undo_availability_change; private: Game(); @@ -103,6 +105,19 @@ private: int8_t punishment { 0 }; }; + struct LastMove { + enum class Type { + Invalid, + MoveCards, + FlipCard + }; + + Type type { Type::Invalid }; + CardStack* from { nullptr }; + NonnullRefPtrVector<Card> cards; + CardStack* to { nullptr }; + }; + enum StackLocation { Stock, Waste, @@ -140,6 +155,9 @@ private: } void mark_intersecting_stacks_dirty(Card& intersecting_card); + void score_move(CardStack& from, CardStack& to, bool inverse); + void remember_move_for_undo(CardStack& from, CardStack& to, NonnullRefPtrVector<Card> moved_cards); + void remember_flip_for_undo(Card& card); void update_score(int to_add); void move_card(CardStack& from, CardStack& to); void start_game_over_animation(); @@ -163,6 +181,7 @@ private: Mode m_mode { Mode::SingleCardDraw }; + LastMove m_last_move; NonnullRefPtrVector<Card> m_focused_cards; NonnullRefPtrVector<Card> m_new_deck; CardStack m_stacks[StackLocation::__Count]; diff --git a/Userland/Games/Solitaire/main.cpp b/Userland/Games/Solitaire/main.cpp index 3cdcddb2f0..e8cb2ca7d7 100644 --- a/Userland/Games/Solitaire/main.cpp +++ b/Userland/Games/Solitaire/main.cpp @@ -177,6 +177,12 @@ int main(int argc, char** argv) game.setup(mode); })); game_menu.add_separator(); + auto undo_action = GUI::CommonActions::make_undo_action([&](auto&) { + game.perform_undo(); + }); + undo_action->set_enabled(false); + game_menu.add_action(undo_action); + game_menu.add_separator(); game_menu.add_action(single_card_draw_action); game_menu.add_action(three_card_draw_action); game_menu.add_separator(); @@ -190,6 +196,11 @@ int main(int argc, char** argv) window->set_menubar(move(menubar)); 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(); |