Compare commits

...

3 commits

Author SHA1 Message Date
ItsDrike ef16bf9188
Keep track of the game score 2024-12-12 20:57:48 +01:00
ItsDrike a52681c0b3
Make automove return what changed
This allows us to optimize the emitted signals, as we will know what has
changed after an automove.
2024-12-12 20:45:39 +01:00
ItsDrike 1ecfd36598
Restart the timer on game restar 2024-12-12 20:02:12 +01:00
5 changed files with 83 additions and 25 deletions

View file

@ -53,7 +53,7 @@ ApplicationWindow {
Timer { Timer {
id: gameTimer id: gameTimer
interval: 1000 interval: 1000
running: true running: GameState.gameWon === false
repeat: true repeat: true
onTriggered: scoreBar.time += 1 onTriggered: scoreBar.time += 1
} }
@ -62,12 +62,15 @@ ApplicationWindow {
id: scoreBar id: scoreBar
moves: GameState.moveAmount moves: GameState.moveAmount
score: GameState.score
time: 0 time: 0
height: Math.max(parent.height * 0.08, 50) height: Math.max(parent.height * 0.08, 50)
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
onRestart: time = 0
} }
// Show the foundation piles, throwaway pile & the draw stack on the first row // Show the foundation piles, throwaway pile & the draw stack on the first row
@ -123,5 +126,9 @@ ApplicationWindow {
cardHeight: app.cardHeight cardHeight: app.cardHeight
} }
WinOverlay {} WinOverlay {
onRestart: {
scoreBar.time = 0
}
}
} }

View file

