mirror of
https://github.com/RGBCube/serenity
synced 2025-07-28 11:07:46 +00:00
Solitaire: Move cards functionality into LibCards
This commit is contained in:
parent
9440a3c280
commit
3e47eec862
9 changed files with 32 additions and 23 deletions
|
@ -1,12 +1,10 @@
|
|||
compile_gml(Solitaire.gml SolitaireGML.h solitaire_gml)
|
||||
|
||||
set(SOURCES
|
||||
Card.cpp
|
||||
CardStack.cpp
|
||||
Game.cpp
|
||||
main.cpp
|
||||
SolitaireGML.h
|
||||
)
|
||||
|
||||
serenity_app(Solitaire ICON app-solitaire)
|
||||
target_link_libraries(Solitaire LibGUI LibGfx LibCore)
|
||||
target_link_libraries(Solitaire LibCards LibGUI LibGfx LibCore)
|
||||
|
|
|
@ -1,160 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Till Mayer <till.mayer@web.de>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "Card.h"
|
||||
#include <LibGUI/Widget.h>
|
||||
#include <LibGfx/Font.h>
|
||||
#include <LibGfx/FontDatabase.h>
|
||||
|
||||
namespace Solitaire {
|
||||
|
||||
static const NonnullRefPtr<Gfx::CharacterBitmap> s_diamond = Gfx::CharacterBitmap::create_from_ascii(
|
||||
" # "
|
||||
" ### "
|
||||
" ##### "
|
||||
" ####### "
|
||||
"#########"
|
||||
" ####### "
|
||||
" ##### "
|
||||
" ### "
|
||||
" # ",
|
||||
9, 9);
|
||||
|
||||
static const NonnullRefPtr<Gfx::CharacterBitmap> s_heart = Gfx::CharacterBitmap::create_from_ascii(
|
||||
" # # "
|
||||
" ### ### "
|
||||
"#########"
|
||||
"#########"
|
||||
"#########"
|
||||
" ####### "
|
||||
" ##### "
|
||||
" ### "
|
||||
" # ",
|
||||
9, 9);
|
||||
|
||||
static const NonnullRefPtr<Gfx::CharacterBitmap> s_spade = Gfx::CharacterBitmap::create_from_ascii(
|
||||
" # "
|
||||
" ### "
|
||||
" ##### "
|
||||
" ####### "
|
||||
"#########"
|
||||
"#########"
|
||||
" ## # ## "
|
||||
" ### "
|
||||
" ### ",
|
||||
9, 9);
|
||||
|
||||
static const NonnullRefPtr<Gfx::CharacterBitmap> s_club = Gfx::CharacterBitmap::create_from_ascii(
|
||||
" ### "
|
||||
" ##### "
|
||||
" ##### "
|
||||
" ## ### ## "
|
||||
"###########"
|
||||
"###########"
|
||||
"#### # ####"
|
||||
" ## ### ## "
|
||||
" ### ",
|
||||
11, 9);
|
||||
|
||||
static RefPtr<Gfx::Bitmap> s_background;
|
||||
|
||||
Card::Card(Type type, uint8_t value)
|
||||
: m_rect(Gfx::IntRect({}, { width, height }))
|
||||
, m_front(*Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, { width, height }))
|
||||
, m_type(type)
|
||||
, m_value(value)
|
||||
{
|
||||
VERIFY(value < card_count);
|
||||
Gfx::IntRect paint_rect({ 0, 0 }, { width, height });
|
||||
|
||||
if (s_background.is_null()) {
|
||||
s_background = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, { width, height });
|
||||
Gfx::Painter bg_painter(*s_background);
|
||||
|
||||
s_background->fill(Color::White);
|
||||
auto image = Gfx::Bitmap::load_from_file("/res/icons/solitaire/buggie-deck.png");
|
||||
VERIFY(!image.is_null());
|
||||
|
||||
float aspect_ratio = image->width() / static_cast<float>(image->height());
|
||||
auto target_size = Gfx::IntSize(static_cast<int>(aspect_ratio * (height - 5)), height - 5);
|
||||
|
||||
bg_painter.draw_scaled_bitmap(
|
||||
{ { (width - target_size.width()) / 2, (height - target_size.height()) / 2 }, target_size },
|
||||
*image, image->rect());
|
||||
bg_painter.draw_rect(paint_rect, Color::Black);
|
||||
}
|
||||
|
||||
Gfx::Painter painter(m_front);
|
||||
auto& font = Gfx::FontDatabase::default_font().bold_variant();
|
||||
|
||||
auto label = labels[value];
|
||||
m_front->fill(Color::White);
|
||||
painter.draw_rect(paint_rect, Color::Black);
|
||||
paint_rect.set_height(paint_rect.height() / 2);
|
||||
paint_rect.shrink(10, 6);
|
||||
|
||||
painter.draw_text(paint_rect, label, font, Gfx::TextAlignment::TopLeft, color());
|
||||
|
||||
NonnullRefPtr<Gfx::CharacterBitmap> symbol = s_diamond;
|
||||
switch (m_type) {
|
||||
case Diamonds:
|
||||
symbol = s_diamond;
|
||||
break;
|
||||
case Clubs:
|
||||
symbol = s_club;
|
||||
break;
|
||||
case Spades:
|
||||
symbol = s_spade;
|
||||
break;
|
||||
case Hearts:
|
||||
symbol = s_heart;
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
painter.draw_bitmap(
|
||||
{ paint_rect.x() + (font.width(label) - symbol->size().width()) / 2, font.glyph_height() + paint_rect.y() + 3 },
|
||||
symbol, color());
|
||||
|
||||
for (int y = height / 2; y < height; ++y) {
|
||||
for (int x = 0; x < width; ++x) {
|
||||
m_front->set_pixel(x, y, m_front->get_pixel(width - x - 1, height - y - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card::~Card()
|
||||
{
|
||||
}
|
||||
|
||||
void Card::draw(GUI::Painter& painter) const
|
||||
{
|
||||
VERIFY(!s_background.is_null());
|
||||
painter.blit(position(), m_upside_down ? *s_background : *m_front, m_front->rect());
|
||||
}
|
||||
|
||||
void Card::clear(GUI::Painter& painter, const Color& background_color) const
|
||||
{
|
||||
painter.fill_rect({ old_positon(), { width, height } }, background_color);
|
||||
}
|
||||
|
||||
void Card::save_old_position()
|
||||
{
|
||||
m_old_position = m_rect.location();
|
||||
m_old_position_valid = true;
|
||||
}
|
||||
|
||||
void Card::clear_and_draw(GUI::Painter& painter, const Color& background_color)
|
||||
{
|
||||
if (is_old_position_valid())
|
||||
clear(painter, background_color);
|
||||
|
||||
draw(painter);
|
||||
save_old_position();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Till Mayer <till.mayer@web.de>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Array.h>
|
||||
#include <AK/Format.h>
|
||||
#include <LibCore/Object.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/CharacterBitmap.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <ctype.h>
|
||||
|
||||
namespace Solitaire {
|
||||
|
||||
class Card final : public Core::Object {
|
||||
C_OBJECT(Card)
|
||||
public:
|
||||
static constexpr int width = 80;
|
||||
static constexpr int height = 100;
|
||||
static constexpr int card_count = 13;
|
||||
static constexpr Array<StringView, card_count> labels = {
|
||||
"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"
|
||||
};
|
||||
|
||||
enum Type {
|
||||
Clubs,
|
||||
Diamonds,
|
||||
Hearts,
|
||||
Spades,
|
||||
__Count
|
||||
};
|
||||
|
||||
virtual ~Card() override;
|
||||
|
||||
Gfx::IntRect& rect() { return m_rect; }
|
||||
Gfx::IntPoint position() const { return m_rect.location(); }
|
||||
const Gfx::IntPoint& old_positon() const { return m_old_position; }
|
||||
uint8_t value() const { return m_value; };
|
||||
Type type() const { return m_type; }
|
||||
|
||||
bool is_old_position_valid() const { return m_old_position_valid; }
|
||||
bool is_moving() const { return m_moving; }
|
||||
bool is_upside_down() const { return m_upside_down; }
|
||||
Gfx::Color color() const { return (m_type == Diamonds || m_type == Hearts) ? Color::Red : Color::Black; }
|
||||
|
||||
void set_position(const Gfx::IntPoint p) { m_rect.set_location(p); }
|
||||
void set_moving(bool moving) { m_moving = moving; }
|
||||
void set_upside_down(bool upside_down) { m_upside_down = upside_down; }
|
||||
|
||||
void save_old_position();
|
||||
|
||||
void draw(GUI::Painter&) const;
|
||||
void clear(GUI::Painter&, const Color& background_color) const;
|
||||
void clear_and_draw(GUI::Painter&, const Color& background_color);
|
||||
|
||||
private:
|
||||
Card(Type type, uint8_t value);
|
||||
|
||||
Gfx::IntRect m_rect;
|
||||
NonnullRefPtr<Gfx::Bitmap> m_front;
|
||||
Gfx::IntPoint m_old_position;
|
||||
Type m_type;
|
||||
uint8_t m_value;
|
||||
bool m_old_position_valid { false };
|
||||
bool m_moving { false };
|
||||
bool m_upside_down { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
struct AK::Formatter<Solitaire::Card> : Formatter<FormatString> {
|
||||
void format(FormatBuilder& builder, const Solitaire::Card& card)
|
||||
{
|
||||
StringView type;
|
||||
|
||||
switch (card.type()) {
|
||||
case Solitaire::Card::Type::Clubs:
|
||||
type = "C"sv;
|
||||
break;
|
||||
case Solitaire::Card::Type::Diamonds:
|
||||
type = "D"sv;
|
||||
break;
|
||||
case Solitaire::Card::Type::Hearts:
|
||||
type = "H"sv;
|
||||
break;
|
||||
case Solitaire::Card::Type::Spades:
|
||||
type = "S"sv;
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
Formatter<FormatString>::format(builder, "{:>2}{}", Solitaire::Card::labels[card.value()], type);
|
||||
}
|
||||
};
|
|
@ -1,236 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Till Mayer <till.mayer@web.de>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "CardStack.h"
|
||||
|
||||
namespace Solitaire {
|
||||
|
||||
CardStack::CardStack()
|
||||
: m_position({ 0, 0 })
|
||||
, m_type(Invalid)
|
||||
, m_base(m_position, { Card::width, Card::height })
|
||||
{
|
||||
}
|
||||
|
||||
CardStack::CardStack(const Gfx::IntPoint& position, Type type)
|
||||
: m_position(position)
|
||||
, m_type(type)
|
||||
, m_rules(rules_for_type(type))
|
||||
, m_base(m_position, { Card::width, Card::height })
|
||||
{
|
||||
VERIFY(type != Invalid);
|
||||
calculate_bounding_box();
|
||||
}
|
||||
|
||||
void CardStack::clear()
|
||||
{
|
||||
m_stack.clear();
|
||||
m_stack_positions.clear();
|
||||
}
|
||||
|
||||
void CardStack::draw(GUI::Painter& painter, const Gfx::Color& background_color)
|
||||
{
|
||||
switch (m_type) {
|
||||
case Stock:
|
||||
if (is_empty()) {
|
||||
painter.fill_rect(m_base.shrunken(Card::width / 4, Card::height / 4), background_color.lightened(1.5));
|
||||
painter.fill_rect(m_base.shrunken(Card::width / 2, Card::height / 2), background_color);
|
||||
painter.draw_rect(m_base, background_color.darkened(0.5));
|
||||
}
|
||||
break;
|
||||
case Foundation:
|
||||
if (is_empty() || (m_stack.size() == 1 && peek().is_moving())) {
|
||||
painter.draw_rect(m_base, background_color.darkened(0.5));
|
||||
for (int y = 0; y < (m_base.height() - 4) / 8; ++y) {
|
||||
for (int x = 0; x < (m_base.width() - 4) / 5; ++x) {
|
||||
painter.draw_rect({ 4 + m_base.x() + x * 5, 4 + m_base.y() + y * 8, 1, 1 }, background_color.darkened(0.5));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Waste:
|
||||
break;
|
||||
case Play:
|
||||
if (is_empty() || (m_stack.size() == 1 && peek().is_moving()))
|
||||
painter.draw_rect(m_base, background_color.darkened(0.5));
|
||||
break;
|
||||
case Normal:
|
||||
painter.draw_rect(m_base, background_color.darkened(0.5));
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
if (is_empty())
|
||||
return;
|
||||
|
||||
if (m_rules.shift_x == 0 && m_rules.shift_y == 0) {
|
||||
auto& card = peek();
|
||||
card.draw(painter);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& card : m_stack) {
|
||||
if (!card.is_moving())
|
||||
card.clear_and_draw(painter, background_color);
|
||||
}
|
||||
}
|
||||
|
||||
void CardStack::rebound_cards()
|
||||
{
|
||||
VERIFY(m_stack_positions.size() == m_stack.size());
|
||||
|
||||
size_t card_index = 0;
|
||||
for (auto& card : m_stack)
|
||||
card.set_position(m_stack_positions.at(card_index++));
|
||||
}
|
||||
|
||||
void CardStack::add_all_grabbed_cards(const Gfx::IntPoint& click_location, NonnullRefPtrVector<Card>& grabbed)
|
||||
{
|
||||
VERIFY(grabbed.is_empty());
|
||||
|
||||
if (m_type != Normal) {
|
||||
auto& top_card = peek();
|
||||
if (top_card.rect().contains(click_location)) {
|
||||
top_card.set_moving(true);
|
||||
grabbed.append(top_card);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<Card> last_intersect;
|
||||
|
||||
for (auto& card : m_stack) {
|
||||
if (card.rect().contains(click_location)) {
|
||||
if (card.is_upside_down())
|
||||
continue;
|
||||
|
||||
last_intersect = card;
|
||||
} else if (!last_intersect.is_null()) {
|
||||
if (grabbed.is_empty()) {
|
||||
grabbed.append(*last_intersect);
|
||||
last_intersect->set_moving(true);
|
||||
}
|
||||
|
||||
if (card.is_upside_down()) {
|
||||
grabbed.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
card.set_moving(true);
|
||||
grabbed.append(card);
|
||||
}
|
||||
}
|
||||
|
||||
if (grabbed.is_empty() && !last_intersect.is_null()) {
|
||||
grabbed.append(*last_intersect);
|
||||
last_intersect->set_moving(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool CardStack::is_allowed_to_push(const Card& card, size_t stack_size) const
|
||||
{
|
||||
if (m_type == Stock || m_type == Waste || m_type == Play)
|
||||
return false;
|
||||
|
||||
if (m_type == Normal && is_empty())
|
||||
return card.value() == 12;
|
||||
|
||||
if (m_type == Foundation && is_empty())
|
||||
return card.value() == 0;
|
||||
|
||||
if (!is_empty()) {
|
||||
auto& top_card = peek();
|
||||
if (top_card.is_upside_down())
|
||||
return false;
|
||||
|
||||
if (m_type == Foundation) {
|
||||
// Prevent player from dragging an entire stack of cards to the foundation stack
|
||||
if (stack_size > 1)
|
||||
return false;
|
||||
return top_card.type() == card.type() && m_stack.size() == card.value();
|
||||
} else if (m_type == Normal) {
|
||||
return top_card.color() != card.color() && top_card.value() == card.value() + 1;
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CardStack::push(NonnullRefPtr<Card> card)
|
||||
{
|
||||
auto size = m_stack.size();
|
||||
auto top_most_position = m_stack_positions.is_empty() ? m_position : m_stack_positions.last();
|
||||
|
||||
if (size && size % m_rules.step == 0) {
|
||||
if (peek().is_upside_down())
|
||||
top_most_position.translate_by(m_rules.shift_x, m_rules.shift_y_upside_down);
|
||||
else
|
||||
top_most_position.translate_by(m_rules.shift_x, m_rules.shift_y);
|
||||
}
|
||||
|
||||
if (m_type == Stock)
|
||||
card->set_upside_down(true);
|
||||
|
||||
card->set_position(top_most_position);
|
||||
|
||||
m_stack.append(card);
|
||||
m_stack_positions.append(top_most_position);
|
||||
calculate_bounding_box();
|
||||
}
|
||||
|
||||
NonnullRefPtr<Card> CardStack::pop()
|
||||
{
|
||||
auto card = m_stack.take_last();
|
||||
|
||||
calculate_bounding_box();
|
||||
if (m_type == Stock)
|
||||
card->set_upside_down(false);
|
||||
|
||||
m_stack_positions.take_last();
|
||||
return card;
|
||||
}
|
||||
|
||||
void CardStack::move_to_stack(CardStack& stack)
|
||||
{
|
||||
while (!m_stack.is_empty()) {
|
||||
auto card = m_stack.take_first();
|
||||
m_stack_positions.take_first();
|
||||
stack.push(move(card));
|
||||
}
|
||||
|
||||
calculate_bounding_box();
|
||||
}
|
||||
|
||||
void CardStack::calculate_bounding_box()
|
||||
{
|
||||
m_bounding_box = Gfx::IntRect(m_position, { Card::width, Card::height });
|
||||
|
||||
if (m_stack.is_empty())
|
||||
return;
|
||||
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
size_t card_position = 0;
|
||||
for (auto& card : m_stack) {
|
||||
if (card_position % m_rules.step == 0 && card_position) {
|
||||
if (card.is_upside_down()) {
|
||||
width += m_rules.shift_x;
|
||||
height += m_rules.shift_y_upside_down;
|
||||
} else {
|
||||
width += m_rules.shift_x;
|
||||
height += m_rules.shift_y;
|
||||
}
|
||||
}
|
||||
++card_position;
|
||||
}
|
||||
|
||||
m_bounding_box.set_size(Card::width + width, Card::height + height);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Till Mayer <till.mayer@web.de>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Card.h"
|
||||
#include <AK/Format.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
namespace Solitaire {
|
||||
|
||||
class CardStack final {
|
||||
public:
|
||||
enum Type {
|
||||
Invalid,
|
||||
Stock,
|
||||
Normal,
|
||||
Waste,
|
||||
Play,
|
||||
Foundation
|
||||
};
|
||||
|
||||
CardStack();
|
||||
CardStack(const Gfx::IntPoint& position, Type type);
|
||||
|
||||
bool is_empty() const { return m_stack.is_empty(); }
|
||||
bool is_focused() const { return m_focused; }
|
||||
Type type() const { return m_type; }
|
||||
const NonnullRefPtrVector<Card>& stack() const { return m_stack; }
|
||||
size_t count() const { return m_stack.size(); }
|
||||
const Card& peek() const { return m_stack.last(); }
|
||||
Card& peek() { return m_stack.last(); }
|
||||
const Gfx::IntRect& bounding_box() const { return m_bounding_box; }
|
||||
|
||||
void set_focused(bool focused) { m_focused = focused; }
|
||||
|
||||
void push(NonnullRefPtr<Card> card);
|
||||
NonnullRefPtr<Card> pop();
|
||||
void move_to_stack(CardStack&);
|
||||
void rebound_cards();
|
||||
|
||||
bool is_allowed_to_push(const Card&, size_t stack_size = 1) const;
|
||||
void add_all_grabbed_cards(const Gfx::IntPoint& click_location, NonnullRefPtrVector<Card>& grabbed);
|
||||
void draw(GUI::Painter&, const Gfx::Color& background_color);
|
||||
void clear();
|
||||
|
||||
private:
|
||||
struct StackRules {
|
||||
uint8_t shift_x { 0 };
|
||||
uint8_t shift_y { 0 };
|
||||
uint8_t step { 1 };
|
||||
uint8_t shift_y_upside_down { 0 };
|
||||
};
|
||||
|
||||
constexpr StackRules rules_for_type(Type stack_type)
|
||||
{
|
||||
switch (stack_type) {
|
||||
case Foundation:
|
||||
return { 2, 1, 4, 1 };
|
||||
case Normal:
|
||||
return { 0, 20, 1, 3 };
|
||||
case Stock:
|
||||
return { 2, 1, 8, 1 };
|
||||
case Waste:
|
||||
return { 0, 0, 1, 0 };
|
||||
case Play:
|
||||
return { 20, 0, 1, 0 };
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void calculate_bounding_box();
|
||||
|
||||
NonnullRefPtrVector<Card> m_stack;
|
||||
Vector<Gfx::IntPoint> m_stack_positions;
|
||||
Gfx::IntPoint m_position;
|
||||
Gfx::IntRect m_bounding_box;
|
||||
Type m_type { Invalid };
|
||||
StackRules m_rules;
|
||||
bool m_focused { false };
|
||||
Gfx::IntRect m_base;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
struct AK::Formatter<Solitaire::CardStack> : Formatter<FormatString> {
|
||||
void format(FormatBuilder& builder, const Solitaire::CardStack& stack)
|
||||
{
|
||||
StringView type;
|
||||
|
||||
switch (stack.type()) {
|
||||
case Solitaire::CardStack::Type::Stock:
|
||||
type = "Stock"sv;
|
||||
break;
|
||||
case Solitaire::CardStack::Type::Normal:
|
||||
type = "Normal"sv;
|
||||
break;
|
||||
case Solitaire::CardStack::Type::Foundation:
|
||||
type = "Foundation"sv;
|
||||
break;
|
||||
case Solitaire::CardStack::Type::Waste:
|
||||
type = "Waste"sv;
|
||||
break;
|
||||
case Solitaire::CardStack::Type::Play:
|
||||
type = "Play"sv;
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
StringBuilder cards;
|
||||
bool first_card = true;
|
||||
|
||||
for (const auto& card : stack.stack()) {
|
||||
cards.appendff("{}{}", (first_card ? "" : " "), card);
|
||||
first_card = false;
|
||||
}
|
||||
|
||||
Formatter<FormatString>::format(builder, "{:<10} {:>16}: {}", type, stack.bounding_box(), cards.build());
|
||||
}
|
||||
};
|
|
@ -6,10 +6,13 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "CardStack.h"
|
||||
#include <LibCards/CardStack.h>
|
||||
#include <LibGUI/Frame.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
|
||||
using Cards::Card;
|
||||
using Cards::CardStack;
|
||||
|
||||
namespace Solitaire {
|
||||
|
||||
enum class Mode : u8 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue