Haskell roguelike

Posted on April 2, 2018

Introduction

In this series of posts I’m going to discuss some of the major design decisions that you will need to consider when making a Haskell roguelike game. I’ll be talking about how the code is implemented as well as the decisions I took along the way and why.

See the chapter list below to get an idea of what topics I’ll be covering.

What is a roguelike?

RogueBasin defines a roguelike as

A Roguelike is usually described as a free turn-based computer game with a strong focus on intricate gameplay and replayability, and an abstract world representation using ASCII-based display, as opposed to 3D graphics. Of course, as with any genre, there are deviations from the norm.

Other roguelike tutorials and engines

There are several good Haskell roguelike tutorials and tools. The ones I know about are

Each of the tutorials takes a different approach, cover different topics and address different levels of familiarity with haskell. The more the merrier! If you are interested in building a roguelike, or just looking for ideas for writing games in Haskell, then looking at the links above as well as this series seems like a good idea.

LambdaHack is a game engine for making ASCII roguelike games. It is definitely worth taking a look at. For this series I decided not to use LambdaHack as it already implements many of the things I was interested in learning about. This is a trade-off you’ll have to make. Are you currently more interested in learning how to build the game or more interested in getting a robust game together? I’d suggest learning with a small roguelike first as you’ll then have a much better idea of what a game engine offers or how it may constrain you.

RogueBasin has a vast amount of detail on creating roguelike games. Its well worth your time to look at for ideas. I’ll be referring to articles from roguebasin throughout the series

The series

Why read this series?

Apart from just being another perspective, I think that these are some reasons you may want to continue reading

Also I’m not a gamer and this is the first game I’ve ever written, so that should bring a slightly different perspective ;)

Structure

There are 20 “chapters”. Each chapter builds on the previous one. Each chapter’s code is available on github and has a working example. At the end of each chapter is a patch file that shows what was added/changed.

Notes on the code

I’ve tried to keep the Haskell code simple, hopefully it is easy to follow even if you don’t have much experience. E.g. There is only one transformer used (chapter 14) and that is entirely optional.

I am using Protolude as my prelude. It should be fairly easy to switch to something else. Note that protolude defines <<$>> which is the same as (fmap . fmap) or <$$> from composition-extra

Lenses

I am using lenses, since there are several nested record types I use. If you have not used lens before it may seem odd. I’m mostly using only three lenses, so you should be able to follow along without worrying about them too much.

If you are unfamiliar with lenses here are some links that may help get you started quickly,

And here are a few quick examples that may help you get a sense for what they do.

Given this definition

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

module Main where
import Protolude
import qualified Data.Text as Txt
import           Control.Lens
import           Control.Lens.TH

data Parent = Parent { _pName :: Text
                     , _pChild :: Child
                     } deriving (Show)
            
data Child = Child { _cName :: Text
                   } deriving (Show)

makeLenses ''Parent
makeLenses ''Child

main :: IO ()
main = do
  let parent = Parent { _pName = "parent1"
                      , _pChild = Child { _cName = "child1" }
                      }

^.

The ^. lens acts as a field getter

  # With lens
  putText $ parent ^. pName
  putText $ parent ^. pChild ^. cName

  # Without lens
  putText $ _pName parent
  putText $ _cName . _pChild $ parent

Which both print

parent1
child1

There is not too much difference between the two styles, yet.

.~

The .~ lens acts as a field setter

  # With lens
  print $ parent & pName .~ "newName"
  print $ parent & (pChild . cName) .~ "new childName"


  # Without lens
  print $ parent { _pName = "newName" }
  print $ parent { _pChild = (_pChild parent) { _cName = "new childName" } }

Which both print

Parent {_pName = "newName", _pChild = Child {_cName = "child1"}}
Parent {_pName = "parent1", _pChild = Child {_cName = "new childName"}}

Once you start updating nested records, I think the lens code is much easier to read. The deeper the nesting the more true this is.

%~

The %~ lens acts as a field updater that works by sending the current value through a function

  # With lens
  print $ parent & pName %~ Txt.toUpper
  print $ parent & (pChild . cName) %~ Txt.toUpper


  # Without lens
  print $ parent { _pName = Txt.toUpper . _pName $ parent }
  print $ parent { _pChild = (_pChild parent) { _cName = Txt.toUpper . _cName . _pChild $ parent  } }

Which both print

Parent {_pName = "PARENT1", _pChild = Child {_cName = "child1"}}
Parent {_pName = "parent1", _pChild = Child {_cName = "CHILD1"}}

Here I’d say the lens code is significantly easier to read, i.e. the intent is much clearer.

Quite often you’ll end up combining these three lenses and the alternative, in my view, is way too noisy. There are some tradeoffs using lens, e.g. template haskell but I feel that it is justified by the resulting code. Obviously if you prefer not to use lens you can do everything reasonably easily without it.

Chapters

Bonus

Links