Rework various gamestate methods

This commit is contained in:
ItsDrike 2024-12-04 00:04:35 +01:00
parent 4ea9a04836
commit 4dbcd700c0
Signed by: ItsDrike
GPG key ID: FA2745890B7048C0
2 changed files with 218 additions and 107 deletions

View file

@ -5,15 +5,8 @@
GameState::GameState(QObject *parent) GameState::GameState(QObject *parent)
: QObject{parent} : QObject{parent}
{ {
// Initialize the foundation piles (4 suits) m_foundation.resize(4);
for (int i = 0; i < 4; ++i) { m_columns.resize(7);
m_foundation.append(QList<PlayingCard*>());
}
// Initialize the columns (7 piles)
for (int i = 0; i < 7; ++i) {
m_columns.append(QList<ColumnSlot*>());
}
dealCards(); dealCards();
} }
@ -46,6 +39,7 @@ void GameState::dealCards()
} }
// Use the remaining cards as the draw pile // Use the remaining cards as the draw pile
assert(index == 28);
m_drawPile = deck.mid(index); m_drawPile = deck.mid(index);
// Reset the foundation & throwaway pile // Reset the foundation & throwaway pile
@ -86,9 +80,10 @@ void GameState::drawNextCard()
emit throwawayPileChanged(); emit throwawayPileChanged();
} }
bool GameState::moveCardToColumn(int columnId) bool GameState::moveThrownCardToColumn(int columnId)
{ {
assert(columnId >= 0 && columnId < 7); assert(columnId >= 0 && columnId < 7);
auto columnStack = m_columns[columnId];
if (m_throwawayPile.isEmpty()) { if (m_throwawayPile.isEmpty()) {
qWarning() << "Attempted to move thrown card to column with empty throwaway pile"; qWarning() << "Attempted to move thrown card to column with empty throwaway pile";
@ -99,15 +94,15 @@ bool GameState::moveCardToColumn(int columnId)
PlayingCard* cardToMove = m_throwawayPile.last(); PlayingCard* cardToMove = m_throwawayPile.last();
qDebug() << "Attempting to move thrown card " << cardToMove->toString() << " to column " << columnId; qDebug() << "Attempting to move thrown card " << cardToMove->toString() << " to column " << columnId;
if (!isMoveToColumnLegal(cardToMove, columnId)) { if (!isColumnMoveValid(*cardToMove, columnId)) {
qDebug() << "> Moving aborted, illegal move"; qDebug() << "> Moving aborted, illegal move";
return false; return false;
} }
// Success, perform the move
ColumnSlot* col = new ColumnSlot(cardToMove, true); ColumnSlot* col = new ColumnSlot(cardToMove, true);
m_columns[columnId].append(col); columnStack.append(col);
m_throwawayPile.removeLast(); m_throwawayPile.removeLast();
ensureColumnRevealed(columnId);
qDebug() << "> Moving complete"; qDebug() << "> Moving complete";
emit throwawayPileChanged(); emit throwawayPileChanged();
@ -115,8 +110,11 @@ bool GameState::moveCardToColumn(int columnId)
return true; return true;
} }
bool GameState::moveThrownCardToFoundation(PlayingCard::Suit foundationId) bool GameState::moveThrownCardToFoundation(int foundationId)
{ {
assert(foundationId >= 0 && foundationId < 4);
auto foundationStack = m_foundation[foundationId];
if (m_throwawayPile.isEmpty()) { if (m_throwawayPile.isEmpty()) {
qWarning() << "Attempted to move thrown card to foundation with empty throwaway pile"; qWarning() << "Attempted to move thrown card to foundation with empty throwaway pile";
return false; return false;
@ -127,23 +125,64 @@ bool GameState::moveThrownCardToFoundation(PlayingCard::Suit foundationId)
qDebug() << "Attempting to move thrown card " << cardToMove->toString() << " to foundation " << foundationId; qDebug() << "Attempting to move thrown card " << cardToMove->toString() << " to foundation " << foundationId;
// Try moving the card into the foundation // Try moving the card into the foundation
if (!tryMoveCardToFoundation(foundationId, cardToMove)) { if (!isFoundationMoveValid(*cardToMove, foundationId)) {
qDebug() << "> Moving aborted, illegal move"; qDebug() << "> Moving aborted, illegal move";
return false; return false;
} }
// We succeeded, the card is now in the appropriate foundation pile, // Succeess, perform the move
// let's remove the card from the throwaway pile. foundationStack.prepend(cardToMove);
m_throwawayPile.removeLast(); m_throwawayPile.removeLast();
qDebug() << "> Moving complete"; qDebug() << "> Moving complete";
emit throwawayPileChanged(); emit throwawayPileChanged();
emit foundationChanged();
return true; return true;
} }
bool GameState::moveColumnCardToFoundation(int columnId, PlayingCard::Suit foundationId) bool GameState::moveColumnCardToColumn(int fromColumnId, int toColumnId, int fromCardIndex)
{
assert(fromColumnId >= 0 && fromColumnId < 7);
assert(toColumnId >= 0 && toColumnId < 7);
auto fromColumnStack = m_columns[fromColumnId];
auto toColumnStack = m_columns[toColumnId];
if (fromColumnStack.isEmpty()) {
qWarning() << "Attempted to move card(s) to column from an empty column";
return false;
}
ColumnSlot* col = fromColumnStack[fromCardIndex];
if (!col->isRevealed()) {
qWarning() << "Attempted to card(s) to column from unrevealed column slot";
return false;
}
PlayingCard* cardToMove = col->card();
qDebug() << "Attempting to move card " << cardToMove->toString() << " from column " << fromColumnId << " to column " << toColumnId;
// Try moving the card
if (!isColumnMoveValid(*cardToMove, toColumnId)) {
qDebug() << "> Moving aborted, illegal move";
return false;
}
// Success, move the card
toColumnStack.append(col);
fromColumnStack.removeLast();
ensureColumnRevealed(fromColumnId);
qDebug() << "> Moving complete";
emit columnsChanged();
return true;
}
bool GameState::moveColumnCardToFoundation(int columnId, int foundationId)
{ {
assert(columnId >= 0 && columnId < 7); assert(columnId >= 0 && columnId < 7);
assert(foundationId >= 0 && foundationId < 4);
auto columnStack = m_columns[columnId];
auto foundationStack = m_foundation[foundationId];
if (m_columns[columnId].isEmpty()) { if (m_columns[columnId].isEmpty()) {
qWarning() << "Attempted to move card to foundation from an empty column"; qWarning() << "Attempted to move card to foundation from an empty column";
@ -151,33 +190,32 @@ bool GameState::moveColumnCardToFoundation(int columnId, PlayingCard::Suit found
} }
// We'll be moving the last card in the column (maybe) // We'll be moving the last card in the column (maybe)
PlayingCard *cardToMove = m_columns[columnId].last()->card(); ColumnSlot* col = columnStack.last();
PlayingCard* cardToMove = col->card();
qDebug() << "Attempting to move card " << cardToMove->toString() << " from column " << columnId << " to foundation " << foundationId; qDebug() << "Attempting to move card " << cardToMove->toString() << " from column " << columnId << " to foundation " << foundationId;
// Try moving the card into the foundation // Try moving the card into the foundation
if (!tryMoveCardToFoundation(foundationId, cardToMove)) { if (!isFoundationMoveValid(*cardToMove, foundationId)) {
qDebug() << "> Moving aborted, illegal move"; qDebug() << "> Moving aborted, illegal move";
return false; return false;
} }
// We succeeded, the card is now in the appropriate foundation pile, // Success, move the card
// let's remove the column slot. foundationStack.prepend(cardToMove);
m_columns[columnId].removeLast(); columnStack.removeLast();
col->deleteLater();
ensureColumnRevealed(columnId); ensureColumnRevealed(columnId);
qDebug() << "> Moving complete"; qDebug() << "> Moving complete";
emit columnsChanged(); emit columnsChanged(); // CRASH (not if I remove the delete col line though)
emit foundationChanged();
return true; return true;
} }
bool GameState::autoMoveThrownCard() bool GameState::autoMoveThrownCard()
{ {
// NOTE: This method is very similar to moveThrownCardToFoundation,
// consider reducing the repetitino here somehow.
if (m_throwawayPile.isEmpty()) { if (m_throwawayPile.isEmpty()) {
// Consider raising an exception here instead
qWarning() << "Attempted to move thrown card to foundation with empty throwaway pile"; qWarning() << "Attempted to move thrown card to foundation with empty throwaway pile";
return false; return false;
} }
@ -187,48 +225,88 @@ bool GameState::autoMoveThrownCard()
qDebug() << "Attempting auto-move of thrown card " << cardToMove->toString(); qDebug() << "Attempting auto-move of thrown card " << cardToMove->toString();
// Try moving the card into the foundation // Try moving the card into the foundation
if (!tryAutoMoveCard(cardToMove)) { if (!tryAutoMoveSingleCard(*cardToMove)) {
qDebug() << "> Moving failed, no available move found"; qDebug() << "> Moving failed, no available move found";
return false; return false;
} }
// We succeeded, the card is now in the appropriate foundation pile, // We succeeded, the card was moved, remove it from throwaway pile
// let's remove the card from the throwaway pile.
m_throwawayPile.removeLast(); m_throwawayPile.removeLast();
qDebug() << "> Moving complete"; qDebug() << "> Moving complete";
emit throwawayPileChanged(); emit throwawayPileChanged();
// We don't know which pile the card was moved to, to be safe, emit
// a change from both
// NOTE: consider returning what changed from tryAutoMoveSingleCard
emit columnsChanged();
emit foundationChanged();
return true; return true;
} }
bool GameState::autoMoveColumnCard(int columnId) bool GameState::autoMoveColumnCard(int columnId, int cardIndex)
{ {
// NOTE: This method is very similar to moveColumnCardToFoundation,
// consider reducing the repetitino here somehow.
assert(columnId >= 0 && columnId < 7); assert(columnId >= 0 && columnId < 7);
auto columnStack = m_columns[columnId];
if (m_columns[columnId].isEmpty()) { if (columnStack.isEmpty()) {
// Consider raising an exception here instead qWarning() << "Attempted to move card(s) to foundation from an empty column";
qWarning() << "Attempted to move card to foundation from an empty column";
return false; return false;
} }
// We'll be moving the last card in the column (maybe) ColumnSlot* col = columnStack[cardIndex];
PlayingCard *cardToMove = m_columns[columnId].last()->card(); if (!col->isRevealed()) {
qDebug() << "Attempting auto-move of column card " << cardToMove->toString(); qWarning() << "Attempted to card(s) to column from unrevealed column slot";
return false;
}
if (!tryAutoMoveCard(cardToMove)) { if (cardIndex == columnStack.size() - 1) {
// This is a single card move (last card)
PlayingCard* cardToMove = col->card();
qDebug() << "Attempting auto-move of column " << columnId << " card " << cardToMove->toString();
if (!tryAutoMoveSingleCard(*cardToMove, columnId)) {
qDebug() << "> Moving failed, no available move found"; qDebug() << "> Moving failed, no available move found";
return false; return false;
} }
// We succeeded, the card is now in the appropriate foundation pile, // We succeeded, the card was moved, remove it from the original column
// let's remove the column slot. columnStack.removeLast();
m_columns[columnId].removeLast(); col->deleteLater();
ensureColumnRevealed(columnId); ensureColumnRevealed(columnId);
qDebug() << "> Moving complete"; qDebug() << "> Moving complete";
emit columnsChanged();
// we don't know where the card was moved, it could've been the foundation too
// to be safe, emit a change signal for it too
// NOTE: consider returning what changed from tryAutoMoveSingleCard
emit foundationChanged();
return true;
}
// This is a multiple cards move
qDebug() << "Attempting auto-move of column " << columnId << " card range " << cardIndex << " to " << columnStack.size() - 1;
QList<PlayingCard*> selectedCards;
for (int i = cardIndex; i < m_columns.size(); ++i) {
ColumnSlot* col = columnStack[i];
selectedCards.append(col->card());
}
if (!tryAutoMoveMultipleCards(selectedCards, cardIndex)) {
qDebug() << "> Moving failed, no available move found";
return false;
}
// We succeeded, the cards were moved,
// now remove the moved cards from the column
while (columnStack.size() > cardIndex) {
ColumnSlot* curSlot = columnStack.takeLast();
curSlot->deleteLater();
}
emit columnsChanged(); emit columnsChanged();
return true; return true;
} }
@ -255,26 +333,82 @@ void GameState::onFoundationChanged()
emit gameWonChanged(); emit gameWonChanged();
} }
bool GameState::tryMoveCardToFoundation(PlayingCard::Suit foundationId, PlayingCard* cardToMove) bool GameState::tryAutoMoveSingleCard(PlayingCard &cardToMove, int skipColumnId)
{ {
assert(foundationId >= PlayingCard::Suit::Clubs && foundationId <= PlayingCard::Suit::Spades); // 1. Try moving the card to the foundation
const int foundationId = static_cast<int>(cardToMove.suit());
if (isFoundationMoveValid(cardToMove, foundationId)) {
m_foundation[foundationId].prepend(&cardToMove);
qDebug() << "* Auto-moved card " << cardToMove.toString() << " to foundation " << foundationId;
return true;
}
qDebug() << "* Trying to move card " << cardToMove->toString() << " to foundation " << foundationId; // 2. Try moving the card to another column
for (int columnId = 0; columnId < m_columns.size(); ++columnId) {
if (columnId == skipColumnId)
continue;
if (cardToMove->suit() != foundationId) { if (isColumnMoveValid(cardToMove, columnId)) {
qDebug() << "* Move attempt failed (wrong suit)"; ColumnSlot* col = new ColumnSlot(&cardToMove, true);
m_columns[columnId].append(col);
qDebug() << "* Auto-moved card " << cardToMove.toString() << " to column " << columnId;
return true;
}
}
// No available auto-move
qDebug() << "* Auto-move failed, no available moves";
return false;
}
bool GameState::tryAutoMoveMultipleCards(const QList<PlayingCard*>& cards, int skipColumnId)
{
assert(cards.size() > 1);
// If we can move the first (selected) card to another column,
// we can also move the rest of the cards below to that column,
// so we only need to care about the first card.
// (Foundation moves are impossible with multiple card movements).
PlayingCard* firstCard = cards.first();
for (int columnId = 0; columnId < m_columns.size(); ++columnId) {
if (columnId == skipColumnId)
continue;
if (isColumnMoveValid(*firstCard, columnId)) {
for (auto card : cards) {
ColumnSlot* col = new ColumnSlot(card, true);
m_columns[columnId].append(col);
qDebug() << "* Auto-moved card " << card->toString() << " to column " << columnId;
}
return true;
}
}
qDebug() << "* Auto-move failed, no available moves";
return false;
}
bool GameState::isFoundationMoveValid(const PlayingCard& cardToMove, int foundationId)
{
assert(foundationId >= 0 && foundationId < 4);
const auto foundationSuit = static_cast<PlayingCard::Suit>(foundationId);
const auto foundationStack = m_foundation[foundationId];
// The card must match the suit of the foundation
if (cardToMove.suit() != foundationSuit) {
return false; return false;
} }
PlayingCard::Value requiredValue; PlayingCard::Value requiredValue;
if (m_foundation[foundationId].isEmpty()) { if (foundationStack.isEmpty()) {
// If the pile is empty, only an ace can go in // If the pile is empty, only an ace can go in
requiredValue = PlayingCard::Value::Ace; requiredValue = PlayingCard::Value::Ace;
} else { } else {
// Otherwise it's the next card by value, unless we're already at king // Otherwise it's the next card by value, unless we're already at king
PlayingCard::Value curValue = m_foundation[foundationId].first()->value(); PlayingCard::Value curValue = foundationStack.first()->value();
if (curValue == PlayingCard::Value::King) { if (curValue == PlayingCard::Value::King) {
qDebug() << "* Move attempt failed (expected King)";
return false; return false;
} }
@ -283,59 +417,32 @@ bool GameState::tryMoveCardToFoundation(PlayingCard::Suit foundationId, PlayingC
requiredValue = static_cast<PlayingCard::Value>(static_cast<int>(curValue) + 1); requiredValue = static_cast<PlayingCard::Value>(static_cast<int>(curValue) + 1);
} }
if (cardToMove->value() != requiredValue) { return cardToMove.value() == requiredValue;
qDebug() << "* Move attempt failed (expected value: " << requiredValue << ")";
return false;
} }
m_foundation[foundationId].push_front(cardToMove); bool GameState::isColumnMoveValid(const PlayingCard& cardToMove, int columnId) {
qDebug() << "* Moved card " << cardToMove->toString() << " to foundation " << foundationId;
emit foundationChanged();
return true;
}
bool GameState::tryAutoMoveCard(PlayingCard *cardToMove)
{
// 1. Try moving the card to the foundation
for (PlayingCard::Suit suit : {PlayingCard::Suit::Clubs, PlayingCard::Suit::Diamonds, PlayingCard::Suit::Hearts, PlayingCard::Suit::Spades})
if (cardToMove->suit() == suit && tryMoveCardToFoundation(suit, cardToMove)) {
qDebug() << "* Auto-moved card " << cardToMove->toString() << " to foundation " << suit;
return true;
}
// 2. Try moving the card to another column
for (int columnId = 0; columnId < m_columns.size(); ++columnId)
if (isMoveToColumnLegal(cardToMove, columnId)) {
moveCardToColumn(columnId);
qDebug() << "* Auto-moved card " << cardToMove->toString() << " to column " << columnId;
return true;
}
// No available auto-move
return false;
}
bool GameState::isMoveToColumnLegal(PlayingCard *cardToMove, int columnId)
{
assert(columnId >= 0 && columnId < 7); assert(columnId >= 0 && columnId < 7);
const auto columnStack = m_columns[columnId];
if (m_columns[columnId].isEmpty()) { if (columnStack.isEmpty()) {
// Column is empty: only a King can be placed in an empty column // Column is empty: only a King can be placed in an empty column
return cardToMove->value() == PlayingCard::Value::King; return cardToMove.value() == PlayingCard::Value::King;
} }
// Compare against the last card in the column // Compare against the last card in the column
PlayingCard* columnCard = m_columns[columnId].last()->card(); const PlayingCard& columnCard = *columnStack.last()->card();
// The card's value must be one less than the card in the column // The card's value must be one less than the card in the column
if (cardToMove->value() != columnCard->value() - 1) if (cardToMove.value() != columnCard.value() - 1) {
qDebug() << "* Move attempt failed (wrong value)";
return false; return false;
}
// The card must be of opposite color // The card must be of opposite color
return PlayingCard::areOppositeColors(*cardToMove, *columnCard); return PlayingCard::areOppositeColors(cardToMove, columnCard);
} }
void GameState::ensureColumnRevealed(int columnId) void GameState::ensureColumnRevealed(int columnId)
{ {
assert(columnId >= 0 && columnId < 7); assert(columnId >= 0 && columnId < 7);

View file

@ -28,17 +28,18 @@ public:
bool gameWon() const; bool gameWon() const;
// General functions // General functions
void dealCards(); Q_INVOKABLE void dealCards();
void drawNextCard(); Q_INVOKABLE void drawNextCard();
// Manual moves (from X to Y) // Manual moves (from X to Y)
bool moveCardToColumn(int columnId); Q_INVOKABLE bool moveThrownCardToColumn(int columnId);
bool moveThrownCardToFoundation(PlayingCard::Suit foundationId); Q_INVOKABLE bool moveThrownCardToFoundation(int foundationId);
bool moveColumnCardToFoundation(int columnId, PlayingCard::Suit foundationId); Q_INVOKABLE bool moveColumnCardToColumn(int fromColumnId, int toColumnId, int fromCardIndex);
Q_INVOKABLE bool moveColumnCardToFoundation(int columnId, int foundationId);
// Automatic moves (from X to auto) // Automatic moves (from X to auto)
bool autoMoveThrownCard(); Q_INVOKABLE bool autoMoveThrownCard();
bool autoMoveColumnCard(int columnId); Q_INVOKABLE bool autoMoveColumnCard(int columnId, int cardIndex);
signals: signals:
void drawPileChanged(); void drawPileChanged();
@ -57,9 +58,12 @@ private:
QList<QList<PlayingCard*>> m_foundation; QList<QList<PlayingCard*>> m_foundation;
bool m_gameWon; bool m_gameWon;
bool tryMoveCardToFoundation(PlayingCard::Suit foundationId, PlayingCard* cardToMove); bool tryAutoMoveSingleCard(PlayingCard& cardToMove, int skipColumnId = -1);
bool tryAutoMoveCard(PlayingCard* cardToMove); bool tryAutoMoveMultipleCards(const QList<PlayingCard*>& cards, int skipColumnId);
bool isMoveToColumnLegal(PlayingCard* cardToMove, int columnId);
bool isFoundationMoveValid(const PlayingCard& cardToMove, int foundationId);
bool isColumnMoveValid(const PlayingCard& cardToMove, int columnId);
void ensureColumnRevealed(int columnId); void ensureColumnRevealed(int columnId);
}; };