Пример #1
0
        public void BuyCard_can_use_bonus()
        {
            var card       = new Card(1, 0, new Pool(0, 0, 2, 0, 2, 1), TokenColour.Black);
            var cardInPlay = new Card(1, 0, new Pool(), TokenColour.Green);
            var purse      = new Pool(0, 1, 2, 0, 1, 1);

            var player = DefaultGame.CurrentPlayer with {
                Purse = purse, CardsInPlay = new[] { cardInPlay }
            };

            var tiers = DefaultGame.Tiers.ToList();

            tiers.RemoveAll(r => r.Tier == 1);
            tiers.Add(new BoardTier(1, new[] { card }, 1));

            var gameState = DefaultGame.WithUpdatedPlayerByName(player) with {
                Tiers = tiers
            };

            var payment = BuyCard.CreateDefaultPaymentOrNull(player, card);

            Assert.AreEqual(4, payment.Sum);
            var action    = new BuyCard(card, payment);
            var nextState = action.Execute(gameState);
        }
Пример #2
0
        public void CannotAffordCard_if_wrong_coins()
        {
            var card   = new Card(1, 0, new Pool(0, 1, 2, 0, 0, 0), TokenColour.Black);
            var budget = new Pool(0, 1, 1, 0, 0, 1);
            var player = new Player("").Clone(withPurse: budget);

            Assert.False(BuyCard.CanAffordCard(player, card));
        }
Пример #3
0
        public void Default_payment_is_cost_when_no_gold_needed()
        {
            var   card    = new Card(1, 0, new Pool(0, 1, 0, 1, 0, 1), TokenColour.Black);
            var   budget  = new Pool(0, 2, 2, 2, 2, 2);
            var   player  = new Player("").Clone(withPurse: budget);
            IPool payment = BuyCard.CreateDefaultPaymentOrNull(player, card);

            Assert.That(PoolsAreExactlyEquivilent(card.Cost, payment));
        }
Пример #4
0
        public void CanAffordCard_uses_gold()
        {
            // Costs 1 white, 2 blue.
            var card = new Card(1, 0, new Pool(0, 1, 2, 0, 0, 0), TokenColour.Black);

            var budget = new Pool(1, 1, 1, 0, 0, 0);
            var player = new Player("").Clone(withPurse: budget);

            Assert.True(BuyCard.CanAffordCard(player, card));
        }
Пример #5
0
        public void Default_payment_is_null_when_cannot_afford()
        {
            var card = new Card(1, 0, new Pool(0, 1, 0, 1, 0, 0), TokenColour.Black);

            var budget = new Pool(1, 0, 0, 0, 1, 1);
            var player = new Player("").Clone(withPurse: budget);

            IPool payment = BuyCard.CreateDefaultPaymentOrNull(player, card);

            Assert.Null(payment);
        }
Пример #6
0
        private char GetBuyIndicator(Card card, Player player)
        {
            if (card == null)
            {
                return(' ');
            }

            // '*' can afford
            // '·' can almost afford
            // ' ' simply cannot afford

            var buyIndicator = BuyCard.CanAffordCard(player, card)
                   ? '*'
                   : player.Budget.DeficitFor(card.Cost).Sum <= 3 ? '·' : ' ';

            return(buyIndicator);
        }
