#include "Main.h"
PlayerHeuristic::PlayerHeuristic(const BuyAgenda *agenda)
{
_agenda = agenda;
_remodelGoldThreshold = 2;
}
void PlayerHeuristic::MakeDecision(const State &s, DecisionResponse &response)
{
const DecisionState &d = s.decision;
if(d.type != DecisionSelectCards && d.type != DecisionDiscreteChoice)
{
SignalError("Invalid decision type");
return;
}
if(d.activeCard == NULL)
{
MakePhaseDecision(s, response);
}
else
{
if(s.stack.Length() > 0)
{
EventDiscardDownToN *discardDownEvent = dynamic_cast<EventDiscardDownToN*>(s.stack.Last());
if(discardDownEvent != NULL)
{
MakeDiscardDownDecision(s, response);
return;
}
EventDiscardNCards *discardNEvent = dynamic_cast<EventDiscardNCards*>(s.stack.Last());
if(discardNEvent != NULL)
{
MakeDiscardDownDecision(s, response);
return;
}
EventPutOnDeckDownToN *putOnTopEvent = dynamic_cast<EventPutOnDeckDownToN*>(s.stack.Last());
if(putOnTopEvent != NULL)
{
MakeDiscardDownDecision(s, response);
return;
}
EventReorderDeck *reorderEvent = dynamic_cast<EventReorderDeck*>(s.stack.Last());
if(reorderEvent != NULL)
{
MakeDeckReorderDecision(s, response);
return;
}
EventRemodelExpand* remodelEvent = dynamic_cast<EventRemodelExpand*>(s.stack.Last());
if(remodelEvent != NULL)
{
if(remodelEvent->trashedCard == NULL)
{
bool prosperity = s.data->options.prosperity;
bool remodelExpensiveThings = ((int)s.SupplyCount(s.data->baseCards.province) < 8) ||
(prosperity && (int)s.SupplyCount(s.data->baseCards.colony) < 8);
auto scoringFunction = [remodelExpensiveThings,prosperity](Card *c)
{
if(remodelExpensiveThings)
{
if(!c->isVictory && c->cost >= 6) return 20.0 + c->cost;
}
if(c->name == "curse") return 19.0;
if(c->name == "estate") return 18.0;
if(c->isVictory) return -200.0 + c->cost;
return (double)-c->cost;
};
AIUtility::SelectCards(s, response, scoringFunction);
}
else
{
response.cards.PushEnd(_agenda->ForceBuy(s, s.decision.controllingPlayer, d.cardChoices));
}
return;
}
}
if(d.activeCard->expansion == "base")
{
MakeBaseDecision(s, response);
}
else if(d.activeCard->expansion == "intrigue")
{
MakeIntrigueDecision(s, response);
}
else if(d.activeCard->expansion == "seaside")
{
MakeSeasideDecision(s, response);
}
else if(d.activeCard->expansion == "alchemy")
{
MakeAlchemyDecision(s, response);
}
else if(d.activeCard->expansion == "prosperity")
{
MakeProsperityDecision(s, response);
}
else if(d.activeCard->expansion == "custom")
{
MakeCustomDecision(s, response);
}
else
{
SignalError("Invalid expansion");
}
}
}
Vector<Card*> PlayerHeuristic::TrashableCards(const State &s) const
{
const PlayerState &p = s.players[s.decision.controllingPlayer];
Vector<Card*> result;
bool trashEstates = !CardDesired(s, s.decision.controllingPlayer, s.data->baseCards.estate);
for(Card *c : p.hand)
{
if(c->name == "curse" || c->name == "copper" || (trashEstates && c->name == "estate")) result.PushEnd(c);
}
return result;
}
UINT PlayerHeuristic::TrashableCardCount(const State &s) const
{
const PlayerState &p = s.players[s.decision.controllingPlayer];
UINT result = 0;
bool trashEstates = !CardDesired(s, s.decision.controllingPlayer, s.data->baseCards.estate);
for(Card *c : p.hand)
{
if(c->name == "curse" || c->name == "copper" || (trashEstates && c->name == "estate")) result++;
}
return result;
}
UINT PlayerHeuristic::DiscardableCardCount(const State &s) const
{
const PlayerState &p = s.players[s.decision.controllingPlayer];
UINT result = 0;
for(Card *c : p.hand)
{
if(c->name == "curse" || c->name == "copper" || c->IsPureVictory()) result++;
}
return result;
}
bool PlayerHeuristic::CardDesired(const State &s, int player, Card *c) const
{
Vector<Card*> choice(1, c);
return (_agenda->Buy(s, player, choice) != NULL);
}
void PlayerHeuristic::MakePhaseDecision(const State &s, DecisionResponse &response)
{
const DecisionState &d = s.decision;
int player = s.decision.controllingPlayer;
const PlayerState &p = s.players[player];
if(s.phase == PhaseAction)
{
auto scoringFunction = [this,&p,&s](Card *c)
{
int score = c->priority + 2;
if(c->name == "moneylender" && !p.hand.Contains(s.data->baseCards.copper)) score = -1;
if(c->name == "treasure map" && p.hand.Count(s.data->baseCards.treasureMap) == 1) score = -1;
if(c->name == "tactician")
{
if(p.hand.Length() == 2) score = 1000;
else if(p.MoneyTotal() >= 6) score = -1;
}
if(c->name == "ambassador" && this->TrashableCardCount(s) == 0) score = -1;
if(c->name == "trade route" && this->TrashableCardCount(s) == 0) score = -1;
if(c->name == "bishop" && this->TrashableCardCount(s) == 0) score = -1;
if(c->name == "trading post" && this->TrashableCardCount(s) <= 1) score = -1;
if(c->name == "shanty town" && p.actions >= 2) score = 1;
if(c->name == "smugglers" && s.prevGainList.Length() == 0) score = 2;
if(c->name == "conspirator" && s.ActionsPlayedThisTurn() >= 2) score = s.data->baseCards.treasury->priority;
return score;
};
response.singleCard = AIUtility::BestCard(d.cardChoices, scoringFunction, 0.0);
}
else if(s.phase == PhaseTreasure)
{
if(s.data->grandMarketInSupply && s.SupplyCount(s.data->baseCards.grandMarket) > 0 && CardDesired(s, player, s.data->baseCards.grandMarket))
{
bool playCoppers = true;
int buyingPower = p.MoneyTotal() - (int)p.hand.Count(s.data->baseCards.copper);
if(s.data->options.prosperity)
{
if(buyingPower >= 6 && buyingPower < 9 && !CardDesired(s, player, s.data->baseCards.province)) playCoppers = false;
}
else
{
if(buyingPower >= 6 && buyingPower < 8) playCoppers = false;
}
if(p.CardInPlay(s.data->baseCards.copper)) playCoppers = true;
if(playCoppers) response.singleCard = d.cardChoices[0];
else
{
response.singleCard = NULL;
for(Card *c : d.cardChoices) if(c != s.data->baseCards.copper) response.singleCard = c;
}
}
else
{
response.singleCard = d.cardChoices[0];
}
}
else if(s.phase == PhaseBuy)
{
response.singleCard = _agenda->Buy(s, player, d.cardChoices);
}
else
{
SignalError("Invalid phase");
}
}
void PlayerHeuristic::MakeCopyDecision(const State &s, DecisionResponse &response)
{
AIUtility::SelectCards(s, response, [](Card *c)
{
int p = c->priority;
if(c->isDuration) p = 1000 + c->cost;
if(c->isPermanent && c->cost >= 3) p = 1000 + c->cost;
return p;
} );
}
void PlayerHeuristic::MakeDiscardDownDecision(const State &s, DecisionResponse &response)
{
auto scoringFunction = [](Card *c)
{
if(c->IsPureVictory()) return 20;
if(c->name == "curse") return 19;
if(c->name == "copper") return 18;
return -100 - c->cost;
};
AIUtility::SelectCards(s, response, scoringFunction);
}
void PlayerHeuristic::MakeTrashDecision(const State &s, DecisionResponse &response)
{
auto scoringFunction = [](Card *c)
{
if(c->name == "curse") return 20;
if(c->name == "estate") return 19;
if(c->name == "copper") return 18;
if(c->isVictory) return -100 - c->cost;
return -(c->cost);
};
AIUtility::SelectCards(s, response, scoringFunction);
}
void PlayerHeuristic::MakeDeckReorderDecision(const State &s, DecisionResponse &response)
{
auto scoringFunction = [&s](Card *c)
{
if(c->isAction) return 100 + c->cost;
if(c->isTreasure) return 50 + c->cost;
return c->cost;
};
AIUtility::SelectCards(s, response, scoringFunction);
}
void PlayerHeuristic::MakeBaseDecision(const State &s, DecisionResponse &response)
{
const DecisionState &d = s.decision;
Card &a = *d.activeCard;
int player = s.decision.controllingPlayer;
if(a.name == "cellar")
{
for(Card *c : d.cardChoices) if((c->IsPureVictory()) || c->cost <= 2) response.cards.PushEnd(c);
}
else if(a.name == "chapel")
{
CardCounter counter(s.players[player]);
int treasureTotal = 0;
for(auto &c : counter.counts) if(c.first->isTreasure) treasureTotal += c.first->treasure * c.second;
bool trashCoppers = (treasureTotal >= 7);
for(Card *c : d.cardChoices) if(c->name == "chapel" || (c->name == "estate" && !CardDesired(s, player, s.data->baseCards.estate)) || c->name == "curse" || (trashCoppers && c->name == "copper")) response.cards.PushEnd(c);
if(response.cards.Length() > 4) response.cards.ReSize(4);
}
else if(a.name == "moat")
{
response.choice = 0;
}
else if(a.name == "chancellor")
{
response.choice = 0;
}
else if(a.name == "workshop")
{
response.cards.PushEnd(_agenda->ForceBuy(s, player, d.cardChoices));
}
else if(a.name == "bureaucrat")
{
response.cards.PushEnd(d.cardChoices[0]);
}
else if(a.name == "feast")
{
response.cards.PushEnd(_agenda->ForceBuy(s, player, d.cardChoices));
}
else if(a.name == "militia")
{
MakeDiscardDownDecision(s, response);
}
else if(a.name == "spy")
{
EventSpy* event = dynamic_cast<EventSpy*>(s.stack.Last());
Card *revealedCard = s.players[event->targetedPlayer].deck.Last();
response.choice = 1;
if(revealedCard->IsPureVictory() || revealedCard->name == "copper" || revealedCard->name == "curse") response.choice = 0;
if(event->targetedPlayer == player) response.choice = 1 - response.choice;
}
else if(a.name == "throne room")
{
MakeCopyDecision(s, response);
}
else if(a.name == "library")
{
if(s.players[s.player].actions == 0) response.choice = 0;
else response.choice = 1;
}
else if(a.name == "mine")
{
EventMine* event = dynamic_cast<EventMine*>(s.stack.Last());
if(event->trashedCard == NULL)
{
auto scoringFunction = [&s](Card *c)
{
if(c->name == "gold" && s.SupplyCount(s.data->baseCards.platinum) > 0) return 20.0;
if(c->name == "silver" && s.SupplyCount(s.data->baseCards.gold) > 0) return 19.0;
if(c->name == "copper" && s.SupplyCount(s.data->baseCards.silver) > 0) return 18.0;
return (double)-c->cost;
};
AIUtility::SelectCards(s, response, scoringFunction);
}
else
{
response.cards.PushEnd(_agenda->ForceBuy(s, player, d.cardChoices));
}
}
else
{
SignalError("Unexpected decision");
}
}
void PlayerHeuristic::MakeIntrigueDecision(const State &s, DecisionResponse &response)
{
const DecisionState &d = s.decision;
Card &a = *d.activeCard;
int player = s.decision.controllingPlayer;
const PlayerState &p = s.players[player];
if(a.name == "pawn")
{
if(p.ActionCount() >= 1) response.choice = 0; else response.choice = 2; }
else if(a.name == "courtyard")
{
auto scoringFunction = [&s,&p](Card *c)
{
if(c->isAction && p.actions == 0) return 4;
if(c->isTreasure) return 2;
if(c->isAction && p.actions >= 1) return 1;
return 3;
};
AIUtility::SelectCards(s, response, scoringFunction);
}
else if(a.name == "steward")
{
EventSteward* event = dynamic_cast<EventSteward*>(s.stack.Last());
if(event != NULL)
{
if(TrashableCardCount(s) >= 2) response.choice = 2;
else if(p.actions >= 1) response.choice = 0;
else response.choice = 1;
}
else
{
MakeTrashDecision(s, response);
}
}
else if(a.name == "wishing well")
{
CardCounter counter(p.deck);
auto scoringFunction = [&counter](Card *c)
{
return counter.Count(c);
};
AIUtility::SelectCards(s, response, scoringFunction);
}
else if(a.name == "baron")
{
response.choice = 0;
}
else if(a.name == "nobles")
{
if(p.actions >= 1 || p.ActionCount() == 0) response.choice = 0;
else response.choice = 1;
}
else if(a.name == "trading post")
{
MakeTrashDecision(s, response);
}
else if(a.name == "ironworks")
{
response.cards.PushEnd(_agenda->ForceBuy(s, player, d.cardChoices));
}
else if(a.name == "swindler")
{
bool giveVictoryCards = true;
if(s.data->options.prosperity && s.SupplyCount(s.data->baseCards.colony) >= 8) giveVictoryCards = true;
if(!s.data->options.prosperity && s.SupplyCount(s.data->baseCards.province) >= 8) giveVictoryCards = true;
auto scoringFunction = [giveVictoryCards](Card *c)
{
if(c->name == "curse") return 20;
if(giveVictoryCards && c->IsPureVictory()) return 19;
return -c->priority;
};
AIUtility::SelectCards(s, response, scoringFunction);
}
else if(a.name == "minion")
{
if(s.player != player) MakeDiscardDownDecision(s, response);
else
{
if(p.MoneyTotal() >= 6) response.choice = 0;
else if(p.ActionCount() >= 1) response.choice = 0;
else response.choice = 1;
}
}
else if(a.name == "saboteur")
{
Card *c = _agenda->Buy(s, player, d.cardChoices);
if(c != NULL) response.cards.PushEnd(c);
}
else if(a.name == "torturer")
{
if(s.SupplyCount(s.data->baseCards.curse) == 0) response.choice = 1;
else if(DiscardableCardCount(s) >= 2) response.choice = 0;
else if(p.MoneyTotal() >= 6) response.choice = 1;
else if(p.hand.Length() <= 3 && p.ActionCount() >= 1) response.choice = 1;
else response.choice = 0;
}
else if(a.name == "mining village")
{
int buyingPower = p.MoneyTotal();
if(s.data->options.prosperity)
{
if(buyingPower == 9 || buyingPower == 7 || buyingPower == 4) response.choice = 0;
else response.choice = 1;
}
else
{
if(buyingPower == 6 || buyingPower == 4) response.choice = 0;
else response.choice = 1;
}
}
else if(a.name == "upgrade")
{
EventUpgrade* event = dynamic_cast<EventUpgrade*>(s.stack.Last());
if(event->trashedCard == NULL)
{
bool buyingDuchies = CardDesired(s, player, s.data->baseCards.duchy);
bool buyingEstates = CardDesired(s, player, s.data->baseCards.estate);
auto scoringFunction = [buyingDuchies,buyingEstates](Card *c)
{
if(!buyingEstates && c->name == "estate") return 21.0;
if(c->name == "curse") return 20.0;
if(buyingDuchies && c->cost == 4) return 19.0;
if(c->cost == 2) return 18.0;
if(c->cost == 0) return 17.0;
if(c->cost == 4) return 16.0;
if(c->cost == 5) return 15.0;
if(c->cost == 3) return 14.0;
if(c->isVictory) return -100.0 - c->cost;
return (double)-c->cost;
};
AIUtility::SelectCards(s, response, scoringFunction);
}
else
{
response.cards.PushEnd(_agenda->ForceBuy(s, player, d.cardChoices));
}
}
else
{
SignalError("Unexpected decision");
}
}
void PlayerHeuristic::MakeAlchemyDecision(const State &s, DecisionResponse &response)
{
const DecisionState &d = s.decision;
Card &a = *d.activeCard;
int player = s.decision.controllingPlayer;
const PlayerState &p = s.players[player];
if(a.name == "pawn")
{
}
else if(a.name == "courtyard")
{
}
else
{
SignalError("Unexpected decision");
}
}
void PlayerHeuristic::MakeSeasideDecision(const State &s, DecisionResponse &response)
{
const DecisionState &d = s.decision;
Card &a = *d.activeCard;
int player = s.decision.controllingPlayer;
const PlayerState &p = s.players[player];
if(a.name == "salvager")
{
auto scoringFunction = [](Card *c)
{
if(c->name == "estate") return 21.0;
if(c->name == "curse") return 20.0;
if(c->cost == 2) return 19.0;
if(c->cost == 4) return 18.0;
if(c->cost == 3) return 17.0;
if(c->isVictory) return -100.0 - c->cost;
return (double)-c->cost;
};
AIUtility::SelectCards(s, response, scoringFunction);
}
else if(a.name == "lookout")
{
EventLookout* event = dynamic_cast<EventLookout*>(s.stack.Last());
int stage = event->Stage();
if(stage == 1)
{
MakeTrashDecision(s, response);
}
else if(stage == 2)
{
MakeDiscardDownDecision(s, response);
}
}
else if(a.name == "pearl diver")
{
Card *c = p.deck.First();
if((c->isAction || c->isTreasure) && c->name != "copper") response.choice = 0;
else response.choice = 1;
}
else if(a.name == "navigator")
{
int cardsToReveal = Math::Min((int)p.deck.Length(), 5);
int handScore = 0;
for(int cardIndex = 0; cardIndex < cardsToReveal; cardIndex++)
{
Card *c = p.deck[p.deck.Length() - 1 - cardIndex];
if(c->isAttack) handScore++;
if(c->isAction) handScore++;
if(c->isTreasure && c->name != "copper") handScore++;
if(c->isVictory && !c->isAction && !c->isTreasure) handScore--;
if(c->name == "curse") handScore--;
}
if(handScore < 0) response.choice = 0;
else response.choice = 1;
}
else if(a.name == "ambassador")
{
if(d.type == DecisionSelectCards)
{
auto scoringFunction = [](Card *c)
{
if(c->name == "curse") return 21;
if(c->name == "estate") return 20;
if(c->name == "copper") return 19;
if(c->isVictory) return -100 - c->cost;
if(c->isTreasure) return -50 - c->cost;
return -c->cost;
};
AIUtility::SelectCards(s, response, scoringFunction);
}
else
{
response.choice = d.minimumCards - 1;
}
}
else if(a.name == "smugglers")
{
response.cards.PushEnd(_agenda->ForceBuy(s, player, d.cardChoices));
}
else if(a.name == "explorer")
{
response.choice = 0;
}
else if(a.name == "island")
{
auto scoringFunction = [](Card *c)
{
if(c->isVictory && !c->isTreasure && !c->isAction && !c->isReaction) return 20;
if(c->name == "copper") return 19;
return -c->cost;
};
AIUtility::SelectCards(s, response, scoringFunction);
}
else
{
SignalError("Unexpected decision");
}
}
void PlayerHeuristic::MakeProsperityDecision(const State &s, DecisionResponse &response)
{
const DecisionState &d = s.decision;
Card &a = *d.activeCard;
int player = s.decision.controllingPlayer;
const PlayerState &p = s.players[player];
if(a.name == "king's court")
{
MakeCopyDecision(s, response);
}
else if(a.name == "mountebank")
{
response.choice = 0;
}
else if(a.name == "counting house")
{
response.choice = s.decision.minimumCards - 1;
}
else if(a.name == "loan")
{
EventLoan *loanEvent = dynamic_cast<EventLoan*>(s.stack.Last());
if(loanEvent->treasureCard->cost > 0) response.choice = 0;
else response.choice = 1;
}
else if(a.name == "bishop")
{
bool estatesDresired = CardDesired(s, player, s.data->baseCards.estate);
if(player == s.player)
{
auto scoringFunction = [estatesDresired](Card *c)
{
if(c->name == "curse") return 100;
if(c->name == "estate" && !estatesDresired) return 20;
if(estatesDresired && (c->cost == 2 || c->cost == 4 || c->cost == 5 || c->cost == 7)) return 20 + c->cost;
if(c->name == "copper") return 19;
return -(c->cost);
};
AIUtility::SelectCards(s, response, scoringFunction);
}
else
{
if(p.hand.Contains(s.data->baseCards.curse) || (!estatesDresired && p.hand.Contains(s.data->baseCards.estate)) || p.hand.Contains(s.data->baseCards.copper))
{
auto scoringFunction = [estatesDresired](Card *c)
{
if(c->name == "curse") return 21;
if(c->name == "estate" && !estatesDresired) return 20;
if(c->name == "copper") return 19;
return -(c->cost);
};
AIUtility::SelectCards(s, response, scoringFunction);
}
}
}
else if(a.name == "royal seal")
{
EventGainCard *gainEvent = dynamic_cast<EventGainCard*>(s.stack.Last());
if(gainEvent->card->cost > 0 && (gainEvent->card->isAction || gainEvent->card->isTreasure)) response.choice = 0;
else response.choice = 1;
}
else if(a.name == "watchtower")
{
EventGainCard *gainEvent = dynamic_cast<EventGainCard*>(s.stack.Last());
if(gainEvent->card->name == "copper" || gainEvent->card->name == "curse") response.choice = 0;
else if(gainEvent->card->isAction || gainEvent->card->isTreasure) response.choice = 1;
else response.choice = 2;
}
else if(a.name == "trade route")
{
MakeTrashDecision(s, response);
}
else if(a.name == "mint")
{
auto scoringFunction = [](Card *c)
{
return (c->cost);
};
AIUtility::SelectCards(s, response, scoringFunction);
}
else if(a.name == "vault")
{
if(s.decision.controllingPlayer == s.player)
{
for(Card *c : d.cardChoices) if((c->isVictory && !c->isAction && !c->isTreasure) || c->cost <= 2) response.cards.PushEnd(c);
}
else
{
Vector<Card*> priorityDiscards;
for(Card *c : d.cardChoices) if(c->isVictory && !c->isAction && !c->isTreasure)
{
priorityDiscards.PushEnd(c);
response.cards.PushEnd(c);
}
for(Card *c : d.cardChoices) if(c->cost <= 2)
{
if(!priorityDiscards.Contains(c)) response.cards.PushEnd(c);
}
if(response.cards.Length() >= 2) response.cards.ReSize(2);
else response.cards.FreeMemory();
}
}
else
{
SignalError("Unexpected decision");
}
}
void PlayerHeuristic::MakeCustomDecision(const State &s, DecisionResponse &response)
{
const DecisionState &d = s.decision;
Card &a = *d.activeCard;
int player = s.decision.controllingPlayer;
const PlayerState &p = s.players[player];
if(a.name == "aqueduct")
{
EventAqueduct* event = dynamic_cast<EventAqueduct*>(s.stack.Last());
if(event->trashedCard == NULL)
{
auto scoringFunction = [&s](Card *c)
{
if(c->name == "province" && s.SupplyCount(s.data->baseCards.colony) > 0) return 20.0;
if(c->name == "duchy" && s.SupplyCount(s.data->baseCards.province) > 0) return 19.0;
if(c->name == "estate" && s.SupplyCount(s.data->baseCards.duchy) > 0) return 18.0;
return (double)-c->cost;
};
AIUtility::SelectCards(s, response, scoringFunction);
}
else
{
response.cards.PushEnd(_agenda->ForceBuy(s, player, d.cardChoices));
}
}
else if(a.name == "architect")
{
auto scoringFunction = [&s,&p](Card *c)
{
if(c->isAction && p.actions == 0) return 4;
if(c->isTreasure) return 2;
if(c->isAction && p.actions >= 1) return 1;
return 3;
};
AIUtility::SelectCards(s, response, scoringFunction);
}
else if(a.name == "gambler")
{
MakeTrashDecision(s, response);
}
else if(a.name == "squire")
{
MakeTrashDecision(s, response);
}
else if(a.name == "aristocrat")
{
MakeDeckReorderDecision(s, response);
}
else if(a.name == "knight")
{
if(p.ActionCount() >= 2) response.choice = 0; else response.choice = 2; }
else if(a.name == "street urchin")
{
response.cards.PushEnd(_agenda->ForceBuy(s, player, d.cardChoices));
}
else if(a.name == "trailblazer")
{
MakeCopyDecision(s, response);
}
else if(a.name == "witchdoctor")
{
MakeCopyDecision(s, response);
}
else if(a.name == "grand court")
{
MakeCopyDecision(s, response);
}
else if(a.name == "evangelist")
{
auto scoringFunction = [](Card *c)
{
if(c->name == "curse") return 20;
if(c->name == "estate") return 19;
if(c->name == "copper") return 18;
return -1;
};
Card *c = AIUtility::BestCard(d.cardChoices, scoringFunction, 0.0);
if(c != NULL) response.cards.PushEnd(c);
}
else if(a.name == "pillage")
{
response.cards.PushEnd(_agenda->ForceBuy(s, player, d.cardChoices));
}
else if(a.name == "sepulcher")
{
auto scoringFunction = [&s,&p](Card *c)
{
return c->cost;
};
AIUtility::SelectCards(s, response, scoringFunction);
}
else if(a.name == "furnace")
{
auto scoringFunction = [](Card *c)
{
if(c->name == "curse") return 5;
if(c->isAction || c->IsPureVictory()) return 4;
if(c->name == "copper") return 3;
if(c->isTreasure) return 1;
return 2;
};
AIUtility::SelectCards(s, response, scoringFunction);
}
else
{
SignalError("Unexpected decision");
}
}
PlayerHeuristic* PlayerHeuristic::Mutate(const CardDatabase &cards, const GameOptions &options) const
{
PlayerHeuristic *result = new PlayerHeuristic(_agenda->Mutate(cards, options));
if(rnd() <= 0.2)
{
result->_remodelGoldThreshold = Utility::Bound(result->_remodelGoldThreshold + AIUtility::Delta(), 0, 12);
}
return result;
}