四子连珠游戏 - 人机对战

PythonPythonBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

本项目是经典四子棋游戏的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玩家走法分析

项目中的一些变量可以直观地反映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每一步的参数。

✨ 查看解决方案并练习

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可以确定对手和自己在接下来两步中的获胜情况,并据此做出决策。

AI走法影响流程图

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

AI适应度计算流程图

从上面的流程图不难发现,如果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操作

实现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中创建实例和移动物体的基础知识,也让我们对蒙特卡洛算法的具体应用有了初步了解。