diff --git a/gamestate.cpp b/gamestate.cpp index 0e882cc..ed7e49a 100644 --- a/gamestate.cpp +++ b/gamestate.cpp @@ -5,15 +5,8 @@ GameState::GameState(QObject *parent) : QObject{parent} { - // Initialize the foundation piles (4 suits) - for (int i = 0; i < 4; ++i) { - m_foundation.append(QList()); - } - - // Initialize the columns (7 piles) - for (int i = 0; i < 7; ++i) { - m_columns.append(QList()); - } + m_foundation.resize(4); + m_columns.resize(7); dealCards(); } @@ -46,6 +39,7 @@ void GameState::dealCards() } // Use the remaining cards as the draw pile + assert(index == 28); m_drawPile = deck.mid(index); // Reset the foundation & throwaway pile @@ -86,9 +80,10 @@ void GameState::drawNextCard() emit throwawayPileChanged(); } -bool GameState::moveCardToColumn(int columnId) +bool GameState::moveThrownCardToColumn(int columnId) { assert(columnId >= 0 && columnId < 7); + auto columnStack = m_columns[columnId]; if (m_throwawayPile.isEmpty()) { qWarning() << "Attempted to move thrown card to column with empty throwaway pile"; @@ -96,18 +91,18 @@ bool GameState::moveCardToColumn(int columnId) } // We'll be moving the last card in the throwaway pile (maybe) - PlayingCard *cardToMove = m_throwawayPile.last(); + PlayingCard* cardToMove = m_throwawayPile.last(); qDebug() << "Attempting to move thrown card " << cardToMove->toString() << " to column " << columnId; - if (!isMoveToColumnLegal(cardToMove, columnId)) { + if (!isColumnMoveValid(*cardToMove, columnId)) { qDebug() << "> Moving aborted, illegal move"; return false; } - ColumnSlot *col = new ColumnSlot(cardToMove, true); - m_columns[columnId].append(col); + // Success, perform the move + ColumnSlot* col = new ColumnSlot(cardToMove, true); + columnStack.append(col); m_throwawayPile.removeLast(); - ensureColumnRevealed(columnId); qDebug() << "> Moving complete"; emit throwawayPileChanged(); @@ -115,35 +110,79 @@ bool GameState::moveCardToColumn(int columnId) 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()) { qWarning() << "Attempted to move thrown card to foundation with empty throwaway pile"; return false; } // We'll be moving the last card in the foundation pile (maybe) - PlayingCard *cardToMove = m_throwawayPile.last(); + PlayingCard* cardToMove = m_throwawayPile.last(); qDebug() << "Attempting to move thrown card " << cardToMove->toString() << " to foundation " << foundationId; // Try moving the card into the foundation - if (!tryMoveCardToFoundation(foundationId, cardToMove)) { + if (!isFoundationMoveValid(*cardToMove, foundationId)) { qDebug() << "> Moving aborted, illegal move"; return false; } - // We succeeded, the card is now in the appropriate foundation pile, - // let's remove the card from the throwaway pile. + // Succeess, perform the move + foundationStack.prepend(cardToMove); m_throwawayPile.removeLast(); qDebug() << "> Moving complete"; emit throwawayPileChanged(); + emit foundationChanged(); 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(foundationId >= 0 && foundationId < 4); + auto columnStack = m_columns[columnId]; + auto foundationStack = m_foundation[foundationId]; if (m_columns[columnId].isEmpty()) { qWarning() << "Attempted to move card to foundation from an empty column"; @@ -151,83 +190,122 @@ bool GameState::moveColumnCardToFoundation(int columnId, PlayingCard::Suit found } // 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; // Try moving the card into the foundation - if (!tryMoveCardToFoundation(foundationId, cardToMove)) { + if (!isFoundationMoveValid(*cardToMove, foundationId)) { qDebug() << "> Moving aborted, illegal move"; return false; } - // We succeeded, the card is now in the appropriate foundation pile, - // let's remove the column slot. - m_columns[columnId].removeLast(); + // Success, move the card + foundationStack.prepend(cardToMove); + columnStack.removeLast(); + col->deleteLater(); ensureColumnRevealed(columnId); qDebug() << "> Moving complete"; - emit columnsChanged(); + emit columnsChanged(); // CRASH (not if I remove the delete col line though) + emit foundationChanged(); return true; } bool GameState::autoMoveThrownCard() { - // NOTE: This method is very similar to moveThrownCardToFoundation, - // consider reducing the repetitino here somehow. - if (m_throwawayPile.isEmpty()) { - // Consider raising an exception here instead qWarning() << "Attempted to move thrown card to foundation with empty throwaway pile"; return false; } // We'll be moving the last card in the foundation pile (maybe) - PlayingCard *cardToMove = m_throwawayPile.last(); + PlayingCard* cardToMove = m_throwawayPile.last(); qDebug() << "Attempting auto-move of thrown card " << cardToMove->toString(); // Try moving the card into the foundation - if (!tryAutoMoveCard(cardToMove)) { + if (!tryAutoMoveSingleCard(*cardToMove)) { qDebug() << "> Moving failed, no available move found"; return false; } - // We succeeded, the card is now in the appropriate foundation pile, - // let's remove the card from the throwaway pile. + // We succeeded, the card was moved, remove it from throwaway pile m_throwawayPile.removeLast(); qDebug() << "> Moving complete"; 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; } -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); + auto columnStack = m_columns[columnId]; - if (m_columns[columnId].isEmpty()) { - // Consider raising an exception here instead - qWarning() << "Attempted to move card to foundation from an empty column"; + if (columnStack.isEmpty()) { + qWarning() << "Attempted to move card(s) to foundation from an empty column"; return false; } - // We'll be moving the last card in the column (maybe) - PlayingCard *cardToMove = m_columns[columnId].last()->card(); - qDebug() << "Attempting auto-move of column card " << cardToMove->toString(); + ColumnSlot* col = columnStack[cardIndex]; + if (!col->isRevealed()) { + 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"; + return false; + } + + // We succeeded, the card was moved, remove it from the original column + columnStack.removeLast(); + col->deleteLater(); + ensureColumnRevealed(columnId); + 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 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 card is now in the appropriate foundation pile, - // let's remove the column slot. - m_columns[columnId].removeLast(); - ensureColumnRevealed(columnId); - qDebug() << "> Moving complete"; + // 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(); return true; @@ -255,26 +333,82 @@ void GameState::onFoundationChanged() 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(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) { - qDebug() << "* Move attempt failed (wrong suit)"; + if (isColumnMoveValid(cardToMove, columnId)) { + 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& 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(foundationId); + const auto foundationStack = m_foundation[foundationId]; + + // The card must match the suit of the foundation + if (cardToMove.suit() != foundationSuit) { return false; } PlayingCard::Value requiredValue; - if (m_foundation[foundationId].isEmpty()) { + if (foundationStack.isEmpty()) { // If the pile is empty, only an ace can go in requiredValue = PlayingCard::Value::Ace; } else { // 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) { - qDebug() << "* Move attempt failed (expected King)"; return false; } @@ -283,59 +417,32 @@ bool GameState::tryMoveCardToFoundation(PlayingCard::Suit foundationId, PlayingC requiredValue = static_cast(static_cast(curValue) + 1); } - if (cardToMove->value() != requiredValue) { - qDebug() << "* Move attempt failed (expected value: " << requiredValue << ")"; - return false; - } - - m_foundation[foundationId].push_front(cardToMove); - qDebug() << "* Moved card " << cardToMove->toString() << " to foundation " << foundationId; - - emit foundationChanged(); - return true; + return cardToMove.value() == requiredValue; } -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) -{ +bool GameState::isColumnMoveValid(const PlayingCard& cardToMove, int columnId) { 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 - return cardToMove->value() == PlayingCard::Value::King; + return cardToMove.value() == PlayingCard::Value::King; } // 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 - if (cardToMove->value() != columnCard->value() - 1) + if (cardToMove.value() != columnCard.value() - 1) { + qDebug() << "* Move attempt failed (wrong value)"; return false; + } // The card must be of opposite color - return PlayingCard::areOppositeColors(*cardToMove, *columnCard); + return PlayingCard::areOppositeColors(cardToMove, columnCard); } + void GameState::ensureColumnRevealed(int columnId) { assert(columnId >= 0 && columnId < 7); diff --git a/gamestate.h b/gamestate.h index e34688b..aa9aee7 100644 --- a/gamestate.h +++ b/gamestate.h @@ -28,17 +28,18 @@ public: bool gameWon() const; // General functions - void dealCards(); - void drawNextCard(); + Q_INVOKABLE void dealCards(); + Q_INVOKABLE void drawNextCard(); // Manual moves (from X to Y) - bool moveCardToColumn(int columnId); - bool moveThrownCardToFoundation(PlayingCard::Suit foundationId); - bool moveColumnCardToFoundation(int columnId, PlayingCard::Suit foundationId); + Q_INVOKABLE bool moveThrownCardToColumn(int columnId); + Q_INVOKABLE bool moveThrownCardToFoundation(int 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) - bool autoMoveThrownCard(); - bool autoMoveColumnCard(int columnId); + Q_INVOKABLE bool autoMoveThrownCard(); + Q_INVOKABLE bool autoMoveColumnCard(int columnId, int cardIndex); signals: void drawPileChanged(); @@ -57,9 +58,12 @@ private: QList> m_foundation; bool m_gameWon; - bool tryMoveCardToFoundation(PlayingCard::Suit foundationId, PlayingCard* cardToMove); - bool tryAutoMoveCard(PlayingCard* cardToMove); - bool isMoveToColumnLegal(PlayingCard* cardToMove, int columnId); + bool tryAutoMoveSingleCard(PlayingCard& cardToMove, int skipColumnId = -1); + bool tryAutoMoveMultipleCards(const QList& cards, int skipColumnId); + + bool isFoundationMoveValid(const PlayingCard& cardToMove, int foundationId); + bool isColumnMoveValid(const PlayingCard& cardToMove, int columnId); + void ensureColumnRevealed(int columnId); };