3

The AI code for my little soccer game basically works like this: There is a function that derives facts that describe the current situation on the pitch:

deriveFacts :: GameState -> [Fact]

... where the facts look kind of like this:

data Fact =
    FactCanIntercept ObjId ObjId
  | FactBestPosition Team Spot Spot
  | FactKickOff
  | FactBallCarrier ObjId
  | FactBestShootingVector Velocity3
  | FactBestPassingVector Velocity3
  | ...

... and there are rules that look uniformly like this:

rule_shoot facts = do
    FactBallCarrier ballCarrier <- checkBallCarrier facts
    FactBestShootingVector goalVector <- checkBestShootingVector facts
    return [message (ballCarrier, shoot goalVector)]

And then there is a rule engine that runs all the rules and gathers up the resulting messages.

That's fine so far and works quite nicely. What's kind of annoying, though: In this approach, I need an "accessor" function like this for every Fact:

checkBestShootingVector :: [Fact] -> Maybe Fact
checkBestShootingVector facts = 
   listToMaybe [e | e@(FactBestShootingVector {}) <- facts]

This approach leads to a lot of ugly boilerplate code. My question: Is there an elegant solution that removes the need for creating these accessor functions by hand?

martingw
  • 4,153
  • 3
  • 21
  • 26

1 Answers1

4

You could refactor most of your Fact data as a record object

data FactRec = FR {
   canIntercept :: [(ObjId,ObjId)], -- use lists for things you previously had multiple times
   factBestPosition :: [(Team,Spot,Spot)], -- not sure about this one, maybe two entries not one list 
   kickOff :: Bool,
   ballCarrier :: ObjID,
   factBestShootingVector :: Velocity3, 
   factBestPassingVector :: Velocity3,
   .....
   }

Which gives you bulti-in accessor functions

if kickOff fr then something else somethingelse

So you wouldn't need to write all the check functions, but instead do

rule_shoot facts = message (ballCarrier facts, shoot $ goalVector facts)

If there are facts that genuinely might or might not be there, they could be of type Maybe something, and if there are facts that can be there an arbitrary number of times, they can be lists.

AndrewC
  • 32,300
  • 7
  • 79
  • 115
  • 1
    Thanks, this give me something to think about! Why use a list of facts in the first place... Felt kind of right then, but right know I can't really say why... – martingw Nov 24 '12 at 10:22
  • 1
    the OP's original code for `rule_shoot` utilizes the `Maybe` monad, and `listToMaybe` will return at most the first element of the list, so perhaps the `FR` record should be defined with `Maybe` types instead of lists and `Bool`. It all depends on how you want `rule_shoot` to behave. – ErikR Nov 24 '12 at 16:57
  • 1
    @user5402 Usually not - I think the `Maybe` stuff was _mainly_ there to deal with the fact that the list might not contain the data you were after. Anyway, I already dealt with the possibility and its generalisation when I said "If there are facts that genuinely might or might not be there, they could be of type Maybe something, and if there are facts that can be there an arbitrary number of times, they can be lists." – AndrewC Nov 24 '12 at 17:06