Skip to content

Commit 8436577

Browse files
committed
refactored morale formula to match Kirills. Added tests for morale, they fail.
1 parent 86fc349 commit 8436577

File tree

8 files changed

+139
-61
lines changed

8 files changed

+139
-61
lines changed

TbsCore/Models/CombatModels/Combat.cs

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public class Combat
1919

2020
public (double, double) Raid()
2121
{
22-
CalculateBaseState();
2322
CalculateTotalPoints();
2423
var ratio = CalculateRatio();
2524
// Calc losses
@@ -28,7 +27,6 @@ public class Combat
2827

2928
public (double, double) Normal()
3029
{
31-
CalculateBaseState();
3230
CalculateTotalPoints();
3331
// CalculateRams()
3432
var ratio = CalculateRatio();
@@ -43,9 +41,9 @@ public class Combat
4341
private double CalculateRatio() => Math.Pow(CalculateBaseRatio(), GetImmensityFactor());
4442

4543
/// <summary>
46-
/// Get Morale/Population bonus
44+
/// Get Morale/Population bonus. Public for testing purposes
4745
/// </summary>
48-
private double GetMoraleBonus(long off, long deff)
46+
public double GetMoraleBonus(double ptsRatio = 1)
4947
{
5048
if (Deffender.DeffTribe == TribeEnum.Nature)
5149
{
@@ -59,25 +57,8 @@ private double GetMoraleBonus(long off, long deff)
5957
if (Deffender.Population == 0) return 1.5F;
6058
if (Attacker.Population == 0) return 1.0F;
6159

62-
double bonus;
63-
64-
if (deff < off)
65-
{
66-
// M^0.2, where M is attacker's population / defender's population
67-
bonus = Math.Pow((Attacker.Population / (double)Deffender.Population), 0.2F);
68-
}
69-
else
70-
{
71-
// If the attacker has fewer points than defender:
72-
// M ^{ 0.2·(offense points / defense points)}
73-
double exp = 0.2F * (off / (double)deff);
74-
bonus = Math.Pow((Attacker.Population / (double)Deffender.Population), exp);
75-
}
76-
77-
// Moralebonus never goes higher than +50%
78-
if (1.5F < bonus) bonus = 1.5;
79-
80-
return bonus;
60+
double popRatio = Attacker.Population / (double)Math.Max(Deffender.Population, 3);
61+
return Math.Max(0.667, Math.Pow(popRatio, -0.2F * Math.Min(ptsRatio, 1)));
8162
}
8263

8364
private double GetWallBonus() // getDefBonus
@@ -103,14 +84,14 @@ private int GetVillDeff() // getDefAbsolute
10384
return wallDeff + baseVillDeff + (int)palaceDeff;
10485
}
10586

106-
private (long, long) CalculateBaseState() // calcBasePoints
87+
/// <summary>
88+
/// Public for testing purposes
89+
/// </summary>
90+
public (long, long) CalculateBaseState() // calcBasePoints
10791
{
10892
var offPts = this.Attacker.Army.GetOffense();
109-
Console.WriteLine($"off = ${offPts.i}/${offPts.c}");
11093
var defPts = Deffender.GetDeffense();
111-
Console.WriteLine($"off = ${defPts.i}/${defPts.c}");
11294
var (off, def) = GetAducedDef(offPts, defPts);
113-
Console.WriteLine($"adduced = ${off}/${def}");
11495
baseState = ( off, def );
11596
return baseState;
11697
}
@@ -119,7 +100,7 @@ private int GetVillDeff() // getDefAbsolute
119100
{
120101
var (baseOff, baseDeff) = CalculateBaseState();
121102
var finalDef = RoundLong((baseDeff + GetVillDeff()) * GetWallBonus());
122-
var morale = GetMoraleBonus(baseOff, finalDef);
103+
var morale = GetMoraleBonus(baseOff / finalDef);
123104
var finalOff = RoundLong(baseOff * morale);
124105
finalState = (finalOff, finalDef);
125106
return finalState;

TbsCore/Models/CombatModels/CombatArmy.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ public class CombatArmy : CombatArmyBase
2828
var heroOff = Hero.GetOff();
2929

3030
var (troop, boost) = Hero.GetWeaponBoost();
31-
var weaponBoost = CombatPoints.both(base.Troops[(int)troop % 10] * boost);
31+
var weaponBoost = CombatPoints.Both(base.Troops[(int)troop % 10] * boost);
3232

33-
return CombatPoints.sum(new CombatPoints[]{
33+
return CombatPoints.Sum(new CombatPoints[]{
3434
unitsOff, heroOff, weaponBoost
3535
}).Mul(Hero.GetOffBonus());
3636
}
@@ -43,9 +43,9 @@ public class CombatArmy : CombatArmyBase
4343
var heroDeff = Hero.GetDeff();
4444

4545
var (troop, boost) = Hero.GetWeaponBoost();
46-
var weaponBoost = CombatPoints.both(base.Troops[(int)troop % 10] * boost);
46+
var weaponBoost = CombatPoints.Both(base.Troops[(int)troop % 10] * boost);
4747

48-
return CombatPoints.sum(new CombatPoints[]{
48+
return CombatPoints.Sum(new CombatPoints[]{
4949
unitsDeff, heroDeff, weaponBoost
5050
}).Mul(Hero.GetDeffBonus());
5151
}

TbsCore/Models/CombatModels/CombatArmyBase.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ public class CombatArmyBase
1414
public int[] Improvements { get; set; }
1515
public CombatHero Hero { get; set; }
1616

17+
/// <summary>
18+
/// For testing purposes only
19+
/// </summary>
20+
public bool IgnoreImprovements { get; set; }
21+
1722

1823
public void ApplyLosses(double losses)
1924
{
@@ -32,7 +37,7 @@ public CombatPoints GetOffense()
3237
if (Troops[i] == 0) continue;
3338
var troop = TroopsHelper.TroopFromInt(Tribe, i);
3439

35-
var lvl = Improvements == null ? 1 : Improvements[i];
40+
var lvl = Improvements == null || IgnoreImprovements ? 1 : Improvements[i];
3641
var off = TroopsData.GetTroopOff(troop, lvl);
3742

3843
if (TroopsData.IsInfantry(troop)) inf += off * Troops[i];
@@ -48,7 +53,7 @@ public CombatPoints GetDeffense()
4853
{
4954
if (Troops[i] == 0) continue;
5055
var troop = TroopsHelper.TroopFromInt(Tribe, i);
51-
var lvl = Improvements == null ? 1 : Improvements[i];
56+
var lvl = Improvements == null || IgnoreImprovements ? 1 : Improvements[i];
5257
var (deffInf, deffCav) = TroopsData.GetTroopDeff(troop, lvl);
5358

5459
inf += deffInf * Troops[i];

TbsCore/Models/CombatModels/CombatDeffender.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class CombatDeffender
3939

4040
public CombatPoints GetDeffense()
4141
{
42-
CombatPoints ret = new CombatPoints();
42+
CombatPoints ret = CombatPoints.Zero();
4343
foreach (var army in Armies)
4444
{
4545
ret.Add(army.GetDeffense());

TbsCore/Models/CombatModels/CombatHero.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class CombatHero
2424
public Dictionary<HeroItemCategory, HeroItemEnum> Items { get; set; }
2525

2626

27-
public CombatPoints GetOff() => CombatPoints.off(GetTotalStrength(), HasHorse());
27+
public CombatPoints GetOff() => CombatPoints.Off(GetTotalStrength(), !HasHorse());
2828
public CombatPoints GetDeff()
2929
{
3030
var str = GetTotalStrength();
@@ -39,10 +39,9 @@ public CombatPoints GetDeff()
3939
private int GetTotalStrength() => GetStrength() + GetItemsStrength();
4040
private int GetStrength()
4141
{
42-
if (Info == null) return 0;
4342
int power = 100; // Base hero power
4443
var levelMultiplier = Tribe == TribeEnum.Romans ? 100 : 80;
45-
power += levelMultiplier * Info.FightingStrengthPoints;
44+
power += levelMultiplier * Info?.FightingStrengthPoints ?? 0;
4645
return power;
4746
}
4847

TbsCore/Models/CombatModels/CombatPoints.cs

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,26 @@ public class CombatPoints
1212
public long i { get; set; }
1313
public long c { get; set; }
1414

15-
public static CombatPoints zero()
15+
public static CombatPoints Zero()
1616
{
1717
return new CombatPoints(0, 0);
1818
}
19-
public static CombatPoints off(long value, bool isInfantry)
19+
public static CombatPoints Off(long value, bool isInfantry)
2020
{
2121
return isInfantry
2222
? new CombatPoints(value, 0)
2323
: new CombatPoints(0, value);
2424
}
25-
public static CombatPoints both(int value)
25+
public static CombatPoints Both(int value)
2626
{
2727
return new CombatPoints(value, value);
2828
}
2929

3030
// operation methods
31-
public static CombatPoints add(CombatPoints a, CombatPoints b)
31+
public static CombatPoints Sum(CombatPoints[] arr)
3232
{
33-
return a.Add(b);
34-
}
35-
public static CombatPoints sum(CombatPoints[] ps)
36-
{
37-
var total = CombatPoints.zero();
38-
foreach(var cp in ps)
33+
var total = CombatPoints.Zero();
34+
foreach(var cp in arr)
3935
{
4036
total.i += cp.i;
4137
total.c += cp.c;
@@ -44,11 +40,6 @@ public static CombatPoints sum(CombatPoints[] ps)
4440
}
4541

4642
// instance methods
47-
public CombatPoints()
48-
{
49-
this.i = 0;
50-
this.c = 0;
51-
}
5243
public CombatPoints(long inf, long cav)
5344
{
5445
this.i = inf;
@@ -60,17 +51,15 @@ public CombatPoints(double inf, double cav)
6051
this.c = (long)Math.Round(cav, MidpointRounding.AwayFromZero);
6152
}
6253

63-
64-
public CombatPoints Add(CombatPoints that) {
54+
public void Add(CombatPoints that) {
6555
this.i += that.i;
6656
this.c += that.c;
67-
return new CombatPoints(this.i + that.i, this.c + that.c);
6857
}
6958
public CombatPoints Mul(double m) {
7059
return new CombatPoints(this.i * m, this.c * m);
7160
}
72-
public CombatPoints Mask(CombatPoints mask) {
73-
return new CombatPoints(0 < mask.i ? this.i : 0, 0 < mask.c ? this.c : 0);
74-
}
61+
//public CombatPoints Mask(CombatPoints mask) {
62+
// return new CombatPoints(0 < mask.i ? this.i : 0, 0 < mask.c ? this.c : 0);
63+
//}
7564
}
7665
}

TbsCoreTest/CombatSimulationTest.cs

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using TbsCore.Helpers;
34
using TbsCore.Models.CombatModels;
45
using TbsCore.Models.ResourceModels;
@@ -14,7 +15,7 @@ namespace TbsCoreTest
1415
{
1516
public class ResSpendingTest
1617
{
17-
[Fact]
18+
//[Fact]
1819
public void TestCasualtiesRatio()
1920
{
2021
var factory = new CombatFactory();
@@ -59,14 +60,118 @@ public void TestCasualtiesRatio()
5960
return (ratio1, ratio2);
6061
}
6162

63+
[Fact]
64+
public void TestRealDeff()
65+
{
66+
var factory = new CombatFactory();
67+
var (deffenders, attackers) = factory.GetBoth();
68+
69+
Assert.Equal((7000, 4000), GetBaseVals(attackers[0], deffenders[0]));
70+
Assert.Equal((7000, 137500), GetBaseVals(attackers[0], deffenders[1]));
71+
Assert.Equal((7000, 289500), GetBaseVals(attackers[0], deffenders[2]));
72+
73+
Assert.Equal((261000, 4598), GetBaseVals(attackers[1], deffenders[0]));
74+
Assert.Equal((261000, 131523), GetBaseVals(attackers[1], deffenders[1]));
75+
Assert.Equal((261000, 294580), GetBaseVals(attackers[1], deffenders[2]));
76+
77+
Assert.Equal((640000, 4563), GetBaseVals(attackers[2], deffenders[0]));
78+
Assert.Equal((640000, 131875), GetBaseVals(attackers[2], deffenders[1]));
79+
Assert.Equal((640000, 294281), GetBaseVals(attackers[2], deffenders[2]));
80+
}
81+
82+
[Fact]
83+
public void TestMorale()
84+
{
85+
Combat combat = new Combat()
86+
{
87+
Attacker = new CombatAttacker() { Population = 1 },
88+
Deffender = new CombatDeffender() { Population = 1 }
89+
};
90+
91+
Assert.Equal(1, combat.GetMoraleBonus());
92+
93+
combat.Attacker.Population = 2;
94+
Assert.Equal(1.08, Rnd(combat.GetMoraleBonus()));
95+
96+
combat.Attacker.Population = 3;
97+
Assert.Equal(1, combat.GetMoraleBonus());
98+
99+
combat.Attacker.Population = 4;
100+
Assert.Equal(0.94, Rnd(combat.GetMoraleBonus()));
101+
102+
combat.Attacker.Population = 2;
103+
combat.Deffender.Population = 2;
104+
Assert.Equal(1, combat.GetMoraleBonus());
105+
106+
combat.Attacker.Population = 3;
107+
Assert.Equal(1, combat.GetMoraleBonus());
108+
109+
combat.Attacker.Population = 4;
110+
combat.Deffender.Population = 2;
111+
Assert.Equal(0.94, Rnd(combat.GetMoraleBonus()));
112+
113+
combat.Attacker.Population = 3;
114+
combat.Deffender.Population = 3;
115+
Assert.Equal(1, combat.GetMoraleBonus());
116+
117+
combat.Attacker.Population = 100;
118+
combat.Deffender.Population = 10;
119+
Assert.Equal(0.67, Rnd(combat.GetMoraleBonus()));
120+
121+
combat.Attacker.Population = 50;
122+
combat.Deffender.Population = 10;
123+
Assert.Equal(0.73, Rnd(combat.GetMoraleBonus()));
124+
125+
combat.Attacker.Population = 20;
126+
combat.Deffender.Population = 10;
127+
Assert.Equal(0.87, Rnd(combat.GetMoraleBonus()));
128+
129+
combat.Attacker.Population = 100;
130+
combat.Deffender.Population = 100;
131+
Assert.Equal(1, combat.GetMoraleBonus());
132+
133+
combat.Attacker.Population = 10;
134+
combat.Deffender.Population = 100;
135+
Assert.Equal(1, combat.GetMoraleBonus());
136+
137+
combat.Attacker.Population = 100;
138+
combat.Deffender.Population = 10;
139+
Assert.Equal(0.79, Rnd(combat.GetMoraleBonus(0.5)));
140+
141+
combat.Attacker.Population = 20;
142+
combat.Deffender.Population = 10;
143+
Assert.Equal(0.87, Rnd(combat.GetMoraleBonus(2)));
144+
}
145+
146+
private (long, long) GetBaseVals(CombatAttacker attacker, CombatDeffender deffender)
147+
{
148+
// For "base" values (upper right corner of http://travian.kirilloid.ru/warsim2.php)
149+
// we have to ignore the troop improvements
150+
attacker.Army.IgnoreImprovements = true;
151+
deffender.Armies.ForEach(x => x.IgnoreImprovements = true);
152+
153+
Combat combat = new Combat()
154+
{
155+
Attacker = attacker,
156+
Deffender = deffender
157+
};
158+
return combat.CalculateBaseState();
159+
}
160+
62161
[Fact]
63162
public void TestImprovedOff()
64163
{
65164
Assert.Equal(40.00, Rnd(TroopsData.GetTroopOff(Classificator.TroopsEnum.Clubswinger, 1)));
66165
Assert.Equal(44.74, Rnd(TroopsData.GetTroopOff(Classificator.TroopsEnum.Clubswinger, 8)));
67166
Assert.Equal(50.40, Rnd(TroopsData.GetTroopOff(Classificator.TroopsEnum.Clubswinger, 17)));
68167
Assert.Equal(52.36, Rnd(TroopsData.GetTroopOff(Classificator.TroopsEnum.Clubswinger, 20)));
168+
169+
Assert.Equal((45.97, 56.69), Rnd(TroopsData.GetTroopDeff(Classificator.TroopsEnum.Phalanx, 10)));
170+
Assert.Equal((52.36, 63.86), Rnd(TroopsData.GetTroopDeff(Classificator.TroopsEnum.Phalanx, 20)));
171+
172+
Assert.Equal((65.76, 49.11), Rnd(TroopsData.GetTroopDeff(Classificator.TroopsEnum.AshWarden, 15)));
69173
}
70174
private double Rnd(double val) => Math.Round(val, 2, MidpointRounding.AwayFromZero);
175+
private (double, double) Rnd((double, double) val) => (Rnd(val.Item1), Rnd(val.Item2));
71176
}
72177
}

version.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)