Adds unit test for gen 7 damage library

This commit is contained in:
Deukhoofd 2024-07-28 10:41:12 +02:00
parent 9186d0efcc
commit 864dda6644
9 changed files with 153 additions and 19 deletions

View File

@ -10,12 +10,12 @@ public interface IDamageCalculator
/// <summary>
/// Calculate the damage for a given hit on a Pokemon.
/// </summary>
uint GetDamage(IExecutingMove executingMove, IPokemon target, byte hitNumber, HitData hitData);
uint GetDamage(IExecutingMove executingMove, IPokemon target, byte hitNumber, IHitData hitData);
/// <summary>
/// Calculate the base power for a given hit on a Pokemon.
/// </summary>
byte GetBasePower(IExecutingMove executingMove, IPokemon target, byte hitNumber, HitData hitData);
byte GetBasePower(IExecutingMove executingMove, IPokemon target, byte hitNumber, IHitData hitData);
/// <summary>
/// Returns whether a specified hit should be critical or not.

View File

@ -4,44 +4,71 @@ using PkmnLib.Static.Moves;
namespace PkmnLib.Dynamic.Models;
/// <summary>
/// A hit data is the data for a single hit, on a single target.
/// </summary>
public record HitData
public interface IHitData
{
/// <summary>
/// Whether the hit is critical.
/// </summary>
public bool IsCritical { get; internal set; }
bool IsCritical { get; }
/// <summary>
/// The base power of the hit.
/// </summary>
public byte BasePower { get; internal set; }
byte BasePower { get; }
/// <summary>
/// The effectiveness of the hit.
/// </summary>
public float Effectiveness { get; internal set; }
float Effectiveness { get; }
/// <summary>
/// The damage done by the hit.
/// </summary>
public uint Damage { get; internal set; }
uint Damage { get; }
/// <summary>
/// The type of the hit.
/// </summary>
public TypeIdentifier Type { get; internal set; }
TypeIdentifier Type { get; }
/// <summary>
/// Whether the hit has failed.
/// </summary>
public bool HasFailed { get; private set; }
bool HasFailed { get; }
/// <summary>
/// Fails the hit.
/// </summary>
void Fail();
bool Equals(HitData? other);
bool Equals(object? other);
int GetHashCode();
string ToString();
}
/// <inheritdoc />
public record HitData : IHitData
{
/// <inheritdoc />
public bool IsCritical { get; internal set; }
/// <inheritdoc />
public byte BasePower { get; internal set; }
/// <inheritdoc />
public float Effectiveness { get; internal set; }
/// <inheritdoc />
public uint Damage { get; internal set; }
/// <inheritdoc />
public TypeIdentifier Type { get; internal set; }
/// <inheritdoc />
public bool HasFailed { get; private set; }
/// <inheritdoc />
public void Fail() => HasFailed = true;
}

View File

