diff --git a/Aplib.Core/Tactics/AnyOfTactic.cs b/Aplib.Core/Tactics/AnyOfTactic.cs index dc776461..4e2888c8 100644 --- a/Aplib.Core/Tactics/AnyOfTactic.cs +++ b/Aplib.Core/Tactics/AnyOfTactic.cs @@ -35,16 +35,19 @@ public AnyOfTactic(List subTactics) public AnyOfTactic(List subTactics, Func guard) : this(subTactics) => Guard = guard; /// - public override List GetFirstEnabledActions() + public override Action? GetAction() { - List primitiveTactics = new(); + List actions = new(); foreach (Tactic subTactic in SubTactics) { - primitiveTactics.AddRange(subTactic.GetFirstEnabledActions()); + Action? action = subTactic.GetAction(); + + if (action is not null) + actions.Add(action); } - return primitiveTactics; + return actions[ThreadSafeRandom.Next(actions.Count)]; } } } diff --git a/Aplib.Core/Tactics/FirstOfTactic.cs b/Aplib.Core/Tactics/FirstOfTactic.cs index fe0d954e..007064f3 100644 --- a/Aplib.Core/Tactics/FirstOfTactic.cs +++ b/Aplib.Core/Tactics/FirstOfTactic.cs @@ -26,14 +26,14 @@ public FirstOfTactic(List subTactics, Func guard) : base(subTactic } /// - public override List GetFirstEnabledActions() + public override Action? GetAction() { foreach (Tactic subTactic in SubTactics) { - List firstOfTactics = subTactic.GetFirstEnabledActions(); + Action? action = subTactic.GetAction(); - if (firstOfTactics.Count > 0) - return firstOfTactics; + if (action is not null && action.IsActionable()) + return action; } return new(); diff --git a/Aplib.Core/Tactics/PrimitiveTactic.cs b/Aplib.Core/Tactics/PrimitiveTactic.cs index 34129353..5a119059 100644 --- a/Aplib.Core/Tactics/PrimitiveTactic.cs +++ b/Aplib.Core/Tactics/PrimitiveTactic.cs @@ -27,7 +27,7 @@ public class PrimitiveTactic : Tactic public PrimitiveTactic(Action action, Func guard) : base(guard) => Action = action; /// - public override List GetFirstEnabledActions() => IsActionable() ? new() { this } : new(); + public override Action? GetAction() => IsActionable() ? Action : null; /// public override bool IsActionable() => base.IsActionable() && Action.IsActionable(); diff --git a/Aplib.Core/Tactics/Tactic.cs b/Aplib.Core/Tactics/Tactic.cs index 3697cc30..cf4054da 100644 --- a/Aplib.Core/Tactics/Tactic.cs +++ b/Aplib.Core/Tactics/Tactic.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; namespace Aplib.Core.Tactics { @@ -27,10 +26,10 @@ protected Tactic() protected Tactic(Func guard) => Guard = guard; /// - /// Gets the first enabled primitive actions. + /// Gets the enabled action. /// - /// A list of primitive tactics that are enabled. - public abstract List GetFirstEnabledActions(); + /// An action that is enabled. + public abstract Action? GetAction(); /// /// Determines whether the tactic is actionable. diff --git a/Aplib.Core/ThreadSafeRandom.cs b/Aplib.Core/ThreadSafeRandom.cs new file mode 100644 index 00000000..0111bf38 --- /dev/null +++ b/Aplib.Core/ThreadSafeRandom.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading.Tasks; + +internal static class ThreadSafeRandom +{ + [ThreadStatic] + private static Random? _local; + private static readonly Random _global = new(); + + private static Random _instance + { + get + { + if (_local is null) + { + int seed; + lock (_global) + { + seed = _global.Next(); + } + + _local = new Random(seed); + } + + return _local; + } + } + + public static int Next() => _instance.Next(); + + public static int Next(int maxValue) => _instance.Next(maxValue); +} diff --git a/Aplib.Tests/Core/Tactics/TacticTests.cs b/Aplib.Tests/Core/Tactics/TacticTests.cs index a6e9f7c1..88d82cb3 100644 --- a/Aplib.Tests/Core/Tactics/TacticTests.cs +++ b/Aplib.Tests/Core/Tactics/TacticTests.cs @@ -4,7 +4,9 @@ namespace Aplib.Tests.Core.Tactics; public class TacticTests { - private readonly Action _emptyAction = new(() => { }); + private readonly Action _emptyAction = new(effect: () => { }); + private static string _result = "abc"; + private readonly Action _filledAction = new(effect: () => _result = "def"); private static bool TrueGuard() => true; @@ -16,18 +18,18 @@ public class TacticTests /// Then the result should be the first subtactic. /// [Fact] - public void GetFirstEnabledActions_WhenTacticTypeIsFirstOf_ReturnsEnabledPrimitiveTactics() + public void GetAction_WhenTacticTypeIsFirstOf_ReturnsEnabledPrimitiveTactics() { // Arrange PrimitiveTactic tactic1 = new(_emptyAction); - PrimitiveTactic tactic2 = new(_emptyAction); + PrimitiveTactic tactic2 = new(_filledAction); FirstOfTactic parentTactic = new([tactic1, tactic2]); // Act - List enabledActions = parentTactic.GetFirstEnabledActions(); + Action? enabledAction = parentTactic.GetAction(); // Assert - Assert.Contains(tactic1, enabledActions); + Assert.Equal(_emptyAction, enabledAction); } /// @@ -36,18 +38,18 @@ public void GetFirstEnabledActions_WhenTacticTypeIsFirstOf_ReturnsEnabledPrimiti /// Then the result should be the first subtactic. /// [Fact] - public void GetFirstEnabledActions_WhenTacticTypeIsFirstOfAndGuardEnabled_ReturnsEnabledPrimitiveTactics() + public void GetAction_WhenTacticTypeIsFirstOfAndGuardEnabled_ReturnsEnabledPrimitiveTactics() { // Arrange PrimitiveTactic tactic1 = new(_emptyAction); - PrimitiveTactic tactic2 = new(_emptyAction); + PrimitiveTactic tactic2 = new(_filledAction); FirstOfTactic parentTactic = new([tactic1, tactic2], TrueGuard); // Act - List enabledActions = parentTactic.GetFirstEnabledActions(); + Action? enabledAction = parentTactic.GetAction(); // Assert - Assert.Contains(tactic1, enabledActions); + Assert.Equal(_emptyAction, enabledAction); } /// @@ -56,7 +58,7 @@ public void GetFirstEnabledActions_WhenTacticTypeIsFirstOfAndGuardEnabled_Return /// Then the result should contain all the subtactics. /// [Fact] - public void GetFirstEnabledActions_WhenTacticTypeIsAnyOf_ReturnsEnabledPrimitiveTactics() + public void GetAction_WhenTacticTypeIsAnyOf_ReturnsEnabledPrimitiveTactics() { // Arrange PrimitiveTactic tactic1 = new(_emptyAction); @@ -64,11 +66,10 @@ public void GetFirstEnabledActions_WhenTacticTypeIsAnyOf_ReturnsEnabledPrimitive AnyOfTactic parentTactic = new([tactic1, tactic2]); // Act - List enabledActions = parentTactic.GetFirstEnabledActions(); + Action? enabledAction = parentTactic.GetAction(); // Assert - Assert.Contains(tactic1, enabledActions); - Assert.Contains(tactic2, enabledActions); + Assert.Equal(_emptyAction, enabledAction); } /// @@ -77,16 +78,16 @@ public void GetFirstEnabledActions_WhenTacticTypeIsAnyOf_ReturnsEnabledPrimitive /// Then the result should contain the primitive tactic. /// [Fact] - public void GetFirstEnabledActions_WhenTacticTypeIsPrimitiveAndActionIsActionable_ReturnsEnabledPrimitiveTactic() + public void GetAction_WhenTacticTypeIsPrimitiveAndActionIsActionable_ReturnsEnabledPrimitiveTactic() { // Arrange PrimitiveTactic tactic = new(_emptyAction, TrueGuard); // Act - List enabledActions = tactic.GetFirstEnabledActions(); + Action? enabledAction = tactic.GetAction(); // Assert - Assert.Contains(tactic, enabledActions); + Assert.Equal(_emptyAction, enabledAction); } /// @@ -95,16 +96,16 @@ public void GetFirstEnabledActions_WhenTacticTypeIsPrimitiveAndActionIsActionabl /// Then the result should be an empty list. /// [Fact] - public void GetFirstEnabledActions_WhenTacticTypeIsPrimitiveAndActionIsNotActionable_ReturnsEmptyList() + public void GetAction_WhenTacticTypeIsPrimitiveAndActionIsNotActionable_ReturnsEmptyList() { // Arrange PrimitiveTactic tactic = new(_emptyAction, FalseGuard); // Act - List enabledActions = tactic.GetFirstEnabledActions(); + Action? enabledAction = tactic.GetAction(); // Assert - Assert.Empty(enabledActions); + Assert.Null(enabledAction); } ///