Dominion Strategy Forum

Please login or register.

Login with username, password and session length
Pages: [1] 2  All

Author Topic: Dominulator: A flexible dominion engine simulator  (Read 18590 times)

0 Members and 1 Guest are viewing this topic.

Sparafucile

  • Thief
  • ****
  • Offline Offline
  • Posts: 98
  • Respect: +153
    • View Profile
Dominulator: A flexible dominion engine simulator
« on: October 04, 2013, 02:00:42 pm »
+18

Hey All, I would like to introduce you to a new simulator for Dominion that I have been working on.  Now called Dominulator

What makes this simulator different?

The other simulators I have seen have an emphasis on allowing non-programmers to easily create new strategies.  There are many obvious advantages this approach.  Sometimes, however, you may be interested in expressing a strategy that is tough to capture with a restricted language.   

This simulator focuses on creating a game engine that is completely separate from play decisions.  Every play decision should be able to be customized - but this also involves writing to a more complicated interface using c# code.   On top of the game engine, I have built enough infrastructure to provide high level strategies similar to what the other simulators have done.  You can create a buy order, trash order etc.  The strategies created tend to be relatively readable.

The goal is to eventually be complete, with all cards playable, and with all bugs squashed ;)   

Its not done yet - but done enough I thought I would share.  I have tested it with some cards from all sets - including Guilds, and Dark ages.   It can simulate games with Coin Tokens, and Ruins and Shelters, and Spoils etc etc...

Where can I find the code and learn more?

https://github.com/NathanTeeuwen/Dominulator

There is a readme in the GIT repository that I will be trying to keep up to date. 

Please make feature requests or report bugs by creating Issues on Github.   You can also reply to this post, and I will try to keep on top of it. 

If you would like to make a contribution, let me know.
« Last Edit: October 05, 2013, 12:47:19 am by Sparafucile »
Logged

SCSN

  • Mountebank
  • *****
  • Offline Offline
  • Posts: 2227
  • Respect: +7140
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #1 on: October 05, 2013, 06:36:05 pm »
0

One small bug in BaseSet.cs:

Quote
    public class Feast
       : Card
    {
        public Feast()
            : base("Feast", coinCost: 4, isAction: true)
        {
        }

        public override void DoSpecializedAction(PlayerState currentPlayer, GameState gameState)
        {
            currentPlayer.MoveCardFromPlayToTrash(gameState);
            currentPlayer.RequestPlayerGainCardFromSupply(
                gameState,
                card => card.CurrentCoinCost(currentPlayer) < 5
                , "cost of card < 5"
);
        }
    }

This should be <= 5.
Logged

Sparafucile

  • Thief
  • ****
  • Offline Offline
  • Posts: 98
  • Respect: +153
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #2 on: October 05, 2013, 10:00:05 pm »
0

Fixed feast
Logged

SCSN

  • Mountebank
  • *****
  • Offline Offline
  • Posts: 2227
  • Respect: +7140
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #3 on: October 06, 2013, 09:44:29 am »
0

I fixed the monument rebuild.  Added RebuildDuchy.   I also looked over Cultist.  It had an exception, but I removed it as I believe the card is fully implemented, though not tested.  Why don't you start with Cultist strategies :).  Let me know if there's a bug with it.

I was going to do hermit card next - as I am also interested in seeing what the hermit/market square strat would look like.

Let's continue here as this is actually the appropriate thread ;)

Two trivial points:

1. The RebuildDuke strategy file is called RebuildDuchy.cs, which is a bit confusing :)
2. The card type of Ruins is called "Ruin", but the proper term is actually "Ruins", see e.g. here.

About the Cultist implementation, there's one bug I found:

Quote
BigMoneyCultist begins turn
With hand: Copper,Copper,Cultist,Cultist,Silver,
  BigMoneyCultist Played Cultist.
    BigMoneyCultist Drew Silver into hand.
    BigMoneyCultist Drew Copper into hand.
    BigMoneyCultist Played Cultist.
      BigMoneyCultist Drew Estate into hand.
      BigMoneyCultist Drew Copper into hand.
      BigMoney gained Ruined Library.
    BigMoney gained Ruined Village.

It looks like the Ruins from the first Cultist is gained only after the second Cultist is played (and even after the Ruins from that Cultist is gained). This is wrong and it matters because when your opponent has a Watchtower or Trader in hand, he reveals this only at the moment when he gains/would gain the card. Knowing that your opponent has one of these cards in hand might deter you from playing the second Cultist, especially if it would trigger a reshuffle. As it is you'd learn of the reaction only after your final Cultist play.

I also saw that the Ruins are never actually played. I think a good heuristic, at least for BM-like strategies and given that you have no non-Ruins actions left in hand, would be to play Abandoned Mine if (ExpectedCoinValueAtEndOfTurn + 1) would get you a card of higher gain priority than ExpectedCoinValueAtEndOfTurn, else play Ruined Library if (ExpectedCoinValueAtEndOfTurn + 1) or (ExpectedCoinValueAtEndOfTurn + 2) would get you a card of higher gain priority, and play Survivors otherwise. You could also choose to not play Survivors if it would trigger a reshuffle and your current hand has above average money density.

Most of the BigMoney scripts contain this:

Code: [Select]
CardAcceptance.For<CardTypes.Province>(gameState => CountAllOwned<CardTypes.Gold>(gameState) > 2),
Preferring Province over Gold after just 2 Golds is better by quite a bit.

BigMoneyCultist.cs A simple BM-Cultist strategy. I didn't spend alot of time optimizing it, but it beats both BMDoubleWitch and DoubleJack, so it can't be that bad.

Code: [Select]
using Dominion;
using CardTypes = Dominion.CardTypes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

//Author: SheCantSayNo

namespace Program
{
    public static partial class Strategies
    {
        public static class BigMoneyCultist
        {

            public static PlayerAction Player(int playerNumber)
            {
                return CustomPlayer(playerNumber);
            }
           
            public static PlayerAction CustomPlayer(int playerNumber, int secondSmithy = 15)
            {
                return new PlayerAction(
                            "BigMoneyCultist",
                            playerNumber,
                            purchaseOrder: PurchaseOrder(secondSmithy),
                            treasurePlayOrder: Default.TreasurePlayOrder(),
                            actionOrder: ActionOrder(),
                            trashOrder: Default.EmptyPickOrder(),
                            discardOrder: Default.EmptyPickOrder());
            }

