26
Rules Questions / Re: Herald Overpay while Haggler in play. (possible dominion online bug)
« on: July 03, 2019, 12:03:16 pm »
Hey. That’s awesome thx for trying to repro. Now only the Dominion gods know what happened.
Any of these suggestions probably makes the card even more unattractive than leaving it overpriced at $5. Also, as already pointed out, there is nothing "automatic" about Silver+ at $4 unless one wants to argue that Delve and Patron are "automatic".
Let's also keep in mind topdecking gained stuff is nice but hardly as important as e.g. the Attacks of Idol or Relic, the trashing of Counterfeit or the Buy of Spices and Charm.
The main problem with Silver-with-a-bonus for $4 (for games where people want Silver and aren't gaining it with a Silver-gainer) is that the pile just automatically empties. It's also not great that then you have that bonus in your deck, but didn't care about it at all, weren't making a decision there.It’d be fine at $4.It would, except, as Donald X once said, "look, I can't make a Treasure that gives $2, has some other effect that is always beneficial, and costs $4; that's just better than Silver, and people by Silver for $4 all the time".
And then he made Delve.
Delve has neither issue; it hits the Silver pile rather than a new 10-card pile, and those Silvers have no other abilities.
The problem is that the changes were not only wording. There were layout and font changes as well, which apply to every card; not just ones like Throne Room. So if players really want the "second edition" experience, they just need to buy second edition.
public class Mine
: Card
{
public static Mine card = new Mine();
private Mine()
: base("Mine", Expansion.Base, coinCost: 5, isAction: true)
{
}
public override void DoSpecializedAction(PlayerState currentPlayer, GameState gameState)
{
currentPlayer.RequestPlayerTrashCardFromHandAndGainCard(
gameState,
card => card.isTreasure,
CostConstraint.UpTo,
3,
CardRelativeCost.RelativeCost,
isOptionalToTrash: false,
isOptionalToGain: false,
defaultLocation: DeckPlacement.Hand);
}
}
public interface IPlayerAction
{
int GetCountToReturnToSupply(Card card, GameState gameState);
Card BanCardToDrawnIntoHandFromRevealedCards(GameState gameState);
Card BanCardForCurrentPlayerPurchase(GameState gameState);
Card ChooseCardToPlayFirst(GameState gameState, Card card1, Card card2);
Card GetTreasureFromHandToPlay(GameState gameState, CardPredicate acceptableCard, bool isOptional);
Card GetCardFromSupplyToEmbargo(GameState gameState);
Card GetCardFromSupplyToPlay(GameState gameState, CardPredicate acceptableCard);
Card GetCardFromSupplyToBuy(GameState gameState, CardPredicate acceptableCard);
Card GetCardFromSupplyToGain(GameState gameState, CardPredicate acceptableCard, bool isOptional);
Card GuessCardTopOfDeck(GameState gameState);
Card NameACard(GameState gameState);
Card GetCardFromTrashToGain(GameState gameState, CardPredicate acceptableCard, bool isOptional);
Card GetCardFromPlayToTopDeckDuringCleanup(GameState gameState, CardPredicate acceptableCard, bool isOptional);
Card GetCardFromDiscardToTopDeck(GameState gameState, bool isOptional);
Card GetCardFromRevealedCardsToTopDeck(GameState gameState, bool isOptional);
Card GetCardFromRevealedCardsToTrash(GameState gameState, CardPredicate acceptableCard);
Card GetCardFromRevealedCardsToPutOnDeck(GameState gameState);
Card GetCardFromRevealedCardsToDiscard(GameState gameState);
Card GetCardFromHandToDeferToNextTurn(GameState gameState);
Card GetCardFromHandToDiscard(GameState gameState, CardPredicate acceptableCard, bool isOptional);
Card GetCardFromHandToIsland(GameState gameState);
Card GetCardFromHandToPassLeft(GameState gameState);
Card GetCardFromHandToPlay(GameState gameState, CardPredicate acceptableCard, bool isOptional);
Card GetCardFromHandToReveal(GameState gameState, CardPredicate acceptableCard); // always optional
Card GetCardFromHandToTopDeck(GameState gameState, CardPredicate acceptableCard, bool isOptional);
Card GetCardFromHandToTrash(GameState gameState, CardPredicate acceptableCard, bool isOptional, CollectionCards cardsTrashedSoFar);
Card GetCardFromHandOrDiscardToTrash(GameState gameState, CardPredicate acceptableCard, bool isOptional, out DeckPlacement deckPlacement);
Card GetCardFromOtherPlayersHandToDiscard(GameState gameState, PlayerState otherPlayer);
Card GetCardFromOtherPlayersRevealedCardsToTrash(GameState gameState, PlayerState otherPlayer, CardPredicate acceptableCard);
int GetNumberOfCoppersToPutInHandForCountingHouse(GameState gameState, int maxNumber);
bool ShouldPlayerDiscardCardFromDeck(GameState gameState, PlayerState player, Card card);
bool ShouldPlayerDiscardCardFromHand(GameState gameState, Card card);
bool ShouldRevealCardFromHandForCard(GameState gameState, Card card, Card cardFor);
bool ShouldRevealCardFromHand(GameState gameState, Card card);
bool ShouldPutCardInHand(GameState gameState, Card card);
bool WantToResign(GameState gameState);
bool ShouldPutDeckInDiscard(GameState gameState);
bool ShouldPutCardOnTopOfDeck(Card card, GameState gameState);
bool ShouldTrashCard(GameState gameState, Card card);
bool ShouldGainCard(GameState gameState, Card card);
PlayerActionChoice ChooseBetween(GameState gameState, IsValidChoice acceptableChoice);
DeckPlacement ChooseBetweenTrashAndTopDeck(GameState gameState, Card card);
DeckPlacement ChooseBetweenTrashTopDeckDiscard(GameState gameState, Card card);
string PlayerName { get; }
int GetCoinAmountToOverpayForCard(GameState gameState, Card card);
int GetCoinAmountToSpendInBuyPhase(GameState gameState);
int GetCoinAmountToUseInButcher(GameState gameState);
void ChooseLocationForStashAfterShuffle(GameState gameState, int[] locations);
}
public interface IGameLog
: IDisposable
{
void PushScope();
void PopScope();
void BeginRound(PlayerState playerState);
void EndRound(GameState gameState);
void BeginPhase(PlayerState playerState);
void EndPhase(PlayerState playerState);
void BeginTurn(PlayerState playerState);
void EndTurn(PlayerState playerState);
void PlayerNamedCard(PlayerState playerState, Card card);
void PlayerRevealedCard(PlayerState playerState, Card card, DeckPlacement source);
void PlayerBoughtCard(PlayerState playerState, Card card);
void PlayedCard(PlayerState playerState, Card card);
void CardWentToLocation(DeckPlacement deckPlacement);
void ReceivedDurationEffectFrom(PlayerState playerState, Card card);
void PlayerGainedCard(PlayerState playerState, Card card);
void PlayerDiscardCard(PlayerState playerState, Card card, DeckPlacement source);
void PlayerTrashedCard(PlayerState playerState, Card card);
void PlayerPutCardInHand(PlayerState playerState, Card card);
void PlayerTopDeckedCard(PlayerState playerState, Card card);
void PlayerSetAsideCardFromHandForNextTurn(PlayerState playerState, Card card);
void PlayerReturnedCardToHand(PlayerState playerState, Card card);
void PlayerReturnedCardToPile(PlayerState playerState, Card card);
void DrewCardIntoHand(PlayerState playerState, Card card);
void DiscardedCard(PlayerState playerState, Card card);
void ReshuffledDiscardIntoDeck(PlayerState playerState);
void StartGame(GameState gameState);
void EndGame(GameState gameState);
void PlayerGainedPotion(PlayerState playerState, int potionCount);
void PlayerGainedCoin(PlayerState playerState, int coinAmount);
void PlayerGainedCoinToken(PlayerState playerState, int coinAmount);
void PlayerGainedActions(PlayerState playerState, int actionAmount);
void PlayerGainedBuys(PlayerState playerState, int actionAmount);
void PlayerOverpaidForCard(Card boughtCard, int overPayAmount);
void PlayerGainedVictoryTokens(PlayerState playerState, int amount);
void PlayerChoseLocationForStash(PlayerState playerState, int[] positions);
}
EDIT: BTW, which BM are you comparing to? BMU?
Did you implement a ppr rule? With 2 provinces remaining you want to buy/trash gold so that you end the game on your turn. I'd imagine this would be quite significant.
class MyPlayerAction
: PlayerAction
{
public MyPlayerAction()
: base("ApothecaryBishop",
purchaseOrder: PurchaseOrder(),
actionOrder: ActionOrder(),
trashOrder: TrashOrder())
{
}
public override Card GetCardFromRevealedCardsToTopDeck(GameState gameState, bool isOptional)
{
var result = gameState.Self.CardsBeingRevealed.FindCard(card => card != Cards.Apothecary);
if (result != null) return result;
return base.GetCardFromRevealedCardsToTopDeck(gameState, isOptional);
}
}
private static ICardPicker PurchaseOrder()
{
return new CardPickByPriority(
CardAcceptance.For(Cards.Province),
CardAcceptance.For(Cards.Potion, 1),
CardAcceptance.For(Cards.Apothecary, 4),
CardAcceptance.For(Cards.Bishop, 1),
CardAcceptance.For(Cards.Apothecary),
CardAcceptance.For(Cards.Copper, 7));
}
private static ICardPicker ActionOrder()
{
return new CardPickByPriority(
CardAcceptance.For(Cards.Apothecary, ShouldPlayApothecary),
CardAcceptance.For(Cards.Bishop));
}
private static ICardPicker TrashOrder()
{
return new CardPickByPriority(
CardAcceptance.For(Cards.Curse),
CardAcceptance.For(Cards.Potion, ShouldTrashPotion),
CardAcceptance.For(Cards.Province, ShouldTrashProvince),
CardAcceptance.For(Cards.Estate, gameState => CountAllOwned(Cards.Estate, gameState) > 1 || ShouldTrashLastEstate(gameState)),
CardAcceptance.For(Cards.Bishop, gameState => CountAllOwned(Cards.Bishop, gameState) > 1));
}
private static bool ShouldTrashPotion(GameState gameState)
{
bool isSelfPlaying = gameState.CurrentContext.IsSelfPlaying(gameState);
if (!isSelfPlaying)
return false;
if (gameState.Self.ExpectedCoinValueAtEndOfTurn >= 7 && CountOfPile(Cards.Province, gameState) <= 3)
return true;
return false;
}
private static bool ShouldTrashProvince(GameState gameState)
{
bool isSelfPlaying = gameState.CurrentContext.IsSelfPlaying(gameState);
if (!isSelfPlaying)
return false;
if (gameState.Self.ExpectedCoinValueAtEndOfTurn >= 7)
return true;
return false;
}
private static bool ShouldTrashLastEstate(GameState gameState)
{
if (CountAllOwned(Cards.Province, gameState) > 0)
return true;
if (gameState.Self.ExpectedCoinValueAtEndOfTurn >= 7)
return true;
return false;
}
private static bool ShouldPlayApothecary(GameState gameState)
{
if (gameState.Self.CardsInDeckAndDiscard.Where(card => card != Cards.Apothecary).Any())
return true;
return false;
}
64.0% difference for GardensBeggarIronworks
59.6% difference for RebuildMonument
57.3% difference for RatsWanderingMinstrelWatchtowerArmory
43.2% difference for KingsCourtRabbleExpandFarmingVillage
33.8% difference for RebuildJack
30.1% difference for HermitMarketSquare
17.5% difference for RebuildAdvanced
11.7% difference for LookoutSalvagerLibraryHighwayFestival
9.8% difference for FishingVillageChapelPoorHouse
4.3% difference for CaravanBridgeDukeCartographer
=====>
-2.1% difference for HermitFeodum
-6.7% difference for BigMoneyCultist
-16.7% difference for BigMoneyColony
-17.3% difference for MountebankMonumentHamletVineyard
-18.2% difference for Rebuild
-19.4% difference for EmbassyTunnelSpiceMerchantPlaza
-20.6% difference for FishingVillageChapelPoorHouseTalisman
-20.9% difference for FishingVillageLibraryCountPoorHouse
-28.3% difference for AmbassadorCaravanApprenticeMerchantGuild
-30.7% difference for RebuildDuke
-31.6% difference for FeodumDevelop
-34.6% difference for BigMoneyCouncilRoomEarlyProvince
-35.8% difference for BigMoneyFishingVillageJack
-38.1% difference for BigMoneyWharf
-40.4% difference for NomadCampLaboratorySpiceMerchantWarehouse
-41.3% difference for AmbassadorCaravanLaboratory
-42.2% difference for BigMoneySingleJack
-44.2% difference for FishingVillageJackLookout
-46.4% difference for BigMoneyDoubleSmithy
-46.5% difference for BigMoneyDoubleWitch
-46.7% difference for BigMoneySingleSmithy
-48.5% difference for BigMoneyDoubleJack
-50.1% difference for LaboratorySpiceMerchantWarehouse
-52.3% difference for DuchyDukeWarehouseEmbassy
-55.5% difference for BigMoneySmithyEarlyProvince
-57.3% difference for BigMoneyDoubleJackSlog
-59.4% difference for MineHoard
-60.3% difference for BigMoneySingleWitch
-60.4% difference for CacheCountingHouseBridgeTunnel
-63.3% difference for RemakeSoothsayer
-63.7% difference for TreasureMapDoctor
-64.4% difference for LookoutTraderNobles
-65.8% difference for ButcherPlazaWatchtower
-67.5% difference for TreasureMap
-67.8% difference for RatsUpgradeBazaar
-69.6% difference for MintBaker
-70.3% difference for DevelopFeastMysticTunnel
-70.7% difference for Doctor
-70.8% difference for BigMoneyBridge
-71.0% difference for ArmoryConspiratorForagerGreatHall
-71.7% difference for TaxMan
-73.2% difference for HorseTraderSoothsayerMinionGreatHall
-74.2% difference for LookoutHaremMiningVillageMysticScout
-75.3% difference for BigMoneyMoneylender
-76.0% difference for DoubleWarehouse2
-77.6% difference for DoubleWarehouse
-79.2% difference for BigMoneyThief
-79.5% difference for DeathCartDoubleWarehouse
-81.1% difference for RatsUpgrade
-82.5% difference for BigMoney
-82.5% difference for Harem
-84.5% difference for MountebankGovernorMaurader
-85.2% difference for BigMoneyDelayed
-88.0% difference for Lookout
-88.5% difference for FamiliarPotionSilverOpenning
-88.7% difference for IronworksGreathallRemodelHuntingGrounds
-89.9% difference for ProcessionGraverobber
-92.8% difference for FamiliarSilverSilverOpenning
-94.2% difference for BigMoneySimple
-97.1% difference for GovernorMarketsquare
-98.0% difference for IllgottengainsMoneylender
-98.4% difference for Illgottengains
-100.0% difference for ChapelCanTripKingsCourtMasquerade
-100.0% difference for GovernorJunkdealer
-100.0% difference for MountebankHoard
public class HermitFeodum
: Strategy
{
public static PlayerAction Player()
{
return new PlayerAction(
"HermitFeodum",
purchaseOrder: PurchaseOrder(),
actionOrder: ActionOrder(),
trashOrder: TrashOrder(),
gainOrder: GainOrder(),
chooseDefaultActionOnNone:false);
}
private static ICardPicker PurchaseOrder()
{
return new CardPickByPriority(
CardAcceptance.For(Cards.Province, gameState => CountAllOwned(Cards.Silver, gameState) < 18 || CountOfPile(Cards.Feodum, gameState) == 0 ),
CardAcceptance.For(Cards.Feodum, ShouldGainFeodum),
// open up double hermit
CardAcceptance.For(Cards.Hermit, gameState => CountAllOwned(Cards.Silver, gameState) == 0 && CountAllOwned(Cards.Hermit, gameState) < 2),
CardAcceptance.For(Cards.Silver));
}
private static ICardPicker GainOrder()
{
return new CardPickByPriority(
CardAcceptance.For(Cards.Hermit, gameState => gameState.Self.ExpectedCoinValueAtEndOfTurn < 3),
CardAcceptance.For(Cards.Silver),
CardAcceptance.For(Cards.Estate));
}
private static CardPickByPriority ActionOrder()
{
return new CardPickByPriority(
CardAcceptance.For(Cards.Madman),
CardAcceptance.For(Cards.Hermit));
}
private static CardPickByPriority TrashOrder()
{
return new CardPickByPriority(
CardAcceptance.For(Cards.Feodum, ShouldTrashFeodum),
CardAcceptance.For(Cards.Estate),
CardAcceptance.For(Cards.Copper));
}
private static bool ShouldTrashFeodum(GameState gameState)
{
int countSilvers = CountAllOwned(Cards.Silver, gameState);
int countFeodum = CountAllOwned(Cards.Feodum, gameState);
// if you have trashed 2 or less feodum, you should have less than 9 silvers.
if (countSilvers < 8)
{
return true;
}
// otherwise maximize feodum points
int scoreTrashNothing = CardTypes.Feodum.VictoryCountForSilver(countSilvers) * countFeodum;
int scoreTrashFeodum = CardTypes.Feodum.VictoryCountForSilver((countSilvers + 4)) * (countFeodum - 1);
return scoreTrashFeodum > scoreTrashNothing;
}
private static bool ShouldGainFeodum(GameState gameState)
{
int countSilvers = CountAllOwned(Cards.Silver, gameState);
if (countSilvers > 9)
{
return true;
}
//hermits left in draw pile >0 && cards left in drawpile [mod 5] / cards left in draw pile <= 0,2
bool hasHermitInDrawPile = CountInDeck(Cards.Hermit, gameState) > 0;
bool atLeast80PercentChanceOfDrawingBeforeShuffle = (((double)(gameState.Self.CardsInDeck.Count % 5)) / gameState.Self.CardsInDeck.Count) < 0.2;
if (hasHermitInDrawPile && atLeast80PercentChanceOfDrawingBeforeShuffle)
return true;
return false;
}
}
simply adding Lab and Caravan to the simulation doesn't really help with that
Simulations are far less useful when it comes to engine strategies
you can probably do something with "perfect shuffle luck" to get ambassador with two estates then two coppers t3/t4, and I would be interested to see how this fares against the A/S perfect shuffle luck.
public class AmbassadorAlwaysReturnBestTrash
: UnimplementedPlayerAction
{
private readonly PlayerAction playerAction;
public AmbassadorAlwaysReturnBestTrash(PlayerAction playerAction)
{
this.playerAction = playerAction;
}
public override Card GetCardFromHandToReveal(GameState gameState, CardPredicate acceptableCard)
{
Card cardToReturn = playerAction.trashOrder.GetPreferredCard(gameState, card => gameState.Self.Hand.HasCard(card) && acceptableCard(card));
return cardToReturn;
}
public override int GetCountToReturnToSupply(Card cardToReturn, GameState gameState)
{
return 2;
}
}
public class AmbassadorReturnIfNotDisruptPurchase
: UnimplementedPlayerAction
{
private readonly PlayerAction playerAction;
public AmbassadorReturnIfNotDisruptPurchase(PlayerAction playerAction)
{
this.playerAction = playerAction;
}
public override Card GetCardFromHandToReveal(GameState gameState, CardPredicate acceptableCard)
{
Card cardToReturn = playerAction.trashOrder.GetPreferredCard(gameState, card => gameState.Self.Hand.HasCard(card) && acceptableCard(card));
if (cardToReturn == null)
return null;
PlayerState self = gameState.Self;
int currentCoin = self.ExpectedCoinValueAtEndOfTurn;
int coinCountIfReturn = currentCoin - cardToReturn.plusCoin;
if (currentCoin < Dominion.Cards.Gold.DefaultCoinCost)
return cardToReturn;
Card cardWithAllCoin = playerAction.GetCardFromSupplyToBuy(gameState, card => card.CurrentCoinCost(self) <= currentCoin);
Card cardWithReturnedCard = playerAction.GetCardFromSupplyToBuy(gameState, card => card.CurrentCoinCost(self) <= coinCountIfReturn);
if (cardWithAllCoin != cardWithReturnedCard)
return null;
return cardToReturn;
}
public override int GetCountToReturnToSupply(Card cardToReturn, GameState gameState)
{
PlayerState self = gameState.Self;
int currentCoin = self.ExpectedCoinValueAtEndOfTurn - cardToReturn.plusCoin;
int coinCountIfReturn = currentCoin - cardToReturn.plusCoin;
if (currentCoin < Dominion.Cards.Gold.DefaultCoinCost)
return 2;
Card cardWithAllCoin = playerAction.GetCardFromSupplyToBuy(gameState, card => card.CurrentCoinCost(self) <= currentCoin);
Card cardWithReturnedCard = playerAction.GetCardFromSupplyToBuy(gameState, card => card.CurrentCoinCost(self) <= coinCountIfReturn);
if (cardWithAllCoin != cardWithReturnedCard)
return 1;
return 2;
}
}
public class AmbassadorMaxReturn
: UnimplementedPlayerAction
{
private readonly PlayerAction playerAction;
public AmbassadorMaxReturn(PlayerAction playerAction)
{
this.playerAction = playerAction;
}
public override Card GetCardFromHandToReveal(GameState gameState, CardPredicate acceptableCard)
{
int maxCount = 0;
// find out which card that is wanted to be trashed you have most of in hand.
foreach (Card card in playerAction.trashOrder.GetNeededCards())
{
maxCount = Math.Max(maxCount, gameState.Self.Hand.CountOf(card));
}
if (maxCount > 2)
maxCount = 2;
Card cardToReturn = playerAction.trashOrder.GetPreferredCard(gameState, card => gameState.Self.Hand.CountOf(card) >= maxCount && acceptableCard(card));
return cardToReturn;
}
public override int GetCountToReturnToSupply(Card cardToReturn, GameState gameState)
{
return 2;
}
}