《我用AI实现四子棋》

411 阅读6分钟

什么是四连棋?四连棋的规则是怎样的?

四子棋是一种棋类智力游戏,共有6行7列,棋盘是垂直摆放的,每位玩家有21个圆形棋子,两个玩家的棋子颜色是不一样的。 游戏规则为两位玩家轮流下棋,玩家必须从最底下的一格开始下棋,在棋盘中,谁能使自己的四个棋子在横,竖或对角线连成一条直线,即可获胜,否则就输了;如果棋盘已经填满,且双方都没有实现四个棋子连在一起时,则为平局。

屏幕截图 2025-05-28 222806.png

如何指导AI实现四连棋

AI时代已经到来,如何指导AI,怎样把AI变成我们的学习工具,也是一门必修课,下面 来看看我是怎么指导AI的吧!

  1. 先在脑海中思考一下你想要干什么
  2. 说出你的需求和想要实现的功能,尽可能说的详细点儿
  3. 将思考的结果和AI给出的答案进行对比,如果有不满意的地方,可以再次进行提问

页面代码

一、双人手动下棋

同一台电脑双方轮流点一下鼠标,自动判断胜负

指导AI:现在我要使用HTML+CSS+JS构建四连棋页面,要求使用Grid布局,界面画风为Q版。棋盘为6行7列,要求对战双方轮流下棋,双方棋子为不同颜色,当有一方在棋盘上形成4个棋子(横、竖或者对角线)连在一起时,获得胜利. 玩家可以选择先下或者后下,双方可使用鼠标点击轮流下棋,移动时应该有棋子占位的预显示功能,每次下棋时,必须叠在已有的棋子上,或者是最下面一层

页面效果 动画.gif 代码部分

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>六一四连棋大作战</title>
</head>
<body>
    <h1>六一快乐!四连棋大作战</h1>
    <div id="controls">
        <button id="start-yellow">我先下(黄)</button>
        <button id="start-red">对方先下(红)</button>
        <button id="reset">重新开始游戏</button>
    </div>
    <div id="message"></div>
    <div id="game-container">
    <div id="board"></div>
    </div>
    </body>
</html>

逻辑代码

落子逻辑代码:确保棋子只能落在已有棋子上方或最底部,否则视为无效落子

<script>
        //落子逻辑
        const ROWS = 6;
        const COLS = 7;
        let board = Array.from({ length: ROWS }, () => Array(COLS).fill(null));
        function handleCellClick(e) {
            if (gameOver || currentPlayer === null) return;
            const col = parseInt(e.target.dataset.col);
            const row = findNextEmptyRow(col);
            if (row === -1 || (row < ROWS - 1 && board[row + 1][col] === null)) {
                return; // 无效落子
            }
            placePiece(row, col);
            checkWin(row, col);
            switchPlayer();
        }
    </script>

二、低级人机对战

AI随机选一列配合下棋,自动判断胜负 指导AI:现在我要写人机对战模式,就是AI随机选一列配合下棋,自动判断胜负

页面效果

现在这一页面是在第一个页面的基础上进行修改的,增加了AI对战的效果,但是AI看起来非常傻,当我三个棋子都连在一起了,它都没有堵我,哈哈哈哈哈,后面还有高级人机模式,尽情期待 动画1.gif 逻辑代码

下面为大家展示一下AI下棋的逻辑:随机选择一列,然后执行落子

// AI 下棋
        function aiMove() {
            // 检查AI是否能直接获胜
            const winningCol = findWinningMove(AI_PLAYER);
            if (winningCol !== -1) return makeMove(winningCol);
            const blockingCol = findThreeInARow();
            if (blockingCol !== -1) return makeMove(blockingCol);
            
            // 随机选择有效列
            const validCols = Array.from({length: COLS}, (_, i) => i)
                .filter(col => findNextEmptyRow(col) !== -1);
            if (validCols.length) makeMove(validCols[Math.floor(Math.random() * validCols.length)]);
        }
        
        // 查找获胜的落子位置
        function findWinningMove(player) {
            for (let col = 0; col < COLS; col++) {
                const row = findNextEmptyRow(col);
                if (row !== -1) {
                    board[row][col] = player;
                    const isWin = checkWin(row, col);
                    board[row][col] = null;
                    if (isWin) return col;
                }
            }
            return -1;
        }
        // 执行落子
        function makeMove(col) {
            const row = findNextEmptyRow(col);
            placePiece(row, col);
            if (!checkWin(row, col)) switchPlayer();
        }
        // 查找下一个空行
        function findNextEmptyRow(col) {
            for (let row = ROWS - 1; row >= 0; row--) {
                if (board[row][col] === null) return row;
            }
            return -1;
        }
        // 放置棋子
        function placePiece(row, col) {
            board[row][col] = currentPlayer;
            document.querySelector(`.cell[data-row="${row}"][data-col="${col}"]`).classList.add(currentPlayer);
        }

三、多人实时对战

指导AI:

  1. 写一个多人实时对战.html (一人一个手机实时对战,使用Socket.io进行实时通信),需要写前后端,用户需要连接同一WiFi,功能和界面可以稍作修改
  2. 可以在页面上提示用户,你是什么颜色的棋,以免混淆,比如说轮到这一方下棋了,就在页面上提示用户,你是哪一方,轮到你下棋啦,另外一个用户下棋时,可以提示等待另一玩家下棋,大概是这样子
  3. 游戏结束后,赢的那一方提示"恭喜你,赢了!",失败提示"很遗憾,你输了!" 平局提示"游戏结束 - 平局!"
  4. 玩家点了再来一局,可以向对方发送一个请求,这边用户同意了,他们双方可以再玩一局,如果用户拒绝了,则进行提示

页面效果 当两个用户连接同一WiFi,访问网址时,只需要填入相同游戏ID房间号,即可进入同一游戏,这样就完成了实时对战效果 动画2.gif 后端运行效果

图片.png 代码部分

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>多人实时四连棋</h1>
    <div id="controls">
        <input type="text" id="game-id-input" placeholder="输入游戏ID">
        <button id="join-btn">加入游戏</button>
    </div>
    <div id="game-id"></div>
    <div id="game-info" style="display: none;">
        <div id="yellow-player" class="player-info">
            <span id="yellow-player-name">黄方</span>
        </div>
        <div id="red-player" class="player-info">
            <span id="red-player-name">红方</span>
        </div>
    </div>
    <div id="turn-message">等待加入游戏...</div>
    <div id="board"></div>
    <button id="rematch-btn">再来一局</button>
    <div id="rematch-dialog" class="rematch-dialog">
        <p id="rematch-message">对手请求再来一局,是否接受?</p>
        <div class="rematch-buttons">
            <button id="accept-rematch" class="rematch-accept">接受</button>
            <button id="reject-rematch" class="rematch-reject">拒绝</button>
        </div>
    </div>
    </body>
</html>

逻辑代码

