about a year ago i was wanting to explore other ways to improve my haskell skills and work towards contributing to open source projects, I spoke with an individual on the haskell irc channel who created a really simple game that demonstrated the lens and machines packages that i instantly thought was a great piece of engineering and simple enough to contribute. while the source code is not a large project it is long enough where i will just share the relevant portions in this post. You can view the two repositories below to follow along.
Original repo on github can be found here
my fork of it with the updates below can be found on github here
what is the game?
glguy wrote a great demonstration of the two packages (LENS, MAchines) called twos game. it creates a 4×4 board that starts with 2 squares with it randomly generating an integer being either 2 or 4. you want to move to merge the squares together to create a sum and once merged your points are scored then generating a new square randomly on the board.
what are the lens and machine packages?
in a high level view the lens package helps us access nested data structures more eloquently than if we were to not use it. in the game we are utilizing lens’s to access the game adt.
-- | Game state
-- Here we declare a Game type with a Game constructor. We define
-- 4 fields with types Board, Int and a Maybe Game as the previous
-- state may not be a valid game.
--
data Game = Game { _board :: Board
, _score, _delta :: Int
, _previous :: Maybe Game
}
-- | Lens expression
-- Our lens expression is very simple we are taking our functor f
-- which could be getting or setting. x is our game record
-- containing Board. (_board x) retrieve the current board and
-- f (_board x) applies our functor operation to that result.
--
-- If we are setting the the board we map over the result with fmap
-- take in a new value b and apply it to board to return a new game
board :: Lens' Game Board
board f x = fmap (\b -> x { _board = b }) (f (_board x))
Not entirely complicated, but how he initially implemented this is well thought out and allows us to utilize the lens package to keep our state updated nicely. Now, the machines package has alot of features that can be utilized depending what we are creating. the package description has a good explanation of how this is used in this game.
Simple machines that take one input are called a
Process
and processes form aCategory
. More generally you can attach aProcess
to the output of any type ofMachine
, yielding a newMachine
.
You will see the machines package more as we talk about the area of code that we are modifying as it is part of the game logic.
updating the vim bindings
His initial advice to me was to pick an area i felt comfortable in and start messing with some changes. his first suggestion was to change the vim bindings to allow us to use the arrow keys that would allow us to move the pieces on the board. so that’s what we first did and named it arrowbindings
-- | Game movement controls
-- Initially this was a very similiar function that used the w,s,a,d
-- keys to move the pieces on the board. To allow for us to use the
-- arrow keys we had to make a few modifications and ofcourse rename
-- the method for clarity.
--
-- We 'repeatedly', wait for input from the previous process result
-- and we will receive a Char, the first case checks if it is an
-- arrow key which initially sends a \ESC followed by another
-- sequence. [A, .. etc. We need to disregard the [ Char to process
-- which arrow key was actually sent and then yield a Direction with
-- the Move command. This happens in the inner case.
arrowBindings :: Process Char Command
arrowBindings = repeatedly $ do
c <- await
case c of
'\ESC' -> do
_ <- await -- disregard [
arrow <- await -- Check which arrow key was pressed
case arrow of
'A' -> yield (Move U)
'B' -> yield (Move D)
'C' -> yield (Move R)
'D' -> yield (Move L)
_ -> return ()
'q' -> stop
'u' -> yield Undo
_ -> return ()
Note about this code: repeatedly is a combinator in the machines package that creates an infinite process, since we are continually retrieving move commands to update state. if we didn’t use this the machine would stop after the first move command. I wanted to show this before the two ADT’s that help abstract away a more complex way to change state based on movement. if you take a look at ‘q’ in the above code this should make sense!
Now I wanted to show how this project used ADTS really well to abstract away the commands move types to use adts. with this we have type safety, reusability and separation of concerns. it very clearly shows what is expected.
-- | Direction and Command ADTs
-- Really simple data types that we send based on our arrowBindings
data Direction = U | D | L | R
data Command = Undo | Move Direction
whats next?
Now we have implemented moving the pieces with arrow keys what should we do next? really anything! increasing the board size, add complexity? add bonuses. This demo is a great starting point to really start expanding your haskell knowledge while creating something with feedback almost immediately.
Again, all credit should go to glguy for creating a well thought out demo of lens and machines packages while creating a fun little game.