从 GPT-4 到 Gemini 3 Pro:一个五子棋游戏见证的 AI 两年进化史

前言

两年前,当 GPT-4 刚刚横空出世时,我怀着激动的心情尝试用它写一个五子棋游戏。那段经历我记录在了掘金文章《GPT-4多轮对话生成五子棋游戏》中。当时,为了得到一个能跑的 Demo,我与 GPT-4 进行了漫长的多轮对话:修复 Bug、调整样式、纠正逻辑,仿佛在带一个虽然聪明但由于"健忘"和"粗心"而需要不断纠正的实习生。

时光荏苒,两年后的今天,我拿到了 Gemini 3 Pro。抱着同样的命题,我再次输入了那个需求:"帮我写一个五子棋人机对战游戏"。

这一次,没有多轮对话,没有报错调试。它直接甩给了我一个单文件,我复制、粘贴、打开------它不仅能跑,而且"懂"战术,甚至连 UI 的渐变色都做好了。

这篇文章,不仅是对代码的复盘,更是对这两年 AI 发展速度的一次微观见证。

1. 直观对比:从"拼积木"到"3D打印"

两年前的 GPT-4 体验

在两年前的那篇文章中,开发过程是碎片化的:

  1. 上下文丢失:代码太长时,GPT-4 会中断,需要我提示"继续"。
  2. 逻辑割裂:HTML、CSS 和 JS 往往是分开生成的,需要我手动组装。
  3. 调试循环:最开始生成的 AI 逻辑经常只会随机落子,或者不仅不堵我的棋,还下在已经被占用的格子上。我需要反复告诉它:"你这里逻辑不对,应该先判断是否为空"。

今天的 Gemini 3 Pro 体验

这是 Gemini 3 Pro 给我生成的完整代码(见文末附录)。

结果是惊人的:

  • One-Shot(一次成型) :它在一个 HTML 文件中完整封装了结构、样式和逻辑,没有任何截断。
  • 审美在线 :它没有给我一个黑白格子的简陋棋盘,而是直接使用了 #dcb35c 木纹底色,棋子甚至使用了 RadialGradient 径向渐变来模拟光影立体感。
  • 逻辑完备 :赢法数组 (wins)、赢法统计 (myWin, computerWin)、启发式评分 (myScore, computerScore) 一气呵成。

2. 代码深度解析:AI 真的懂"算法"了

让我们看看 Gemini 3 Pro 生成的核心 AI 逻辑,这在两年前是需要我引导很久才能写出来的。

赢法数组的预计算

代码中有一个非常经典的五子棋算法实现------赢法数组

JavaScript

csharp 复制代码
// 初始化赢法数组
function initWins(){
    // ... 省略循环 ...
    // 横线、竖线、斜线、反斜线
    // 统计出所有可能连成5子的线,总共572种(15x15棋盘)
}

Gemini 3 Pro 在初始化时,直接计算了棋盘上所有横、竖、撇、捺能构成五连珠的情况,并在内存中构建了一个三维数组。这意味着它"知道"五子棋的胜利条件不仅仅是"看到5个子",而是基于数学上的组合可能性。

启发式评分系统

最让我惊讶的是 computerAI 函数中的评分逻辑:

JavaScript

scss 复制代码
// 拦截玩家
if(myWin[k] == 1) myScore[i][j] += 200;
else if(myWin[k] == 2) myScore[i][j] += 400;
else if(myWin[k] == 3) myScore[i][j] += 2000;
else if(myWin[k] == 4) myScore[i][j] += 10000;

// 电脑进攻
if(computerWin[k] == 1) computerScore[i][j] += 220;
else if(computerWin[k] == 2) computerScore[i][j] += 420;
else if(computerWin[k] == 3) computerScore[i][j] += 2100;
else if(computerWin[k] == 4) computerScore[i][j] += 20000;

注意这里的细节:

  1. 攻守兼备:它同时计算了"封堵玩家"的分数和"自己进攻"的分数。
  2. 权重差异:注意看,同样是连3子,电脑进攻的分数 (2100) 略高于拦截玩家的分数 (2000)。这说明 AI 被设定为**"在确保安全的情况下,优先进攻"**的激进策略。
  3. 绝杀判断:当达到 4 子时,分数直接跳跃到万级,确保 AI 能够识别"冲四"和"防守冲四"的最高优先级。

