Support for integration tests, fixes

This commit is contained in:
Deukhoofd 2024-08-23 11:15:53 +02:00
parent e7dc885afd
commit 2a0aaed4c3
13 changed files with 346 additions and 14 deletions

View File

@ -516,7 +516,7 @@ ij_javascript_wrap_comments = false
[{*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.postman_collection,*.postman_collection.json,*.postman_environment,*.postman_environment.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,.ws-context,jest.config}]
indent_size = 2
ij_json_array_wrapping = split_into_lines
ij_json_array_wrapping = normal
ij_json_keep_blank_lines_in_code = 0
ij_json_keep_indents_on_empty_lines = false
ij_json_keep_line_breaks = true

View File

@ -26,7 +26,7 @@ public static class TypeDataLoader
if (!types.Any())
throw new InvalidDataException("No types found in type data.");
foreach (var type in types)
foreach (var type in types.Skip(1))
library.RegisterType(type);
while (!reader.EndOfStream)

View File

@ -223,7 +223,7 @@ public class BattleImpl : ScriptSource, IBattle
/// <inheritdoc />
public bool CanUse(ITurnChoice choice)
{
if (choice.User.IsUsable)
if (!choice.User.IsUsable)
return false;
if (choice is IMoveChoice moveChoice)
{

View File

@ -1,3 +1,5 @@
using PkmnLib.Dynamic.ScriptHandling;
namespace PkmnLib.Dynamic.Models.Choices;
/// <summary>
@ -7,3 +9,24 @@ public interface IPassChoice : ITurnChoice
{
}
public class PassChoice : TurnChoice, IPassChoice
{
public PassChoice(IPokemon user) : base(user)
{
}
/// <inheritdoc />
public override int ScriptCount => User.ScriptCount;
/// <inheritdoc />
public override void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts)
{
}
/// <inheritdoc />
public override void CollectScripts(List<IEnumerable<ScriptContainer>> scripts)
{
User.CollectScripts(scripts);
}
}

View File

@ -271,7 +271,7 @@ public interface IPokemon : IScriptSource
/// <summary>
/// Learn a move by name.
/// </summary>
void LearnMove(string moveName, MoveLearnMethod method, byte index);
void LearnMove(StringKey moveName, MoveLearnMethod method, byte index);
/// <summary>
/// Removes the current non-volatile status from the Pokemon.
@ -658,7 +658,7 @@ public class PokemonImpl : ScriptSource, IPokemon
/// <remarks>
/// If the index is 255, it will try to find the first empty move slot.
/// </remarks>
public void LearnMove(string moveName, MoveLearnMethod method, byte index)
public void LearnMove(StringKey moveName, MoveLearnMethod method, byte index)
{
if (index == 255)
{

View File

@ -8,6 +8,10 @@ namespace PkmnLib.Dynamic.ScriptHandling.Registry;
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
public abstract class Plugin
{
protected Plugin(PluginConfiguration configuration)
{
}
/// <summary>
/// The name of the plugin. Mostly used for debugging purposes.
/// </summary>
@ -24,3 +28,7 @@ public abstract class Plugin
/// </summary>
public abstract void Register(ScriptRegistry registry);
}
public abstract class PluginConfiguration
{
}

View File

@ -10,5 +10,13 @@ public class TypeDataloaderTests
using var file = File.Open("Data/Types.csv", FileMode.Open);
var library = TypeDataLoader.LoadTypeLibrary(file);
Assert.That(library, Is.Not.Null);
var fire = library.TryGetTypeIdentifier("Fire", out var fireId);
Assert.That(fire, Is.True);
var grass = library.TryGetTypeIdentifier("Grass", out var grassId);
Assert.That(grass, Is.True);
var fireEffectiveness = library.GetSingleEffectiveness(fireId, grassId);
Assert.That(fireEffectiveness, Is.EqualTo(2.0f));
}
}

View File

@ -0,0 +1,101 @@
using System.Collections;
using System.Text.Json;
using PkmnLib.Dataloader;
using PkmnLib.Dynamic.Libraries;
using PkmnLib.Dynamic.Models;
using PkmnLib.Plugin.Gen7;
using PkmnLib.Static.Libraries;
using PkmnLib.Static.Species;
using PkmnLib.Tests.Integration.Models;
namespace PkmnLib.Tests.Integration;
public class IntegrationTestRunner
{
private static IEnumerable TestCases
{
get
{
var files = Directory.GetFiles("Integration/Tests", "*.json");
var serializerOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
foreach (var file in files)
{
var json = File.ReadAllText(file);
var test = JsonSerializer.Deserialize<IntegrationTestModel>(json, serializerOptions)!;
yield return new TestCaseData(test).SetName(test.Name).SetDescription(test.Description);
}
}
}
private static IDynamicLibrary LoadLibrary()
{
using var typesFile = File.Open("Data/Types.csv", FileMode.Open);
var types = TypeDataLoader.LoadTypeLibrary(typesFile);
using var naturesFile = File.Open("Data/Natures.csv", FileMode.Open);
var natures = NatureDataLoader.LoadNatureLibrary(naturesFile);
using var movesFile = File.Open("Data/Moves.json", FileMode.Open);
var moves = MoveDataLoader.LoadMoves(movesFile, types);
using var itemsFile = File.Open("Data/Items.json", FileMode.Open);
var items = ItemDataLoader.LoadItems(itemsFile);
using var abilitiesFile = File.Open("Data/Abilities.json", FileMode.Open);
var abilities = AbilityDataLoader.LoadAbilities(abilitiesFile);
using var growthRatesFile = File.Open("Data/GrowthRates.json", FileMode.Open);
var growthRates = GrowthRateDataLoader.LoadGrowthRates(growthRatesFile);
using var speciesFile = File.Open("Data/Pokemon.json", FileMode.Open);
var species = SpeciesDataLoader.LoadSpecies(speciesFile, types);
var staticLibrary = new StaticLibraryImpl(new LibrarySettings()
{
MaxLevel = 100,
ShinyRate = 4096,
}, species, moves, abilities, types, natures, growthRates, items);
var dynamicLibrary = DynamicLibraryImpl.Create(staticLibrary, [
new Gen7Plugin(new Gen7PluginConfiguration()
{
DamageCalculatorHasRandomness = false,
}),
]);
return dynamicLibrary;
}
[TestCaseSource(nameof(TestCases))]
public void RunIntegrationTest(IntegrationTestModel test)
{
var library = LoadLibrary();
var parties = test.BattleSetup.Parties.Select(IBattleParty (x) =>
{
var party = new PokemonParty(6);
for (var index = 0; index < x.Pokemon.Length; index++)
{
var pokemon = x.Pokemon[index];
Assert.That(library.StaticLibrary.Species.TryGet(pokemon.Species, out var species), Is.True);
var mon = new PokemonImpl(library, species!, species!.GetDefaultForm(), new AbilityIndex
{
IsHidden = false,
Index = 0,
},
pokemon.Level, 0, Gender.Genderless, 0, "hardy");
foreach (var move in pokemon.Moves)
{
mon.LearnMove(move, MoveLearnMethod.Unknown, 255);
}
party.SwapInto(mon, index);
}
return new BattlePartyImpl(party, x.Indices.Select(y => new ResponsibleIndex(y[0], y[1])).ToArray());
}).ToArray();
var battle = new BattleImpl(library, parties, test.BattleSetup.CanFlee, test.BattleSetup.NumberOfSides,
test.BattleSetup.PositionsPerSide);
foreach (var action in test.Actions)
{
action.Execute(battle);
}
}
}

View File

@ -0,0 +1,79 @@
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using CSPath;
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;
using JsonSerializer = System.Text.Json.JsonSerializer;
namespace PkmnLib.Tests.Integration.Models;
[JsonDerivedType(typeof(SetPokemonAction), "setPokemon")]
[JsonDerivedType(typeof(SetMoveChoiceAction), "setMoveChoice")]
[JsonDerivedType(typeof(SetPassChoiceAction), "setPassChoice")]
[JsonDerivedType(typeof(AssertAction), "assert")]
public abstract class IntegrationTestAction
{
public abstract void Execute(IBattle battle);
}
public class SetPokemonAction : IntegrationTestAction
{
public List<byte> Place { get; set; } = null!;
public List<byte> FromParty { get; set; } = null!;
public override void Execute(IBattle battle)
{
var mon = battle.Parties[FromParty[0]].Party[FromParty[1]];
battle.Sides[Place[0]].SwapPokemon(Place[1], mon);
}
}
public class SetMoveChoiceAction : IntegrationTestAction
{
public List<byte> Place { get; set; } = null!;
public string Move { get; set; } = null!;
public List<byte> Target { get; set; } = null!;
/// <inheritdoc />
public override void Execute(IBattle battle)
{
var user = battle.Sides[Place[0]].Pokemon[Place[1]];
Assert.That(user, Is.Not.Null);
var move = user.Moves.First(m => m?.MoveData.Name == Move);
Assert.That(move, Is.Not.Null);
var res = battle.TrySetChoice(new MoveChoice(user, move, Target[0], Target[1]));
Assert.That(res, Is.True);
}
}
public class SetPassChoiceAction : IntegrationTestAction
{
public List<byte> Place { get; set; } = null!;
/// <inheritdoc />
public override void Execute(IBattle battle)
{
var user = battle.Sides[Place[0]].Pokemon[Place[1]];
Assert.That(user, Is.Not.Null);
var res = battle.TrySetChoice(new PassChoice(user));
Assert.That(res, Is.True);
}
}
public class AssertAction : IntegrationTestAction
{
public string Value { get; set; } = null!;
public JsonNode Expected { get; set; } = null!;
/// <inheritdoc />
public override void Execute(IBattle battle)
{
var list = battle.Path(Value).ToList();
var value = list.Count == 1 ? list[0] : list;
var serialized = JsonSerializer.Serialize(value);
Assert.That(serialized, Is.EqualTo(Expected.ToJsonString()));
}
}

View File

@ -0,0 +1,31 @@
namespace PkmnLib.Tests.Integration.Models;
public class IntegrationTestModel
{
public string Name { get; set; } = null!;
public string Description { get; set; } = null!;
public IntegrationTestBattleSetup BattleSetup { get; set; } = null!;
public IntegrationTestAction[] Actions { get; set; } = null!;
}
public class IntegrationTestBattleSetup
{
public int Seed { get; set; }
public bool CanFlee { get; set; }
public byte NumberOfSides { get; set; }
public byte PositionsPerSide { get; set; }
public IntegrationTestParty[] Parties { get; set; } = null!;
}
public class IntegrationTestParty
{
public List<List<byte>> Indices { get; set; } = null!;
public IntegrationTestPokemon[] Pokemon { get; set; } = null!;
}
public class IntegrationTestPokemon
{
public string Species { get; set; } = null!;
public LevelInt Level { get; set; }
public string[] Moves { get; set; } = null!;
}

View File

@ -0,0 +1,64 @@
{
"name": "BasicSingleTurn",
"description": "An extremely basic test that checks if a single turn of a battle works correctly. This test has two Pokemon, a Charizard and a Venusaur, and the Charizard uses Ember on the Venusaur.",
"battleSetup": {
"seed": 10,
"canFlee": false,
"numberOfSides": 2,
"positionsPerSide": 1,
"parties": [
{
"indices": [[0, 0]],
"pokemon": [
{
"species": "charizard",
"level": 50,
"moves": ["ember"]
}
]
},
{
"indices": [[1, 0]],
"pokemon": [
{
"species": "venusaur",
"level": 50,
"moves": ["vine_whip"]
}
]
}
]
},
"actions": [
{
"$type": "setPokemon",
"place": [0, 0],
"fromParty": [0, 0]
},
{
"$type": "setPokemon",
"place": [1, 0],
"fromParty": [1, 0]
},
{
"$type": "assert",
"value": ".Sides[1].Pokemon[0].CurrentHealth",
"expected": 140
},
{
"$type": "setMoveChoice",
"place": [0, 0],
"move": "ember",
"target": [1, 0]
},
{
"$type": "setPassChoice",
"place": [1, 0]
},
{
"$type": "assert",
"value": ".Sides[1].Pokemon[0].CurrentHealth",
"expected": 78
}
]
}

View File

@ -10,7 +10,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="CSPath" Version="0.0.4" />
<PackageReference Include="FluentAssertions" Version="6.12.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
@ -19,19 +20,23 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PkmnLib.Dataloader\PkmnLib.Dataloader.csproj" />
<ProjectReference Include="..\PkmnLib.Dynamic\PkmnLib.Dynamic.csproj" />
<ProjectReference Include="..\PkmnLib.Static\PkmnLib.Static.csproj" />
<ProjectReference Include="..\PkmnLib.Dataloader\PkmnLib.Dataloader.csproj"/>
<ProjectReference Include="..\PkmnLib.Dynamic\PkmnLib.Dynamic.csproj"/>
<ProjectReference Include="..\PkmnLib.Static\PkmnLib.Static.csproj"/>
<ProjectReference Include="..\Plugins\PkmnLib.Plugin.Gen7\PkmnLib.Plugin.Gen7.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Data\" />
<Folder Include="Data\"/>
</ItemGroup>
<ItemGroup>
<None Update="Data\*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Integration\Tests\*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -3,8 +3,21 @@ using PkmnLib.Plugin.Gen7.Libraries;
namespace PkmnLib.Plugin.Gen7;
public class Gen7PluginConfiguration : PluginConfiguration
{
public bool DamageCalculatorHasRandomness { get; set; }
}
public class Gen7Plugin : Dynamic.ScriptHandling.Registry.Plugin
{
private readonly Gen7PluginConfiguration _configuration;
/// <inheritdoc />
public Gen7Plugin(PluginConfiguration configuration) : base(configuration)
{
_configuration = (Gen7PluginConfiguration)configuration;
}
/// <inheritdoc />
public override string Name => "Gen7";
@ -16,7 +29,7 @@ public class Gen7Plugin : Dynamic.ScriptHandling.Registry.Plugin
{
registry.RegisterAssemblyScripts(typeof(Gen7Plugin).Assembly);
registry.RegisterBattleStatCalculator(new Gen7BattleStatCalculator());
registry.RegisterDamageCalculator(new Gen7DamageCalculator(true));
registry.RegisterDamageCalculator(new Gen7DamageCalculator(_configuration.DamageCalculatorHasRandomness));
registry.RegisterMiscLibrary(new Gen7MiscLibrary());
}
}