diff --git a/RPGLibrary.Implementation/AttackCommand.cs b/RPGLibrary.Implementation/AttackCommand.cs new file mode 100644 index 0000000..1fb799e --- /dev/null +++ b/RPGLibrary.Implementation/AttackCommand.cs @@ -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 _damageCalculator; + + public AttackCommand(Character[] target, StrengthAttackData attackData, IDamageCalculator damageCalculator) + { + _target = target; + _attackData = attackData; + _damageCalculator = damageCalculator; + } + + + public async IAsyncEnumerable 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); + } + } + } + +} \ No newline at end of file diff --git a/RPGLibrary.Implementation/Attribute/DefenseAttribute.cs b/RPGLibrary.Implementation/Attribute/DefenseAttribute.cs new file mode 100644 index 0000000..fb3c21a --- /dev/null +++ b/RPGLibrary.Implementation/Attribute/DefenseAttribute.cs @@ -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}"; + } + } + +} \ No newline at end of file diff --git a/RPGLibrary.Implementation/Attribute/HP.cs b/RPGLibrary.Implementation/Attribute/HP.cs new file mode 100644 index 0000000..74cba89 --- /dev/null +++ b/RPGLibrary.Implementation/Attribute/HP.cs @@ -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})"; + } + } + +} \ No newline at end of file diff --git a/RPGLibrary.Implementation/Attribute/Speed.cs b/RPGLibrary.Implementation/Attribute/Speed.cs new file mode 100644 index 0000000..85b37ab --- /dev/null +++ b/RPGLibrary.Implementation/Attribute/Speed.cs @@ -0,0 +1,13 @@ +namespace RPGLibrary.Implementation +{ + public class Speed + { + public Speed(double value) + { + Value = value; + } + + public double Value { get; } + } + +} \ No newline at end of file diff --git a/RPGLibrary.Implementation/Attribute/StrengthAttribute.cs b/RPGLibrary.Implementation/Attribute/StrengthAttribute.cs new file mode 100644 index 0000000..e504cf3 --- /dev/null +++ b/RPGLibrary.Implementation/Attribute/StrengthAttribute.cs @@ -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}"; + } + } + +} \ No newline at end of file diff --git a/RPGLibrary.Implementation/Character.cs b/RPGLibrary.Implementation/Character.cs new file mode 100644 index 0000000..45300d8 --- /dev/null +++ b/RPGLibrary.Implementation/Character.cs @@ -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); + } + +} \ No newline at end of file diff --git a/RPGLibrary.Implementation/RPGLibrary.Implementation.csproj b/RPGLibrary.Implementation/RPGLibrary.Implementation.csproj new file mode 100644 index 0000000..57fac48 --- /dev/null +++ b/RPGLibrary.Implementation/RPGLibrary.Implementation.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/RPGLibrary.Implementation/StrengthAttackData.cs b/RPGLibrary.Implementation/StrengthAttackData.cs new file mode 100644 index 0000000..c9383ef --- /dev/null +++ b/RPGLibrary.Implementation/StrengthAttackData.cs @@ -0,0 +1,8 @@ +using RPGLibrary.Abstraction.Character; + +namespace RPGLibrary.Implementation +{ + public record StrengthAttackData(ICharacter Source, StrengthAttribute Strength) + : AttackData(Source, Strength); + +} \ No newline at end of file diff --git a/RPGLibrary.Implementation/StrengthBasedDamageCalculator.cs b/RPGLibrary.Implementation/StrengthBasedDamageCalculator.cs new file mode 100644 index 0000000..cfb203b --- /dev/null +++ b/RPGLibrary.Implementation/StrengthBasedDamageCalculator.cs @@ -0,0 +1,16 @@ +using RPGLibrary.Abstraction.Character; +using RPGLibrary.Abstraction.Services; + +namespace RPGLibrary.Implementation +{ + public class StrengthBasedDamageCalculator : IDamageCalculator + { + 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); + + } + +} \ No newline at end of file diff --git a/RPGLibrary.Test/BattleSimulationTest.cs b/RPGLibrary.Test/BattleSimulationTest.cs new file mode 100644 index 0000000..6c1653e --- /dev/null +++ b/RPGLibrary.Test/BattleSimulationTest.cs @@ -0,0 +1,6 @@ +namespace RPGLibrary.Test +{ + internal class BattleSimulationTest + { + } +} diff --git a/RPGLibrary.Test/CommandExecuterTest.cs b/RPGLibrary.Test/CommandExecuterTest.cs new file mode 100644 index 0000000..8b8a94d --- /dev/null +++ b/RPGLibrary.Test/CommandExecuterTest.cs @@ -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(); + + 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(); + + 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(); + 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)); + } + } + } +} \ No newline at end of file diff --git a/RPGLibrary.Test/RPGLibrary.Test.csproj b/RPGLibrary.Test/RPGLibrary.Test.csproj new file mode 100644 index 0000000..71eafe5 --- /dev/null +++ b/RPGLibrary.Test/RPGLibrary.Test.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + + + diff --git a/RPGLibrary.Test/Usings.cs b/RPGLibrary.Test/Usings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/RPGLibrary.Test/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/RPGLibrary.sln b/RPGLibrary.sln new file mode 100644 index 0000000..ecb48f0 --- /dev/null +++ b/RPGLibrary.sln @@ -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 diff --git a/RPGLibrary/AttackData/AttackData.cs b/RPGLibrary/AttackData/AttackData.cs new file mode 100644 index 0000000..a65da50 --- /dev/null +++ b/RPGLibrary/AttackData/AttackData.cs @@ -0,0 +1,8 @@ +using RPGLibrary.Abstraction.Attribute; +using RPGLibrary.Abstraction.Character; + +namespace RPGLibrary +{ + public abstract record AttackData(ICharacter Source, IAttribute DamageModifier); + +} \ No newline at end of file diff --git a/RPGLibrary/Attribute/IAttribute.cs b/RPGLibrary/Attribute/IAttribute.cs new file mode 100644 index 0000000..e83bd80 --- /dev/null +++ b/RPGLibrary/Attribute/IAttribute.cs @@ -0,0 +1,8 @@ +namespace RPGLibrary.Abstraction.Attribute +{ + public interface IAttribute + { + double Value { get; } + } + +} \ No newline at end of file diff --git a/RPGLibrary/Attribute/PoolAttribute.cs b/RPGLibrary/Attribute/PoolAttribute.cs new file mode 100644 index 0000000..c249fdd --- /dev/null +++ b/RPGLibrary/Attribute/PoolAttribute.cs @@ -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; } + } + +} \ No newline at end of file diff --git a/RPGLibrary/Character/ICharacter.cs b/RPGLibrary/Character/ICharacter.cs new file mode 100644 index 0000000..dcc99a9 --- /dev/null +++ b/RPGLibrary/Character/ICharacter.cs @@ -0,0 +1,8 @@ +namespace RPGLibrary.Abstraction.Character +{ + public interface ICharacter + { + string Name { get; } + } + +} \ No newline at end of file diff --git a/RPGLibrary/Command/CommandExecuter.cs b/RPGLibrary/Command/CommandExecuter.cs new file mode 100644 index 0000000..6338140 --- /dev/null +++ b/RPGLibrary/Command/CommandExecuter.cs @@ -0,0 +1,28 @@ +using System.Runtime.CompilerServices; +using RPGLibrary.Abstraction.Character; + +namespace RPGLibrary.Command +{ + + public sealed class CommandExecuter + { + private static readonly Lazy _instance = new Lazy(() => new CommandExecuter()); + + public static CommandExecuter Singleton => _instance.Value; + + private CommandExecuter() { } + + public async IAsyncEnumerable> Execute( + IEnumerable commands, + [EnumeratorCancellation] CancellationToken ct) + { + foreach (var command in commands) + { + if (ct.IsCancellationRequested) + break; + yield return command.Execute(); + } + } + } + +} \ No newline at end of file diff --git a/RPGLibrary/Command/ICommand.cs b/RPGLibrary/Command/ICommand.cs new file mode 100644 index 0000000..9929db3 --- /dev/null +++ b/RPGLibrary/Command/ICommand.cs @@ -0,0 +1,10 @@ +using RPGLibrary.Abstraction.Character; + +namespace RPGLibrary.Command +{ + public interface ICommand + { + public IAsyncEnumerable Execute(); + } + +} \ No newline at end of file diff --git a/RPGLibrary/RPGConstants.cs b/RPGLibrary/RPGConstants.cs new file mode 100644 index 0000000..c5350d4 --- /dev/null +++ b/RPGLibrary/RPGConstants.cs @@ -0,0 +1,10 @@ +namespace RPGLibrary +{ + public struct RPGConstants + { + public static double MinDamage => 0; + + public static double MaxDamage => 9999; + } + +} \ No newline at end of file diff --git a/RPGLibrary/RPGLibrary.Abstraction.csproj b/RPGLibrary/RPGLibrary.Abstraction.csproj new file mode 100644 index 0000000..132c02c --- /dev/null +++ b/RPGLibrary/RPGLibrary.Abstraction.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/RPGLibrary/Services/IDamageCalculator.cs b/RPGLibrary/Services/IDamageCalculator.cs new file mode 100644 index 0000000..a5821d0 --- /dev/null +++ b/RPGLibrary/Services/IDamageCalculator.cs @@ -0,0 +1,11 @@ +using RPGLibrary.Abstraction.Character; + +namespace RPGLibrary.Abstraction.Services +{ + public interface IDamageCalculator + where TAttackData : AttackData + { + double Calculate(ICharacter target, TAttackData attackData); + } + +} \ No newline at end of file diff --git a/RPGTestGUI/RPGTestGUI.csproj b/RPGTestGUI/RPGTestGUI.csproj new file mode 100644 index 0000000..56d70ca --- /dev/null +++ b/RPGTestGUI/RPGTestGUI.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + enable + enable + + + + + + +