hubFS: THE place for F#

. . . are you on The Hub?
Welcome to hubFS: THE place for F# Sign in | Join | Help
in Search

Polyphonic

A walk with a newbie

When I was at Santa Clara University there was this class on formal systems that all students were required to take. It covered predicate calculus, lambda calculus, and computational theory. For most of us, this class was hell. American students are notorious for scoring theory and this class was no different. There was much wailing and gnashing of teeth, followed by a near revolt. All the students really wanted was vocational training in the latest language de jour.

Nonetheless, I took the challenge and was frustrated that I had no tools to try out all these new concepts other Tarski's world. There was nothing for lambda calculus. But then a search on the internet turned up this thing called ML. I was hooked, but there was no way to do commercial work with it. No one I worked with even knew what ML was let alone lambda calculus. All I got from them were blank stares.

So 6 years passed and then along came F#. Being based on the .Net platform, I thought; why not give it a go again? Even if none of my peers share my interest, at least they can still use what I create.

To get started, I took the Concurrent Life sample application from the distribution (thank you to whoever wrote it) and put a C# face on it just to make sure I could build a GUI in C#. Then I set out to build a Prisoner's Dilemma application modeled on the concurrent life just to reinforce what I learned.

I soon realized that I was struggling with the new language even after having programmed ML/Haskell many years past. The thousands of lines of C# code I had written had set down grooves in my mind like an old LP. There must be millions of groove heads out there, so I thought I should help my fellow programmers get their arms around this thing called F#.

The application has four layers:

  1. C# User Interface
  2. F# Controller Class
  3. F# Engine Class
  4. F# Core Logic

The core logic contains the functions for managing the grid and cells. The engine is the state machine that runs in a background thread doing the work. The controller class runs in the user interface thread and gives the user interface a class based interface and uses friendly types to make the C# code simpler.

Core Logic

The core logic resides in a module with an interface. Don't confuse interfaces for modules with interfaces in C#. In F# they are called signatures and they reside in a separate file, in our case prisoners.fsi. The implementation of the interface resides in the file prisoners.fs.

Note: If you are using Visual Studio to write F# using the Add In, you must make sure that the .fsi file is closer to the top of the project than the fs file so that it is compiled first. If you get them out of order, simply edit the .fsharpp file with Notepad and move the .fsi file above the .fs file.

The Prisoners Interface

The module begins with a module statement followed by type statements and value statements. Let's walk through some of the statements.

module Prisoners

type point = int * int
type points = point list
type strategy = Cooperate | Defect
type behavior =
{
i: strategy;
c: strategy;
d: strategy
}
type behaviors = behavior list

type payoff = (strategy * strategy) -> (int * int)

type cellData =
{
coordinate : point;
value : behavior
}

type grid

The first type is a point:

type point = int * int

This defines a tuple of two integers. The * is a separator between types. In many texts a tuple is shown as <3, 4>. When you use a tuple, you always have two numbers, you access them individually as will be discussed below, but they are inseparable, because they are a type.

type points = point list

The type points is a list of points; type is a keyword, and list is a keyword; points is the name of the type, and point is the type defined above.

F# does not require that you specify types, because the language supports type inference. This means it can figure out the type at compile time.

Note: If you are a C# programmer, this is going to drive you nuts. However, you will eventually realize that this allows you to write code that can operate on more than one type. The closest thing I can think of to this in C# is generics. But with generics even though you can write a class that operates on different types safely, you have to give the type when you write the code. With F#, the compiler figures it out.

type strategy = Cooperate | Defect

The strategy type is an enumeration. Like enumerations in C#, a value can only have the value of one of the choices, in this case Cooperate or Defect.

type behavior =
{
i: strategy;
c: strategy;
d: strategy
}
type behaviors = behavior list

The type behavior is a record. Records allow you to obtain values in the record by a name. For example, this record has three items (i, c, d) of type strategy. I is for the initial strategy, c is the strategy used if your opponent cooperates on the previous move of the game, and d is the strategy used if your opponent defects on the previous move of the game.

type payoff = (strategy * strategy) -> (int * int)

