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 {
anchors.fill: parent
onClicked: {
if (GameState.drawNextCard()) {
if (GameState.isWinnable()) {
console.log("Still winnable");
} else {
console.warn("Game is lost");
}
}
}
onClicked: GameState.drawNextCard()
}
}

View file

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

View file

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

View file

@ -1,14 +1,18 @@
#include "gamestate.h"
#include <QDebug>
#include <QSet>
#include <optional>
#include <qlist.h>
#include <qqmllist.h>
#include <queue>
#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::throwawayPileChanged, this, &GameState::onThrowawayPileChanged));
assert(connect(this, &GameState::moveAmountChanged, this, &GameState::onMoveAmountChanged));
if (preDealCards)
dealCards();
@ -363,8 +367,12 @@ void GameState::onThrowawayPileChanged() {
prelimWinCheck();
}
GameState* GameState::clone() const {
GameState* newGameState = new GameState(nullptr, false);
void GameState::onMoveAmountChanged() {
checkWinnable();
}
GameState* GameState::clone(bool enableWinnabilitySim) const {
GameState* newGameState = new GameState(nullptr, false, enableWinnabilitySim);
// Deep copy the necessary data
@ -435,7 +443,8 @@ void GameState::cleanupBoard(bool emitChanges) {
m_moveAmt = 0;
// 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) {
emit drawPileChanged();
@ -871,6 +880,13 @@ bool GameState::gameWon() 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 ---";
QElapsedTimer timer;
@ -889,5 +905,11 @@ std::pair<std::optional<bool>, int> GameState::isWinnable() const {
qInstallMessageHandler(originalHandler);
qDebug() << "--- Simulation end (result:" << res << ", took:" << elapsedTime << "ms) ---";
if (m_isWinnable != res) {
m_isWinnable = res;
emit isWinnableChanged();
}
return res;
}

View file

@ -9,6 +9,7 @@
#include <qelapsedtimer.h>
#include <qqmlintegration.h>
#include <qqmllist.h>
#include <qtmetamacros.h>
// Limits for checking winnability
#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(bool gameWon READ gameWon NOTIFY gameWonChanged)
Q_PROPERTY(bool preliminaryWin READ preliminaryWin NOTIFY preliminaryWinChanged)
Q_PROPERTY(std::pair<std::optional<bool>, int> isWinnable READ isWinnable NOTIFY isWinnableChanged)
public:
explicit GameState(QObject* parent = nullptr, bool preDealCards = true);
explicit GameState(QObject* parent = nullptr, bool preDealCards = true, bool enableWinnabilitySim = true);
~GameState();
// Getters
@ -37,12 +39,12 @@ class GameState : public QObject {
int moveAmount() const;
bool preliminaryWin() const;
bool gameWon() const;
std::pair<std::optional<bool>, int> isWinnable() const;
// General functions
Q_INVOKABLE void dealCards();
Q_INVOKABLE void setupWinningDeck();
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)
Q_INVOKABLE bool moveThrownCardToColumn(int columnId);
@ -62,10 +64,12 @@ class GameState : public QObject {
void moveAmountChanged();
void gameWonChanged();
void preliminaryWinChanged();
void isWinnableChanged();
private slots:
void onFoundationChanged();
void onThrowawayPileChanged();
void onMoveAmountChanged();
private:
QList<PlayingCard*> m_drawPile;
@ -75,8 +79,10 @@ class GameState : public QObject {
int m_moveAmt;
bool m_gameWon;
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);
QString generateStateHash() const;
@ -91,7 +97,7 @@ class GameState : public QObject {
bool winCheck();
bool prelimWinCheck();
std::pair<std::optional<bool>, int> checkWinnable();
std::pair<std::optional<bool>, int> canWinThroughSimulation(QSet<QString>& visitedStates, QElapsedTimer timer) const;
};