Пример #7
0
        public IAction ChooseAction(GameState gameState)
        {
            var me = gameState.CurrentPlayer;

            bool CanBuy(Card card) => BuyCard.CanAffordCard(me, card);

            var allFaceUpCards = gameState.Tiers
                                 .SelectMany(t => t.ColumnSlots)
                                 .Select(s => s.Value)
                                 .Where(card => card != null) // If a stack runs out of cards, a slot will be null
                                 .ToArray();

            // First, if I can buy a card that gives victory points, I always do.
            foreach (var card in allFaceUpCards.Concat(me.ReservedCards)
                     .Where(c => c.VictoryPoints > 0)
                     .OrderByDescending(c => c.VictoryPoints)
                     .Where(CanBuy))
            {
                var payment = BuyCard.CreateDefaultPaymentOrNull(me, card);
                return(new BuyCard(card, payment));
            }

            // I look at all the cards I can see and choose one that looks the best in terms of accessibility
            var bestCardStudy = AnalyseCards(me, allFaceUpCards.Concat(me.ReservedCards), gameState)
                                .OrderBy(s => s.DifficultyRating)
                                .FirstOrDefault();

            // Second, if the most accessible card is purchasable, I buy it.
            if (CanBuy(bestCardStudy.Card))
            {
                return(new BuyCard(bestCardStudy.Card, BuyCard.CreateDefaultPaymentOrNull(me, bestCardStudy.Card)));
            }

            // Third, I try and get rid of reserved cards.
            foreach (var card in me.ReservedCards.Where(CanBuy))
            {
                return(new BuyCard(card, BuyCard.CreateDefaultPaymentOrNull(me, card)));
            }

            // Fourth, I check if I've got loads of coins and if so, I buy any card I can afford
            if (me.Purse.Sum > 8)
            {
                foreach (var card in allFaceUpCards.Where(CanBuy))
                {
                    var payment = BuyCard.CreateDefaultPaymentOrNull(me, card);
                    return(new BuyCard(card, payment));
                }
            }

            // Fifth, I top up my coins, favouring colours needed by the most accessible card.
            var coloursAvailable   = gameState.Bank.Colours(includeGold: false).ToList();
            var coinsCountICanTake = Math.Min(Math.Min(10 - me.Purse.Sum, 3), coloursAvailable.Count);

            if (coinsCountICanTake > 0)
            {
                if (bestCardStudy != null)
                {
                    var coloursNeeded = bestCardStudy.Deficit.Colours().ToArray();
                    coloursAvailable = coloursAvailable.OrderByDescending(col => coloursNeeded.Contains(col)).ToList();
                }
                else
                {
                    coloursAvailable.Shuffle();
                }
                var transaction = new Pool();
                if (bestCardStudy.Deficit.Colours().Any(col => bestCardStudy.Deficit[col] >= 2) && coinsCountICanTake > 1)
                {
                    var neededColour = bestCardStudy.Deficit.Colours().First(col => bestCardStudy.Deficit[col] >= 2);
                    if (gameState.Bank[neededColour] > 3)
                    {
                        transaction[neededColour] = 2;
                        return(new TakeTokens(transaction));
                    }
                }
                foreach (var colour in coloursAvailable.Take(coinsCountICanTake))
                {
                    transaction[colour] = 1;
                }
                return(new TakeTokens(transaction));
            }

            // Sixth, if it comes to it, I just reserve the best looking card I can see.
            if (!me.ReservedCards.Contains(bestCardStudy.Card) && me.ReservedCards.Count < 3)
            {
                return(new ReserveCard(bestCardStudy.Card));
            }

            // Seventh, If I've already reserved the best looking card, I take a gamble.
            var action = ChooseRandomCardOrNull(gameState);

            if (action != null)
            {
                return(action);
            }

            // Lastly, if I think I can't do anything at all I just pass the turn.
            return(new NoAction());
        }