The payoff type is a signature of a function. You can think of this as something like a delegate in C#.

Payoff takes two arguments of type strategy, and it returns two integers. More specifically, it takes the two arguments as a tuple and returns two integers as a tuple. You can make functions without the bracket on the incoming parameters and then there are ways to write code such that you only apply the function to one argument, then pass that result around and then apply the final argument later. This is quite power, but you might find it unnerving at first.

type cellData =
{
coordinate : point;
value : behavior
}

type grid

The final types are the definition of cellData which is a record with a point and behavior, and the type grid. In the implementation, grid will have parts. However, they are not defined in the signature to prevent client code from mucking with the insides of the grid. The signature defines what the world outside the module can see.

The Prisoners Implementation

Here is the whole of the implementation. I will cover each section one by one below, so if you feel lost, you can skip to the explanation afterwards and then come back to the whole code when you have a better feel for what is going one.

module Prisoners

open List

type point = int * int
type points = point list
type strategy = Cooperate | Defect
type behavior =
    { i: strategy;
      c: strategy;
      d: strategy }
type behaviors = behavior list

type payoff = (strategy * strategy) -> (int * int)

type cellData =
{
  coordinate : point;
  value : behavior
}

let alwaysDefect = { i=Defect; c=Defect; d=Defect }
let suspiciousDoormat = { i=Defect; c=Defect; d=Cooperate }
let suspiciousTitForTat = { i=Defect; c=Cooperate; d=Defect }
let suspiciousQuaker = { i=Defect; c=Cooperate; d=Cooperate }
let deceptiveDefector = { i=Cooperate; c=Defect; d=Defect }
let gullibleDoormat = { i=Cooperate; c=Defect; d=Cooperate }
let titForTat = { i=Cooperate; c=Cooperate; d=Defect }
let quaker = { i=Cooperate; c=Cooperate; d=Cooperate }

let behaviors = [| alwaysDefect; suspiciousDoormat; suspiciousTitForTat;
  suspiciousQuaker; deceptiveDefector; gullibleDoormat; titForTat; quaker |]
 
let hobbes (a,b) =
  match (a,b) with
      (Cooperate, Cooperate) -> (3, 3)
   |  (Cooperate, Defect)    -> (0, 5)
   |  (Defect,    Cooperate) -> (5, 0)
   |  (Defect,    Defect)    -> (1, 1)
  
let chicken (a,b) =
  match (a,b) with
      (Cooperate, Cooperate) -> (3, 3)
   |  (Cooperate, Defect)    -> (1, 5)
   |  (Defect,    Cooperate) -> (5, 1)
   |  (Defect,    Defect)    -> (0, 0)
  
let stag (a,b) =
  match (a,b) with
      (Cooperate, Cooperate) -> (5, 5)
   |  (Cooperate, Defect)    -> (0, 3)
   |  (Defect,    Cooperate) -> (3, 0)
   |  (Defect,    Defect)    -> (1, 1)

let prev size i = if i = 0 then size-1 else i-1
let next size i = if i = size-1 then 0 else i+1
let neighbours size (i,j) =
  [ (prev size i,prev size j); (prev size i,j); (prev size i,next size j);
   
(i,          prev size
j);                 
(i,          next size j);
    (next size i,prev size j); (next size i,j); (next size i,next size j)]

type cell =
    { x: int;
      y: int;
      mutable neighbours:  cell array;
      mutable score: int; 
      mutable plan : behavior;
      mutable first : bool;
      mutable last : strategy;
      mutable temp : strategy }

type grid =
    { size: int;
      mutable pay: payoff;
      cells: cell array array;
      mutable changed: cellData list }
     
let changePay grid pay =
  grid.pay <- pay;
  grid

let newEmptyGame = { size=0; cells = [| |]; changed = []; pay=hobbes}

let getCell grid (x,y) = grid.cells.(x).(y)

