基于α-β剪枝的含禁手AI五子棋

前言:

正常的五子棋应当设有禁手规则,否则先手黑棋必赢,基于此点设计出一款包含禁手的AI五子棋项目,该项目代码已在github开源,感兴趣的友友可以自取试玩:ace-trump-tech/AI-Gomoku-with-Prohibition-Moves: 含禁手的AI五子棋 (github.com)

一、五子棋的禁手规则

禁手规则的设立有其深刻的原因。在五子棋中,黑方作为先手方,具有明显的优势。如果没有禁手规则,黑方可以通过一些特定的战术轻松取得胜利,使得比赛失去悬念和公平性。因此,禁手规则的出现,有效地限制了黑方的先手优势,增加了比赛的策略深度和观赏性。

该规则主要针对黑方,白方则没有禁手限制。黑方在一子落下时,如果同时形成了两个或两个以上的活三、双四、长连等特定棋形,那么这一步棋就被视为禁手。具体来说,禁手包括以下几种情况:

  1. 三三禁手:黑方一子落下同时形成两个或两个以上的活三。活三指的是两端都没有被封闭,且再走一着可以形成活四的三子连线。如果黑方一步棋形成了两个这样的活三,就构成了三三禁手。

  2. 四四禁手:黑方一子落下同时形成两个或两个以上的四。这里的"四"包括活四(两端都没有被封闭,且任一端落子都可以立即形成连五)和冲四(只有一端没有被封闭,且该端落子可以形成连五)。如果黑方一步棋形成了两个这样的四,就构成了四四禁手。

  3. 长连禁手:黑方一子落下形成超过五子的连续棋形,通常是六子或更多。这种情况下,黑方的这一步棋被视为禁手。

在实际比赛中,禁手的判定通常由裁判或计算机程序完成。当黑方出现禁手时,白方必须立即指出,否则禁手失效。如果白方没有及时指出,而是继续落子,那么禁手点将不再有效。此外,如果黑方在形成五连的同时也形成了禁手,那么禁手失效,黑方获胜。在实际比赛中,禁手的判定通常由裁判或计算机程序完成。当黑方出现禁手时,白方必须立即指出,否则禁手失效。如果白方没有及时指出,而是继续落子,那么禁手点将不再有效。此外,如果黑方在形成五连的同时也形成了禁手,那么禁手失效,黑方获胜。

二、α-β剪枝算法

α-β剪枝算法是一种在博弈树搜索中广泛应用的优化算法,它基于极小化极大算法(Minimax算法),通过巧妙地剪去一些不必要的分支来减少搜索空间,从而提高搜索效率。在两人对弈的游戏中,通常有两个玩家:极大方(Max)和极小方(Min)。极大方的目标是最大化自己的得分,而极小方则试图最小化极大方的得分。极小化极大算法的核心思想是递归地评估每个可能的走法,从当前局面开始,极大方会尝试所有可能的走法,计算每种走法的得分,并选择得分最高的走法;极小方则会尝试所有可能的走法,选择得分最低的走法。然而,这种算法的搜索空间非常庞大,尤其是对于复杂的棋类游戏,计算量巨大,因此需要一种更高效的搜索方法,这就是α-β剪枝算法。

α-β剪枝算法的核心在于引入了两个关键参数:α值和β值。α值是极大方的下界,表示极大方在当前搜索路径中已经找到的最优值;β值是极小方的上界,表示极小方在当前搜索路径中已经找到的最优值。这两个参数在搜索过程中不断更新,通过比较α值和β值来判断是否可以剪掉某些分支。具体来说,如果在搜索过程中,极小方发现某个分支的值小于等于α值,那么这个分支可以被剪掉,因为极大方不会选择这个分支;同样,如果极大方发现某个分支的值大于等于β值,那么这个分支也可以被剪掉,因为极小方不会选择这个分支。这种剪枝机制大大减少了需要搜索的节点数量,从而提高了搜索效率。

在实现α-β剪枝算法时,搜索过程是从根节点开始的,根节点通常是当前的游戏局面。在搜索开始时,α值被初始化为负无穷大,β值被初始化为正无穷大。然后,算法递归地遍历博弈树的每个节点。对于极大方,它会尝试所有可能的走法,计算每种走法的得分,并更新α值。如果某个分支的得分大于等于β值,那么后续的分支就可以被剪掉。对于极小方,它会尝试所有可能的走法,计算每种走法的得分,并更新β值。如果某个分支的得分小于等于α值,那么后续的分支也可以被剪掉。通过这种方式,算法逐步向上回溯,最终返回根节点的最优值。

