Tic Tac Toe with ReasonML

ReasonML provides an alternative syntax for OCaml, which Imandra can read. Let's walk through an example ReasonML model of Tic Tac Toe and use Imandra to understand and verify some properties of it.

This model is wired into some ReasonReact components in order to produce this final, playable version. You can see the model code and components as a ReasonReact project in this github repo.

Setting up the model

Let's start by adding some definitions for the player, state of a square on the game grid, and the grid itself.

In [1]:
type player =
  | X
  | O;

/* the state of a particular square - None = (no move yet), Some(X/O) = either X or O has moved there */
type square_state = option(player);

/* each letter represents a square on the grid, starting in the top left and going along each row */
type grid = {
  a: square_state,
  b: square_state,
  c: square_state,
  d: square_state,
  e: square_state,
  f: square_state,
  g: square_state,
  h: square_state,
  i: square_state,
};
Out[1]:
type player = X | O
type square_state = player option
type grid = {
  a : square_state;
  b : square_state;
  c : square_state;
  d : square_state;
  e : square_state;
  f : square_state;
  g : square_state;
  h : square_state;
  i : square_state;
}

(NB: we're inputting in Reason syntax, but Imandra currently outputs OCaml syntax - this is something we'll have resolved very soon!).

Grid document printer

Here's an example grid:

In [2]:
let a_grid = {
  a: Some(O),
  b: Some(X),
  c: Some(O),
  d: None,
  e: None,
  f: Some(X),
  g: None,
  h: None,
  i: None,
};
Out[2]:
val a_grid : grid =
  {a = Some O; b = Some X; c = Some O; d = None; e = None; f = Some X;
   g = None; h = None; i = None}

As you can tell, it's not the easiest thing to interpret from Imandra's output given the way we've described the grid. Let's setup a custom document mapping for the grid type. Imandra will render the Document data structures as richer output to display our types in a clearer way:

In [3]:
[@program] let doc_of_square_state = (s : square_state) =>
  Document.s(switch (s) {
  | Some(X) => "❌" /* emojis! */
  | Some(O) => "⭕"
  | None => "·"
});

[@program] let doc_of_grid = (g: grid) => {
  let f = doc_of_square_state;
  Document.tbl([[f(g.a), f(g.b), f(g.c)], [f(g.d), f(g.e), f(g.f)], [f(g.g), f(g.h), f(g.i)]])
};
[@install_doc doc_of_grid];
Out[3]:
val doc_of_square_state : square_state -> Document.t = <fun>
val doc_of_grid : grid -> Document.t = <fun>
In [4]:
a_grid
Out[4]:
- : grid = <document>
··
···

Much better! The [@program] annotation tells Imandra that we're only going to be using these functions for non-analytical (and potentially side-effecting) operations, and that it doesn't have to worry about analysing them. See Logic and program modes for more information.

More definitions

Let's continue adding some more definitions:

In [5]:
/* maps to the grid entries */
type move =
  | A
  | B
  | C
  | D
  | E
  | F
  | G
  | H
  | I;

/* the full game state - the current grid, and who went last */
type game_state = {
  grid,
  last_player: option(player),
};

type game_status =
  | Won(player)
  | InProgress
  | InvalidMove(move)
  | Tied;

let initial_game = {
  grid: {
    a: None,
    b: None,
    c: None,
    d: None,
    e: None,
    f: None,
    g: None,
    h: None,
    i: None,
  },
  last_player: None,
};

let initial_player = X;

/* convenience function from our move variant to the value in that grid position */
let value = ({grid: {a, b, c, d, e, f, g, h, i}, _}) =>
  fun
  | A => a
  | B => b
  | C => c
  | D => d
  | E => e
  | F => f
  | G => g
  | H => h
  | I => i;
Out[5]:
type move = A | B | C | D | E | F | G | H | I
type game_state = { grid : grid; last_player : player option; }
type game_status = Won of player | InProgress | InvalidMove of move | Tied
val initial_game : game_state = {grid = <document>; last_player = None}
val initial_player : player = X
val value : game_state -> move -> square_state = <fun>
···
···
···

Game state document printer

Let's also add another document for printing game_states, showing both the grid and who went last:

In [6]:
[@program] let doc_of_game_state = (gs: game_state) => {
  Document.tbl([[doc_of_grid(gs.grid), Document.s("Last:"), doc_of_square_state(gs.last_player)]])
};

[@install_doc doc_of_game_state];

initial_game;
Out[6]:
val doc_of_game_state : game_state -> Document.t = <fun>
- : game_state = <document>
···
···
···
Last:·

Some logic and our first instance

Now for our first bit of proper logic - whether or not a game is won for a particular player:

In [7]:
let is_winning = ({grid: {a, b, c, d, e, f, g, h, i}, _}, player) => {
  let winning_state = (Some(player), Some(player), Some(player));
  (a, b, c) == winning_state
  || (d, e, f) == winning_state
  || (g, h, i) == winning_state
  || (a, d, g) == winning_state
  || (b, e, h) == winning_state
  || (c, f, i) == winning_state
  || (a, e, i) == winning_state
  || (c, e, g) == winning_state;
};
Out[7]:
val is_winning : game_state -> player -> bool = <fun>

Let's use Imandra to get a feel for this function - let's ask for an instance of a game where the game is being won by the player X. We use Imandra's instance mechanism, and ask for a game g where the predicate is_winning(g, X) is true:

In [8]:
instance((g : game_state) => is_winning(g, X));
Out[8]:
- : game_state -> bool = <fun>
module CX : sig val g : game_state end
Instance (after 0 steps, 0.021s):
let g : game_state =
  let (_x_0 : player option) = Some X in
  {grid =
   {a = _x_0; b = _x_0; c = _x_0; d = None; e = _x_0; f = _x_0; g = None;
    h = _x_0; i = _x_0};
   last_player = None}
