Add project files.

This commit is contained in:
2023-02-28 19:15:03 -08:00
parent 24cdd62c8a
commit c13f80e2ab
24 changed files with 459 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
using RPGLibrary.Abstraction.Character;
using RPGLibrary.Abstraction.Services;
using RPGLibrary.Command;
namespace RPGLibrary.Implementation.Command
{
public class AttackCommand : ICommand
{
private readonly Character[] _target;
private readonly StrengthAttackData _attackData;
private readonly IDamageCalculator<StrengthAttackData> _damageCalculator;
public AttackCommand(Character[] target, StrengthAttackData attackData, IDamageCalculator<StrengthAttackData> damageCalculator)
{
_target = target;
_attackData = attackData;
_damageCalculator = damageCalculator;
}
public async IAsyncEnumerable<ICharacter> Execute()
{
foreach (var character in _target)
{
var damage = _damageCalculator.Calculate(character, (dynamic)_attackData);
var newCurrentHP = new HP(character.HP.Value - damage, character.HP.Maximum);
yield return character.With(newCurrentHP);
}
}
}
}

View File

@@ -0,0 +1,20 @@
using RPGLibrary.Abstraction.Attribute;
namespace RPGLibrary.Implementation
{
public class DefenseAttribute : IAttribute
{
public DefenseAttribute(double value)
{
Value = value;
}
public double Value { get; }
public override string? ToString()
{
return $"Defense: {Value}";
}
}
}

View File

@@ -0,0 +1,18 @@
using RPGLibrary.Attribute;
namespace RPGLibrary.Implementation
{
public class HP : PoolAttribute
{
public HP(double current, double maximum)
: base(current, maximum)
{
}
public override string? ToString()
{
return $"HP: ({Value}/{Maximum})";
}
}
}

View File

@@ -0,0 +1,13 @@
namespace RPGLibrary.Implementation
{
public class Speed
{
public Speed(double value)
{
Value = value;
}
public double Value { get; }
}
}

View File

@@ -0,0 +1,20 @@
using RPGLibrary.Abstraction.Attribute;
namespace RPGLibrary.Implementation
{
public class StrengthAttribute : IAttribute
{
public StrengthAttribute(double value)
{
Value = value;
}
public double Value { get; }
public override string? ToString()
{
return $"Strength: {Value}";
}
}
}

View File