            private static CardPickByPriority PurchaseOrder(int secondSmithy)
            {
                return new CardPickByPriority(
                           CardAcceptance.For<CardTypes.Cultist>(gameState => CountOfPile<CardTypes.Ruin>(gameState) > 2),
                           CardAcceptance.For<CardTypes.Province>(gameState => CountAllOwned<CardTypes.Gold>(gameState) > 1),
                           CardAcceptance.For<CardTypes.Duchy>(gameState => CountOfPile<CardTypes.Province>(gameState) <= 5),
                           CardAcceptance.For<CardTypes.Estate>(gameState => CountOfPile<CardTypes.Province>(gameState) < 3),
                           CardAcceptance.For<CardTypes.Gold>(),
                           CardAcceptance.For<CardTypes.Cultist>(),
                           CardAcceptance.For<CardTypes.Silver>());
            }

            private static CardPickByPriority ActionOrder()
            {
                return new CardPickByPriority(
                           CardAcceptance.For<CardTypes.Cultist>());
            }
        }
    }
}
« Last Edit: October 06, 2013, 09:47:19 am by SheCantSayNo »
Logged

Sparafucile

  • Thief
  • ****
  • Offline Offline
  • Posts: 98
  • Respect: +153
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #4 on: October 06, 2013, 01:58:16 pm »
0

  • Fixed RebuildDuke file to be named correctly.
  • Ruins are called ruins instead of Ruin. 
  • Fixed cultist to distribute ruins before drawing additional cultists. 
  • Strategies will now play ruins by default. 
The ruin it plays will be arbitrary.   The ordering you suggested seems reasonable.  I just haven't implemented it yet.  If you want to make that happen, implement CompareRuins method in Strategies.cs :).    Of course, you can customize the ruin play order for a specific strategy, but implementing CompareRuins will determine the default play order for ruins.
Logged

SCSN

  • Mountebank
  • *****
  • Offline Offline
  • Posts: 2227
  • Respect: +7140
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #5 on: October 07, 2013, 01:20:47 pm »
0


Ok, I first created a fixed default ordering to test whether it would work (it did). It obviously isn't terrific, but I expect it to be slightly better than random:

Code: [Select]
private static int CompareRuins(Card first, Card second, GameState gameState)
{
string[] DefaultRuinsOrder = {"Ruined Village", "Abandoned Mine", "Ruined Library", "Survivors",
"Ruined Market"};
return Array.IndexOf(DefaultRuinsOrder, first.name) <= Array.IndexOf(DefaultRuinsOrder, second.name) ? 0 : 1;
}

Onto the more advanced version: the first thing I want to, after always having it play Ruined Village, is to play Abandoned Mine if it allows you to buy a card of higher gain priority:

Code: [Select]
int coinsToSpend = gameState.players.CurrentPlayer.ExpectedCoinValueAtEndOfTurn;

if(first.name == "Ruined Village" || second.name == "Ruined Village")
{
return first.name == "Ruined Village" ? 0 : 1;
}

if((first.name == "Abandoned Mine" || second.name == "Abandoned Mine") &&
   GetGainPriority(coinsToSpend + 1) < GetGainPriority(coinsToSpend))
{
return first.name == "Abandoned Mine" ? 0 : 1;
}

However, I don't know how to convert an amount of Coins into the gain priority. I assume you need to communicate with the strategy's PurchaseOrder method in some way, but I'm not sure how this should be done.
Logged

Sparafucile

  • Thief
  • ****
  • Offline Offline
  • Posts: 98
  • Respect: +153
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #6 on: October 07, 2013, 03:48:07 pm »
0

Quote
However, I don't know how to convert an amount of Coins into the gain priority. I assume you need to communicate with the strategy's PurchaseOrder method in some way, but I'm not sure how this should be done.

Here's how I would do it.  First, the comparer must have access to the purchase order.  You can pass this in via a parameter.  That requires modifying the following methods:
Code: [Select]
Program.Strategies.Default.ActionPlayOrder(ICardPicker purchaseOrder)
Program.Strategies.Default.SortCardByDefaultActionOrder.Ctr(ICardPicker purchaseOrder)
Program.Strategies.Default.SortCardByDefaultActionOrder.Comparer.Ctr(GameState gameState, ICardPicker purchaseOrder)
Program.Strategies.Default.SortCardByDefaultActionOrder.CompareRuins(Card first, Card second, GameState gameState, ICardPicker purchaseOrder)

Next, how to use the purchase order now that you have it.

Similar choices have to be made in spending coin tokens.  So, I would examine how that gets done. 

Look at the Program.PlayerAction.GetCoinAmountToSpend method.  You can do something similar where you invoke

Code: [Select]
purchaseOrder.GetPreferredCard(gameState, shouldGainCard);

where the shouldGainCard delegate checks for exactly the coin cost you expect to have.  (Use ExpectedCoinValueAtEndOfTurn + 1).  If it returns a match of exactly equal, then you know that you must play the abandoned mine to get the bonus coin to make that purchase.
Logged

SCSN

  • Mountebank
  • *****
  • Offline Offline
  • Posts: 2227
  • Respect: +7140
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #7 on: October 07, 2013, 08:15:24 pm »
0

When I change

Code: [Select]
            public ICardPicker ActionPlayOrder()
            {
                return new CardPickFromWhatsInHand(new SortCardByDefaultActionOrder());
            }

to

Code: [Select]
public ICardPicker ActionPlayOrder(ICardPicker purchaseOrder)
            {
                return new CardPickFromWhatsInHand(new SortCardByDefaultActionOrder(purchaseOrder));
            }

(and apply the other modifications) I get the error message "Error   1   No overload for method 'ActionPlayOrder' takes 0 arguments   C:\Dominion\Dominulator-master\TestProgram\PlayerAction.cs   25":

Code: [Select]
static readonly ICardPicker defaultActionOrder = Strategies.Default.ActionPlayOrder();
When I pass purchaseOrder as an argument:

Code: [Select]
static readonly ICardPicker defaultActionOrder = Strategies.Default.ActionPlayOrder(purchaseOrder);
I get the errors "Error   1   An object reference is required for the non-static field, method, or property 'Program.PlayerAction.purchaseOrder'   C:\Dominion\Dominulator-master\TestProgram\PlayerAction.cs   25
" and "Error   2   An object reference is required for the non-static field, method, or property 'Program.Strategies.Default.ActionPlayOrder(Program.ICardPicker)'   C:\Dominion\Dominulator-master\TestProgram\PlayerAction.cs   25
". I googled the errors but became none the wiser from it, and certainly didn't learn how to fix it.

Here is my altered strategies.cs, fwiw:

Code: [Select]
using Dominion;
using CardTypes = Dominion.CardTypes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Program
{
    public static partial class Strategies
    {
        private enum RelativeAmount
        {
            LessThan,
            LessThanEqual,
            GreaterThan,
            GreaterThanEqual,
            Equal
        }

        private static GameStatePredicate CountAllOwned<T>(RelativeAmount relativeAmount, int amount)
            where T : Card, new()
        {
            switch (relativeAmount)
            {
                case RelativeAmount.LessThan: return delegate(GameState gameState) { return CountAllOwned<T>(gameState) < amount; };
                case RelativeAmount.GreaterThan: return delegate(GameState gameState) { return CountAllOwned<T>(gameState) > amount; };
                case RelativeAmount.LessThanEqual: return delegate(GameState gameState) { return CountAllOwned<T>(gameState) <= amount; };
                case RelativeAmount.GreaterThanEqual: return delegate(GameState gameState) { return CountAllOwned<T>(gameState) >= amount; };
                case RelativeAmount.Equal: return delegate(GameState gameState) { return CountAllOwned<T>(gameState) == amount; };
                default: throw new System.Exception();
            }
        }

        private static int CountInDeck<T>(GameState gameState)
        {
            return gameState.players.CurrentPlayer.CardsInDeck.Where(card => card is T).Count();
        }

        private static int CountInDeckAndDiscard<T>(GameState gameState)
        {
            return gameState.players.CurrentPlayer.CardsInDeckAndDiscard.Where(card => card is T).Count();
        }

        private static int CountMightDraw<T>(GameState gameState, int maxCount)
        {
            if (gameState.players.CurrentPlayer.CardsInDeck.Count() >= maxCount)
                return CountInDeck<T>(gameState);
            else
                return CountInDeckAndDiscard<T>(gameState);
        }

        public static bool CardBeingPlayedIs<T>(GameState gameState)
                where T : Card, new()
        {
            var cardBeingPlayed = gameState.players.CurrentPlayer.CurrentCardBeingPlayed;
            return cardBeingPlayed != null && cardBeingPlayed.Is<T>();
        }

        public static int CostOfCard<T>(GameState gameState)
            where T : Card, new()
        {
            return Card.Type<T>().CurrentCoinCost(gameState.players.CurrentPlayer);
        }

        public static int CountAllOwned<T>(GameState gameState)
            where T : Card, new()
        {
            return CountAllOwned(Card.Type<T>(), gameState);
        }

        public static int CountAllOwned(Card cardType, GameState gameState)
        {
            return gameState.players.CurrentPlayer.AllOwnedCards.Where(card => card.Is(cardType)).Count();
        }

        public static int CountInHand(Card cardType, GameState gameState)
        {
            return gameState.players.CurrentPlayer.Hand.Where(card => card.Is(cardType)).Count();
        }

        public static int CountInHand<T>(GameState gameState)
            where T : Card, new()
        {
            return CountInHand(Card.Type<T>(), gameState);
        }

        public static int CountOfPile<T>(GameState gameState)
            where T : Card, new()
        {
            return CountOfPile(Card.Type<T>(), gameState);
        }

        public static int CountOfPile(Card cardType, GameState gameState)
        {
            return gameState.GetPile(cardType).Count();
        }

        private static int CountAllOwnedMatching(ICardPicker matchingCards, GameState gameState)
        {
            int result = 0;

            foreach (Card card in gameState.players.CurrentPlayer.AllOwnedCards)
            {
                if (matchingCards.GetPreferredCard(gameState, testCard => testCard.Is(card)) != null)
                {
                    result += 1;
                }
            }

            return result;
        }

        private static int PlayersPointLead(GameState gameState)
        {
            return gameState.players.CurrentPlayer.TotalScore() - gameState.players.OtherPlayers.First().TotalScore();
        }

        private static GameStatePredicate HasCardInHand<T>()
            where T : Card, new()
        {
            return delegate(GameState gameState)
            {
                return HasCardInHand<T>(gameState);
            };
        }

        private static bool HasCardInHand<T>(GameState gameState)
            where T : Card, new()
        {
            return gameState.players.CurrentPlayer.Hand.HasCard<T>();
        }

        internal static Card WhichCardFromInHand(ICardPicker matchingCards, GameState gameState)
        {
            return matchingCards.GetPreferredCard(gameState, card => gameState.players.CurrentPlayer.Hand.HasCard(card));
        }

        private static bool HasCardFromInHand(ICardPicker matchingCards, GameState gameState)
        {
            return WhichCardFromInHand(matchingCards, gameState) != null;
        }

        private static bool HandHasOnlyCardsFrom(ICardPicker matchingCards, GameState gameState)
        {
            foreach (Card card in gameState.players.CurrentPlayer.Hand)
            {
                if (matchingCards.GetPreferredCard(gameState, current => current.Is(card)) == null)
                {
                    return false;
                }
            }

            return true;
        }

        private static int CountInHandFrom(ICardPicker matchingCards, GameState gameState)
        {
            int result = 0;
            foreach (Card card in gameState.players.CurrentPlayer.Hand)
            {
                if (matchingCards.GetPreferredCard(gameState, current => current.Is(card)) != null)
                {
                    ++result;
                }
            }

            return result;
        }

        public static class Default
        {
            public static CardPickByPriority EmptyPickOrder()
            {
                return new CardPickByPriority();
            }

            public ICardPicker ActionPlayOrder(ICardPicker purchaseOrder)
            {
                return new CardPickFromWhatsInHand(new SortCardByDefaultActionOrder(purchaseOrder));
            }

            private class CardPickFromWhatsInHand
                : ICardPicker
            {
                private readonly IComparerFactory comparerFactory;

                public CardPickFromWhatsInHand(IComparerFactory comparerFactory)
                {
                    this.comparerFactory = comparerFactory;
                }

                public int AmountWillingtoOverPayFor(Card card, GameState gameState)
                {
                    throw new NotImplementedException();
                }

                public Card GetPreferredCard(GameState gameState, CardPredicate cardPredicate)
                {
                    IComparer<Card> comparer = this.comparerFactory.GetComparer(gameState);

                    PlayerState currentPlayer = gameState.players.CurrentPlayer;

                    Card cardToPlay = currentPlayer.Hand.Where(card => cardPredicate(card)).OrderBy(card => card, comparer).FirstOrDefault();
                    if (cardToPlay == null)
                        return null;

                    return cardToPlay;
                }

                public Card GetPreferredCardReverse(GameState gameState, CardPredicate cardPredicate)
                {
                    IComparer<Card> comparer = this.comparerFactory.GetComparer(gameState);

                    PlayerState currentPlayer = gameState.players.CurrentPlayer;

                    Card cardToPlay = currentPlayer.Hand.Where(card => cardPredicate(card)).OrderByDescending(card => card, comparer).FirstOrDefault();
                    if (cardToPlay == null)
                        return null;

                    return cardToPlay;
                }

                public IEnumerable<Card> GetNeededCards()
                {
                    yield break;
                }
            }

            private interface IComparerFactory
            {
                IComparer<Card> GetComparer(GameState gameState);
                IComparer<Card> GetComparer(GameState gameState, ICardPicker purchaseOrder);
            }

            private class SortCardByDefaultActionOrder
                : IComparerFactory
            {

                public SortCardByDefaultActionOrder()
                {
                }

   
                public SortCardByDefaultActionOrder(ICardPicker purchaseOrder)
                {
                }

                public IComparer<Card> GetComparer(GameState gameState)
                {
                    return new Comparer(gameState);
                }

                public IComparer<Card> GetComparer(GameState gameState, ICardPicker purchaseOrder)
                {
                    return new Comparer(gameState, purchaseOrder);
                }

                private class Comparer
                    : IComparer<Card>
                {
                    private readonly GameState gameState;
                    private readonly ICardPicker purchaseOrder;

                    public Comparer(GameState gameState)
                    {
                        this.gameState = gameState;
                    }

                    public Comparer(GameState gameState, ICardPicker purchaseOrder)
                    {
                        this.gameState = gameState;
                        this.purchaseOrder = purchaseOrder;
                    }

                    public int Compare(Card first, Card second)
                    {
                        if (first.plusAction != 0 ^ second.plusAction != 0)
                        {
                            return first.plusAction != 0 ? -1 : 1;
                        }

                        if (first.DefaultCoinCost != second.DefaultCoinCost)
                        {
                            return first.DefaultCoinCost - second.DefaultCoinCost;
                        }

                        if (first.isRuins && second.isRuins)
                        {
                            int result = CompareRuins(first, second, this.gameState, this.purchaseOrder);
                            if (result != 0)
                                return result;
                        }

                        return 0;
                    }
                }

                // TODO:  implement a better default choice of which Ruins to player.
                private static int CompareRuins(Card first, Card second, GameState gameState, ICardPicker purchaseOrder)
                {
                    PlayerState currentPlayer = gameState.players.CurrentPlayer;

                    int coinsToSpend = gameState.players.CurrentPlayer.ExpectedCoinValueAtEndOfTurn;

                    if (first.name == "Abandoned Mine" || second.name == "Abandoned Mine")
                    {
                        CardPredicate shouldGainCard = delegate(Card card)
                        {
                            int currentCardCost = card.CurrentCoinCost(currentPlayer);

                            return currentCardCost == coinsToSpend + 1;                                   
                        };

                        Card cardType = purchaseOrder.GetPreferredCard(gameState, shouldGainCard);
                        if (cardType != null)
                            return first.name == "Abandoned Mine" ? 0 : 1;

                        //Card Card1 = purchaseOrder.GetPreferredCard(
                        //        gameState,
                        //        card => coinsToSpend >= card.CurrentCoinCost(currentPlayer) &&
                        //        gameState.GetPile(card).Any());
                        //Card Card2 = purchaseOrder.GetPreferredCard(
                        //        gameState,
                        //        card => coinsToSpend + 1 >= card.CurrentCoinCost(currentPlayer) &&
                        //        gameState.GetPile(card).Any());

                        //if (Card1 != Card2)
                        //    return first.name == "Abandoned Mine" ? 0 : 1;
                    }

                    return 0;
                }
            }

            public static CardPickByPriority TreasurePlayOrder()
            {
                return new CardPickByPriority(
                    CardAcceptance.For<CardTypes.Contraband>(),       // play early to provide opponent as little information when banning
                    // base set first
                    CardAcceptance.For<CardTypes.Platinum>(),
                    CardAcceptance.For<CardTypes.Gold>(),
                    CardAcceptance.For<CardTypes.Silver>(),
                    CardAcceptance.For<CardTypes.Copper>(),
                    CardAcceptance.For<CardTypes.Spoils>(),
                    // alphabetical, all other treasures that dont really depend on order
                    CardAcceptance.For<CardTypes.Cache>(),
                    CardAcceptance.For<CardTypes.FoolsGold>(),
                    CardAcceptance.For<CardTypes.Loan>(),
                    CardAcceptance.For<CardTypes.Harem>(),
                    CardAcceptance.For<CardTypes.Hoard>(),
                    CardAcceptance.For<CardTypes.Masterpiece>(),
                    CardAcceptance.For<CardTypes.PhilosophersStone>(),
                    CardAcceptance.For<CardTypes.Quarry>(),
                    CardAcceptance.For<CardTypes.Stash>(),
                    CardAcceptance.For<CardTypes.Talisman>(),
                    // cards whose benefit is sensitive to ordering
                    CardAcceptance.For<CardTypes.Venture>(),          // playing this card might increase the number of treasures played
                    CardAcceptance.For<CardTypes.CounterFeit>(),      // after venture so that you have more variety to counterfeit
                    CardAcceptance.For<CardTypes.IllGottenGains>(),   // by playing after venture, you have more information about whether to gain the copper
                    CardAcceptance.For<CardTypes.HornOfPlenty>(),     // play relatively last so it has the most variety of cards to trigger with
                    CardAcceptance.For<CardTypes.Bank>());            // try to make bank as valuable as possibile.
            }

            public static CardPickByPriority DefaultDiscardOrder()
            {
                return new CardPickByPriority(
                    CardAcceptance.For<CardTypes.Province>(),
                    CardAcceptance.For<CardTypes.Duchy>(),
                    CardAcceptance.For<CardTypes.Estate>(),
                    CardAcceptance.For<CardTypes.OvergrownEstate>(),
                    CardAcceptance.For<CardTypes.Hovel>(),
                    CardAcceptance.For<CardTypes.Ruins>(),
                    CardAcceptance.For<CardTypes.Copper>(),
                    CardAcceptance.For<CardTypes.Curse>());
            }

            public static ICardPicker DefaultTrashOrder()
            {
                return new CardPickByPriority(
                    CardAcceptance.For<CardTypes.Curse>(),
                    CardAcceptance.For<CardTypes.Estate>(gameState => CountOfPile<CardTypes.Province>(gameState) == 8),
                    CardAcceptance.For<CardTypes.OvergrownEstate>(),
                    CardAcceptance.For<CardTypes.Hovel>(),
                    CardAcceptance.For<CardTypes.Copper>());
            }

            public static bool ShouldBuyProvinces(GameState gameState)
            {
                return CountAllOwned<CardTypes.Gold>(gameState) > 2;
            }

            public static bool ShouldGainIllGottenGains(GameState gameState)
            {
                return CountOfPile<CardTypes.Curse>(gameState) > 0;
            }

            public static bool ShouldPlaySalvager(ICardPicker trashOrder, GameState gameState)
            {
                return HasCardFromInHand(trashOrder, gameState);
            }

            public static GameStatePredicate ShouldPlaySalvager(ICardPicker trashOrder)
            {
                return delegate(GameState gameState)
                {
                    return HasCardFromInHand(trashOrder, gameState);
                };
            }

            public static GameStatePredicate ShouldPlayLookout(GameStatePredicate shouldBuyProvinces = null)
            {
                if (shouldBuyProvinces == null)
                {
                    shouldBuyProvinces = ShouldBuyProvinces;
                }
                return delegate(GameState gameState)
                {
                    return ShouldPlayLookout(gameState, shouldBuyProvinces);
                };
            }

            public static bool ShouldPlayLookout(GameState gameState, GameStatePredicate shouldBuyProvinces)
            {
                int cardCountToTrash = CountInDeck<CardTypes.Copper>(gameState);

                if (!shouldBuyProvinces(gameState))
                {
                    cardCountToTrash += CountInDeck<CardTypes.Estate>(gameState);
                }

                cardCountToTrash += CountInDeck<CardTypes.Hovel>(gameState);
                cardCountToTrash += CountInDeck<CardTypes.Necropolis>(gameState);
                cardCountToTrash += CountInDeck<CardTypes.OvergrownEstate>(gameState);

                cardCountToTrash += CountInDeck<CardTypes.Lookout>(gameState);

                int totalCardsOwned = gameState.players.CurrentPlayer.CardsInDeck.Count();

                return ((double)cardCountToTrash) / totalCardsOwned > 0.4;
            }
        }

        public static class DoubleWarehouse
        {
            // big money smithy player
            public static PlayerAction Player(int playerNumber)
            {
                return new PlayerAction(
                            "DoubleWarehouse",
                            playerNumber,
                            purchaseOrder: PurchaseOrder(),
                            actionOrder: ActionOrder(),
                            discardOrder: DiscardOrder());
            }

            static CardPickByPriority PurchaseOrder()
            {
                return new CardPickByPriority(
                           CardAcceptance.For<CardTypes.Province>(gameState => gameState.players.CurrentPlayer.AllOwnedCards.Where(card => card is CardTypes.Gold).Count() > 2),
                           CardAcceptance.For<CardTypes.Duchy>(gameState => CountOfPile<CardTypes.Province>(gameState) < 5),
                           CardAcceptance.For<CardTypes.Estate>(gameState => CountOfPile<CardTypes.Province>(gameState) < 2),
                           CardAcceptance.For<CardTypes.Gold>(),
                           CardAcceptance.For<CardTypes.Warehouse>(gameState => gameState.players.CurrentPlayer.AllOwnedCards.Where(card => card is CardTypes.Warehouse).Count() < 1),
                           CardAcceptance.For<CardTypes.Warehouse>(gameState => gameState.players.CurrentPlayer.AllOwnedCards.Where(card => card is CardTypes.Silver).Count() > 2 &&
                                                                                gameState.players.CurrentPlayer.AllOwnedCards.Where(card => card is CardTypes.Warehouse).Count() < 2),
                           CardAcceptance.For<CardTypes.Silver>());

            }

            static CardPickByPriority ActionOrder()
            {
                return new CardPickByPriority(
                    CardAcceptance.For<CardTypes.Warehouse>());
            }

            static CardPickByPriority DiscardOrder()
            {
                return new CardPickByPriority(
                    CardAcceptance.For<CardTypes.Province>(),
                    CardAcceptance.For<CardTypes.Duchy>(),
                    CardAcceptance.For<CardTypes.Estate>(),
                    CardAcceptance.For<CardTypes.Copper>(),
                    CardAcceptance.For<CardTypes.Silver>(),
                    CardAcceptance.For<CardTypes.Warehouse>(),
                    CardAcceptance.For<CardTypes.Gold>());
            }
        }

        public static class BigMoneyDelayed
        {
            public static PlayerAction Player(int playerNumber)
            {
                return new PlayerAction(
                            "BigMoneyDelayed",
                            playerNumber,
                            purchaseOrder: PurchaseOrder());
            }

            private static CardPickByPriority PurchaseOrder()
            {
                return new CardPickByPriority(
                           CardAcceptance.For<CardTypes.Province>(gameState => CountAllOwned<CardTypes.Gold>(gameState) > 3),
                           CardAcceptance.For<CardTypes.Duchy>(gameState => CountOfPile<CardTypes.Province>(gameState) < 5),
                           CardAcceptance.For<CardTypes.Estate>(gameState => CountOfPile<CardTypes.Province>(gameState) <= 2),
                           CardAcceptance.For<CardTypes.Gold>(),
                           CardAcceptance.For<CardTypes.Estate>(gameState => CountOfPile<CardTypes.Province>(gameState) <= 2),
                           CardAcceptance.For<CardTypes.Silver>());
            }
        }
    }
}
Logged

