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