用Trae,找初恋,代码写人生,Trae圆你初恋梦。

我正在参加Trae「超级体验官」创意实践征文,本文所使用的 Trae 免费下载链接:www.trae.com.cn/?utm_source...

藏在井字棋里的青春密码

十五岁的午后,教室窗外的梧桐叶在初夏的风里沙沙作响。她用铅笔在草稿纸上画下歪歪扭扭的井字格,抬头时马尾辫扫过我的课本。"该你了",她指着第三行第二列的空格,眼睫在阳光下扑闪。那时我们总以为,这九宫格里的"X"与"O"不过是课间十分钟的游戏,却不知那些交错的三连棋里,藏着后来让我痴迷的算法奥秘。

二十年后的今天,当我用代码复现这个游戏时,屏幕上的AI总能精准落子。那些被橡皮擦抹去的棋局,那些故意输掉的对局,那些藏在胜负里的小心思,忽然在记忆里鲜活起来------原来当年我们稚嫩的博弈策略,早已预言了人工智能领域最经典的Minimax算法。

Trae 圆梦

首先,我们向Trae发出请求: "基于pytorch或者TensorFlow实现井字棋AI开发(Minimax算法+alpha-beta剪枝)"

随后系统会自动生成大量代码,其中部分内容你可能熟悉,也可能有些陌生。不过无需担心,我们可以根据提示信息,将这些代码逐一复制粘贴到对应的项目文件中。

完成代码的复制粘贴后,请运行 main.py 文件。若程序正常运行,控制台将进入交互模式,此时可以开始输入指令。

由于命令行交互体验不佳,我们进一步向 Trae 发送指令: "使用 PyQt5 实现 GUI 界面" ,以优化用户操作体验。

执行效果如下:

现在,这个小游戏已经可以发送给你的初恋对象啦!下面我们简单分析下游戏原理:为什么当年玩井字棋总是赢不了呢?

藏在青春悸动里的决策智慧

2.1 青涩的"如果体"思维

"如果我把棋子放在这里,她会怎么应对?"当年的课桌上,我们本能地推演着两三步后的棋局。这种朴素的预判思维,正是Minimax算法的雏形。

就像少年在纸条上写下又划去的告白词,算法也在虚拟的决策树上反复权衡:

  • 若此刻落子角落(最大收益)
  • 但对方可能封堵中间(最小收益)
  • 于是转而抢占中心(平衡策略)

2.2 懵懂的最大最小原则

那时的我们不懂算法,却在本能践行着核心逻辑:

  • 自己回合(最大化层):偷偷创造双杀机会
  • 对方回合(极小化层):假设她会做出最聪明的应对
  • 最终选择:在所有"最坏可能"里选"相对最好"

这种思维模式,像极了初恋时小心翼翼的试探:既期待对方察觉心意,又害怕被直接拒绝后的尴尬。算法中的评分机制,恰似少年藏在课桌抽屉里的日记,给每个选择默默打分。

逐行解析核心算法

3.1 胜负判定函数

这个函数就像赛场裁判,每走一步就检查是否有玩家连成直线。返回1表示玩家胜,-1表示AI胜,0代表继续比赛。

python 复制代码
def check_winner(board):
    # 检查横向三连
    for i in range(3):
        if board[i,0] !=0 and (board[i] == board[i,0]).all():
            return board[i,0]
    # 检查纵向三连(类似逻辑)
    # 检查两条对角线
    return 0

3.2 Minimax递归框架

这个递归函数像一位深谋远虑的军师,不断推演后续可能的发展。参数说明:

  • depth:记录递归深度(井字棋最多9层)
  • is_maximizing:当前是AI还是玩家回合
  • alpha/beta:记录当前最优解的上下界
python 复制代码
def minimax(board, depth, is_maximizing, alpha, beta):
    # 终止条件
    winner = check_winner(board)
    if winner !=0:
        return 1 if winner==-1 else -1  # AI胜得1分
    
    if 棋盘已满:
        return 0  # 平局

    if is_maximizing:  # AI回合
        best = -∞
        for 每个空位:
            模拟落子
            score = minimax(新棋盘, depth+1, False, alpha, beta)
            best = max(best, score)
            alpha = max(alpha, best)
            if beta <= alpha:  # Alpha-Beta剪枝
                break
        return best
    else:  # 玩家回合
        # 类似逻辑,取最小值

Alpha-Beta剪枝原理

4.1 剪枝的必要性

假设AI在评估某个走法时,发现后续存在必输的局面,就可以立即放弃这个分支。就像下棋时说:"这步棋走下去肯定要输,不用再算了!"

4.2 剪枝的实现技巧

通过维护两个值:

  • alpha:已知的玩家最少能给AI的分数
  • beta:已知的AI最多能获得的分数

alpha >= beta时,说明当前分支已经没有继续探索的价值。代码中对应的关键片段:

python 复制代码
if beta <= alpha:
    break  # 剪枝!

4.3 实际案例演示

假设当前棋盘:AI(O方)在计算时,发现某步棋可能导致玩家下一步形成双杀。这时Alpha-Beta机制会立即终止该分支的深入计算。

python 复制代码
X |   | O
---------
  | X |  
---------
O |   |  

当代码中的check_winner()函数扫描棋盘时,我总会想起她突然亮起来的眼睛------那年午后,她用手指划过三个连成一线的"O",欢呼声惊飞了窗台的白鸽。如今的AI虽能瞬间判断胜负,却永远读不懂获胜时人类眼底的星光。

minimax()函数的递归深处,算法在虚拟时空中推演着万千可能。这多像十七岁那个雨季,我在数学课上草稿本写满的"如果":"如果那天帮她捡起橡皮时多说一句话"、"如果校运会时报名双人项目"...那些未说出口的选择枝桠,最终都成了记忆里的alpha-beta剪枝。

