public static void Main()
double totalWinnerHP = 0;
var withFlanking = false;
if (verbose) iterations = 1;
for (int i = 1; i <= iterations; i++)
var encounter = new Encounter();
encounter.Side1 = new Creature[] {
encounter.Side2 = new Creature[side2Array];
for (int j = 0; j < side2Array; j++) {
encounter.Side2[j] = new StandardNPC("c"+j, side2Lvl);
encounter.Side2 = new Creature[] {
new StandardNPC("c1", 1),
new StandardNPC("c2", 1),
new StandardNPC("c3", 1),
encounter.RollForInitiative(verbose);
var roundStats = encounter.RunEncounter(withFlanking, verbose);
if (roundStats.Winner == 1) Side1Wins++;
if (roundStats.Winner == 2) Side2Wins++;
totalWinnerHP += roundStats.AverageWinnerHP;
totalRounds += roundStats.Rounds;
Console.WriteLine("Side1: " + Side1Wins + ", Side2: " + Side2Wins + ", Avg Rounds: " + (double)totalRounds / iterations + ", Avg Winner HP: " + (double)totalWinnerHP/iterations);
public Creature[] Side1 { get; set; } = new Creature[] { };
public Creature[] Side2 { get; set; } = new Creature[] { };
public int[] Side1Initiative { get; set; } = new int[] { };
public int[] Side2Initiative { get; set; } = new int[] { };
public int HighestInitiative { get; set; }
public int LowestInitiative { get; set; }
public void RollForInitiative(bool verbose = true)
Side1Initiative = new int[Side1.Length];
Side2Initiative = new int[Side2.Length];
foreach (var creature in Side1)
creature.EnemyList = Side2;
Side1Initiative[side1Id] = creature.RollInitiative();
foreach (var creature in Side2)
creature.EnemyList = Side1;
Side2Initiative[side2Id] = creature.RollInitiative();
HighestInitiative = Side1[0].Initiative;
LowestInitiative = Side1[0].Initiative;
foreach (var creature in Side1)
if (creature.Initiative > HighestInitiative) HighestInitiative = creature.Initiative;
if (creature.Initiative < LowestInitiative) LowestInitiative = creature.Initiative;
foreach (var creature in Side2)
if (creature.Initiative > HighestInitiative) HighestInitiative = creature.Initiative;
if (creature.Initiative < LowestInitiative) LowestInitiative = creature.Initiative;
foreach (var creature in Side1)
if (verbose) Console.WriteLine(creature.Name + ": HP " + creature.HP + " Init " + creature.Initiative);
foreach (var creature in Side2)
if (verbose) Console.WriteLine(creature.Name + ": HP " + creature.HP + " Init " + creature.Initiative);
public RoundStats RunEncounter(bool withFlanking = false, bool verbose = true)
foreach (var creature in Side1)
if (creature.HP > 0) Side1Members++;
foreach (var creature in Side2)
if (creature.HP > 0) Side2Members++;
if (verbose) Console.WriteLine("Round " + round);
for (int i = HighestInitiative; i >= LowestInitiative; i--)
foreach (var creature in Side1)
if (creature.Initiative == i)
Random rnd = new Random();
var flankingAtkBonus = 0;
var flankingDmgBonus = 0;
if (Side1Members > Side2Members) flankingAtkBonus = 2;
if (Side1Members > Side2Members) flankingDmgBonus = 1;
if (!withFlanking) { flankingAtkBonus = 0; flankingDmgBonus = 0; advantage = false; }
if (creature.HP > 0) creature.TakeTurn(flankingAtkBonus, flankingDmgBonus, advantage, verbose);
foreach (var creature in Side2)
if (creature.Initiative == i)
Random rnd = new Random();
var flankingAtkBonus = 0;
var flankingDmgBonus = 0;
if (Side2Members > Side1Members) flankingAtkBonus = 2;
if (Side2Members > Side1Members) flankingDmgBonus = 1;
if (!withFlanking) { flankingAtkBonus = 0; flankingDmgBonus = 0; advantage = false; }
if (creature.HP > 0) creature.TakeTurn(flankingAtkBonus, flankingDmgBonus, advantage, verbose);
foreach (var creature in Side1)
foreach (var creature in Side2)
if (Side1HP == 0 || Side2HP == 0) ongoing = false;
if (Side1HP == 0) return new RoundStats() { Winner = 2, Rounds = round - 1, AverageWinnerHP = (double) Side2HP / Side2.Length };
else return new RoundStats() { Winner = 1, Rounds = round - 1, AverageWinnerHP = (double) Side1HP / Side1.Length }; ;
public int Winner { get; set; }
public int Rounds { get; set; }
public double AverageWinnerHP { get; set; }
public abstract class Creature
public string Name { get; set; }
public int HP { get; set; }
public int Level { get; set; }
public int AttackBonus {get; set;}
public int DC { get; set; }
public int Toughness {get; set;}
public int Initiative { get; set; }
public Creature[] EnemyList { get; set; } = new Creature[] { };
public virtual int RollInitiative()
var initiative = Dice.Rolld20() + Level;
public virtual void TakeTurn(int atkModifier, int dmgModifier, bool advantage = false, bool verbose = true)
TakeAction(atkModifier, dmgModifier, advantage, verbose);
TakeAction(atkModifier-5, dmgModifier, advantage, verbose);
TakeAction(atkModifier-10, dmgModifier, advantage, verbose);
public virtual void TakeAction(int atkModifier, int dmgModifier, bool advantage = false, bool verbose = true)
foreach (var enemy in EnemyList)
Attack(enemy, atkModifier, dmgModifier, advantage, verbose);
public virtual void Attack(Creature target, int atkModifier, int dmgModifier, bool advantage = false, bool verbose = true)
var naturalAttackRoll = Dice.Rolld20();
var baseDamageRoll = Dice.Roll2d6();
if (naturalAttackRoll == 20) crit = true;
var advRoll = Dice.Rolld20();
if (advRoll > naturalAttackRoll) naturalAttackRoll = advRoll;
if (crit == true) critDamage = Dice.Roll2d6();
if (naturalAttackRoll == 1)
if (verbose) Console.WriteLine(String.Format("{0}({1}) attacks {2}, rolling {3}+{4}({5}) to hit, and misses.", Name, Initiative, target.Name, naturalAttackRoll, atkModifier + AttackBonus, naturalAttackRoll + atkModifier + AttackBonus));
else if (crit || naturalAttackRoll + atkModifier + AttackBonus >= target.DC)
var finalDamage = target.TakeDamage((baseDamageRoll + critDamage) + dmgModifier + Level);
if (verbose) Console.WriteLine(String.Format("{0}({1}) attacks {2}, rolling {3}+{4}({5}) to hit for {6}+{7}({8}) dmg, reduced to {9} by a Toughness of {10}, and leaving the target with {11} HP.", Name, Initiative, target.Name, naturalAttackRoll, atkModifier + AttackBonus, naturalAttackRoll + atkModifier + AttackBonus, baseDamageRoll + critDamage, Level, baseDamageRoll + critDamage + Level, finalDamage, target.Toughness, target.HP));
if (verbose) Console.WriteLine(String.Format("{0}({1}) attacks {2}, rolling {3}+{4}({5}) to hit, and misses.", Name, Initiative, target.Name, naturalAttackRoll, atkModifier + AttackBonus, naturalAttackRoll + atkModifier + AttackBonus));
public virtual int TakeDamage(int incomingDamage)
if (incomingDamage < 0) incomingDamage = 0;
var finalDamage = incomingDamage - Level;
if (finalDamage < 1) finalDamage = 1;
public class StandardNPC : Creature
public StandardNPC(string name, int level)
this.AttackBonus = Level;
this.HP = 30 + 3 * level;
public class SimpleNPC : Creature
public SimpleNPC(string name, int level)
this.AttackBonus = Level;
this.Toughness = Level+10;
public override void TakeTurn(int atkModifier, int dmgModifier, bool advantage = false, bool verbose = true)
TakeAction(atkModifier, dmgModifier, advantage, verbose);
public override int TakeDamage(int incomingDamage)
if (incomingDamage < 0) incomingDamage = 0;
if (incomingDamage >= Toughness) finalDamage = 2;
public class SoloNPC : Creature
public int MaxHP {get; set;}
public SoloNPC(string name, int level, int levelHPAdjustment)
this.AttackBonus = Level;
this.HP = (30 + 3* levelHPAdjustment + 3 * level)*3;
public override void TakeTurn(int atkModifier, int dmgModifier, bool advantage = false, bool verbose = true)
TakeAction(atkModifier, dmgModifier, advantage, verbose);
TakeAction(atkModifier-5, dmgModifier, advantage, verbose);
TakeAction(atkModifier-10, dmgModifier, advantage, verbose);
if (verbose) Console.WriteLine(Name+" takes Frenzy turn 1!");
TakeAction(atkModifier, dmgModifier, advantage, verbose);
TakeAction(atkModifier-5, dmgModifier, advantage, verbose);
TakeAction(atkModifier-10, dmgModifier, advantage, verbose);
if (verbose) Console.WriteLine(Name+" takes Frenzy turn 2!");
TakeAction(atkModifier, dmgModifier, advantage, verbose);
TakeAction(atkModifier-5, dmgModifier, advantage, verbose);
TakeAction(atkModifier-10, dmgModifier, advantage, verbose);
public static int Rolld20()
Random rnd = new Random();
public static int Roll2d6()
Random rnd = new Random();
return rnd.Next(1, 7) + rnd.Next(1, 7);