Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update Tactic structure #18

Merged
merged 10 commits into from
Mar 18, 2024
18 changes: 12 additions & 6 deletions Aplib.Core/Tactics/AnyOfTactic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class AnyOfTactic : Tactic
/// Initializes a new instance of the <see cref="AnyOfTactic"/> class with the specified sub-tactics.
/// </summary>
/// <param name="subTactics">The list of sub-tactics.</param>
public AnyOfTactic(List<Tactic> subTactics)
public AnyOfTactic(params Tactic[] subTactics)
{
SubTactics = new();

Expand All @@ -32,19 +32,25 @@ public AnyOfTactic(List<Tactic> subTactics)
/// </summary>
/// <param name="subTactics">The list of sub-tactics.</param>
/// <param name="guard">The guard condition.</param>
public AnyOfTactic(List<Tactic> subTactics, Func<bool> guard) : this(subTactics) => Guard = guard;
public AnyOfTactic(Func<bool> guard, params Tactic[] subTactics) : this(subTactics) => Guard = guard;

/// <inheritdoc/>
public override List<PrimitiveTactic> GetFirstEnabledActions()
public override Action? GetAction()
{
List<PrimitiveTactic> primitiveTactics = new();
List<Action> 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;
if (actions.Count == 0)
return null;

return actions[ThreadSafeRandom.Next(actions.Count)];
Tboefijn marked this conversation as resolved.
Show resolved Hide resolved
Tboefijn marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
14 changes: 7 additions & 7 deletions Aplib.Core/Tactics/FirstOfTactic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class FirstOfTactic : AnyOfTactic
/// Initializes a new instance of the <see cref="FirstOfTactic"/> class with the specified sub-tactics.
/// </summary>
/// <param name="subTactics">The list of sub-tactics.</param>
public FirstOfTactic(List<Tactic> subTactics) : base(subTactics)
public FirstOfTactic(params Tactic[] subTactics) : base(subTactics)
{
}

Expand All @@ -21,22 +21,22 @@ public FirstOfTactic(List<Tactic> subTactics) : base(subTactics)
/// </summary>
/// <param name="subTactics">The list of sub-tactics.</param>
/// <param name="guard">The guard condition.</param>
public FirstOfTactic(List<Tactic> subTactics, Func<bool> guard) : base(subTactics, guard)
public FirstOfTactic(Func<bool> guard, params Tactic[] subTactics) : base(guard, subTactics)
{
}

/// <inheritdoc/>
public override List<PrimitiveTactic> GetFirstEnabledActions()
public override Action? GetAction()
{
foreach (Tactic subTactic in SubTactics)
{
List<PrimitiveTactic> firstOfTactics = subTactic.GetFirstEnabledActions();
Action? action = subTactic.GetAction();

if (firstOfTactics.Count > 0)
return firstOfTactics;
if (action is not null)
return action;
}

return new();
return null;
}
}
}
2 changes: 1 addition & 1 deletion Aplib.Core/Tactics/PrimitiveTactic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class PrimitiveTactic : Tactic
public PrimitiveTactic(Action action, Func<bool> guard) : base(guard) => Action = action;

/// <inheritdoc/>
public override List<PrimitiveTactic> GetFirstEnabledActions() => IsActionable() ? new() { this } : new();
public override Action? GetAction() => IsActionable() ? Action : null;

/// <inheritdoc/>
public override bool IsActionable() => base.IsActionable() && Action.IsActionable();
Expand Down
7 changes: 3 additions & 4 deletions Aplib.Core/Tactics/Tactic.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;

namespace Aplib.Core.Tactics
{
Expand Down Expand Up @@ -27,10 +26,10 @@ protected Tactic()
protected Tactic(Func<bool> guard) => Guard = guard;

/// <summary>
/// Gets the first enabled primitive actions.
/// Gets the enabled action.
/// </summary>
/// <returns>A list of primitive tactics that are enabled.</returns>
public abstract List<PrimitiveTactic> GetFirstEnabledActions();
/// <returns>An action that is enabled.</returns>
public abstract Action? GetAction();

/// <summary>
/// Determines whether the tactic is actionable.
Expand Down
34 changes: 34 additions & 0 deletions Aplib.Core/ThreadSafeRandom.cs
Tboefijn marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;

namespace Aplib.Core
{
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);
}
}
62 changes: 41 additions & 21 deletions Aplib.Tests/Core/Tactics/TacticTests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using Aplib.Core.Tactics;
using FluentAssertions;
using Action = Aplib.Core.Action;

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;

Expand All @@ -16,18 +19,18 @@ public class TacticTests
/// Then the result should be the first subtactic.
/// </summary>
[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<PrimitiveTactic> enabledActions = parentTactic.GetFirstEnabledActions();
Action? enabledAction = parentTactic.GetAction();

// Assert
Assert.Contains(tactic1, enabledActions);
Assert.Equal(_emptyAction, enabledAction);
}

