Урок 3

在Tezos上开发井字棋游戏

区块链游戏世界为开发者提供了诸多机会。它提供了一种独特而创新的方式,将去中心化和透明的机制整合到游戏中。通过在区块链上开发游戏,我们可以整合安全透明的交易、游戏内资产的所有权等功能。在本课中,我们将在Tezos区块链上开发经典的井字棋游戏,开启区块链游戏领域的开发之旅,帮助大家了解区块链游戏的游戏逻辑和状态管理。

首先,我们来具体分析一下这个井字棋游戏合约:

合约结构

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)

我们在Tezos上的井字棋游戏合约是用SmartPy语言编写的。它包含两个主要部分组成:合约状态和游戏逻辑。

合约状态

合约的状态通过**init函数初始化。它包括:

  • nbMoves:这是游戏中移动次数的计数器。初始值为零。
  • winner:此变量用于跟踪游戏的获胜者。初始值为零,表示没有获胜者。
  • draw:指示游戏是否以平局结束的标志。初始状态为False(假)。
  • deck:这是一个3x3的网格,代表井字棋棋盘。棋盘上的所有点最初都为空,用零表示。
  • nextPlayer:表示轮到哪个玩家下棋。游戏从玩家1开始,所以最初设置为1。

游戏逻辑

游戏逻辑包含在play函数中。它会执行多项检查以确保有效的移动:

  • 确认没有玩家赢得比赛,游戏也没有以平局结束。
  • 验证玩家选择的网格位置的索引是否在网格的边界内。
  • 确保正在下棋的玩家与nextPlayer匹配。
  • 确保网格上选择的位置为空。
    一旦落棋,游戏逻辑将会递增nbMoves,切换nextPlayer,并检查这一步棋是否导致胜利或平局。

胜利条件将在最新棋步所在的行、列以及两个对角线上进行检查。

如果棋盘上的所有点都被填满且没有玩家获胜(即nbMoves等于9且winner仍然为0),则宣布游戏为平局。

检查是否获胜

checkLine函数用于检查是否有玩家获胜。它检查一条线(包括行、列或对角线)上的所有点是否由同一玩家填充。如果是,则宣布该玩家为获胜者。

与合约交互

与合约的交互用交易来表示。当玩家通过调用play函数进行移动时,会生成一笔交易。这笔交易被记录下来,可以在SmartPy IDE的右侧面板中看到:

不成功或无效的移动也会生成交易,但带有错误指示:

第二步及之后的棋步

在我们的井字棋游戏中,第一步相对简单,因为棋盘是空的。然而,从第二步开始,棋步将变得比较有趣了,因为它们不仅会向棋盘上添加棋子,还会调用游戏逻辑来检查可能的获胜者。

在第一步之后,nextPlayer值切换到玩家2。现在,play函数会验证玩家2的棋步。合约会执行类似的检查以确保棋步是有效的,即所选网格点在边界内并且为空。

每个玩家落子后,游戏的状态会发生变化。nbMoves会增加,nextPlayer会切换, deck也会更新。此外,在每步棋之后,合约都会检查是否有获胜者或是否平局。

例如,第一个玩家在棋盘的中央i=1, j=1进行了一步棋,第二位玩家可以在不同的位置进行下一步,如i=1, j=2。这两个棋步都会经过验证并成功执行,并生成相应的交易。

游戏进展

后续的棋步以类似的方式进行。每个玩家选择棋盘上的一个空点,轮流落子。在每一次落子之后,合约都会检查是否存在获胜条件。如果一名玩家用他的棋子填满一整行、整列或整个对角线,则游戏结束,该玩家获胜。合约状态中的winner变量将相应更新。

需要注意的是,一旦有玩家获胜,就不再允许继续落子。在游戏结束后尝试进行棋步都将被视为无效,相应的交易也将失败。

平局

在某些游戏中,即使整个游戏棋盘都被填满,也有可能没有玩家达到获胜条件,这将导致平局。合约的设计中已经包含了处理这种情况的方案。

如果棋盘上的所有点位都被填满(nbMoves等于9)并且没有玩家获胜(winner仍然为0),则游戏为平局。合约状态下的draw标识为True(真),表示游戏以平局结束。同样,在此点之后,任何后续棋步都是无效的。

井字棋游戏合约测试场景的第二部分对该平局场景进行了演示。它模拟了一系列导致平局的棋步,并验证了合约是否正确处理它。

Отказ от ответственности
* Криптоинвестирование сопряжено со значительными рисками. Будьте осторожны. Курс не является инвестиционным советом.
* Курс создан автором, который присоединился к Gate Learn. Мнение автора может не совпадать с мнением Gate Learn.
Каталог
Урок 3

