using System.Collections.Generic;
public const int Iterations = 100000;
public static readonly Dictionary<int, Building> Buildings = new ()
{ 5, new (){ Number = 5, Name = "Bergbaulager", UndestroyCost = 3 } },
{ 12, new (){ Number = 12, Name = "Jagdhütte", UndestroyCost = 3 } },
{ 17, new (){ Number = 17, Name = "Holzfällerlager", UndestroyCost = 3 } },
{ 34, new (){ Number = 34, Name = "Handwerker", UndestroyCost = 4 } },
{ 35, new (){ Number = 35, Name = "Alchemist", UndestroyCost = 3 } },
{ 84, new (){ Number = 84, Name = "Werkstatt", UndestroyCost = 4 } },
{ 98, new (){ Number = 98, Name = "Kaserne", UndestroyCost = 2 } },
public static void Main()
deck.Add(0, CardEffect.Defend, 6);
deck.Add(-10, CardEffect.Defend, 5);
deck.Add(10, CardEffect.Defend, 5);
deck.Add(-20, CardEffect.Defend, 1);
deck.Add(20, CardEffect.Defend, 1);
deck.Add(int.MinValue, CardEffect.Destroy, 1);
deck.Add(int.MaxValue, CardEffect.Success, 1);
public static AverageResult Test(int soldiers, Deck deck, int soldiersPerBuilding)
var conditions = new Conditions {
Buildings = new ( new [] { Buildings[84], Buildings[98], Buildings[12] } ),
UsableSoldiers = soldiers,
SoldiersPerBuilding = soldiersPerBuilding,
Console.Write($"Testing {soldiers} soldier(s), {soldiersPerBuilding} per building: ");
var player = new AttackEventPlayer(conditions);
var result = player.PlayMultiple(Iterations, deck);
Console.WriteLine(result.GetAveragesString());
public class AttackEventPlayer
public Conditions Conditions { get; }
public AttackEventPlayer(Conditions conditions)
public AverageResult PlayMultiple(int rounds, Deck deck)
var averageResult = new AverageResult();
averageResult.AddResult(Play(deck));
averageResult.UpdateAverages(Conditions.DestroyedCosts);
public Result Play(Deck deck)
var result = new Result();
var buildings = new Queue<Building>(Conditions.Buildings);
var soldiers = Conditions.UsableSoldiers;
var forceDisadvantage = false;
while(buildings.Count > 0 && buildings.Dequeue() is Building building)
var extraCard = deck.Draw();
if(extraCard.Defense < card.Defense)
for(int spb = Math.Min(Conditions.SoldiersPerBuilding, soldiers); spb > 0 ; spb--, soldiers--)
var extraCard = deck.Draw();
if(extraCard.Defense > card.Defense)
result.UndestroyCost += building.UndestroyCost;
if(building.Number == BuildingNumbers.Kaserne)
forceDisadvantage = true;
if(Conditions.ThreatLevel > Conditions.DefenseLevel + card.Defense)
public CardEffect Effect = 0;
public override string ToString() {
case CardEffect.Success: return "success";
case CardEffect.Destroy: return "destroy";
return Defense.ToString();
static Random Rng = Random.Shared;
List<Card> CardPool = new List<Card>();
Queue<Card> CurrentCards = new Queue<Card>();
public Deck(Card[] cardPool)
CardPool = new List<Card>(cardPool);
var cards = new List<Card>(CardPool);
CurrentCards = new (cards);
static void Shuffle<T>(IList<T> list)
for (int i = list.Count - 1; i > 0; i--)
(list[i], list[k]) = (list[k], list[i]);
if(CurrentCards.Count < 1)
return CurrentCards.Dequeue();
public void Add(int defense, CardEffect effect, int count = 1)
CardPool.Add(new Card { Defense = defense, Effect = effect } );
public int ThreatLevel { get; init;}
public Queue<Building> Buildings { get; init; }
public int DefenseLevel { get; init; }
public int UsableSoldiers { get; init; }
public int SoldiersPerBuilding { get; init; }
public double DestroyedCosts { get; init; } = 20f / 6;
public int Destroyed = 0;
public int SoldiersUsed = 0;
public int UndestroyCost = 0;
public class AverageResult
public int TotalRounds = 0;
public int TotalDamaged = 0;
public int TotalDestroyed = 0;
public int TotalSoldiers = 0;
public int TotalGoldCosts = 0;
public SortedDictionary<int, int> DestroyedCount = new ();
public SortedDictionary<int, int> MaterialCosts = new ();
public double AvgMaterials { get; private set; } = 0;
public double AvgDestroyed { get; private set; } = 0;
public double AvgDamaged { get; private set; } = 0;
public double AvgGoldCosts { get; private set; } = 0;
public void UpdateAverages(double destroyedCosts)
AvgMaterials = (TotalDamaged * 2f + TotalSoldiers + TotalDestroyed * destroyedCosts) / TotalRounds;
AvgDestroyed = 1f * TotalDestroyed / TotalRounds;
AvgDamaged = 1f * TotalDamaged / TotalRounds;
AvgGoldCosts = 1f * TotalGoldCosts / TotalRounds;
public void AddResult(Result result)
TotalDamaged += result.Damaged;
TotalDestroyed += result.Destroyed;
TotalSoldiers += result.SoldiersUsed;
TotalGoldCosts += result.SoldiersUsed * 3;
DestroyedCount.TryGetValue(result.Destroyed, out var destroyed);
DestroyedCount[result.Destroyed] = destroyed + 1;
var materialCosts = (int)(result.Damaged * 2f + result.SoldiersUsed + result.UndestroyCost);
MaterialCosts.TryGetValue(materialCosts, out var material);
MaterialCosts[materialCosts] = material + 1;
public string GetAveragesString()
return $"avg. materials: {AvgMaterials:0.##}, avg. destroyed: {AvgDestroyed:0.##}, avg. damaged: {AvgDamaged:0.##}, avg. gold costs: {AvgGoldCosts:0.##}";
public string GetMinMatCostString()
var str = new StringBuilder();
foreach(var pair in MaterialCosts)
str.AppendLine($"at least {pair.Key} materials: {100f * right / TotalRounds:0.##}%");
public string GetMinDestroyedString()
var str = new StringBuilder();
foreach(var pair in DestroyedCount)
str.AppendLine($"at least {pair.Key} destroyed: {100f * right / TotalRounds:0.##}%");
public static class BuildingNumbers
public const int Kaserne = 98;
public int Number { get; init; }
public string Name { get; init; }
public int RepairCost { get; init; } = 2;
public int UndestroyCost { get; init; }