zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Python实现四子棋(四连环)游戏

Python游戏 实现 连环
2023-09-27 14:19:58 时间

游戏说明

  1. 四连环是一个双人游戏,两位玩家轮流将自己的棋子放在一个6行8列的筒状棋盘上,棋盘的列号从左至右依次为1,2,3,…,8。下棋时每个玩家只需选择列号,棋子则自动落在该列的底部,如果该列已经有棋子,则新投的棋子叠放在已有的棋子之上。游戏的目标是在对手之前将自己的4个相邻棋子放在一行、一列或一条斜线上。
  2. 程序扮演一位玩家(劳拉),用户作为其对手。程序按照某种策略选择要放棋子的列,用户则是手工输入要放棋子的列。假设用户输入的都是整数,当用户输入的列号不在1到8之间时,程序会提示用户,并让用户重新输入;当用户输入的列号已经放满了棋子时,程序也会提示用户,并让用户重新输入。
  3. 由程序判断双方的胜负,分出胜负则程序结束。当出现棋盘满了而无法放棋子的时候,判为平局。
  4. 程序按照什么策略来选择要放棋子的列呢?一种最简单的策略就是:首先忽略那些已经放满棋子的列,在剩余的可用列中随机选择一列。当然你也可以引入一些更加智能的策略。请在py文件中的合适位置用文字描述清楚你的实现中程序所采用的策略。
  5. 程序选择的策略的智能程度将作为本次project的额外加分(bonus的取值范围[0,15])

游戏效果展示

Hi,我是劳拉,我们来玩一局四连环。我用0型棋子,你用X型棋子。
游戏规则:双方轮流选择棋盘的列号放进自己的棋子,
        若棋盘上有四颗相同型号的棋子在一行、一列或一条斜线上连接起来,
        则使用该型号棋子的玩家就赢了!
开始了!这是棋盘的初始状态:
 1 2 3 4 5 6 7 8
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
轮到我了,我把0棋子放在第4列
 1 2 3 4 5 6 7 8
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | |0| | | | |
轮到你了,你放X棋子,请选择列号(1-8):1
 1 2 3 4 5 6 7 8
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
|X| | |0| | | | |
轮到我了,我把0棋子放在第2列
 1 2 3 4 5 6 7 8
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
|X|0| |0| | | | |
轮到你了,你放X棋子,请选择列号(1-8):1
 1 2 3 4 5 6 7 8
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
|X| | | | | | | |
|X|0| |0| | | | |
轮到我了,我把0棋子放在第8列
 1 2 3 4 5 6 7 8
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
|X| | | | | | | |
|X|0| |0| | | |0|
轮到你了,你放X棋子,请选择列号(1-8):1
 1 2 3 4 5 6 7 8
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
|X| | | | | | | |
|X| | | | | | | |
|X|0| |0| | | |0|
轮到我了,我把0棋子放在第7列
 1 2 3 4 5 6 7 8
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
|X| | | | | | | |
|X| | | | | | | |
|X|0| |0| | |0|0|
轮到你了,你放X棋子,请选择列号(1-8):1
 1 2 3 4 5 6 7 8
| | | | | | | | |
| | | | | | | | |
|X| | | | | | | |
|X| | | | | | | |
|X| | | | | | | |
|X|0| |0| | |0|0|
******* 好吧,你赢了!^_*

Process finished with exit code 0

Python代码实现

本版本电脑是随机落子,并没有实现智能落子

首先导入 numpy 模块,需要用到它的一些特性,会方便一点

import numpy as np

定义四连环游戏类:

class ConnectFour(object):
    _board = np.full((6, 8), ' ')  # 棋盘(6x8), ' ' 表示没有棋子, 'X'表示玩家棋子,'0'表示电脑棋子
    _column_numbers = [5] * 8  # 每列当前落子应放的index,例如 [-1,0,1,2,3,4,5,6] 表示第0列已经放满,第1列还有一个位置,如果放的话就放在顶部,也就是index为0的这个位置
    _flag = False  # 当前是否轮到玩家

定义打印棋盘方法,这里使用了python序列解包的特性:

    def print_board(self):
        """
        打印当前的棋盘情况
        """
        print('', *range(1, 9), sep=' ')  # 打印 [1,8] 这几个数字,每个数字中间空一格
        for row in self._board:
            """循环打印每行的棋盘情况"""
            print('|', end='')  # 打印最左边的”|“
            print(*row, sep='|', end='|\n')  # 打印该行的所有棋子,每个棋子中间隔一个”|“,最后以”|“结尾

定义检测本次落子是否可以分出胜负的方法:

    def _check_victory(self, r: int, c: int):
        """
        检测本次落子是否可以分出胜负
        :param r: 本次落子的行index,范围[0,5]
        :param c: 本次落子的列index,范围[0,7]
        :return: 如果胜利,则返回True,否则返回False
        """
        piece = self._board[r, c]  # 获取本次落的是什么棋子

检测行是否满足胜利条件,这里的思路是:计算当前落子行是否有连续的4个棋子都是piece(本次落的棋子)。从第c-3 列开始,计算其后面的4位是否一样。按照该逻辑,一直遍历到c列(当前落子列)

        """检测行是否满足胜利条件"""
        for i in range(max(c - 3, 0), c + 1): # 从第0列开始遍历,遍历到第c列
            if np.sum(self._board[r, i:min(i + 4, 8)] == piece) >= 4: # 计算 [i, i+4] 列的值是否都为piece,如果是,则说明行满足胜利条件
                return True

检测列是否满足胜利条件,与检测行思路一致:

        """检测列是否满足胜利条件,与检测行思路一致"""
        for i in range(max(r - 3, 0), r + 1): # 从第r-3行开始,遍历到第r行
            if np.sum(self._board[i:min(i + 4, 6), c] == piece) >= 4:
                return True

检测左斜线是否满足胜利条件,思路为:从左上角往右下角开始遍历,查看是否有连续的4个都为piece

        """检测左斜线是否满足胜利条件"""
        r_i, c_i = r - min(r, c), c - min(r, c)  # 将坐标移动到左上角
        while r_i < 6 and c_i < 8:  # 当坐标到达右下角时,停止
            r_index = range(r_i, min(r_i + 4, 6))  # 计算往右下角移动时,都有哪些行index
            c_index = range(c_i, min(c_i + 4, 8))  # 计算往右下角移动时,都有哪些列index
            min_len = min(len(r_index), len(c_index))  # 由于行和列其中某一个会先达到停止,所以 r_index和c_index的数量不一定一致,所以取最小的
            r_index = list(r_index[:min_len])  # 获取到最终的行index
            c_index = list(c_index[:min_len])  # 获取到最终的列index
            if np.sum(self._board[r_index, c_index] == piece) >= 4:  # 计算4个棋子是否都是piece,如果是,则说明满足条件
                return True
            r_i += 1 # 向右下角挪一位
            c_i += 1

检测右斜线是否满足胜利条件,这里借用了左斜线的逻辑,思路为:先将右斜线转为左斜线求坐标位置,然后再讲左线线转回右斜线求出最终的坐标位置

        """检测右斜线是否满足胜利条件"""
        c = 7 - c  # 将 c 变成与其纵坐标对称的位置,这样就可以继续利用上面的逻辑进行计算了
        r_i, c_i = r - min(r, c), c - min(r, c)
        while r_i < 6 and c_i < 8:
            r_index = range(r_i, min(r_i + 4, 6))
            c_index = range(c_i, min(c_i + 4, 8))
            min_len = min(len(r_index), len(c_index))
            r_index = list(r_index[:min_len])
            c_index = list(c_index[:min_len])
            c_index = 7 - np.array(c_index)  # 将c_index再次沿纵坐标翻转,恢复原本的位置
            if np.sum(self._board[r_index, c_index] == piece) >= 4:
                return True
            r_i += 1
            c_i += 1

最终,如果本次落子不能获得胜利,则返回False

        return False

定义通用落子方法:

    def _play(self, n: int, piece: str):
        """
        进行落子
        :param n: 本次要落子的列,范围[0,7]
        :param piece: 本次要落的棋子, piece in (0, X)
        """
        r, c = self._column_numbers[n], n  # 求出当前落子的坐标
        self._board[r, c] = piece  # 将该坐标放到棋盘中
        self._column_numbers[n] -= 1 # 将该列的下次落子的index减1
        self.print_board() # 打印棋盘

        """判断落子是否可以胜利"""
        if self._check_victory(r, c):
            print('******* 好吧,你赢了!^_*') if piece == 'X' else print('******* 哈哈,我赢了!^_^')
            exit(0)

        if sum(self._column_numbers) <= -8: # 如果所有的列都放慢了,即所有的列都是-1,则表示棋盘已经放满
            print("棋盘放满了,游戏结束!")
            exit(0)

电脑(劳拉)落子:

    def laura_play(self):
        n = np.random.randint(0, 8)  # 生成[0,7]的随机数
        if self._column_numbers[n] < 0:
            self.laura_play()  # 如果随机数那列已放满,则重新尝试
            return

        print("轮到我了,我把0棋子放在第%s列" % (n + 1))
        self._play(n, '0')  # 落子

玩家落子:

    def i_play(self):
        print("轮到你了,你放X棋子,请选择列号(1-8):", end='')
        n = input()

        if not n.isnumeric():
            print("请输入(1-8)的数字!")
            self.i_play()
            return

        n = int(n) - 1  # 玩家输入的是[1,8]的,修改索引为[0.7]

        if n < 0 or n > 7:
            print("请输入(1-8)的数字!")
            self.i_play()
            return

        if self._column_numbers[n] < 0:
            print('该列已放满,请重新选择!')
            self.i_play()
            return

        self._play(n, 'X')

开始游戏方法:

    def start_play(self):
        print("""
Hi,我是劳拉,我们来玩一局四连环。我用0型棋子,你用X型棋子。
游戏规则:双方轮流选择棋盘的列号放进自己的棋子,
        若棋盘上有四颗相同型号的棋子在一行、一列或一条斜线上连接起来,
        则使用该型号棋子的玩家就赢了!
开始了!这是棋盘的初始状态:""")
        game.print_board()  # 打印初始棋盘

        while True: # 无限循环,直到游戏结束
            self.i_play() if self._flag else self.laura_play()
            self._flag = not self._flag # 交换旗手

定义main方法,开始游戏:

if __name__ == '__main__':
    game = ConnectFour()
    game.start_play()

Python完整代码

import numpy as np


