Add preliminary win detection

This commit is contained in:
ItsDrike 2024-12-08 14:43:52 +01:00
parent e46d153604
commit 3254818410
Signed by: ItsDrike
GPG key ID: FA2745890B7048C0
2 changed files with 81 additions and 32 deletions

View file

@ -8,6 +8,7 @@
GameState::GameState(QObject* parent, bool preDealCards) : QObject{parent}, m_columns(7), m_foundation(4), m_gameWon(false) { 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::foundationChanged, this, &GameState::onFoundationChanged));
assert(connect(this, &GameState::throwawayPileChanged, this, &GameState::onThrowawayPileChanged));
if (preDealCards) if (preDealCards)
dealCards(); dealCards();
@ -354,24 +355,12 @@ bool GameState::autoMoveColumnCard(int columnId, int cardIndex) {
} }
void GameState::onFoundationChanged() { void GameState::onFoundationChanged() {
// Check if the game is won (can only happen on a foundation pile change) prelimWinCheck();
bool gameWon = true; winCheck();
for (const QList<PlayingCard*>& 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) void GameState::onThrowawayPileChanged() {
return; prelimWinCheck();
if (gameWon)
qDebug() << "The game was won!";
m_gameWon = gameWon;
emit gameWonChanged();
} }
GameState* GameState::clone() const { GameState* GameState::clone() const {
@ -406,6 +395,7 @@ GameState* GameState::clone() const {
} }
newGameState->m_gameWon = m_gameWon; newGameState->m_gameWon = m_gameWon;
newGameState->m_prelimWin = m_prelimWin;
assert(this->generateStateHash() == newGameState->generateStateHash()); assert(this->generateStateHash() == newGameState->generateStateHash());
return newGameState; return newGameState;
@ -444,7 +434,7 @@ void GameState::cleanupBoard(bool emitChanges) {
m_moveAmt = 0; 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 // auto-checked from onFoundationChanged, which the emits trigger
if (emitChanges) { if (emitChanges) {
@ -498,9 +488,6 @@ QString GameState::generateStateHash() const {
} }
stateHash.removeLast(); stateHash.removeLast();
stateHash += " ; ";
stateHash += m_gameWon ? "t" : "f";
return stateHash; return stateHash;
} }
@ -635,8 +622,63 @@ void GameState::incrementMoveAmt() {
emit moveAmountChanged(); 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<PlayingCard*>& 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<std::optional<bool>, int> GameState::canWinThroughSimulation(QSet<QString>& visitedStates, QElapsedTimer timer) const { std::pair<std::optional<bool>, int> GameState::canWinThroughSimulation(QSet<QString>& visitedStates, QElapsedTimer timer) const {
if (m_gameWon) if (m_prelimWin)
return std::make_pair(true, 0); // Already won at depth 0 return std::make_pair(true, 0); // Already won at depth 0
// Go over all possible moves using BFS // Go over all possible moves using BFS
@ -676,9 +718,8 @@ std::pair<std::optional<bool>, int> GameState::canWinThroughSimulation(QSet<QStr
QString stateHash = newState->generateStateHash(); QString stateHash = newState->generateStateHash();
assert(currentHash != stateHash); assert(currentHash != stateHash);
if (newState->m_gameWon) { if (newState->prelimWinCheck())
return std::make_pair(true, depth + 1); // Return depth if game won return std::make_pair(true, depth + 1); // Return depth if game won
}
if (!visitedStates.contains(stateHash)) { if (!visitedStates.contains(stateHash)) {
visitedStates.insert(stateHash); visitedStates.insert(stateHash);
@ -701,9 +742,8 @@ std::pair<std::optional<bool>, int> GameState::canWinThroughSimulation(QSet<QStr
QString stateHash = newState->generateStateHash(); QString stateHash = newState->generateStateHash();
assert(currentHash != stateHash); assert(currentHash != stateHash);
if (newState->m_gameWon) { if (newState->prelimWinCheck())
return std::make_pair(true, depth + 1); return std::make_pair(true, depth + 1);
}
if (!visitedStates.contains(stateHash)) { if (!visitedStates.contains(stateHash)) {
visitedStates.insert(stateHash); visitedStates.insert(stateHash);
@ -721,9 +761,8 @@ std::pair<std::optional<bool>, int> GameState::canWinThroughSimulation(QSet<QStr
QString stateHash = newState->generateStateHash(); QString stateHash = newState->generateStateHash();
assert(currentHash != stateHash); assert(currentHash != stateHash);
if (newState->m_gameWon) { if (newState->prelimWinCheck())
return std::make_pair(true, depth + 1); return std::make_pair(true, depth + 1);
}
if (!visitedStates.contains(stateHash)) { if (!visitedStates.contains(stateHash)) {
visitedStates.insert(stateHash); visitedStates.insert(stateHash);
@ -741,9 +780,8 @@ std::pair<std::optional<bool>, int> GameState::canWinThroughSimulation(QSet<QStr
QString stateHash = newState->generateStateHash(); QString stateHash = newState->generateStateHash();
assert(currentHash != stateHash); assert(currentHash != stateHash);
if (newState->m_gameWon) { if (newState->prelimWinCheck())
return std::make_pair(true, depth + 1); return std::make_pair(true, depth + 1);
}
if (!visitedStates.contains(stateHash)) { if (!visitedStates.contains(stateHash)) {
visitedStates.insert(stateHash); visitedStates.insert(stateHash);
@ -773,9 +811,8 @@ std::pair<std::optional<bool>, int> GameState::canWinThroughSimulation(QSet<QStr
QString stateHash = newState->generateStateHash(); QString stateHash = newState->generateStateHash();
assert(currentHash != stateHash); assert(currentHash != stateHash);
if (newState->m_gameWon) { if (newState->prelimWinCheck())
return std::make_pair(true, depth + 1); return std::make_pair(true, depth + 1);
}
if (!visitedStates.contains(stateHash)) { if (!visitedStates.contains(stateHash)) {
visitedStates.insert(stateHash); visitedStates.insert(stateHash);
@ -825,6 +862,10 @@ int GameState::moveAmount() const {
return m_moveAmt; return m_moveAmt;
} }
bool GameState::preliminaryWin() const {
return m_prelimWin;
}
bool GameState::gameWon() const { bool GameState::gameWon() const {
return m_gameWon; return m_gameWon;
} }

View file

@ -23,6 +23,7 @@ class GameState : public QObject {
Q_PROPERTY(QVariantList foundation READ foundation NOTIFY foundationChanged) Q_PROPERTY(QVariantList foundation READ foundation NOTIFY foundationChanged)
Q_PROPERTY(int moveAmount READ moveAmount NOTIFY moveAmountChanged) Q_PROPERTY(int moveAmount READ moveAmount NOTIFY moveAmountChanged)
Q_PROPERTY(bool gameWon READ gameWon NOTIFY gameWonChanged) Q_PROPERTY(bool gameWon READ gameWon NOTIFY gameWonChanged)
Q_PROPERTY(bool preliminaryWin READ preliminaryWin NOTIFY preliminaryWinChanged)
public: public:
explicit GameState(QObject* parent = nullptr, bool preDealCards = true); explicit GameState(QObject* parent = nullptr, bool preDealCards = true);
@ -34,6 +35,7 @@ class GameState : public QObject {
QVariantList columns() const; QVariantList columns() const;
QVariantList foundation() const; QVariantList foundation() const;
int moveAmount() const; int moveAmount() const;
bool preliminaryWin() const;
bool gameWon() const; bool gameWon() const;
// General functions // General functions
@ -59,9 +61,11 @@ class GameState : public QObject {
void foundationChanged(); void foundationChanged();
void moveAmountChanged(); void moveAmountChanged();
void gameWonChanged(); void gameWonChanged();
void preliminaryWinChanged();
private slots: private slots:
void onFoundationChanged(); void onFoundationChanged();
void onThrowawayPileChanged();
private: private:
QList<PlayingCard*> m_drawPile; QList<PlayingCard*> m_drawPile;
@ -70,6 +74,7 @@ class GameState : public QObject {
QList<QList<PlayingCard*>> m_foundation; QList<QList<PlayingCard*>> m_foundation;
int m_moveAmt; int m_moveAmt;
bool m_gameWon; bool m_gameWon;
bool m_prelimWin;
GameState* clone() const; GameState* clone() const;
void cleanupBoard(bool emitChanges); void cleanupBoard(bool emitChanges);
@ -84,6 +89,9 @@ class GameState : public QObject {
void ensureColumnRevealed(int columnId); void ensureColumnRevealed(int columnId);
void incrementMoveAmt(); void incrementMoveAmt();
bool winCheck();
bool prelimWinCheck();
std::pair<std::optional<bool>, int> canWinThroughSimulation(QSet<QString>& visitedStates, QElapsedTimer timer) const; std::pair<std::optional<bool>, int> canWinThroughSimulation(QSet<QString>& visitedStates, QElapsedTimer timer) const;
}; };