just another programmer blog

Main menu

Post navigation

Alloy practice problem: Tic Tac Toe

One of my friends was recently interviewing and was asked to create an API for tic tac toe. As far as interview questions go it seems like a reasonable problem for demonstrating programming understanding but I want to one-up the interviewers by solving the game in Alloy.

I’m going to assume everyone knows the game but even if you don’t I’ll explain it as I elaborate the model.

The evolution of the game is going to consist of states

1

open util/ordering[State]

The game itself happens on a 3×3 board which we are going to model as a matrix with rows and columns

1

2

enumRow{R1,R2,R3}

enumCol{C1,C2,C3}

The game has two players which are identified with the marks they are allowed to place on the board

1

enumPlayer{X,O}

The game board itself consists of marked rows and columns. Since we have identified the marks with the players the signature for the board is a pretty simple mapping from row/column pairs to players with the restriction that two players can’t mark the same row/column pair

1

sigBoard{cells:Row->Col->Player}{no cells.X&cells.O}

The state itself consists of a board and the player that is going to make the next mark on the board

1

sigState{board:Board,player:Player}

We are done with the fundamental building blocks and restrictions and can now define the transition relation which encodes the rules of the game

1

2

3

4

5

6

7

8

9

10

11

12

13

pred transition(s,s': State) {

let p = s.player, p'=s'.player,

scells = s.board.cells, scells'=s'.board.cells {

// Players must alternate

p != p'

// Previous cells must be preserved

scells inscells'

// Previous players cells must be preserved

scells.p'=scells'.p'

// Current player must make 1 move

one(scells'-scells).p

}

}

The comments explain what is going on but to unpack it a bit more: if a player is about to make a mark then in the next state it can’t be allowed to make a mark again, the cells from the previous state must be in the next state, we don’t tamper with the moves of the previous player when the current player is making a mark (think about what happens if this restriction is removed), the next state contains only 1 extra mark and it is a mark by the current player.

The only other thing we need to specify is the winning condition

1

2

3

4

5

6

7

8

9

10

11

12

pred winner(b:Board,p:Player){

let positions=b.cells.p{

// Player occupies a column

(somec:Col|Row=positions.c)||

// Player occupies a row

(somer:Row|Col=positions[r])||

// Player occupies off diagonal

(R1->C1+R2->C2+R3->C3)inpositions||

// Player occupies diagonal

(R3->C1+R2->C2+R1->C3)inpositions

}

}

Again I think the comments explain it but to unpack it: if the player occupies a row or a column or one of the diagonals then that player is winning.

With all of the above we can run the model and find some winning state transitions for player X

1

2

3

4

5

6

7

8

run{

// Initial state consists of an empty board

no first.board.cells

// States following each other must conform to the transition predicate

alls:State,s': s.next | transition[s, s']

// We want to know if X can win so the last state must be a winning board for X