为了更好地理解α-β剪枝算法,可以考虑一个简单的博弈树示例。假设有一个博弈树,极大方和极小方交替进行决策,每个节点的值表示该局面的得分。在搜索过程中,算法会从根节点开始,逐步向下搜索每个分支。当遇到极小方的节点时,算法会计算该节点的子节点的得分,并选择最小值作为该节点的得分;当遇到极大方的节点时,算法会计算该节点的子节点的得分,并选择最大值作为该节点的得分。在这个过程中,如果某个分支的得分已经超出了当前的α或β值范围,那么这个分支就可以被剪掉,从而避免了进一步的搜索。通过这种方式,算法最终能够找到最优的走法,同时大大减少了搜索的计算量。

α-β剪枝算法的优势在于它能够在不牺牲最优解的前提下,显著减少搜索空间。虽然剪枝减少了需要搜索的节点数量,但算法仍然能够找到最优的决策路径。然而,α-β剪枝算法的性能也受到搜索顺序的影响。如果搜索顺序不合理,可能会导致剪枝效果不佳。例如,如果先搜索到的分支值较差,可能会错过更好的剪枝机会。此外,尽管α-β剪枝算法提高了搜索效率,但对于非常复杂的棋类游戏,搜索空间仍然可能非常庞大,因此可能需要结合其他优化技术,如启发式评估函数,来进一步提高搜索效率。

在五子棋AI中,α-β剪枝算法可以有效地评估棋局的得分,并选择最优的落子点。通过结合启发式评估函数,如棋型评估、局势评估等,AI可以更快速地找到最佳走法,从而在对弈中表现出色。以下是一个简单的 Python 示例代码,展示了如何实现基于 α-β 剪枝算法的极小化极大搜索。这个示例使用了一个简单的博弈树结构,模拟了一个两人对弈的游戏场景:

python 复制代码
# 定义一个简单的博弈树节点类
class Node:
    def __init__(self, value=None):
        self.value = value  # 节点的值,表示该局面的得分
        self.children = []  # 子节点列表

    def add_child(self, child):
        self.children.append(child)

# α-β剪枝算法的实现
def alpha_beta(node, depth, alpha, beta, is_maximizing):
    # 如果到达叶子节点或达到搜索深度限制,返回节点的值
    if depth == 0 or not node.children:
        return node.value

    if is_maximizing:  # 极大方
        max_val = float('-inf')  # 初始化为负无穷大
        for child in node.children:
            val = alpha_beta(child, depth - 1, alpha, beta, False)
            max_val = max(max_val, val)
            alpha = max(alpha, max_val)
            if beta <= alpha:  # 剪枝条件
                break
        return max_val
    else:  # 极小方
        min_val = float('inf')  # 初始化为正无穷大
        for child in node.children:
            val = alpha_beta(child, depth - 1, alpha, beta, True)
            min_val = min(min_val, val)
            beta = min(beta, min_val)
            if beta <= alpha:  # 剪枝条件
                break
        return min_val

# 构建一个简单的博弈树
root = Node()
child1 = Node(3)
child2 = Node(5)
child3 = Node(2)
child4 = Node(9)
child5 = Node(1)
child6 = Node(7)

root.add_child(child1)
root.add_child(child2)
root.add_child(child3)

child1.add_child(child4)
child1.add_child(child5)
child2.add_child(child6)

# 调用 α-β 剪枝算法
result = alpha_beta(root, 2, float('-inf'), float('inf'), True)
print("最优值:", result)

在示例中,使用 Node 类来表示博弈树的节点,每个节点可以有多个子节点,其构建了一个简单的博弈树,包含一个根节点和若干子节点,每个节点的值表示该局面的得分。

三、UI界面设计

1.核心功能设计

在设计基于α-β剪枝的含禁手AI五子棋的用户界面(UI)时,我们需要综合考虑用户体验、功能布局、视觉效果以及交互逻辑等多个方面,以确保游戏界面既美观又实用,能够让玩家轻松上手并享受游戏过程。

首先,界面的整体布局是设计的核心。棋盘作为游戏的核心区域,应该占据界面的中心位置,占据较大的空间比例,以确保玩家能够清晰地看到棋局的每一个细节。棋盘的格子线条应该清晰且均匀,颜色选择上要与棋子形成鲜明对比,以增强视觉效果。例如,可以选择深色的棋盘背景搭配白色和黑色的棋子,这样不仅符合传统的五子棋视觉习惯,还能让棋子在棋盘上更加突出。棋盘的边缘可以添加一些轻微的阴影效果,以增加立体感和深度感,让棋盘看起来更加真实和吸引人。