@@ -0,0 +1,31 @@
using RPGLibrary.Abstraction.Character;
namespace RPGLibrary.Implementation
{
public class Character : ICharacter
{
public Character(string name, HP hp, StrengthAttribute strength, DefenseAttribute defense)
{
Name = name;
HP = hp;
Strength = strength;
Defense = defense;
}
public string Name { get; }
public HP HP { get; private set; }
public StrengthAttribute Strength { get; }
public DefenseAttribute Defense { get; }
public override string? ToString()
{
return $"{HP}, {Strength}, {Defense}";
}
public ICharacter With(HP hp) => new Character(Name, hp, Strength, Defense);
}
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\RPGLibrary\RPGLibrary.Abstraction.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
using RPGLibrary.Abstraction.Character;
namespace RPGLibrary.Implementation
{
public record StrengthAttackData(ICharacter Source, StrengthAttribute Strength)
: AttackData(Source, Strength);
}

View File

@@ -0,0 +1,16 @@
using RPGLibrary.Abstraction.Character;
using RPGLibrary.Abstraction.Services;
namespace RPGLibrary.Implementation
{
public class StrengthBasedDamageCalculator : IDamageCalculator<StrengthAttackData>
{
public double Calculate(ICharacter target, StrengthAttackData attackData)
=> CalculateInternal((dynamic)target, attackData);
private double CalculateInternal(Character target, StrengthAttackData attackData)
=> Math.Clamp(attackData.Strength.Value - target.Defense.Value, RPGConstants.MinDamage, RPGConstants.MaxDamage);
}
}

View File

@@ -0,0 +1,6 @@
namespace RPGLibrary.Test
{
internal class BattleSimulationTest
{
}
}

View File

@@ -0,0 +1,97 @@
using RPGLibrary.Command;
using RPGLibrary.Implementation;
using RPGLibrary.Implementation.Command;
namespace RPGLibrary.Test
{
public class CommandExecuterTest
{
[Test]
public async Task GivenAttackExpectHPIsReduced()
{
var source = new Character("A", new HP(38, 120), new StrengthAttribute(7), new DefenseAttribute(5));
var target = new Character("B", new HP(95, 100), new StrengthAttribute(7), new DefenseAttribute(5));
var damageCalculator = new StrengthBasedDamageCalculator();
var attackData = new StrengthAttackData(source, source.Strength);
var command = new AttackCommand(new[] { target }, attackData, damageCalculator);
var commands = new[] { command };
var results = CommandExecuter.Singleton.Execute(commands, new CancellationToken());
var actual = new List<Character>();
await foreach (var resultOfCommand in results)
{
await foreach (var resultOfAttack in resultOfCommand)
actual.Add((Character)resultOfAttack);
}
Assert.That(actual[0].HP.Value, Is.EqualTo(93));
}
[Test]
public async Task MultiHitAttackTest()
{
var source = new Character("A", new HP(100, 100), new StrengthAttribute(10), new DefenseAttribute(4));
var target = new[]
{
new Character("B", new HP(38, 100), new StrengthAttribute(12), new DefenseAttribute(4)),
new Character("C", new HP(47, 100), new StrengthAttribute(18), new DefenseAttribute(6)),
new Character("D", new HP(99, 100), new StrengthAttribute(11), new DefenseAttribute(2))
};
var damageCalculator = new StrengthBasedDamageCalculator();
var attackData = new StrengthAttackData(source, source.Strength);
var command = new AttackCommand(target, attackData, damageCalculator);
var commands = new[] { command };
var results = CommandExecuter.Singleton.Execute(commands, new CancellationToken());
var actual = new List<Character>();
await foreach (var resultOfCommand in results)
{
await foreach (var resultOfAttack in resultOfCommand)
actual.Add((Character)resultOfAttack);
}
Assert.That(actual[0].HP.Value, Is.EqualTo(32));
Assert.That(actual[1].HP.Value, Is.EqualTo(43));
Assert.That(actual[2].HP.Value, Is.EqualTo(91));
}
[Test]
public async Task QueueAttacks()
{
var source = new Character("A", new HP(100, 100), new StrengthAttribute(10), new DefenseAttribute(4));
var target = new[]
{
new Character("B", new HP(38, 100), new StrengthAttribute(12), new DefenseAttribute(4)),
new Character("C", new HP(47, 100), new StrengthAttribute(18), new DefenseAttribute(6)),
new Character("D", new HP(99, 100), new StrengthAttribute(11), new DefenseAttribute(2))
};
var damageCalculator = new StrengthBasedDamageCalculator();
var attackData = new StrengthAttackData(source, source.Strength);
var commandA = new AttackCommand(target, attackData, damageCalculator);
var commandB = new AttackCommand(target, attackData, damageCalculator);
var commandC = new AttackCommand(target, attackData, damageCalculator);
var commands = new[] { commandA, commandB, commandC };
var results = CommandExecuter.Singleton.Execute(commands, new CancellationToken());
await foreach (var resultOfCommand in results)
{
var characterList = new List<Character>();
await foreach (var r in resultOfCommand)
{
characterList.Add((Character)r);
}
Assert.That(characterList[0].HP.Value, Is.EqualTo(32));
Assert.That(characterList[1].HP.Value, Is.EqualTo(43));
Assert.That(characterList[2].HP.Value, Is.EqualTo(91));
}
}
}
}

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" />
<PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RPGLibrary.Implementation\RPGLibrary.Implementation.csproj" />
</ItemGroup>
</Project>

View File

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

37
RPGLibrary.sln Normal file
View File

@@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32526.322
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RPGLibrary.Abstraction", "RPGLibrary\RPGLibrary.Abstraction.csproj", "{E5EBC955-9F1E-4179-BFC4-26D21B85799E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RPGLibrary.Test", "RPGLibrary.Test\RPGLibrary.Test.csproj", "{B8EDB7DF-0160-4BB7-BA55-DE6FEB858327}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RPGLibrary.Implementation", "RPGLibrary.Implementation\RPGLibrary.Implementation.csproj", "{BCE26FFD-0AF9-4C20-BADC-FE7070F41772}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E5EBC955-9F1E-4179-BFC4-26D21B85799E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5EBC955-9F1E-4179-BFC4-26D21B85799E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5EBC955-9F1E-4179-BFC4-26D21B85799E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5EBC955-9F1E-4179-BFC4-26D21B85799E}.Release|Any CPU.Build.0 = Release|Any CPU
{B8EDB7DF-0160-4BB7-BA55-DE6FEB858327}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8EDB7DF-0160-4BB7-BA55-DE6FEB858327}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8EDB7DF-0160-4BB7-BA55-DE6FEB858327}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B8EDB7DF-0160-4BB7-BA55-DE6FEB858327}.Release|Any CPU.Build.0 = Release|Any CPU
{BCE26FFD-0AF9-4C20-BADC-FE7070F41772}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BCE26FFD-0AF9-4C20-BADC-FE7070F41772}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BCE26FFD-0AF9-4C20-BADC-FE7070F41772}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BCE26FFD-0AF9-4C20-BADC-FE7070F41772}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {779F151F-ADC1-4D34-8D53-586743ADED93}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,8 @@
using RPGLibrary.Abstraction.Attribute;
using RPGLibrary.Abstraction.Character;
namespace RPGLibrary
{
public abstract record AttackData(ICharacter Source, IAttribute DamageModifier);
}

