bool Engine::playCurrentRound() { #define CHECK_QUIT \ if (mQuit.get()) { \ return false; \ } lock(); if (!mCurrentRoundIndex) { mCurrentRoundIndex = &mRoundIndex; // prepare round data // pick current player as first attacker mAttackers.push_back(mCurrentPlayer); // if there was no deal yet (very first round) - do not consider cards while picking next players std::map<const PlayerId*, CardSet>* cards = *mCurrentRoundIndex ? &mPlayersCards : NULL; // pick next player as defender mDefender = Rules::pickNext(mGeneratedIds, mCurrentPlayer, cards); // gather rest players as additional attackers const PlayerId* attacker = mDefender; while((attacker = Rules::pickNext(mGeneratedIds, attacker, cards)) != mCurrentPlayer) { if (attacker) { mAttackers.push_back(attacker); } } unlock(); std::for_each(mGameObservers.begin(), mGameObservers.end(), RoundStartNotification(mAttackers, mDefender, mRoundIndex)); // deal cards dealCards(); lock(); mMaxAttackCards = Rules::maxAttackCards(mPlayersCards[mDefender].size()); assert(mMaxAttackCards); } CardSet& defenderCards = mPlayersCards[mDefender]; if (!mCurrentRoundAttackerId) { mCurrentRoundAttackerId = mAttackers[0]; mPassedCounter = 0; } Player& defender = *mPlayers[mDefender]; unlock(); for (;;) { if (mTableCards.attackCards().size() == mMaxAttackCards) { // defender has no more cards - defend succeeded break; } const Card* attackCardPtr; if (mPickAttackCardFromTable) { assert(!mTableCards.attackCards().empty()); attackCardPtr = &*(mTableCards.attackCards().end() - 1); } else { CardSet attackCards = Rules::getAttackCards(mTableCards.all(), mPlayersCards[mCurrentRoundAttackerId]); Player& currentAttacker = *mPlayers[mCurrentRoundAttackerId]; if (mTableCards.empty()) { attackCardPtr = attackCards.empty() ? NULL : ¤tAttacker.attack(mDefender, attackCards); } else { // ask for pitch even with empty attackCards - expected NULL attack card pointer attackCardPtr = currentAttacker.pitch(mDefender, attackCards); } // check if quit requested and only after that transfer move to defender CHECK_QUIT; if (attackCards.empty() || !attackCardPtr) { lock(); // player skipped the move - pick next attacker mCurrentRoundAttackerId = Rules::pickNext(mAttackers, mCurrentRoundAttackerId, &mPlayersCards); // if more than one attacker and we have first attacker again - reset pass counter if (mAttackers.size() > 1 && mCurrentRoundAttackerId == mAttackers[0]) { mPassedCounter = 0; } mPassedCounter++; unlock(); if (mPassedCounter == mAttackers.size()) { // all attackers "passed" - round ended break; } continue; } assert(attackCardPtr); if(!findByPtr(attackCards, attackCardPtr)) { // invalid card returned - the card is not from attackCards assert(!attackCards.empty()); // take any card attackCardPtr = &*attackCards.begin(); } Card attackCard = *attackCardPtr; lock(); mTableCards.addAttackCard(attackCard); mPlayersCards[mCurrentRoundAttackerId].erase(attackCard); unlock(); CHECK_QUIT; std::for_each(mGameObservers.begin(), mGameObservers.end(), CardsDroppedNotification(mCurrentRoundAttackerId, attackCard)); // the card is removed from the `attackCards` and is added to `mTableCards`, so update its pointer attackCardPtr = &*std::find(mTableCards.attackCards().begin(), mTableCards.attackCards().end(), attackCard); } if (mDefendFailed) { continue; } CardSet defendCards = Rules::getDefendCards(*attackCardPtr, defenderCards, mDeck->trumpSuit()); const Card* defendCardPtr = defender.defend(mCurrentRoundAttackerId, *attackCardPtr, defendCards); bool noCardsToDefend = defendCards.empty(); bool userGrabbedCards = !defendCardPtr; bool invalidDefendCard = !findByPtr(defendCards, defendCardPtr); lock(); mPickAttackCardFromTable = false; unlock(); if(noCardsToDefend || userGrabbedCards || invalidDefendCard) { // defend failed lock(); mDefendFailed = true; unlock(); } else { lock(); mTableCards.addDefendCard(*defendCardPtr); defenderCards.erase(*defendCardPtr); unlock(); CHECK_QUIT; std::for_each(mGameObservers.begin(), mGameObservers.end(), CardsDroppedNotification(mDefender, *defendCardPtr)); } } if (mDefendFailed) { lock(); defenderCards.insert(mTableCards.all().begin(), mTableCards.all().end()); defender.cardsUpdated(defenderCards); unlock(); CHECK_QUIT; std::for_each(mGameObservers.begin(), mGameObservers.end(), CardsReceivedNotification(mDefender, mTableCards.all())); } else { std::for_each(mGameObservers.begin(), mGameObservers.end(), CardsGoneNotification(mTableCards.all())); } // cleanup lock(); mCurrentRoundAttackerId = NULL; mMaxAttackCards = 0; mCurrentRoundIndex = NULL; unlock(); CHECK_QUIT; std::for_each(mGameObservers.begin(), mGameObservers.end(), RoundEndNotification(mRoundIndex)); return !mDefendFailed; }