Sparafucile

  • Thief
  • ****
  • Offline Offline
  • Posts: 98
  • Respect: +153
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #8 on: October 07, 2013, 08:45:17 pm »
0

Thanks for giving it a go.  It's not a super trivial thing to do so no worries.   I made the changes you were looking for and pushed them up to the depot.  Take a look at the changelist, and let  me know if it's not sufficient.
Logged

SCSN

  • Mountebank
  • *****
  • Offline Offline
  • Posts: 2227
  • Respect: +7140
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #9 on: October 08, 2013, 01:26:34 pm »
0

Thanks, I'll have a look at it later.

I spent quite some time today improving the new HermitMarketSquare strategy. Let me know what you think of it!

Code: [Select]
using Dominion;
using CardTypes = Dominion.CardTypes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Program
{
    public static partial class Strategies
    {
        public static class HermitMarketSquareImproved
        {
            public static PlayerAction Player(int playerNumber)
            {
                return new MyPlayerAction(playerNumber);
            }

            class MyPlayerAction
                : PlayerAction
            {
                public MyPlayerAction(int playerNumber)
                    : base("HermitMarketSquareImproved",
                        playerNumber,
                        purchaseOrder: PurchaseOrder(),
                        actionOrder: ActionOrder(TrashOrder()),
                        trashOrder: TrashOrder())
                {
                }

                private static CardPickByPriority PurchaseOrder()
                {
                    return new CardPickByPriority(
                               CardAcceptance.For<CardTypes.Province>(),
                               CardAcceptance.For<CardTypes.Duchy>(gameState => CountAllOwned<CardTypes.Province>(gameState) > 0),
                               CardAcceptance.For<CardTypes.Estate>(gameState => CountAllOwned<CardTypes.Province>(gameState) > 0),
                               CardAcceptance.For<CardTypes.Hermit>(ShouldGainHermit),
                               CardAcceptance.For<CardTypes.MarketSquare>(ShouldGainMarketSquare));
                }

                private static CardPickByPriority ActionOrder(ICardPicker trashOrder)
                {
                    return new CardPickByPriority(
                               CardAcceptance.For<CardTypes.Madman>(ShouldPlayMadman(trashOrder)),
                               CardAcceptance.For<CardTypes.Hermit>(IsDoingMegaTurn),
                               CardAcceptance.For<CardTypes.MarketSquare>(ShouldPlayMarketSquare(trashOrder)),
                               CardAcceptance.For<CardTypes.Hermit>(gameState => CountAllOwned<CardTypes.Madman>(gameState) -
                                   CountAllOwned<CardTypes.Hermit>(gameState) < 3),
                               CardAcceptance.For<CardTypes.Hermit>(gameState => CountAllOwned<CardTypes.Province>(gameState) > 0
                               ));                                       
                }

                private static CardPickByPriority TrashOrder()
                {
                    return new CardPickByPriority(
                               CardAcceptance.For<CardTypes.Estate>(gameState => IsDoingMegaTurn(gameState) && CountAllOwned<CardTypes.Province>(gameState) == 0),                               
                               CardAcceptance.For<CardTypes.Hermit>(IsDoingMegaTurn));
                }                     
            }

            private static bool ShouldGainHermit(GameState gameState)
            {
                if (PlayBigHermit(gameState))
                    return CountHermitsEverGained(gameState) < 9 && CountAllOwned<CardTypes.MarketSquare>(gameState) == 0;

                return CountHermitsEverGained(gameState) < 7 && CountAllOwned<CardTypes.MarketSquare>(gameState) == 0;
            }

            private static bool ShouldGainMarketSquare(GameState gameState)
            {
              PlayerState currentPlayer = gameState.players.CurrentPlayer;

              //Prioritize gaining Madmen over buying Market Squares once you have three squares
              if (CountAllOwned<CardTypes.Hermit>(gameState) > CountInDeckAndDiscard<CardTypes.Hermit>(gameState) +
                  CountInHand<CardTypes.Hermit>(gameState) &&
                  currentPlayer.Hand.Count < 4 &&
                  CountAllOwned<CardTypes.MarketSquare>(gameState) > 2)
                  return false;

              return true;
            }

            private static bool PlayBigHermit(GameState gameState)
            {
                //Play the 9-Hermit version if possible
                return CountOfPile<CardTypes.Hermit>(gameState) + CountHermitsEverGained(gameState) >= 9;
            }

            private static GameStatePredicate ShouldPlayMadman(ICardPicker trashOrder)
            {
                return delegate(GameState gameState)
                {
                    PlayerState currentPlayer = gameState.players.CurrentPlayer;

                    if (!currentPlayer.Hand.HasCard<CardTypes.Madman>())
                        return false;

                    if (CountAllOwned<CardTypes.Province>(gameState) > 0)
                        return true;

                    if (ShouldStartMegaTurn(gameState))
                        return true;

                    if (IsDoingMegaTurn(gameState))
                    {
                        if (currentPlayer.CardsInDeckAndDiscard.Count() > 5)
                            return true;
                    }

                    return false;
                };
            }

            private static GameStatePredicate ShouldPlayMarketSquare(ICardPicker trashOrder)
            {
                return delegate(GameState gameState)
                {
                    var currentPlayer = gameState.players.CurrentPlayer;

                    if (!IsDoingMegaTurn(gameState))
                    {
                        return !CanTrashForGold(gameState, trashOrder);                       
                    }
                    else
                    {

                        if (ShouldPlayMadman(trashOrder)(gameState))
                            return false;

                        int numberOfProvincesCanAfford = currentPlayer.ExpectedCoinValueAtEndOfTurn / Card.Type<CardTypes.Province>().CurrentCoinCost(currentPlayer);
                        if (currentPlayer.AvailableBuys < numberOfProvincesCanAfford)
                            return true;

                        return !CanTrashForGold(gameState, trashOrder);
                    }
                };
            }

            private static bool CanTrashForGold(GameState gameState, ICardPicker trashOrder)
            {
                PlayerState currentPlayer = gameState.players.CurrentPlayer;

                return trashOrder.GetPreferredCard(gameState, c => (currentPlayer.Hand.HasCard(c) || currentPlayer.Discard.HasCard(c)) && CardTypes.Hermit.CanTrashCard(c)) != null &&
                       currentPlayer.Hand.HasCard<CardTypes.Hermit>() &&
                       currentPlayer.Hand.HasCard<CardTypes.MarketSquare>();
            }

            private static bool ShouldStartMegaTurn(GameState gameState)
            {
                PlayerState currentPlayer = gameState.players.CurrentPlayer;

                int CountMSNotInPlay = CountInDeckAndDiscard<CardTypes.MarketSquare>(gameState) +
                  CountInHand<CardTypes.MarketSquare>(gameState);

                if(PlayBigHermit(gameState))
                    return currentPlayer.Hand.Count >= 5 &&
                           currentPlayer.Hand.Where(c => c.Is<CardTypes.Madman>()).Count() >= 2 &&
                           currentPlayer.AllOwnedCards.Where(c => c.Is<CardTypes.Madman>()).Count() >= 6 &&
                           CountMSNotInPlay >= 3 &&
                           CountHermitsEverGained(gameState) >= 9;

                return currentPlayer.Hand.Count >= 5 &&
                       currentPlayer.Hand.Where(c => c.Is<CardTypes.Madman>()).Count() >= 2 &&
                       currentPlayer.AllOwnedCards.Where(c => c.Is<CardTypes.Madman>()).Count() >= 4 &&
                       CountMSNotInPlay >= 3 &&
                       CountHermitsEverGained(gameState) >= 7;
            }

            private static bool IsDoingMegaTurn(GameState gameState)
            {
                PlayerState currentPlayer = gameState.players.CurrentPlayer;

                return currentPlayer.Hand.Count > 6;
            }

            private static int CountHermitsEverGained(GameState gameState)
            {
                PlayerState currentPlayer = gameState.players.CurrentPlayer;
                return CountAllOwned<CardTypes.Hermit>(gameState) + CountAllOwned<CardTypes.Madman>(gameState);
            }
        }
    }
}
Logged

