I’m a bit of a Pacman fan so when I came across the Pacman Kata in the Kata Catalogue I was hooked:
Pacman finds himself in a grid filled with monsters. Will he be able to eat all the dots on the board before the monsters eat him?
Back in early 2012 we ran a Pacman Kata at the F#unctional Londoners meetup where you start with a maze and sprites and your task is to write a simple AI. After the event I extended the sample to run on Windows 8, Windows Phone as well as Silverlight and WPF, and somehow got a mention from Channel 9.
A few months back Neil Danson ported the sample to iOS, and over the last few weeks I’ve been playing with the HTML 5 canvas and have created a FunScript version too.
When Rich Minerich suggested a version for the Progressive F# Tutorials in New York I jumped at the chance, and teamed up with Mathias Brandewinder to create a fun session centred around Pacman:
Code samples from the session: http://trelford.com/Pacman.Kata.zip
Test-driven Pacman
The session began with some good old unit tests:
[<Test>]
let ``pacman should wrap from left to right beyond left extent`` () =
let p = wrap { x = leftExtent-1; y =0 }
Assert.AreEqual(rightExtent, p.x)
Followed by automated acceptance tests using TickSpec:
Scenario: Pacman eats dots
When pacman eats a pac-dot
Then he scores 10
Scenario: Pacman eats power pellets
When pacman eats a power pellet
Then he scores 50
In the sample the scenarios are automated using attribute based step definitions:
type Property = PacDot | PowerPellet
let [<When>] ``pacman eats a pac-dot`` () =
properties <- PacDot::properties
let [<When>] ``pacman eats a power pellet`` () =
properties <- PowerPellet::properties
let [<Then>] ``he scores (.*)`` (points:int) =
let scored =
properties |> List.sumBy (function
| PacDot -> 10
| PowerPellet -> 50
)
Assert.AreEqual(points, scored)
Controls
Next up we looked at implementing keyboard controls, first with Mario and then Pacman, both in FunScript:
The controls can be composed from simple functions:
let left (dx,_) p = if dx = –1 then {p with x=p.x+dx} else p
let right (dx,_) p = if dx = 1 then {p with x=p.x+dx} else p
let up (_,dy) p = if dy = -1 then {p with y=p.y+dy} else p
let down (_,dy) p = if dy = 1 then {p with y=p.y+dy} else p
let step dir pacman = pacman |> left dir |> right dir |> up dir |> down dir
Ghosts
The grand finale was writing your own AI for the Ghosts (or Pacman) using an API designed by Mathias specifically for the session:
// Decision is based on current move, line of sight and possible moves
member this.Decide (current: Move) (lineOfSight: Sight) (choices: Move Set) =
n <- n + 1
let inSight (creatures:Creature list) =
creatures
|> List.exists (function PacMan 0 -> true | _ -> false)
let standard () =
let restricted =
choices
|> Set.filter (fun c -> not (c = backwards current))
if (restricted |> Set.count > 0)
then randomMove restricted
else randomMove choices
let home () =
let s = lineOfSight
let xs = [Up,s.Up; Down,s.Down; Left,s.Left; Right,s.Right]
let x =
xs
|> List.tryFind (fun (d,xs) ->
xs |> List.exists (snd >> inSight)
)
match x with
| Some (d,_) -> d
| None -> standard ()
if (n / 500) % 2 = 0 then home ()
else standard ()
The simple AI above switches the ghosts between homing in on Pacman and random moves every 500 frame updates (roughly every 8 seconds).
At the end we played off Mathias’s payoff function based Pacman AI against my simple Ghosts on the big screen which ended in a tie :)
In the more competitive Pacman session ran by Paulmichael Blasucci and Rich Minerich the final was between Jack Fox and Tyler Smith.
More fun
And there’s more fun to come, Rich Minerich will be running a Pacman competition at the Progressive F# Tutorials in London this year:
and there’s a Pacman Kata scheduled for November at the F#unctional Londoners meetup.