Пример #8
0
        public IAction ChooseAction(GameState gameState)
        {
            /* PRE-CALCULATIONS */

            var me           = gameState.CurrentPlayer;
            var otherPlayers = gameState.Players.Where(p => p != me);

            bool CanBuy(Card card) => BuyCard.CanAffordCard(me, card);

            var allFaceUpCards = gameState.Tiers.SelectMany(t => t.ColumnSlots)
                                 .Select(s => s.Value)
                                 .Where(card => card != null)
                                 .ToArray();

            var faceUpAndMyReserved = allFaceUpCards.Concat(me.ReservedCards).ToArray();

            var cardsICanBuy = faceUpAndMyReserved.Where(CanBuy).ToArray();

            var myOrderedCardStudy = AnalyseCards(me, faceUpAndMyReserved, gameState)
                                     .OrderBy(s => s.Repulsion).ToArray();

            var myTargetCard = myOrderedCardStudy.FirstOrDefault();

            /* BEHAVIOUR */

            // Check to see if a player can win (including me)
            if (_options.IsThieving)
            {
                foreach (var player in gameState.Players.OrderByDescending(p => p == me))
                {
                    var score = player.VictoryPoints;

                    var nobleDangerMap = new List <TokenColour>();
                    foreach (var noble in gameState.Nobles)
                    {
                        var deficit = player.Bonuses.DeficitFor(noble.Cost);
                        if (deficit.Sum != 1)
                        {
                            continue;
                        }
                        nobleDangerMap.Add(deficit.Colours().Single());
                    }

                    if (score < 10 && !nobleDangerMap.Any())
                    {
                        continue;
                    }

                    var winCards = allFaceUpCards.Where(c => c.VictoryPoints + score + (nobleDangerMap.Contains(c.BonusGiven) ? 3 : 0) >= 15)
                                   .Where(c => BuyCard.CanAffordCard(player, c))
                                   .OrderByDescending(c => c.VictoryPoints + (nobleDangerMap.Contains(c.BonusGiven) ? 3 : 0))
                                   .ToArray();

                    if (winCards.Length == 0)
                    {
                        continue;
                    }
                    if (player != me && winCards.Length > 1)
                    {
                        break;                                  // We're screwed if he has more than one winning move.
                    }
                    var winningCard = winCards.First();
                    if (cardsICanBuy.Contains(winningCard))
                    {
                        return(new BuyCard(winningCard, BuyCard.CreateDefaultPaymentOrNull(me, winningCard)));
                    }
                    if (player != me && me.ReservedCards.Count < 3)
                    {
                        return(new ReserveCard(winningCard));
                    }
                }
            }

            // If the second best card isn't very appealing, then check to see if I should reserve the first.
            if (_options.IsVeryTheiving && myOrderedCardStudy.Length > 1)
            {
                var second              = myOrderedCardStudy.Skip(1).First();
                var firstIsAmazing      = myTargetCard.Repulsion < -5m;
                var sedcondIsntAmazing  = second.Repulsion > -5m;
                var otherPlayerCanBuy   = otherPlayers.Any(p => BuyCard.CanAffordCard(p, myTargetCard.Card));
                var iCanAffordIfReserve = myTargetCard.DeficitWithGold == 1;
                if (firstIsAmazing &&
                    sedcondIsntAmazing &&
                    otherPlayerCanBuy &&
                    iCanAffordIfReserve &&
                    me.ReservedCards.Count < 3 &&
                    !me.ReservedCards.Contains(myTargetCard.Card))
                {
                    return(new ReserveCard(myTargetCard.Card));
                }
            }

            // Buy favourite card if possible
            if (myTargetCard?.DeficitWithGold == 0)
            {
                return(new BuyCard(myTargetCard.Card, BuyCard.CreateDefaultPaymentOrNull(me, myTargetCard.Card)));
            }

            // If I have 9 or more coins, or there aren't many coins left to take, buy any reasonable card I can at all
            if (me.Purse.Sum > 8 || gameState.Bank.Colours(includeGold: false).Count() < 3)
            {
                foreach (var study in myOrderedCardStudy.Where(s => s.DeficitWithGold == 0))
                {
                    var payment = BuyCard.CreateDefaultPaymentOrNull(me, study.Card);
                    return(new BuyCard(study.Card, payment));
                }
            }

            /* more precalc */
            CardFeasibilityStudy myNextTargetCard = null;

            if (_options.LooksAhead && myTargetCard != null)
            {
                var nextCards = me.CardsInPlay.ToList();
                nextCards.Add(myTargetCard.Card);
                var nextMe = me with {
                    CardsInPlay = nextCards
                };
                myNextTargetCard = AnalyseCards(nextMe, faceUpAndMyReserved.Except(new[] { myTargetCard.Card }).ToArray(), gameState)
                                   .OrderBy(s => s.Repulsion)
                                   .FirstOrDefault();
            }

            // Take some coins - but if there are coins to return then favour reserving a card
            var takeTokens = GetTakeTokens(gameState, myTargetCard, myNextTargetCard);

            if (takeTokens != null && !takeTokens.TokensToReturn.Colours().Any())
            {
                return(takeTokens);
            }

            // Do a reserve
            if (myTargetCard != null && !me.ReservedCards.Contains(myTargetCard.Card) && me.ReservedCards.Count < 3)
            {
                return(new ReserveCard(myTargetCard.Card));
            }

            // Do a random reserve if not maxxed out.
            var action = ChooseRandomCardOrNull(gameState);

            if (action != null && _options.RandomReserves)
            {
                return(action);
            }

            // do the give/take coins if possible
            return(takeTokens ?? (IAction) new NoAction());
        }