Sparafucile

  • Thief
  • ****
  • Offline Offline
  • Posts: 98
  • Respect: +153
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #10 on: October 08, 2013, 08:21:13 pm »
0

Quote
I spent quite some time today improving the new HermitMarketSquare strategy. Let me know what you think of it!

Wow.  This strategy beats all the other ones right now.  Nicely done.   I just replaced the first version of the strategy, as we don't really need two of them around.
Logged

sudgy

  • Cartographer
  • *****
  • Offline Offline
  • Posts: 3431
  • Shuffle iT Username: sudgy
  • It's pronounced "SOO-jee"
  • Respect: +2707
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #11 on: October 08, 2013, 10:04:10 pm »
0

Just looking through the code, couldn't you make it better against other strategies by telling it to trash curses and ruins whenever possible?  It won't change it much, but it could be a bit better.

Edit: For Hermit/Market Square, that is.
Logged
If you're wondering what my avatar is, watch this.

Check out my logic puzzle blog!

   Quote from: sudgy on June 31, 2011, 11:47:46 pm

SCSN

  • Mountebank
  • *****
  • Offline Offline
  • Posts: 2227
  • Respect: +7140
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #12 on: October 09, 2013, 08:11:10 am »
0

Quote
I spent quite some time today improving the new HermitMarketSquare strategy. Let me know what you think of it!

