Skip to content

Commit 3bac25a

Browse files
committed
Add binary appraisals and intent utility helpers
1 parent 5457b48 commit 3bac25a

File tree

14 files changed

+434
-3
lines changed

14 files changed

+434
-3
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
namespace BrainAI.Tests
2+
{
3+
using BrainAI.AI.UtilityAI;
4+
5+
using NUnit.Framework;
6+
7+
[TestFixture]
8+
public class AndAppraisalTest
9+
{
10+
private class Context
11+
{
12+
}
13+
14+
[Test]
15+
public void GetScore_NoAppraisals_False()
16+
{
17+
var context = new Context();
18+
19+
var and = new AndAppraisal<Context>();
20+
var score = and.GetScore(context);
21+
22+
Assert.AreEqual(0, score);
23+
}
24+
25+
[Test]
26+
public void GetScore_AllAppraisalsHaveValues_True()
27+
{
28+
var context = new Context();
29+
30+
var and = new AndAppraisal<Context>(
31+
new FixedAppraisal<Context>(1),
32+
new FixedAppraisal<Context>(-1)
33+
);
34+
var score = and.GetScore(context);
35+
36+
Assert.AreEqual(1, score);
37+
}
38+
39+
[Test]
40+
public void GetScore_NotAllAppraisalsHaveValues_False()
41+
{
42+
var context = new Context();
43+
44+
var and = new AndAppraisal<Context>(
45+
new FixedAppraisal<Context>(1),
46+
new FixedAppraisal<Context>(-1),
47+
new FixedAppraisal<Context>(0)
48+
);
49+
var score = and.GetScore(context);
50+
51+
Assert.AreEqual(0, score);
52+
}
53+
54+
[Test]
55+
public void GetScore_AllAppraisalsHaveNoValues_False()
56+
{
57+
var context = new Context();
58+
59+
var and = new AndAppraisal<Context>(
60+
new FixedAppraisal<Context>(0),
61+
new FixedAppraisal<Context>(0),
62+
new FixedAppraisal<Context>(0)
63+
);
64+
var score = and.GetScore(context);
65+
66+
Assert.AreEqual(0, score);
67+
}
68+
}
69+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
namespace BrainAI.Tests
2+
{
3+
using BrainAI.AI.UtilityAI;
4+
5+
using NUnit.Framework;
6+
7+
[TestFixture]
8+
public class NotAppraisalTest
9+
{
10+
private class Context
11+
{
12+
}
13+
14+
[Test]
15+
public void GetScore_AppraisalHaveValues_False()
16+
{
17+
var context = new Context();
18+
19+
var and = new NotAppraisal<Context>(
20+
new FixedAppraisal<Context>(-1)
21+
);
22+
var score = and.GetScore(context);
23+
24+
Assert.AreEqual(0, score);
25+
}
26+
27+
[Test]
28+
public void GetScore_AppraisalHaveNoValues_True()
29+
{
30+
var context = new Context();
31+
32+
var and = new NotAppraisal<Context>(
33+
new FixedAppraisal<Context>(0)
34+
);
35+
var score = and.GetScore(context);
36+
37+
Assert.AreEqual(1, score);
38+
}
39+
}
40+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
namespace BrainAI.Tests
2+
{
3+
using BrainAI.AI.UtilityAI;
4+
5+
using NUnit.Framework;
6+
7+
[TestFixture]
8+
public class OrAppraisalTest
9+
{
10+
private class Context
11+
{
12+
}
13+
14+
[Test]
15+
public void GetScore_NoAppraisals_False()
16+
{
17+
var context = new Context();
18+
19+
var and = new OrAppraisal<Context>();
20+
var score = and.GetScore(context);
21+
22+
Assert.AreEqual(0, score);
23+
}
24+
25+
[Test]
26+
public void GetScore_AllAppraisalsHaveValues_True()
27+
{
28+
var context = new Context();
29+
30+
var and = new OrAppraisal<Context>(
31+
new FixedAppraisal<Context>(1),
32+
new FixedAppraisal<Context>(-1)
33+
);
34+
var score = and.GetScore(context);
35+
36+
Assert.AreEqual(1, score);
37+
}
38+
39+
[Test]
40+
public void GetScore_NotAllAppraisalsHaveValues_False()
41+
{
42+
var context = new Context();
43+
44+
var and = new OrAppraisal<Context>(
45+
new FixedAppraisal<Context>(1),
46+
new FixedAppraisal<Context>(-1),
47+
new FixedAppraisal<Context>(0)
48+
);
49+
var score = and.GetScore(context);
50+
51+
Assert.AreEqual(1, score);
52+
}
53+
54+
[Test]
55+
public void GetScore_AllAppraisalsHaveNoValues_False()
56+
{
57+
var context = new Context();
58+
59+
var and = new OrAppraisal<Context>(
60+
new FixedAppraisal<Context>(0),
61+
new FixedAppraisal<Context>(0),
62+
new FixedAppraisal<Context>(0)
63+
);
64+
var score = and.GetScore(context);
65+
66+
Assert.AreEqual(0, score);
67+
}
68+
}
69+
}
File renamed without changes.

BrainAI/AI/README.md

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,23 +133,74 @@ Selects the best Action from a list of Actions and its Appraisals attached to th
133133
- **LowestScoreReasoner<T>:** Selects action with minimum score
134134
- **Reasoner<T>:** Base class for custom implementations
135135

136-
137136
## Appraisal
138137
Appraisals calculate and return a score which is used by the Reasoner to determine the action.
139138

139+
- **IAppraisal<T>:** Base interface for custom implementations
140140
- **ActionAppraisal<T>:** Func wrapper for use as an Appraisal without having to create a subclass.
141141
- **FirstAppraisal<T>:** Returns first score that is above then threshold.
142142
- **FixedAppraisal<T>:** Always returns a fixed score.
143143
- **MaxAppraisal<T>:** Return max score of child appraisals. For binary child appraisals (that returns 1 or 0) can be used as boolean 'OR' operator.
144144
- **MultiAppraisal<T>:** Scores by multiplying the score of all child Appraisals. For binary child appraisals (that returns 1 or 0) can be used as boolean 'AND' operator if threshold is set to 0.
145145
- **SumAppraisal<T>:** Scores if all child Appraisals score above the threshold. If threshold not specified - float.MinValue is used.
146-
- **IAppraisal<T>:** Base interface for custom implementations
147146

147+
Additionaly there are a few helper Appraisals for binary operations. As an input those Appraisals take child's appraisals score and comparing them to 0 - treat it as false, other values treated as true:
148+
149+
- **AndAppraisal** - return true(1) if all child appraisal scores treated as true, othervise false(0)
150+
- **OrAppraisal** - return true(1) if any child appraisal scores treated as true, othervise false(0)
151+
- **NotAppraisal** - return true(1) if child appraisal score treated as false, othervise false(0)
148152

149153
## Action
150154
The action that the AI executes when a specific consideration is selected.
151155

152-
- **FirstScoreReasoner<T>:** Selects first action with score above the threshold
156+
- **FirstScoreReasoner<T>:** Selects first action with score above the threshold.
153157
- **HighestScoreReasoner<T>:** Selects the action with the highest score.
154158
- **LowestScoreReasoner<T>:** Selects the action with the lowest score.
155159

160+
## Intents
161+
To execute lower level actions (like move to the point, or wait for attack animation) that is not eally related to decision making intents can be used.
162+
163+
Example usecase:
164+
UtilityAI decide to move to specific point. But the move itself may take time, and during this time no other decisions should be made (unless something more urgent happens).
165+
166+
To use intents the Context should implement **IIntentContainer<T>** and any low level action should implement **IIntent**
167+
168+
To simplify integration with UtilityAI a few classes can be used:
169+
- **SetIntentAction** an action that sets specified IIntent to IIntentContainer.
170+
- **HasIntentAppraisal** checks that containser has Intent set, if yes - score is returned, othervise 0.
171+
- **UseIntentAction** an action that executes specified IIntent. Intent is cleared on Exit or when IIntent.Execute return true (finished).
172+
173+
Example usage:
174+
175+
``` csharp
176+
var reasoner = new FirstScoreReasoner<Unit>(1);
177+
// Intent phase. If there is an intent to act - intent will be executed.
178+
reasoner.Add(new HasIntentAppraisal<Unit>(2), new UseIntentAction<Unit>());
179+
// Decision phase. If there is no intent (previous one is finished, or not yet set) - find target to attack or move.
180+
reasoner.Add(new CanAttackAppraisal(), new SetIntentAction<Unit, AttackOpponentIntent>(new AttackIntent()));
181+
reasoner.Add(new CantAttackAppraisal(), new SetIntentAction<Unit, MoveIntent>(new MoveIntent()));
182+
```
183+
184+
And MoveIntent can be like:
185+
186+
``` csharp
187+
public class MoveIntent : IIntent<Unit>
188+
{
189+
public void Enter(Unit unit)
190+
{
191+
unit.StartMoveAnimation();
192+
}
193+
194+
public bool Execute(Unit unit)
195+
{
196+
// Recalculate unit position. It is done every tick.
197+
unit.Position = unit.CalculateNewPosition();
198+
// Finish move intent when unit at a target position.
199+
return unit.Position == unit.MoveTarget;
200+
}
201+
202+
public void Exit(Unit unit)
203+
{
204+
}
205+
}
206+
```
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System.Collections.Generic;
2+
3+
namespace BrainAI.AI.UtilityAI
4+
{
5+
/// <summary>
6+
/// Scores by checking each Appraisal score comparing it to 0.
7+
/// Returns 1 if all the Appraisals scores were converted to 1.
8+
/// Returns 0 othervise or if list of Appraisals is empty.
9+
/// </summary>
10+
public class AndAppraisal<T> : IAppraisal<T>
11+
{
12+
public readonly List<IAppraisal<T>> Appraisals = new List<IAppraisal<T>>();
13+
14+
15+
public AndAppraisal()
16+
{
17+
18+
}
19+
20+
public AndAppraisal(params IAppraisal<T>[] apparisals)
21+
{
22+
Appraisals.AddRange(apparisals);
23+
}
24+
25+
public float GetScore(T context)
26+
{
27+
if (Appraisals.Count == 0)
28+
{
29+
return 0;
30+
}
31+
32+
for (var i = 0; i < Appraisals.Count; i++)
33+
{
34+
if (Appraisals[i].GetScore(context) == 0)
35+
{
36+
return 0;
37+
}
38+
}
39+
40+
return 1;
41+
}
42+
}
43+
}
44+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System.Collections.Generic;
2+
3+
namespace BrainAI.AI.UtilityAI
4+
{
5+
/// <summary>
6+
/// Scores by checking each Appraisal score comparing it to 0.
7+
/// Returns 1 if child Appraisal score were converted to 0.
8+
/// Returns 0 othervise.
9+
/// </summary>
10+
public class NotAppraisal<T> : IAppraisal<T>
11+
{
12+
public readonly IAppraisal<T> ChildAppraisal;
13+
14+
public NotAppraisal(IAppraisal<T> apparisal)
15+
{
16+
ChildAppraisal = apparisal;
17+
}
18+
19+
public float GetScore(T context)
20+
{
21+
if (ChildAppraisal.GetScore(context) == 0)
22+
{
23+
return 1;
24+
}
25+
26+
return 0;
27+
}
28+
}
29+
}
30+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System.Collections.Generic;
2+
3+
namespace BrainAI.AI.UtilityAI
4+
{
5+
/// <summary>
6+
/// Scores by checking each Appraisal score comparing it to 0.
7+
/// Returns 1 if any Appraisal scores were converted to 1.
8+
/// Returns 0 othervise or if list of Appraisals is empty.
9+
/// </summary>
10+
public class OrAppraisal<T> : IAppraisal<T>
11+
{
12+
public readonly List<IAppraisal<T>> Appraisals = new List<IAppraisal<T>>();
13+
14+
15+
public OrAppraisal()
16+
{
17+
18+
}
19+
20+
public OrAppraisal(params IAppraisal<T>[] apparisals)
21+
{
22+
Appraisals.AddRange(apparisals);
23+
}
24+
25+
public float GetScore(T context)
26+
{
27+
if (Appraisals.Count == 0)
28+
{
29+
return 0;
30+
}
31+
32+
for (var i = 0; i < Appraisals.Count; i++)
33+
{
34+
if (Appraisals[i].GetScore(context) != 0)
35+
{
36+
return 1;
37+
}
38+
}
39+
40+
return 0;
41+
}
42+
}
43+
}
44+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace BrainAI.AI.UtilityAI
2+
{
3+
public class HasIntentAppraisal<T> : IAppraisal<T> where T : IIntentContainer<T>
4+
{
5+
private readonly float score;
6+
7+
public HasIntentAppraisal(float score)
8+
{
9+
this.score = score;
10+
}
11+
12+
public float GetScore(T context)
13+
{
14+
return context.Intent == null ? 0 : score;
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)