两年前,GPT-4 生成的代码往往只是随机找空位,或者仅仅判断周围一格是否有子。而现在的 Gemini 3 Pro 直接写出了一个具备贪心算法雏形的 AI。

3. 两年的技术跨越:我们经历了什么?

通过这个五子棋 Demo,我们可以清晰地看到 AI 进化的三个维度:

1. 上下文窗口与推理深度的质变

两年前,我们需要把大任务拆解成小任务喂给 AI。现在,Gemini 3 Pro 的上下文窗口和推理能力允许它在一次输出中处理全局依赖 。它清楚定义在第 10 行的 CSS 类名需要在第 50 行的 HTML 中使用,以及第 100 行的 JS 逻辑要操作第 20 行的 Canvas DOM。这种全局一致性是最大的进步。

2. 从"模仿者"到"工程师"

以前的 AI 像是在 Stack Overflow 上搜索代码片段并拼接(常常拼错)。现在的 AI 更像是一个工程师。它考虑了:

  • UI 交互 :点击后稍作延迟 (setTimeout) 再让电脑落子,为了让 UI 刷新,避免浏览器假死。
  • 边界条件 :游戏结束后锁定点击事件 (if (over) return)。
  • 代码规范:变量命名清晰,注释详尽。

3. 多模态理解的反哺

虽然这次是纯代码生成,但 Gemini 3 Pro 对颜色的运用(木纹色、渐变棋子)暗示了它对物理世界"五子棋"视觉样貌的深刻理解。它不仅仅是在写代码,它是在"构建"一个它认知中的游戏场景。

4. 结语

两年前,我在 Juejin 文章的结尾可能还在感叹:"AI 辅助编程的时代来了,但还需要人来把关。"

今天,看着 Gemini 3 Pro 生成的这个无需修改、直接运行、甚至有点难赢的五子棋游戏,我必须更新我的观点:AI 已经从"副驾驶"开始尝试握住方向盘了。

对于开发者而言,这意味着门槛的再一次提高和降低------编写基础代码的门槛降到了零,但理解复杂系统、优化算法权重的门槛依然存在。

附:Gemini 3 Pro 生成的完整代码与效果图

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>五子棋人机对战</title>
    <style>
        body {
            font-family: 'Microsoft YaHei', sans-serif;
            background-color: #f0f0f0;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
            margin: 0;
        }

        h1 {
            color: #333;
            margin-bottom: 10px;
        }

        #game-container {
            position: relative;
            box-shadow: 0 10px 25px rgba(0,0,0,0.5);
            border-radius: 4px;
            padding: 10px;
            background-color: #dcb35c; /* 木纹底色 */
        }

        canvas {
            background-color: #dcb35c;
            cursor: pointer;
            display: block;
        }

        .status-bar {
            margin-top: 20px;
            display: flex;
            gap: 20px;
            align-items: center;
        }

        #status {
            font-size: 1.2em;
            font-weight: bold;
            color: #333;
        }

        button {
            padding: 10px 20px;
            font-size: 1em;
            cursor: pointer;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 5px;
            transition: background 0.3s;
        }

        button:hover {
            background-color: #45a049;
        }
    </style>