Wow.  This strategy beats all the other ones right now.  Nicely done.   I just replaced the first version of the strategy, as we don't really need two of them around.

Thanks! I'm learning quite a bit from this, particularly how much execution details matters. The first script won only 18% against RebuildMonument iirc; some tweaking increased the win % to over 50. I already thought that people tend to focus too much on strategy and too little on execution details, but damn!

While incorporating sudgy's suggestion to always trash Curses and Ruins when it can, I tested it against BigMoneyCultist but got some very weird error. I then re-downloaded the program from GitHub and extracted it to a new location, but running

Code: [Select]
        static void Main()
        {
            var stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();

            ComparePlayers(Strategies.HermitMarketSquare.Player(1), Strategies.BigMoneyCultist.Player(2));
            //CompareStrategyVsAllKnownStrategies(Strategies.HermitMarketSquare.Player(1));
           
            stopwatch.Stop();

            System.Console.WriteLine("");
            System.Console.WriteLine("Elapsed Time per game: {0}us", stopwatch.ElapsedMilliseconds * 1000 / totalGameCount);
            System.Console.WriteLine("Elapsed Time per Players Turn: {0}ns", (int)((double) stopwatch.ElapsedTicks / System.Diagnostics.Stopwatch.Frequency * 1000 * 1000 * 1000 / GameState.turnTotalCount));
            Console.ReadLine();
        }

