Add preliminary win detection
This commit is contained in:
parent
e46d153604
commit
3254818410
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue