Let’s begin by exploring our contract for the TicTacToe game:
Python
# TicTacToe - Example for illustrative purposes only.
import smartpy as sp
@sp.module
def main():
class TicTacToe(sp.Contract):
def __init__(self):
self.data.nbMoves = 0
self.data.winner = 0
self.data.draw = False
self.data.deck = {
0: {0: 0, 1: 0, 2: 0},
1: {0: 0, 1: 0, 2: 0},
2: {0: 0, 1: 0, 2: 0},
}
self.data.nextPlayer = 1
@sp.entrypoint
def play(self, params):
assert self.data.winner == 0 and not self.data.draw
assert params.i >= 0 and params.i < 3
assert params.j >= 0 and params.j < 3
assert params.move == self.data.nextPlayer
assert self.data.deck[params.i][params.j] == 0
self.data.deck[params.i][params.j] = params.move
self.data.nbMoves += 1
self.data.nextPlayer = 3 - self.data.nextPlayer
self.data.winner = self.checkLine(
sp.record(winner=self.data.winner, line=self.data.deck[params.i])
)
self.data.winner = self.checkLine(
sp.record(
winner=self.data.winner,
line={
0: self.data.deck[0][params.j],
1: self.data.deck[1][params.j],
2: self.data.deck[2][params.j],
},
)
)
self.data.winner = self.checkLine(
sp.record(
winner=self.data.winner,
line={
0: self.data.deck[0][0],
1: self.data.deck[1][1],
2: self.data.deck[2][2],
},
)
)
self.data.winner = self.checkLine(
sp.record(
winner=self.data.winner,
line={
0: self.data.deck[0][2],
1: self.data.deck[1][1],
2: self.data.deck[2][0],
},
)
)
if self.data.nbMoves == 9 and self.data.winner == 0:
self.data.draw = True
@sp.private()
def checkLine(self, winner, line):
winner_ = winner
if line[0] != 0 and line[0] == line[1] and line[0] == line[2]:
winner_ = line[0]
return winner_
# Add a game reset function
@sp.entrypoint
def confirm_and_reset(self):
assert self.data.winner != 0 or self.data.draw
self.__init__()
# Tests
if "templates" not in __name__:
@sp.add_test(name="TicTacToe")
def test():
scenario = sp.test_scenario(main)
scenario.h1("Tic-Tac-Toe")
# define a contract
c1 = main.TicTacToe()
# show its representation
scenario.h2("A sequence of interactions with a winner")
scenario += c1
scenario.h2("Message execution")
scenario.h3("A first move in the center")
c1.play(i=1, j=1, move=1)
scenario.h3("A forbidden move")
c1.play(i=1, j=1, move=2).run(valid=False)
scenario.h3("A second move")
c1.play(i=1, j=2, move=2)
scenario.h3("Other moves")
c1.play(i=2, j=1, move=1)
c1.play(i=2, j=2, move=2)
scenario.verify(c1.data.winner == 0)
c1.play(i=0, j=1, move=1)
scenario.verify(c1.data.winner == 1)
scenario.p("Player1 has won")
c1.play(i=0, j=0, move=2).run(valid=False)
c2 = main.TicTacToe()
scenario.h2("A sequence of interactions with a draw")
scenario += c2
scenario.h2("Message execution")
scenario.h3("A first move in the center")
c2.play(i=1, j=1, move=1)
scenario.h3("A forbidden move")
c2.play(i=1, j=1, move=2).run(valid=False)
scenario.h3("A second move")
c2.play(i=1, j=2, move=2)
scenario.h3("Other moves")
c2.play(i=2, j=1, move=1)
c2.play(i=2, j=2, move=2)
c2.play(i=0, j=0, move=1)
c2.play(i=0, j=1, move=2)
c2.play(i=0, j=2, move=1)
c2.play(i=2, j=0, move=2)
c2.play(i=1, j=0, move=1)
# Add tests for game reset
scenario.h2("Testing game reset")
scenario.p("Winner or draw confirmed, now resetting the game")
c1.confirm_and_reset()
scenario.verify(c1.data.nbMoves == 0)
scenario.verify(c1.data.winner == 0)
scenario.verify(not c1.data.draw)
c2.confirm_and_reset()
scenario.verify(c2.data.nbMoves == 0)
scenario.verify(c2.data.winner == 0)
scenario.verify(not c2.data.draw)
The contract for our TicTacToe game on Tezos is written in the SmartPy language. It consists of two main parts: the state of the contract, and the logic of the game.
The state of the contract is initialized in the **init
function. It includes:
nbMoves
: This is a counter for the number of moves made in the game. It starts at zero.winner
: This keeps track of the winner of the game. Initially, it’s zero, indicating no winner.draw
: This is a flag indicating whether the game has ended in a draw. Initially, it’s False.deck
: This is a 3x3 grid representing the TicTacToe board. All spots on the board are initially empty, represented by zeroes.nextPlayer
: This indicates whose turn it is to play. The game starts with player 1, so it’s initially set to 1.The game logic is encapsulated in the play
function. It performs several checks to ensure a valid move:
nextPlayer
.nbMoves
, switches the nextPlayer
, and checks if the move has resulted in a win or a draw.The win condition is checked across the row and column of the latest move, as well as the two diagonals.
If all spots on the board are filled and no player has won (i.e., nbMoves
equals 9 and winner
is still 0), the game is declared a draw.
The checkLine
function is used to check if any player has won. It checks if all spots in a line (which can be a row, a column, or a diagonal) are filled by the same player. If so, that player is declared the winner.
Interactions with the contract are represented as transactions. When a player makes a move by calling the play
function, it generates a transaction. This transaction gets logged and can be seen in the right panel of the SmartPy IDE:
An unsuccessful or invalid move would also generate a transaction but with an error indication:
The first move in our TicTacToe game is relatively straightforward since the game board is empty. However, things get more interesting with the second move and subsequent moves. These moves not only add pieces to the game board but also invoke the game’s logic to check for potential winners.
After the first move, the nextPlayer
value switches to player 2. Now, the play
function validates player 2’s move. Similar checks are performed to ensure the move is valid, i.e., the selected grid spot is within the boundaries and is empty.
As each player makes a move, the game’s state evolves. The nbMoves
increases, the nextPlayer
toggles, and the deck
is updated. Also, after each move, the contract checks if there’s a winner or if it’s a draw.
For example, after the first player makes a move in the center of the board at i=1, j=1
, the second player can play at a different spot, say i=1, j=2
. Both of these moves would be validated and executed successfully, with the corresponding transactions being generated.
Subsequent moves continue in a similar manner. Each player takes turns to play, choosing an empty spot on the board. After each move, the contract checks for any winning condition. If a player fills an entire row, column, or diagonal with their symbol, the game ends and that player is declared the winner. The winner
variable in the contract’s state would be updated accordingly.
It’s important to note that once a player has won, no further moves are valid. Any attempt to make a move after the game has ended would be considered invalid and the corresponding transaction would fail.
In some games, it’s possible that no player achieves a winning condition even after the entire game board is filled. This results in a draw. The contract has been designed to handle this situation as well.
If all spots on the board are filled (nbMoves
equals 9) and no player has won (winner
remains 0), the game is declared a draw. The draw
flag in the contract’s state is set to True, indicating the game ended in a draw. Once again, no further moves are valid after this point. Any attempts to make a move after a draw would also fail.
The second part of the TicTacToe contract’s test scenario demonstrates this drawing scenario. It simulates a series of moves that result in a draw and verifies that the contract handles it correctly.
Let’s begin by exploring our contract for the TicTacToe game:
Python
# TicTacToe - Example for illustrative purposes only.
import smartpy as sp
@sp.module
def main():
class TicTacToe(sp.Contract):
def __init__(self):
self.data.nbMoves = 0
self.data.winner = 0
self.data.draw = False
self.data.deck = {
0: {0: 0, 1: 0, 2: 0},
1: {0: 0, 1: 0, 2: 0},
2: {0: 0, 1: 0, 2: 0},
}
self.data.nextPlayer = 1
@sp.entrypoint
def play(self, params):
assert self.data.winner == 0 and not self.data.draw
assert params.i >= 0 and params.i < 3
assert params.j >= 0 and params.j < 3
assert params.move == self.data.nextPlayer
assert self.data.deck[params.i][params.j] == 0
self.data.deck[params.i][params.j] = params.move
self.data.nbMoves += 1
self.data.nextPlayer = 3 - self.data.nextPlayer
self.data.winner = self.checkLine(
sp.record(winner=self.data.winner, line=self.data.deck[params.i])
)
self.data.winner = self.checkLine(
sp.record(
winner=self.data.winner,
line={
0: self.data.deck[0][params.j],
1: self.data.deck[1][params.j],
2: self.data.deck[2][params.j],
},
)
)
self.data.winner = self.checkLine(
sp.record(
winner=self.data.winner,
line={
0: self.data.deck[0][0],
1: self.data.deck[1][1],
2: self.data.deck[2][2],
},
)
)
self.data.winner = self.checkLine(
sp.record(
winner=self.data.winner,
line={
0: self.data.deck[0][2],
1: self.data.deck[1][1],
2: self.data.deck[2][0],
},
)
)
if self.data.nbMoves == 9 and self.data.winner == 0:
self.data.draw = True
@sp.private()
def checkLine(self, winner, line):
winner_ = winner
if line[0] != 0 and line[0] == line[1] and line[0] == line[2]:
winner_ = line[0]
return winner_
# Add a game reset function
@sp.entrypoint
def confirm_and_reset(self):
assert self.data.winner != 0 or self.data.draw
self.__init__()
# Tests
if "templates" not in __name__:
@sp.add_test(name="TicTacToe")
def test():
scenario = sp.test_scenario(main)
scenario.h1("Tic-Tac-Toe")
# define a contract
c1 = main.TicTacToe()
# show its representation
scenario.h2("A sequence of interactions with a winner")
scenario += c1
scenario.h2("Message execution")
scenario.h3("A first move in the center")
c1.play(i=1, j=1, move=1)
scenario.h3("A forbidden move")
c1.play(i=1, j=1, move=2).run(valid=False)
scenario.h3("A second move")
c1.play(i=1, j=2, move=2)
scenario.h3("Other moves")
c1.play(i=2, j=1, move=1)
c1.play(i=2, j=2, move=2)
scenario.verify(c1.data.winner == 0)
c1.play(i=0, j=1, move=1)
scenario.verify(c1.data.winner == 1)
scenario.p("Player1 has won")
c1.play(i=0, j=0, move=2).run(valid=False)
c2 = main.TicTacToe()
scenario.h2("A sequence of interactions with a draw")
scenario += c2
scenario.h2("Message execution")
scenario.h3("A first move in the center")
c2.play(i=1, j=1, move=1)
scenario.h3("A forbidden move")
c2.play(i=1, j=1, move=2).run(valid=False)
scenario.h3("A second move")
c2.play(i=1, j=2, move=2)
scenario.h3("Other moves")
c2.play(i=2, j=1, move=1)
c2.play(i=2, j=2, move=2)
c2.play(i=0, j=0, move=1)
c2.play(i=0, j=1, move=2)
c2.play(i=0, j=2, move=1)
c2.play(i=2, j=0, move=2)
c2.play(i=1, j=0, move=1)
# Add tests for game reset
scenario.h2("Testing game reset")
scenario.p("Winner or draw confirmed, now resetting the game")
c1.confirm_and_reset()
scenario.verify(c1.data.nbMoves == 0)
scenario.verify(c1.data.winner == 0)
scenario.verify(not c1.data.draw)
c2.confirm_and_reset()
scenario.verify(c2.data.nbMoves == 0)
scenario.verify(c2.data.winner == 0)
scenario.verify(not c2.data.draw)
The contract for our TicTacToe game on Tezos is written in the SmartPy language. It consists of two main parts: the state of the contract, and the logic of the game.
The state of the contract is initialized in the **init
function. It includes:
nbMoves
: This is a counter for the number of moves made in the game. It starts at zero.winner
: This keeps track of the winner of the game. Initially, it’s zero, indicating no winner.draw
: This is a flag indicating whether the game has ended in a draw. Initially, it’s False.deck
: This is a 3x3 grid representing the TicTacToe board. All spots on the board are initially empty, represented by zeroes.nextPlayer
: This indicates whose turn it is to play. The game starts with player 1, so it’s initially set to 1.The game logic is encapsulated in the play
function. It performs several checks to ensure a valid move:
nextPlayer
.nbMoves
, switches the nextPlayer
, and checks if the move has resulted in a win or a draw.The win condition is checked across the row and column of the latest move, as well as the two diagonals.
If all spots on the board are filled and no player has won (i.e., nbMoves
equals 9 and winner
is still 0), the game is declared a draw.
The checkLine
function is used to check if any player has won. It checks if all spots in a line (which can be a row, a column, or a diagonal) are filled by the same player. If so, that player is declared the winner.
Interactions with the contract are represented as transactions. When a player makes a move by calling the play
function, it generates a transaction. This transaction gets logged and can be seen in the right panel of the SmartPy IDE:
An unsuccessful or invalid move would also generate a transaction but with an error indication:
The first move in our TicTacToe game is relatively straightforward since the game board is empty. However, things get more interesting with the second move and subsequent moves. These moves not only add pieces to the game board but also invoke the game’s logic to check for potential winners.
After the first move, the nextPlayer
value switches to player 2. Now, the play
function validates player 2’s move. Similar checks are performed to ensure the move is valid, i.e., the selected grid spot is within the boundaries and is empty.
As each player makes a move, the game’s state evolves. The nbMoves
increases, the nextPlayer
toggles, and the deck
is updated. Also, after each move, the contract checks if there’s a winner or if it’s a draw.
For example, after the first player makes a move in the center of the board at i=1, j=1
, the second player can play at a different spot, say i=1, j=2
. Both of these moves would be validated and executed successfully, with the corresponding transactions being generated.
Subsequent moves continue in a similar manner. Each player takes turns to play, choosing an empty spot on the board. After each move, the contract checks for any winning condition. If a player fills an entire row, column, or diagonal with their symbol, the game ends and that player is declared the winner. The winner
variable in the contract’s state would be updated accordingly.
It’s important to note that once a player has won, no further moves are valid. Any attempt to make a move after the game has ended would be considered invalid and the corresponding transaction would fail.
In some games, it’s possible that no player achieves a winning condition even after the entire game board is filled. This results in a draw. The contract has been designed to handle this situation as well.
If all spots on the board are filled (nbMoves
equals 9) and no player has won (winner
remains 0), the game is declared a draw. The draw
flag in the contract’s state is set to True, indicating the game ended in a draw. Once again, no further moves are valid after this point. Any attempts to make a move after a draw would also fail.
The second part of the TicTacToe contract’s test scenario demonstrates this drawing scenario. It simulates a series of moves that result in a draw and verifies that the contract handles it correctly.