let makeGrid size payoff =
  let dummyCell = { x=0; y=0;
neighbours=[| |]; score=0; plan=alwaysDefect; first=true;
last=Cooperate; temp=Cooperate } in
  let grid =
    { size = size;
      cells = Array.create_matrix size size dummyCell; changed=[]; pay=payoff } in
  for i = 0 to size - 1 do
    for j = 0 to size - 1 do
      grid.cells.(i).(j) <- { x=i; y=j; neighbours=[| |];
        score=0; plan=alwaysDefect; first=true; last=Cooperate; temp=Cooperate }
    done;
  done;
  for i = 0 to size - 1 do
    for j = 0 to size - 1 do
      grid.cells.(i).(j).neighbours <- Array.of_list (map (getCell grid) (neighbours size (i,j)))
    done;
  done;
  grid
 
let resetGrid grid =
  grid.changed <- [];
  for i = 0 to grid.size - 1 do
    for j = 0 to grid.size - 1 do
      (getCell grid (i,j)).score <- 0
    done;
  done
 
let augment grid cellCoordinates behaviors =
  resetGrid grid;
  begin
    let cellBehaviors = List.combine cellCoordinates behaviors in
    iter (fun (point, behavior) ->
      let c = getCell grid point in
        c.plan <- behavior;
        grid.changed <- { coordinate=(c.x, c.y); value=c.plan }::grid.changed
      ) cellBehaviors
  end;
  grid.changed
 
let randomGenerator size =
  let gen = new System.Random() in
  let acc = ref [] in
  for i = 0 to size-1 do
    for j = 0 to size-1 do
      acc:=behaviors.(System.Convert.ToInt32(gen.NextDouble() * 7.0))::(!acc);
    done;
  done;
  Array.of_list !acc
 
let newRandomGame size payoff =
  let grid = makeGrid size payoff in
  let plans = randomGenerator size in
  let index = ref 0 in
  for i = 0 to grid.size - 1 do
    for j = 0 to grid.size - 1 do
      let c = (getCell grid (i,j)) in
      c.plan <- plans.(!index);
      index := !index + 1;
      grid.changed <- { coordinate=(i, j); value=c.plan }::grid.changed
    done;
  done;
  grid, grid.changed

let cellPayoff (grid,a,b) =
  if a.first then
    begin
      a.first <- false;
      a.temp <- a.plan.i;
      grid.pay (a.plan.i, b.plan.i)
    end
  else
    let aBehavior = if b.last = Cooperate then a.plan.c else a.plan.d in
    let bBehavior = if a.last = Cooperate then b.plan.c else b.plan.d in
    begin
      a.temp <- aBehavior;
      grid.pay (aBehavior, bBehavior)
    end
   
let step grid =
  resetGrid grid;
  for i = 0 to grid.size - 1 do
    for j = 0 to grid.size - 1 do
      let me = (getCell grid (i,j)) in
      let nbs = me.neighbours in
      for k = 0 to 7 do
        let nb = nbs.(k)in
          me.score <- me.score + fst (cellPayoff (grid, me, nb))
      done;
    done;
  done;
  for i = 0 to grid.size - 1 do
    for j = 0 to grid.size - 1 do
      let me = (getCell grid (i,j)) in
        me.last <- me.temp
    done;
  done;
  for i = 0 to grid.size - 1 do
    for j = 0 to grid.size - 1 do
      let me = (getCell grid (i,j)) in
      let nbs = me.neighbours in
      let max = ref (-1, -1) in
      let score = ref me.score in
      for k = 0 to 7 do
        let nb = nbs.(k)in
          if nb.score > !score then
            begin
              score := nb.score;
              max := (nb.x, nb.y)
            end
      done;
      if fst !max >= 0 then
        let c = (getCell grid !max) in
          me.plan <- c.plan;
          grid.changed <- { coordinate = (i,j); value = me.plan }::grid.changed
    done;
  done;
  grid.changed

Let's disect the code bit by bit:

module Prisoners

open List

type point = int * int
type points = point list
type strategy = Cooperate | Defect
type behavior =
    { i: strategy;
      c: strategy;
      d: strategy }
type behaviors = behavior list

type payoff = (strategy * strategy) -> (int * int)

type cellData =
{
  coordinate : point;
  value : behavior
}

We open with a module statement that matches the interface file. Then we have an open statement so we can use the List module without having to prefix each use. This is similar to a using statement in C#.