Пример #9
0
        private IAction GetActionFromInput(string input, GameState state)
        {
            var i = input.ToLowerInvariant().Trim();

            if (i.StartsWith("h"))
            {
                Console.WriteLine("Commands: refer to cards buy their tier and index, e.g. '1-2' for tier 1, 2nd card.");
                Console.WriteLine("    t: take tokens, e.g. 't ugb' for take blUe, Green, Black.");
                Console.WriteLine("    b: buy card. e.g. 'b 1-2', or 'b res' to buy a reserved card (choice made for you).");
                Console.WriteLine("    r: reserve card. e.g. 'r 1-2', or just 'r 1' to reserve a random face down card from tier 1.");
                Console.WriteLine("Instruments:");
                Console.WriteLine("    *: you can afford this card.");
                Console.WriteLine("    ·: you can almost afford this card.");
                return(null);
            }

            if (i.StartsWith("t"))
            {
                var   whiteSpace     = i.IndexOf(' ');
                var   codes          = i.Substring(whiteSpace);
                var   tokens         = TokenPoolFromInput(codes);
                IPool tokensToReturn = new Pool();
                var   commaIndex     = codes.IndexOf(',');
                if (commaIndex != -1)
                {
                    tokensToReturn = TokenPoolFromInput(codes.Substring(commaIndex + 1));
                }
                return(new TakeTokens(tokens, tokensToReturn));
            }
            ;

            if (i.StartsWith("b"))
            {
                var whiteSpace = i.IndexOf(' ');
                var args       = i.Substring(whiteSpace).Trim();
                if (args.StartsWith("res"))
                {
                    var resCard = state.CurrentPlayer.ReservedCards.OrderByDescending(c => c.VictoryPoints).FirstOrDefault(c => BuyCard.CanAffordCard(state.CurrentPlayer, c));
                    if (resCard == null)
                    {
                        throw new ArgumentException("You can't afford any of your reserved cards.");
                    }
                    var resPayment = BuyCard.CreateDefaultPaymentOrNull(state.CurrentPlayer, resCard);
                    if (resPayment == null)
                    {
                        throw new ArgumentException("You cannot afford this card or did not specify sufficient payment.");
                    }
                    return(new BuyCard(resCard, resPayment));
                }
                if (args[1] != '-')
                {
                    throw new ArgumentException($"Syntax error. Usage: 'b 1-2' for buying the second card in tier one.");
                }
                var tier      = int.Parse(args[0].ToString());
                var cardIndex = int.Parse(args[2].ToString());
                var card      = state.Tiers.Single(t => t.Tier == tier).ColumnSlots[cardIndex];
                if (card == null)
                {
                    throw new ArgumentException("There's no card in that slot. The tier {tier} deck has been exhasted.");
                }
                var payment = args.Length > 3 ? TokenPoolFromInput(args.Substring(3)) : BuyCard.CreateDefaultPaymentOrNull(state.CurrentPlayer, card);
                if (payment == null)
                {
                    throw new ArgumentException("You cannot afford this card or did not specify sufficient payment.");
                }
                return(new BuyCard(card, payment));
            }

            if (i.StartsWith("r"))
            {
                var   whiteSpace     = i.IndexOf(' ');
                var   args           = i.Substring(whiteSpace).Trim();
                var   tier           = int.Parse(args[0].ToString());
                IPool tokensToReturn = new Pool();
                var   commaIndex     = args.IndexOf(',');
                if (commaIndex != -1)
                {
                    tokensToReturn = TokenPoolFromInput(args.Substring(commaIndex + 1));
                }
                var colourToReturn = tokensToReturn.Colours().Cast <TokenColour?>().SingleOrDefault();
                if (args.Length == 1)
                {
                    return(new ReserveFaceDownCard(tier, colourToReturn));
                }
                if (args[1] != '-')
                {
                    throw new ArgumentException($"Syntax error. Usage: 'r 1-2' for reserving the second card in tier one.");
                }
                var cardIndex = int.Parse(args[2].ToString());
                var card      = state.Tiers.Single(t => t.Tier == tier).ColumnSlots[cardIndex];
                return(new ReserveCard(card, colourToReturn));
            }

            throw new NotImplementedException("Don't know that one.");
        }