From 3254818410cef3395418868109c64f10351a6d20 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Sun, 8 Dec 2024 14:43:52 +0100 Subject: [PATCH] Add preliminary win detection --- src/gamestate.cpp | 105 ++++++++++++++++++++++++++++++++-------------- src/gamestate.h | 8 ++++ 2 files changed, 81 insertions(+), 32 deletions(-) diff --git a/src/gamestate.cpp b/src/gamestate.cpp index 1af3ff8..5340168 100644 --- a/src/gamestate.cpp +++ b/src/gamestate.cpp @@ -8,6 +8,7 @@ GameState::GameState(QObject* parent, bool preDealCards) : QObject{parent}, m_columns(7), m_foundation(4), m_gameWon(false) { assert(connect(this, &GameState::foundationChanged, this, &GameState::onFoundationChanged)); + assert(connect(this, &GameState::throwawayPileChanged, this, &GameState::onThrowawayPileChanged)); if (preDealCards) dealCards(); @@ -354,24 +355,12 @@ bool GameState::autoMoveColumnCard(int columnId, int cardIndex) { } void GameState::onFoundationChanged() { - // Check if the game is won (can only happen on a foundation pile change) - bool gameWon = true; - for (const QList& foundationPile : std::as_const(m_foundation)) { - // The piles need to contain all 13 card values each, otherwise the game isn't won - if (foundationPile.size() != 13) { - gameWon = false; - break; - } - } + prelimWinCheck(); + winCheck(); +} - if (gameWon == m_gameWon) - return; - - if (gameWon) - qDebug() << "The game was won!"; - - m_gameWon = gameWon; - emit gameWonChanged(); +void GameState::onThrowawayPileChanged() { + prelimWinCheck(); } GameState* GameState::clone() const { @@ -406,6 +395,7 @@ GameState* GameState::clone() const { } newGameState->m_gameWon = m_gameWon; + newGameState->m_prelimWin = m_prelimWin; assert(this->generateStateHash() == newGameState->generateStateHash()); return newGameState; @@ -444,7 +434,7 @@ void GameState::cleanupBoard(bool emitChanges) { m_moveAmt = 0; - // Note that we don't need to reset gameWon 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 if (emitChanges) { @@ -498,9 +488,6 @@ QString GameState::generateStateHash() const { } stateHash.removeLast(); - stateHash += " ; "; - - stateHash += m_gameWon ? "t" : "f"; return stateHash; } @@ -635,8 +622,63 @@ void GameState::incrementMoveAmt() { emit moveAmountChanged(); } +bool GameState::prelimWinCheck() { + // Check if the game is preliminarily won: + // This occurs when there are no cards in the draw or throwaway piles, + // and all cards in the tableau are revealed. Such games are essentially won, + // as the cards only need to be moved to the foundation piles. + bool prelimWin = m_drawPile.isEmpty() && m_throwawayPile.isEmpty(); + + if (prelimWin) { + for (const auto& column : std::as_const(m_columns)) { + for (const ColumnSlot* card : column) { + if (!card->isRevealed()) { + prelimWin = false; + break; + } + } + if (!prelimWin) + break; + } + } + + if (prelimWin == m_prelimWin) + return m_prelimWin; + + if (prelimWin) + qDebug() << "Preliminary win detected! The game is essentially won."; + + m_prelimWin = prelimWin; + emit preliminaryWinChanged(); + + return m_prelimWin; +} + +bool GameState::winCheck() { + // Check if the game is won (can only happen on a foundation pile change) + bool gameWon = true; + for (const QList& foundationPile : std::as_const(m_foundation)) { + // The piles need to contain all 13 card values each, otherwise the game isn't won + if (foundationPile.size() != 13) { + gameWon = false; + break; + } + } + + if (gameWon == m_gameWon) + return m_gameWon; + + if (gameWon) + qDebug() << "The game was won!"; + + m_gameWon = gameWon; + emit gameWonChanged(); + + return m_gameWon; +} + std::pair, int> GameState::canWinThroughSimulation(QSet& visitedStates, QElapsedTimer timer) const { - if (m_gameWon) + if (m_prelimWin) return std::make_pair(true, 0); // Already won at depth 0 // Go over all possible moves using BFS @@ -676,9 +718,8 @@ std::pair, int> GameState::canWinThroughSimulation(QSetgenerateStateHash(); assert(currentHash != stateHash); - if (newState->m_gameWon) { + if (newState->prelimWinCheck()) return std::make_pair(true, depth + 1); // Return depth if game won - } if (!visitedStates.contains(stateHash)) { visitedStates.insert(stateHash); @@ -701,9 +742,8 @@ std::pair, int> GameState::canWinThroughSimulation(QSetgenerateStateHash(); assert(currentHash != stateHash); - if (newState->m_gameWon) { + if (newState->prelimWinCheck()) return std::make_pair(true, depth + 1); - } if (!visitedStates.contains(stateHash)) { visitedStates.insert(stateHash); @@ -721,9 +761,8 @@ std::pair, int> GameState::canWinThroughSimulation(QSetgenerateStateHash(); assert(currentHash != stateHash); - if (newState->m_gameWon) { + if (newState->prelimWinCheck()) return std::make_pair(true, depth + 1); - } if (!visitedStates.contains(stateHash)) { visitedStates.insert(stateHash); @@ -741,9 +780,8 @@ std::pair, int> GameState::canWinThroughSimulation(QSetgenerateStateHash(); assert(currentHash != stateHash); - if (newState->m_gameWon) { + if (newState->prelimWinCheck()) return std::make_pair(true, depth + 1); - } if (!visitedStates.contains(stateHash)) { visitedStates.insert(stateHash); @@ -773,9 +811,8 @@ std::pair, int> GameState::canWinThroughSimulation(QSetgenerateStateHash(); assert(currentHash != stateHash); - if (newState->m_gameWon) { + if (newState->prelimWinCheck()) return std::make_pair(true, depth + 1); - } if (!visitedStates.contains(stateHash)) { visitedStates.insert(stateHash); @@ -825,6 +862,10 @@ int GameState::moveAmount() const { return m_moveAmt; } +bool GameState::preliminaryWin() const { + return m_prelimWin; +} + bool GameState::gameWon() const { return m_gameWon; } diff --git a/src/gamestate.h b/src/gamestate.h index ebf3e17..1d08fe1 100644 --- a/src/gamestate.h +++ b/src/gamestate.h @@ -23,6 +23,7 @@ class GameState : public QObject { Q_PROPERTY(QVariantList foundation READ foundation NOTIFY foundationChanged) Q_PROPERTY(int moveAmount READ moveAmount NOTIFY moveAmountChanged) Q_PROPERTY(bool gameWon READ gameWon NOTIFY gameWonChanged) + Q_PROPERTY(bool preliminaryWin READ preliminaryWin NOTIFY preliminaryWinChanged) public: explicit GameState(QObject* parent = nullptr, bool preDealCards = true); @@ -34,6 +35,7 @@ class GameState : public QObject { QVariantList columns() const; QVariantList foundation() const; int moveAmount() const; + bool preliminaryWin() const; bool gameWon() const; // General functions @@ -59,9 +61,11 @@ class GameState : public QObject { void foundationChanged(); void moveAmountChanged(); void gameWonChanged(); + void preliminaryWinChanged(); private slots: void onFoundationChanged(); + void onThrowawayPileChanged(); private: QList m_drawPile; @@ -70,6 +74,7 @@ class GameState : public QObject { QList> m_foundation; int m_moveAmt; bool m_gameWon; + bool m_prelimWin; GameState* clone() const; void cleanupBoard(bool emitChanges); @@ -84,6 +89,9 @@ class GameState : public QObject { void ensureColumnRevealed(int columnId); void incrementMoveAmt(); + bool winCheck(); + bool prelimWinCheck(); + std::pair, int> canWinThroughSimulation(QSet& visitedStates, QElapsedTimer timer) const; };