Another Glance at LambdaHack - A Year Later
Jul 10, 2011
4 minute read

A while back, when I first started to get serious about writing a game in Haskell, I decided to take a look at some existing code and try to figure out how they did things. At the time I had an interest in roguelikes so LambdaHack was a natural choice for investigaion. (part 1 and part 2)

I learned a lot from that project, though the way I wrote up my posts was a little bizarre. After making some decent progress on my turn-based strategy game, I stalled out after getting frustrated with Haskell and my difficulty in managing game state. Oddly enough, after a year, I’m getting the urge to give Haskell another shot so I figured I’d catch up on what’s changed with LambdaHack and take another run through the code now that I have some more experience.


Firstly, the project now has documentation on how to play and the design of the project. Writing them up in markdown is a great idea and github displays it nicely.

I’m also sad to report that there were still some installation issues for me. I’m running a new install of Fedora 15 with Haskell Platform 2011.2.0.0 and I could not cabal install LambdaHack my way through a default install. I don’t believe this to be LambdaHack’s problem as it appears to be some issue with cairo-0.12.0 and it’s dependencies. Cabal is a pretty awesome tool, but one problem with having a wide and deep dependency tree is that one hitch in the install process ruins it all. In my previous posts I installed LambdaHack using the -fcurses flag in cabal. This installed successfully, but ended up corrupting the terminal when I ran the game. The README now mentions using the following to install for terminals (which did work for me, though I also used –global):

cabal install -fvty LambdaHack

interesting bits

With the installation done, I want to cover some things that a new Haskell programmer [like me] might find interesting that I didn’t cover in earlier posts. My hope is that this post helps other people new to Haskell navigate the code to LambdaHack and make it easier to learn from the project.

startup and display specific code

As noted in part 2, the definition of main in immediately calls into whatever module got included into the build (controlled via LambdaHack.cabal). For the steps above, this is Display\Vty.hs. Each of the sub-systems in the Display folder end up implementing a startup function that takes a parameter of type Session. For vty, this is simply a Graphics.Vty.Vty type synonym.

type Session = V.Vty

Gtk.hs and Curses.hs each implement a record.

-- Gtk.hs
data Session =
  Session {
    schan :: Chan String,
    stags :: Map AttrKey TextTag,
    sview :: TextView }
-- Curses.hs
data Session =
    { win :: Window,
      styles :: Map (Maybe AttrColor, Maybe AttrColor) C.CursesStyle }

All of these are called Session, so that way cabal can choose which implementation to include. Naturally, each of the display implementations have a startup function defined (to be called straight away from main) with the same type:

startup :: (Session -> IO ()) -> IO ()

the game loop

Once some boilerplate stuff is done in the startup code of LambdaHack.hs, ultimately a call is made to handlerToIO.

handlerToIO :: Session -> State -> Message -> Action () -> IO ()

This can be found in Action.hs and exists to run the Action monad defined in that file. As a new Haskeller, also having been away for a while, the code in here is a bit scary. Fortunately, the details of this are not important to getting a high level view of the game loop.

Lets approach this from a practical standpoint by looking at Turn.hs. The start and generate functions from eventually call handlerToIO feeding it a function called handle defined in Turn.hs.

handle :: Action ()

This function simply returns an Action monad. It does some updating to the state, updates monsters and the player and then ultimately calls nextMove which then calls handle. That’s effectively it. The functions in Turn.hs do some book keeping by updating time, perceptions, monsters and running the player’s commands, but that it.

In this loop, handlePlayer may get called if the player can make a turn. This function will eventually evoke playerCommand which is where all of the input handling takes place. The playerCommand function handles some special run processing, but then calls handleKey (defined in KeyBindings.hs) looks the action up in the standard key bindings (defined back in Turn.hs) that were passed in and then returns the Action associated with that key (defined in Command.hs). As for the Actions themselves, they’re defined in Actions.hs which is where you should go to see the game logic.

what’s next

This barely scratches the surface of interesting stuff about LambdaHack like the level generation stuff in Dungeon.hs.

Hopefully it inspires you to take a look at some open source projects and read some source code yourself!