Convert winnability check to property

This commit is contained in:
ItsDrike 2024-12-08 17:06:08 +01:00
parent 3254818410
commit c5e68601a4
Signed by: ItsDrike
GPG key ID: FA2745890B7048C0
5 changed files with 39 additions and 31 deletions

View file

@ -78,14 +78,6 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: GameState.drawNextCard()
if (GameState.drawNextCard()) {
if (GameState.isWinnable()) {
console.log("Still winnable");
} else {
console.warn("Game is lost");
}
}
}
} }
} }

View file

@ -50,13 +50,7 @@ Row {
isFaceDown: parent.colSlot ? !parent.colSlot.revealed : false isFaceDown: parent.colSlot ? !parent.colSlot.revealed : false
onClicked: { onClicked: {
if (parent.colSlot && parent.colSlot.revealed) { if (parent.colSlot && parent.colSlot.revealed) {
if (GameState.autoMoveColumnCard(parent.columnId, parent.cardId)) { GameState.autoMoveColumnCard(parent.columnId, parent.cardId);
if (GameState.isWinnable()) {
console.log("Still winnable");
} else {
console.log("Game is lost");
}
}
} }
} }
} }

View file

@ -26,13 +26,7 @@ Row {
// Only auto-move the last card in the throwaway pile // Only auto-move the last card in the throwaway pile
// cards below it are shown, but shouldn't have a click effect // cards below it are shown, but shouldn't have a click effect
if (reversedIndex == 0) { if (reversedIndex == 0) {
if (GameState.autoMoveThrownCard()) { GameState.autoMoveThrownCard();
if (GameState.isWinnable()) {
console.log("Still winnable");
} else {
console.log("Game is lost");
}
}
} }
} }
} }

View file

