diff --git a/src/gamestate.cpp b/src/gamestate.cpp index b2a5a2e..5912a85 100644 --- a/src/gamestate.cpp +++ b/src/gamestate.cpp @@ -1,7 +1,6 @@ #include "gamestate.h" #include #include -#include #include #include @@ -617,13 +616,19 @@ void GameState::ensureColumnRevealed(int columnId) { qDebug() << "Revealed card " << col->card()->toString() << " in column " << columnId; } -std::optional GameState::canWinThroughSimulation(QSet& visitedStates, int depth) const { +std::optional GameState::canWinThroughSimulation(QSet& visitedStates, QElapsedTimer timer, int depth) const { // Check if the game is already won if (m_gameWon) return true; - // Limit evaluation to a max depth of 20 moves - if (depth > MAX_EVAL_DEPTH) + // Limit evaluation time (ensures this doesn't block the game) + if (timer.hasExpired(MAX_EVAL_TIME)) + return std::nullopt; + + // Limit depth (ensures we don't spend all eval time looking at a single branch.) + // Note that it might be a good idea to switch to a BFS type search, instead of depth + // limiting DFS. + if (depth > MAX_DEPTH) return std::nullopt; // Generate the current state hash @@ -651,7 +656,7 @@ std::optional GameState::canWinThroughSimulation(QSet& visitedSta assert(clonedState->moveColumnCardToFoundation(columnId, foundationId)); assert(clonedState->generateStateHash() != generateStateHash()); - auto res = clonedState->canWinThroughSimulation(visitedStates, depth + 1); + auto res = clonedState->canWinThroughSimulation(visitedStates, timer, depth + 1); delete clonedState; if (res.value_or(false)) @@ -674,7 +679,7 @@ std::optional GameState::canWinThroughSimulation(QSet& visitedSta assert(clonedState->moveThrownCardToFoundation(foundationId)); assert(clonedState->generateStateHash() != generateStateHash()); - auto res = clonedState->canWinThroughSimulation(visitedStates, depth + 1); + auto res = clonedState->canWinThroughSimulation(visitedStates, timer, depth + 1); delete clonedState; if (res.value_or(false)) @@ -692,7 +697,7 @@ std::optional GameState::canWinThroughSimulation(QSet& visitedSta assert(clonedState->moveThrownCardToColumn(toColumnId)); assert(clonedState->generateStateHash() != generateStateHash()); - auto res = clonedState->canWinThroughSimulation(visitedStates, depth + 1); + auto res = clonedState->canWinThroughSimulation(visitedStates, timer, depth + 1); delete clonedState; if (res.value_or(false)) @@ -710,7 +715,7 @@ std::optional GameState::canWinThroughSimulation(QSet& visitedSta assert(clonedState->drawNextCard()); assert(clonedState->generateStateHash() != generateStateHash()); - auto res = clonedState->canWinThroughSimulation(visitedStates, depth + 1); + auto res = clonedState->canWinThroughSimulation(visitedStates, timer, depth + 1); delete clonedState; if (res.value_or(false)) @@ -741,7 +746,7 @@ std::optional GameState::canWinThroughSimulation(QSet& visitedSta assert(clonedState->moveColumnCardToColumn(fromColumnId, toColumnId, fromCardIndex)); assert(clonedState->generateStateHash() != generateStateHash()); - auto res = clonedState->canWinThroughSimulation(visitedStates, depth + 1); + auto res = clonedState->canWinThroughSimulation(visitedStates, timer, depth + 1); delete clonedState; if (res.value_or(false)) @@ -804,7 +809,7 @@ std::optional GameState::isWinnable() const { timer.start(); QSet visitedStates; - std::optional res = canWinThroughSimulation(visitedStates, 0); + std::optional res = canWinThroughSimulation(visitedStates, timer, 0); qint64 elapsedTime = timer.elapsed(); // Restore the original message handler diff --git a/src/gamestate.h b/src/gamestate.h index 36a14a5..b7daf1c 100644 --- a/src/gamestate.h +++ b/src/gamestate.h @@ -6,11 +6,13 @@ #include #include #include +#include #include #include -// Evaluation depth for checking win-ability (this may impact performance) -#define MAX_EVAL_DEPTH 80 +// Limits for checking winnability +#define MAX_EVAL_TIME 100 // Evaluation time limit (ms) +#define MAX_DEPTH 100 // Max moves into the future limit class GameState : public QObject { Q_OBJECT @@ -78,7 +80,7 @@ class GameState : public QObject { void ensureColumnRevealed(int columnId); - std::optional canWinThroughSimulation(QSet& visitedStates, int depth) const; + std::optional canWinThroughSimulation(QSet& visitedStates, QElapsedTimer timer, int depth) const; }; #endif // GAMESTATE_H