-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Prepare to release sum type support in verify
#1274
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
d85ad63
Use sum types for tictactoe example
f0f26b0
Update Apalache version
d8fdf12
Update option.qnt for verification
a32a631
Update examples script
f53a6c3
Update changelog
be9a510
Use a default case in the tictactoe example
f0f0c3e
Update examples dashboard
c53b2ca
Update feature table
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,10 +12,14 @@ | |
* Gabriela Moreira, Informal Systems, 2022-2023 | ||
*/ | ||
module tictactoe { | ||
type Player = X | O | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's nice :) |
||
type Square = Occupied(Player) | Empty | ||
|
||
/// A 3x3 tic-tac-toe board | ||
var board: int -> (int -> str) | ||
var board: int -> (int -> Square) | ||
|
||
/// Who goes next | ||
var nextTurn: str | ||
var nextTurn: Player | ||
|
||
pure val boardCoordinates = tuples(1.to(3), 1.to(3)) | ||
|
||
|
@@ -40,33 +44,41 @@ module tictactoe { | |
(3,3) | ||
) | ||
|
||
def hasValue(coordinate, player) = | ||
board.get(coordinate._1).get(coordinate._2) == player | ||
def square(coordinate: (int, int)): Square = | ||
board.get(coordinate._1).get(coordinate._2) | ||
|
||
def isEmpty(coordinate) = hasValue(coordinate, "_") | ||
def hasPlayer(coordinate, player) = match square(coordinate) { | ||
| Empty => false | ||
| Occupied(p) => player == p | ||
} | ||
|
||
def isEmpty(coordinate) = match square(coordinate) { | ||
| Empty => true | ||
| _ => false | ||
} | ||
|
||
val boardEmpty = boardCoordinates.forall(isEmpty) | ||
|
||
def won(player) = winningPatterns.exists(pattern => | ||
pattern.forall(coordinate => hasValue(coordinate, player)) | ||
pattern.forall(coordinate => hasPlayer(coordinate, player)) | ||
) | ||
|
||
val boardFull = not(boardCoordinates.exists(isEmpty)) | ||
val stalemate = boardFull and not(won("X")) and not(won("O")) | ||
val gameOver = won("X") or won("O") or boardFull | ||
val stalemate = boardFull and not(won(X)) and not(won(O)) | ||
val gameOver = won(X) or won(O) or boardFull | ||
|
||
def canWinWithPattern(pattern) = and { | ||
pattern.filter(coordinate => coordinate.hasValue("X")).size() == 2, | ||
pattern.filter(coordinate => coordinate.hasPlayer(X)).size() == 2, | ||
pattern.filter(coordinate => coordinate.isEmpty()).size() == 1, | ||
} | ||
|
||
def canBlockWithPattern(pattern) = and { | ||
pattern.filter(coordinate => coordinate.hasValue("O")).size() == 2, | ||
pattern.filter(coordinate => coordinate.hasPlayer(O)).size() == 2, | ||
pattern.filter(coordinate => coordinate.isEmpty()).size() == 1, | ||
} | ||
|
||
def canSetupWinWithPattern(pattern) = and { | ||
pattern.filter(coordinate => coordinate.hasValue("X")).size() == 1, | ||
pattern.filter(coordinate => coordinate.hasPlayer(X)).size() == 1, | ||
pattern.filter(coordinate => coordinate.isEmpty()).size() == 2, | ||
} | ||
|
||
|
@@ -79,30 +91,30 @@ module tictactoe { | |
isEmpty(coordinate), | ||
board' = board.setBy( | ||
coordinate._1, | ||
row => row.set(coordinate._2, player) | ||
row => row.set(coordinate._2, Occupied(player)) | ||
), | ||
} | ||
|
||
action Win = all { | ||
canWin, | ||
nondet pattern = winningPatterns.filter(canWinWithPattern).oneOf() | ||
nondet coordinate = pattern.filter(isEmpty).oneOf() | ||
Move("X", coordinate), | ||
Move(X, coordinate), | ||
} | ||
|
||
action Block = all { | ||
canBlock, | ||
nondet pattern = winningPatterns.filter(canBlockWithPattern).oneOf() | ||
nondet coordinate = pattern.filter(isEmpty).oneOf() | ||
Move("X", coordinate), | ||
Move(X, coordinate), | ||
} | ||
|
||
action TakeCenter = Move("X", (2, 2)) | ||
action TakeCenter = Move(X, (2, 2)) | ||
|
||
action SetupWin = all { | ||
nondet pattern = winningPatterns.filter(canSetupWinWithPattern).oneOf() | ||
nondet coordinate = pattern.filter(isEmpty).oneOf() | ||
Move("X", coordinate), | ||
Move(X, coordinate), | ||
} | ||
|
||
action MoveToEmpty(player) = | ||
|
@@ -111,32 +123,32 @@ module tictactoe { | |
|
||
action StartInCorner = | ||
nondet corner = oneOf(corners) | ||
Move("X", corner) | ||
Move(X, corner) | ||
|
||
action MoveX = all { | ||
nextTurn == "X", | ||
not(won("O")), | ||
nextTurn == X, | ||
not(won(O)), | ||
if (boardEmpty) StartInCorner else | ||
if (canWin) Win else | ||
if (canBlock) Block else | ||
if (canTakeCenter) TakeCenter else | ||
if (canSetupWin) SetupWin else | ||
MoveToEmpty("X"), | ||
nextTurn' = "O", | ||
MoveToEmpty(X), | ||
nextTurn' = O, | ||
} | ||
|
||
action MoveO = all { | ||
nextTurn == "O", | ||
not(won("X")), | ||
MoveToEmpty("O"), | ||
nextTurn' = "X", | ||
nextTurn == O, | ||
not(won(X)), | ||
MoveToEmpty(O), | ||
nextTurn' = X, | ||
} | ||
|
||
action init = all { | ||
// X always goes first | ||
nextTurn' = "X", | ||
nextTurn' = X, | ||
// Every space in the board starts blank | ||
board' = 1.to(3).mapBy(_ => 1.to(3).mapBy(_ => "_")), | ||
board' = 1.to(3).mapBy(_ => 1.to(3).mapBy(_ => Empty)), | ||
} | ||
|
||
/// Either X or O will make a move, depending on the value of `nextTurn` | ||
|
@@ -150,10 +162,10 @@ module tictactoe { | |
/* Invariants */ | ||
|
||
/// X has not won. This does not hold, as X wins most of the times. | ||
val XHasNotWon = not(won("X")) | ||
val XHasNotWon = not(won(X)) | ||
|
||
/// O has not won. This should hold, as O can only achieve a draw. | ||
val OHasNotWon = not(won("O")) | ||
val OHasNotWon = not(won(O)) | ||
|
||
/// It's not a stalemate if one player has won or the board is not filled | ||
/// Check with `quint run --invariant=NotStalemate tictactoe.qnt` | ||
|
@@ -166,5 +178,5 @@ module tictactoe { | |
|
||
/// This is not always true, as if O picks the right moves, the game will | ||
/// result in a stalemate. | ||
temporal XMustEventuallyWin = eventually(won("X")) | ||
temporal XMustEventuallyWin = eventually(won(X)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not have this if and always keep the explanations updated. If something is succeeding, then there should be no explanation, and a new failing thing should prompt us to "investigate" enough to assign a new explanation to the failing cell. Otherwise, we might fall back to old explanations that are not the real problem anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This conditional ensures that will only be an explanation if something fails. If it success, we don't print the explanation. My thinking was that will result in a bigger diff and a better chance of someone noticing that this needs to be updated. With the current logic, the explanation is printed whether or not the run succeeds, which I think makes it less likely for the required change to be noted.
It's exactly your concern that motivated this change :)
But I think we ought to rework this script entirely before long. So if you don't object hard here, I'll leave it as is and either adjust in a followup or when we rework this.