@ -1,14 +1,18 @@
#include "gamestate.h" #include "gamestate.h"
#include <QDebug> #include <QDebug>
#include <QSet> #include <QSet>
#include <optional>
#include <qlist.h> #include <qlist.h>
#include <qqmllist.h> #include <qqmllist.h>
#include <queue> #include <queue>
#include <random> #include <random>
GameState::GameState(QObject* parent, bool preDealCards) : QObject{parent}, m_columns(7), m_foundation(4), m_gameWon(false) { GameState::GameState(QObject* parent, bool preDealCards, bool enableWinnabilitySim) :
QObject{parent}, m_columns(7), m_foundation(4), m_moveAmt(0), m_gameWon(false), m_prelimWin(false), m_isWinnable({std::nullopt, 0}),
m_enableWinnabilitySim(enableWinnabilitySim) {
assert(connect(this, &GameState::foundationChanged, this, &GameState::onFoundationChanged)); assert(connect(this, &GameState::foundationChanged, this, &GameState::onFoundationChanged));
assert(connect(this, &GameState::throwawayPileChanged, this, &GameState::onThrowawayPileChanged)); assert(connect(this, &GameState::throwawayPileChanged, this, &GameState::onThrowawayPileChanged));
assert(connect(this, &GameState::moveAmountChanged, this, &GameState::onMoveAmountChanged));
if (preDealCards) if (preDealCards)
dealCards(); dealCards();
@ -363,8 +367,12 @@ void GameState::onThrowawayPileChanged() {
prelimWinCheck(); prelimWinCheck();
} }
GameState* GameState::clone() const { void GameState::onMoveAmountChanged() {
GameState* newGameState = new GameState(nullptr, false); checkWinnable();
}
GameState* GameState::clone(bool enableWinnabilitySim) const {
GameState* newGameState = new GameState(nullptr, false, enableWinnabilitySim);
// Deep copy the necessary data // Deep copy the necessary data
@ -435,7 +443,8 @@ void GameState::cleanupBoard(bool emitChanges) {
m_moveAmt = 0; m_moveAmt = 0;
// Note that we don't need to reset gameWon/prelimWin 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. Similarly
// isWinnable status will run from onMoveAmountChanged slot.
if (emitChanges) { if (emitChanges) {
emit drawPileChanged(); emit drawPileChanged();
@ -871,6 +880,13 @@ bool GameState::gameWon() const {
} }
std::pair<std::optional<bool>, int> GameState::isWinnable() const { std::pair<std::optional<bool>, int> GameState::isWinnable() const {
return m_isWinnable;
}
std::pair<std::optional<bool>, int> GameState::checkWinnable() {
if (!m_enableWinnabilitySim)
return {std::nullopt, 0};
qDebug() << "--- Simulating winning scenario ---"; qDebug() << "--- Simulating winning scenario ---";
QElapsedTimer timer; QElapsedTimer timer;
@ -889,5 +905,11 @@ std::pair<std::optional<bool>, int> GameState::isWinnable() const {
qInstallMessageHandler(originalHandler); qInstallMessageHandler(originalHandler);
qDebug() << "--- Simulation end (result:" << res << ", took:" << elapsedTime << "ms) ---"; qDebug() << "--- Simulation end (result:" << res << ", took:" << elapsedTime << "ms) ---";
if (m_isWinnable != res) {
m_isWinnable = res;
emit isWinnableChanged();
}
return res; return res;
} }

View file

@ -9,6 +9,7 @@
#include <qelapsedtimer.h> #include <qelapsedtimer.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <qqmllist.h> #include <qqmllist.h>
#include <qtmetamacros.h>
// Limits for checking winnability // Limits for checking winnability
#define MAX_EVAL_TIME 100 // Evaluation time limit (ms) #define MAX_EVAL_TIME 100 // Evaluation time limit (ms)
@ -24,9 +25,10 @@ class GameState : public QObject {
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) Q_PROPERTY(bool preliminaryWin READ preliminaryWin NOTIFY preliminaryWinChanged)
Q_PROPERTY(std::pair<std::optional<bool>, int> isWinnable READ isWinnable NOTIFY isWinnableChanged)
public: public:
explicit GameState(QObject* parent = nullptr, bool preDealCards = true); explicit GameState(QObject* parent = nullptr, bool preDealCards = true, bool enableWinnabilitySim = true);
~GameState(); ~GameState();
// Getters // Getters
@ -37,12 +39,12 @@ class GameState : public QObject {
int moveAmount() const; int moveAmount() const;
bool preliminaryWin() const; bool preliminaryWin() const;
bool gameWon() const; bool gameWon() const;
std::pair<std::optional<bool>, int> isWinnable() const;
// General functions // General functions
Q_INVOKABLE void dealCards(); Q_INVOKABLE void dealCards();
Q_INVOKABLE void setupWinningDeck(); Q_INVOKABLE void setupWinningDeck();
Q_INVOKABLE bool drawNextCard(); Q_INVOKABLE bool drawNextCard();
Q_INVOKABLE std::pair<std::optional<bool>, int> isWinnable() const; // TODO: Implement as Q_PROPERTY instead
// Manual moves (from X to Y) // Manual moves (from X to Y)
Q_INVOKABLE bool moveThrownCardToColumn(int columnId); Q_INVOKABLE bool moveThrownCardToColumn(int columnId);
@ -62,10 +64,12 @@ class GameState : public QObject {
void moveAmountChanged(); void moveAmountChanged();
void gameWonChanged(); void gameWonChanged();
void preliminaryWinChanged(); void preliminaryWinChanged();
void isWinnableChanged();
private slots: private slots:
void onFoundationChanged(); void onFoundationChanged();
void onThrowawayPileChanged(); void onThrowawayPileChanged();
void onMoveAmountChanged();
private: private:
QList<PlayingCard*> m_drawPile; QList<PlayingCard*> m_drawPile;
@ -75,8 +79,10 @@ class GameState : public QObject {
int m_moveAmt; int m_moveAmt;
bool m_gameWon; bool m_gameWon;
bool m_prelimWin; bool m_prelimWin;
std::pair<std::optional<bool>, int> m_isWinnable;
bool m_enableWinnabilitySim;
GameState* clone() const; GameState* clone(bool enableWinnabilitySim = false) const;
void cleanupBoard(bool emitChanges); void cleanupBoard(bool emitChanges);
QString generateStateHash() const; QString generateStateHash() const;
@ -91,7 +97,7 @@ class GameState : public QObject {
bool winCheck(); bool winCheck();
bool prelimWinCheck(); bool prelimWinCheck();
std::pair<std::optional<bool>, int> checkWinnable();
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;
}; };