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()); }
private TakeTokens GetTakeTokens(GameState gameState, CardFeasibilityStudy firstChoice, CardFeasibilityStudy secondChoice) { if (firstChoice == null) { return(null); } var me = gameState.CurrentPlayer; var coloursByPreference = gameState.Bank.Colours(includeGold: false).ToList(); var coinsCountICanTake = Math.Min(Math.Min(10 - me.Purse.Sum, 3), coloursByPreference.Count); var coloursNeeded = firstChoice.Deficit.Colours().ToList(); var coloursNeeded2 = secondChoice?.Deficit.Colours().ToList(); var ordering = coloursByPreference.OrderByDescending(col => coloursNeeded.Contains(col)); if (secondChoice != null) { ordering = ordering.ThenByDescending(col => coloursNeeded2.Contains(col)); } coloursByPreference = ordering.ToList(); if (coinsCountICanTake > 0) { var deficitColours = firstChoice.Deficit.Colours().ToArray(); var transaction = new Pool(); if (_options.CanTakeTwo && deficitColours.Length == 1 && deficitColours.Any(col => firstChoice.Deficit[col] >= 2) && coinsCountICanTake > 1) { var neededColour = deficitColours.Single(); if (gameState.Bank[neededColour] > 3) { transaction[neededColour] = 2; return(new TakeTokens(transaction)); } } foreach (var colour in coloursByPreference.Take(coinsCountICanTake)) { transaction[colour] = 1; } return(new TakeTokens(transaction)); } // otherwise just swap a single coin towards what we need if (coloursByPreference.Count == 0) { return(null); } var colourToTake = coloursByPreference.First(); var colourToGiveBack = me.Purse.Colours() .OrderBy(c => c == TokenColour.Gold || c == colourToTake) .Cast <TokenColour?>() .FirstOrDefault(); if (!colourToGiveBack.HasValue) { return(null); } var take = new Pool(); var give = new Pool(); take[colourToTake] = 1; give[colourToGiveBack.Value] = 1; return(new TakeTokens(take, give)); }