summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorMatthew B. Jones <matthewbjones85@gmail.com>2021-06-02 18:16:49 -0600
committerGitHub <noreply@github.com>2021-06-03 01:16:49 +0100
commitab4f4ddc3c7375a758925c3ff66a553a835f706d (patch)
treeb9bdd5468fc4a41a46720f18002285e56155af37 /Userland
parent9444272ba02cc0a1815d79c22ef2fb0cc3c79548 (diff)
downloadserenity-ab4f4ddc3c7375a758925c3ff66a553a835f706d.zip
Solitaire: Add undo functionality
Diffstat (limited to 'Userland')
-rw-r--r--Userland/Games/Solitaire/Game.cpp105
-rw-r--r--Userland/Games/Solitaire/Game.h19
-rw-r--r--Userland/Games/Solitaire/main.cpp11
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();