@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkmnLib.Plugin.Gen7", "Pkmn
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{63C1B450-DC26-444A-AEBD-15979F3EEE53}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PkmnLib.Plugin.Gen7.Tests", "PkmnLib.Plugin.Gen7.Tests\PkmnLib.Plugin.Gen7.Tests.csproj", "{FBB53861-081F-4DAC-B006-79EE238D0DFC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -32,8 +34,13 @@ Global
{FA5380F0-28CC-4AEC-8963-814B347A89BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA5380F0-28CC-4AEC-8963-814B347A89BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA5380F0-28CC-4AEC-8963-814B347A89BA}.Release|Any CPU.Build.0 = Release|Any CPU
{FBB53861-081F-4DAC-B006-79EE238D0DFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FBB53861-081F-4DAC-B006-79EE238D0DFC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FBB53861-081F-4DAC-B006-79EE238D0DFC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FBB53861-081F-4DAC-B006-79EE238D0DFC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{FA5380F0-28CC-4AEC-8963-814B347A89BA} = {63C1B450-DC26-444A-AEBD-15979F3EEE53}
{FBB53861-081F-4DAC-B006-79EE238D0DFC} = {63C1B450-DC26-444A-AEBD-15979F3EEE53}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,10 @@
namespace PkmnLib.Plugin.Gen7.Tests;
public class BattleStatCalculatorTests
{
[Test]
public void Test1()
{
Assert.Pass();
}
}

View File

@ -0,0 +1,62 @@
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Plugin.Gen7.Libraries;
using PkmnLib.Static;
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Tests;
public class DamageCalculatorTests
{
/// <summary>
/// Implements the following example from Bulbapedia:
/// </summary>
/// <para>
/// Imagine a level 75 Glaceon that does not suffer a burn and holds no item with an effective Attack stat of 123
/// uses Ice Fang (an Ice-type physical move with a power of 65) against a Garchomp with an effective Defense stat
/// of 163 in Generation VI, and does not land a critical hit. Then, the move will receive STAB, because Glaceon's
/// Ice type matches the move's: STAB = 1.5. Additionally, Garchomp is Dragon/Ground, and therefore has a double
/// weakness to the move's Ice type: Type = 4. All other (non-random) modifiers will be 1.
///
/// That means Ice Fang will do between 168 and 196 HP damage, depending on luck.
/// </para>
[Test]
public void BulbapediaExampleDamageTest()
{
var attacker = new Mock<IPokemon>();
// Imagine a level 75 Glaceon
attacker.Setup(x => x.Level).Returns(75);
// with an effective Attack stat of 123
attacker.Setup(x => x.BoostedStats).Returns(new StatisticSet<uint>(
1, 123, 1, 1, 1, 1));
// We use 10 as the Ice type
attacker.Setup(x => x.Types).Returns([new TypeIdentifier(10)]);
var defender = new Mock<IPokemon>();
// a Garchomp with an effective Defense stat of 163
defender.Setup(x => x.BoostedStats).Returns(new StatisticSet<uint>(
1, 1, 163, 1, 1, 1));
var useMove = new Mock<IMoveData>();
// Ice Fang (an Ice-type physical move with a power of 65)
useMove.Setup(x => x.Category).Returns(MoveCategory.Physical);
var damageCalculator = new Gen7DamageCalculator(false);
var executingMove = new Mock<IExecutingMove>();
executingMove.Setup(x => x.UseMove).Returns(useMove.Object);
executingMove.Setup(x => x.User).Returns(attacker.Object);
executingMove.Setup(x => x.GetScripts()).Returns(new ScriptIterator([]));
var hit = new Mock<IHitData>();
// Ice Fang (an Ice-type physical move with a power of 65)
hit.Setup(x => x.BasePower).Returns(65);
hit.Setup(x => x.Type).Returns(new TypeIdentifier(10));
// has a double weakness to the move's Ice type
hit.Setup(x => x.Effectiveness).Returns(4.0f);
var damage = damageCalculator.GetDamage(executingMove.Object, defender.Object, 0, hit.Object);
// That means Ice Fang will do between 168 and 196 HP damage, depending on luck.
// Note that we are testing deterministic damage, so we expect the maximum damage.
Assert.That(damage, Is.EqualTo(196));
}
}

View File

@ -0,0 +1,2 @@
global using NUnit.Framework;
global using Moq;

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/>
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PkmnLib.Dynamic\PkmnLib.Dynamic.csproj" />
<ProjectReference Include="..\PkmnLib.Plugin.Gen7\PkmnLib.Plugin.Gen7.csproj" />
</ItemGroup>
</Project>

View File

@ -48,7 +48,7 @@ public class Gen7BattleStatCalculator : IBattleStatCalculator
return (uint)boostedStat;
}
private uint CalculateHealthStat(IPokemon pokemon)
private static uint CalculateHealthStat(IPokemon pokemon)
{
var baseValue = (ulong)pokemon.Form.BaseStats.Hp;
var iv = (ulong)pokemon.IndividualValues.Hp;
@ -59,7 +59,7 @@ public class Gen7BattleStatCalculator : IBattleStatCalculator
return (uint)health;
}
private uint CalculateNormalStat(IPokemon pokemon, Statistic statistic)
private static uint CalculateNormalStat(IPokemon pokemon, Statistic statistic)
{
var baseValue = (ulong)pokemon.Form.BaseStats.GetStatistic(statistic);
var iv = (ulong)pokemon.IndividualValues.GetStatistic(statistic);
@ -72,7 +72,7 @@ public class Gen7BattleStatCalculator : IBattleStatCalculator
return (uint)modified;
}
private float GetStatBoostModifier(IPokemon pokemon, Statistic statistic)
private static float GetStatBoostModifier(IPokemon pokemon, Statistic statistic)
{
var boost = pokemon.StatBoost.GetStatistic(statistic);
return boost switch

View File

@ -12,7 +12,7 @@ namespace PkmnLib.Plugin.Gen7.Libraries;
public class Gen7DamageCalculator(bool hasRandomness) : IDamageCalculator
{
/// <inheritdoc />
public uint GetDamage(IExecutingMove executingMove, IPokemon target, byte hitNumber, HitData hitData)
public uint GetDamage(IExecutingMove executingMove, IPokemon target, byte hitNumber, IHitData hitData)
{
var category = executingMove.UseMove.Category;
if (category == MoveCategory.Status)
@ -71,7 +71,7 @@ public class Gen7DamageCalculator(bool hasRandomness) : IDamageCalculator
}
/// <inheritdoc />
public byte GetBasePower(IExecutingMove executingMove, IPokemon target, byte hitNumber, HitData hitData)
public byte GetBasePower(IExecutingMove executingMove, IPokemon target, byte hitNumber, IHitData hitData)
{
if (executingMove.UseMove.Category == MoveCategory.Status)
return 0;
@ -99,7 +99,7 @@ public class Gen7DamageCalculator(bool hasRandomness) : IDamageCalculator
};
}
private static float GetStatModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber, HitData hitData)
private static float GetStatModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber, IHitData hitData)
{
var category = executingMove.UseMove.Category;
if (category == MoveCategory.Status)
@ -146,7 +146,7 @@ public class Gen7DamageCalculator(bool hasRandomness) : IDamageCalculator
/// to apply a raw modifier to the damage.
/// </summary>
private static float GetDamageModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber,
HitData hitData)
IHitData hitData)
{
var modifier = 1.0f;