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
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}
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 Signal
s, 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.
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.