When then define most of the types in the interface. Grid will be defined later.

let alwaysDefect = { i=Defect; c=Defect; d=Defect }
let suspiciousDoormat = { i=Defect; c=Defect; d=Cooperate }
let suspiciousTitForTat = { i=Defect; c=Cooperate; d=Defect }
let suspiciousQuaker = { i=Defect; c=Cooperate; d=Cooperate }
let deceptiveDefector = { i=Cooperate; c=Defect; d=Defect }
let gullibleDoormat = { i=Cooperate; c=Defect; d=Cooperate }
let titForTat = { i=Cooperate; c=Cooperate; d=Defect }
let quaker = { i=Cooperate; c=Cooperate; d=Cooperate }

let behaviors = [| alwaysDefect; suspiciousDoormat; suspiciousTitForTat;
  suspiciousQuaker; deceptiveDefector; gullibleDoormat; titForTat; quaker |]

We have defined a record for each strategy. We begin with a let to tell the compiler we are defining a record, give it a name, and then set its value. All of these records are of type behavior. Each value is prefixed with the i/c/d from the type definition. The compiler automatically figures out that these definitions are the behavior type.

We then create and array called behaviors. Arrays are specified using [| |]. When you see [ ] in the code, these are lists. Arrays are mutable, meaning you can modify their value. Lists are immutable. You can make new lists from existing lists, but you can not modify a list. Arrays are used for the imperative style of programming, and lists are used for functional style of programming.

let hobbes (a,b) =
  match (a,b) with
      (Cooperate, Cooperate) -> (3, 3)
   |  (Cooperate, Defect)    -> (0, 5)
   |  (Defect,    Cooperate) -> (5, 0)
   |  (Defect,    Defect)    -> (1, 1)
  
let chicken (a,b) =
  match (a,b) with
      (Cooperate, Cooperate) -> (3, 3)
   |  (Cooperate, Defect)    -> (1, 5)
   |  (Defect,    Cooperate) -> (5, 1)
   |  (Defect,    Defect)    -> (0, 0)
  
let stag (a,b) =
  match (a,b) with
      (Cooperate, Cooperate) -> (5, 5)
   |  (Cooperate, Defect)    -> (0, 3)
   |  (Defect,    Cooperate) -> (3, 0)
   |  (Defect,    Defect)    -> (1, 1)

There are three payoffs functions. These functions pass in two strategies and return a tuple with the score. The score tuple has two values; the left value is the score for the left strategy, and the right for the right. For example, the Hobbes strategy says that if both behaviors are cooperate, then each gets 3 points. If they both defect, they each get 1.
The method uses pattern matching. Pattern matching is a bit like a switch in C#, except there are more sophisticated matching techniques not covered here. When the incoming values match the parenthesized values, the values to the right of the -> are the result value.

let prev size i = if i = 0 then size-1 else i-1
let next size i = if i = size-1 then 0 else i+1
let neighbours size (i,j) =
  [ (prev size i,prev size j); (prev size i,j); (prev size i,next size j);
   
(i,          prev size
j);                 
(i,          next size j);
    (next size i,prev size j); (next size i,j); (next size i,next size j)]

type cell =
    { x: int;
      y: int;
      mutable neighbours:  cell array;
      mutable score: int; 
      mutable plan : behavior;
      mutable first : bool;
      mutable last : strategy;
      mutable temp : strategy }

type grid =
    { size: int;
      mutable pay: payoff;
      cells: cell array array;
      mutable changed: cellData list }

We now have some definitions that set up the core types. The cell type holds the values for each square on the playing field. The cell has an x/y coordinate. These are values and are immutable. There are mutable values for neighbors (the cells on all 8 squares around the cell), the score (for one round of play), the current behavior (which will change if the neighbours get more points), a Boolean that is true if this is the initial play, the last strategy used, and a temporary strategy used for the algorithm that updates cells.
The grid type has the size of the playing field, the payoff function that was defined above, the cells, and a list of changed cells. The payoff contains a function. This is like using a reference to a delegate in C#. It allows the user to change the payoff by setting a different payoff scheme. The arrays is immutable, but remember, the contents of an array are mutable. The change data is a list which is not mutable, but the list as a whole is, which means the list can be replaced with a new list.
Let’s look at how pay would be changed:

let changePay grid pay =
  grid.pay <- pay;
  grid

Change pay takes a grid and payoff arguments and then sets the grids pay field to the payoff, then gives the same grid as a result. The syntax for setting a mutable value is:
field <- value
The <- operator means to assign the memory that the field references to the value, just like = does in C#.

let newEmptyGame = { size=0; cells = [| |]; changed = []; pay=hobbes}
This defines a function that creates and empty game and gives a grid as a result. Actually, it constructs a grid of size 0, with and empty array of cells, and an empty list of changes. Notice that the array is initialized with [| |] and the list with [ ]/

let getCell grid (x,y) = grid.cells.(x).(y)

It is quite common to access each cell in the grid, so here is a function that takes a grid and a tuple of an x/y coordinate and results in a cell. We access arrays with dot and parenthesis. Notice we also use dotting to access fields in the grid type.

let makeGrid size payoff =
  let
dummyCell = { x=0; y=0; neighbours=[| |]; score=0; plan=alwaysDefect;
first=true; last=Cooperate; temp=Cooperate } in
  // First make all the cells
  let grid =
    { size = size;
      cells = Array.create_matrix size size dummyCell; changed=[]; pay=payoff } in
  for i = 0 to size - 1 do
    for j = 0 to size - 1 do
      grid.cells.(i).(j) <- { x=i; y=j; neighbours=[| |];
        score=0; plan=alwaysDefect; first=true; last=Cooperate; temp=Cooperate }
    done;
  done;
  for i = 0 to size - 1 do
    for j = 0 to size - 1 do
      grid.cells.(i).(j).neighbours <- Array.of_list (map (getCell grid) (neighbours size (i,j)))
    done;
  done;
  grid

Now we are getting closer to the heart of things. makeGrid takes a size and payoff record and makes a grid. Look at the initial structure:
let ... in
let ... in
  for ... to ... do
  done

We are writing imperative code here, and the nested let/in statements will ensure that the definitions are evaluated in the order they are written. Functional code does not control order of evaluation, because it does not matter. But imperative code (code with side effects), it matters. So you have to pay close attention and make sure that your code is written in a way that controls order of evaluation when it matters.

In this case the first statement makes a dummyCell, and the second one makes a grid using the dummy cell. Because the second let statement is in the first statement, when the second is evaluated, it is assured that the first let is evaluated first.

If you use a definition like:

let ...
and ... in

all the definitions are simultaneous and the order of evaluation is not specified. For pure functional code, this does not matter because there are no side effects of the code because we are dealing with values and there is no state modification, but to do this with imperative code will cause unpredictable results in many cases.

for i = 0 to size - 1 do
    for j = 0 to size - 1 do
      grid.cells.(i).(j).neighbours <- Array.of_list (map (getCell grid) (neighbours size (i,j)))
    done;

Here is something new. We are looping through the cells by an i/j coordinate in the grid and changing the value of neighbours using some unusual operations with Array and List. The neighbours function evaluates to a list. The map function is really List.map. We can use map directly because of the open List statement at the top of the module file. The map function says to apply the first argument to each element in the second argument and then return a new list. The second argument of course is a list. Let's break this down more.

(neighbours size (i,j))

This statement says evaluate neighbours with size and the tuple (i,j), which evaluates to a list.

(getCell grid)

This statement says evaluate getCell with a grid, however the definition of grid also includes a tuple (i,j). Just what is going on here? Well, functions can be partially evaluated. Since the second argument was not given, then the result is a function that requires the tuple (i,j) to complete evaluation. For the C# programmers, I know what you are thinking; yuck. But wait, map applies this function to each element in the list created by neighbours, which evaluates to a list of (i,j) tuples, just what the new function requires to evaluate. Partial evaluation combined with map allowed us to make these functions work together with ease. The final statement:

Array.of_list

turns that list into an array so it can set the cell's neighbors. The map function is doing something functional, and the assignment of the array is doing something imperative. The Array.of_list is bridging the gap between functional and imperative programming.