@ -10,10 +10,15 @@ Rectangle {
property int time: 0 property int time: 0
property int moves: 0 property int moves: 0
signal restart
color: "lightgray" color: "lightgray"
RoundButton { RoundButton {
onClicked: GameState.dealCards() onClicked: {
GameState.dealCards()
scoreBarRoot.restart()
}
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: parent.width * 0.02 anchors.leftMargin: parent.width * 0.02

View file

@ -10,6 +10,8 @@ Rectangle {
visible: GameState.gameWon === true visible: GameState.gameWon === true
anchors.fill: parent anchors.fill: parent
signal restart
Text { Text {
id: winText id: winText
@ -23,7 +25,10 @@ Rectangle {
Button { Button {
text: "Restart" text: "Restart"
onClicked: GameState.dealCards() onClicked: {
GameState.dealCards();
winOverlay.restart();
}
anchors.top: winText.bottom anchors.top: winText.bottom
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }

View file

@ -59,6 +59,7 @@ void GameState::dealCards() {
emit columnsChanged(); emit columnsChanged();
emit foundationChanged(); emit foundationChanged();
emit moveAmountChanged(); emit moveAmountChanged();
emit scoreChanged();
} }
void GameState::setupWinningDeck() { void GameState::setupWinningDeck() {
@ -102,6 +103,7 @@ void GameState::setupWinningDeck() {
emit columnsChanged(); emit columnsChanged();
emit foundationChanged(); emit foundationChanged();
emit moveAmountChanged(); emit moveAmountChanged();
emit scoreChanged();
} }
bool GameState::drawNextCard() { bool GameState::drawNextCard() {
@ -153,6 +155,7 @@ bool GameState::moveThrownCardToColumn(int columnId) {
qDebug() << "> Moving complete"; qDebug() << "> Moving complete";
incrementMoveAmt(); incrementMoveAmt();
increaseScore(5); // Score increase for moving to column
emit throwawayPileChanged(); emit throwawayPileChanged();
emit columnsChanged(); emit columnsChanged();
return true; return true;
@ -183,6 +186,7 @@ bool GameState::moveThrownCardToFoundation(int foundationId) {
qDebug() << "> Moving complete"; qDebug() << "> Moving complete";
incrementMoveAmt(); incrementMoveAmt();
increaseScore(10); // Score increase for moving to foundation
emit throwawayPileChanged(); emit throwawayPileChanged();
emit foundationChanged(); emit foundationChanged();
return true; return true;
@ -263,6 +267,7 @@ bool GameState::moveColumnCardToFoundation(int columnId, int foundationId) {
ensureColumnRevealed(columnId); ensureColumnRevealed(columnId);
qDebug() << "> Moving complete"; qDebug() << "> Moving complete";
increaseScore(10); // Score increase for moving to foundation
incrementMoveAmt(); incrementMoveAmt();
emit columnsChanged(); emit columnsChanged();
emit foundationChanged(); emit foundationChanged();
@ -280,7 +285,8 @@ bool GameState::autoMoveThrownCard() {
qDebug() << "Attempting auto-move of thrown card " << cardToMove->toString(); qDebug() << "Attempting auto-move of thrown card " << cardToMove->toString();
// Try moving the card into the foundation // Try moving the card into the foundation
if (!tryAutoMoveSingleCard(*cardToMove)) { auto changed = tryAutoMoveSingleCard(*cardToMove);
if (!changed.has_value()) {
qDebug() << "> Moving failed, no available move found"; qDebug() << "> Moving failed, no available move found";
return false; return false;
} }
@ -291,11 +297,17 @@ bool GameState::autoMoveThrownCard() {
emit throwawayPileChanged(); emit throwawayPileChanged();
// We don't know which pile the card was moved to, to be safe, emit switch (changed.value().destinationType) {
// a change from both case AutoMoveResult::DestinationType::Foundation:
// NOTE: consider returning what changed from tryAutoMoveSingleCard increaseScore(10); // Score increase for moving to foundation
emit columnsChanged();
emit foundationChanged(); emit foundationChanged();
break;
case AutoMoveResult::DestinationType::Column:
increaseScore(5); // Score increase for moving to column
emit columnsChanged();
break;
default: assert(false); break;
}
incrementMoveAmt(); incrementMoveAmt();
return true; return true;
@ -320,7 +332,8 @@ bool GameState::autoMoveColumnCard(int columnId, int cardIndex) {
// This is a single card move (last card) // This is a single card move (last card)
PlayingCard* cardToMove = col->card(); PlayingCard* cardToMove = col->card();
qDebug() << "Attempting auto-move of column " << columnId << " card " << cardToMove->toString(); qDebug() << "Attempting auto-move of column " << columnId << " card " << cardToMove->toString();
if (!tryAutoMoveSingleCard(*cardToMove, columnId)) { auto changed = tryAutoMoveSingleCard(*cardToMove, columnId);
if (!changed.has_value()) {
qDebug() << "> Moving failed, no available move found"; qDebug() << "> Moving failed, no available move found";
return false; return false;
} }
@ -331,12 +344,14 @@ bool GameState::autoMoveColumnCard(int columnId, int cardIndex) {
ensureColumnRevealed(columnId); ensureColumnRevealed(columnId);
qDebug() << "> Moving complete"; qDebug() << "> Moving complete";
// Columns always change, we're moving from them, if the move is to another column
// that's all, if it's to a foundation, also emit foundation change
emit columnsChanged(); emit columnsChanged();
if (changed.value().destinationType == AutoMoveResult::DestinationType::Foundation) {
// we don't know where the card was moved, it could've been the foundation too increaseScore(10); // Score increase for moving to foundation
// to be safe, emit a change signal for it too
// NOTE: consider returning what changed from tryAutoMoveSingleCard
emit foundationChanged(); emit foundationChanged();
}
incrementMoveAmt(); incrementMoveAmt();
return true; return true;
@ -350,7 +365,7 @@ bool GameState::autoMoveColumnCard(int columnId, int cardIndex) {
selectedCards.append(curCol->card()); selectedCards.append(curCol->card());
} }
if (!tryAutoMoveMultipleCards(selectedCards, columnId)) { if (!tryAutoMoveMultipleCards(selectedCards, columnId).has_value()) {
qDebug() << "> Moving failed, no available move found"; qDebug() << "> Moving failed, no available move found";
return false; return false;
} }
@ -415,6 +430,7 @@ GameState* GameState::clone(bool enableWinnabilitySim) const {
newGameState->m_gameWon = m_gameWon; newGameState->m_gameWon = m_gameWon;
newGameState->m_prelimWin = m_prelimWin; newGameState->m_prelimWin = m_prelimWin;
newGameState->m_score = m_score;
assert(this->generateStateHash() == newGameState->generateStateHash()); assert(this->generateStateHash() == newGameState->generateStateHash());
return newGameState; return newGameState;
@ -452,6 +468,7 @@ void GameState::cleanupBoard(bool emitChanges) {
} }
m_moveAmt = 0; m_moveAmt = 0;
m_score = 0;
// Note that we don't need to reset gameWon/prelimWin from here, as it's // Note that we don't need to reset gameWon/prelimWin from here, as it's
// auto-checked from onFoundationChanged, which the emits trigger. Similarly // auto-checked from onFoundationChanged, which the emits trigger. Similarly
@ -512,13 +529,13 @@ QString GameState::generateStateHash() const {
return stateHash; return stateHash;
} }
bool GameState::tryAutoMoveSingleCard(PlayingCard& cardToMove, int skipColumnId) { std::optional<GameState::AutoMoveResult> GameState::tryAutoMoveSingleCard(PlayingCard& cardToMove, int skipColumnId) {
// 1. Try moving the card to the foundation // 1. Try moving the card to the foundation
const int foundationId = static_cast<int>(cardToMove.suit()); const int foundationId = static_cast<int>(cardToMove.suit());
if (isFoundationMoveValid(cardToMove, foundationId)) { if (isFoundationMoveValid(cardToMove, foundationId)) {
m_foundation[foundationId].prepend(&cardToMove); m_foundation[foundationId].prepend(&cardToMove);
qDebug() << "* Auto-moved card " << cardToMove.toString() << " to foundation " << foundationId; qDebug() << "* Auto-moved card " << cardToMove.toString() << " to foundation " << foundationId;
return true; return AutoMoveResult{AutoMoveResult::DestinationType::Foundation, foundationId};
} }
// 2. Try moving the card to another column // 2. Try moving the card to another column
@ -530,16 +547,16 @@ bool GameState::tryAutoMoveSingleCard(PlayingCard& cardToMove, int skipColumnId)
ColumnSlot* col = new ColumnSlot(&cardToMove, true, this); ColumnSlot* col = new ColumnSlot(&cardToMove, true, this);
m_columns[columnId].append(col); m_columns[columnId].append(col);
qDebug() << "* Auto-moved card " << cardToMove.toString() << " to column " << columnId; qDebug() << "* Auto-moved card " << cardToMove.toString() << " to column " << columnId;
return true; return AutoMoveResult{AutoMoveResult::DestinationType::Column, columnId};
} }
} }
// No available auto-move // No available auto-move
qDebug() << "* Auto-move failed, no available moves"; qDebug() << "* Auto-move failed, no available moves";
return false; return std::nullopt;
} }
bool GameState::tryAutoMoveMultipleCards(const QList<PlayingCard*>& cards, int skipColumnId) { std::optional<GameState::AutoMoveResult> GameState::tryAutoMoveMultipleCards(const QList<PlayingCard*>& cards, int skipColumnId) {
assert(cards.size() > 1); assert(cards.size() > 1);
// If we can move the first (selected) card to another column, // If we can move the first (selected) card to another column,
@ -559,12 +576,12 @@ bool GameState::tryAutoMoveMultipleCards(const QList<PlayingCard*>& cards, int s
m_columns[columnId].append(col); m_columns[columnId].append(col);
qDebug() << "* Auto-moved card " << card->toString() << " to column " << columnId; qDebug() << "* Auto-moved card " << card->toString() << " to column " << columnId;
} }
return true; return AutoMoveResult{AutoMoveResult::DestinationType::Column, columnId};
} }
} }
qDebug() << "* Auto-move failed, no available moves"; qDebug() << "* Auto-move failed, no available moves";
return false; return std::nullopt;
} }
bool GameState::isFoundationMoveValid(const PlayingCard& cardToMove, int foundationId) const { bool GameState::isFoundationMoveValid(const PlayingCard& cardToMove, int foundationId) const {
@ -634,6 +651,7 @@ void GameState::ensureColumnRevealed(int columnId) {
// First slot in the column must always be revealed, reveal it // First slot in the column must always be revealed, reveal it
col->reveal(); col->reveal();
increaseScore(5); // Score increase for revealing a card
qDebug() << "Revealed card " << col->card()->toString() << " in column " << columnId; qDebug() << "Revealed card " << col->card()->toString() << " in column " << columnId;
} }
@ -642,6 +660,11 @@ void GameState::incrementMoveAmt() {
emit moveAmountChanged(); emit moveAmountChanged();
} }
void GameState::increaseScore(int amount) {
m_score += amount;
emit scoreChanged();
}
bool GameState::prelimWinCheck() { bool GameState::prelimWinCheck() {
// Check if the game is preliminarily won: // Check if the game is preliminarily won:
// This occurs when all cards in the tableau are revealed. // This occurs when all cards in the tableau are revealed.
@ -903,6 +926,10 @@ QVariant GameState::isWinnable() const {
return map; return map;
} }
int GameState::score() const {
return m_score;
}
std::pair<std::optional<bool>, int> GameState::checkWinnable() { std::pair<std::optional<bool>, int> GameState::checkWinnable() {
if (!m_enableWinnabilitySim) if (!m_enableWinnabilitySim)
return {std::nullopt, 0}; return {std::nullopt, 0};

View file

@ -27,6 +27,7 @@ class GameState : public QObject {
Q_PROPERTY(bool gameWon READ gameWon NOTIFY gameWonChanged) Q_PROPERTY(bool gameWon READ gameWon NOTIFY gameWonChanged)
Q_PROPERTY(bool preliminaryWin READ preliminaryWin NOTIFY preliminaryWinChanged) Q_PROPERTY(bool preliminaryWin READ preliminaryWin NOTIFY preliminaryWinChanged)
Q_PROPERTY(QVariant isWinnable READ isWinnable NOTIFY isWinnableChanged) Q_PROPERTY(QVariant isWinnable READ isWinnable NOTIFY isWinnableChanged)
Q_PROPERTY(int score READ score NOTIFY scoreChanged)
public: public:
explicit GameState(QObject* parent = nullptr, bool preDealCards = true, bool enableWinnabilitySim = true); explicit GameState(QObject* parent = nullptr, bool preDealCards = true, bool enableWinnabilitySim = true);
@ -41,6 +42,7 @@ class GameState : public QObject {
bool preliminaryWin() const; bool preliminaryWin() const;
bool gameWon() const; bool gameWon() const;
QVariant isWinnable() const; QVariant isWinnable() const;
int score() const;
// General functions // General functions
Q_INVOKABLE void dealCards(); Q_INVOKABLE void dealCards();
@ -66,6 +68,7 @@ class GameState : public QObject {
void gameWonChanged(); void gameWonChanged();
void preliminaryWinChanged(); void preliminaryWinChanged();
void isWinnableChanged(); void isWinnableChanged();
void scoreChanged();
private slots: private slots:
void onFoundationChanged(); void onFoundationChanged();
@ -82,19 +85,30 @@ class GameState : public QObject {
bool m_prelimWin; bool m_prelimWin;
std::pair<std::optional<bool>, int> m_isWinnable; std::pair<std::optional<bool>, int> m_isWinnable;
bool m_enableWinnabilitySim; bool m_enableWinnabilitySim;
int m_score;
struct AutoMoveResult {
enum class DestinationType {
Foundation,
Column
};
DestinationType destinationType;
int destinationId;
};
GameState* clone(bool enableWinnabilitySim = false) const; GameState* clone(bool enableWinnabilitySim = false) const;
void cleanupBoard(bool emitChanges); void cleanupBoard(bool emitChanges);
QString generateStateHash() const; QString generateStateHash() const;
bool tryAutoMoveSingleCard(PlayingCard& cardToMove, int skipColumnId = -1); std::optional<AutoMoveResult> tryAutoMoveSingleCard(PlayingCard& cardToMove, int skipColumnId = -1);
bool tryAutoMoveMultipleCards(const QList<PlayingCard*>& cards, int skipColumnId); std::optional<AutoMoveResult> tryAutoMoveMultipleCards(const QList<PlayingCard*>& cards, int skipColumnId);
bool isFoundationMoveValid(const PlayingCard& cardToMove, int foundationId) const; bool isFoundationMoveValid(const PlayingCard& cardToMove, int foundationId) const;
bool isColumnMoveValid(const PlayingCard& cardToMove, int columnId) const; bool isColumnMoveValid(const PlayingCard& cardToMove, int columnId) const;
void ensureColumnRevealed(int columnId); void ensureColumnRevealed(int columnId);
void incrementMoveAmt(); void incrementMoveAmt();
void increaseScore(int amount);
bool winCheck(); bool winCheck();
bool prelimWinCheck(); bool prelimWinCheck();