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:
- C# User Interface
- F# Controller Class
- F# Engine Class
- 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);
  Â