To get a winning instance of our Tic Tac Toe board, we define the run function such that someone is winning and the board is valid.
Run { some b:Board | some p:Player | win[b, p] and validBoard[b]}
Now, we want to define a move predicate to model the way two players would actually play Tic Tac Toe.
What do we need?
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]
Constraints for a valid move:
no b.places[r][c]
oturn[b] implies p = O
and xturn[b] implies p = X
newb.places = b.places + (r -> c -> p)
b != newb
Alloy returns "No Instance Found" from Alloy. It also gives you a “Core.” Clicking on this gives you the subset of your constrains that are mutually contradictory. A good move to debug alloy is to look at the Core, and weaken the constraints some to figure out which statements are contradictory. Core Settings are found in “Options” of your Alloy menu.
In this case, we had specified one Sig Board
so we couldn’t run our move
predicate, which requires at least two Boards.
Alloy surprised us! There is a third unexpected board because we never specified all Boards have to be a part of the move.
How can we construct a full game?
Impose ordering on our boards:
Open util/ordering[Board]
This will ensure that all Boards are ordered. When we generate an instance, we can find the ordering that Alloy has imposed by using the Evaluator and typing in “first,” “first.next,” “first.next.next,” etc.
To start the game, we define a fact that always holds true at the beginning.
fact first {
no first.places
}
How do we define the next step?
fact next {
all b: Board – last |
some r, c, Index, p: Player |
move[b, r, c, p, b.next]
}
To clarify muddled instances of Alloy, we can “project over” some type (e.g. Board).
Can ask the Evaluator if any predicate we have outlined is true, e.g. win[last, X] and win[last, O] will tell us who has won.