/// <summary>
Expand All @@ -36,18 +39,18 @@ public void GetFirstEnabledActions_WhenTacticTypeIsFirstOf_ReturnsEnabledPrimiti
/// Then the result should be the first subtactic.
/// </summary>
[Fact]
public void GetFirstEnabledActions_WhenTacticTypeIsFirstOfAndGuardEnabled_ReturnsEnabledPrimitiveTactics()
public void GetAction_WhenTacticTypeIsFirstOfAndGuardEnabled_ReturnsEnabledPrimitiveTactics()
{
// Arrange
PrimitiveTactic tactic1 = new(_emptyAction);
PrimitiveTactic tactic2 = new(_emptyAction);
FirstOfTactic parentTactic = new([tactic1, tactic2], TrueGuard);
PrimitiveTactic tactic2 = new(_filledAction);
FirstOfTactic parentTactic = new(TrueGuard, [tactic1, tactic2]);

// Act
List<PrimitiveTactic> enabledActions = parentTactic.GetFirstEnabledActions();
Action? enabledAction = parentTactic.GetAction();

// Assert
Assert.Contains(tactic1, enabledActions);
Assert.Equal(_emptyAction, enabledAction);
}

/// <summary>
Expand All @@ -56,19 +59,18 @@ public void GetFirstEnabledActions_WhenTacticTypeIsFirstOfAndGuardEnabled_Return
/// Then the result should contain all the subtactics.
/// </summary>
[Fact]
public void GetFirstEnabledActions_WhenTacticTypeIsAnyOf_ReturnsEnabledPrimitiveTactics()
public void GetAction_WhenTacticTypeIsAnyOf_ReturnsEnabledPrimitiveTactics()
{
// Arrange
PrimitiveTactic tactic1 = new(_emptyAction);
PrimitiveTactic tactic2 = new(_emptyAction);
AnyOfTactic parentTactic = new([tactic1, tactic2]);

// Act
List<PrimitiveTactic> enabledActions = parentTactic.GetFirstEnabledActions();
Action? enabledAction = parentTactic.GetAction();

// Assert
Assert.Contains(tactic1, enabledActions);
Assert.Contains(tactic2, enabledActions);
Assert.Equal(_emptyAction, enabledAction);
}

/// <summary>
Expand All @@ -77,16 +79,16 @@ public void GetFirstEnabledActions_WhenTacticTypeIsAnyOf_ReturnsEnabledPrimitive
/// Then the result should contain the primitive tactic.
/// </summary>
[Fact]
public void GetFirstEnabledActions_WhenTacticTypeIsPrimitiveAndActionIsActionable_ReturnsEnabledPrimitiveTactic()
public void GetAction_WhenTacticTypeIsPrimitiveAndActionIsActionable_ReturnsEnabledPrimitiveTactic()
{
// Arrange
PrimitiveTactic tactic = new(_emptyAction, TrueGuard);

// Act
List<PrimitiveTactic> enabledActions = tactic.GetFirstEnabledActions();
Action? enabledAction = tactic.GetAction();

// Assert
Assert.Contains(tactic, enabledActions);
Assert.Equal(_emptyAction, enabledAction);
}

/// <summary>
Expand All @@ -95,16 +97,34 @@ public void GetFirstEnabledActions_WhenTacticTypeIsPrimitiveAndActionIsActionabl
/// Then the result should be an empty list.
/// </summary>
[Fact]
public void GetFirstEnabledActions_WhenTacticTypeIsPrimitiveAndActionIsNotActionable_ReturnsEmptyList()
public void GetAction_WhenTacticTypeIsPrimitiveAndActionIsNotActionable_ReturnsEmptyList()
{
// Arrange
PrimitiveTactic tactic = new(_emptyAction, FalseGuard);

// Act
List<PrimitiveTactic> enabledActions = tactic.GetFirstEnabledActions();
Action? enabledAction = tactic.GetAction();

// Assert
Assert.Null(enabledAction);
}

/// <summary>
/// Given a tactic with a guard that returns true and an action,
/// When calling the Execute method,
/// Then _result should be "def".
/// </summary>
[Fact]
public void Execute_WhenGuardReturnsTrue_ActionIsExecuted()
{
// Arrange
PrimitiveTactic tactic = new(_filledAction, TrueGuard);

// Act
tactic.Action.Execute();

// Assert
Assert.Empty(enabledActions);
Assert.Equal("def", _result);
}

/// <summary>
Expand All @@ -116,7 +136,7 @@ public void GetFirstEnabledActions_WhenTacticTypeIsPrimitiveAndActionIsNotAction
public void IsActionable_WhenGuardReturnsTrue_ReturnsTrue()
{
// Arrange
PrimitiveTactic tactic = new(_emptyAction, TrueGuard);
PrimitiveTactic tactic = new(_filledAction, TrueGuard);

// Act
bool isActionable = tactic.IsActionable();
Expand Down