Introduction
In this part I will build the infrastructor to validate game commands. Game commands are the requests to perform an action in the game such as move an actor, take an item, attack an actor etc. These will be composed of two parts:
- Validator that ensures the command is valid.
- The CRUD operations that perform the manipulation in the game world.
I pointed out last time in CRUD Operations that operations throw an exception if something is not where it is expected to be. So the validators ensure this is the case and give an error message when the command is invalid, they are also used in the AI code.
WARNING: I make use of those oh so scary Monad things in the validators. I will try to ease you in gently :)
Monad Primer
This short primer is to help you follow the later code, contrary to what you will find on the internet Monads are actually piss easy. People make they sound too bloody complex imho. All they do is wrap value(s) with some state and provide a means to compose them. First some common Monad types:
- Maybe - A safe way to make a value "nullable" for want of a better term. This is option in F#
- Sequence - A way to interact with elements in some form of collection. C# LINQ and Java 8 Streams are actually Monad engines
- Either - A way to represent a value or an error. Functional programmers hate exceptions, they are the devils work!
A monad is just a value or values wrapped in a container, such as Maybe, along with two functions. The functions are:
- Return - Takes a value and wraps it inside of a Monad. The c# code "int? a = 5;" is return, it takes 5 and stores it as a nullable type.
- Bind - Unpacks the value from a Monad and passes it to the supplied function which returns a new Monad. A common name is fmap or flatmap, in LINQ it is SelectMany.
The first one is easy enough but the second one is more subtle if you have not used map/reduce libraries before.
First some basic function composition. If you have function F that takes 'a and returns 'b and function G that takes 'b and returns 'c it stands to reason that you can feed the results of F directly into G and this gives you a new function H from 'a to 'c.
The top two boxes show this, you can see how they would plug together.
The bottom two boxes show the same thing but the result is an Option<'x>, which can be Some 'x or None. It has two distinct results, the function signatures are now:
'a -> 'b option
'b -> 'c option
They no longer plug together :(
Bind to the rescue
let bind func monad =
match monad with
| Some value -> func(value)
| None -> None
It unpacks the option Monad with match. If the Monad is a Some of value it calls the function otherwise it just perpetuates the None through the chain. We now have a means to compose the functions again.
And that is all there is to it, feel free to ask questions if you need to.
Hopefully the rest of this will show their true power :)
Result Monad
The Monad I use for my validators is similar to F# Choice. Instead of holding a value or nothing it holds a value or an error message. I could have used Choice which is built in to F# but it has such ugly syntax, it makes my eyes bleed. So I created my own called result:
First we have the type. This can be a Valid of value or an Invalid of error message. Bind is similar to what you have already seen. If it is valid then it calls the function otherwise it perpetuates the error.
type result<'a> =
| Valid of 'a
| Invalid of string
let bind func monad =
match monad with
| Valid value -> func value
| Invalid error -> Invalid error
Next up is some F# slight of hand to make everything nicer to use. resultFactory builds a comprehension block for the monad, this is similar to C# query syntax. Do not worry about this bit to much you will see it in action later on.
type resultFactory() =
member this.Bind(monad, func) = bind func monad
member this.Return(value) = Valid value
let result = new resultFactory()
So there we have it, our Monad. Time to use it in anger :)
Validation
Each validator is made from a set of game rules. If all the rules pass we have a valid command but if a rule fails that error message is resurned and no further validation occurs.
I have only built the first validator at the moment, the one for move actor. The reason for this is I am happy with all the code structure and want to push onwards to the game loop in the next article. The only step after that is get the user input and render the display and the game will be alive...
ALIVE I TELL YOU wahahahaha
Or put is another way, we should then be able to move around a map :)
The rules we need for move actor are easy enough:
- The target location to move to is within the map bounds
- The actor to move exists
- The target location does not block movement
- The target location is within the valid move distance for an actor
The logic is already implemented in for the tests in Level Queries. This makes the rules easy to write, each one just calls the associated query function and then returns the associated information or an error message.
let isValidLocation location level =
if level |> hasCoordinate location then
Valid location
else
Invalid "That location is not on the map"
let actorExists actorId level =
match level |> getActor actorId with
| Some actor -> Valid actor
| None -> Invalid "The actor does not exist"
let isEmptyTile location level =
if level |> checkLocationFor blockMove location then
Invalid "You can't move to that location"
else
Valid (getTile location level)
let isValidMoveDistance target location =
if location |> distanceFrom target <= 1.0 then
Valid target
else
Invalid "You can't move that far"
Now we just plug them together, this is where your head might spin a little.
let isValidMove target actorId level =
result {
let! validTarget = level |> isValidLocation target
let! tile = level |> isEmptyTile validTarget
let! actor = level |> actorExists actorId
let! validMove = actor.location |> isValidMoveDistance validTarget
return level
}
I know at first glace this is cryptic as F*&k, so I will walk you through it.
You have seen let before, it assigns a value or function to a name. let! is similar but is a short hand for calling bind. In this case let! wraps either level or actor.location in a monad then calls bind on the monad to call the related rule, if Valid it unpacks the value from the Result and assigns it to the let! name. If all the rules pass it returns the level as Valid, otherwise we get the error from the rule that failed and none of the rules after it run.
That is a crap load of logic abstracted away in a nice clean syntax, this is the power of Monads. They allows you to abstract away the control logic that you do not care about and hide it, leaving clean syntax that almost looks like normal code.
This would be an if/else ladder from hell otherwise, think about how you might implement it using an OO style language. Not nice.
You can use this technique to hide away anything from null handling to logging and leave what we actually care about, what is happening. How many simple functions have you seen made to look cluttered and complex with null checking and logging etc.
Summary
So that is it. Validation done! At least for move actor :)
In the next part I will move onto the game loop, this is probably what people want to see. The place where too many people start but as you will see next time my approach of building upward and thinking about how interaction will occur means the game loop almost writes itself :)
And for those who have only just found the series, here is the story so far for Building a roguelike in F# from scratch :
Finally the GitHub Repo
Feel free to ask questions and happy coding :)
Woz