1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 22:57:44 +00:00

Solitaire: Add undo functionality

This commit is contained in:
Matthew B. Jones 2021-06-02 18:16:49 -06:00 committed by GitHub
parent 9444272ba0
commit ab4f4ddc3c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 125 additions and 10 deletions

View file

@ -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) {

View file

@ -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];

View file

@ -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();