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

View file

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

View file

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

View file

@ -59,6 +59,7 @@ void GameState::dealCards() {
emit columnsChanged();
emit foundationChanged();
emit moveAmountChanged();
emit scoreChanged();
}
void GameState::setupWinningDeck() {
@ -102,6 +103,7 @@ void GameState::setupWinningDeck() {
emit columnsChanged();
emit foundationChanged();
emit moveAmountChanged();
emit scoreChanged();
}
bool GameState::drawNextCard() {
@ -153,6 +155,7 @@ bool GameState::moveThrownCardToColumn(int columnId) {
qDebug() << "> Moving complete";
incrementMoveAmt();
increaseScore(5); // Score increase for moving to column
emit throwawayPileChanged();
emit columnsChanged();
return true;
@ -183,6 +186,7 @@ bool GameState::moveThrownCardToFoundation(int foundationId) {
qDebug() << "> Moving complete";
incrementMoveAmt();
increaseScore(10); // Score increase for moving to foundation
emit throwawayPileChanged();
emit foundationChanged();
return true;
@ -263,6 +267,7 @@ bool GameState::moveColumnCardToFoundation(int columnId, int foundationId) {
ensureColumnRevealed(columnId);
qDebug() << "> Moving complete";
increaseScore(10); // Score increase for moving to foundation
incrementMoveAmt();
emit columnsChanged();
emit foundationChanged();
@ -280,7 +285,8 @@ bool GameState::autoMoveThrownCard() {
qDebug() << "Attempting auto-move of thrown card " << cardToMove->toString();
// 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";
return false;
}
@ -291,11 +297,17 @@ bool GameState::autoMoveThrownCard() {
emit throwawayPileChanged();
// We don't know which pile the card was moved to, to be safe, emit
// a change from both
// NOTE: consider returning what changed from tryAutoMoveSingleCard
emit columnsChanged();
switch (changed.value().destinationType) {
case AutoMoveResult::DestinationType::Foundation:
increaseScore(10); // Score increase for moving to foundation
emit foundationChanged();
break;
case AutoMoveResult::DestinationType::Column:
increaseScore(5); // Score increase for moving to column
emit columnsChanged();
break;
default: assert(false); break;
}
incrementMoveAmt();
return true;
@ -320,7 +332,8 @@ bool GameState::autoMoveColumnCard(int columnId, int cardIndex) {
// This is a single card move (last card)
PlayingCard* cardToMove = col->card();
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";
return false;
}
@ -331,12 +344,14 @@ bool GameState::autoMoveColumnCard(int columnId, int cardIndex) {
ensureColumnRevealed(columnId);
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();
// we don't know where the card was moved, it could've been the foundation too
// to be safe, emit a change signal for it too
// NOTE: consider returning what changed from tryAutoMoveSingleCard
if (changed.value().destinationType == AutoMoveResult::DestinationType::Foundation) {
increaseScore(10); // Score increase for moving to foundation
emit foundationChanged();
}
incrementMoveAmt();
return true;
@ -350,7 +365,7 @@ bool GameState::autoMoveColumnCard(int columnId, int cardIndex) {
selectedCards.append(curCol->card());
}
if (!tryAutoMoveMultipleCards(selectedCards, columnId)) {
if (!tryAutoMoveMultipleCards(selectedCards, columnId).has_value()) {
qDebug() << "> Moving failed, no available move found";
return false;
}
@ -415,6 +430,7 @@ GameState* GameState::clone(bool enableWinnabilitySim) const {
newGameState->m_gameWon = m_gameWon;
newGameState->m_prelimWin = m_prelimWin;
newGameState->m_score = m_score;
assert(this->generateStateHash() == newGameState->generateStateHash());
return newGameState;
@ -452,6 +468,7 @@ void GameState::cleanupBoard(bool emitChanges) {
}
m_moveAmt = 0;
m_score = 0;
// Note that we don't need to reset gameWon/prelimWin from here, as it's
// auto-checked from onFoundationChanged, which the emits trigger. Similarly
@ -512,13 +529,13 @@ QString GameState::generateStateHash() const {
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
const int foundationId = static_cast<int>(cardToMove.suit());
if (isFoundationMoveValid(cardToMove, foundationId)) {
m_foundation[foundationId].prepend(&cardToMove);
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
@ -530,16 +547,16 @@ bool GameState::tryAutoMoveSingleCard(PlayingCard& cardToMove, int skipColumnId)
ColumnSlot* col = new ColumnSlot(&cardToMove, true, this);
m_columns[columnId].append(col);
qDebug() << "* Auto-moved card " << cardToMove.toString() << " to column " << columnId;
return true;
return AutoMoveResult{AutoMoveResult::DestinationType::Column, columnId};
}
}
// No available auto-move
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);
// 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);
qDebug() << "* Auto-moved card " << card->toString() << " to column " << columnId;
}
return true;
return AutoMoveResult{AutoMoveResult::DestinationType::Column, columnId};
}
}
qDebug() << "* Auto-move failed, no available moves";
return false;
return std::nullopt;
}
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
col->reveal();
increaseScore(5); // Score increase for revealing a card
qDebug() << "Revealed card " << col->card()->toString() << " in column " << columnId;
}
@ -642,6 +660,11 @@ void GameState::incrementMoveAmt() {
emit moveAmountChanged();
}
void GameState::increaseScore(int amount) {
m_score += amount;
emit scoreChanged();
}
bool GameState::prelimWinCheck() {
// Check if the game is preliminarily won:
// This occurs when all cards in the tableau are revealed.
@ -903,6 +926,10 @@ QVariant GameState::isWinnable() const {
return map;
}
int GameState::score() const {
return m_score;
}
std::pair<std::optional<bool>, int> GameState::checkWinnable() {
if (!m_enableWinnabilitySim)
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 preliminaryWin READ preliminaryWin NOTIFY preliminaryWinChanged)
Q_PROPERTY(QVariant isWinnable READ isWinnable NOTIFY isWinnableChanged)
Q_PROPERTY(int score READ score NOTIFY scoreChanged)
public:
explicit GameState(QObject* parent = nullptr, bool preDealCards = true, bool enableWinnabilitySim = true);
@ -41,6 +42,7 @@ class GameState : public QObject {
bool preliminaryWin() const;
bool gameWon() const;
QVariant isWinnable() const;
int score() const;
// General functions
Q_INVOKABLE void dealCards();
@ -66,6 +68,7 @@ class GameState : public QObject {
void gameWonChanged();
void preliminaryWinChanged();
void isWinnableChanged();
void scoreChanged();
private slots:
void onFoundationChanged();
@ -82,19 +85,30 @@ class GameState : public QObject {
bool m_prelimWin;
std::pair<std::optional<bool>, int> m_isWinnable;
bool m_enableWinnabilitySim;
int m_score;
struct AutoMoveResult {
enum class DestinationType {
Foundation,
Column
};
DestinationType destinationType;
int destinationId;
};
GameState* clone(bool enableWinnabilitySim = false) const;
void cleanupBoard(bool emitChanges);
QString generateStateHash() const;
bool tryAutoMoveSingleCard(PlayingCard& cardToMove, int skipColumnId = -1);
bool tryAutoMoveMultipleCards(const QList<PlayingCard*>& cards, int skipColumnId);
std::optional<AutoMoveResult> tryAutoMoveSingleCard(PlayingCard& cardToMove, int skipColumnId = -1);
std::optional<AutoMoveResult> tryAutoMoveMultipleCards(const QList<PlayingCard*>& cards, int skipColumnId);
bool isFoundationMoveValid(const PlayingCard& cardToMove, int foundationId) const;
bool isColumnMoveValid(const PlayingCard& cardToMove, int columnId) const;
void ensureColumnRevealed(int columnId);
void incrementMoveAmt();
void increaseScore(int amount);
bool winCheck();
bool prelimWinCheck();