在Tezos上开发井字棋游戏

区块链游戏世界为开发者提供了诸多机会。它提供了一种独特而创新的方式,将去中心化和透明的机制整合到游戏中。通过在区块链上开发游戏,我们可以整合安全透明的交易、游戏内资产的所有权等功能。在本课中,我们将在Tezos区块链上开发经典的井字棋游戏,开启区块链游戏领域的开发之旅,帮助大家了解区块链游戏的游戏逻辑和状态管理。

首先,我们来具体分析一下这个井字棋游戏合约:

合约结构

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)

我们在Tezos上的井字棋游戏合约是用SmartPy语言编写的。它包含两个主要部分组成:合约状态和游戏逻辑。

合约状态

合约的状态通过**init函数初始化。它包括:

  • nbMoves:这是游戏中移动次数的计数器。初始值为零。
  • winner:此变量用于跟踪游戏的获胜者。初始值为零,表示没有获胜者。
  • draw:指示游戏是否以平局结束的标志。初始状态为False(假)。
  • deck:这是一个3x3的网格,代表井字棋棋盘。棋盘上的所有点最初都为空,用零表示。
  • nextPlayer:表示轮到哪个玩家下棋。游戏从玩家1开始,所以最初设置为1。

游戏逻辑

游戏逻辑包含在play函数中。它会执行多项检查以确保有效的移动:

  • 确认没有玩家赢得比赛,游戏也没有以平局结束。
  • 验证玩家选择的网格位置的索引是否在网格的边界内。
  • 确保正在下棋的玩家与nextPlayer匹配。
  • 确保网格上选择的位置为空。
    一旦落棋,游戏逻辑将会递增nbMoves,切换nextPlayer,并检查这一步棋是否导致胜利或平局。

胜利条件将在最新棋步所在的行、列以及两个对角线上进行检查。

如果棋盘上的所有点都被填满且没有玩家获胜(即nbMoves等于9且winner仍然为0),则宣布游戏为平局。

检查是否获胜

checkLine函数用于检查是否有玩家获胜。它检查一条线(包括行、列或对角线)上的所有点是否由同一玩家填充。如果是,则宣布该玩家为获胜者。

与合约交互

与合约的交互用交易来表示。当玩家通过调用play函数进行移动时,会生成一笔交易。这笔交易被记录下来,可以在SmartPy IDE的右侧面板中看到:

不成功或无效的移动也会生成交易,但带有错误指示:

第二步及之后的棋步

在我们的井字棋游戏中,第一步相对简单,因为棋盘是空的。然而,从第二步开始,棋步将变得比较有趣了,因为它们不仅会向棋盘上添加棋子,还会调用游戏逻辑来检查可能的获胜者。

在第一步之后,nextPlayer值切换到玩家2。现在,play函数会验证玩家2的棋步。合约会执行类似的检查以确保棋步是有效的,即所选网格点在边界内并且为空。

每个玩家落子后,游戏的状态会发生变化。nbMoves会增加,nextPlayer会切换, deck也会更新。此外,在每步棋之后,合约都会检查是否有获胜者或是否平局。

例如,第一个玩家在棋盘的中央i=1, j=1进行了一步棋,第二位玩家可以在不同的位置进行下一步,如i=1, j=2。这两个棋步都会经过验证并成功执行,并生成相应的交易。

游戏进展

后续的棋步以类似的方式进行。每个玩家选择棋盘上的一个空点,轮流落子。在每一次落子之后,合约都会检查是否存在获胜条件。如果一名玩家用他的棋子填满一整行、整列或整个对角线,则游戏结束,该玩家获胜。合约状态中的winner变量将相应更新。

需要注意的是,一旦有玩家获胜,就不再允许继续落子。在游戏结束后尝试进行棋步都将被视为无效,相应的交易也将失败。

平局

在某些游戏中,即使整个游戏棋盘都被填满,也有可能没有玩家达到获胜条件,这将导致平局。合约的设计中已经包含了处理这种情况的方案。

如果棋盘上的所有点位都被填满(nbMoves等于9)并且没有玩家获胜(winner仍然为0),则游戏为平局。合约状态下的draw标识为True(真),表示游戏以平局结束。同样,在此点之后,任何后续棋步都是无效的。

井字棋游戏合约测试场景的第二部分对该平局场景进行了演示。它模拟了一系列导致平局的棋步,并验证了合约是否正确处理它。

Отказ от ответственности
* Криптоинвестирование сопряжено со значительными рисками. Будьте осторожны. Курс не является инвестиционным советом.
* Курс создан автором, который присоединился к Gate Learn. Мнение автора может не совпадать с мнением Gate Learn.