Instance
proof attempt
ground_instances:0
definitions:0
inductions:0
search_time:
0.021s
details:
Expand
smt_stats:
num checks:1
rlimit count:2507
mk clause:39
datatype occurs check:1
mk bool var:80
datatype splits:19
decisions:30
propagations:26
eliminated applications:1
conflicts:6
datatype accessor ax:5
minimized lits:4
datatype constructor ax:30
num allocs:7403454
final checks:1
added eqs:142
time:0.010000
memory:17.090000
max memory:17.460000
Expand
  • start[0.021s]
      let (_x_0 : grid) = ( :var_0: ).grid in
      let (_x_1 : player option) = _x_0.a in
      let (_x_2 : player option) = _x_0.b in
      let (_x_3 : player option) = _x_0.c in
      let (_x_4 : player option) = Some X in
      let (_x_5 : (player option * player option * player option))
          = (_x_4, _x_4, _x_4)
      in
      let (_x_6 : player option) = _x_0.d in
      let (_x_7 : player option) = _x_0.e in
      let (_x_8 : player option) = _x_0.f in
      let (_x_9 : player option) = _x_0.g in
      let (_x_10 : player option) = _x_0.h in
      let (_x_11 : player option) = _x_0.i in
      ((_x_1, _x_2, _x_3) = _x_5)
      || (((_x_6, _x_7, _x_8) = _x_5)
          || (((_x_9, _x_10, _x_11) = _x_5)
              || (((_x_1, _x_6, _x_9) = _x_5)
                  || (((_x_2, _x_7, _x_10) = _x_5)
                      || (((_x_3, _x_8, _x_11) = _x_5)
                          || (((_x_1, _x_7, _x_11) = _x_5)
                              || ((_x_3, _x_7, _x_9) = _x_5)))))))
  • simplify

    into:
    let (_x_0 : grid) = ( :var_0: ).grid in
    let (_x_1 : player option) = Some X in
    let (_x_2 : bool) = _x_0.a = _x_1 in
    let (_x_3 : bool) = _x_0.b = _x_1 in
    let (_x_4 : bool) = _x_0.c = _x_1 in
    let (_x_5 : bool) = _x_0.d = _x_1 in
    let (_x_6 : bool) = _x_0.e = _x_1 in
    let (_x_7 : bool) = _x_0.f = _x_1 in
    let (_x_8 : bool) = _x_0.g = _x_1 in
    let (_x_9 : bool) = _x_0.h = _x_1 in
    let (_x_10 : bool) = _x_0.i = _x_1 in
    (_x_2 && _x_3 && _x_4) || (_x_5 && _x_6 && _x_7) || (_x_8 && _x_9 && _x_10)
    || (_x_2 && _x_5 && _x_8) || (_x_3 && _x_6 && _x_9)
    || (_x_4 && _x_7 && _x_10) || (_x_2 && _x_6 && _x_10)
    || (_x_4 && _x_6 && _x_8)
    expansions:
    []
    rewrite_steps:
      forward_chaining:
      • Sat (Some let g : game_state = let (_x_0 : player option) = Some X in {grid = {a = _x_0; b = _x_0; c = _x_0; d = None; e = _x_0; f = _x_0; g = None; h = _x_0; i = _x_0}; last_player = None} )

      Rather than executing our function with random inputs as you might imagine, Imandra has examined our definitions mathematically, and generated an instance that matches what we asked for based on the code itself, without executing it directly. This means we don't have to write generators as we would if we were using a library like QuickCheck for example. We can also use Imandra's other tools, such as state space decomposition, to identify edge cases we may not have thought about. See the other documentation pages/notebooks for more detail.

      In this case we explicitly annotated the type of g as game_state, but if this can be inferred using the usual ReasonML/OCaml inference rules, it can be omitted.

      instance has also reflected the value it found back into our Imandra session, so we can take a look at it:

      In [9]:
      CX.g
      
      Out[9]:
      - : game_state = <document>
      
      ·
      ·
      Last:·

      and compute with it:

      In [10]:
      is_winning(CX.g, X)
      
      Out[10]:
      - : bool = true
      

      We can see here that X is definitely 'winning', although the game doesn't look particularly valid. O hasn't made any moves, and the game state hasn't got a last_player set. That's fine for now though, we'll build up additional layers in our logic to handle these other cases. It can be very useful to see a concrete example of our function in action!

      Valid grids

      Let's continue:

      In [11]:
      let other_player =
        fun
        | X => O
        | O => X;
      
      /* count up the number of moves for each player across the whole grid - sounds useful! */
      let move_counts = ({a, b, c, d, e, f, g, h, i}) =>
        List.fold_right(
          (el, (x, o)) =>
            switch (el) {
            | None => (x, o)
            | Some(X) => (x + 1, o)
            | Some(O) => (x, o + 1)
            },
          ~base=(0, 0),
          [a, b, c, d, e, f, g, h, i],
        );
      
      /* whether a grid is 'valid' - the difference between move totals should never be more than 1 */
      let is_valid_grid = (grid, last_player) => {
        let (x, o) = move_counts(grid);
      
        (x + o == 0 && last_player == None) ||
        (x - o == 1 && last_player == Some(X)) ||
        (x == o && last_player == Some(O));
      };
      
      Out[11]:
      val other_player : player -> player = <fun>
      val move_counts : grid -> Z.t * Z.t = <fun>
      val is_valid_grid : grid -> player option -> bool = <fun>
      

      Let's grab a game where the grid is valid:

      In [12]:
      instance((game) => is_valid_grid(game.grid, game.last_player));
      
      Out[12]:
      - : game_state -> bool = <fun>
      module CX : sig val game : game_state end
      
      Instance (after 10 steps, 0.035s):
      let game : game_state =
        let (_x_0 : player option) = Some O in
        {grid =
         {a = Some X; b = None; c = None; d = None; e = None; f = _x_0; g = None;
          h = None; i = None};
         last_player = _x_0}
      
      Instance
      proof attempt
      ground_instances:10
      definitions:0
      inductions:0
      search_time:
      0.035s
      details:
      Expand
      smt_stats:
      arith offset eqs:431
      arith assert lower:414
      arith patches_succ:4
      datatype occurs check:796
      datatype splits:97
      arith bound prop:112
      propagations:1345
      arith patches:4
      interface eqs:35
      conflicts:106
      arith fixed eqs:494
      datatype constructor ax:1327
      num allocs:15085347
      final checks:47
      added eqs:7180
      del clause:375
      num checks:21
      arith tableau max rows:84
      arith tableau max columns:107
      arith pivots:302
      rlimit count:107995
      mk clause:734
      mk bool var:2665
      arith gcd tests:40
      arith assert upper:506
      decisions:1142
      arith row summations:2991
      arith assume eqs:35
      datatype accessor ax:71
      minimized lits:76
      arith conflicts:26
      arith num rows:84
      arith assert diseq:90
      arith eq adapter:404
      time:0.006000
      memory:18.970000
      max memory:18.970000
      Expand
      • start[0.035s]
          let (_x_0 : (int * int))
              = List.fold_right anon_fun.move_counts.1 (0, 0) (….a :: (… :: …))
          in
          let (_x_1 : int) = _x_0.0 in
          let (_x_2 : int) = _x_0.1 in
          let (_x_3 : player option) = ( :var_0: ).last_player in
          ((_x_1 + _x_2 = 0) && (_x_3 = None))
          || (((_x_1 - _x_2 = 1) && (_x_3 = Some X))
              || ((_x_1 = _x_2) && (_x_3 = Some O)))
      • simplify

        into:
        let (_x_0 : (int * int))
            = List.fold_right anon_fun.move_counts.1 (0, 0) (….a :: (… :: …))
        in
        let (_x_1 : int) = _x_0.0 in
        let (_x_2 : int) = _x_0.1 in
        let (_x_3 : player option) = ( :var_0: ).last_player in
        ((_x_1 + _x_2 = 0) && (_x_3 = None)) || ((_x_1 = _x_2) && (_x_3 = Some O))
        || ((_x_1 + (-1) * _x_2 = 1) && (_x_3 = Some X))
        expansions:
        []
        rewrite_steps:
          forward_chaining:
          • unroll
            expr:
            (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1357/client))
                             (|::| (i_1273/…
            expansions:
            • unroll
              expr:
              (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1357/client))
                               (|::| (i_1273/…
              expansions:
              • unroll
                expr:
                (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1357/client))
                                 (|::| (i_1273/…
                expansions:
                • unroll
                  expr:
                  (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1357/client))
                                   (|::| (i_1273/…
                  expansions:
                  • unroll
                    expr:
                    (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1357/client))
                                     (|::| (i_1273/…
                    expansions:
                    • unroll
                      expr:
                      (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1357/client))
                                       (|::| (i_1273/…
                      expansions:
                      • unroll
                        expr:
                        (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1357/client))
                                         (|::| (i_1273/…
                        expansions:
                        • unroll
                          expr:
                          (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1357/client))
                                           (|::| (i_1273/…
                          expansions:
                          • unroll
                            expr:
                            (|List.fold_right_524/server|
                              (tuple_mk_518/server 0 0)
                              (|::| (i_1273/client (grid_1290/client g…
                            expansions:
                            • unroll
                              expr:
                              (|List.fold_right_524/server| (tuple_mk_518/server 0 0) |[]|)
                              expansions:
                              • Sat (Some let game : game_state = let (_x_0 : player option) = Some O in {grid = {a = Some X; b = None; c = None; d = None; e = None; f = _x_0; g = None; h = None; i = None}; last_player = _x_0} )
                              In [13]:
                              CX.game;
                              
                              Out[13]:
                              - : game_state = <document>
                              
                              ··
                              ··
                              ···
                              Last:

                              This is a bit better - the grid looks more like a real game of Tic Tac Toe now.

                              Let's also see what happens if we ask for an instance that we know shouldn't be possible - if player X is more than 1 move ahead:

                              In [14]:
                              instance((game) => {
                                 let (x, o) = move_counts(game.grid);
                                 is_valid_grid(game.grid, game.last_player) && ((x - o) >= 2)
                              });
                              
                              Out[14]:
                              - : game_state -> bool = <fun>
                              
                              Unsatisfiable
                              proof
                              ground_instances:10
                              definitions:0
                              inductions:0
                              search_time:
                              0.054s
                              details:
                              Expand
                              smt_stats:
                              arith offset eqs:914
                              num checks:21
                              arith assert lower:725
                              arith tableau max rows:100
                              arith tableau max columns:123
                              arith pivots:375
                              rlimit count:124862
                              mk clause:1282
                              datatype occurs check:655
                              restarts:1
                              mk bool var:3100
                              arith assert upper:888
                              datatype splits:187
                              decisions:1061
                              arith row summations:3432
                              arith bound prop:266
                              propagations:3178
                              interface eqs:30
                              conflicts:169
                              arith fixed eqs:852
                              arith assume eqs:30
                              datatype accessor ax:116
                              minimized lits:98
                              arith conflicts:24
                              arith num rows:100
                              arith assert diseq:286
                              datatype constructor ax:1193
                              num allocs:28612979
                              final checks:40
                              added eqs:9478
                              del clause:726
                              arith eq adapter:567
                              time:0.024000
                              memory:19.920000
                              max memory:19.920000
                              Expand
                              • start[0.054s]
                                  let (_x_0 : (int * int))
                                      = List.fold_right anon_fun.move_counts.1 (0, 0) (….a :: (… :: …))
                                  in
                                  let (_x_1 : int) = _x_0.0 in
                                  let (_x_2 : int) = _x_0.1 in
                                  let (_x_3 : player option) = ( :var_0: ).last_player in
                                  let (_x_4 : int) = _x_1 - _x_2 in
                                  (((_x_1 + _x_2 = 0) && (_x_3 = None))
                                   || (((_x_4 = 1) && (_x_3 = Some X)) || ((_x_1 = _x_2) && (_x_3 = Some O))))
                                  && (_x_4 >= 2)
                              • simplify

                                into:
                                let (_x_0 : (int * int))
                                    = List.fold_right anon_fun.move_counts.1 (0, 0) (….a :: (… :: …))
                                in
                                let (_x_1 : int) = _x_0.0 in
                                let (_x_2 : int) = _x_0.1 in
                                let (_x_3 : player option) = ( :var_0: ).last_player in
                                let (_x_4 : int) = _x_1 + (-1) * _x_2 in
                                (((_x_1 + _x_2 = 0) && (_x_3 = None)) || ((_x_1 = _x_2) && (_x_3 = Some O))
                                 || ((_x_4 = 1) && (_x_3 = Some X)))
                                && (_x_4 >= 2)
                                expansions:
                                []
                                rewrite_steps:
                                  forward_chaining:
                                  • unroll
                                    expr:
                                    (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1359/client))
                                                     (|::| (i_1273/…
                                    expansions:
                                    • unroll
                                      expr:
                                      (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1359/client))
                                                       (|::| (i_1273/…
                                      expansions:
                                      • unroll
                                        expr:
                                        (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1359/client))
                                                         (|::| (i_1273/…
                                        expansions:
                                        • unroll
                                          expr:
                                          (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1359/client))
                                                           (|::| (i_1273/…
                                          expansions:
                                          • unroll
                                            expr:
                                            (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1359/client))
                                                             (|::| (i_1273/…
                                            expansions:
                                            • unroll
                                              expr:
                                              (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1359/client))
                                                               (|::| (i_1273/…
                                              expansions:
                                              • unroll
                                                expr:
                                                (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1359/client))
                                                                 (|::| (i_1273/…
                                                expansions:
                                                • unroll
                                                  expr:
                                                  (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1359/client))
                                                                   (|::| (i_1273/…
                                                  expansions:
                                                  • unroll
                                                    expr:
                                                    (|List.fold_right_592/server|
                                                      (tuple_mk_586/server 0 0)
                                                      (|::| (i_1273/client (grid_1290/client g…
                                                    expansions:
                                                    • unroll
                                                      expr:
                                                      (|List.fold_right_592/server| (tuple_mk_586/server 0 0) |[]|)
                                                      expansions:
                                                      • Unsat

                                                      As this is invalid according to the description we've provided, Imandra can't find an instance. In fact, this Unsatisfiable judgment tells us that Imandra has actually constructed a mathematical proof that no such instance exists.

                                                      Verifying

                                                      We can invert this question, and turn it into a verification statement and ask Imandra to check it for us - similar to a test, but mathematically checked for all possible games (as game is the free argument to the verification function).

                                                      In [15]:
                                                      verify((game) => {
                                                         let (x, o) = move_counts(game.grid);
                                                         not(is_valid_grid(game.grid, game.last_player) && ((x - o) >= 2))
                                                      });
                                                      
                                                      Out[15]:
                                                      Error:
                                                        Type error (env): Unbound value not_
                                                        Hint: Did you mean not?
                                                        At jupyter cell 15:3,3--6
                                                        3 |    not(is_valid_grid(game.grid, game.last_player) && ((x - o) >= 2))
                                                               ^^^
                                                        

                                                      Valid games

                                                      Let's continue adding some more definitions - we haven't really covered winning criteria as part of is_valid_grid, so let's include a few:

                                                      In [16]:
                                                      let is_tie = ({grid: {a, b, c, d, e, f, g, h, i}, _}) =>
                                                        List.for_all((!=)(None), [a, b, c, d, e, f, g, h, i]);
                                                      
                                                      Out[16]:
                                                      val is_tie : game_state -> bool = <fun>
                                                      
                                                      In [17]:
                                                      let is_valid_game = game => {
                                                        let winning_X = is_winning(game, X);
                                                        let winning_O = is_winning(game, O);
                                                        is_valid_grid(game.grid, game.last_player)
                                                        && (! winning_X && ! winning_O || winning_X != winning_O);
                                                      };
                                                      
                                                      Out[17]:
                                                      val is_valid_game : game_state -> bool = <fun>
                                                      

                                                      ... and let's ask for a valid game where O is winning:

                                                      In [18]:
                                                      instance((game) => is_valid_game(game) && is_winning(game, O));
                                                      
                                                      Out[18]:
                                                      - : game_state -> bool = <fun>
                                                      module CX : sig val game : game_state end
                                                      
                                                      Instance (after 10 steps, 0.061s):
                                                      let game : game_state =
                                                        let (_x_0 : player option) = Some X in
                                                        let (_x_1 : player option) = Some O in
                                                        {grid =
                                                         {a = _x_0; b = _x_1; c = _x_0; d = None; e = _x_1; f = _x_0; g = None;
                                                          h = _x_1; i = None};
                                                         last_player = _x_1}
                                                      
                                                      Instance
                                                      proof attempt
                                                      ground_instances:10
                                                      definitions:0
                                                      inductions:0
                                                      search_time:
                                                      0.061s
                                                      details:
                                                      Expand
                                                      smt_stats:
                                                      arith offset eqs:597
                                                      arith assert lower:813
                                                      datatype occurs check:444
                                                      datatype splits:173
                                                      arith bound prop:150
                                                      propagations:3060
                                                      interface eqs:18
                                                      conflicts:161
                                                      arith fixed eqs:699
                                                      datatype constructor ax:592
                                                      num allocs:51007088
                                                      final checks:30
                                                      added eqs:7264
                                                      del clause:386
                                                      num checks:21
                                                      arith tableau max rows:89
                                                      arith tableau max columns:112
                                                      arith pivots:350
                                                      rlimit count:126918
                                                      mk clause:1035
                                                      restarts:1
                                                      mk bool var:2224
                                                      arith gcd tests:1
                                                      arith assert upper:766
                                                      decisions:879
                                                      arith row summations:3423
                                                      arith assume eqs:18
                                                      datatype accessor ax:109
                                                      minimized lits:133
                                                      arith conflicts:35
                                                      arith num rows:89
                                                      arith assert diseq:287
                                                      arith eq adapter:459
                                                      time:0.024000
                                                      memory:21.250000
                                                      max memory:21.250000
                                                      Expand
                                                      • start[0.061s]
                                                          let (_x_0 : player option) = ….a in
                                                          let (_x_1 : (int * int))
                                                              = List.fold_right anon_fun.move_counts.1 (0, 0) (_x_0 :: (… :: …))
                                                          in
                                                          let (_x_2 : int) = _x_1.0 in
                                                          let (_x_3 : int) = _x_1.1 in
                                                          let (_x_4 : player option) = ( :var_0: ).last_player in
                                                          let (_x_5 : player option) = Some X in
                                                          let (_x_6 : player option) = Some O in
                                                          let (_x_7 : player option) = ….c in
                                                          let (_x_8 : (player option * player option * player option))
                                                              = (_x_0, …, _x_7)
                                                          in
                                                          let (_x_9 : (player option * player option * player option))
                                                              = (_x_5, _x_5, _x_5)
                                                          in
                                                          let (_x_10 : player option) = ….d in
                                                          let (_x_11 : player option) = ….e in
                                                          let (_x_12 : player option) = ….f in
                                                          let (_x_13 : (player option * player option * player option))
                                                              = (_x_10, _x_11, _x_12)
                                                          in
                                                          let (_x_14 : player option) = ….g in
                                                          let (_x_15 : player option) = ….h in
                                                          let (_x_16 : player option) = ….i in
                                                          let (_x_17 : (player option * player option * player option))
                                                              = (_x_14, _x_15, _x_16)
                                                          in
                                                          let (_x_18 : (player option * player option * player option))
                                                              = (_x_0, _x_10, _x_14)
                                                          in
                                                          let (_x_19 : (player option * player option * player option))
                                                              = (…, _x_11, _x_15)
                                                          in
                                                          let (_x_20 : (player option * player option * player option))
                                                              = (_x_7, _x_12, _x_16)
                                                          in
                                                          let (_x_21 : (player option * player option * player option))
                                                              = (_x_0, _x_11, _x_16)
                                                          in
                                                          let (_x_22 : (player option * player option * player option))
                                                              = (_x_7, _x_11, _x_14)
                                                          in
                                                          let (_x_23 : bool)
                                                              = (_x_8 = _x_9)
                                                                || ((_x_13 = _x_9)
                                                                    || ((_x_17 = _x_9)
                                                                        || ((_x_18 = _x_9)
                                                                            || ((_x_19 = _x_9)
                                                                                || ((_x_20 = _x_9)
                                                                                    || ((_x_21 = _x_9) || (_x_22 = _x_9)))))))
                                                          in
                                                          let (_x_24 : (player option * player option * player option))
                                                              = (_x_6, _x_6, _x_6)
                                                          in
                                                          let (_x_25 : bool)
                                                              = (_x_8 = _x_24)
                                                                || ((_x_13 = _x_24)
                                                                    || ((_x_17 = _x_24)
                                                                        || ((_x_18 = _x_24)
                                                                            || ((_x_19 = _x_24)
                                                                                || ((_x_20 = _x_24)
                                                                                    || ((_x_21 = _x_24) || (_x_22 = _x_24)))))))
                                                          in
                                                          (((_x_2 + _x_3 = 0) && (_x_4 = None))
                                                           || (((_x_2 - _x_3 = 1) && (_x_4 = _x_5))
                                                               || ((_x_2 = _x_3) && (_x_4 = _x_6))))
                                                          && ((not _x_23 && not _x_25) || not (_x_23 = _x_25)) && _x_25
                                                      • simplify

                                                        into:
                                                        let (_x_0 : player option) = ….a in
                                                        let (_x_1 : (int * int))
                                                            = List.fold_right anon_fun.move_counts.1 (0, 0) (_x_0 :: (… :: …))
                                                        in
                                                        let (_x_2 : int) = _x_1.0 in
                                                        let (_x_3 : int) = _x_1.1 in
                                                        let (_x_4 : player option) = ( :var_0: ).last_player in
                                                        let (_x_5 : player option) = Some O in
                                                        let (_x_6 : player option) = Some X in
                                                        let (_x_7 : bool) = _x_0 = _x_6 in
                                                        let (_x_8 : bool) = … = _x_6 in
                                                        let (_x_9 : player option) = ….c in
                                                        let (_x_10 : bool) = _x_9 = _x_6 in
                                                        let (_x_11 : player option) = ….d in
                                                        let (_x_12 : bool) = _x_11 = _x_6 in
                                                        let (_x_13 : player option) = ….e in
                                                        let (_x_14 : bool) = _x_13 = _x_6 in
                                                        let (_x_15 : player option) = ….f in
                                                        let (_x_16 : bool) = _x_15 = _x_6 in
                                                        let (_x_17 : player option) = ….g in
                                                        let (_x_18 : bool) = _x_17 = _x_6 in
                                                        let (_x_19 : player option) = ….h in
                                                        let (_x_20 : bool) = _x_19 = _x_6 in
                                                        let (_x_21 : player option) = ….i in
                                                        let (_x_22 : bool) = _x_21 = _x_6 in
                                                        let (_x_23 : bool) = _x_0 = _x_5 in
                                                        let (_x_24 : bool) = … = _x_5 in
                                                        let (_x_25 : bool) = _x_9 = _x_5 in
                                                        let (_x_26 : bool) = _x_11 = _x_5 in
                                                        let (_x_27 : bool) = _x_13 = _x_5 in
                                                        let (_x_28 : bool) = _x_15 = _x_5 in
                                                        let (_x_29 : bool) = _x_17 = _x_5 in
                                                        let (_x_30 : bool) = _x_19 = _x_5 in
                                                        let (_x_31 : bool) = _x_21 = _x_5 in
                                                        let (_x_32 : grid) = ( :var_0: ).grid in
                                                        let (_x_33 : player option) = _x_32.a in
                                                        let (_x_34 : player option) = _x_32.e in
                                                        let (_x_35 : bool) = _x_34 = _x_5 in
                                                        let (_x_36 : player option) = _x_32.i in
                                                        let (_x_37 : player option) = _x_32.c in
                                                        let (_x_38 : player option) = _x_32.g in
                                                        let (_x_39 : bool)
                                                            = (_x_23 && _x_24 && _x_25) || (_x_26 && _x_27 && _x_28)
                                                              || (_x_29 && _x_30 && _x_31) || (_x_23 && _x_26 && _x_29)
                                                              || (_x_24 && _x_27 && _x_30) || (_x_25 && _x_28 && _x_31)
                                                              || ((_x_33 = _x_5) && _x_35 && (_x_36 = _x_5))
                                                              || ((_x_37 = _x_5) && _x_35 && (_x_38 = _x_5))
                                                        in
                                                        let (_x_40 : bool) = _x_33 = _x_6 in
                                                        let (_x_41 : bool) = _x_32.b = _x_6 in
                                                        let (_x_42 : bool) = _x_37 = _x_6 in
                                                        let (_x_43 : bool) = _x_32.d = _x_6 in
                                                        let (_x_44 : bool) = _x_34 = _x_6 in
                                                        let (_x_45 : bool) = _x_32.f = _x_6 in
                                                        let (_x_46 : bool) = _x_38 = _x_6 in
                                                        let (_x_47 : bool) = _x_32.h = _x_6 in
                                                        let (_x_48 : bool) = _x_36 = _x_6 in
                                                        (((_x_2 + _x_3 = 0) && (_x_4 = None)) || ((_x_2 = _x_3) && (_x_4 = _x_5))
                                                         || ((_x_2 + (-1) * _x_3 = 1) && (_x_4 = _x_6)))
                                                        && ((not
                                                             ((_x_7 && _x_8 && _x_10) || (_x_12 && _x_14 && _x_16)
                                                              || (_x_18 && _x_20 && _x_22) || (_x_7 && _x_12 && _x_18)
                                                              || (_x_8 && _x_14 && _x_20) || (_x_10 && _x_16 && _x_22)
                                                              || (_x_7 && _x_14 && _x_22) || (_x_10 && _x_14 && _x_18))
                                                             && not _x_39)
                                                            || (not
                                                                ((_x_40 && _x_41 && _x_42) || (_x_43 && _x_44 && _x_45)
                                                                 || (_x_46 && _x_47 && _x_48) || (_x_40 && _x_43 && _x_46)
                                                                 || (_x_41 && _x_44 && _x_47) || (_x_42 && _x_45 && _x_48)
                                                                 || (_x_40 && _x_44 && _x_48) || (_x_42 && _x_44 && _x_46))
                                                                = _x_39))
                                                        && _x_39
                                                        expansions:
                                                        []
                                                        rewrite_steps:
                                                          forward_chaining:
                                                          • unroll
                                                            expr:
                                                            (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1379/client))
                                                                             (|::| (i_1273/…
                                                            expansions:
                                                            • unroll
                                                              expr:
                                                              (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1379/client))
                                                                               (|::| (i_1273/…
                                                              expansions:
                                                              • unroll
                                                                expr:
                                                                (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1379/client))
                                                                                 (|::| (i_1273/…
                                                                expansions:
                                                                • unroll
                                                                  expr:
                                                                  (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1379/client))
                                                                                   (|::| (i_1273/…
                                                                  expansions:
                                                                  • unroll
                                                                    expr:
                                                                    (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1379/client))
                                                                                     (|::| (i_1273/…
                                                                    expansions:
                                                                    • unroll
                                                                      expr:
                                                                      (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1379/client))
                                                                                       (|::| (i_1273/…
                                                                      expansions:
                                                                      • unroll
                                                                        expr:
                                                                        (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1379/client))
                                                                                         (|::| (i_1273/…
                                                                        expansions:
                                                                        • unroll
                                                                          expr:
                                                                          (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1379/client))
                                                                                           (|::| (i_1273/…
                                                                          expansions:
                                                                          • unroll
                                                                            expr:
                                                                            (|List.fold_right_720/server|
                                                                              (tuple_mk_714/server 0 0)
                                                                              (|::| (i_1273/client (grid_1290/client g…
                                                                            expansions:
                                                                            • unroll
                                                                              expr:
                                                                              (|List.fold_right_720/server| (tuple_mk_714/server 0 0) |[]|)
                                                                              expansions:
                                                                              • Sat (Some let game : game_state = let (_x_0 : player option) = Some X in let (_x_1 : player option) = Some O in {grid = {a = _x_0; b = _x_1; c = _x_0; d = None; e = _x_1; f = _x_0; g = None; h = _x_1; i = None}; last_player = _x_1} )
                                                                              In [19]:
                                                                              CX.game;
                                                                              
                                                                              Out[19]:
                                                                              - : game_state = <document>
                                                                              
                                                                              ·
                                                                              ··
                                                                              Last:

                                                                              Valid moves

                                                                              Looks good! We're on the home stretch now - let's add the last few helper functions around playing moves:

                                                                              In [20]:
                                                                              let is_valid_move = (game, player, move) =>
                                                                                ! (is_winning(game, X) || is_winning(game, O) || is_tie(game))
                                                                                && is_valid_game(game)
                                                                                && (
                                                                                  game.last_player == None
                                                                                  && player == initial_player
                                                                                  || game.last_player == Some(other_player(player))
                                                                                );
                                                                              
                                                                              let play_move = ({grid, _}, player, move) => {
                                                                                let play = Some(player);
                                                                                let grid =
                                                                                  switch (move) {
                                                                                  | A => {...grid, a: play}
                                                                                  | B => {...grid, b: play}
                                                                                  | C => {...grid, c: play}
                                                                                  | D => {...grid, d: play}
                                                                                  | E => {...grid, e: play}
                                                                                  | F => {...grid, f: play}
                                                                                  | G => {...grid, g: play}
                                                                                  | H => {...grid, h: play}
                                                                                  | I => {...grid, i: play}
                                                                                  };
                                                                                {grid, last_player: play};
                                                                              };
                                                                              
                                                                              let status = game =>
                                                                                if (is_winning(game, X)) {
                                                                                  Won(X);
                                                                                } else if (is_winning(game, O)) {
                                                                                  Won(O);
                                                                                } else if (is_tie(game)) {
                                                                                  Tied;
                                                                                } else {
                                                                                  InProgress;
                                                                                };
                                                                              
                                                                              Out[20]:
                                                                              val is_valid_move : game_state -> player -> 'a -> bool = <fun>
                                                                              val play_move : game_state -> player -> move -> game_state = <fun>
                                                                              val status : game_state -> game_status = <fun>
                                                                              

                                                                              And finally, the function we call from the ReasonReact frontend to actually play the game!

                                                                              In [21]:
                                                                              let play = ({last_player, _} as game, move) => {
                                                                                let player =
                                                                                  switch (last_player) {
                                                                                  | None => initial_player
                                                                                  | Some(player) => other_player(player)
                                                                                  };
                                                                                if (is_valid_move(game, player, move)) {
                                                                                  let game = play_move(game, player, move);
                                                                                  (game, status(game));
                                                                                } else {
                                                                                  (game, InvalidMove(move));
                                                                                };
                                                                              };
                                                                              
                                                                              Out[21]:
                                                                              val play : game_state -> move -> game_state * game_status = <fun>
                                                                              

                                                                              ...and we're done! Let's add one more verification statement to check that these functions work as we expect. If we've got a valid game, and a valid move, then if we play the move on the game, we should still have a valid game.

                                                                              verify((game, player, move) =>
                                                                                (is_valid_game(game) && is_valid_move(game, player, move))
                                                                                ==> is_valid_game(play_move(game, player, move))
                                                                              );

                                                                              The a ==> b operator here is the logical "implies" - "if a is true, then b is also true". It's defined as a regular infix operator in the Imandra prelude:

                                                                              In [22]:
                                                                              (==>)
                                                                              
                                                                              Out[22]:
                                                                              - : bool -> bool -> bool = <fun>
                                                                              

                                                                              Anyway! Let's actually run the statement:

                                                                              In [23]:
                                                                              verify((game, player, move) =>
                                                                                (is_valid_game(game) && is_valid_move(game, player, move))
                                                                                ==> is_valid_game(play_move(game, player, move))
                                                                              );
                                                                              
                                                                              Out[23]:
                                                                              - : game_state -> player -> move -> bool = <fun>
                                                                              module CX : sig val game : game_state val player : player val move : move end
                                                                              
                                                                              Counterexample (after 23 steps, 0.113s):
                                                                              let game : game_state =
                                                                                let (_x_0 : player option) = Some X in
                                                                                let (_x_1 : player option) = Some O in
                                                                                {grid =
                                                                                 {a = None; b = _x_0; c = _x_1; d = _x_1; e = _x_1; f = _x_0; g = _x_0;
                                                                                  h = _x_0; i = None};
                                                                                 last_player = _x_0}
                                                                              let player : player = O
                                                                              let move : move = H
                                                                              
                                                                              Refuted
                                                                              proof attempt
                                                                              ground_instances:23
                                                                              definitions:0
                                                                              inductions:0
                                                                              search_time:
                                                                              0.113s
                                                                              details:
                                                                              Expand
                                                                              smt_stats:
                                                                              arith offset eqs:2480
                                                                              num checks:47
                                                                              arith assert lower:1392
                                                                              arith tableau max rows:139
                                                                              arith tableau max columns:180
                                                                              arith pivots:685
                                                                              rlimit count:478706
                                                                              mk clause:1455
                                                                              datatype occurs check:1625
                                                                              restarts:1
                                                                              mk bool var:9536
                                                                              arith assert upper:1548
                                                                              datatype splits:1067
                                                                              decisions:5390
                                                                              arith row summations:6929
                                                                              arith bound prop:203
                                                                              propagations:9418
                                                                              interface eqs:31
                                                                              conflicts:282
                                                                              arith fixed eqs:1891
                                                                              arith assume eqs:31
                                                                              datatype accessor ax:376
                                                                              minimized lits:206
                                                                              arith conflicts:20
                                                                              arith num rows:139
                                                                              arith assert diseq:247
                                                                              datatype constructor ax:5465
                                                                              num allocs:104915753
                                                                              final checks:55
                                                                              added eqs:40188
                                                                              del clause:603
                                                                              arith eq adapter:1136
                                                                              time:0.024000
                                                                              memory:24.130000
                                                                              max memory:24.130000
                                                                              Expand
                                                                              • start[0.113s]
                                                                                  let (_x_0 : player option) = ….a in
                                                                                  let (_x_1 : (int * int))
                                                                                      = List.fold_right anon_fun.move_counts.1 (0, 0) (_x_0 :: (… :: …))
                                                                                  in
                                                                                  let (_x_2 : int) = _x_1.0 in
                                                                                  let (_x_3 : int) = _x_1.1 in
                                                                                  let (_x_4 : bool) = _x_2 + _x_3 = 0 in
                                                                                  let (_x_5 : player option) = ( :var_0: ).last_player in
                                                                                  let (_x_6 : bool) = _x_5 = None in
                                                                                  let (_x_7 : bool) = _x_2 - _x_3 = 1 in
                                                                                  let (_x_8 : player option) = Some X in
                                                                                  let (_x_9 : bool) = _x_2 = _x_3 in
                                                                                  let (_x_10 : player option) = Some O in
                                                                                  let (_x_11 : grid) = ( :var_0: ).grid in
                                                                                  let (_x_12 : player option) = _x_11.a in
                                                                                  let (_x_13 : player option) = _x_11.b in
                                                                                  let (_x_14 : player option) = _x_11.c in
                                                                                  let (_x_15 : (player option * player option * player option))
                                                                                      = (_x_12, _x_13, _x_14)
                                                                                  in
                                                                                  let (_x_16 : (player option * player option * player option))
                                                                                      = (_x_8, _x_8, _x_8)
                                                                                  in
                                                                                  let (_x_17 : player option) = _x_11.d in
                                                                                  let (_x_18 : player option) = _x_11.e in
                                                                                  let (_x_19 : player option) = _x_11.f in
                                                                                  let (_x_20 : (player option * player option * player option))
                                                                                      = (_x_17, _x_18, _x_19)
                                                                                  in
                                                                                  let (_x_21 : player option) = _x_11.g in
                                                                                  let (_x_22 : player option) = _x_11.h in
                                                                                  let (_x_23 : player option) = _x_11.i in
                                                                                  let (_x_24 : (player option * player option * player option))
                                                                                      = (_x_21, _x_22, _x_23)
                                                                                  in
                                                                                  let (_x_25 : (player option * player option * player option))
                                                                                      = (_x_12, _x_17, _x_21)
                                                                                  in
                                                                                  let (_x_26 : (player option * player option * player option))
                                                                                      = (_x_13, _x_18, _x_22)
                                                                                  in
                                                                                  let (_x_27 : (player option * player option * player option))
                                                                                      = (_x_14, _x_19, _x_23)
                                                                                  in
                                                                                  let (_x_28 : (player option * player option * player option))
                                                                                      = (_x_12, _x_18, _x_23)
                                                                                  in
                                                                                  let (_x_29 : (player option * player option * player option))
                                                                                      = (_x_14, _x_18, _x_21)
                                                                                  in
                                                                                  let (_x_30 : bool)
                                                                                      = (_x_15 = _x_16)
                                                                                        || ((_x_20 = _x_16)
                                                                                            || ((_x_24 = _x_16)
                                                                                                || ((_x_25 = _x_16)
                                                                                                    || ((_x_26 = _x_16)
                                                                                                        || ((_x_27 = _x_16)
                                                                                                            || ((_x_28 = _x_16) || (_x_29 = _x_16)))))))
                                                                                  in
                                                                                  let (_x_31 : (player option * player option * player option))
                                                                                      = (_x_10, _x_10, _x_10)
                                                                                  in
                                                                                  let (_x_32 : bool)
                                                                                      = (_x_15 = _x_31)
                                                                                        || ((_x_20 = _x_31)
                                                                                            || ((_x_24 = _x_31)
                                                                                                || ((_x_25 = _x_31)
                                                                                                    || ((_x_26 = _x_31)
                                                                                                        || ((_x_27 = _x_31)
                                                                                                            || ((_x_28 = _x_31) || (_x_29 = _x_31)))))))
                                                                                  in
                                                                                  let (_x_33 : bool)
                                                                                      = ((_x_4 && _x_6)
                                                                                         || ((_x_7 && (_x_5 = _x_8)) || (_x_9 && (_x_5 = _x_10))))
                                                                                        && ((not _x_30 && not _x_32) || not (_x_30 = _x_32))
                                                                                  in
                                                                                  let (_x_34 : bool) = ( :var_1: ) = X in
                                                                                  let (_x_35 : player option) = ….c in
                                                                                  let (_x_36 : (player option * player option * player option))
                                                                                      = (_x_0, …, _x_35)
                                                                                  in
                                                                                  let (_x_37 : player option) = ….d in
                                                                                  let (_x_38 : player option) = ….e in
                                                                                  let (_x_39 : player option) = ….f in
                                                                                  let (_x_40 : (player option * player option * player option))
                                                                                      = (_x_37, _x_38, _x_39)
                                                                                  in
                                                                                  let (_x_41 : player option) = ….g in
                                                                                  let (_x_42 : player option) = ….h in
                                                                                  let (_x_43 : player option) = ….i in
                                                                                  let (_x_44 : (player option * player option * player option))
                                                                                      = (_x_41, _x_42, _x_43)
                                                                                  in
                                                                                  let (_x_45 : (player option * player option * player option))
                                                                                      = (_x_0, _x_37, _x_41)
                                                                                  in
                                                                                  let (_x_46 : (player option * player option * player option))
                                                                                      = (…, _x_38, _x_42)
                                                                                  in
                                                                                  let (_x_47 : (player option * player option * player option))
                                                                                      = (_x_35, _x_39, _x_43)
                                                                                  in
                                                                                  let (_x_48 : (player option * player option * player option))
                                                                                      = (_x_0, _x_38, _x_43)
                                                                                  in
                                                                                  let (_x_49 : (player option * player option * player option))
                                                                                      = (_x_35, _x_38, _x_41)
                                                                                  in
                                                                                  let (_x_50 : bool)
                                                                                      = (_x_36 = _x_16)
                                                                                        || ((_x_40 = _x_16)
                                                                                            || ((_x_44 = _x_16)
                                                                                                || ((_x_45 = _x_16)
                                                                                                    || ((_x_46 = _x_16)
                                                                                                        || ((_x_47 = _x_16)
                                                                                                            || ((_x_48 = _x_16) || (_x_49 = _x_16)))))))
                                                                                  in
                                                                                  let (_x_51 : bool)
                                                                                      = (_x_36 = _x_31)
                                                                                        || ((_x_40 = _x_31)
                                                                                            || ((_x_44 = _x_31)
                                                                                                || ((_x_45 = _x_31)
                                                                                                    || ((_x_46 = _x_31)
                                                                                                        || ((_x_47 = _x_31)
                                                                                                            || ((_x_48 = _x_31) || (_x_49 = _x_31)))))))
                                                                                  in
                                                                                  _x_33
                                                                                  && (not
                                                                                      (_x_30
                                                                                       || (_x_32
                                                                                           || List.for_all ((<>) None) (_x_12 :: (_x_13 :: (_x_14 :: …)))))
                                                                                      && (_x_33
                                                                                          && ((_x_6 && _x_34) || (_x_5 = Some (if _x_34 then O else X)))))
                                                                                  ==> ((_x_4 && (… = None))
                                                                                       || ((_x_7 && (… = _x_8)) || (_x_9 && (… = _x_10))))
                                                                                      && ((not _x_50 && not _x_51) || not (_x_50 = _x_51))
                                                                              • simplify

                                                                                into:
                                                                                let (_x_0 : player option) = ….a in
                                                                                let (_x_1 : player option list) = _x_0 :: (… :: …) in
                                                                                let (_x_2 : (int * int)) = List.fold_right anon_fun.move_counts.1 (0, 0) _x_1
                                                                                in
                                                                                let (_x_3 : int) = _x_2.0 in
                                                                                let (_x_4 : int) = _x_2.1 in
                                                                                let (_x_5 : player option) = ( :var_0: ).last_player in
                                                                                let (_x_6 : bool) = _x_5 = None in
                                                                                let (_x_7 : bool) = _x_3 = _x_4 in
                                                                                let (_x_8 : player option) = Some O in
                                                                                let (_x_9 : bool) = _x_3 + (-1) * _x_4 = 1 in
                                                                                let (_x_10 : player option) = Some X in
                                                                                let (_x_11 : bool) = _x_0 = _x_10 in
                                                                                let (_x_12 : bool) = … = _x_10 in
                                                                                let (_x_13 : bool) = _x_11 && _x_12 in
                                                                                let (_x_14 : player option) = ….c in
                                                                                let (_x_15 : bool) = _x_14 = _x_10 in
                                                                                let (_x_16 : bool) = _x_13 && _x_15 in
                                                                                let (_x_17 : player option) = ….d in
                                                                                let (_x_18 : bool) = _x_17 = _x_10 in
                                                                                let (_x_19 : player option) = ….e in
                                                                                let (_x_20 : bool) = _x_19 = _x_10 in
                                                                                let (_x_21 : player option) = ….f in
                                                                                let (_x_22 : bool) = _x_21 = _x_10 in
                                                                                let (_x_23 : bool) = _x_18 && _x_20 && _x_22 in
                                                                                let (_x_24 : player option) = ….g in
                                                                                let (_x_25 : bool) = _x_24 = _x_10 in
                                                                                let (_x_26 : player option) = ….h in
                                                                                let (_x_27 : bool) = _x_26 = _x_10 in
                                                                                let (_x_28 : player option) = ….i in
                                                                                let (_x_29 : bool) = _x_28 = _x_10 in
                                                                                let (_x_30 : bool) = _x_25 && _x_27 && _x_29 in
                                                                                let (_x_31 : bool) = _x_11 && _x_18 && _x_25 in
                                                                                let (_x_32 : bool) = _x_12 && _x_20 && _x_27 in
                                                                                let (_x_33 : bool) = _x_15 && _x_22 && _x_29 in
                                                                                let (_x_34 : bool) = _x_11 && _x_20 && _x_29 in
                                                                                let (_x_35 : bool) = _x_15 && _x_20 && _x_25 in
                                                                                let (_x_36 : bool)
                                                                                    = not
                                                                                      (_x_16 || _x_23 || _x_30 || _x_31 || _x_32 || _x_33 || _x_34 || _x_35)
                                                                                in
                                                                                let (_x_37 : bool) = _x_0 = _x_8 in
                                                                                let (_x_38 : bool) = … = _x_8 in
                                                                                let (_x_39 : bool) = _x_37 && _x_38 in
                                                                                let (_x_40 : bool) = _x_14 = _x_8 in
                                                                                let (_x_41 : bool) = _x_39 && _x_40 in
                                                                                let (_x_42 : bool) = _x_17 = _x_8 in
                                                                                let (_x_43 : bool) = _x_19 = _x_8 in
                                                                                let (_x_44 : bool) = _x_21 = _x_8 in
                                                                                let (_x_45 : bool) = _x_42 && _x_43 && _x_44 in
                                                                                let (_x_46 : bool) = _x_24 = _x_8 in
                                                                                let (_x_47 : bool) = _x_26 = _x_8 in
                                                                                let (_x_48 : bool) = _x_28 = _x_8 in
                                                                                let (_x_49 : bool) = _x_46 && _x_47 && _x_48 in
                                                                                let (_x_50 : bool) = _x_37 && _x_42 && _x_46 in
                                                                                let (_x_51 : bool) = _x_38 && _x_43 && _x_47 in
                                                                                let (_x_52 : bool) = _x_40 && _x_44 && _x_48 in
                                                                                let (_x_53 : bool) = _x_37 && _x_43 && _x_48 in
                                                                                let (_x_54 : bool) = _x_40 && _x_43 && _x_46 in
                                                                                let (_x_55 : bool)
                                                                                    = _x_41 || _x_45 || _x_49 || _x_50 || _x_51 || _x_52 || _x_53 || _x_54
                                                                                in
                                                                                let (_x_56 : bool) = ( :var_1: ) = X in
                                                                                let (_x_57 : player option) = Some ( :var_1: ) in
                                                                                let (_x_58 : grid)
                                                                                    = if ( :var_2: ) = A
                                                                                      then
                                                                                        {a = _x_57; b = …; c = _x_14; d = _x_17; e = _x_19; f = _x_21;
                                                                                         g = _x_24; h = _x_26; i = _x_28}
                                                                                      else
                                                                                      if ( :var_2: ) = B
                                                                                      then
                                                                                        {a = _x_0; b = _x_57; c = _x_14; d = _x_17; e = _x_19; f = _x_21;
                                                                                         g = _x_24; h = _x_26; i = _x_28}
                                                                                      else if ( :var_2: ) = C then … else …
                                                                                in
                                                                                let (_x_59 : player option) = _x_58.c in
                                                                                let (_x_60 : bool) = _x_59 = _x_10 in
                                                                                let (_x_61 : player option) = _x_58.d in
                                                                                let (_x_62 : bool) = _x_61 = _x_10 in
                                                                                let (_x_63 : player option) = _x_58.e in
                                                                                let (_x_64 : bool) = _x_63 = _x_10 in
                                                                                let (_x_65 : player option) = _x_58.f in
                                                                                let (_x_66 : bool) = _x_65 = _x_10 in
                                                                                let (_x_67 : player option) = _x_58.g in
                                                                                let (_x_68 : bool) = _x_67 = _x_10 in
                                                                                let (_x_69 : player option) = _x_58.h in
                                                                                let (_x_70 : bool) = _x_69 = _x_10 in
                                                                                let (_x_71 : player option) = _x_58.i in
                                                                                let (_x_72 : bool) = _x_71 = _x_10 in
                                                                                let (_x_73 : bool)
                                                                                    = not
                                                                                      ((_x_13 && _x_60) || (_x_62 && _x_64 && _x_66)
                                                                                       || (_x_68 && _x_70 && _x_72) || (_x_11 && _x_62 && _x_68)
                                                                                       || (_x_12 && _x_64 && _x_70) || (_x_60 && _x_66 && _x_72)
                                                                                       || (_x_11 && _x_64 && _x_72) || (_x_60 && _x_64 && _x_68))
                                                                                in
                                                                                let (_x_74 : bool) = _x_59 = _x_8 in
                                                                                let (_x_75 : bool) = _x_61 = _x_8 in
                                                                                let (_x_76 : bool) = _x_63 = _x_8 in
                                                                                let (_x_77 : bool) = _x_65 = _x_8 in
                                                                                let (_x_78 : bool) = _x_67 = _x_8 in
                                                                                let (_x_79 : bool) = _x_69 = _x_8 in
                                                                                let (_x_80 : bool) = _x_71 = _x_8 in
                                                                                let (_x_81 : bool)
                                                                                    = (_x_39 && _x_74) || (_x_75 && _x_76 && _x_77)
                                                                                      || (_x_78 && _x_79 && _x_80) || (_x_37 && _x_75 && _x_78)
                                                                                      || (_x_38 && _x_76 && _x_79) || (_x_74 && _x_77 && _x_80)
                                                                                      || (_x_37 && _x_76 && _x_80) || (_x_74 && _x_76 && _x_78)
                                                                                in
                                                                                not
                                                                                ((((_x_3 + _x_4 = 0) && _x_6) || (_x_7 && (_x_5 = _x_8))
                                                                                  || (_x_9 && (_x_5 = _x_10)))
                                                                                 && ((_x_36 && not _x_55) || (_x_36 = _x_55))
                                                                                 && not
                                                                                    (List.for_all ((<>) None) _x_1 || _x_16 || _x_23 || _x_30 || _x_31
                                                                                     || _x_32 || _x_33 || _x_34 || _x_35 || _x_41 || _x_45 || _x_49 || _x_50
                                                                                     || _x_51 || _x_52 || _x_53 || _x_54)
                                                                                 && ((_x_6 && _x_56) || (_x_5 = Some (if _x_56 then O else X))))
                                                                                || (((_x_9 && _x_56) || (_x_7 && (( :var_1: ) = O)))
                                                                                    && ((_x_73 && not _x_81) || (_x_73 = _x_81)))
                                                                                expansions:
                                                                                []
                                                                                rewrite_steps:
                                                                                  forward_chaining:
                                                                                  • unroll
                                                                                    expr:
                                                                                    (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1403/client))
                                                                                                     (|::| (i_1273/…
                                                                                    expansions:
                                                                                    • unroll
                                                                                      expr:
                                                                                      (let ((a!1 (ite (= move_1405/client H_1285/client)
                                                                                                      (|rec_mk.grid_1044/server|
                                                                                            …
                                                                                      expansions:
                                                                                      • unroll
                                                                                        expr:
                                                                                        (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1403/client))
                                                                                                         (|::| (i_1273/…
                                                                                        expansions:
                                                                                        • unroll
                                                                                          expr:
                                                                                          (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1403/client))
                                                                                                           (|::| (i_1273/…
                                                                                          expansions:
                                                                                          • unroll
                                                                                            expr:
                                                                                            (let ((a!1 (ite (= move_1405/client H_1285/client)
                                                                                                            (|rec_mk.grid_1044/server|
                                                                                                  …
                                                                                            expansions:
                                                                                            • unroll
                                                                                              expr:
                                                                                              (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1403/client))
                                                                                                               (|::| (i_1273/…
                                                                                              expansions:
                                                                                              • unroll
                                                                                                expr:
                                                                                                (let ((a!1 (ite (= move_1405/client H_1285/client)
                                                                                                                (|rec_mk.grid_1044/server|
                                                                                                      …
                                                                                                expansions:
                                                                                                • unroll
                                                                                                  expr:
                                                                                                  (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1403/client))
                                                                                                                   (|::| (i_1273/…
                                                                                                  expansions:
                                                                                                  • unroll
                                                                                                    expr:
                                                                                                    (let ((a!1 (ite (= move_1405/client H_1285/client)
                                                                                                                    (|rec_mk.grid_1044/server|
                                                                                                          …
                                                                                                    expansions:
                                                                                                    • unroll
                                                                                                      expr:
                                                                                                      (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1403/client))
                                                                                                                       (|::| (i_1273/…
                                                                                                      expansions:
                                                                                                      • unroll
                                                                                                        expr:
                                                                                                        (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1403/client))
                                                                                                                         (|::| (i_1273/…
                                                                                                        expansions:
                                                                                                        • unroll
                                                                                                          expr:
                                                                                                          (let ((a!1 (ite (= move_1405/client H_1285/client)
                                                                                                                          (|rec_mk.grid_1044/server|
                                                                                                                …
                                                                                                          expansions:
                                                                                                          • unroll
                                                                                                            expr:
                                                                                                            (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1403/client))
                                                                                                                             (|::| (i_1273/…
                                                                                                            expansions:
                                                                                                            • unroll
                                                                                                              expr:
                                                                                                              (let ((a!1 (ite (= move_1405/client H_1285/client)
                                                                                                                              (|rec_mk.grid_1044/server|
                                                                                                                    …
                                                                                                              expansions:
                                                                                                              • unroll
                                                                                                                expr:
                                                                                                                (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1403/client))
                                                                                                                                 (|::| (i_1273/…
                                                                                                                expansions:
                                                                                                                • unroll
                                                                                                                  expr:
                                                                                                                  (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1403/client))
                                                                                                                                   (|::| (i_1273/…
                                                                                                                  expansions:
                                                                                                                  • unroll
                                                                                                                    expr:
                                                                                                                    (let ((a!1 (ite (= move_1405/client H_1285/client)
                                                                                                                                    (|rec_mk.grid_1044/server|
                                                                                                                          …
                                                                                                                    expansions:
                                                                                                                    • unroll
                                                                                                                      expr:
                                                                                                                      (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1403/client))
                                                                                                                                       (|::| (i_1273/…
                                                                                                                      expansions:
                                                                                                                      • unroll
                                                                                                                        expr:
                                                                                                                        (let ((a!1 (ite (= move_1405/client H_1285/client)
                                                                                                                                        (|rec_mk.grid_1044/server|
                                                                                                                              …
                                                                                                                        expansions:
                                                                                                                        • unroll
                                                                                                                          expr:
                                                                                                                          (let ((a!1 (|::| (h_1272/client (grid_1290/client game_1403/client))
                                                                                                                                           (|::| (i_1273/…
                                                                                                                          expansions:
                                                                                                                          • unroll
                                                                                                                            expr:
                                                                                                                            (|List.fold_right_1059/server|
                                                                                                                              (tuple_mk_1053/server 0 0)
                                                                                                                              (|::| (i_1273/client (grid_1290/client…
                                                                                                                            expansions:
                                                                                                                            • unroll
                                                                                                                              expr:
                                                                                                                              (let ((a!1 (ite (= move_1405/client H_1285/client)
                                                                                                                                              (|rec_mk.grid_1044/server|
                                                                                                                                    …
                                                                                                                              expansions:
                                                                                                                              • unroll
                                                                                                                                expr:
                                                                                                                                (|List.fold_right_1059/server| (tuple_mk_1053/server 0 0) |[]|)
                                                                                                                                expansions:
                                                                                                                                • Sat (Some let game : game_state = let (_x_0 : player option) = Some X in let (_x_1 : player option) = Some O in {grid = {a = None; b = _x_0; c = _x_1; d = _x_1; e = _x_1; f = _x_0; g = _x_0; h = _x_0; i = None}; last_player = _x_0} let player : player = O let move : move = H )

                                                                                                                                Uh oh! What's gone wrong?

                                                                                                                                In this case, the problem is clear - the player is trying to play a move in a square that's already occupied:

                                                                                                                                In [24]:
                                                                                                                                value(CX.game, CX.move)
                                                                                                                                
                                                                                                                                Out[24]:
                                                                                                                                - : square_state = Some X
                                                                                                                                

                                                                                                                                If you try out the initial version of the game based on the code so far, you'll see the bug if you try and make a move in a square that's already filled.

                                                                                                                                Imandra has spotted the issue for us from a straightforward statement about the logic of the game!

                                                                                                                                The culprit is the is_valid_move function - we don't check that the square is empty. Let's add && value(game, move) == None to our check:

                                                                                                                                In [25]:
                                                                                                                                let is_valid_move = (game, player, move) =>
                                                                                                                                  ! (is_winning(game, X) || is_winning(game, O) || is_tie(game))
                                                                                                                                  && is_valid_game(game)
                                                                                                                                  && (
                                                                                                                                    game.last_player == None
                                                                                                                                    && player == initial_player
                                                                                                                                    || game.last_player == Some(other_player(player))
                                                                                                                                  )
                                                                                                                                  && value(game, move) == None
                                                                                                                                ;
                                                                                                                                
                                                                                                                                Out[25]:
                                                                                                                                val is_valid_move : game_state -> player -> move -> bool = <fun>
                                                                                                                                

                                                                                                                                ... and verify once more (using [@blast] to speed it up):

                                                                                                                                In [26]:
                                                                                                                                [@blast]
                                                                                                                                verify((game, player, move) =>
                                                                                                                                  (is_valid_game(game) && is_valid_move(game, player, move))
                                                                                                                                  ==> is_valid_game(play_move(game, player, move))
                                                                                                                                );
                                                                                                                                
                                                                                                                                Out[26]:
                                                                                                                                - : game_state -> player -> move -> bool = <fun>
                                                                                                                                
                                                                                                                                proof from "blast" (after 1.21s)!
                                                                                                                                Proved

                                                                                                                                Much better! You can play the final version here, and you'll see that the bug is fixed. Hopefully this gives you a feel for an interactive development workflow with Imandra, in particular how seeing concrete examples can be really useful to understanding what's going on.