right out of the box--i.e. with HermitMarketSquare unaltered from the GitHub version--still produces this dialog box:



It outputs all the requested gamelogs correctly, but doesn't write any results to the console.
Logged

Sparafucile

  • Thief
  • ****
  • Offline Offline
  • Posts: 98
  • Respect: +153
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #13 on: October 09, 2013, 10:42:15 am »
0

Fixed the assert.  Sorry about that.
Logged

SCSN

  • Mountebank
  • *****
  • Offline Offline
  • Posts: 2227
  • Respect: +7140
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #14 on: October 09, 2013, 12:05:20 pm »
0

Thanks.

Changing the TrashOrder method to the one below makes it trash Curses and Ruins whenever it can and makes the strategy also work with Shelters.

Code: [Select]
private static CardPickByPriority TrashOrder()
{
return new CardPickByPriority(
   CardAcceptance.For<CardTypes.Curse>(),
   CardAcceptance.For<CardTypes.RuinedVillage>(),
   CardAcceptance.For<CardTypes.RuinedMarket>(),
   CardAcceptance.For<CardTypes.Survivors>(),
   CardAcceptance.For<CardTypes.RuinedLibrary>(),
   CardAcceptance.For<CardTypes.AbandonedMine>(),                               
   CardAcceptance.For<CardTypes.Necropolis>(gameState => IsDoingMegaTurn(gameState)),
   CardAcceptance.For<CardTypes.OvergrownEstate>(gameState => IsDoingMegaTurn(gameState)),
   CardAcceptance.For<CardTypes.Hovel>(gameState => IsDoingMegaTurn(gameState)),
   CardAcceptance.For<CardTypes.Estate>(gameState => IsDoingMegaTurn(gameState)),
   CardAcceptance.For<CardTypes.Hermit>(IsDoingMegaTurn));
}

It improves the win % from 87% to 95% against BigMoneyDoubleWitch, and from 70% to 90% against BigMoneyCultist. Incidentally, when I just told it to trash "Ruins" instead of spelling out each Ruins individually, it refused to trash any of them at all.
Logged

sudgy

  • Cartographer
  • *****
  • Offline Offline
  • Posts: 3431
  • Shuffle iT Username: sudgy
  • It's pronounced "SOO-jee"
  • Respect: +2707
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #15 on: October 09, 2013, 12:08:15 pm »
0

It improves the win % from 87% to 95% against BigMoneyDoubleWitch, and from 70% to 90% against BigMoneyCultist. Incidentally, when I just told it to trash "Ruins" instead of spelling out each Ruins individually, it refused to trash any of them at all.

Maybe you should have told it to trash "Ruin" ;)
Logged
If you're wondering what my avatar is, watch this.

Check out my logic puzzle blog!

   Quote from: sudgy on June 31, 2011, 11:47:46 pm

SCSN

  • Mountebank
  • *****
  • Offline Offline
  • Posts: 2227
  • Respect: +7140
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #16 on: October 09, 2013, 12:11:10 pm »
0

  • Ruins are called ruins instead of Ruin. 

