用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_())
相关推荐
夕水3 分钟前
自动化按需导入组件库的工具rust版本完成开源了
前端·rust·trae
石小石Orz1 小时前
写了个插件,给代码里藏东西,线上可用!
trae
用户4099322502121 小时前
FastAPI与Tortoise-ORM模型配置及aerich迁移工具
后端·ai编程·trae
AI布道师Warren2 小时前
AI 智能体蓝图:拆解认知、进化与协作核心
llm
JoernLee2 小时前
Qwen3术语解密:读懂大模型黑话
人工智能·开源·llm
火云牌神3 小时前
本地大模型编程实战(28)查询图数据库NEO4J(1)
python·llm·neo4j·langgraph
CoderJia程序员甲4 小时前
MarkItDown:如何高效将各类文档转换为适合 LLM 处理的 Markdown 格式
ai·llm·markdown·文档转换
星际码仔4 小时前
让大模型“活在当下”: 你必须要了解的 Context7 MCP
ai编程·mcp·trae
win4r17 小时前
🚀企业级最强开源大模型Qwen3震撼发布!本地部署+全面客观测评!Qwen3-235B-A22B+Qwen3-32B+Qwen3-14B谁是王者?ollama
llm·aigc·openai