Inside F#

Brian's thoughts on F# and .NET

“Game of Life” Challenge

Posted by Brian on June 15, 2008

I was browsing the web looking for fun small programming tasks, and I stumbled across Conway’s Game of Life.  This is an oldie but a goodie; I am continually fascinated by how such a simple set of rules and starting conditions leads to such complex behavior.

Coding up the game logic is simple; however I know so little about graphics/presentation that I struggled to make a minimal display.  The first thing I got working was displaying a different bitmap image each frame, yielding results that look like this:

GameOfLife

While good enough to watch, I would love to make it prettier (show the number of generations, perhaps use color to suggest the age of cells, use circles or gridlines or something that makes the image itself look nicer) and have interaction (slider bar to control speed, ability to pause and interact to add/remove cells, change size/zoom of overall grid), as well as be better engineered (the code I wrote does both the animation and the display very crudely), but I lack the WPF skills, the time, or both.

…Hence the "challenge" in the blog title today.  Show me your skills!  I would like to see what others can do with F#.  Extra points for good eye candy, cool user interaction, and demonstration of advanced WPF features.  You can put a link to your submission as a ‘comment’ on this blog entry.  Have fun!

(EDIT, Feb 2010: I just saw http://fsharpnews.blogspot.com/2010/02/john-conways-game-of-life-in-32-lines.html check it out)

The source code

Here’s the code I have:

#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0" 
#r @"WindowsBase.dll" 
#r @"PresentationCore.dll" 
#r @"PresentationFramework.dll"

open System
open System.Windows 
open System.Windows.Controls 
open System.Windows.Media 
open System.Windows.Media.Imaging
open System.Threading

let MAXX = 80
let MAXY = 60
let SCALE = 8   // size of block in pixels – this must be 8 (for simplicity of coding bitmap)

// game board is just 2D array of bools
let mutable cells = Array2D.create MAXX MAXY false

let NumNeighbors(x,y) =
    let At(x,y) =
        if x < 0 || x >= MAXX || y < 0 || y >= MAXY then
            false
        else
            cells.[x,y]
    let mutable r = 0
    if At(x-1,y-1) then r <- r + 1
    if At(x-1,y) then r <- r + 1
    if At(x-1,y+1) then r <- r + 1
    if At(x,y-1) then r <- r + 1
    if At(x,y+1) then r <- r + 1
    if At(x+1,y-1) then r <- r + 1
    if At(x+1,y) then r <- r + 1
    if At(x+1,y+1) then r <- r + 1
    r

// compute next board
let Tick() =
    let nextCells = Array2D.init MAXX MAXY (fun x y ->
        if cells.[x,y] then
            match NumNeighbors(x,y) with
            | 2 | 3 -> true
            | _ -> false
        else
            NumNeighbors(x,y) = 3)
    cells <- nextCells

// use a small grid of 1s and 0s to initialize the whole grid;
// put initial contents in the center of the large grid
let Init(seed) =
    let seedHeight = Array.length seed
    let seedWidth = Array.length seed.[0]
    let starty = (MAXY – seedHeight) / 2
    let startx = (MAXX – seedWidth) / 2
    for x in 0..seedWidth-1 do
        for y in 0..seedHeight-1 do
            cells.[startx+x,starty+y] <- seed.[y].[x] = 1

// some sample starters
let glider = [| [| 1; 1; 1 |] 
                [| 1; 0; 0 |]
                [| 0; 1; 0 |]
             |]
let diehard = [| [| 0; 0; 0; 0; 0; 0; 1; 0 |] 
                 [| 1; 1; 0; 0; 0; 0; 0; 0 |]
                 [| 0; 1; 0; 0; 0; 1; 1; 1 |]
              |]
let fPentomino = [| [| 0; 1; 1 |] 
                    [| 1; 1; 0 |]
                    [| 0; 1; 0 |]
                 |]
let acorn = [| [| 0; 1; 0; 0; 0; 0; 0 |] 
               [| 0; 0; 0; 1; 0; 0; 0 |]
               [| 1; 1; 0; 0; 1; 1; 1 |]
            |]
             
Init(acorn)

type MyWPFWindow() as this = 
    inherit Window()    

    // make a bitmap of the data
    let GetByte bn =
        let n = bn * 8
        let y = n / (MAXX*SCALE*SCALE)
        let xs = n % (MAXX*SCALE*SCALE)
        let x = (xs % (MAXX*SCALE))/SCALE
        if not(cells.[x,y]) then
            byte 255
        else
            byte 0
    let MakeBitmapSource() =
        let bytes = Array.init (SCALE*MAXX*MAXY) GetByte
        BitmapSource.Create(SCALE*MAXX, SCALE*MAXY, 1., 1., PixelFormats.BlackWhite, 
                            BitmapPalettes.BlackAndWhite, bytes, MAXX)
    // display code   
    let image = new Image(Width=float (SCALE*MAXX), Height=float (SCALE*MAXY))
    let mutable iter = 0
    do
        image.Source <- MakeBitmapSource()
        CompositionTarget.add_Rendering(fun o e -> // prevent loss of first frame
                                                   if iter < 2 then 
                                                       iter <- iter + 1
                                                   else
                                                       Tick()
                                                       image.Source <- MakeBitmapSource()
                                                       Thread.Sleep(200))
        this.Content <- image
        this.Title <- "Game of Life" 
        this.SizeToContent <- SizeToContent.WidthAndHeight  

[<STAThread()>] 
do  
    let app =  new Application() 
    app.Run(new MyWPFWindow()) |> ignore

Advertisements

One Response to ““Game of Life” Challenge”

  1. Pavel Tishkin said

    Hello BrainAs response to your challenge. My version of "Life". Not WPF, but XNA version in 3Dhttp://code.google.com/p/fsharp/FS_LifeXNA.zipYou need install XNA 3.0 CTP to compile and run projectB.R. Pavel

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

 
%d bloggers like this: