Add basic GameState class
This commit is contained in:
parent
6ad3fe5bba
commit
0b65ce5d56
|
@ -23,6 +23,7 @@ qt_add_qml_module(appSolitare
|
|||
QML_FILES ScoreBar.qml
|
||||
QML_FILES CardModel.qml
|
||||
SOURCES playingcard.h playingcard.cpp
|
||||
SOURCES gamestate.h gamestate.cpp
|
||||
)
|
||||
|
||||
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
|
||||
|
|
2
Main.qml
2
Main.qml
|
@ -20,7 +20,7 @@ ApplicationWindow {
|
|||
|
||||
CardModel {
|
||||
anchors.centerIn: parent
|
||||
card: myCard
|
||||
card: gameState.drawPile[0];
|
||||
isFaceDown: false
|
||||
}
|
||||
}
|
||||
|
|
204
gamestate.cpp
Normal file
204
gamestate.cpp
Normal file
|
@ -0,0 +1,204 @@
|
|||
#include "gamestate.h"
|
||||
#include <random>
|
||||
|
||||
GameState::GameState(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
// Initialize the foundation piles (4 suits)
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
m_foundation.append(QList<PlayingCard*>());
|
||||
}
|
||||
|
||||
// Initialize the columns (7 piles)
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
m_columns.append(QList<PlayingCard*>());
|
||||
}
|
||||
}
|
||||
|
||||
void GameState::dealCards()
|
||||
{
|
||||
QList<PlayingCard*> deck = PlayingCard::createDeck();
|
||||
|
||||
// Randomly shuffle the deck
|
||||
std::random_device rd;
|
||||
std::default_random_engine rng(rd());
|
||||
std::shuffle(deck.begin(), deck.end(), rng);
|
||||
|
||||
// Deal the cards into the columns
|
||||
int index = 0;
|
||||
for (int i = 0; i < 7; i++) {
|
||||
// Deal exactly i+1 cards to the i-th column
|
||||
m_columns[i] = deck.mid(index, i + 1);
|
||||
index += i + 1;
|
||||
}
|
||||
|
||||
// Use the remaining cards as the draw pile
|
||||
m_drawPile = deck.mid(index);
|
||||
|
||||
// Reset the foundation & throwaway pile
|
||||
m_foundation.clear();
|
||||
m_throwawayPile.clear();
|
||||
|
||||
emit onDrawPileChanged();
|
||||
emit onThrowawayPileChanged();
|
||||
emit onColumnsChanged();
|
||||
emit onFoundationChanged();
|
||||
}
|
||||
|
||||
void GameState::drawNextCard()
|
||||
{
|
||||
// If drawPile is empty, flip the throwawayPile to drawPile
|
||||
if (m_drawPile.isEmpty()) {
|
||||
m_drawPile = m_throwawayPile;
|
||||
m_throwawayPile.clear();
|
||||
std::reverse(m_drawPile.begin(), m_drawPile.end());
|
||||
}
|
||||
|
||||
// Draw the top card from drawPile, dropping it into throwawayPile
|
||||
m_throwawayPile.append(m_drawPile.takeFirst());
|
||||
|
||||
emit onDrawPileChanged();
|
||||
emit onThrowawayPileChanged();
|
||||
}
|
||||
|
||||
bool GameState::moveCardToColumn(int columnId)
|
||||
{
|
||||
assert(columnId >= 0 && columnId < 7);
|
||||
|
||||
if (m_throwawayPile.isEmpty()) {
|
||||
// Consider raising an exception here instead
|
||||
return false;
|
||||
}
|
||||
|
||||
// We'll be moving the last card in the throwaway pile (maybe)
|
||||
PlayingCard *cardToMove = m_throwawayPile.last();
|
||||
|
||||
if (m_columns[columnId].isEmpty()) {
|
||||
// If the column is empty, we can only place a king
|
||||
if (cardToMove->value() != PlayingCard::Value::King)
|
||||
return false;
|
||||
|
||||
m_columns[columnId].append(cardToMove);
|
||||
m_throwawayPile.removeLast();
|
||||
|
||||
emit onThrowawayPileChanged();
|
||||
emit onColumnsChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
// We'll be comparing this card against the last card in the column
|
||||
PlayingCard* columnCard = m_columns[columnId].last();
|
||||
|
||||
// This card's value must be one less than the card in the column
|
||||
if (cardToMove->value() != columnCard->value() - 1)
|
||||
return false;
|
||||
|
||||
// This card must be of opposite color
|
||||
if (!PlayingCard::areOppositeColors(*cardToMove, *columnCard))
|
||||
return false;
|
||||
|
||||
m_columns[columnId].append(cardToMove);
|
||||
m_throwawayPile.removeLast();
|
||||
|
||||
emit onThrowawayPileChanged();
|
||||
emit onColumnsChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GameState::moveThrownCardToFoundation(PlayingCard::Suit foundationId)
|
||||
{
|
||||
if (m_throwawayPile.isEmpty()) {
|
||||
// Consider raising an exception here instead
|
||||
return false;
|
||||
}
|
||||
|
||||
// We'll be moving the last card in the foundation pile (maybe)
|
||||
PlayingCard *cardToMove = m_throwawayPile.last();
|
||||
|
||||
// Try moving the card into the foundation
|
||||
if (!tryMoveCardToFoundation(foundationId, cardToMove))
|
||||
return false;
|
||||
|
||||
// We succeeded, the card is now in the appropriate foundation pile,
|
||||
// let's remove the card from the throwaway pile.
|
||||
m_throwawayPile.removeLast();
|
||||
|
||||
emit onThrowawayPileChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GameState::moveColumnCardToFoundation(int columnId, PlayingCard::Suit foundationId)
|
||||
{
|
||||
assert(columnId >= 0 && columnId < 7);
|
||||
|
||||
if (m_columns[columnId].isEmpty()) {
|
||||
// Consider raising an exception here instead
|
||||
return false;
|
||||
}
|
||||
|
||||
// We'll be moving the top card in the column (maybe)
|
||||
PlayingCard *cardToMove = m_columns[columnId].first();
|
||||
|
||||
// Try moving the card into the foundation
|
||||
if (!tryMoveCardToFoundation(foundationId, cardToMove))
|
||||
return false;
|
||||
|
||||
// We succeeded, the card is now in the appropriate foundation pile,
|
||||
// let's remove the card from the column.
|
||||
m_columns[columnId].removeFirst();
|
||||
|
||||
emit onColumnsChanged();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool GameState::tryMoveCardToFoundation(PlayingCard::Suit foundationId, PlayingCard* cardToMove)
|
||||
{
|
||||
assert(foundationId >= PlayingCard::Suit::Clubs && foundationId < PlayingCard::Suit::Spades);
|
||||
|
||||
if (cardToMove->suit() != foundationId)
|
||||
return false;
|
||||
|
||||
PlayingCard::Value requiredValue;
|
||||
if (m_foundation[foundationId].isEmpty()) {
|
||||
// If the pile is empty, only an ace can go in
|
||||
requiredValue = PlayingCard::Value::Ace;
|
||||
} else {
|
||||
// Otherwise it's the next card by value, unless we're already at king
|
||||
PlayingCard::Value curValue = m_foundation[foundationId].first()->value();
|
||||
if (curValue == PlayingCard::Value::King)
|
||||
return false;
|
||||
|
||||
// Clever trick to get the next value. Note that this relies on the enum having
|
||||
// the variants defined in correct order.
|
||||
requiredValue = static_cast<PlayingCard::Value>(static_cast<int>(curValue) + 1);
|
||||
}
|
||||
|
||||
if (cardToMove->value() != requiredValue)
|
||||
return false;
|
||||
|
||||
m_foundation[foundationId].push_front(cardToMove);
|
||||
|
||||
emit onFoundationChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
QList<PlayingCard *> GameState::drawPile() const
|
||||
{
|
||||
return m_drawPile;
|
||||
}
|
||||
|
||||
QList<PlayingCard *> GameState::throwawayPile() const
|
||||
{
|
||||
return m_throwawayPile;
|
||||
}
|
||||
|
||||
QList<QList<PlayingCard *> > GameState::columns() const
|
||||
{
|
||||
return m_columns;
|
||||
}
|
||||
|
||||
QList<QList<PlayingCard *> > GameState::foundation() const
|
||||
{
|
||||
return m_foundation;
|
||||
}
|
44
gamestate.h
Normal file
44
gamestate.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
#ifndef GAMESTATE_H
|
||||
#define GAMESTATE_H
|
||||
|
||||
#include <QObject>
|
||||
#include "playingcard.h"
|
||||
|
||||
class GameState : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QList<PlayingCard*> drawPile READ drawPile NOTIFY onDrawPileChanged)
|
||||
Q_PROPERTY(QList<PlayingCard*> throwawayPile READ throwawayPile NOTIFY onThrowawayPileChanged)
|
||||
Q_PROPERTY(QList<QList<PlayingCard*>> columns READ columns NOTIFY onColumnsChanged)
|
||||
Q_PROPERTY(QList<QList<PlayingCard*>> foundation READ foundation NOTIFY onFoundationChanged)
|
||||
|
||||
public:
|
||||
explicit GameState(QObject *parent = nullptr);
|
||||
|
||||
QList<PlayingCard*> drawPile() const;
|
||||
QList<PlayingCard*> throwawayPile() const;
|
||||
QList<QList<PlayingCard*>> columns() const;
|
||||
QList<QList<PlayingCard*>> foundation() const;
|
||||
|
||||
void dealCards();
|
||||
void drawNextCard();
|
||||
bool moveCardToColumn(int columnId);
|
||||
bool moveThrownCardToFoundation(PlayingCard::Suit foundationId);
|
||||
bool moveColumnCardToFoundation(int columnId, PlayingCard::Suit foundationId);
|
||||
|
||||
signals:
|
||||
void onDrawPileChanged();
|
||||
void onThrowawayPileChanged();
|
||||
void onColumnsChanged();
|
||||
void onFoundationChanged();
|
||||
|
||||
private:
|
||||
QList<PlayingCard*> m_drawPile;
|
||||
QList<PlayingCard*> m_throwawayPile;
|
||||
QList<QList<PlayingCard*>> m_columns;
|
||||
QList<QList<PlayingCard*>> m_foundation;
|
||||
|
||||
bool tryMoveCardToFoundation(PlayingCard::Suit foundationId, PlayingCard* cardToMove);
|
||||
};
|
||||
|
||||
#endif // GAMESTATE_H
|
7
main.cpp
7
main.cpp
|
@ -1,7 +1,7 @@
|
|||
#include <QGuiApplication>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
#include "playingcard.h"
|
||||
#include "gamestate.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
@ -17,8 +17,9 @@ int main(int argc, char *argv[])
|
|||
|
||||
qmlRegisterUncreatableType<PlayingCard>("Solitare", 1, 0, "PlayingCard", "PlayingCard cannot be directly created in QML. Use C++ logic to instantiate.");
|
||||
|
||||
PlayingCard myCard(PlayingCard::Suit::Hearts, PlayingCard::Value::Seven);
|
||||
engine.rootContext()->setContextProperty("myCard", &myCard);
|
||||
GameState gameState;
|
||||
gameState.dealCards();
|
||||
engine.rootContext()->setContextProperty("gameState", &gameState);
|
||||
|
||||
engine.loadFromModule("Solitare", "Main");
|
||||
|
||||
|
|
|
@ -78,3 +78,11 @@ QList<PlayingCard *> PlayingCard::createDeck()
|
|||
|
||||
return deck;
|
||||
}
|
||||
|
||||
bool PlayingCard::areOppositeColors(const PlayingCard &card1, const PlayingCard &card2)
|
||||
{
|
||||
bool card1IsRed = (card1.suit() == Suit::Hearts || card1.suit() == Suit::Diamonds);
|
||||
bool card2IsRed = (card2.suit() == Suit::Hearts || card2.suit() == Suit::Diamonds);
|
||||
|
||||
return card1IsRed != card2IsRed;
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ public:
|
|||
void setValue(const Value &value);
|
||||
|
||||
static QList<PlayingCard*> createDeck();
|
||||
static bool areOppositeColors(const PlayingCard &card1, const PlayingCard &card2);
|
||||
|
||||
signals:
|
||||
void onSuitChanged();
|
||||
|
|
Loading…
Reference in a new issue