Serenity Operating System
1/*
2 * Copyright (c) 2020, Till Mayer <till.mayer@web.de>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#pragma once
8
9#include "Card.h"
10#include <AK/Format.h>
11#include <AK/RefCounted.h>
12#include <AK/Vector.h>
13
14namespace Cards {
15
16class CardStack final : public RefCounted<CardStack> {
17public:
18 enum class Type {
19 Invalid,
20 Stock,
21 Normal,
22 Waste,
23 Play,
24 Foundation
25 };
26
27 enum class MovementRule {
28 Alternating,
29 Same,
30 Any,
31 };
32
33 CardStack();
34 CardStack(Gfx::IntPoint position, Type type, RefPtr<CardStack> covered_stack = nullptr);
35
36 bool is_empty() const { return m_stack.is_empty(); }
37 Type type() const { return m_type; }
38 Vector<NonnullRefPtr<Card>> const& stack() const { return m_stack; }
39 size_t count() const { return m_stack.size(); }
40 Card const& peek() const { return m_stack.last(); }
41 Card& peek() { return m_stack.last(); }
42 Gfx::IntRect const& bounding_box() const { return m_bounding_box; }
43
44 bool make_top_card_visible(); // Returns true if the card was flipped.
45
46 ErrorOr<void> push(NonnullRefPtr<Card>);
47 NonnullRefPtr<Card> pop();
48 ErrorOr<void> take_all(CardStack&);
49 void rebound_cards();
50
51 bool is_allowed_to_push(Card const&, size_t stack_size = 1, MovementRule movement_rule = MovementRule::Alternating) const;
52 ErrorOr<void> add_all_grabbed_cards(Gfx::IntPoint click_location, Vector<NonnullRefPtr<Card>>& grabbed, MovementRule movement_rule = MovementRule::Alternating);
53
54 bool preview_card(Gfx::IntPoint click_location);
55 void clear_card_preview();
56
57 void paint(GUI::Painter&, Gfx::Color background_color);
58 void clear();
59
60 void set_highlighted(bool highlighted) { m_highlighted = highlighted; }
61
62private:
63 struct StackRules {
64 uint8_t shift_x { 0 };
65 uint8_t shift_y { 0 };
66 uint8_t step { 1 };
67 uint8_t shift_y_upside_down { 0 };
68 };
69
70 static constexpr StackRules rules_for_type(Type stack_type)
71 {
72 switch (stack_type) {
73 case Type::Foundation:
74 return { 2, 1, 4, 1 };
75 case Type::Normal:
76 return { 0, 20, 1, 3 };
77 case Type::Stock:
78 return { 2, 1, 8, 1 };
79 case Type::Waste:
80 return { 0, 0, 1, 0 };
81 case Type::Play:
82 return { 20, 0, 1, 0 };
83 default:
84 return {};
85 }
86 }
87
88 void calculate_bounding_box();
89
90 // An optional stack that this stack is painted on top of.
91 // eg, in Solitaire the Play stack is positioned over the Waste stack.
92 RefPtr<CardStack> m_covered_stack;
93
94 Vector<NonnullRefPtr<Card>> m_stack;
95 Vector<Gfx::IntPoint> m_stack_positions;
96 Gfx::IntPoint m_position;
97 Gfx::IntRect m_bounding_box;
98 Type m_type { Type::Invalid };
99 StackRules m_rules;
100 Gfx::IntRect m_base;
101 bool m_highlighted { false };
102};
103
104}
105
106template<>
107struct AK::Formatter<Cards::CardStack> : Formatter<FormatString> {
108 ErrorOr<void> format(FormatBuilder& builder, Cards::CardStack const& stack)
109 {
110 StringView type;
111
112 switch (stack.type()) {
113 case Cards::CardStack::Type::Stock:
114 type = "Stock"sv;
115 break;
116 case Cards::CardStack::Type::Normal:
117 type = "Normal"sv;
118 break;
119 case Cards::CardStack::Type::Foundation:
120 type = "Foundation"sv;
121 break;
122 case Cards::CardStack::Type::Waste:
123 type = "Waste"sv;
124 break;
125 case Cards::CardStack::Type::Play:
126 type = "Play"sv;
127 break;
128 default:
129 VERIFY_NOT_REACHED();
130 }
131
132 StringBuilder cards;
133 bool first_card = true;
134
135 for (auto const& card : stack.stack()) {
136 cards.appendff("{}{}", (first_card ? "" : " "), card);
137 first_card = false;
138 }
139
140 return Formatter<FormatString>::format(builder, "{:<10} {:>16}: {}"sv, type, stack.bounding_box(), cards.to_deprecated_string());
141 }
142};