在棋盘的周围,我们需要合理安排功能按钮区域。这些按钮应该包括模式选择、难度选择、先手选择、悔棋、重置、认输以及显示禁手等功能。为了保持界面的整洁和美观,可以将这些按钮设计成简洁的图标形式,并在鼠标悬停时显示相应的文字提示。例如,悔棋按钮可以设计成一个带有箭头的圆形图标,当鼠标悬停在该按钮上时,显示"悔棋"字样。这样的设计既节省了空间,又方便玩家快速识别和操作。

python 复制代码
class Board(QWidget):
    # ...之前的代码...

    def is_three_three(self, x, y, player):
        # 检查是否形成三三禁手
        directions = [(0, 1), (1, 0), (1, 1), (1, -1)]
        count = 0
        for dx, dy in directions:
            live_three = self.check_direction(x, y, dx, dy, player)
            if live_three:
                count += 1
            if count >= 2:
                return True
        return False

    def check_direction(self, x, y, dx, dy, player):
        # 检查某个方向是否形成活三
        count = 0
        for i in range(1, 3):  # 检查两个方向
            nx, ny = x + dx * i, y + dy * i
            if 0 <= nx < self.board_size and 0 <= ny < self.board_size:
                if self.grid[ny][nx] == player:
                    count += 1
                else:
                    break
            else:
                break
        for i in range(1, 3):  # 检查相反方向
            nx, ny = x - dx * i, y - dy * i
            if 0 <= nx < self.board_size and 0 <= ny < self.board_size:
                if self.grid[ny][nx] == player:
                    count += 1
                else:
                    break
            else:
                break
        return count == 2

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            x = (event.pos().x() - self.margin) // self.cell_size

2.美观娱乐设置

除了功能按钮,状态栏也是界面设计中不可或缺的一部分。状态栏可以放置在界面的底部或顶部,用于显示当前轮到谁落子、棋局的当前状态(如"黑方胜""白方胜""和棋")以及一些提示信息。状态栏的字体应该清晰易读,颜色与背景形成对比,以确保信息能够被玩家快速捕捉到。例如,当玩家尝试在禁手点落子时,状态栏可以显示一条醒目的提示信息,如"该点为禁手,禁止落子",同时可以弹出一个提示框,进一步解释禁手规则,帮助玩家更好地理解游戏规则。

在视觉设计方面,我们需要注重颜色搭配和图标设计。颜色是影响用户体验的重要因素之一,因此我们需要选择一组既美观又符合游戏氛围的颜色方案。棋盘的背景色可以选择深棕色或深灰色,给人一种沉稳的感觉;棋子则采用传统的黑白两色,以突出棋子在棋盘上的视觉效果。对于功能按钮和状态栏,可以选择明亮的颜色,如蓝色或绿色,以吸引玩家的注意力。图标设计也应该简洁明了,具有直观性。例如,重置按钮可以设计成一个带有循环箭头的图标,表示重新开始;显示禁手按钮可以设计成一个带有"禁"字的图标,直观地传达其功能。

在交互设计方面,我们需要确保玩家能够通过简单的鼠标操作来完成游戏中的各种操作。玩家可以通过鼠标点击棋盘上的格子来落子,点击功能按钮来执行相应的操作。为了增强用户体验,我们还可以为一些操作添加键盘快捷键。例如,"Ctrl+Z"可以用于悔棋,"Ctrl+R"用于重置棋盘,这样可以让玩家在熟悉操作后更加高效地进行游戏。

此外,我们还可以考虑添加一些额外的功能来丰富游戏体验。例如,棋谱记录功能可以让玩家保存对局记录,方便后续回顾和分析。在人机对战模式下,当AI思考下一步棋时,可以显示一个加载动画,如一个旋转的棋子或一个闪烁的光点,以提示玩家AI正在计算,增加游戏的趣味性和互动性。

python 复制代码
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout, QPushButton, QLabel, QVBoxLayout, QHBoxLayout
from PyQt5.QtGui import QPainter, QPen, QBrush, QColor, QFont
from PyQt5.QtCore import Qt, QRect

class GobangUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle('五子棋')
        self.setGeometry(100, 100, 800, 600)

        # 主窗口布局
        main_widget = QWidget(self)
        self.setCentralWidget(main_widget)
        main_layout = QVBoxLayout(main_widget)

        # 棋盘区域
        self.board = Board(self)
        main_layout.addWidget(self.board)

        # 功能按钮区域
        button_layout = QHBoxLayout()
        self.mode_button = QPushButton('人机对战', self)
        self.mode_button.clicked.connect(self.toggle_mode)
        button_layout.addWidget(self.mode_button)

        self.difficulty_button = QPushButton('难度:初级', self)
        self.difficulty_button.clicked.connect(self.change_difficulty)
        button_layout.addWidget(self.difficulty_button)

        self.reset_button = QPushButton('重置', self)
        self.reset_button.clicked.connect(self.reset_game)
        button_layout.addWidget(self.reset_button)

        main_layout.addLayout(button_layout)

        # 状态栏
        self.status_bar = QLabel(self)
        self.status_bar.setAlignment(Qt.AlignCenter)
        self.status_bar.setFont(QFont("Arial", 14))
        main_layout.addWidget(self.status_bar)
        self.update_status("黑方回合")

    def toggle_mode(self):
        # 切换模式
        if self.mode_button.text() == "人机对战":
            self.mode_button.setText("人人对战")
        else:
            self.mode_button.setText("人机对战")
        self.reset_game()

    def change_difficulty(self):
        # 切换难度
        difficulties = ["初级", "中级", "高级"]
        current_difficulty = self.difficulty_button.text().split(":")[1]
        next_difficulty = difficulties[(difficulties.index(current_difficulty) + 1) % len(difficulties)]
        self.difficulty_button.setText(f"难度:{next_difficulty}")

    def reset_game(self):
        # 重置游戏
        self.board.reset()
        self.update_status("黑方回合")

    def update_status(self, message):
        self.status_bar.setText(message)

class Board(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.initBoard()

    def initBoard(self):
        self.board_size = 15
        self.cell_size = 40
        self.margin = 20
        self.width = self.board_size * self.cell_size + 2 * self.margin
        self.height = self.board_size * self.cell_size + 2 * self.margin
        self.resize(self.width, self.height)
        self.grid = [[0 for _ in range(self.board_size)] for _ in range(self.board_size)]
        self.black_turn = True

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)

        # 绘制棋盘背景
        painter.setBrush(QBrush(QColor(240, 217, 181)))
        painter.drawRect(self.rect())

        # 绘制棋盘格子
        painter.setPen(QPen(Qt.black, 2))
        for i in range(self.board_size):
            painter.drawLine(self.margin, self.margin + i * self.cell_size,
                             self.margin + (self.board_size - 1) * self.cell_size, self.margin + i * self.cell_size)
            painter.drawLine(self.margin + i * self.cell_size, self.margin,
                             self.margin + i * self.cell_size, self.margin + (self.board_size - 1) * self.cell_size)

        # 绘制棋子
        painter.setPen(Qt.black)
        for i in range(self.board_size):
            for j in range(self.board_size):
                if self.grid[i][j] == 1:  # 黑棋
                    painter.setBrush(QBrush(Qt.black))
                    painter.drawEllipse(self.margin + j * self.cell_size - self.cell_size // 2,
                                         self.margin + i * self.cell_size - self.cell_size // 2,
                                         self.cell_size, self.cell_size)
                elif self.grid[i][j] == 2:  # 白棋
                    painter.setBrush(QBrush(Qt.white))
                    painter.drawEllipse(self.margin + j * self.cell_size - self.cell_size // 2,
                                         self.margin + i * self.cell_size - self.cell_size // 2,
                                         self.cell_size, self.cell_size)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            x = (event.pos().x() - self.margin) // self.cell_size
            y = (event.pos().y() - self.margin) // self.cell_size
            if 0 <= x < self.board_size and 0 <= y < self.board_size and self.grid[y][x] == 0:
                self.grid[y][x] = 1 if self.black_turn else 2
                self.black_turn = not self.black_turn
                self.update()
                self.parent.update_status("黑方回合" if self.black_turn else "白方回合")

    def reset(self):
        self.grid = [[0 for _ in range(self.board_size)] for _ in range(self.board_size)]
        self.black_turn = True
        self.update()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = GobangUI()
    ex.show()
    sys.exit(app.exec_())
相关推荐
訾博ZiBo17 分钟前
AI日报 - 2025年4月8日
人工智能
James. 常德 student22 分钟前
深度学习之微调
人工智能·深度学习
liuyunshengsir44 分钟前
chromadb 安装和使用
人工智能·大模型
FIT2CLOUD飞致云1 小时前
全面支持MCP协议,开启便捷连接之旅,MaxKB知识库问答系统v1.10.3 LTS版本发布
人工智能·开源
云水木石1 小时前
ChatGPT-4o 在汉字显示上进步巨大
人工智能·chatgpt
Mr_LeeCZ1 小时前
PyTorch 深度学习 || 7. Unet | Ch7.1 Unet 框架
人工智能·深度学习·机器学习
不要天天开心1 小时前
Scala集合
图像处理·算法·机器学习·scala
James. 常德 student1 小时前
多GPU训练
人工智能·pytorch·深度学习
Jozky861 小时前
大语言模型在端到端智驾中的应用
人工智能·语言模型·自然语言处理
Y1nhl2 小时前
搜广推校招面经六十六
pytorch·python·深度学习·机器学习·广告算法·推荐算法·搜索算法