patterncsharpMinor
Defining instructions for a robot's movements
Viewed 0 times
robotinstructionsmovementsfordefining
Problem
I have a project which should be thought of split into two layers: an IO layer which consists of sensors (input) and effectors (output), and a core layer which contains logic for determining which actions are desired from particular observations. The IO layer is dependent on the core layer, and the core layer is ignorant of the details of particular sensors and effectors.
The flow is as follows: the IO layer gets some sensory input, translates that into an observation that the core understands. The core uses its logic to generate an action from that, which is sent back to the IO layer and translated into some instruction to the effectors. So for example, a camera sensor might sense an object becoming larger. This would be translated into a
The logic for determining which action needs to be taken based on each observation is in "Instruction" objects. The class in the core layer coordinating all of this is called
Here's the core code:
```
public class Agent
{
private readonly InstructionCheckerStorer _instructionCheckerStorer;
public Agent(InstructionCheckerStorer instructionCheckerStorer)
{
_instructionCheckerStorer = instructionCheckerStorer;
}
public void Observe(T observation)
{
var checkers = _instructionCheckerStorer.GetCheckers();
foreach(var checker in checkers)
checker.Check(observation);
}
}
public class InstructionCheckerStorer
{
private readonly Dictionary> _storage = new Dictionary>();
public IEnumerable> GetCheckers()
{
if (!_storage.ContainsKey(typeof (TObservation)))
return new List>();
return _storage[typeof (TObservation)].Cast>();
}
public void Add(IInstructionChecker checker)
{
List checkers;
if(!_storage.TryG
The flow is as follows: the IO layer gets some sensory input, translates that into an observation that the core understands. The core uses its logic to generate an action from that, which is sent back to the IO layer and translated into some instruction to the effectors. So for example, a camera sensor might sense an object becoming larger. This would be translated into a
ApproachingObjectObservation. The core layer would translate this to a AmbulatoryAction, which would be sent to the WheelsEffector. The logic for determining which action needs to be taken based on each observation is in "Instruction" objects. The class in the core layer coordinating all of this is called
Agent, and the corresponding class in the IO layer is Robot.Here's the core code:
```
public class Agent
{
private readonly InstructionCheckerStorer _instructionCheckerStorer;
public Agent(InstructionCheckerStorer instructionCheckerStorer)
{
_instructionCheckerStorer = instructionCheckerStorer;
}
public void Observe(T observation)
{
var checkers = _instructionCheckerStorer.GetCheckers();
foreach(var checker in checkers)
checker.Check(observation);
}
}
public class InstructionCheckerStorer
{
private readonly Dictionary> _storage = new Dictionary>();
public IEnumerable> GetCheckers()
{
if (!_storage.ContainsKey(typeof (TObservation)))
return new List>();
return _storage[typeof (TObservation)].Cast>();
}
public void Add(IInstructionChecker checker)
{
List checkers;
if(!_storage.TryG
Solution
From my understanding of the problem we have,
On the input side - a set of arbitrary observations. The data in each of which can/will be different.
On the output side - a set of possible actions that can be effected. The action is decided based upon the observations and the actions can be parameterised based upon the observations.
This solution seems to work
Marker interfaces just to make the generics a bit better. I am dubious of using generics without constraints.
Here is how we match up the observations and the actions. Sorry about the name changes but I got a bit lost with the various different interfaces.
The store for the rules. The important bit here is checking only against the rules for a given observation type else we get a cast error.
I have some test scenarios. We have observations of moving and static objects and depending upon the observations we either laugh (at the puny missile), dodge out of the way or shoot the sitting duck.
And the rules are
I stuck them into some test cases and they seem to work well.
```
[TestMethod]
public void CheckRules() {
var rules = new ThreeRingBinder();
rules.RegisterRule(new ApproachingRuleOne());
rules.RegisterRule(new ApproachingRuleTwo());
rules.RegisterRule(new LoiteringRule());
// if a small object is approaching, laugh
IObservation ob = new ApproachingObjectObservation { Size = 1 };
IAction act = rules.CheckForAction(ob);
var laughAct = act as LaughAction;
Assert.IsNotNull(laughAct);
Assert.AreEqual("VERY", laughAct.HowLoud);
// if a large object, dodge
On the input side - a set of arbitrary observations. The data in each of which can/will be different.
On the output side - a set of possible actions that can be effected. The action is decided based upon the observations and the actions can be parameterised based upon the observations.
This solution seems to work
Marker interfaces just to make the generics a bit better. I am dubious of using generics without constraints.
public interface IObservation {
}
public interface IAction {
}Here is how we match up the observations and the actions. Sorry about the name changes but I got a bit lost with the various different interfaces.
public interface IRule {
Type ObservationType { get; }
bool Match(IObservation observation, out IAction action);
}
public interface IRule : IRule where TObservation : IObservation where TAction : IAction {
}
public abstract class Rule : IRule where TObservation : IObservation where TAction : IAction {
public Type ObservationType { get { return typeof(TObservation); } }
public virtual bool Match(IObservation observation, out IAction action) {
var ret = false;
action = default(TAction);
if (OnMatch((TObservation)observation)) {
action = CreateAction((TObservation)observation);
ret = true;
}
return ret;
}
protected abstract bool OnMatch(TObservation observation);
protected abstract TAction CreateAction(TObservation observation);
}The store for the rules. The important bit here is checking only against the rules for a given observation type else we get a cast error.
public class ThreeRingBinder {
private readonly List _rules;
public ThreeRingBinder() {
_rules = new List();
}
public void RegisterRule(IRule rule) {
_rules.Add(rule);
}
public IAction CheckForAction(IObservation observation) {
IAction action = null;
foreach(var rule in _rules.Where(r => r.ObservationType == observation.GetType())) {
if (rule.Match(observation, out action)) {
break;
}
}
return action;
}
}I have some test scenarios. We have observations of moving and static objects and depending upon the observations we either laugh (at the puny missile), dodge out of the way or shoot the sitting duck.
public class ApproachingObjectObservation : IObservation {
public int Size { get; set; }
public int Speed { get; set; }
}
public class StaticObjectObservation : IObservation {
public int Size { get; set; }
public int Distance { get; set; }
}
public class LaughAction : IAction {
public string HowLoud { get; set; }
}
public class DodgeAction : IAction {
public int HowFar { get; set; }
}
public class ShootAction : IAction {
public int Power { get; set; }
}And the rules are
// If something is approaching then if it is small, we laugh at it (the smaller it is the louder we laugh)
// if it is big, we dodge out of the way, the bigger it is, the farther we dodge
// If something is sitting there, we see if it is in range, if it is we shoot it, if not we ignore.
// The bigger it is, the more power we hit it with.
// laugh at small missiles
public class ApproachingRuleOne : Rule {
protected override bool OnMatch(ApproachingObjectObservation observation) {
return observation.Size {
protected override bool OnMatch(ApproachingObjectObservation observation) {
return observation.Size >= 100;
}
protected override DodgeAction CreateAction(ApproachingObjectObservation observation) {
var ret = new DodgeAction();
if (observation.Size >= 1000) {
ret.HowFar = 1000;
} else if (observation.Size >= 500) {
ret.HowFar = 500;
} else {
ret.HowFar = 100;
}
return ret;
}
}
// shoot (nearby) targets
public class LoiteringRule : Rule {
protected override bool OnMatch(StaticObjectObservation observation) {
return observation.Distance = 1000) {
ret.Power = 1000;
} else if (observation.Size >= 500) {
ret.Power = 500;
} else {
ret.Power = 100;
}
return ret;
}
}I stuck them into some test cases and they seem to work well.
```
[TestMethod]
public void CheckRules() {
var rules = new ThreeRingBinder();
rules.RegisterRule(new ApproachingRuleOne());
rules.RegisterRule(new ApproachingRuleTwo());
rules.RegisterRule(new LoiteringRule());
// if a small object is approaching, laugh
IObservation ob = new ApproachingObjectObservation { Size = 1 };
IAction act = rules.CheckForAction(ob);
var laughAct = act as LaughAction;
Assert.IsNotNull(laughAct);
Assert.AreEqual("VERY", laughAct.HowLoud);
// if a large object, dodge
Code Snippets
public interface IObservation {
}
public interface IAction {
}public interface IRule {
Type ObservationType { get; }
bool Match(IObservation observation, out IAction action);
}
public interface IRule<TObservation, TAction> : IRule where TObservation : IObservation where TAction : IAction {
}
public abstract class Rule<TObservation, TAction> : IRule<TObservation, TAction> where TObservation : IObservation where TAction : IAction {
public Type ObservationType { get { return typeof(TObservation); } }
public virtual bool Match(IObservation observation, out IAction action) {
var ret = false;
action = default(TAction);
if (OnMatch((TObservation)observation)) {
action = CreateAction((TObservation)observation);
ret = true;
}
return ret;
}
protected abstract bool OnMatch(TObservation observation);
protected abstract TAction CreateAction(TObservation observation);
}public class ThreeRingBinder {
private readonly List<IRule> _rules;
public ThreeRingBinder() {
_rules = new List<IRule>();
}
public void RegisterRule(IRule rule) {
_rules.Add(rule);
}
public IAction CheckForAction(IObservation observation) {
IAction action = null;
foreach(var rule in _rules.Where(r => r.ObservationType == observation.GetType())) {
if (rule.Match(observation, out action)) {
break;
}
}
return action;
}
}public class ApproachingObjectObservation : IObservation {
public int Size { get; set; }
public int Speed { get; set; }
}
public class StaticObjectObservation : IObservation {
public int Size { get; set; }
public int Distance { get; set; }
}
public class LaughAction : IAction {
public string HowLoud { get; set; }
}
public class DodgeAction : IAction {
public int HowFar { get; set; }
}
public class ShootAction : IAction {
public int Power { get; set; }
}// If something is approaching then if it is small, we laugh at it (the smaller it is the louder we laugh)
// if it is big, we dodge out of the way, the bigger it is, the farther we dodge
// If something is sitting there, we see if it is in range, if it is we shoot it, if not we ignore.
// The bigger it is, the more power we hit it with.
// laugh at small missiles
public class ApproachingRuleOne : Rule<ApproachingObjectObservation, LaughAction> {
protected override bool OnMatch(ApproachingObjectObservation observation) {
return observation.Size < 100;
}
protected override LaughAction CreateAction(ApproachingObjectObservation observation) {
var ret = new LaughAction();
if (observation.Size < 10) {
ret.HowLoud = "VERY";
} else if (observation.Size < 50) {
ret.HowLoud = "very";
} else{
ret.HowLoud = "a bit";
}
return ret;
}
}
// dodge large ones
public class ApproachingRuleTwo : Rule<ApproachingObjectObservation, DodgeAction> {
protected override bool OnMatch(ApproachingObjectObservation observation) {
return observation.Size >= 100;
}
protected override DodgeAction CreateAction(ApproachingObjectObservation observation) {
var ret = new DodgeAction();
if (observation.Size >= 1000) {
ret.HowFar = 1000;
} else if (observation.Size >= 500) {
ret.HowFar = 500;
} else {
ret.HowFar = 100;
}
return ret;
}
}
// shoot (nearby) targets
public class LoiteringRule : Rule<StaticObjectObservation, ShootAction> {
protected override bool OnMatch(StaticObjectObservation observation) {
return observation.Distance < 100;
}
protected override ShootAction CreateAction(StaticObjectObservation observation) {
var ret = new ShootAction();
if (observation.Size >= 1000) {
ret.Power = 1000;
} else if (observation.Size >= 500) {
ret.Power = 500;
} else {
ret.Power = 100;
}
return ret;
}
}Context
StackExchange Code Review Q#54566, answer score: 4
Revisions (0)
No revisions yet.