</head>
<body>

    <h1>五子棋 - 人机对战</h1>
    
    <div id="game-container">
        <canvas id="chess" width="450" height="450"></canvas>
    </div>

    <div class="status-bar">
        <div id="status">当前回合: 玩家 (黑棋)</div>
        <button onclick="restartGame()">重新开始</button>
    </div>

    <script>
        // 配置参数
        const canvas = document.getElementById('chess');
        const context = canvas.getContext('2d');
        const statusText = document.getElementById('status');
        
        const gridSize = 30; // 格子大小
        const padding = 15;  // 棋盘边缘留白
        const boardSize = 15; // 15x15 棋盘
        
        let chessBoard = []; // 棋盘状态数组 0:空, 1:黑(玩家), 2:白(电脑)
        let me = true; // true: 玩家回合, false: 电脑回合
        let over = false; // 游戏结束标志
        
        //赢法数组
        let wins = [];
        //赢法统计数组
        let myWin = [];
        let computerWin = [];
        let count = 0; // 赢法总数

        // 初始化游戏
        window.onload = function() {
            initGame();
        };

        function initGame() {
            // 重置变量
            me = true;
            over = false;
            chessBoard = [];
            statusText.innerText = "当前回合: 玩家 (黑棋)";
            statusText.style.color = "#333";

            // 初始化棋盘数组
            for(let i = 0; i < boardSize; i++){
                chessBoard[i] = [];
                for(let j = 0; j < boardSize; j++){
                    chessBoard[i][j] = 0;
                }
            }

            // 初始化赢法统计
            initWins();
            
            // 绘制棋盘
            drawChessBoard();
        }

        // 绘制棋盘网格
        function drawChessBoard() {
            context.clearRect(0, 0, canvas.width, canvas.height);
            context.strokeStyle = "#555";
            
            for (let i = 0; i < boardSize; i++) {
                // 竖线
                context.moveTo(padding + i * gridSize, padding);
                context.lineTo(padding + i * gridSize, canvas.height - padding);
                context.stroke();
                // 横线
                context.moveTo(padding, padding + i * gridSize);
                context.lineTo(canvas.width - padding, padding + i * gridSize);
                context.stroke();
            }
        }

        // 绘制棋子
        function oneStep(i, j, me) {
            context.beginPath();
            context.arc(padding + i * gridSize, padding + j * gridSize, 13, 0, 2 * Math.PI);
            context.closePath();
            
            let gradient = context.createRadialGradient(
                padding + i * gridSize + 2, padding + j * gridSize - 2, 13, 
                padding + i * gridSize + 2, padding + j * gridSize - 2, 0
            );

            if (me) { // 黑棋
                gradient.addColorStop(0, "#0a0a0a");
                gradient.addColorStop(1, "#636766");
            } else { // 白棋
                gradient.addColorStop(0, "#d1d1d1");
                gradient.addColorStop(1, "#f9f9f9");
            }
            
            context.fillStyle = gradient;
            context.fill();
        }

        // 玩家点击落子
        canvas.onclick = function(e) {
            if (over || !me) return;

            let x = e.offsetX;
            let y = e.offsetY;
            
            let i = Math.floor(x / gridSize);
            let j = Math.floor(y / gridSize);

            if (chessBoard[i][j] == 0) {
                oneStep(i, j, true);
                chessBoard[i][j] = 1; // 1代表黑棋

                // 检查玩家是否胜利
                for(let k = 0; k < count; k++){
                    if(wins[i][j][k]){
                        myWin[k]++;
                        computerWin[k] = 6; // 设置异常值,因为对方不可能在这个赢法上赢了
                        if(myWin[k] == 5){
                            statusText.innerText = "恭喜你!你赢了!";
                            statusText.style.color = "red";
                            over = true;
                        }
                    }
                }

                if (!over) {
                    me = !me;
                    statusText.innerText = "当前回合: 电脑 (思考中...)";
                    setTimeout(computerAI, 50); // 稍作延迟,让UI刷新
                }
            }
        }

        // 电脑AI逻辑
        function computerAI() {
            let myScore = []; // 玩家分数(防守)
            let computerScore = []; // 电脑分数(进攻)
            let max = 0; // 最高分
            let u = 0, v = 0; // 最佳落子点

            // 初始化分数数组
            for(let i = 0; i < boardSize; i++){
                myScore[i] = [];
                computerScore[i] = [];
                for(let j = 0; j < boardSize; j++){
                    myScore[i][j] = 0;
                    computerScore[i][j] = 0;
                }
            }

            // 遍历棋盘所有空点进行评分
            for(let i = 0; i < boardSize; i++){
                for(let j = 0; j < boardSize; j++){
                    if(chessBoard[i][j] == 0){
                        // 遍历所有赢法
                        for(let k = 0; k < count; k++){
                            if(wins[i][j][k]){
                                // 计算拦截玩家的分数
                                if(myWin[k] == 1) myScore[i][j] += 200;
                                else if(myWin[k] == 2) myScore[i][j] += 400;
                                else if(myWin[k] == 3) myScore[i][j] += 2000;
                                else if(myWin[k] == 4) myScore[i][j] += 10000;

                                // 计算电脑进攻的分数
                                if(computerWin[k] == 1) computerScore[i][j] += 220;
                                else if(computerWin[k] == 2) computerScore[i][j] += 420;
                                else if(computerWin[k] == 3) computerScore[i][j] += 2100;
                                else if(computerWin[k] == 4) computerScore[i][j] += 20000;
                            }
                        }

                        // 综合评分,决定最佳落子
                        // 只要该点总分比当前最高分高,就更新
                        if(myScore[i][j] > max){
                            max = myScore[i][j];
                            u = i; v = j;
                        } else if(myScore[i][j] == max){
                            if(computerScore[i][j] > computerScore[u][v]){
                                u = i; v = j;
                            }
                        }
                        
                        if(computerScore[i][j] > max){
                            max = computerScore[i][j];
                            u = i; v = j;
                        } else if(computerScore[i][j] == max){
                            if(myScore[i][j] > myScore[u][v]){
                                u = i; v = j;
                            }
                        }
                    }
                }
            }

            // 电脑落子
            oneStep(u, v, false);
            chessBoard[u][v] = 2; // 2代表白棋

            // 检查电脑是否胜利
            for(let k = 0; k < count; k++){
                if(wins[u][v][k]){
                    computerWin[k]++;
                    myWin[k] = 6;
                    if(computerWin[k] == 5){
                        statusText.innerText = "遗憾,电脑赢了!";
                        statusText.style.color = "blue";
                        over = true;
                    }
                }
            }

            if (!over) {
                me = !me;
                statusText.innerText = "当前回合: 玩家 (黑棋)";
            }
        }

        // 初始化赢法数组(这是算法核心:预先计算所有可能的连线)
        function initWins(){
            wins = [];
            for(let i=0; i<boardSize; i++){
                wins[i] = [];
                for(let j=0; j<boardSize; j++){
                    wins[i][j] = [];
                }
            }
            
            count = 0;

            // 横线赢法
            for(let i=0; i<boardSize; i++){
                for(let j=0; j<boardSize-4; j++){
                    for(let k=0; k<5; k++){
                        wins[i][j+k][count] = true;
                    }
                    count++;
                }
            }

            // 竖线赢法
            for(let i=0; i<boardSize; i++){
                for(let j=0; j<boardSize-4; j++){
                    for(let k=0; k<5; k++){
                        wins[j+k][i][count] = true;
                    }
                    count++;
                }
            }

            // 斜线赢法 ( \ )
            for(let i=0; i<boardSize-4; i++){
                for(let j=0; j<boardSize-4; j++){
                    for(let k=0; k<5; k++){
                        wins[i+k][j+k][count] = true;
                    }
                    count++;
                }
            }

            // 反斜线赢法 ( / )
            for(let i=0; i<boardSize-4; i++){
                for(let j=boardSize-1; j>3; j--){
                    for(let k=0; k<5; k++){
                        wins[i+k][j-k][count] = true;
                    }
                    count++;
                }
            }

            // 初始化统计数组
            for(let i=0; i<count; i++){
                myWin[i] = 0;
                computerWin[i] = 0;
            }
        }

        function restartGame() {
            initGame();
        }
    </script>
</body>
</html>
相关推荐
量子位2 天前
Gemini 3“超前点映”效果炸场,巴菲特305亿重仓谷歌
ai编程·gemini·trae
用户8150916072607 天前
【AI绘图新手必看】Nano Banana实战教学:从提示词到画面构图!
aigc·gemini
寒冰大叔10 天前
Dify 添加 Google cloud 模型供应商
googlecloud·dify·gemini
马丁的代码日记10 天前
Google Gemini 综合教程:从入门到 API 接入
ai·google·gemini
腾讯云云开发1 个月前
Gemini CLI接入CloudBase-AI-Toolkit(MCP)保姆级教程
ai编程·小程序·云开发·gemini
魁首1 个月前
为什么 Claude/Gemini/Codex 都用 stdio 传输机制?
claude·gemini·mcp
魁首2 个月前
MCP与ACP本质区别深度分析
claude·gemini·mcp
魁首2 个月前
初识 MCP (Model Context Protocol)
claude·gemini·mcp
戴着眼镜看不清2 个月前
2025年最新开源Nano Banana Plus商业化系统本地搭建教程
人工智能·gemini·nanobanana