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:
parent
9444272ba0
commit
ab4f4ddc3c
3 changed files with 125 additions and 10 deletions
|
@ -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) {
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue