介绍
本项目是经典四子棋游戏的 Python 实现,玩家可以与 AI 对战。它使用 Pygame 库来实现游戏界面和控制。AI 的决策基于蒙特卡洛树搜索算法,并且难度级别可调,允许玩家挑战更智能的 AI 对手。
关键概念:
- 使用 Pygame 进行游戏开发。
- 实现蒙特卡洛树搜索算法用于 AI 决策。
👀 预览

🎯 任务
在本项目中,你将学习:
- 如何使用 Pygame 构建游戏。
- 如何实现蒙特卡洛树搜索算法用于 AI 决策。
- 如何定制和提高 AI 的难度级别。
- 如何创建一个有趣且互动的四子棋游戏用于人机对战。
🏆 成果
完成本项目后,你将能够:
- 使用 Python 和 Pygame 开发游戏。
- 理解蒙特卡洛树搜索算法的原理。
- 调整 AI 对手的难度以创建具有挑战性的游戏体验。
- 增强用户界面以使游戏体验更具吸引力。
开发准备
四子棋游戏在一个 7×6 的网格上进行。玩家轮流从一列的顶部放下棋子。棋子会落到该列最底部的空白位置。在一条直线上(水平、垂直或对角线)连接四个棋子的玩家赢得游戏。

在~/project目录下创建一个名为fourinrow.py的文件,用于存储此项目的代码。此外,我们需要安装 Pygame 库来实现游戏界面和支持操作。
cd ~/project
touch fourinrow.py
sudo pip install pygame
你可以在~/project/images目录中找到此项目所需的图像资源。
为了更好地理解本项目中的代码,建议你结合完整解决方案的代码一起学习。
初始化变量
使用的变量包括棋盘的宽度和高度(可修改以设计不同大小的棋盘)、难度级别、棋子大小以及一些坐标变量的设置。
在fourinrow.py文件中,输入以下代码:
import random, copy, sys, pygame
from pygame.locals import *
BOARDWIDTH = 7 ## 游戏棋盘的列数
BOARDHEIGHT = 6 ## 游戏棋盘的行数
assert BOARDWIDTH >= 4 and BOARDHEIGHT >= 4, '棋盘必须至少为 4x4。'
## python 的 assert 语句用于声明其给定的布尔表达式必须为真。
## 如果表达式为假,则引发异常。
DIFFICULTY = 2 ## 难度级别,计算机可考虑的步数
## 这里,2 表示考虑对手的 7 种可能走法以及如何应对这 7 种走法
SPACESIZE = 50 ## 棋子大小
FPS = 30 ## 屏幕刷新率,30 次/秒
WINDOWWIDTH = 640 ## 游戏屏幕的像素宽度
WINDOWHEIGHT = 480 ## 游戏屏幕的像素高度
XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * SPACESIZE) / 2) ## 网格左边缘的 X 坐标
YMARGIN = int((WINDOWHEIGHT - BOARDHEIGHT * SPACESIZE) / 2) ## 网格上边缘的 Y 坐标
BRIGHTBLUE = (0, 50, 255) ## 蓝色
WHITE = (255, 255, 255) ## 白色
BGCOLOR = BRIGHTBLUE
TEXTCOLOR = WHITE
RED ='red'
BLACK = 'black'
EMPTY = None
HUMAN = 'human'
COMPUTER = 'computer'
此外,我们还需要定义一些 Pygame 的全局变量。这些全局变量稍后将在各个模块中多次调用。其中许多是存储加载图像的变量,所以准备工作有点长,请耐心等待。
## 初始化 Pygame 模块
pygame.init()
## 创建一个 Clock 对象
FPSCLOCK = pygame.time.Clock()
## 创建游戏窗口
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
## 设置游戏窗口标题
pygame.display.set_caption(u'四子棋')
## Rect(left, top, width, height) 用于定义位置和大小
REDPILERECT = pygame.Rect(int(SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE)
## 在窗口中创建左下角和右下角的棋子
BLACKPILERECT = pygame.Rect(WINDOWWIDTH - int(3 * SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE)
## 加载红色棋子图像
REDTOKENIMG = pygame.image.load('images/4rowred.png')
## 将红色棋子图像缩放到 SPACESIZE
REDTOKENIMG = pygame.transform.smoothscale(REDTOKENIMG, (SPACESIZE, SPACESIZE))
## 加载黑色棋子图像
BLACKTOKENIMG = pygame.image.load('images/4rowblack.png')
## 将黑色棋子图像缩放到 SPACESIZE
BLACKTOKENIMG = pygame.transform.smoothscale(BLACKTOKENIMG, (SPACESIZE, SPACESIZE))
## 加载棋盘图像
BOARDIMG = pygame.image.load('images/4rowboard.png')
## 将棋盘图像缩放到 SPACESIZE
BOARDIMG = pygame.transform.smoothscale(BOARDIMG, (SPACESIZE, SPACESIZE))
## 加载人类获胜图像
HUMANWINNERIMG = pygame.image.load('images/4rowhumanwinner.png')
## 加载 AI 获胜图像
COMPUTERWINNERIMG = pygame.image.load('images/4rowcomputerwinner.png')
## 加载平局图像
TIEWINNERIMG = pygame.image.load('images/4rowtie.png')
## 返回一个 Rect 对象
WINNERRECT = HUMANWINNERIMG.get_rect()
## 将获胜图像在游戏窗口中居中
WINNERRECT.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))
## 加载用于用户指示的箭头图像
ARROWIMG = pygame.image.load('images/4rowarrow.png')
## 返回一个 Rect 对象
ARROWRECT = ARROWIMG.get_rect()
## 设置箭头图像的左位置
ARROWRECT.left = REDPILERECT.right + 10
## 将箭头图像与下方的红色棋子垂直对齐
ARROWRECT.centery = REDPILERECT.centery
为了更好地理解本项目中的代码,建议你结合完整解决方案的代码一起学习。
棋盘设计
首先,清空表示棋盘的二维列表,然后根据玩家和 AI 的走法在棋盘上相应位置设置颜色。
def drawBoard(board, extraToken=None):
## DISPLAYSURF 是我们的界面,在变量初始化模块中定义。
DISPLAYSURF.fill(BGCOLOR) ## 用蓝色填充游戏窗口背景颜色。
spaceRect = pygame.Rect(0, 0, SPACESIZE, SPACESIZE) ## 创建一个 Rect 实例。
for x in range(BOARDWIDTH):
## 确定每列每行中每个单元格的左上角位置坐标。
for y in range(BOARDHEIGHT):
spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE))
## 当 x = 0 且 y = 0 时,它是第一列第一行的第一个单元格。
if board[x][y] == RED: ## 如果单元格值为红色,
## 在游戏窗口的 spaceRect 内绘制一个红色棋子。
DISPLAYSURF.blit(REDTOKENIMG, spaceRect)
elif board[x][y] == BLACK: ## 否则,绘制一个黑色棋子。
DISPLAYSURF.blit(BLACKTOKENIMG, spaceRect)
## extraToken 是一个包含位置信息和颜色信息的变量。
## 它用于显示指定的棋子。
if extraToken!= None:
if extraToken['color'] == RED:
DISPLAYSURF.blit(REDTOKENIMG, (extraToken['x'], extraToken['y'], SPACESIZE, SPACESIZE))
elif extraToken['color'] == BLACK:
DISPLAYSURF.blit(BLACKTOKENIMG, (extraToken['x'], extraToken['y'], SPACESIZE, SPACESIZE))
## 绘制棋子面板。
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE))
DISPLAYSURF.blit(BOARDIMG, spaceRect)
## 在游戏窗口的左下角和右下角绘制棋子。
DISPLAYSURF.blit(REDTOKENIMG, REDPILERECT) ## 左边红色棋子。
DISPLAYSURF.blit(BLACKTOKENIMG, BLACKPILERECT) ## 右边黑色棋子。
def getNewBoard():
board = []
for x in range(BOARDWIDTH):
board.append([EMPTY] * BOARDHEIGHT)
return board ## 返回包含 BOARDHEIGHT 个 None 值的棋盘列表。
在上述代码中,drawBoard()函数绘制棋盘和棋盘上的棋子。getNewBoard()函数返回一个新的棋盘数据结构。
用于最佳移动的 AI 算法
简要解释一下蒙特卡洛树搜索的思路:
使用一维蒙特卡洛方法来评估围棋棋盘。具体来说,当给定一个特定的棋盘局面时,程序会从当前局面的所有可用点中随机选择一个点,并在上面放置一枚棋子。这个随机选择可用点(掷点)的过程会重复进行,直到双方都没有可用点(游戏结束),然后将这个最终状态的胜负结果反馈回来,作为评估当前局面的依据。
在本项目中,AI 会不断选择不同的列,并评估双方获胜的结果。AI 最终会选择一个评估值更高的策略。
在查看下面的图片和文字之前,请先看一下末尾的代码,然后参考相应的解释。
观察下图中 AI 与玩家的对抗:

项目中的一些变量可以直观地反映 AI 棋子操作的过程:
PotentialMoves:返回一个列表,该列表表示将棋子移动到列表中的任何一列时 AI 获胜的可能性。值是从 -1 到 1 的随机数。当值为负数时,表示玩家可能在接下来的两步中获胜,值越小,玩家获胜的可能性越大。如果值为 0,表示玩家不会获胜,AI 也不会获胜。如果值为 1,表示 AI 可以获胜。
bestMoveFitness:适应度是从 PotentialMoves 中选择的最大值。
bestMoves:如果 PotentialMoves 中有多个最大值,这意味着当 AI 将棋子移动到这些值所在的列时,玩家获胜的机会最小。因此,这些列会被添加到 bestMoves 列表中。
column:当 bestMoves 中有多个值时,从 bestMoves 中随机选择一列作为 AI 的走法。如果只有一个值,column 就是这个唯一的值。
在项目中,通过打印这些 bestMoveFitness、bestMoves、column 和 potentialMoves,我们可以推断出上图中 AI 每一步的参数。
人工智能的走法
| 步骤 | potentialMoves | 最佳走法适应度 | bestMoves | 列 |
|---|---|---|---|---|
| 1 | [0, 0, 0, 0, 0, 0, 0] | 0 | [0, 1, 2, 3, 4, 5, 6] | 0 |
| 2 | [0, 0, 0, 0, 0, 0, 0] | 0 | [0, 1, 2, 3, 4, 5, 6] | 6 |
| 3 | [-0.12, -0.12, -0.12, 0, -0.12, -0.12, -0.12] | 0 | [3] | 3 |
| 4 | [-0.34, -0.22, 0, -0.34, -0.34, -0.22, -0.34] | 0 | [2] | 2 |

通过研究 AI 在第三步的选择,我们可以更好地理解该算法:
下图展示了 AI 的一些走法,显示了如果 AI 在第一列放置棋子,玩家可能的选择,以及 AI 下一步走法对玩家获胜机会的影响。通过这个搜索和迭代过程,AI 可以确定对手和自己在接下来两步中的获胜情况,并据此做出决策。

下图是计算 AI 适应度值的流程图。在本项目中,难度系数为 2,我们需要考虑(7^4 = 2041) 种情况:

从上面的流程图不难发现,如果 AI 的第一步棋子放在第 0、1、2、4、5 或 6 列,玩家总能将剩下的两枚棋子放在第 3 列并获胜。为了便于表达,我们用一个序列来表示各种组合,其中第一个元素表示 AI 的第一步走法,第二个数字表示玩家的回应,第三个数字表示 AI 的回应。“X”表示任何有效走法。因此,[0,0,x]=0,可以推断出当序列为[0,x<>3,x]时,玩家不会获胜。只有当玩家的第二步棋子在第 3 列,且 AI 的第二步走法不在第 3 列时,AI 才能获胜。因此,[0,x=3,x<>3] = -1,这样的情况有 6 种。最终结果是(0 + 0 +...(43次)-1*6)/7/7 = -0.12。
同理,其他四种情况的结果都是 -0.12。如果 AI 的第一步走法在第 3 列,玩家不会获胜,AI 也不会获胜,所以值为 0。AI 选择适应度值最高的走法,这意味着它会将棋子放在第 3 列。
同样的分析也适用于 AI 的后续走法。综上所述,AI 走法后玩家获胜的可能性越高,AI 的适应度值就越低,AI 会选择适应度值更高的走法来防止玩家获胜。当然,如果 AI 自己能获胜,它会优先选择能导致自己胜利的走法。
def getPotentialMoves(board, tile, lookAhead):
if lookAhead == 0 or isBoardFull(board):
'''
如果难度系数为 0 或棋盘已满,
返回一个所有值都为 0 的列表。这意味着
适应度值等于每列的潜在走法。
在这种情况下,AI 会随机放置棋子,失去其智能。
'''
return [0] * BOARDWIDTH
## 确定对手棋子的颜色
if tile == RED:
enemyTile = BLACK
else:
enemyTile = RED
potentialMoves = [0] * BOARDWIDTH
## 初始化一个潜在走法列表,所有值都设为 0
for firstMove in range(BOARDWIDTH):
## 遍历每一列,并将双方的任何走法都视为 firstMove
## 另一方的走法视为 counterMove
## 这里,我们的 firstMove 指的是 AI 的走法,对手的走法视为 counterMove
## 对棋盘进行深拷贝,以防止棋盘和 dupeBoard 之间相互影响
dupeBoard = copy.deepcopy(board)
if not isValidMove(dupeBoard, firstMove):
## 如果在 dupeBoard 中,在 firstMove 指定的列放置黑色棋子的走法无效
continue
## 继续下一个 firstMove
makeMove(dupeBoard, tile, firstMove)
## 如果是有效走法,设置相应网格的颜色
if isWinner(dupeBoard, tile):
## 如果 AI 获胜
potentialMoves[firstMove] = 1
## 获胜的棋子自动获得一个高值,以表明其获胜的机会
## 值越大,获胜的机会越高,对手获胜的机会越低
break
## 不干扰其他走法的计算
else:
if isBoardFull(dupeBoard):
## 如果 dupeBoard 中没有空网格
potentialMoves[firstMove] = 0
## 无法移动
else:
for counterMove in range(BOARDWIDTH):
## 考虑对手的走法
dupeBoard2 = copy.deepcopy(dupeBoard)
if not isValidMove(dupeBoard2, counterMove):
continue
makeMove(dupeBoard2, enemyTile, counterMove)
if isWinner(dupeBoard2, enemyTile):
potentialMoves[firstMove] = -1
## 如果玩家获胜,AI 在这一列的适应度值是最低的
break
else:
## 递归调用 getPotentialMoves
results = getPotentialMoves(dupeBoard2, tile, lookAhead - 1)
## 这里使用浮点数表示,以获得更准确的结果
## 这确保了 potentialMoves 中的值在 [-1, 1] 范围内
potentialMoves[firstMove] += (sum(results)*1.0 / BOARDWIDTH) / BOARDWIDTH
return potentialMoves
玩家操作
拖动棋子,确定棋子所在的方格,验证棋子,调用棋子下落函数,完成操作。
def getHumanMove(board, isFirstMove):
draggingToken = False
tokenx, tokeny = None, None
while True:
## 使用 pygame.event.get() 处理所有事件
for event in pygame.event.get():
if event.type == QUIT: ## 停止并退出
pygame.quit()
sys.exit()
elif event.type == MOUSEBUTTONDOWN and not draggingToken and REDPILERECT.collidepoint(event.pos):
## 如果事件类型是鼠标按下,draggingToken 为 True,并且鼠标点击位置在 REDPILERECT 内
draggingToken = True
tokenx, tokeny = event.pos
elif event.type == MOUSEMOTION and draggingToken: ## 如果红色棋子被拖动
tokenx, tokeny = event.pos ## 更新被拖动棋子的位置
elif event.type == MOUSEBUTTONUP and draggingToken:
## 如果鼠标松开,并且棋子被拖动
## 如果棋子被直接拖动到棋盘上方
if tokeny < YMARGIN and tokenx > XMARGIN and tokenx < WINDOWWIDTH - XMARGIN:
column = int((tokenx - XMARGIN) / SPACESIZE) ## 根据棋子的 x 坐标确定棋子将下落的列 (0,1...6)
if isValidMove(board, column): ## 如果棋子移动有效
"""
落入相应的空方格,
此函数仅显示下落效果
填充方格的棋子也可以通过以下代码在没有此函数的情况下实现
"""
animateDroppingToken(board, column, RED)
## 将空列中最底部的方格设置为红色
board[column][getLowestEmptySpace(board, column)] = RED
drawBoard(board) ## 在下落的方格中绘制红色棋子
pygame.display.update() ## 窗口更新
return
tokenx, tokeny = None, None
draggingToken = False
if tokenx!= None and tokeny!= None: ## 如果棋子被拖动,显示被拖动的棋子
drawBoard(board, {'x':tokenx - int(SPACESIZE / 2), 'y':tokeny - int(SPACESIZE / 2), 'color':RED})
## 调整 x,y 坐标,以便在拖动过程中鼠标始终位于棋子的中心位置
else:
drawBoard(board) ## 当移动无效时,鼠标松开后,因为棋盘上的所有值都是 None
## 调用 drawBoard 时,执行的操作是显示下方的两个棋子,这相当于将棋子返回到开始拖动的位置
if isFirstMove:
DISPLAYSURF.blit(ARROWIMG, ARROWRECT) ## AI 先走,显示提示操作图像
pygame.display.update()
FPSCLOCK.tick()
在上述代码中,getHumanMove()函数处理玩家的移动。animateDroppingToken()函数为棋子下落添加动画效果。getLowestEmptySpace()函数返回一列中最低的空位置。
人工智能操作
实现 AI 棋子在各自位置移动和落下的动画效果函数。
def animateComputerMoving(board, column):
x = BLACKPILERECT.left ## 底部黑色棋子的左坐标
y = BLACKPILERECT.top ## 底部黑色棋子的上坐标
speed = 1.0
while y > (YMARGIN - SPACESIZE): ## 当 y 值较大时,表示棋子在窗口下方
y -= int(speed) ## 持续减小 y,即棋子向上移动
speed += 0.5 ## 增加 y 减小的速度
drawBoard(board, {'x':x, 'y':y, 'color':BLACK})
## y 不断变化,持续绘制黑色棋子,产生持续上升的效果
pygame.display.update()
FPSCLOCK.tick()
## 当棋子上升到棋盘顶部时
y = YMARGIN - SPACESIZE ## 重置 y,使棋子底部与棋盘顶部对齐
speed = 1.0
while x > (XMARGIN + column * SPACESIZE): ## 当 x 大于目标列的 x 坐标时
x -= int(speed) ## 持续减小 x,即棋子向左移动
speed += 0.5
drawBoard(board, {'x':x, 'y':y, 'color':BLACK})
## 此时 y 坐标不变,即棋子水平移动到该列
pygame.display.update()
FPSCLOCK.tick()
## 黑色棋子落在计算出的空位置上
animateDroppingToken(board, column, BLACK)
从返回的 potentialMoves 列表中选择最高的数字作为适应度值,并从那些适应度值高的列中随机选择作为最终的移动目标。
def getComputerMove(board):
potentialMoves = getPotentialMoves(board, BLACK, DIFFICULTY) ## 潜在走法,一个包含 BOARDWIDTH 个值的列表
## 列表中的值与设置的难度级别相关
bestMoves = [] ## 创建一个空的 bestMoves 列表
bestMoveFitness = -1 ## 由于 potentialMoves 中的最小值是 -1,作为下限
print(bestMoveFitness)
for i in range(len(potentialMoves)):
if potentialMoves[i] > bestMoveFitness and isValidMove(board, i):
bestMoveFitness = potentialMoves[i] ## 不断更新 bestMoves,使 bestMoves 中的每个值都是最大的
## 同时确保走法有效。
for i in range(len(potentialMoves)):
if potentialMoves[i] == bestMoveFitness and isValidMove(board, i):
bestMoves.append(i) ## 列出所有棋子可以移动到的列。这个列表可能为空、只包含一个值或多个值。
print(bestMoves)
return random.choice(bestMoves) ## 从棋子可以移动到的列中随机选择一列作为目标走法。
棋子移动操作
通过不断改变棋子的相应坐标,实现下落的动画效果。
def getLowestEmptySpace(board, column):
## 返回一列中最低的空位置
for y in range(BOARDHEIGHT-1, -1, -1):
if board[column][y] == EMPTY:
return y
return -1
def makeMove(board, player, column):
lowest = getLowestEmptySpace(board, column)
if lowest!= -1:
board[column][lowest] = player
'''
将玩家(红色/黑色)分配到该列中最低的空位置。
因为棋子落在一列中最低的空位置,
所以被视为该位置的颜色。
'''
def animateDroppingToken(board, column, color):
x = XMARGIN + column * SPACESIZE
y = YMARGIN - SPACESIZE
dropSpeed = 1.0
lowestEmptySpace = getLowestEmptySpace(board, column)
while True:
y += int(dropSpeed)
dropSpeed += 0.5
if int((y - YMARGIN) / SPACESIZE) >= lowestEmptySpace:
return
drawBoard(board, {'x':x, 'y':y, 'color':color})
pygame.display.update()
FPSCLOCK.tick()
在上述代码中,makeMove()函数在棋盘上进行一步移动。animateDroppingToken()函数为棋子下落添加动画效果。getLowestEmptySpace()函数返回一列中最低的空位置。
一些判断功能
判断棋子移动的有效性,判断棋盘上是否还有空格。
def isValidMove(board, column):
## 判断棋子移动的有效性
if column < 0 or column >= (BOARDWIDTH) or board[column][0]!= EMPTY:
## 如果列小于 0 或大于 BOARDWIDTH,或者该列没有空格
return False
## 那么这是无效移动,否则是有效移动
return True
def isBoardFull(board):
## 如果网格中没有空格,返回 True
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
if board[x][y] == EMPTY:
return False
return True
在上述代码中,如果移动有效,isValidMove()函数返回 True。如果棋盘已满,isBoardFull()函数返回 True。
获胜条件判断
提供了几幅图,便于理解四种获胜条件。图中所示位置对应 x 和 y 的极值。

def isWinner(board, tile):
## 检查棋子的水平情况
for x in range(BOARDWIDTH - 3): ## x 取值为 0、1、2、3
for y in range(BOARDHEIGHT): ## 遍历所有行
## 如果 x = 0,检查第 y 行的前四个棋子是否都为同一棋子。这可用于遍历所有四子连珠的水平情况。如果任何 x、y 满足条件,即可判定为获胜
if board[x][y] == tile and board[x+1][y] == tile and board[x+2][y] == tile and board[x+3][y] == tile:
return True
## 检查棋子的垂直情况,与水平情况类似
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT - 3):
if board[x][y] == tile and board[x][y+1] == tile and board[x][y+2] == tile and board[x][y+3] == tile:
return True
## 检查棋子的左斜对角情况
for x in range(BOARDWIDTH - 3): ## x 取值为 0、1、2、3
for y in range(3, BOARDHEIGHT): ## 因为要形成左斜对角四子连珠,最下面的棋子必须至少离顶部四个方格,即 y >= 3
if board[x][y] == tile and board[x+1][y-1] == tile and board[x+2][y-2] == tile and board[x+3][y-3] == tile: ## 判断左斜对角的四个棋子是否为同一颜色
return True
## 检查棋子的右斜对角情况,与左斜对角情况类似
for x in range(BOARDWIDTH - 3):
for y in range(BOARDHEIGHT - 3):
if board[x][y] == tile and board[x+1][y+1] == tile and board[x+2][y+2] == tile and board[x+3][y+3] == tile:
return True
return False
创建游戏主循环
最后,我们创建游戏主循环,以使游戏持续运行。
def main():
## 省略现有代码
isFirstGame = True ## 初始化 isFirstGame
while True: ## 使游戏持续运行
runGame(isFirstGame)
isFirstGame = False
def runGame(isFirstGame):
if isFirstGame:
## 在第一场游戏开始时
## 让 AI 先走,以便玩家观看游戏如何进行
turn = COMPUTER
showHelp = True
else:
## 对于第二场及以后的游戏,随机分配回合
if random.randint(0, 1) == 0:
turn = COMPUTER
else:
turn = HUMAN
showHelp = False
mainBoard = getNewBoard() ## 设置初始的空棋盘结构
while True: ## 游戏主循环
if turn == HUMAN: ## 如果轮到玩家
getHumanMove(mainBoard, showHelp) ## 调用玩家走棋的方法,详情见 getHumanMove 方法
if showHelp:
## 如果有提示图像,在 AI 走完第一步后关闭提示
showHelp = False
if isWinner(mainBoard, RED): ## 如果红色棋子(玩家)获胜
winnerImg = HUMANWINNERIMG ## 加载玩家获胜图像
break ## 退出循环
turn = COMPUTER ## 将第一步交给 AI
else:
## 如果轮到 AI
column = getComputerMove(mainBoard) ## 调用 AI 走棋的方法,详情见 getComputerMove 方法
print(column)
animateComputerMoving(mainBoard, column) ## 移动黑色棋子
makeMove(mainBoard, BLACK, column) ## 将该列中最底部的空槽设置为黑色
if isWinner(mainBoard, BLACK):
winnerImg = COMPUTERWINNERIMG
break
turn = HUMAN ## 切换到玩家回合
if isBoardFull(mainBoard):
## 如果棋盘已满,则为平局
winnerImg = TIEWINNERIMG
break
运行与测试
接下来,我们将运行程序并查看其运行情况。
cd ~/project
python fourinrow.py

总结
基于蒙特卡洛算法,本项目使用 Python 和 Pygame 模块实现了一款人机对战的棋类游戏。该项目让我们熟悉了在 Pygame 中创建实例和移动物体的基础知识,也让我们对蒙特卡洛算法的具体应用有了初步了解。



