Some other roguelike

TL;DR: I'm making a game in a browser based functional language called Elm. Here's a simple Hello World-type demo.

Inspired by purely functional retro games and Steve Losh's The Caves of Clojure series, I've decided to write a roguelike game in a variant of Haskell called Elm. I call the game "The Enchantments of Elm", keeping with Steve's alliterative naming.

According to the language's website, Elm is a functional language that compiles to HTML, CSS, and JavaScript. It is designed for functional reactive programming, which is based on the idea of auto-updating values (similar to two-way data binding in Angular.js). To me, this seems like an ideal language to write a browser-based game.

I plan to develop this roguelike over time and blog about it. This series is best if you're familiar with functional programming concepts such as high-order functions and combinators. This first iteration of the game isn't that exciting. It waits for the player to hit Enter and then ends the game.

Let's get started. First some necessary imports that we will use later.

import Keyboard
import Window

State Manipulation

Our game will need a state object to store player stats, inventory and such. Like in other languages with immutable data structures, the state will be manipulated by functions that accept the current state and return a new state.

For now, we only keep track of whether the game has ended yet. Elm has a data structure called a record, which is similar to a dictionary. We'll define a GameState type to be a record with one field.

type GameState = {finished : Bool}

We'll make a function to return the default state of the game. Naturally, the finished property will start out as false.

defaultGame: GameState
defaultGame = {finished = False}

It's time to write that update function to transform the state. This function will get called on user input, namely when the player presses Enter. It ignores the input for now since it just tells us that the key has been pressed.

update : Bool -> GameState -> GameState
update input state = case state.finished of
                       True -> state
                       False -> {state | finished <- True}

Signals and FRP

Now we get to the fun part of Elm. While game programming in most languages would involve a main loop, Elm doesn't have loops at all. Updates are triggered by Signals, which represent "reactive" values.

The one we care about for now is the built-in signal Keyboard.enter. It gets triggered when the Enter key is pressed.

Instead of writing handlers or callbacks, we will write a function called gameState that "collects" keyboard Signal events and turns them into game state Signals.

gameState : Signal GameState
gameState = foldp update defaultGame Keyboard.enter

foldp is a folding function over a Signal (which I think are applicative functors...) Every time Keyboard.enter is triggered, foldp will call update, which turns a signal input and current state into a new state. In this way, user interactions can trigger changes to our game's state. We pass defaultGame as the starting value.

Rendering

All this state manipulation is useless if it doesn't output anything to the screen. So we'll define a function called renderState that does just that. It displays a different message depending on whether or not the game has finished.

renderState : GameState -> Element
renderState {finished} = case finished of
                      False -> plainText "Press enter to end game"
                      True -> plainText "GOODBYE"

The Elm standard library has many text and image processing functions. As this game becomes a real roguelike, that functionality will be useful to render menues, map tiles, and character sprites. For now, we are just going to use the plaintext features.

Finally our main function ties everything together. It takes the rendered gameState and passes it to display, which just puts it on the center of the screen.

Earlier I said that gameState is itself a signal. Whenever the gameState signal triggers, main will re-render and display it.

Note that we lift renderState and display in order to apply them to Signals.

display : (Int, Int) -> Element -> Element
display (h, w) el = container h w middle el

main = lift2 display Window.dimensions <| lift renderState gameState

Let us enjoy the fruits of our labor. Click here or enjoy this very visually impressive screen capture.

Or visit the live demo.