class ConnectFour(object):
    _board = np.full((6, 8), ' ')  # 棋盘(6x8), ' ' 表示没有棋子, 'X'表示玩家棋子,'0'表示电脑棋子
    _column_numbers = [5] * 8  # 每列当前落子应放的index,例如 [-1,0,1,2,3,4,5,6] 表示第0列已经放满,第1列还有一个位置,如果放的话就放在顶部,也就是index为0的这个位置
    _flag = False  # 当前是否轮到玩家

    def print_board(self):
        """
        打印当前的棋盘情况
        """
        print('', *range(1, 9), sep=' ')  # 打印 [1,8] 这几个数字,每个数字中间空一格
        for row in self._board:
            """循环打印每行的棋盘情况"""
            print('|', end='')  # 打印最左边的”|“
            print(*row, sep='|', end='|\n')  # 打印该行的所有棋子,每个棋子中间隔一个”|“,最后以”|“结尾

    def _check_victory(self, r: int, c: int):
        """
        检测本次落子是否可以分出胜负
        :param r: 本次落子的行index,范围[0,5]
        :param c: 本次落子的列index,范围[0,7]
        :return: 如果胜利,则返回True,否则返回False
        """
        piece = self._board[r, c]  # 获取本次落的是什么棋子

        """检测行是否满足胜利条件"""
        for i in range(max(c - 3, 0), c + 1):  # 从第0列开始遍历,遍历到第c列
            if np.sum(self._board[r, i:min(i + 4, 8)] == piece) >= 4:  # 计算 [i, i+4] 列的值是否都为piece,如果是,则说明行满足胜利条件
                return True

        """检测列是否满足胜利条件,与检测行思路一致"""
        for i in range(max(r - 3, 0), r + 1):  # 从第r-3行开始,遍历到第r行
            if np.sum(self._board[i:min(i + 4, 6), c] == piece) >= 4:
                return True

        """检测左斜线是否满足胜利条件"""
        r_i, c_i = r - min(r, c), c - min(r, c)  # 将坐标移动到左上角
        while r_i < 6 and c_i < 8:  # 当坐标到达右下角时,停止
            r_index = range(r_i, min(r_i + 4, 6))  # 计算往右下角移动时,都有哪些行index
            c_index = range(c_i, min(c_i + 4, 8))  # 计算往右下角移动时,都有哪些列index
            min_len = min(len(r_index), len(c_index))  # 由于行和列其中某一个会先达到停止,所以 r_index和c_index的数量不一定一致,所以取最小的
            r_index = list(r_index[:min_len])  # 获取到最终的行index
            c_index = list(c_index[:min_len])  # 获取到最终的列index
            if np.sum(self._board[r_index, c_index] == piece) >= 4:  # 计算4个棋子是否都是piece,如果是,则说明满足条件
                return True
            r_i += 1  # 向右下角挪一位
            c_i += 1

        """检测右斜线是否满足胜利条件"""
        c = 7 - c  # 将 c 变成与其纵坐标对称的位置,这样就可以继续利用上面的逻辑进行计算了
        r_i, c_i = r - min(r, c), c - min(r, c)
        while r_i < 6 and c_i < 8:
            r_index = range(r_i, min(r_i + 4, 6))
            c_index = range(c_i, min(c_i + 4, 8))
            min_len = min(len(r_index), len(c_index))
            r_index = list(r_index[:min_len])
            c_index = list(c_index[:min_len])
            c_index = 7 - np.array(c_index)  # 将c_index再次沿纵坐标翻转,恢复原本的位置
            if np.sum(self._board[r_index, c_index] == piece) >= 4:
                return True
            r_i += 1
            c_i += 1

        return False

    def _play(self, n: int, piece: str):
        """
        进行落子
        :param n: 本次要落子的列,范围[0,7]
        :param piece: 本次要落的棋子, piece in (0, X)
        """
        r, c = self._column_numbers[n], n  # 求出当前落子的坐标
        self._board[r, c] = piece  # 将该坐标放到棋盘中
        self._column_numbers[n] -= 1  # 将该列的下次落子的index减1
        self.print_board()  # 打印棋盘

        """判断落子是否可以胜利"""
        if self._check_victory(r, c):
            print('******* 好吧,你赢了!^_*') if piece == 'X' else print('******* 哈哈,我赢了!^_^')
            exit(0)

        if sum(self._column_numbers) <= -8:  # 如果所有的列都放慢了,即所有的列都是-1,则表示棋盘已经放满
            print("棋盘放满了,游戏结束!")
            exit(0)

    def laura_play(self):
        n = np.random.randint(0, 8)  # 生成[0,7]的随机数
        if self._column_numbers[n] < 0:
            self.laura_play()  # 如果随机数那列已放满,则重新尝试
            return

        print("轮到我了,我把0棋子放在第%s列" % (n + 1))
        self._play(n, '0')  # 落子

    def i_play(self):
        print("轮到你了,你放X棋子,请选择列号(1-8):", end='')
        n = input()

        if not n.isnumeric():
            print("请输入(1-8)的数字!")
            self.i_play()
            return

        n = int(n) - 1  # 玩家输入的是[1,8]的,修改索引为[0.7]

        if n < 0 or n > 7:
            print("请输入(1-8)的数字!")
            self.i_play()
            return

        if self._column_numbers[n] < 0:
            print('该列已放满,请重新选择!')
            self.i_play()
            return

        self._play(n, 'X')

    def start_play(self):
        print("""
Hi,我是劳拉,我们来玩一局四连环。我用0型棋子,你用X型棋子。
游戏规则:双方轮流选择棋盘的列号放进自己的棋子,
        若棋盘上有四颗相同型号的棋子在一行、一列或一条斜线上连接起来,
        则使用该型号棋子的玩家就赢了!
开始了!这是棋盘的初始状态:""")
        game.print_board()  # 打印初始棋盘

        while True: # 无限循环,直到游戏结束
            self.i_play() if self._flag else self.laura_play()
            self._flag = not self._flag # 交换旗手


if __name__ == '__main__':
    game = ConnectFour()
    game.start_play()