<script src="https://cdn.jsdelivr.net/npm/socket.io-client@4/dist/socket.io.js"></script>
    <script>
    const socket = io();
        let myColor = null;
        let currentGame = null;
        let currentGameId = null;
        // 加入游戏
        joinBtn.addEventListener('click', () => {
            const gameId = gameIdInput.value.trim();
            if (!gameId) return alert('请输入游戏ID');
            currentGameId = gameId;
            socket.emit('join', gameId);
            gameIdElement.textContent = `游戏ID: ${gameId}`;
            createBoard();
        });
        // 再来一局
        rematchBtn.addEventListener('click', () => {
            socket.emit('requestRematch', currentGameId);
            rematchBtn.disabled = true;
            rematchBtn.textContent = '等待对方响应...';
        });
        // 处理重赛请求
        socket.on('rematchRequested', () => {
            rematchMessage.textContent = '对手请求再来一局,是否接受?';
            rematchDialog.style.display = 'block';
        });
        socket.on('rematchPending', () => {
            rematchBtn.disabled = true;
            rematchBtn.textContent = '等待对方响应...';
        });
        socket.on('rematchAccepted', () => {
            rematchDialog.style.display = 'none';
            rematchBtn.disabled = false;
            rematchBtn.textContent = '再来一局';
            rematchBtn.style.display = 'none';
            turnMessageElement.textContent = '游戏重新开始!';
        });
        socket.on('rematchRejected', () => {
            rematchDialog.style.display = 'none';
            rematchBtn.disabled = false;
            rematchBtn.textContent = '再来一局';
            turnMessageElement.textContent = '对手拒绝了重赛请求';
        });

        // Socket事件处理
        socket.on('color', (color) => {
            myColor = color;
            gameInfoElement.style.display = 'flex';

    </script>

后端代码,需要下载模块npm install express socket.io,然后再运行

const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
const PORT = 3000;
// Socket.io连接处理
io.on('connection', (socket) => {
    console.log('新用户连接:', socket.id);
    socket.on('join', (gameId) => {
        if (!games[gameId]) {
            games[gameId] = {
                players: [],
                board: Array(6).fill().map(() => Array(7).fill(null)),
                currentPlayer: 'yellow',
                gameOver: false,
                rematchRequested: false
            };
        }
        if (games[gameId].players.length >= 2) {
            socket.emit('full');
            return;
        }
        const color = games[gameId].players.length === 0 ? 'yellow' : 'red';
        games[gameId].players.push({ id: socket.id, color });
        socket.join(gameId);
        socket.emit('color', color);
        io.to(gameId).emit('update', games[gameId]);
        console.log(`${socket.id} (${color})加入了游戏${gameId}`);
    });
    socket.on('move', (col) => {
        const gameId = Array.from(socket.rooms)[1];
        const game = games[gameId];
        if (!game || game.gameOver) return;
        const player = game.players.find(p => p.id === socket.id);
        if (!player || player.color !== game.currentPlayer) return;
        const row = findEmptyRow(game.board, col);
        if (row === -1) return;
        game.board[row][col] = player.color;
        if (checkWin(game.board, row, col, player.color)) {
            game.gameOver = true;
            game.winner = player.color;
        } else if (isBoardFull(game.board)) {
            game.gameOver = true;
            game.winner = 'draw';
        } else {
            game.currentPlayer = player.color === 'yellow' ? 'red' : 'yellow';
        }
        io.to(gameId).emit('update', game);
    });
    socket.on('requestRematch', (gameId) => {
        const game = games[gameId];
        if (!game || !game.gameOver) return;
        // 标记重赛请求
        game.rematchRequested = true;
        // 通知对方玩家
        const opponent = game.players.find(p => p.id !== socket.id);
        if (opponent) {
            io.to(opponent.id).emit('rematchRequested');
        }
        // 通知请求方
        socket.emit('rematchPending');
    });
    socket.on('respondRematch', ({ gameId, accept }) => {
        const game = games[gameId];
        if (!game || !game.rematchRequested) return;
        if (accept) {
            // 重置游戏状态
            games[gameId] = {
                players: game.players,
                board: Array(6).fill().map(() => Array(7).fill(null)),
                currentPlayer: 'yellow',
                gameOver: false,
                rematchRequested: false
            };
            io.to(gameId).emit('rematchAccepted');
            io.to(gameId).emit('update', games[gameId]);
        } else {
            // 拒绝重赛
            game.rematchRequested = false;
            const requester = game.players.find(p => p.id !== socket.id);
            if (requester) {
                io.to(requester.id).emit('rematchRejected');
            }
        }
    });
    socket.on('disconnect', () => {
        console.log('用户断开:', socket.id);
    });
});
// 静态文件服务
app.use(express.static('public'));
server.listen(PORT, () => {
    console.log(`服务器运行在 http://localhost:${PORT}`);
});

四、高级人机对战

指导AI:现在我要写高级人机对战,使用算法或训练AI经过思考下棋,达到必胜或者平手的水平

AI的回复为:使用极小化极大算法(Minimax)和α-β剪枝来提升AI的决策能力,这种算法可以让AI思考几步棋之后的情况,选择最优的下法

页面效果

图片.png 代码部分和逻辑代码*

下面是AI回复的答案,我测试之后,直接震惊了,觉得这种算法很厉害,迫不及待地想和大家分享一下,大家可以研究研究。这个算法添加了难度选择,可以设置AI思考的深度(3步、5步或7步),思考深度越大,AI越强,但计算时间也越长,同时也实现了复杂的评估函数,考虑了多种因素:

  • 优先选择中心列(中心列更有价值)
  • 发现潜在的四连机会
  • 阻止对手形成四连
  • 优先形成自己的三连和双连 自己试玩之后,发现这个AI实力相当强大,自己挑战了多次,几乎没有赢过,很少能达成平局或者胜利,非常具有挑战性,可以通过调整难度选择来获得更适合你的挑战级别。以下是完整代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>六一四连棋大作战 - 高级AI版</title>
    <style>
        body {
            font-family: 'Comic Sans MS', cursive;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
            margin: 0;
            background-color: #f5e6ff;
        }
        #game-container {
            display: flex;
            flex-direction: column;
            align-items: center;
        }
        #board {
            display: grid;
            grid-template-columns: repeat(7, 80px);
            grid-template-rows: repeat(6, 80px);
            gap: 5px;
            background-color: #73a6ff;
            padding: 10px;
            border-radius: 15px;
            box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
        }
        .cell {
            width: 80px;
            height: 80px;
            background-color: white;
            border-radius: 50%;
            box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.2);
            cursor: pointer;
        }
        .yellow { background-color: #ffd700; box-shadow: 0 0 10px #ffd700; }
        .red { background-color: #ff4500; box-shadow: 0 0 10px #ff4500; }
        .hover-yellow { background-color: #ffd700; opacity: 0.5; }
        .hover-red { background-color: #ff4500; opacity: 0.5; }
        #controls {
            margin-top: 20px;
            display: flex;
            gap: 10px;
        }
        button {
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            background-color: #9966cc;
            color: white;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        button:hover { background-color: #8a56b7; }
        #message {
            margin-top: 20px;
            font-size: 24px;
            font-weight: bold;
            color: #333;
        }
        #difficulty {
            margin-top: 10px;
            padding: 8px;
            border-radius: 5px;
            border: 1px solid #9966cc;
        }
    </style>
</head>
<body>
    <h1>六一快乐!四连棋大作战 - 高级AI版</h1>
    <div id="controls">
        <button id="start-yellow">我先下(黄)</button>
        <button id="start-red">AI 先下(红)</button>
        <button id="reset">重新开始游戏</button>
    </div>
    <select id="difficulty">
        <option value="3">简单 (思考3步)</option>
        <option value="5" selected>中等 (思考5步)</option>
        <option value="7">困难 (思考7步)</option>
    </select>
    <div id="message"></div>
    <div id="game-container">
        <div id="board"></div>
    </div>
    <script>
        const ROWS = 6, COLS = 7;
        let board = Array(ROWS).fill().map(() => Array(COLS).fill(null));
        let currentPlayer = null, gameOver = false;
        const AI_PLAYER = 'red', HUMAN_PLAYER = 'yellow';
        const boardElement = document.getElementById('board');
        const messageElement = document.getElementById('message');
        const [startYellowButton, startRedButton, resetButton] = ['start-yellow', 'start-red', 'reset'].map(id => document.getElementById(id));
        const difficultySelect = document.getElementById('difficulty');

        // 创建棋盘
        function createBoard() {
            boardElement.innerHTML = '';
            for (let row = 0; row < ROWS; row++) {
                for (let col = 0; col < COLS; col++) {
                    const cell = document.createElement('div');
                    cell.className = 'cell';
                    cell.dataset.row = row;
                    cell.dataset.col = col;
                    cell.addEventListener('click', handleCellClick);
                    cell.addEventListener('mouseenter', showPreview);
                    cell.addEventListener('mouseleave', removePreview);
                    boardElement.appendChild(cell);
                }
            }
        }

        // 处理单元格点击
        function handleCellClick(e) {
            if (gameOver || currentPlayer !== HUMAN_PLAYER) return;
            const col = +e.target.dataset.col;
            const row = findNextEmptyRow(col);
            if (row !== -1 && (row === ROWS - 1 || board[row + 1][col] !== null)) {
                placePiece(row, col);
                if (!checkWin(row, col)) {
                    switchPlayer();
                    setTimeout(aiMove, 500);
                }
            }
        }

        // AI 下棋 - 使用Minimax算法
        function aiMove() {
            const difficulty = parseInt(difficultySelect.value);
            const [bestMove, _] = minimax(board, difficulty, -Infinity, Infinity, true);
            if (bestMove !== null && bestMove !== undefined) {
                makeMove(bestMove);
            }
        }

        // Minimax算法实现,带有α-β剪枝
        function minimax(board, depth, alpha, beta, maximizingPlayer) {
            // 检查游戏是否结束或达到最大深度
            const gameResult = evaluateBoard(board);
            if (depth === 0 || gameResult !== null) {
                return [null, gameResult !== null ? gameResult : evaluatePosition(board)];
            }
            const validCols = getValidColumns(board);
            if (maximizingPlayer) {
                let value = -Infinity;
                let bestCol = validCols[0];
                for (const col of validCols) {
                    const row = findNextEmptyRowInBoard(board, col);
                    const newBoard = copyBoard(board);
                    newBoard[row][col] = AI_PLAYER;
                    const newScore = minimax(newBoard, depth - 1, alpha, beta, false)[1];
                    if (newScore > value) {
                        value = newScore;
                        bestCol = col;
                    }
                    alpha = Math.max(alpha, value);
                    if (alpha >= beta) break;
                }
                return [bestCol, value];
            } else {
                let value = Infinity;
                let bestCol = validCols[0];
                for (const col of validCols) {
                    const row = findNextEmptyRowInBoard(board, col);
                    const newBoard = copyBoard(board);
                    newBoard[row][col] = HUMAN_PLAYER;
                    const newScore = minimax(newBoard, depth - 1, alpha, beta, true)[1];
                    if (newScore < value) {
                        value = newScore;
                        bestCol = col;
                    }
                    beta = Math.min(beta, value);
                    if (alpha >= beta) break;
                }
                return [bestCol, value];
            }
        }
        // 评估棋盘状态
        function evaluateBoard(board) {
            // 检查AI是否获胜
            if (checkWinInBoard(board, AI_PLAYER)) return Infinity;
            // 检查玩家是否获胜
            if (checkWinInBoard(board, HUMAN_PLAYER)) return -Infinity;
            // 检查平局
            if (isBoardFull(board)) return 0;
            // 游戏未结束
            return null;
        }
        // 评估棋盘位置分数
        function evaluatePosition(board) {
            let score = 0;
            // 中心列优先
            const centerCol = Math.floor(COLS / 2);
            for (let row = 0; row < ROWS; row++) {
                if (board[row][centerCol] === AI_PLAYER) score += 3;
            }
            // 评估所有可能的四连位置
            score += evaluateLines(board, AI_PLAYER);
            score -= evaluateLines(board, HUMAN_PLAYER);
            return score;
        }
        // 评估所有可能的四连位置
        function evaluateLines(board, player) {
            let score = 0;       
            // 水平方向
            for (let row = 0; row < ROWS; row++) {
                for (let col = 0; col < COLS - 3; col++) {
                    score += evaluateWindow(board, row, col, 0, 1, player);
                }
            }

            // 垂直方向
            for (let row = 0; row < ROWS - 3; row++) {
                for (let col = 0; col < COLS; col++) {
                    score += evaluateWindow(board, row, col, 1, 0, player);
                }
            }
            // 正对角线
            for (let row = 0; row < ROWS - 3; row++) {
                for (let col = 0; col < COLS - 3; col++) {
                    score += evaluateWindow(board, row, col, 1, 1, player);
                }
            }
            // 反对角线
            for (let row = 3; row < ROWS; row++) {
                for (let col = 0; col < COLS - 3; col++) {
                    score += evaluateWindow(board, row, col, -1, 1, player);
                }
            }
            return score;
        }

        // 评估一个四连窗口
        function evaluateWindow(board, row, col, dr, dc, player) {
            let score = 0;
            let opponent = player === AI_PLAYER ? HUMAN_PLAYER : AI_PLAYER;
            let playerCount = 0;
            let emptyCount = 0;
            let opponentCount = 0;
            for (let i = 0; i < 4; i++) {
                const r = row + i * dr;
                const c = col + i * dc;
                if (board[r][c] === player) playerCount++;
                else if (board[r][c] === null) emptyCount++;
                else opponentCount++;
            }
            // 根据窗口中的棋子数量评分
            if (playerCount === 4) score += 100;
            else if (playerCount === 3 && emptyCount === 1) score += 5;
            else if (playerCount === 2 && emptyCount === 2) score += 2;
            if (opponentCount === 3 && emptyCount === 1) score -= 4;
            return score;
        }
        
        // 获取所有有效列
        function getValidColumns(board) {
            const validCols = [];
            for (let col = 0; col < COLS; col++) {
                if (findNextEmptyRowInBoard(board, col) !== -1) {
                    validCols.push(col);
                }
            }
            return validCols;
        }

        // 复制棋盘
        function opyBoard(board) {
            return board.map(row => [...row]);
        }

        // 在指定棋盘上查找下一个空行
        function findNextEmptyRowInBoard(board, col) {
            for (let row = ROWS - 1; row >= 0; row--) {
                if (board[row][col] === null) return row;
            }
            return -1;
        }
        // 在指定棋盘上检查是否获胜
        function checkWinInBoard(board, player) {
            // 检查水平方向
            for (let row = 0; row < ROWS; row++) {
                for (let col = 0; col < COLS - 3; col++) {
                    if (board[row][col] === player &&
                        board[row][col+1] === player &&
                        board[row][col+2] === player &&
                        board[row][col+3] === player) {
                        return true;
                    }
                }
            }
            // 检查垂直方向
            for (let row = 0; row < ROWS - 3; row++) {
                for (let col = 0; col < COLS; col++) {
                    if (board[row][col] === player &&
                        board[row+1][col] === player &&
                        board[row+2][col] === player &&
                        board[row+3][col] === player) {
                        return true;
                    }
                }
            }
            // 检查正对角线
            for (let row = 0; row < ROWS - 3; row++) {
                for (let col = 0; col < COLS - 3; col++) {
                    if (board[row][col] === player &&
                        board[row+1][col+1] === player &&
                        board[row+2][col+2] === player &&
                        board[row+3][col+3] === player) {
                        return true;
                    }
                }
            }

            // 检查反对角线
            for (let row = 3; row < ROWS; row++) {
                for (let col = 0; col < COLS - 3; col++) {
                    if (board[row][col] === player &&
                        board[row-1][col+1] === player &&
                        board[row-2][col+2] === player &&
                        board[row-3][col+3] === player) {
                        return true;
                    }
                }
            }
            return false;
        }

        // 执行落子
        function makeMove(col) {
            const row = findNextEmptyRow(col);
            placePiece(row, col);
            if (!checkWin(row, col)) switchPlayer();
        }

        // 查找下一个空行
        function findNextEmptyRow(col) {
            for (let row = ROWS - 1; row >= 0; row--) {
                if (board[row][col] === null) return row;
            }
            return -1;
        }
        
        // 放置棋子
        function placePiece(row, col) {
            board[row][col] = currentPlayer;
            document.querySelector(`.cell[data-row="${row}"][data-col="${col}"]`).classList.add(currentPlayer);
        }
        
        // 检查是否获胜
        function checkWin(row, col) {
            const directions = [[1,0],[0,1],[1,1],[1,-1]];
            for (const [dr, dc] of directions) {
                let count = 1;
                for (let i = 1; i < 4; i++) {
                    const r = row + i * dr, c = col + i * dc;
                    if (r >= 0 && r < ROWS && c >= 0 && c < COLS && board[r][c] === currentPlayer) count++;
                    else break;
                }
                for (let i = 1; i < 4; i++) {
                    const r = row - i * dr, c = col - i * dc;
                    if (r >= 0 && r < ROWS && c >= 0 && c < COLS && board[r][c] === currentPlayer) count++;
                    else break;
                }
                if (count >= 4) {
                    gameOver = true;
                    messageElement.textContent = `${currentPlayer === HUMAN_PLAYER ? '你' : 'AI'} 获胜!`;
                    return true;
                }
            }
            if (board.every(row => row.every(cell => cell !== null))) {
                gameOver = true;
                messageElement.textContent = '平局!';
                return true;
            }
            return false;
        }
        
        // 检查棋盘是否已满
        function isBoardFull(board) {
            return board.every(row => row.every(cell => cell !== null));
        }
        
        // 切换玩家
        function switchPlayer() {
            if (gameOver) return;
            currentPlayer = currentPlayer === HUMAN_PLAYER ? AI_PLAYER : HUMAN_PLAYER;
            messageElement.textContent = `${currentPlayer === HUMAN_PLAYER ? '你' : 'AI'} 回合`;
        }
        
        // 显示棋子占位预显示
        function showPreview(e) {
            if (gameOver || currentPlayer !== HUMAN_PLAYER) return;
            const col = +e.target.dataset.col;
            const row = findNextEmptyRow(col);
            if (row !== -1) {
                document.querySelector(`.cell[data-row="${row}"][data-col="${col}"]`)
                    .classList.add(`hover-${currentPlayer}`);
            }
        }

        // 移除棋子占位预显示
        function removePreview() {
            document.querySelectorAll('.cell').forEach(cell => {
                cell.classList.remove('hover-yellow', 'hover-red');
            });
        }

        // 重置游戏
        function resetGame() {
            board = Array(ROWS).fill().map(() => Array(COLS).fill(null));
            document.querySelectorAll('.cell').forEach(cell => {
                cell.className = 'cell';
            });
            currentPlayer = null;
            gameOver = false;
            messageElement.textContent = '';
            startYellowButton.disabled = false;
            startRedButton.disabled = false;
        }

        // 事件监听
        startYellowButton.addEventListener('click', () => {
            currentPlayer = HUMAN_PLAYER;
            messageElement.textContent = '你 回合';
            startYellowButton.disabled = true;
            startRedButton.disabled = true;
        });
        startRedButton.addEventListener('click', () => {
            currentPlayer = AI_PLAYER;
            messageElement.textContent = 'AI 回合';
            startYellowButton.disabled = true;
            startRedButton.disabled = true;
            setTimeout(aiMove, 500);
        });
        resetButton.addEventListener('click', resetGame);
        createBoard();
    </script>
</body>
</html>

总结

谢谢大家看了这么久的文章,当然代码依然还有一些功能没有实现,我也会继续改进并进行学习,总之,这一次指导AI做游戏收获满满,谢谢大家!