#include "Main.h" const int debugResourceBonus = 0; const int debugRandomCardCount = 0; const char *debugCardName = "gambler"; const int debugCardCount = 0; bool DecisionState::IsTrivial() const { if(type == DecisionSelectCards && cardChoices.Length() == 1 && minimumCards == 1 && maximumCards == 1) { return true; } return false; } DecisionResponse DecisionState::TrivialResponse() const { DecisionResponse response; if(cardChoices.Length() == 1 && minimumCards == 1 && maximumCards == 1) { response.cards.Allocate(1); response.cards[0] = cardChoices[0]; } return response; } void DecisionState::GainCardFromSupply(State &s, Card *c, int minCost, int maxCost, CardFilter filter) { SelectCards(c, 1, 1); if(decisionText) text = "Select a card to gain:"; for(UINT supplyIndex = 0; supplyIndex < s.data->supplyCards.Length(); supplyIndex++) { int cost = s.SupplyCost(supplyIndex); int count = s.supply[supplyIndex].count; Card *candidate = s.data->supplyCards[supplyIndex]; bool cardAcceptable = (count > 0 && cost >= minCost && cost <= maxCost); if(filter == FilterTreasure) cardAcceptable &= candidate->isTreasure; if(cardAcceptable) { AddUniqueCard(candidate); } } } void DecisionState::GainTreasureFromSupply(State &s, Card *c, int minCost, int maxCost) { SelectCards(c, 1, 1); if(decisionText) text = "Select a treasure to gain:"; for(UINT supplyIndex = 0; supplyIndex < s.data->supplyCards.Length(); supplyIndex++) { int cost = s.SupplyCost(supplyIndex); int count = s.supply[supplyIndex].count; if(count > 0 && cost >= minCost && cost <= maxCost && s.data->supplyCards[supplyIndex]->isTreasure) { AddUniqueCard(s.data->supplyCards[supplyIndex]); } } } void DecisionState::GainVictoryFromSupply(State &s, Card *c, int minCost, int maxCost) { SelectCards(c, 1, 1); if(decisionText) text = "Select a treasure to gain:"; for(UINT supplyIndex = 0; supplyIndex < s.data->supplyCards.Length(); supplyIndex++) { int cost = s.SupplyCost(supplyIndex); int count = s.supply[supplyIndex].count; if(count > 0 && cost >= minCost && cost <= maxCost && s.data->supplyCards[supplyIndex]->isVictory) { AddUniqueCard(s.data->supplyCards[supplyIndex]); } } } void PlayerState::NewGame(const GameData &data) { playArea.FreeMemory(); discard.FreeMemory(); hand.FreeMemory(); deck.FreeMemory(); islandZone.FreeMemory(); if(data.options.startingCondition == StartingCondition34Split) { for(UINT copperIndex = 0; copperIndex < 3; copperIndex++) deck.PushEnd(data.cards->GetCard("copper")); for(UINT estateIndex = 0; estateIndex < 2; estateIndex++) deck.PushEnd(data.cards->GetCard("estate")); for(UINT copperIndex = 0; copperIndex < 4; copperIndex++) deck.PushEnd(data.cards->GetCard("copper")); for(UINT estateIndex = 0; estateIndex < 1; estateIndex++) deck.PushEnd(data.cards->GetCard("estate")); } else if(data.options.startingCondition == StartingCondition25Split) { for(UINT copperIndex = 0; copperIndex < 2; copperIndex++) deck.PushEnd(data.cards->GetCard("copper")); for(UINT estateIndex = 0; estateIndex < 3; estateIndex++) deck.PushEnd(data.cards->GetCard("estate")); for(UINT copperIndex = 0; copperIndex < 5; copperIndex++) deck.PushEnd(data.cards->GetCard("copper")); } else { for(UINT copperIndex = 0; copperIndex < 7; copperIndex++) deck.PushEnd(data.cards->GetCard("copper")); for(UINT estateIndex = 0; estateIndex < 3; estateIndex++) deck.PushEnd(data.cards->GetCard("estate")); deck.Randomize(); } for(UINT debugCardIndex = 0; debugCardIndex < debugCardCount; debugCardIndex++) deck.PushEnd(data.cards->GetCard(debugCardName)); //deck.PushEnd(data.cards->GetCard("platinum")); //deck.PushEnd(data.cards->GetCard("silver")); //deck.PushEnd(data.cards->GetCard("silver")); //deck.PushEnd(data.cards->GetCard("copper")); //deck.PushEnd(data.cards->GetCard("copper")); for(UINT randomCards = 0; randomCards < debugRandomCardCount; randomCards++) deck.PushEnd(data.cards->RandomSupplyCard()); //deck.PushEnd(data.cards->GetCard("mine")); actions = 1 + debugResourceBonus; buys = 1 + debugResourceBonus; money = 0 + debugResourceBonus; VPTokens = 0; turns = 0; } int PlayerState::TotalCards() const { return deck.Length() + hand.Length() + discard.Length() + playArea.Length() + islandZone.Length(); } int PlayerState::ActionCount() const { int result = 0; for(Card *c : hand) if(c->isAction) result++; return result; } int PlayerState::TreasureCount() const { int result = 0; for(Card *c : hand) if(c->isTreasure) result++; return result; } int PlayerState::VictoryCount() const { int result = 0; for(Card *c : hand) if(c->isVictory) result++; return result; } int PlayerState::MoneyTotal() const { int result = money; for(Card *c : hand) if(c->isTreasure) result += c->treasure; return result; } /*map PlayerState::CardCounts() const { map result; for(Card *c : hand ) if(result.find(c) == result.end()) result[c] = 1; else result[c]++; for(Card *c : deck ) if(result.find(c) == result.end()) result[c] = 1; else result[c]++; for(Card *c : discard ) if(result.find(c) == result.end()) result[c] = 1; else result[c]++; for(Card *c : islandZone ) if(result.find(c) == result.end()) result[c] = 1; else result[c]++; for(CardPlayInfo c : playArea) if(result.find(c.card) == result.end()) result[c.card] = 1; else result[c.card]++; return result; }*/ UINT State::SupplyCost(int supplyIndex) const { return SupplyCost(data->supplyCards[supplyIndex]); } UINT State::SupplyCost(Card *c) const { int cost = c->cost; if(c == data->baseCards.peddler && phase == PhaseBuy) { int actionCardCount = 0; for(const CardPlayInfo &c : players[player].playArea) if(c.card->isAction) actionCardCount++; cost = Math::Max(0, cost - actionCardCount * 2); } if(data->bridgeInSupply) { const Vector &playArea = players[player].playArea; for(UINT playIndex = 0; playIndex < playArea.Length(); playIndex++) { if(playArea[playIndex].card == data->baseCards.bridge) cost -= playArea[playIndex].copies; } if(cost < 0) cost = 0; } if(data->quarryInSupply) { const Vector &playArea = players[player].playArea; for(UINT playIndex = 0; playIndex < playArea.Length(); playIndex++) { if(c->isAction && playArea[playIndex].card == data->baseCards.quarry) cost -= 2; } if(cost < 0) cost = 0; } if(data->plunderInSupply) { const Vector &playArea = players[player].playArea; for(UINT playIndex = 0; playIndex < playArea.Length(); playIndex++) { if(c->isTreasure && playArea[playIndex].card == data->baseCards.plunder) cost -= 2; } if(cost < 0) cost = 0; } return cost; } UINT State::SupplyCount(Card *c) const { int supplyIndex = data->SupplyIndex(c); if(supplyIndex == -1) return 0; return supply[supplyIndex].count; } void State::NewGame(const GameData &_data) { data = &_data; const UINT playerCount = data->players.Length(); for(UINT playerIndex = 0; playerIndex < playerCount; playerIndex++) { players[playerIndex].NewGame(*data); DrawCards(playerIndex, 5); } player = rand() % playerCount; players[player].turns = 1; phase = PhaseAction; stack.FreeMemory(); gainList.FreeMemory(); prevGainList.FreeMemory(); if(logging) { data->log.Reset(); data->log("New Game"); data->log("Players: " + data->players[0].name + ", " + data->players[1].name); //data->log(data->players[player].name + " goes first"); data->log("< " + data->players[player].name + "'s turn -- Turn 1>"); } int victoryCardCount = 8; if(playerCount >= 3) { victoryCardCount = 12; } tradeRouteValue = 0; for(UINT supplyIndex = 0; supplyIndex < maxSupply; supplyIndex++) supply[supplyIndex].count = 0; for(UINT supplyIndex = 0; supplyIndex < data->supplyCards.Length(); supplyIndex++) { Card *c = data->supplyCards[supplyIndex]; if(c->isVictory) { supply[supplyIndex].count = victoryCardCount; supply[supplyIndex].tradeRouteToken = 1; } else { supply[supplyIndex].count = data->supplyCards[supplyIndex]->supply; supply[supplyIndex].tradeRouteToken = 0; } } supply[data->SupplyIndex(data->baseCards.copper)].count = 60 - 7 * playerCount; supply[data->SupplyIndex(data->baseCards.curse)].count = 10 * (playerCount - 1); decision.type = DecisionNone; decision.text.FreeMemory(); AdvanceToNextDecision(0); } void State::Shuffle(UINT playerIndex) { if(logging) Log(playerIndex, "shuffles"); PlayerState &curPlayer = players[playerIndex]; curPlayer.deck.Append(curPlayer.discard); curPlayer.discard.FreeMemory(); curPlayer.deck.Randomize(); } void State::DrawCards(UINT playerIndex, UINT cardCount) { for(UINT cardIndex = 0; cardIndex < cardCount; cardIndex++) DrawCard(playerIndex); } Card* State::DrawCard(UINT playerIndex) { PlayerState &curPlayer = players[playerIndex]; if(curPlayer.deck.Length() == 0) { Shuffle(playerIndex); } if(curPlayer.deck.Length() > 0) { Card *c = curPlayer.deck.Last(); if(logging) LogIndent(1, playerIndex, "draws " + c->PrettyName()); curPlayer.hand.PushEnd(c); curPlayer.deck.PopEnd(); return c; } else { if(logging) Log(playerIndex, "tries to draw, but has no cards left"); return NULL; } } void State::DiscardCard(UINT playerIndex, Card *c) { PlayerState &p = players[playerIndex]; if(logging) Log(playerIndex, "discards " + c->PrettyName()); int index = p.hand.FindFirstIndex(c); p.hand.RemoveSwap(index); p.discard.PushEnd(c); } void State::PlayCard(UINT playerIndex, Card *c) { PlayerState &p = players[playerIndex]; if(logging) Log(playerIndex, "plays " + c->PrettyName()); int index = p.hand.FindFirstIndex(c); p.hand.RemoveSwap(index); int turnsLeft = 0; if(c->isDuration) turnsLeft = 1; if(c->isPermanent) turnsLeft = 999; p.playArea.PushEnd(CardPlayInfo(c, turnsLeft)); } void State::ReorderDeck(Card *source, UINT playerIndex, UINT cardCount) { for(UINT reorderIndex = 2; reorderIndex <= cardCount; reorderIndex++) { stack.PushEnd(new EventReorderDeck(source, playerIndex, reorderIndex)); } } void State::ProcessAction(Card *c) { PlayerState &p = players[player]; p.actions += c->actions; p.buys += c->buys; p.money += c->money; p.VPTokens += c->victoryTokens; if(logging) { if(c->victoryTokens > 0) LogIndent(1, "gains " + String(c->victoryTokens) + " VP"); if(c->victoryTokens < 0) LogIndent(1, "loses " + String(c->victoryTokens) + " VP"); if(c->money > 0) LogIndent(1, "gets $" + String(c->money)); if(c->money < 0) LogIndent(1, "pays $" + String(-c->money)); } for(int cardIndex = 0; cardIndex < c->cards; cardIndex++) { DrawCard(player); } if(c->isAttack && data->championInSupply) { for(const PlayerInfo &p : data->players) { if(p.index != player) { for(CardPlayInfo &c : players[p.index].playArea) { if(c.card == data->baseCards.champion) { for(int copyIndex = 0; copyIndex < c.copies; copyIndex++) { if(logging) LogIndent(1, p.index, "gains 1 VP from " + data->baseCards.champion->PrettyName()); players[p.index].VPTokens++; } } } } } } if(c->effect != NULL) { c->effect->PlayAction(*this); } } void State::ProcessTreasure(Card *c) { PlayerState &p = players[player]; int treasureValue = c->treasure; if(data->coppersmithInSupply) { const Vector &playArea = players[player].playArea; for(UINT playIndex = 0; playIndex < playArea.Length(); playIndex++) { if(playArea[playIndex].card == data->baseCards.coppersmith) treasureValue += playArea[playIndex].copies; } } if(c == data->baseCards.bank) { for(const CardPlayInfo &c : players[player].playArea) if(c.card->isTreasure) treasureValue++; } if(logging) LogIndent(1, "gets $" + String(treasureValue)); p.money += treasureValue; p.buys += c->buys; if(c->effect != NULL) { c->effect->PlayAction(*this); } } void State::ProcessDecision(const DecisionResponse &response) { if(decision.type == DecisionGameOver) return; Assert(decision.type != DecisionNone, "No decision active"); Card *singleCard = response.singleCard; if(decision.type == DecisionSelectCards && decision.maximumCards <= 1) { if(response.cards.Length() == 1) singleCard = response.cards[0]; Assert(response.cards.Length() <= 1, "Invalid number of cards in response"); Assert(decision.minimumCards == 0 || singleCard != NULL, "No response chosen"); } else { Assert(decision.type != DecisionSelectCards || (response.cards.Length() >= decision.minimumCards && response.cards.Length() <= decision.maximumCards), "Invalid number of cards in response"); Assert(decision.type != DecisionDiscreteChoice || (response.choice < decision.minimumCards), "Invalid choice"); } // // Reset the current decision. Note that some cards may still spawn another decision // before this function returns. // decision.type = DecisionNone; PlayerState &p = players[player]; if(decision.activeCard == NULL) { if(phase == PhaseAction) { if(singleCard == NULL) { if(logging) Log("chooses not to play an action"); phase = PhaseTreasure; } else { PlayCard(player, singleCard); p.actions--; ProcessAction(singleCard); } } else if(phase == PhaseTreasure) { if(singleCard == NULL) { if(logging) Log("chooses not to play a teasure"); phase = PhaseBuy; BuyPhaseStart(); } else { PlayCard(player, singleCard); ProcessTreasure(singleCard); } } else if(phase == PhaseBuy) { if(singleCard == NULL) { if(logging) Log("chooses not to buy a card"); phase = PhaseCleanup; } else { data->players[player].ledger.RecordBuy(singleCard); if(data->hoardInSupply && singleCard->isVictory) { const Vector &playArea = players[player].playArea; for(UINT playIndex = 0; playIndex < playArea.Length(); playIndex++) { if(playArea[playIndex].card == data->baseCards.hoard) stack.PushEnd(new EventGainCard(player, data->baseCards.gold, false, false, GainToDiscard)); } } if(data->gardenerInSupply && singleCard->isVictory) { const Vector &playArea = players[player].playArea; for(UINT playIndex = 0; playIndex < playArea.Length(); playIndex++) { if(playArea[playIndex].card == data->baseCards.gardener) { players[player].VPTokens++; if(logging) LogIndent(1, "gains 1 VP from " + data->baseCards.gardener->PrettyName()); } } } if(data->goonsInSupply) { const Vector &playArea = players[player].playArea; for(UINT playIndex = 0; playIndex < playArea.Length(); playIndex++) { if(playArea[playIndex].card == data->baseCards.goons) { players[player].VPTokens++; if(logging) LogIndent(1, "gains 1 VP from " + data->baseCards.goons->PrettyName()); } } } if(data->mintInSupply && singleCard == data->baseCards.mint) { Vector &playArea = players[player].playArea; Vector treasuresToTrash; for(UINT playIndex = 0; playIndex < playArea.Length(); playIndex++) { if(playArea[playIndex].card->isTreasure) treasuresToTrash.PushEnd(playArea[playIndex].card); } for(Card *c : treasuresToTrash) stack.PushEnd(new EventTrashCardFromPlay(player, c)); } stack.PushEnd(new EventGainCard(player, singleCard, true, false, GainToDiscard)); } } } else { // // This order of processing card effects before the stack must be kept because of throne room and king's court // if(decision.activeCard->effect != NULL && decision.activeCard->effect->CanProcessDecisions()) { decision.activeCard->effect->ProcessDecision(*this, response); } else if(stack.Length() > 0 && stack.Last()->CanProcessDecisions()) { stack.Last()->ProcessDecision(*this, response); } else { SignalError("Decision cannot be processed"); } } if(decision.controllingPlayer == -1) decision.controllingPlayer = player; decision.maximumCards = Math::Min(decision.maximumCards, decision.cardChoices.Length()); } void State::AdvancePhase() { PlayerState &p = players[player]; if(phase == PhaseAction) { if(p.ActionCount() == 0 || p.actions == 0) phase = PhaseTreasure; else { if(decisionText) decision.text = "Choose an action to play:"; decision.SelectCards(NULL, 0, 1); for(Card *c : p.hand) { if(c->isAction) decision.AddUniqueCard(c); } } } if(phase == PhaseTreasure) { if(p.TreasureCount() == 0) { phase = PhaseBuy; BuyPhaseStart(); } else { if(decisionText) decision.text = "Choose a treasure to play:"; decision.SelectCards(NULL, 0, 1); // // Bank should always be played last so that it's easy to play treasures // bool bankPresent = false; for(Card *c : p.hand) { if(c->isTreasure) { if(c == data->baseCards.bank) bankPresent = true; else decision.AddUniqueCard(c); } } if(bankPresent) decision.AddUniqueCard(data->baseCards.bank); } } if(phase == PhaseBuy && stack.Length() == 0) { if(p.buys == 0) phase = PhaseCleanup; else { if(decisionText) decision.text = "Choose a card to buy:"; decision.SelectCards(NULL, 0, 1); for(UINT supplyIndex = 0; supplyIndex < data->supplyCards.Length(); supplyIndex++) { int cost = SupplyCost(supplyIndex); int count = supply[supplyIndex].count; if(cost <= p.money && count > 0) { Card *c = data->supplyCards[supplyIndex]; bool canBuyCard = true; if(c == data->baseCards.grandMarket && p.CardInPlay(data->baseCards.copper)) { canBuyCard = false; } if(c == data->baseCards.grandCourt) { for(CardPlayInfo &c : p.playArea) { if(c.card->isAction) canBuyCard = false; } } if(canBuyCard) decision.cardChoices.PushEnd(c); } } if(decision.cardChoices.Length() == 0) { decision.type = DecisionNone; phase = PhaseCleanup; if(logging) Log("cannot afford to buy any cards"); } } } if(phase == PhaseCleanup) { while(p.hand.Length() > 0) { DiscardCard(player, p.hand.Last()); } Vector newPlayArea; for(CardPlayInfo &c : p.playArea) { if(c.turnsLeft >= 1) { CardPlayInfo newInfo = c; newInfo.turnsLeft = c.turnsLeft - 1; newPlayArea.PushEnd(newInfo); } else { bool discardCard = true; if(data->treasuryInSupply && c.card == data->baseCards.treasury) { bool victoryCardGained = false; for(Card *gainedCard : gainList) { if(gainedCard->isVictory) victoryCardGained = true; } if(!victoryCardGained) { discardCard = false; if(logging) Log("puts " + c.card->PrettyName() + " on top of their deck"); p.deck.PushEnd(c.card); } } if(discardCard) p.discard.PushEnd(c.card); } } if(newPlayArea.Length() > 0) p.playArea = newPlayArea; else p.playArea.FreeMemory(); if(logging) Log("draws a new hand"); int cardsToDraw = 5; if(data->promisedLandInSupply) { for(CardPlayInfo &c : p.playArea) { if(c.card == data->baseCards.promisedLand) { cardsToDraw -= c.copies; } } if(cardsToDraw < 0) cardsToDraw = 0; } for(int cardIndex = 0; cardIndex < cardsToDraw; cardIndex++) { DrawCard(player); } if(logging) Log("ends their turn\n"); CheckEndConditions(); if(decision.type == DecisionGameOver) return; player = (player + 1) % playerMaximum; phase = PhaseAction; PlayerState &newPlayer = players[player]; newPlayer.actions = 1 + debugResourceBonus; newPlayer.buys = 1 + debugResourceBonus; newPlayer.money = 0 + debugResourceBonus; newPlayer.turns++; if(data->useGainList) { prevGainList.FreeMemory(); prevGainList = move(gainList); } if(logging) data->log("< " + data->players[player].name + "'s turn - Turn " + String(newPlayer.turns) + " >"); for(CardPlayInfo &c : players[player].playArea) { for(int copyIndex = 0; copyIndex < c.copies; copyIndex++) { if(c.card->effect) c.card->effect->ProcessDuration(*this); } } } } void State::AdvanceToNextDecision(UINT recursionDepth) { if(recursionDepth >= 500) { SignalError("Excessive recusion depth -- top of stack is probably stuck"); } if(decision.type == DecisionGameOver) return; // // Sometimes, resolving a decision will automatically create a new decision. // if(decision.type != DecisionNone) { // // If the decision has only one possible response, respond to it and keep looking for // a non-trivial decision. // if(decision.IsTrivial()) { ProcessDecision(decision.TrivialResponse()); AdvanceToNextDecision(recursionDepth + 1); } return; } if(stack.Length() == 0) { // // If the stack is empty, the basic rules of Dominion control what happens. // AdvancePhase(); } else { // // If the stack is not empty, process the top element on the stack // UINT eventStackIndex = stack.Length() - 1; Event *curEvent = stack[eventStackIndex]; // // If a reaction is played, we may need to skip processing this event until the reaction is resolved. // bool skipEventProcessing = false; bool lighthouseActive = false; if(curEvent->IsAttack()) // && data->reactionCards.Length() > 0) -> this optimization doesn't work when we're debugging because people can have cards not in the supply { Assert(curEvent->AttackedPlayer() != -1, "Invalid player"); const PlayerState &p = players[curEvent->AttackedPlayer()]; AttackAnnotations &annotations = *curEvent->Annotations(); // // Moat // if(!annotations.moatProcessed && p.hand.Contains(data->baseCards.moat)) { stack.PushEnd(new EventMoatReveal(data->baseCards.moat, curEvent->AttackedPlayer())); annotations.moatProcessed = true; skipEventProcessing = true; } // // Lighthouse // if(p.CardInPlay(data->baseCards.lighthouse)) { if(logging) LogIndent(1, curEvent->AttackedPlayer(), " is protected from the attack by " + data->baseCards.lighthouse->PrettyName()); Event *nextEvent = stack.Last(); stack.PopEnd(); delete nextEvent; skipEventProcessing = true; } } if(!skipEventProcessing) { bool eventCompleted = curEvent->Advance(*this); if(eventCompleted) { bool destroyNextEvent = lighthouseActive || curEvent->DestroyNextEventOnStack(); stack.RemoveSlow(eventStackIndex); delete curEvent; // // Cards such as Moat and Lighthouse will also remove the next event on the stack (typically, an attack event) // if(destroyNextEvent) { Event *nextEvent = stack.Last(); stack.PopEnd(); delete nextEvent; } } } } // // If we failed to find a decision, keep advancing the game state until we do. // if(decision.type == DecisionNone) AdvanceToNextDecision(recursionDepth + 1); // // By default, decisions are controlled by the current player. // if(decision.controllingPlayer == -1) decision.controllingPlayer = player; decision.maximumCards = Math::Min(decision.maximumCards, decision.cardChoices.Length()); // // If the decision has only one possible response, respond to it and keep looking for // a non-trivial decision. // if(decision.IsTrivial()) { ProcessDecision(decision.TrivialResponse()); AdvanceToNextDecision(recursionDepth + 1); } Assert(decisionText || decision.text.Length() == 0, "Decision text is disabled!"); // // Events and effects should never have no available options; they should never have // been put on the stack in the first place. // if(decision.type == DecisionSelectCards && decision.cardChoices.Length() == 0) { ofstream debugFile("debug.txt"); debugFile << decision.activeCard->name << ' ' << decision.minimumCards << "," << decision.maximumCards << endl; debugFile.flush(); debugFile.close(); __asm int 3; } } void State::Log(const String &s) { Assert(logging, "Logging is disabled!"); data->log(data->players[player].name + " " + s); } void State::Log(UINT playerIndex, const String &s) { Assert(logging, "Logging is disabled!"); data->log(data->players[playerIndex].name + " " + s); } void State::LogIndent(UINT indentLevel, const String &s) { Assert(logging, "Logging is disabled!"); String preamble; for(UINT i = 0; i < indentLevel; i++) preamble += " "; data->log(preamble + data->players[player].name + " " + s); } void State::LogIndent(UINT indentLevel, UINT playerIndex, const String &s) { Assert(logging, "Logging is disabled!"); String preamble; for(UINT i = 0; i < indentLevel; i++) preamble += " "; data->log(preamble + data->players[playerIndex].name + " " + s); } int State::PlayerScore(UINT playerIndex) const { const PlayerState &p = players[playerIndex]; int sum = p.VPTokens; for(Card *c : p.hand) { sum += c->victoryPoints; if(c->isVictory && c->effect != NULL) sum += c->effect->VictoryPoints(*this, playerIndex); } for(Card *c : p.deck) { sum += c->victoryPoints; if(c->isVictory && c->effect != NULL) sum += c->effect->VictoryPoints(*this, playerIndex); } for(Card *c : p.discard) { sum += c->victoryPoints; if(c->isVictory && c->effect != NULL) sum += c->effect->VictoryPoints(*this, playerIndex); } for(Card *c : p.islandZone) { sum += c->victoryPoints; if(c->isVictory && c->effect != NULL) sum += c->effect->VictoryPoints(*this, playerIndex); } for(const CardPlayInfo &c : p.playArea) { sum += c.card->victoryPoints; if(c.card->isVictory && c.card->effect != NULL) sum += c.card->effect->VictoryPoints(*this, playerIndex); } return sum; } Vector State::WinningPlayers() const { Vector result; double bestScore = -100000.0; for(const PlayerInfo &player : data->players) { int score = PlayerScore(player.index); double trueScore = score + 1.0 - players[player.index].turns * 0.0001; if(trueScore > bestScore) { bestScore = trueScore; result.Allocate(1, player.index); } else if(trueScore == bestScore) { result.PushEnd(player.index); } } return result; } void State::CheckEndConditions() { if(players[player].turns >= 100 || EmptySupplyPiles() >= 3 || SupplyCount(data->baseCards.province) == 0 || (data->supplyCards.Contains(data->baseCards.colony) && SupplyCount(data->baseCards.colony) == 0)) { decision.type = DecisionGameOver; if(logging) { data->log("\n\n\n"); double bestScore = -100000.0; for(const PlayerInfo &player : data->players) { data->log(player.name + ": " + String(PlayerScore(player.index)) + " VP"); } for(int playerIndex : WinningPlayers()) { data->log(data->players[playerIndex].name + " is the winner!!"); } data->log("\n\n"); } } } UINT State::EmptySupplyPiles() const { UINT result = 0; for(UINT supplyIndex = 0; supplyIndex < data->supplyCards.Length(); supplyIndex++) { if(supply[supplyIndex].count == 0) result++; } return result; } UINT State::ActionsPlayedThisTurn() const { int result = 0; const PlayerState &p = players[player]; for(const CardPlayInfo &c : p.playArea) { if(c.card->isDuration) { if(c.turnsLeft == 1) result++; } if(c.card->isPermanent) { if(c.turnsLeft == 999) result++; } else result++; } return result; } void State::BuyPhaseStart() { if(data->furnaceInSupply) { for(CardPlayInfo &c : players[player].playArea) { if(c.card == data->baseCards.furnace) { for(int copyIndex = 0; copyIndex < c.copies; copyIndex++) stack.PushEnd(new EventDiscardNCards(c.card, player, 0, 1)); } } } }