View File

@@ -0,0 +1,8 @@
namespace RPGLibrary.Abstraction.Attribute
{
public interface IAttribute
{
double Value { get; }
}
}

View File

@@ -0,0 +1,18 @@
using RPGLibrary.Abstraction.Attribute;
namespace RPGLibrary.Attribute
{
public abstract class PoolAttribute : IAttribute
{
protected PoolAttribute(double value, double maximum)
{
Value = value;
Maximum = maximum;
}
public double Value { get; protected set; }
public double Maximum { get; protected set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace RPGLibrary.Abstraction.Character
{
public interface ICharacter
{
string Name { get; }
}
}

View File

@@ -0,0 +1,28 @@
using System.Runtime.CompilerServices;
using RPGLibrary.Abstraction.Character;
namespace RPGLibrary.Command
{
public sealed class CommandExecuter
{
private static readonly Lazy<CommandExecuter> _instance = new Lazy<CommandExecuter>(() => new CommandExecuter());
public static CommandExecuter Singleton => _instance.Value;
private CommandExecuter() { }
public async IAsyncEnumerable<IAsyncEnumerable<ICharacter>> Execute(
IEnumerable<ICommand> commands,
[EnumeratorCancellation] CancellationToken ct)
{
foreach (var command in commands)
{
if (ct.IsCancellationRequested)
break;
yield return command.Execute();
}
}
}
}

View File

@@ -0,0 +1,10 @@
using RPGLibrary.Abstraction.Character;
namespace RPGLibrary.Command
{
public interface ICommand
{
public IAsyncEnumerable<ICharacter> Execute();
}
}

View File

@@ -0,0 +1,10 @@
namespace RPGLibrary
{
public struct RPGConstants
{
public static double MinDamage => 0;
public static double MaxDamage => 9999;
}
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,11 @@
using RPGLibrary.Abstraction.Character;
namespace RPGLibrary.Abstraction.Services
{
public interface IDamageCalculator<TAttackData>
where TAttackData : AttackData
{
double Calculate(ICharacter target, TAttackData attackData);
}
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\RPGLibrary\RPGLibrary.Abstraction.csproj" />
</ItemGroup>
</Project>