Last class, we started to model Tic Tac Toe in Alloy:
abstract sig Player
means that you have a type called Player, but every instance of Player must be a subtypeone sig X
and one sig O
mean that there is only one X and and one O playersig Board
has places: (Index -> Index) -> lone Player
, which we can think of as a relation from pairs of indices to lone Player
which means there is either zero or one player in each place.Why is it Index -> Index
, not Index, Index
?
In Alloy, everything is a relation, so you can read these as two different columns in a table/database
{r, c: Index | b.places[r][c] = X}
: set expression that contains pairs of indices on the board that correspond to places on the board containing Xs
pred xturn[b: Board]
-- predicate takes some input and produces a boolean.
pred xturn[b: Board] {
#{r,c: Index | b.places[r][c] = X} =
#{r,c: Index | b.places[r][c] = O}
}
This predicate will be true if the number of places with X is equal to the number of places with O on the given board.
Why do we get an instance with multiple boards?
Why not? We only specify to Alloy that there is a type board, and Alloy's job is to find instances we may not have considered, so it will find all instances that satisfy your constraints. Here, we never specified that there could only be one board, so it finds instances of multiple boards.
In Alloy, everything is implicitly a set.
Note: -
is set difference and +
is set union. If you want to add or subtract, you should use add[]
and sub[]
Why have two predicates xturn
and oturn
? Isn't it true that every time it's not X's turn, it is O's turn?
We want to disallow some invalid boards. There are boards that are neither O's turn nor X's turn, so they're not exactly the opposite of each other. For example, if there are three X pieces and zero O pieces on the board, we don't really want that to count as O's turn, because it should never come up in a valid game.
What does it mean to win?
One player has three pieces in a row either vertically, horizontally, or diagonally. We will model the options with three separate predicates, winH
, winV
, and winD
and another predicate win
that is true if any of the first three predicates are true.
some r: Index | ...
is a quantifier that says "there exists some Index, r, such that..."
There are annotations in the visualizer that say ($winH_p), ($winH_b), and ($winH_r). These indicate which values correspond to p, b, and r to make the predicate true.
What does it mean to be a valid board?
It should be either X's turn or O's turn
What does it mean to be a valid move?
A player can move into any open space if it is their turn. We will model this using a move
predicate with the following type:
pred move[b: Board, r: Index, c: Index, p: Player, newb: Board]
But we said there was only one board (one sig Board
), so we get "No Instance Found" from Alloy.
Constraints for a valid move:
no b.places[r][c]
newb.places[r][c] = p
p = O implies oturn[b]
and p = X implies xturn[b]
newb.places = b.places + (r -> c -> p)
Next time, we will expand our model to model full games