Let's fast forward a bit because I think you can now figure out some of this.

let cellPayoff (grid,a,b) =
  if a.first then
    begin
      a.first <- false;
      a.temp <- a.plan.i;
      grid.pay (a.plan.i, b.plan.i)
    end
  else
    let aBehavior = if b.last = Cooperate then a.plan.c else a.plan.d in
    let bBehavior = if a.last = Cooperate then b.plan.c else b.plan.d in
    begin
      a.temp <- aBehavior;
      grid.pay (aBehavior, bBehavior)
    end

This function performs the payoff calculation. The conditional determines if this is the first play of the game, and if so uses the i: of the behavior record. The second part of the conditional does the real work. It compares the current strategy of the cell with the past strategy of an opponent and calculates the pay off. Notice this line:

grid.pay (aBehavior, bBehavior)

grid.pay is a function, sort of like a delegate reference in C#, and it is evaluated by passing a tuple of behaviors. The pay field is mutable, so this allows us to change the definition of pay dynamically at run time. You will see later that the user can determine the payoff structure in the GUI and this is the mutable field that is changed.

let step grid =
  resetGrid grid;
  for i = 0 to grid.size - 1 do
    for j = 0 to grid.size - 1 do
      let me = (getCell grid (i,j)) in
      let nbs = me.neighbours in
      for k = 0 to 7 do
        let nb = nbs.(k)in
          me.score <- me.score + fst (cellPayoff (grid, me, nb))
      done;
    done;
  done;
  for i = 0 to grid.size - 1 do
    for j = 0 to grid.size - 1 do
      let me = (getCell grid (i,j)) in
        me.last <- me.temp
    done;
  done;
  for i = 0 to grid.size - 1 do
    for j = 0 to grid.size - 1 do
      let me = (getCell grid (i,j)) in
      let nbs = me.neighbours in
      let max = ref (-1, -1) in
      let score = ref me.score in
      for k = 0 to 7 do
        let nb = nbs.(k)in
          if nb.score > !score then
            begin
              score := nb.score;
              max := (nb.x, nb.y)
            end
      done;
      if fst !max >= 0 then
        let c = (getCell grid !max) in
          me.plan <- c.plan;
          grid.changed <- { coordinate = (i,j); value = me.plan }::grid.changed
    done;
  done;
  grid.changed

We are now down the final function which evaluates one step in the game. This will be called in a loop to make the game progress. The form of the definition is:

let ... in
... ;
... ;
... ;
...

which is implicitly a sequence of statements that are guaranteed to evaluate in the order given. Let's take each item one by one.

resetGrid grid;

This resets the score of each cell by setting it to zero.

for i = 0 to grid.size - 1 do
    for j = 0 to grid.size - 1 do
      let me = (getCell grid (i,j)) in
      let nbs = me.neighbours in
      for k = 0 to 7 do
        let nb = nbs.(k)in
          me.score <- me.score + fst (cellPayoff (grid, me, nb))
      done;
    done;

This loop gets each cell one by one and then gets the neighbour and calculates the payoff. The cellPayoff function takes a grid, the current cell, and the neighbour cell, and returns a tuple. The fst function gets the first integer in the tuple. You can get the second tuple with snd.

We then set the score in the current cell using the <- operate which assigns a mutable value.

for i = 0 to grid.size - 1 do
    for j = 0 to grid.size - 1 do
      let me = (getCell grid (i,j)) in
        me.last <- me.temp
    done;
  done;

Now we loop through each cell and set the temp value to last. Temp was set during payoff calculation. The reason for the two step process is the payoff calculation knows what behavior was used (i, c, or d) and saves it. But, we can't use it until all scoring is done, because last is used when comparing our behavior to our neighbours past behavior.

for j = 0 to grid.size - 1 do
      let me = (getCell grid (i,j)) in
      let nbs = me.neighbours in
      let max = ref (-1, -1) in
      let score = ref me.score in
      for k = 0 to 7 do
        let nb = nbs.(k)in
          if nb.score > !score then
            begin
              score := nb.score;
              max := (nb.x, nb.y)
            end
      done;
      if fst !max >= 0 then
        let c = (getCell grid !max) in
          me.plan <- c.plan;
          grid.changed <- { coordinate = (i,j); value = me.plan }::grid.changed
    done;
  done;

