I’m Alive! (Or, the Game of Life in F#)

Things have been pretty busy lately. Over the summer, I got my first job working as a programmer. A little over a month ago, I began attending university (first year). While the experiences thus far have been excellent on both counts, there have been a couple unfortunate downsides:

  1. I’ve barely had any time to blog. Not such an issue, since my writing sucks anyway 🙂
  2. I’ve had absolutely no time to sit down and write a game (!!!)

Surely I can’t be that busy… can I? There’s another reason: I’ve become totally addicted to functional programming. The traditional rendering APIs don’t lend themselves well to a functional style, and I’ve spent a lot of time pondering how I can twist them to suit my desires. I began working on some rough sketches of possible wrapper APIs, and attempted to implement some of them without much success.

I have come to realize that I can’t do it like that. Trying to design a rendering API without any particular game in mind is… hard. What exactly is needed? What features do I need to support? How do I design it in such a way that the necessarily impure functionality is separated from the pure? There’s a target, but it’s far away. I can try to shoot now, but I’d probably miss. I need to get closer, and it would seem the best way to do that is to write more games in functional languages.

In other words, actually get something done.

Bearing this in mind, I once again venture into the fantastic world of game programming…

John Conway’s Game of Life

(This counts as a game, right?)

I have to say, F# is pretty damn good. Interopability with the other languages on the .NET frameworks seems like it could use some work (how about you try passing a function object from C# to F#?) but overall it’s quite nice. I miss Haskell’s epically powerful type system, but I suppose it can’t be helped.

So here you go. The Game of Life, entirely in F#:

open System

// int -> int -> bool [,]
// Creates a width * height grid of boolean values which
// are randomly initialized to true or false
let Create width height =
    let random = Random()
    Array2D.init width height (fun _ _ -> random.Next() % 2 = 0)

// bool [,] -> int -> int -> int
// Computes the number of live neighbors in a 9x9 grid surrounding (x, y)
let Neighbors grid x y =
    // Create the 9x9 grid
    [for u in x - 1 .. x + 1 do
     for v in y - 1 .. y + 1 do
         yield u, v]

    // Ignore the current cell (the centre of the 9x9 grid)
    |> List.filter (fun (u, v) -> u <> x || v <> y)

    // Ensure that all the coordinates are within the bounds of the array
    |> List.filter (fun (u, _) -> 0 <= u && u < Array2D.length1 grid)
    |> List.filter (fun (_, v) -> 0 <= v && v < Array2D.length2 grid)

    // Count the number of live cells
    |> List.filter (fun (u, v) -> grid.[u, v])
    |> List.length

// bool [,] -> int -> int -> bool
// Determines whether a cell should be dead or alive in
// the next frame of the simulation.
let IsAlive grid x y =
    match Neighbors grid x y with
    | 3 -> true
    | 2 -> grid.[x, y]
    | _ -> false

// bool [,] -> bool [,]
// Advances the simulation by one frame
let Update grid =
    Array2D.mapi (fun x y _ -> IsAlive grid x y) grid

// bool [,] -> string
// Converts a grid of cells to a suitable string representation
// for printing to the console
let Show grid =
    [for y in 1 .. Array2D.length2 grid do
        for x in 1 .. Array2D.length1 grid do
            match grid.[x - 1, y - 1] with
            | true -> yield "O"
            | false -> yield " "
        yield "\n"]
    |> String.Concat

// bool [,] -> unit
// Renders a grid of cells to the console
let Render grid =
    Console.SetCursorPosition(0, 0)
    Console.WriteLine(Show grid) // WriteLine is much faster than printf

// Create, simulate and render a grid until the user presses a key.
Create 70 20
|> Seq.unfold (fun x -> Some (x, Update x))
|> Seq.takeWhile (fun _ -> not Console.KeyAvailable)
|> Seq.iter Render

Wow, eh? Excluding the comments, the program is a little over 50 lines long. Considering that (almost?) everything in F# is an expression, I could probably reduce this to a couple lines with little effort. Not that I’d every want to do that…

So what have I learned from this?

  1. The console can actually render things pretty quickly. I don’t know what kind of frame rate I was getting, but the entire simulation would usually “finish” in around 5 – 30 seconds. I know that’s a pretty big range, but this thing can go on for ages if the starting conditions are right.
  2. The rendering function could be abstractly defined as a function which simply takes in a description of what needs to be rendered, and returns nothing. This is in contrast to previous ideas I had about “functionalizing” the rendering pipeline, where I would repeatedly pipe the “render target” (which was really just a list of rendering commands) through a series of functions which would “transform” the current render target (i.e. add a command to the list) and return a new copy. Perhaps this idea will be more appealing when I get to more complex visuals?
  3. Game logic, user input, and rendering can be separated from rendering quite easily (at least for this example). Take a look at the last statement – see how each line corresponds to exactly one of the aforementioned tasks?
    Game Logic:     Seq.unfold (fun x -> Some (x, Update x))
    User Input:     Seq.takeWhile (fun _ -> not Console.KeyAvailable)
    Rendering:      Seq.iter Render

It’s not much, but it’s a start. The plan is to slowly implement games with increasing complexity, and document my observations about their implementations. So what should I implement next?

The name’s Pong. Console Pong.

Gah, sorry. Until next time.



Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s