/* * Copyright (c) 2020, Till Mayer * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include using Cards::Card; using Cards::CardStack; namespace Solitaire { enum class Mode : u8 { SingleCardDraw, ThreeCardDraw, __Count }; enum class GameOverReason { Victory, NewGame, }; class Game final : public GUI::Frame { C_OBJECT(Game) public: static constexpr int width = 640; static constexpr int height = 480; virtual ~Game() override; Mode mode() const { return m_mode; } void setup(Mode); Function on_score_update; Function on_game_start; Function on_game_end; private: Game(); class Animation { public: Animation() { } Animation(RefPtr animation_card, float gravity, int x_vel, float bouncyness) : m_animation_card(animation_card) , m_gravity(gravity) , m_x_velocity(x_vel) , m_bouncyness(bouncyness) { } RefPtr card() { return m_animation_card; } void draw(GUI::Painter& painter) { VERIFY(!m_animation_card.is_null()); m_animation_card->draw(painter); m_dirty = false; } bool tick() { // Don't move the animation card until the event loop has had a chance to paint its current location. if (m_dirty) return false; VERIFY(!m_animation_card.is_null()); m_y_velocity += m_gravity; if (m_animation_card->position().y() + Card::height + m_y_velocity > Game::height + 1 && m_y_velocity > 0) { m_y_velocity = min((m_y_velocity * -m_bouncyness), -8.f); m_animation_card->rect().set_y(Game::height - Card::height); m_animation_card->rect().translate_by(m_x_velocity, 0); } else { m_animation_card->rect().translate_by(m_x_velocity, m_y_velocity); } m_dirty = true; return true; } private: RefPtr m_animation_card; float m_gravity { 0 }; int m_x_velocity { 0 }; float m_y_velocity { 0 }; float m_bouncyness { 0 }; bool m_dirty { false }; }; struct WasteRecycleRules { uint8_t passes_allowed_before_punishment { 0 }; int8_t punishment { 0 }; }; enum StackLocation { Stock, Waste, Play, Foundation1, Foundation2, Foundation3, Foundation4, Pile1, Pile2, Pile3, Pile4, Pile5, Pile6, Pile7, __Count }; static constexpr Array piles = { Pile1, Pile2, Pile3, Pile4, Pile5, Pile6, Pile7 }; ALWAYS_INLINE const WasteRecycleRules& recycle_rules() { static constexpr Array rules { { { 0, -100 }, { 2, -20 }, } }; switch (m_mode) { case Mode::SingleCardDraw: return rules[0]; case Mode::ThreeCardDraw: return rules[1]; default: VERIFY_NOT_REACHED(); } } void mark_intersecting_stacks_dirty(Card& intersecting_card); void update_score(int to_add); void move_card(CardStack& from, CardStack& to); void start_game_over_animation(); void stop_game_over_animation(); void create_new_animation_card(); void check_for_game_over(); void dump_layout() const; ALWAYS_INLINE CardStack& stack(StackLocation location) { return m_stacks[location]; } virtual void paint_event(GUI::PaintEvent&) override; virtual void mousedown_event(GUI::MouseEvent&) override; virtual void mouseup_event(GUI::MouseEvent&) override; virtual void mousemove_event(GUI::MouseEvent&) override; virtual void doubleclick_event(GUI::MouseEvent&) override; virtual void keydown_event(GUI::KeyEvent&) override; virtual void timer_event(Core::TimerEvent&) override; Mode m_mode { Mode::SingleCardDraw }; NonnullRefPtrVector m_focused_cards; NonnullRefPtrVector m_new_deck; CardStack m_stacks[StackLocation::__Count]; CardStack* m_focused_stack { nullptr }; Gfx::IntPoint m_mouse_down_location; bool m_mouse_down { false }; Animation m_animation; bool m_game_over_animation { false }; bool m_waiting_for_new_game { true }; bool m_new_game_animation { false }; uint8_t m_new_game_animation_pile { 0 }; uint8_t m_new_game_animation_delay { 0 }; uint32_t m_score { 0 }; uint8_t m_passes_left_before_punishment { 0 }; }; }