The last loop looks for the largest neighbour score and switches to that strategy. A couple of things to note. We are using a reference value.

let score = ref me.score in

Reference values are mutable. You refer to their value by placing a ! in front of its name and you assign with :=.

score := nb.score;
...
if fst !max >= 0 then

The last piece is:

grid.changed

which returns the list of points and behaviors that changed. These will eventually go to the GUI for screen update.

The Engine Implementation

The engine is a statemachine running in a background thread. It recieves signals from the client and notifies the client about changes in its state.

module Engine

module Array = Microsoft.FSharp.Compatibility.CompatArray

open System.Threading

type IEngineControl =
  interface
    abstract Start: unit -> unit;
    abstract NewStrategiesReady: AutoResetEvent;
    abstract NewPayoffReady: AutoResetEvent;
    abstract RunSignal: AutoResetEvent;
    abstract ExitSignal: AutoResetEvent;
    abstract StopSignal: AutoResetEvent;
    abstract StepSignal: AutoResetEvent;
    abstract ResetSignal: AutoResetEvent
  end
 
back from the engine.
type IEngineCallbacks =
  interface
    abstract NotifyUpdates: Prisoners.cellData list -> unit
    abstract NotifyFinishedEarly: unit -> unit
    abstract CollectStrategies: unit -> Prisoners.points * Prisoners.behaviors
    abstract CollectPayoff: unit -> Prisoners.payoff
  end
 
type 'a state = ('a -> unit)
   
let PeekForOne (xs : (AutoResetEvent * 'a state) list)
               (otherwise : 'a state)
               (s : 'a) =
  let waithandles = Array.of_list (List.map (fun x -> (fst x :> WaitHandle)) xs) in
  let actions = Array.of_list (List.map snd xs) in
  let i = WaitHandle.WaitAny(waithandles,0,false) in
  if i = WaitHandle.WaitTimeout then
    otherwise s
  else
    actions.(i) s
 
let WaitForOne (xs : (AutoResetEvent * 'a state) list)
               (s : 'a) =
  let waithandles = Array.of_list (List.map (fun x -> (fst x :> WaitHandle)) xs) in
  let actions = Array.of_list (List.map snd xs) in
  let i = WaitHandle.WaitAny(waithandles) in
  actions.(i) s

let mkEngine (size:int) (callbacks : #IEngineCallbacks) =
  /// These are the events used to control the engine
  let exitSignal = new AutoResetEvent(false) in
  let runSignal = new AutoResetEvent(false) in
  let stopSignal = new AutoResetEvent(false) in
  let stepSignal = new AutoResetEvent(false) in
  let resetSignal = new AutoResetEvent(false) in
  let newStrategiesReadySignal = new AutoResetEvent(false) in
  let newPayoffReadySignal = new AutoResetEvent(false) in
  let pay = ref Prisoners.hobbes in

  let OneStep(s) =
    let data = Prisoners.step(s) in
    callbacks.NotifyUpdates(data);
    s, (data <> []) in

  let ResetGame(s) =
    let s,changed = Prisoners.newRandomGame size !pay in
    callbacks.NotifyUpdates(changed);
    s in

  let CollectStrategies(s) =
    let userPoints, userBehavior = callbacks.CollectStrategies() in
    let data = Prisoners.augment s userPoints userBehavior in
    callbacks.NotifyUpdates(data);
    s in

  let CollectPayoff(s) =
    let p = callbacks.CollectPayoff() in
    let s = Prisoners.changePay s p in
    pay := p;
    s in

  let rec StartRun(s) =
      let s = ResetGame(s) in
      Running(s)
     
  and StartPause(s) = 
      let s = ResetGame(s) in
      Paused(s)
     
  and Running(s) =
      PeekForOne
        [ (stopSignal, Paused);
          (stepSignal, Running);
          (runSignal, Running);
  Â