致青春

当我们用算法复现青春的游戏,那些冰冷的代码竟也有了温度。屏幕上的AI永远理性,但当年故意输掉棋局时,藏在规则漏洞里的温柔,才是人类最珍贵的算法。或许人工智能永远学不会,为什么某个初夏的课间,有个少年明明看到必胜棋路,却悄悄把棋子落在了别处。

完整代码示例

python 复制代码
import sys
import torch
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QGridLayout, QMessageBox

PLAYER = 1
AI = -1
EMPTY = 0

symbols = {PLAYER: 'X', AI: 'O', EMPTY: ' '}


def check_winner(board):
    for i in range(3):
        if board[i, 0] != 0 and (board[i] == board[i, 0]).all():
            return board[i, 0].item()
        if board[0, i] != 0 and (board[:, i] == board[0, i]).all():
            return board[0, i].item()

    if board[1, 1] != 0:
        if (board[0, 0] == board[1, 1] and board[2, 2] == board[1, 1]) or \
           (board[0, 2] == board[1, 1] and board[2, 0] == board[1, 1]):
            return board[1, 1].item()
    return 0


def minimax(board, depth, is_maximizing, alpha, beta):
    winner = check_winner(board)
    if winner != 0:
        return 10 - depth if winner == AI else depth - 10

    if torch.all(board != 0):
        return 0

    if is_maximizing:
        best_score = -float('inf')
        for move in torch.nonzero(board == 0):
            row, col = move
            new_board = board.clone()
            new_board[row, col] = AI
            score = minimax(new_board, depth + 1, False, alpha, beta)
            best_score = max(best_score, score)
            alpha = max(alpha, best_score)
            if beta <= alpha:
                break
        return best_score
    else:
        best_score = float('inf')
        for move in torch.nonzero(board == 0):
            row, col = move
            new_board = board.clone()
            new_board[row, col] = PLAYER
            score = minimax(new_board, depth + 1, True, alpha, beta)
            best_score = min(best_score, score)
            beta = min(beta, best_score)
            if beta <= alpha:
                break
        return best_score


def find_best_move(board):
    best_score = -float('inf')
    best_move = None
    for move in torch.nonzero(board == 0):
        row, col = move
        new_board = board.clone()
        new_board[row, col] = AI
        score = minimax(new_board, 0, False, -float('inf'), float('inf'))
        if score > best_score:
            best_score = score
            best_move = (row.item(), col.item())
    return best_move


class TicTacToe(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Tic Tac Toe - PyQt5")
        self.board = torch.zeros((3, 3), dtype=torch.int)
        self.buttons = [[None for _ in range(3)] for _ in range(3)]
        self.init_ui()

    def init_ui(self):
        layout = QGridLayout()
        for row in range(3):
            for col in range(3):
                btn = QPushButton('')
                btn.setFixedSize(100, 100)
                btn.setStyleSheet("font-size: 24px;")
                btn.clicked.connect(lambda _, r=row, c=col: self.player_move(r, c))
                self.buttons[row][col] = btn
                layout.addWidget(btn, row, col)
        self.setLayout(layout)

    def player_move(self, row, col):
        if self.board[row, col] != 0:
            return
        self.board[row, col] = PLAYER
        self.update_ui()
        if self.check_game_end():
            return
        self.ai_move()

    def ai_move(self):
        move = find_best_move(self.board)
        if move:
            row, col = move
            self.board[row, col] = AI
            self.update_ui()
            self.check_game_end()

    def update_ui(self):
        for row in range(3):
            for col in range(3):
                self.buttons[row][col].setText(symbols[self.board[row, col].item()])

    def check_game_end(self):
        winner = check_winner(self.board)
        if winner != 0:
            QMessageBox.information(self, "Game Over", "You win!" if winner == PLAYER else "AI wins!")
            self.reset_game()
            return True
        elif torch.all(self.board != 0):
            QMessageBox.information(self, "Game Over", "It's a tie!")
            self.reset_game()
            return True
        return False

    def reset_game(self):
        self.board[:] = 0
        self.update_ui()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = TicTacToe()
    window.show()
    sys.exit(app.exec_())
相关推荐
SunStriKE13 小时前
SgLang代码细读-3. Cache
llm·源码阅读·推理
小胡说人工智能1 天前
深度剖析:Dify+Sanic+Vue+ECharts 搭建 Text2SQL 项目 sanic-web 的 Debug 实战
人工智能·python·llm·text2sql·dify·vllm·ollama
扫地僧9851 天前
基于大模型微调的智能医疗诊断协助系统(LLM,RAG,Agent)
人工智能·llm·agent·arg
mingshili2 天前
[AI算法] LLM训练-构建transformers custom model
算法·大模型·llm
小技工丨2 天前
LLaMA-Factory:了解webUI参数
人工智能·llm·llama·llama-factory
SunStriKE2 天前
SgLang代码细读-2.forward过程
深度学习·llm·源码阅读·推理
uncle_ll3 天前
Dify-3:系统架构
系统架构·llm·agent·dify·rag
RuizhiHe4 天前
从零开始实现大语言模型(十六):加载开源大语言模型参数
人工智能·chatgpt·llm·大语言模型·deepseek·从零开始实现大语言模型
SunStriKE4 天前
SgLang代码细读-1.从req到batch
llm·推理
RuizhiHe4 天前
从零开始实现大语言模型(十五):并行计算与分布式机器学习
人工智能·chatgpt·llm·大语言模型·deepseek·从零开始实现大语言模型