:P
Logged

SCSN

  • Mountebank
  • *****
  • Offline Offline
  • Posts: 2227
  • Respect: +7140
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #17 on: October 12, 2013, 03:59:24 am »
0

I downloaded the latest version from GitHub and wanted to experiment a bit with your Remake/HT strategies, but it refuses to compile, giving:

Quote
Error   1   'Dominion.GameState' does not contain a definition for 'CurrentCardBeingPlayed' and no extension method 'CurrentCardBeingPlayed' accepting a first argument of type 'Dominion.GameState' could be found (are you missing a using directive or an assembly reference?)   C:\Google Drive\Dominion\Dominulator-master\TestProgram\Kingdoms\ShouldRemakeOrHorseTradersIntoSoothayer.cs   71

Error   2   'Dominion.GameState' does not contain a definition for 'CurrentCardBeingPlayed' and no extension method 'CurrentCardBeingPlayed' accepting a first argument of type 'Dominion.GameState' could be found (are you missing a using directive or an assembly reference?)   C:\Google Drive\Dominion\Dominulator-master\TestProgram\Kingdoms\ShouldRemakeOrHorseTradersIntoSoothayer.cs   90
Logged

Sparafucile

  • Thief
  • ****
  • Offline Offline
  • Posts: 98
  • Respect: +153
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #18 on: October 12, 2013, 12:51:03 pm »
0

Are you still getting the error around ShouldRemakeOrHorseTradersIntoSoothayer.cs?  I just compiled this morning and dont see the issue with what's latest on the head.
Logged

SCSN

  • Mountebank
  • *****
  • Offline Offline
  • Posts: 2227
  • Respect: +7140
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #19 on: October 12, 2013, 01:00:24 pm »
0

It's working with the latest download.
Logged

SCSN

  • Mountebank
  • *****
  • Offline Offline
  • Posts: 2227
  • Respect: +7140
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #20 on: October 15, 2013, 12:17:29 pm »
+1

Advisor:

Code: [Select]
    public class Advisor
        : Card
    {
        public static Advisor card = new Advisor();

        private Advisor()
            : base("Advisor", coinCost: 4, isAction: true,  plusActions:1)
        {
        }

        public override void DoSpecializedAction(PlayerState currentPlayer, GameState gameState)
        {
            currentPlayer.RevealCardsFromDeck(3);
            Card cardType = gameState.players.PlayerLeft.actions.BanCardForCurrentPlayerRevealedCards(gameState);
            if (!currentPlayer.cardsBeingRevealed.HasCard(cardType))
            {
                throw new Exception("Must ban a card currently being revealed");
            }
            currentPlayer.MoveRevealedCardToDiscard(cardType, gameState);
            currentPlayer.MoveAllRevealedCardsToHand();
        }
    }
Logged

Sparafucile

  • Thief
  • ****
  • Offline Offline
  • Posts: 98
  • Respect: +153
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #21 on: October 15, 2013, 12:40:21 pm »
0

Ty.  Advisor and Herald are now finished.
Logged

SCSN

  • Mountebank
  • *****
  • Offline Offline
  • Posts: 2227
  • Respect: +7140
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #22 on: October 15, 2013, 12:46:24 pm »
0

Ty.  Advisor and Herald are now finished.

Oh nice, I just started working on Herald and got the basic mechanics to function correctly but got the exception "With more than one card in revealed cards it's ambiguous which order to move cards on top of deck" to show up sometimes, which shouldn't occur because it always reveals one card only. I'm interested in how you did it :)

Edit: ah, I didn't include "currentPlayer.cardsBeingRevealed.RemoveCard(revealedCard);", I figured cards would be removed automatically upon being played.
« Last Edit: October 15, 2013, 12:49:33 pm by SheCantSayNo »
Logged

SCSN

  • Mountebank
  • *****
  • Offline Offline
  • Posts: 2227
  • Respect: +7140
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #23 on: October 15, 2013, 01:04:00 pm »
+1

Fortress:

Code: [Select]
    public class Fortress
      : Card
    {
        public static Fortress card = new Fortress();

        private Fortress()
            : base("Fortress", coinCost: 4, isAction: true, plusActions: 2, plusCards:1)
        {
        }

        public override void DoSpecializedTrash(PlayerState currentPlayer, GameState gameState)
        {           
            gameState.trash.RemoveCard(this);
            gameState.players.CurrentPlayer.hand.AddCard(this);
        }
    }

Edit: hmm, if your opponent trashes the Fortress with Saboteur/Knights/Rogue, he gets the card in his hand... I can't find an easy way to circumvent this, as there doesn't seem to be a way for a card to figure out its latest owner.

Btw, Saboteur is misspelled as "Sabateur" in the code.
« Last Edit: October 15, 2013, 01:28:53 pm by SheCantSayNo »
Logged

SCSN

  • Mountebank
  • *****
  • Offline Offline
  • Posts: 2227
  • Respect: +7140
    • View Profile
Re: Dominulator: A flexible dominion engine simulator
« Reply #24 on: October 15, 2013, 02:24:55 pm »
+1

Fixed Fortress:

Code: [Select]
    public class Fortress
      : Card
    {
        public static Fortress card = new Fortress();

        private Fortress()
            : base("Fortress", coinCost: 4, isAction: true, plusActions: 2, plusCards:1)
        {
        }

        public override void DoSpecializedTrash(PlayerState currentPlayer, GameState gameState)
        {           
            gameState.trash.RemoveCard(this);
            for (int i = 0; i < gameState.players.PlayerCount; i++)
            {
                if (gameState.players[i].CardsBeingRevealed.Contains(this))
                {
                    gameState.players[i].hand.AddCard(this);
                    return;
                }
            }
            currentPlayer.hand.AddCard(this);
        }
    }

It does require modifying the MoveRevealedCardToTrash method in PlayerState.cs:

Code: [Select]
internal void MoveRevealedCardToTrash(Card typeOfCard, GameState gameState)
        {
            this.MoveCardToTrash(typeOfCard, gameState);
            Card card = this.cardsBeingRevealed.RemoveCard(typeOfCard);
            if (card == null)
            {
                throw new Exception("Revealed cards did not have the specified card");
            }           
        }

It now trashes the card before removing it from the revealed cards stack, so that Fortress gets the chance to figure out which player is revealing it.
Logged
Pages: [1] 2  All
 

Page created in 0.064 seconds with 21 queries.