Bài học 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(真),表示游戏以平局结束。同样,在此点之后,任何后续棋步都是无效的。

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

Tuyên bố từ chối trách nhiệm
* Đầu tư tiền điện tử liên quan đến rủi ro đáng kể. Hãy tiến hành một cách thận trọng. Khóa học không nhằm mục đích tư vấn đầu tư.
* Khóa học được tạo bởi tác giả đã tham gia Gate Learn. Mọi ý kiến chia sẻ của tác giả không đại diện cho Gate Learn.
Danh mục
Bài học 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(真),表示游戏以平局结束。同样,在此点之后,任何后续棋步都是无效的。

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

Tuyên bố từ chối trách nhiệm
* Đầu tư tiền điện tử liên quan đến rủi ro đáng kể. Hãy tiến hành một cách thận trọng. Khóa học không nhằm mục đích tư vấn đầu tư.
* Khóa học được tạo bởi tác giả đã tham gia Gate Learn. Mọi ý kiến chia sẻ của tác giả không đại diện cho Gate Learn.
It seems that you are attempting to access our services from a Restricted Location where Gate.io is unable to provide services. We apologize for any inconvenience this may cause. Currently, the Restricted Locations include but not limited to: the United States of America, Canada, Cambodia, Thailand, Cuba, Iran, North Korea and so on. For more information regarding the Restricted Locations, please refer to the User Agreement. Should you have any other questions, please contact our Customer Support Team.