我用AI实现四子棋

346 阅读24分钟

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

四连棋示意图

四连棋是一款经典的策略型桌面游戏,也被称为"四子棋"、"连珠棋"或"connect 4"。它的规则简单易懂,适合各年龄段玩家,核心目标是通过策略布局,使己方的棋子在横、竖、斜方向上形成连续的四个,从而获胜。

基本规则

1. 棋盘与棋子

  • 棋盘:通常是一个垂直放置的网格,标准尺寸为7列×6行(共42个格子),底部有支架支撑,棋子可从上方放入列中并垂直下落。
  • 棋子:双方各使用一种颜色的棋子(如红色和黄色),轮流放置。

2. 回合与放置方式

  • 轮流行动:玩家交替进行,每次将一枚棋子放入棋盘顶部的任意一列(棋子会自动落到该列的最底层空格里)。
  • 禁止操作:不能跳过空格或横向、纵向移动已放置的棋子,只能从列的顶端放入。

3. 胜利条件

  • 四连珠:当己方的棋子在以下任意方向形成连续的四个时,立即获胜:
    • 横向:同一行中连续四个棋子
    • 纵向:同一列中连续四个棋子
    • 斜向:左上至右下或右上至左下的斜线上连续四个棋子
  • 平局:若棋盘全部填满且无人达成四连珠,则判定为平局。

网上有哪些四连棋卖

社细立体四子棋四连棋

产品图片

特点:可用于儿童玩具空间对战,也适合双人桌面游戏,能够锻炼玩家的空间思维和对战能力。

魔圣魔方玩具趣味立体四子棋四连棋重力五子棋

产品图片

特点:将四子棋、五子棋与重力元素相结合,增加了游戏的趣味性和挑战性,适合儿童棋类玩具益智亲子双人桌游。

小卡尼儿童四连环棋

产品图片 特点:专门为儿童设计,外观可能比较时尚,适合小学生游戏,能够培养儿童的益智能力和策略思维。


如何指导AI实现四连棋

开发流程

  1. 需求分析

    • 确定需要的功能模块
    • 规划开发步骤
  2. 构建基础页面 打开以后差不多就像下面这样。

image.png

但是点击按钮跳转到的页面是没有东西的,我们接着往下写提示词。

帮我实现四连棋的小游戏,将公共部分提取,方便多模式直接使用。

然后就会生成一个js文件,里面是四子棋的一个基本的规则,以及判断胜负之类的代码,接下来将你的LV0_双人手动下棋.html文件塞还有生成的js文件一并给ai,给提示词说

帮我实现双人手动下棋页面功能,要求,在一个页面中,点击开始游戏以后开始,红方先手下棋,下完以后到绿方,直到一方获胜,规则参考四连棋,并且添加一个返回标题按钮用于返回home.html。

ai写完以后,再次打开home页面,点击第一个模式可能会报错,但是没关系,点击f12打开控制台,将报错信息复制下来,塞给ai,让ai帮你修复就行。最终效果差不多就和下面差不多,分数是我最终的成品,你们跟着写的没有也没关系

image.png

接下来实现第二个页面,给ai提示词,依旧将你要写的文件塞给ai,以及js代码,之前完成的第一个页面文件也可以塞给ai,让ai参考。

帮我写一个低级人机对战功能,要求,点击开始游戏以后,用户下棋,下完以后低级人机自动下棋,并且随机下1-7。

一般第一个页面实现了以后第二个会变得简单很多。最终就是下面这个效果

5.gif 低级ai很蠢对吧,没关系到后面有高级ai,可以变得更加强大

我们继续第三个模式,在线模式,就是两个用户可以通过两部手机进行对战,不再局限于一个页面,这里我们要用到一个组件socket.io,那么我们继续写提示词,并且将之前完成的页面文件全部塞给ai

帮我用socket.io实现多人实时对战模式,要求能够一人一个手机实时对战,玩家1可以创建房间,等待玩家2加入,玩家2输入房间id则加入房间,两人加入房间以后就可以点击准备,双方都点击准备以后倒计时3秒开始游戏,如果一方取消准备则会通知另一方对方取消了准备,则不会开始游戏,并且在上面显示对方的一个实时在线状态,来判断对方是否离线,当房间里用户都退出时,房间进行销毁,每隔5分钟检测一次房间状况,每个房间只能进入两个玩家。

然后ai会帮你生成两个js文件,一个server.js服务器文件,一个是online-game.js用于与服务器之间通讯交互数据,注意server.js需要放在顶级目录,不要放在目录夹层里,不然会启动不了,而online-game.js和srcipt.js放在一起就行。也就是四子棋基本规代码文件。

在这一个模式中可能会报很多错,需要很多的耐心去解决,我碰到过的最多的错误就行,服务器链接错误。但好在ai足够强大,将错误发给ai,并且说明你遇到的错误,他就会自己帮你修复。最终的一个效果就如下

6.gif

完成到这,那么最难的部分完成了,接下来就是高级人机对战,我们继续将写完的全部文件塞给ai

帮我完成高级人机对战页面,要求高级人机能够预判用户的行动,获胜优先,防止用户获胜其次,规则参考四子棋。

ai帮你写完了这个页面以后,你去试会发现,ai并不是很强,碰到很多种情况都没有办法应对,但是没有关系,我们直接用ai去指导我们的人机,就是用ai和人机下棋,并且将ai的每一次获胜结果发给ai,让ai帮忙加强。最终效果就是能够预判用户的7步棋,已经是非常强悍了,一般的人都打不过我们的高级人机了。

这里我找了一个人来测试我的高级ai,让他熟悉了规则以后来挑战高级ai下面就是整个对战过程,可以看到我们的高级人机也是非常给力

7.gif

最终完成了之后,可以让ai帮你写一个全局css样式来美化你的棋盘,页面,以及按钮之类的,并且做一个适配,让手机访问也能够看到整个棋盘,通过手机也能够操控棋盘进行下棋。

页面代码

主页

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>四连棋 - 主页</title>
    <link rel="stylesheet" href="../assets/styles.css">
</head>
<body>
    <div class="game-container">
        <h1 class="game-title">四连棋</h1>
        <div class="game-mode-selector">
            <h3>选择游戏模式:</h3>
            <div class="game-options">
                <div class="game-option" onclick="navigateToGame('two-player')">
                    双人模式
                </div>
                <div class="game-option" onclick="navigateToGame('easy-ai')">
                    低级人机对战
                </div>
                <div class="game-option" onclick="navigateToGame('online')">
                    在线双人模式
                </div>
                <div class="game-option" onclick="navigateToGame('hard-ai')">
                    高级人机对战
                </div>
            </div>
        </div>
    </div>

    <script>
        function navigateToGame(mode) {
            // 使用更可靠的路径处理
            const basePath = window.location.pathname
                .split('/')
                .slice(0, -1)
                .join('/');
            window.location.href = `${basePath}/${mode}.html`;
        }
    </script>
</body>
</html>

双人模式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="../assets/styles.css">
    <title>双人模式</title>
</head>
<body>
<div class="game-container">
    <button id="back-to-home" class="back-button">返回主页</button>
    <div class="scoreboard">
        <div class="score red-score">
            <span>粉色玩家: </span>
            <span id="red-score">0</span>
        </div>
        <div class="score green-score">
            <span>绿色玩家: </span>
            <span id="green-score">0</span>
        </div>
    </div>

    <h1>四连棋:双人对战</h1>
    <div class="game-info">
        <span id="game-mode"></span>
        <span>当前玩家: <span id="current-player"></span></span>
    </div>

    <div class="board-container">
        <div class="board" id="board"></div>
    </div>

    <button id="start-game">开始游戏</button>
    <button id="restart-game" style="display: none;">重新开始</button>

</div>

<script src="../components/script.js"></script>
</body>
</html>

低级人机对战

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="../assets/styles.css">
    <title>低级人机对战</title>
</head>
<body>
<div class="game-container">
    <button id="back-to-home" class="back-button">返回主页</button>

    <div class="scoreboard">
        <div class="score red-score">
            <span>粉色玩家: </span>
            <span id="red-score">0</span>
        </div>
        <div class="score green-score">
            <span>绿色玩家: </span>
            <span id="green-score">0</span>
        </div>
    </div>

    <h1>四连棋:低级人机对战</h1>
    <div class="player-info">
        <span id="game-mode"></span>
        <span>当前玩家: <span id="current-player"></span></span>
    </div>

    <div class="board-container">
        <div class="board" id="board"></div>
    </div>

    <button id="start-game">开始游戏</button>
    <button id="restart-game" style="display: none;">重新开始</button>

</div>
<script src="../components/script.js"></script>
</body>
</html>

多人实时对战

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <link rel="stylesheet" href="../assets/styles.css">
    <title>多人实时对战</title>
</head>
<body>
<div class="game-container">

    <div class="room-controls">
        <button id="back-to-home" class="back-button">返回主页</button>

        <input type="text" id="room-id" placeholder="输入房间号" class="room-input">
        <div class="room-buttons">
            <button id="create-room" class="room-button">创建</button>
            <button id="join-room" class="room-button">加入</button>
        </div>
        <div id="room-status" class="room-status"></div>
    </div>

    <div class="scoreboard">
        <div class="score red-score">
            <span>粉色玩家: </span>
            <span id="red-score">0</span>
        </div>
        <div class="score green-score">
            <span>绿色玩家: </span>
            <span id="green-score">0</span>
        </div>
    </div>

    <h1>四连棋:多人实时对战</h1>
    <div class="game-info">
        <span id="game-mode-display">等待连接...</span><br>
        <span>当前玩家:<br><span id="current-player"></span></span>
    </div>

    <!-- 在scoreboard和board-container之间添加准备控制区域 -->
    <div class="ready-controls">
        <button id="ready-button" class="ready-button">准备</button>
        <div id="countdown-display" class="countdown"></div>
    </div>

    <div class="board-container">
        <div class="board" id="board"></div>
    </div>

</div>

<!-- 引入Socket.io客户端库 -->
<script src="../components/script.js"></script>
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<script src="../components/online-game.js"></script>
</body>
</html>

高级人机对战

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="../assets/styles.css">
    <title>高级人机对战</title>
</head>
<body>
<div class="game-container">
    <button id="back-to-home" class="back-button">返回主页</button>

    <div class="scoreboard">
        <div class="score red-score">
            <span>粉色玩家: </span>
            <span id="red-score">0</span>
        </div>
        <div class="score green-score">
            <span>绿色玩家: </span>
            <span id="green-score">0</span>
        </div>
    </div>

    <h1>四连棋:高级人机对战</h1>
    <div class="player-info">
        <span id="game-mode">高级AI模式</span><br>
        <span>当前玩家: <span id="current-player"></span></span>
    </div>
    
    <!-- 已存在的换行div防止溢出 -->
    <div style="clear:both; margin: 10px 0;"></div>

    <div class="board-container">
        <div class="board" id="board"></div>
    </div>

    <button id="start-game">开始游戏</button>
    <button id="restart-game" style="display: none;">重新开始</button>
</div>

<script src="../components/script.js"></script>

<script>
    document.addEventListener('DOMContentLoaded', () => {
        const game = new ConnectFourGame('hard-ai');
        if (game.getElements()) {
            game.init();

            game.elements.board.addEventListener('click', (e) => {
                if (e.target.classList.contains('cell')) {
                    const col = parseInt(e.target.dataset.col);
                    game.handlePlayerMove(col);
                }
            });
        }
    });
</script>
</body>
</html>

逻辑代码

online-game.js

/**
 * OnlineGame类继承自ConnectFourGame,实现联网对战功能
 * 处理房间创建、加入、游戏开始和移动同步等网络交互逻辑
 */
class OnlineGame extends ConnectFourGame {
    constructor() {
        super('online');
        this.socket = io(); // 确保在构造函数中初始化socket
        this.roomId = null;
        this.playerRole = null;
        this.isReady = false;
        this.countdownInterval = null;
    }

    // 修改: 增强初始化方法
    init() {
        if (!this.getElements()) {
            console.error('初始化失败: 无法获取基础元素');
            return;
        }

        // 确保获取在线模式特有元素
        if (!this.getOnlineElements()) {
            console.error('初始化失败: 无法获取在线模式元素');
            return;
        }

        this.createBoard();
        this.setupSocketEvents();
        this.setupRoomControls();
        this.setupReadyControls();

        // 确保完全移除 startGameButton 引用
        if ('startGameButton' in this.elements) {
            delete this.elements.startGameButton;
        }
    }

    // 新增: 获取在线模式特有元素
    getOnlineElements() {
        const onlineElements = {
            createRoomBtn: document.getElementById('create-room'),
            joinRoomBtn: document.getElementById('join-room'),
            readyButton: document.getElementById('ready-button')
        };

        for (const [key, element] of Object.entries(onlineElements)) {
            if (!element) {
                console.error(`在线模式必需元素缺失: ${key}`);
                return false;
            }
        }

        this.elements = {...this.elements, ...onlineElements};
        return true;
    }


    // 设置准备控制
    setupReadyControls() {
        this.elements.readyButton.addEventListener('click', () => {
            this.handleReady();
        });
    }

    // 处理准备状态
    handleReady() {
        if (!this.isReady) {
            this.isReady = true;
            this.elements.readyButton.textContent = "取消准备";
            this.socket.emit('player-ready', this.roomId);
        } else {
            this.isReady = false;
            this.elements.readyButton.textContent = "准备";
            this.socket.emit('player-cancel-ready', this.roomId);
        }
    }

    /**
     * 获取所有必要的DOM元素
     * 继承父类元素并添加联网模式特有元素
     * @returns {boolean} 是否成功获取所有元素
     */
    getElements() {
        // 先调用修改后的父类方法获取基础元素
        if (!super.getElements()) return false;

        // 添加在线模式特有元素
        this.elements = {
            ...this.elements,
            createRoomBtn: document.getElementById('create-room'),
            joinRoomBtn: document.getElementById('join-room'),
            roomIdInput: document.getElementById('room-id'),
            roomStatus: document.getElementById('room-status'),
            readyButton: document.getElementById('ready-button'),
            countdownDisplay: document.getElementById('countdown-display')
        };

        // 验证在线模式必需元素
        const requiredOnlineElements = ['readyButton', 'createRoomBtn', 'joinRoomBtn'];
        for (const key of requiredOnlineElements) {
            if (!this.elements[key]) {
                console.error(`在线模式必需元素缺失: ${key}`);
                return false;
            }
        }

        return true;
    }


    /**
     * 设置房间控制按钮的事件监听
     * 包含创建房间和加入房间的逻辑
     */
    setupRoomControls() {
        console.log('尝试绑定房间控制按钮...');

        // 创建房间按钮事件
        this.elements.createRoomBtn.addEventListener('click', () => {
            const roomId = this.elements.roomIdInput.value ||
                Math.random().toString(36).substring(2, 6).toUpperCase();
            this.socket.emit('create-room', roomId);  // 发送创建房间请求
        });

        // 加入房间按钮事件
        this.elements.joinRoomBtn.addEventListener('click', () => {
            const roomId = this.elements.roomIdInput.value;
            if (!roomId) {
                alert('请输入房间号');
                return;
            }
            this.socket.emit('join-room', roomId);  // 发送加入房间请求
        });
    }

    /**
     * 设置所有Socket.IO事件监听
     * 处理房间状态变更、游戏状态同步等网络事件
     */
    setupSocketEvents() {
        // 增强错误处理 - 添加详细的错误类型处理
        this.socket.on('connect_error', (error) => {
            console.error('WebSocket连接错误:', error);
            // 自动重连机制 - 5秒后重试
            setTimeout(() => {
                console.log('尝试重新连接...');
                this.socket.connect();
            }, 5000);
        });

        // 添加详细的错误类型处理
        this.socket.on('error', (error) => {
            console.error('WebSocket错误:', error);
            
            // 处理特定错误类型
            if (error.type === 'TransportError') {
                setTimeout(() => this.socket.connect(), 5000);
            } else if (error.type === 'PingTimeout') {
                setTimeout(() => this.socket.connect(), 5000);
            }
        });

        // 添加连接状态监听
        this.socket.on('connect', () => {
            console.log('成功连接到服务器');
            this.updateRoomStatus('已连接到服务器');
            // 重新加入房间逻辑
            if (this.roomId) {
                this.socket.emit('rejoin-room', this.roomId);
            }
        });

        // 添加重连尝试和失败处理
        this.socket.on('reconnect_attempt', (attemptNumber) => {
            console.log(`第${attemptNumber}次尝试重新连接...`);
        });

        this.socket.on('reconnect_failed', () => {
            console.error('重新连接失败');
        });

        // 房间创建成功事件
        this.socket.on('room-created', (data) => {
            this.roomId = data.roomId;
            this.playerRole = 'red';  // 创建者默认为红方
            this.updateRoomStatus(`房间 ${this.roomId} 创建成功,等待对手加入...`);
            // 更新UI显示
            this.elements.gameModeDisplay.textContent = `房主(粉色玩家),房间号: ${this.roomId}`;
            this.elements.currentPlayerDisplay.textContent = "等待对手...";
        });

        // 成功加入房间事件
        this.socket.on('room-joined', (data) => {
            this.roomId = data.roomId;
            this.playerRole = 'green';  // 加入者默认为绿方
            this.updateRoomStatus(`已加入房间 ${this.roomId}`);
            // 更新UI显示
            this.elements.gameModeDisplay.textContent = `加入者(绿色玩家),房间号: ${this.roomId}`;
            this.elements.currentPlayerDisplay.textContent = "已连接房间";
        });

        // 玩家更新事件
        this.socket.on('players-updated', (data) => {
            if (data.players.length === 2) {
                this.updateRoomStatus('对手已加入房间');
                this.elements.currentPlayerDisplay.textContent = "双方已连接,请准备开始游戏";
            } else {
                this.updateRoomStatus('等待对手加入...');
            }
        });

        // 对手准备事件
        this.socket.on('opponent-ready', () => {
            this.elements.countdownDisplay.textContent = '对手已准备';
        });

        // 对手取消准备事件
        this.socket.on('opponent-cancel-ready', () => {
            this.elements.countdownDisplay.textContent = '对手已取消准备';
        });

        // 对手移动事件
        this.socket.on('opponent-move', (data) => {
            if (this.gameStarted && this.currentPlayer !== this.playerRole) {
                this.handleOpponentMove(data.col); // 确保正确处理对手移动
            }
        });

        /**
         * 清除棋盘状态方法
         */
        this.clearBoard = () => {
            this.board = Array(6).fill().map(() => Array(7).fill(null));
            const cells = this.elements.board.querySelectorAll('.cell');
            cells.forEach(cell => {
                cell.className = 'cell';
                cell.style.pointerEvents = 'none';
            });
        }

        // 修改: 处理游戏结束事件 - 使用服务器同步的分数
        this.socket.on('game-ended', (data) => {
            if (data.winner === 'draw') {
                this.showDraw();
            } else {
                // 直接使用服务器同步的分数(移除本地加分逻辑)
                this.scores.red = data.redScore || 0;
                this.scores.green = data.greenScore || 0;
                this.elements.redScoreDisplay.textContent = this.scores.red;
                this.elements.greenScoreDisplay.textContent = this.scores.green;

                const winner = data.winner === 'red' ? '粉色玩家' : '绿色玩家';
                this.elements.currentPlayerDisplay.textContent = `${winner}获胜!`;
            }
            
            // 强制重置游戏状态
            this.gameStarted = false;
            this.currentPlayer = 'red'; // 重置先手为红色
            this.clearBoard(); // 强制清除棋盘
            this.resetReadyStatus();
        });

        // 新增: 处理服务端发送的重置准备状态事件
        this.socket.on('reset-ready-status', () => {
            this.resetReadyStatus();
        });

        // 房间相关错误事件
        this.socket.on('room-exists', () => alert('房间已存在,请使用其他房间号'));
        this.socket.on('room-not-found', () => alert('房间不存在,请检查房间号'));
        this.socket.on('room-full', () => alert('房间已满,请加入其他房间'));

        // 处理玩家离开事件
        this.socket.on('player-left', (data) => {
            console.log(`玩家离开: ${data.playerId}`);
            this.updateRoomStatus('对手已断开连接');
            this.gameStarted = false;
        });

        // 处理连接断开事件
        this.socket.on('disconnect', () => {
            console.log('已断开与服务器的连接');
            this.gameStarted = false;
        });

        // 添加所有玩家准备就绪事件
        this.socket.on('all-players-ready', () => {
            this.startCountdown();
        });

        // 添加对手取消准备事件
        this.socket.on('player-cancel-ready', () => {
            this.opponentReady = false;
            clearInterval(this.countdownInterval);
            this.elements.countdownDisplay.textContent = '对手取消了准备';
        });

    }

    /**
     * 设置UI事件监听
     * 包含棋盘点击事件等
     */
    setupEventListeners() {
        this.elements.board.addEventListener('click', (e) => {
            if (e.target.classList.contains('cell')) {
                const col = parseInt(e.target.dataset.col);
                this.handlePlayerMove(col);
            }
        });

        // 新增: 绑定开始游戏按钮事件
        this.elements.startGameButton.addEventListener('click', () => this.handleStartGame());
    }

    // 离开房间
    leaveRoom() {
        if (this.roomId) {
            // 重置游戏状态
            this.board = Array(6).fill().map(() => Array(7).fill(null));
            this.currentPlayer = 'red';
            this.gameStarted = false;

            // 发送离开房间请求
            this.socket.emit('leave-room', this.roomId);

            // 重置UI状态
            this.elements.startGameButton.disabled = true;
            this.updateRoomStatus('已离开房间');
            this.elements.gameModeDisplay.textContent = '等待连接...';

            // 清除棋盘显示
            const cells = this.elements.board.querySelectorAll('.cell');
            cells.forEach(cell => {
                cell.className = 'cell';
                cell.style.pointerEvents = 'none';
            });

            // 重置变量
            this.roomId = null;
            this.playerRole = null;

        }
    }


// 修改返回主页的处理
    handleBackToHome() {
        if (confirm('确定要返回主页吗?当前游戏进度将会丢失。')) {
            this.leaveRoom();  // 确保先离开房间
            window.location.href = '../views/home.html';
        }
    }

    /**
     * 处理游戏开始逻辑
     * 验证对手连接状态后开始游戏
     */
    handleStartGame() {
        // 在线模式不应该有这个方法的调用
        if (this.mode === 'online') return;

        this.startGame();
        this.updateRoomStatus('游戏进行中...');
    }

    /**
     * 处理玩家移动
     * @param {number} col - 要放置棋子的列索引
     */
    handlePlayerMove(col) {
        if (!this.gameStarted || this.currentPlayer !== this.playerRole) return;

        if (super.placePiece(col, this.currentPlayer)) {
            // 发送移动信息给对手
            this.socket.emit('player-move', {
                roomId: this.roomId,
                col: col,
                player: this.playerRole
            });

            // 只有房主(red)执行胜负判断
            if (this.playerRole === 'red') {
                if (this.checkForWin()) {
                    this.socket.emit('game-over', {
                        roomId: this.roomId,
                        winner: this.playerRole
                    });
                } else if (this.isBoardFull()) {
                    this.socket.emit('game-over', {
                        roomId: this.roomId,
                        winner: 'draw'
                    });
                } else {
                    this.switchPlayer();
                }
            } else {
                // 加入者(green)只切换玩家
                this.switchPlayer();
            }
        }
    }

    /**
     * 处理对手移动
     * @param {number} col - 对手放置棋子的列索引
     */
    handleOpponentMove(col) {
        const opponentColor = this.playerRole === 'red' ? 'green' : 'red';
        super.placePiece(col, opponentColor);

        // 只有房主(red)执行胜负判断
        if (this.playerRole === 'red') {
            if (this.checkForWin()) {
                this.socket.emit('game-over', {
                    roomId: this.roomId,
                    winner: opponentColor
                });
            } else if (this.isBoardFull()) {
                this.socket.emit('game-over', {
                    roomId: this.roomId,
                    winner: 'draw'
                });
            }
        }
        
        // 无论是否胜负判断,都要切换玩家
        this.switchPlayer();
    }

    /**
     * 更新房间状态显示
     * @param {string} message - 要显示的状态信息
     */
    updateRoomStatus(message) {
        this.elements.roomStatus.textContent = message;
    }

    // 新增倒计时开始方法
    startCountdown() {
        let countdown = 3;
        this.elements.countdownDisplay.textContent = `游戏即将开始: ${countdown}`;

        this.countdownInterval = setInterval(() => {
            countdown--;
            this.elements.countdownDisplay.textContent = `游戏即将开始: ${countdown}`;

            if (countdown <= 0) {
                clearInterval(this.countdownInterval);
                this.startGame();
                this.elements.countdownDisplay.textContent = '游戏开始!';
            }
        }, 1000);
    }

    // 修改: 增强重置准备状态方法
    resetReadyStatus() {
        this.isReady = false;
        this.elements.readyButton.textContent = '准备';
        this.elements.countdownDisplay.textContent = '';
        clearInterval(this.countdownInterval);
        if (this.roomId) {
            // 确保向服务器发送取消准备状态
            this.socket.emit('player-cancel-ready', this.roomId);
        }
    }


    // 修改开始游戏方法
    startGame() {
        super.startGame();
        this.elements.board.querySelectorAll('.cell').forEach(cell => {
            // 确保当前玩家可以交互
            cell.style.pointerEvents = this.currentPlayer === this.playerRole ? 'auto' : 'none';
        });

        // 新增: 明确设置当前玩家显示
        this.updateCurrentPlayerDisplay();

        // 新增: 确保棋盘点击事件绑定
        this.setupBoardClickEvent();
    }

    // 新增: 专门设置棋盘点击事件
    setupBoardClickEvent() {
        this.elements.board.addEventListener('click', (e) => {
            if (e.target.classList.contains('cell')) {
                const col = parseInt(e.target.dataset.col);
                this.handlePlayerMove(col);
            }
        });
    }

    // 修改: 确保updateCurrentPlayerDisplay正确处理交互状态
    updateCurrentPlayerDisplay() {
        if (this.gameStarted) {
            const playerText = this.currentPlayer === 'red' ? '粉色(1)' : '绿色(2)';
            const roleText = this.playerRole === 'red' ? '房主' : '加入者';
            this.elements.currentPlayerDisplay.innerHTML =
                `当前: <br>${playerText} (${roleText})`;

            // 修改: 准备按钮文本保持为"取消准备"
            if (this.isReady) {
                this.elements.readyButton.textContent = '取消准备';
            }

            // 设置棋盘交互状态
            const canInteract = this.currentPlayer === this.playerRole;
            this.elements.board.querySelectorAll('.cell').forEach(cell => {
                cell.style.pointerEvents = canInteract ? 'auto' : 'none';
            });
        } else {
            this.elements.currentPlayerDisplay.textContent = "游戏未开始";
        }
    }
}

// 页面加载完成后初始化游戏实例
document.addEventListener('DOMContentLoaded', () => {
    const game = new OnlineGame();
    game.init();
});


srcipt.js

// 公共游戏逻辑类
class ConnectFourGame {
    constructor(mode = 'two-player') {
        // 统一模式判断逻辑
        this.mode = this.determineGameMode(mode);
        
        this.board = Array(6).fill().map(() => Array(7).fill(null));
        this.currentPlayer = 'red';
        this.gameStarted = false;
        this.scores = {red: 0, green: 0};
        this.elements = {};
        
        // 新增: 在线模式相关属性
        this.isConnected = false;
        this.roomId = null;
        this.playerRole = null; // 'host' 或 'guest'
        this.socket = null;
    }

    // 新增: 集中处理游戏模式判断逻辑
    determineGameMode(paramMode) {
        if (window.location.pathname.includes('online')) return 'online';
        if (window.location.pathname.includes('hard-ai')) return 'hard-ai';
        if (window.location.pathname.includes('easy-ai')) return 'easy-ai';
        return paramMode || 'two-player';
    }

    // 修改: 统一游戏初始化方式
    init() {
        if (!this.getElements()) return;
        this.createBoard();
        this.setupEventListeners();
        
        // 根据模式执行特定初始化
        if (this.mode === 'online') {
            // 修改: 仅在线模式才尝试初始化在线逻辑
            if (typeof this.initOnlineMode === 'function') {
                this.initOnlineMode();
            }
        } else {
            this.updateCurrentPlayerDisplay();
        }
    }

    // 实现完整的在线模式初始化方法
    initOnlineMode() {
        // 1. 获取房间ID
        this.roomId = this.getRoomIdFromURL();
        
        // 3. 设置在线模式特有的事件监听
        this.setupOnlineEventListeners();
        
        // 4. 显示等待连接状态
        // this.showOnlineStatus('正在连接服务器...');
    }

    // 新增: 获取房间ID
    getRoomIdFromURL() {
        const params = new URLSearchParams(window.location.search);
        return params.get('roomId') || this.generateRoomId();
    }

    // 新增: 生成随机房间ID
    generateRoomId() {
        return Math.random().toString(36).substring(2, 8).toUpperCase();
    }





    // 新增: 发送在线消息
    sendOnlineMessage(message) {
        if (this.socket && this.socket.readyState === WebSocket.OPEN) {
            this.socket.send(JSON.stringify(message));
        }
    }

    // 新增: 设置在线模式特有的事件监听
    setupOnlineEventListeners() {
        if (this.elements.startGameButton) {
            this.elements.startGameButton.addEventListener('click', () => {
                this.sendOnlineMessage({ type: 'start' });
                this.handleStartGame();
            });
        }
    }

    // 新增: 显示在线状态
    showOnlineStatus(message, isError = false) {
        const statusElement = document.getElementById('online-status') || 
                            document.createElement('div');
        statusElement.id = 'online-status';
        statusElement.textContent = message;
        statusElement.className = isError ? 'error' : 'info';
        
        if (!document.getElementById('online-status')) {
            this.elements.board.parentNode.insertBefore(
                statusElement, 
                this.elements.board.nextSibling
            );
        }
    }

    // 获取DOM元素
    getElements() {
        // 基础元素是所有模式都需要的
        const baseElements = {
            board: document.getElementById('board'),
            currentPlayerDisplay: document.getElementById('current-player'),
            backToHomeButton: document.getElementById('back-to-home'),
            redScoreDisplay: document.getElementById('red-score'),
            greenScoreDisplay: document.getElementById('green-score'),
            gameModeDisplay: document.getElementById('game-mode-display') ||
                document.getElementById('game-mode') // 兼容不同命名
        };

        // 验证基础必要元素
        for (const [key, element] of Object.entries(baseElements)) {
            if (!element) {
                console.error(`基础元素缺失: ${key}`);
                return false;
            }
        }

        // 根据实际URL路径判断模式,而不仅仅是构造函数参数
        const isOnlineMode = window.location.pathname.includes('online');

        if (!isOnlineMode) {
            baseElements.startGameButton = document.getElementById('start-game');
            if (!baseElements.startGameButton) {
                console.error('本地模式必须包含开始按钮');
                return false;
            }
            baseElements.restartGameButton = document.getElementById('restart-game');
        }

        this.elements = baseElements;
        return true;
    }


    // 创建棋盘
    createBoard() {
        this.elements.board.innerHTML = '';

        for (let row = 0; row < 6; row++) {
            for (let col = 0; col < 7; col++) {
                const cell = document.createElement('div');
                cell.classList.add('cell');
                cell.dataset.row = row;
                cell.dataset.col = col;
                cell.style.pointerEvents = 'none';
                this.elements.board.appendChild(cell);
            }
        }
    }

    // 设置事件监听
    setupEventListeners() {
        // 通用事件监听
        this.elements.board.addEventListener('mouseover', (e) => this.handleCellHover(e));
        this.elements.board.addEventListener('mouseleave', (e) => this.handleCellLeave(e));
        this.elements.backToHomeButton.addEventListener('click', () => this.handleBackToHome());
        
        // 确保棋盘点击事件绑定
        this.elements.board.addEventListener('click', (e) => {
            if (e.target.classList.contains('cell')) {
                const col = parseInt(e.target.dataset.col);
                this.handlePlayerMove(col);
            }
        });

        // 仅非在线模式添加开始按钮事件
        if (!this.mode.includes('online') && this.elements.startGameButton) {
            this.elements.startGameButton.addEventListener('click', () => this.handleStartGame());
        }
    }


    // 处理开始游戏
    handleStartGame() {
        if (!this.gameStarted) {
            this.startGame();
            this.elements.startGameButton.textContent = "重新开始";
            this.elements.currentPlayerDisplay.textContent = "游戏开始!粉色玩家先手";
        } else {
            this.startGame();
            this.elements.currentPlayerDisplay.textContent = "游戏重新开始!粉色玩家先手";
        }
    }

    // 开始游戏
    startGame() {
        this.board = Array(6).fill().map(() => Array(7).fill(null));
        this.currentPlayer = 'red';
        this.gameStarted = true;

        const cells = this.elements.board.querySelectorAll('.cell');
        cells.forEach(cell => {
            cell.className = 'cell';
            cell.style.pointerEvents = 'auto';
        });

        setTimeout(() => {
            this.updateCurrentPlayerDisplay();
        }, 2000);
    }

    // 放置棋子
    placePiece(col, player) {
        for (let row = 5; row >= 0; row--) {
            if (this.board[row][col] === null) {
                this.board[row][col] = player;
                const cell = this.elements.board.children[row * 7 + col];
                cell.classList.remove('preview', 'preview-red', 'preview-green');
                cell.classList.add(player === 'red' ? 'red-piece' : 'green-piece');
                cell.style.pointerEvents = 'none';
                return true;
            }
        }
        return false;
    }

    // AI随机落子(低级AI)
    makeAIMove() {
        if (this.currentPlayer === 'green' && this.mode === 'easy-ai') {
            setTimeout(() => {
                const availableColumns = [];
                for (let col = 0; col < 7; col++) {
                    if (this.board[0][col] === null) {
                        availableColumns.push(col);
                    }
                }

                if (availableColumns.length > 0) {
                    const randomCol = availableColumns[Math.floor(Math.random() * availableColumns.length)];
                    this.placePiece(randomCol, 'green');
                    this.handleAIMoveResult();
                }
            }, 500);
        }
    }

    // 增强高级AI移动方法
    makeAdvancedAIMove() {
        if (this.currentPlayer === 'green' && this.mode === 'hard-ai') {
            setTimeout(() => {
                // 1. 检查AI是否有可以立即获胜的位置
                const winningMove = this.findWinningMove('green');
                if (winningMove !== null) {
                    this.placePiece(winningMove, 'green');
                    this.handleAIMoveResult();
                    return;
                }
                
                // 2. 检查玩家是否有即将获胜的位置并拦截
                const blockMove = this.findWinningMove('red');
                if (blockMove !== null) {
                    this.placePiece(blockMove, 'green');
                    this.handleAIMoveResult();
                    return;
                }
                
                // 3. 新增: 检查玩家是否有三连且下方有棋子的斜线需要拦截
                const blockDiagonalThree = this.findDiagonalThreeWithSupport('red');
                if (blockDiagonalThree !== null) {
                    this.placePiece(blockDiagonalThree, 'green');
                    this.handleAIMoveResult();
                    return;
                }
                
                // 4. 检查玩家是否有三连棋且可以延伸成四连的位置
                const blockThreeMove = this.findThreeWithSpace('red');
                if (blockThreeMove !== null) {
                    this.placePiece(blockThreeMove, 'green');
                    this.handleAIMoveResult();
                    return;
                }
                
                // 5. 使用MiniMax算法选择最佳位置
                let bestScore = -Infinity;
                let bestCol = null;
                
                for (let col = 0; col < 7; col++) {
                    if (this.board[0][col] === null) {
                        const row = this.findDropRow(col);
                        this.board[row][col] = 'green';
                        const score = this.minimax(this.board, 4, false);
                        this.board[row][col] = null;
                        
                        if (score > bestScore) {
                            bestScore = score;
                            bestCol = col;
                        }
                    }
                }
                
                if (bestCol !== null) {
                    this.placePiece(bestCol, 'green');
                    this.handleAIMoveResult();
                }
            }, 500);
        }
    }

    // 新增: 查找斜向三连且有下方支撑的棋子
    findDiagonalThreeWithSupport(player) {
        for (let col = 0; col < 7; col++) {
            if (this.board[0][col] !== null) continue;
            
            const row = this.findDropRow(col);
            this.board[row][col] = player;
            
            // 检查斜线三连并且下方有支撑
            const hasDiagonalThreeWithSupport = this.checkDiagonalThreeWithSupport(player, row, col);
            this.board[row][col] = null;
            
            if (hasDiagonalThreeWithSupport) {
                return col;
            }
        }
        return null;
    }

    // 新增: 检查斜向三连且下方有支撑
    checkDiagonalThreeWithSupport(player, row, col) {
        const directions = [
            [1, 1],   // 正对角线
            [1, -1]   // 反对角线
        ];
        
        for (const [dx, dy] of directions) {
            let count = 1;
            
            // 向一个方向检查
            for (let i = 1; i < 4; i++) {
                const r = row + i * dx;
                const c = col + i * dy;
                if (r < 0 || r >= 6 || c < 0 || c >= 7 || this.board[r][c] !== player) break;
                count++;
            }
            
            // 向相反方向检查
            for (let i = 1; i < 4; i++) {
                const r = row - i * dx;
                const c = col - i * dy;
                if (r < 0 || r >= 6 || c < 0 || c >= 7 || this.board[r][c] !== player) break;
                count++;
            }
            
            // 如果有三连,检查是否有下方支撑
            if (count >= 3) {
                // 检查目标位置下方是否有棋子
                const targetRow1 = row + 3 * dx;
                const targetCol1 = col + 3 * dy;
                const targetRow2 = row - 3 * dx;
                const targetCol2 = col - 3 * dy;
                
                // 检查正方向目标位置下方是否有棋子
                if (targetRow1 >= 0 && targetRow1 < 6 && targetCol1 >= 0 && targetCol1 < 7 && 
                    this.board[targetRow1][targetCol1] === null) {
                    // 检查下方是否有棋子支撑
                    if (targetRow1 + 1 < 6 && this.board[targetRow1 + 1][targetCol1] !== null) {
                        return true;
                    }
                }
                
                // 检查反方向目标位置下方是否有棋子
                if (targetRow2 >= 0 && targetRow2 < 6 && targetCol2 >= 0 && targetCol2 < 7 && 
                    this.board[targetRow2][targetCol2] === null) {
                    // 检查下方是否有棋子支撑
                    if (targetRow2 + 1 < 6 && this.board[targetRow2 + 1][targetCol2] !== null) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    // 新增: 查找三连且有延伸空间的棋
    findThreeWithSpace(player) {
        for (let col = 0; col < 7; col++) {
            if (this.board[0][col] !== null) continue;
            
            const row = this.findDropRow(col);
            this.board[row][col] = player;
            
            // 检查是否会形成三连且有延伸空间
            const threeWithSpace = this.checkThreeInARowWithSpace(player, row, col);
            this.board[row][col] = null;
            
            if (threeWithSpace) {
                return col;
            }
        }
        return null;
    }

    // 修改现有的findWinningMove方法,专注查找四连棋
    findWinningMove(player) {
        for (let col = 0; col < 7; col++) {
            if (this.board[0][col] !== null) continue;
            
            const row = this.findDropRow(col);
            this.board[row][col] = player;
            
            // 检查这次落子是否会形成四连
            if (this.checkForWin()) {
                this.board[row][col] = null; // 恢复棋盘状态
                return col;
            }
            
            this.board[row][col] = null;
        }
        return null;
    }

    // 新增: 查找拦截移动的方法
    findBlockingMove(player, length) {
        for (let col = 0; col < 7; col++) {
            if (this.board[0][col] !== null) continue;
            
            const row = this.findDropRow(col);
            this.board[row][col] = player;
            
            // 检查是否会形成指定长度的连棋
            if (this.checkLineOfLength(player, length, row, col)) {
                this.board[row][col] = null;
                return col;
            }
            
            this.board[row][col] = null;
        }
        return null;
    }

    // 新增: 检查三连且有延伸空间
    checkThreeInARowWithSpace(player, lastRow, lastCol) {
        // 检查所有方向
        const directions = [
            [0, 1],   // 水平
            [1, 0],   // 垂直
            [1, 1],   // 正对角线
            [1, -1]   // 反对角线
        ];
        
        for (const [dx, dy] of directions) {
            let count = 1; // 包括刚下的这个棋子
            
            // 向一个方向检查
            for (let i = 1; i < 4; i++) {
                const r = lastRow + i * dx;
                const c = lastCol + i * dy;
                if (r < 0 || r >= 6 || c < 0 || c >= 7) break;
                if (this.board[r][c] !== player) break;
                count++;
            }
            
            // 向相反方向检查
            for (let i = 1; i < 4; i++) {
                const r = lastRow - i * dx;
                const c = lastCol - i * dy;
                if (r < 0 || r >= 6 || c < 0 || c >= 7) break;
                if (this.board[r][c] !== player) break;
                count++;
            }
            
            // 如果有三连,检查是否有延伸空间
            if (count === 3) {
                // 检查两边是否有空位可以形成四连
                const r1 = lastRow + 3 * dx;
                const c1 = lastCol + 3 * dy;
                const r2 = lastRow - 3 * dx;
                const c2 = lastCol - 3 * dy;
                
                if ((r1 >= 0 && r1 < 6 && c1 >= 0 && c1 < 7 && this.board[r1][c1] === null) || 
                    (r2 >= 0 && r2 < 6 && c2 >= 0 && c2 < 7 && this.board[r2][c2] === null)) {
                    return true;
                }
            }
        }
        return false;
    }

    // 新增: 检查指定长度的连棋
    checkLineOfLength(player, length, row, col) {
        const directions = [
            [0, 1],   // 水平
            [1, 0],   // 垂直
            [1, 1],   // 正对角线
            [1, -1]   // 反对角线
        ];
        
        for (const [dx, dy] of directions) {
            let count = 1;
            
            // 向一个方向检查
            for (let i = 1; i < length; i++) {
                const r = row + i * dx;
                const c = col + i * dy;
                if (r < 0 || r >= 6 || c < 0 || c >= 7 || this.board[r][c] !== player) break;
                count++;
            }
            
            // 向相反方向检查
            for (let i = 1; i < length; i++) {
                const r = row - i * dx;
                const c = col - i * dy;
                if (r < 0 || r >= 6 || c < 0 || c >= 7 || this.board[r][c] !== player) break;
                count++;
            }
            
            if (count >= length) {
                // 检查是否有延伸空间
                if (length === 3) {
                    const r1 = row + length * dx;
                    const c1 = col + length * dy;
                    const r2 = row - length * dx;
                    const c2 = col - length * dy;
                    
                    if ((r1 >= 0 && r1 < 6 && c1 >= 0 && c1 < 7 && this.board[r1][c1] === null) || 
                        (r2 >= 0 && r2 < 6 && c2 >= 0 && c2 < 7 && this.board[r2][c2] === null)) {
                        return true;
                    }
                } else {
                    return true;
                }
            }
        }
        return false;
    }

    // 处理AI移动结果
    handleAIMoveResult() {
        if (this.checkForWin()) {
            this.showWinner('green');
        } else if (this.isBoardFull()) {
            this.showDraw();
        } else {
            this.currentPlayer = 'red';  // 强制切换回红色玩家
            this.updateCurrentPlayerDisplay();
        }
    }

    // Minimax算法实现
    minimax(board, depth, isMaximizing) {
        // 检查游戏是否结束
        const winner = this.checkWinner(board);
        if (winner === 'green') return 100 - depth;
        if (winner === 'red') return -100 + depth;
        if (this.isBoardFull(board)) return 0;
        if (depth === 0) return this.evaluateBoard(board);

        if (isMaximizing) {
            let bestScore = -Infinity;
            for (let col = 0; col < 7; col++) {
                if (board[0][col] === null) {
                    const row = this.findDropRow(col);
                    board[row][col] = 'green';
                    const score = this.minimax(board, depth - 1, false);
                    board[row][col] = null;
                    bestScore = Math.max(score, bestScore);
                }
            }
            return bestScore;
        } else {
            let bestScore = Infinity;
            for (let col = 0; col < 7; col++) {
                if (board[0][col] === null) {
                    const row = this.findDropRow(col);
                    board[row][col] = 'red';
                    const score = this.minimax(board, depth - 1, true);
                    board[row][col] = null;
                    bestScore = Math.min(score, bestScore);
                }
            }
            return bestScore;
        }
    }

    // 查找棋子落下的行
    findDropRow(col) {
        for (let row = 5; row >= 0; row--) {
            if (this.board[row][col] === null) {
                return row;
            }
        }
        return -1;
    }

    // 评估棋盘状态
    evaluateBoard(board) {
        let score = 0;
        
        // 中心列优先级更高
        const centerCol = Math.floor(7 / 2);
        for (let row = 0; row < 6; row++) {
            if (board[row][centerCol] === 'green') {
                score += 3;
            }
        }
        
        // 检查所有可能的四连棋组合
        // 水平方向
        for (let row = 0; row < 6; row++) {
            for (let col = 0; col <= 3; col++) {
                score += this.evaluateWindow(
                    [board[row][col], board[row][col+1], board[row][col+2], board[row][col+3]]
                );
            }
        }
        
        // 垂直方向
        for (let col = 0; col < 7; col++) {
            for (let row = 0; row <= 2; row++) {
                score += this.evaluateWindow(
                    [board[row][col], board[row+1][col], board[row+2][col], board[row+3][col]]
                );
            }
        }
        
        // 对角线方向
        for (let row = 0; row <= 2; row++) {
            for (let col = 0; col <= 3; col++) {
                score += this.evaluateWindow(
                    [board[row][col], board[row+1][col+1], board[row+2][col+2], board[row+3][col+3]]
                );
            }
            for (let col = 3; col < 7; col++) {
                score += this.evaluateWindow(
                    [board[row][col], board[row+1][col-1], board[row+2][col-2], board[row+3][col-3]]
                );
            }
        }
        
        return score;
    }

    // 评估窗口(四个连续的格子)
    evaluateWindow(window) {
        let score = 0;
        const greenCount = window.filter(cell => cell === 'green').length;
        const redCount = window.filter(cell => cell === 'red').length;
        const emptyCount = 4 - greenCount - redCount;
        
        if (greenCount === 4) score += 100;
        else if (greenCount === 3 && emptyCount === 1) score += 10;
        else if (greenCount === 2 && emptyCount === 2) score += 3;
        
        if (redCount === 3 && emptyCount === 1) score -= 80;
        else if (redCount === 2 && emptyCount === 2) score -= 2;
        
        return score;
    }

    /**
     * 检查指定棋盘是否有赢家
     * 修改: 增加边界检查和提前返回逻辑,确保四连棋判断准确
     */
    checkWinner(board) {
        // 检查所有方向可能的四连棋
        for (let row = 0; row < 6; row++) {
            for (let col = 0; col < 7; col++) {
                const player = board[row][col];
                if (player === null) continue;

                // 水平方向检查(优化:增加边界检查)
                if (col <= 3 && 
                    board[row][col + 1] === player &&
                    board[row][col + 2] === player &&
                    board[row][col + 3] === player) {
                    return player;
                }

                // 垂直方向检查(优化:增加边界检查)
                if (row <= 2 && 
                    board[row + 1][col] === player &&
                    board[row + 2][col] === player &&
                    board[row + 3][col] === player) {
                    return player;
                }

                // 正对角线 (左上到右下)
                if (row <= 2 && col <= 3 && 
                    board[row + 1][col + 1] === player &&
                    board[row + 2][col + 2] === player &&
                    board[row + 3][col + 3] === player) {
                    return player;
                }

                // 反对角线 (右上到左下)
                if (row <= 2 && col >= 3 && 
                    board[row + 1][col - 1] === player &&
                    board[row + 2][col - 2] === player &&
                    board[row + 3][col - 3] === player) {
                    return player;
                }
            }
        }
        return null;
    }

    /**
     * 检查胜利条件
     * 修改: 1. 添加边界检查 2. 使用优化后的checkWinner方法
     */
    checkForWin() {
        return this.checkWinner(this.board) !== null;
    }

    // 修改: 处理玩家移动方法 - 添加在线模式支持
    handlePlayerMove(col) {
        if (!this.gameStarted || (this.mode.includes('ai') && this.currentPlayer === 'green')) {
            return;
        }

        // 在线模式下检查是否是该玩家的回合
        if (this.mode === 'online' && 
            ((this.playerRole === 'host' && this.currentPlayer !== 'red') || 
             (this.playerRole === 'guest' && this.currentPlayer !== 'green'))) {
            return;
        }

        if (this.placePiece(col, this.currentPlayer)) {
            // 在线模式下发送移动消息
            if (this.mode === 'online') {
                this.sendOnlineMessage({
                    type: 'move',
                    col: col,
                    player: this.playerRole
                });
            }

            if (this.checkForWin()) {
                this.showWinner(this.currentPlayer);
            } else if (this.isBoardFull()) {
                this.showDraw();
            } else {
                this.switchPlayer();
                // 确保AI能够响应
                if (this.mode === 'easy-ai' && this.currentPlayer === 'green') {
                    this.makeAIMove();
                } else if (this.mode === 'hard-ai' && this.currentPlayer === 'green') {
                    this.makeAdvancedAIMove();
                }
            }
        }
    }

    // 切换玩家
    switchPlayer() {
        this.currentPlayer = this.currentPlayer === 'red' ? 'green' : 'red';
        this.updateCurrentPlayerDisplay();
    }

    // 显示获胜玩家信息
    showWinner(player) {
        // 检查游戏是否已结束,避免重复计分
        if (!this.gameStarted) return;

        // 只给当前获胜玩家加分(修改: 确保只加1分)
        if (player === this.currentPlayer) {
            if (player === 'red') {
                console.log(`红方得分更新: ${this.scores.red}`); // 添加日志输出
                this.scores.red ++;
                this.elements.redScoreDisplay.textContent = this.scores.red;
            } else {
                console.log(`红方得分更新: ${this.scores.green}`); // 添加日志输出
                this.scores.green ++;
                this.elements.greenScoreDisplay.textContent = this.scores.green;
            }
        }

        this.elements.currentPlayerDisplay.textContent = player === 'red' ? '粉色玩家获胜!' : '绿色玩家获胜!';
        this.elements.currentPlayerDisplay.classList.remove('player-red', 'player-green');
        this.elements.currentPlayerDisplay.classList.add(player === 'red' ? 'player-red' : 'player-green');
        
        // 重置游戏状态
        this.gameStarted = false;
    }

    // 显示平局信息
    showDraw() {
        this.elements.currentPlayerDisplay.textContent = "平局!";
        this.elements.currentPlayerDisplay.classList.remove('player-red', 'player-green', 'waiting');
        this.elements.currentPlayerDisplay.classList.add('draw');
        
        // 重置游戏状态
        this.gameStarted = false;
    }

    // 检查棋盘是否已满(平局)
    isBoardFull() {
        for (let row = 0; row < 6; row++) {
            for (let col = 0; col < 7; col++) {
                if (this.board[row][col] === null) {
                    return false;
                }
            }
        }
        return true;
    }

    // 更新当前玩家显示
    updateCurrentPlayerDisplay() {
        if (this.gameStarted) {
            this.elements.currentPlayerDisplay.textContent = this.currentPlayer === 'red'
                ? '当前玩家: 粉色(1)'
                : '当前玩家: 绿色(2)';
            this.elements.currentPlayerDisplay.classList.remove('player-red', 'player-green');
            this.elements.currentPlayerDisplay.classList.add(this.currentPlayer === 'red' ? 'player-red' : 'player-green');
        } else {
            this.elements.currentPlayerDisplay.textContent = "等待开始...";
            this.elements.currentPlayerDisplay.classList.remove('player-red', 'player-green');
            this.elements.currentPlayerDisplay.classList.add('waiting');
        }
    }

    // 处理单元格悬停事件 - 显示当前玩家棋子预览
    handleCellHover(e) {
        if (!this.gameStarted) return;

        if (!e.target.classList.contains('cell')) return;

        const col = parseInt(e.target.dataset.col);

        for (let row = 5; row >= 0; row--) {
            if (this.board[row][col] === null) {
                const cell = this.elements.board.children[row * 7 + col];
                cell.classList.add('preview', this.currentPlayer === 'red' ? 'preview-red' : 'preview-green');
                break;
            }
        }
    }

    // 处理单元格离开事件 - 清除预览效果
    handleCellLeave(e) {
        if (!this.gameStarted) return;
        
        if (!e.target.classList.contains('cell')) return;

        const col = parseInt(e.target.dataset.col);

        // 清除该列所有单元格的预览效果
        for (let row = 0; row < 6; row++) {
            const cell = this.elements.board.children[row * 7 + col];
            cell.classList.remove('preview', 'preview-red', 'preview-green');
        }
    }

    // 返回主页
    handleBackToHome() {
        if (confirm('确定要返回主页吗?当前游戏进度将会丢失。')) {
            window.location.href = '../views/home.html';
        }
    }
}

// 修改: 游戏初始化逻辑统一处理
document.addEventListener('DOMContentLoaded', () => {
    const game = new ConnectFourGame();
    game.init();
});

server.js

// G:\RuoYi-Vue-springboot3\connect_four\vue3\server.js
const express = require('express');
const http = require('http');
const {Server} = require('socket.io');
const path = require('path');

const app = express();
const server = http.createServer(app);
const io = new Server(server, {
    cors: {
        origin: "*",
        methods: ["GET", "POST"]
    }
});

// 存储房间和对应的socket连接
const rooms = new Map();
const socketToRoom = new WeakMap();

// 添加定时清理空房间的函数
function cleanupEmptyRooms() {
    const now = Date.now();
    for (const [roomId, room] of rooms.entries()) {
        if (room.players.length === 0) {
            rooms.delete(roomId);
            console.log(`定时清理空房间: ${roomId}`);
        } else if (now - room.lastActivity > 30000) { // 30秒无活动也清理
            rooms.delete(roomId);
            console.log(`清理长时间无活动的房间: ${roomId}`);
        }
    }
}

// 启动定时器,每5秒检查一次
setInterval(cleanupEmptyRooms, 5000);

// 设置静态文件目录
app.use(express.static(path.join(__dirname, 'src')));

// 路由处理
app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'src/views/home.html'));
});

app.get('/:page', (req, res) => {
    res.sendFile(path.join(__dirname, `src/views/${req.params.page}`));
});

// Socket.io 逻辑
io.on('connection', (socket) => {
    console.log('新玩家连接:', socket.id);

    // 添加重新加入房间的处理
    socket.on('rejoin-room', (roomId) => {
        if (rooms.has(roomId)) {
            const room = rooms.get(roomId);
            if (room.players.length < 2) {
                room.players.push(socket.id);
                socket.join(roomId);
                socketToRoom.set(socket, roomId);
                socket.emit('room-rejoined', { 
                    roomId,
                    playerRole: room.players[0] === socket.id ? 'red' : 'green'
                });
                io.to(roomId).emit('players-updated', {
                    players: room.players
                });
            } else {
                socket.emit('room-full');
            }
        } else {
            socket.emit('room-not-found');
        }
    });

    // 修改leave-room事件处理
    socket.on('leave-room', (roomId) => {
        if (rooms.has(roomId)) {
            const room = rooms.get(roomId);
            // 确保从房间中移除当前socket
            room.players = room.players.filter(id => id !== socket.id);
            socketToRoom.delete(socket);

            console.log(`玩家 ${socket.id} 离开房间 ${roomId}`);

            // 立即检查并清理空房间,不需要等待定时器
            if (room.players.length === 0) {
                rooms.delete(roomId);
                console.log(`房间 ${roomId} 已被清理(空房间)`);
            } else {
                // 通知剩余玩家有人离开
                socket.to(roomId).emit('player-left', {playerId: socket.id});
            }
        }
    });

    // 修改创建房间逻辑
    socket.on('create-room', (roomId) => {
        // 增加房间状态检查
        if (rooms.has(roomId)) {
            const room = rooms.get(roomId);
            if (room.players.length > 0) {
                socket.emit('room-exists');
                return;
            } else {
                // 清理空房间
                rooms.delete(roomId);
            }
        }

        rooms.set(roomId, {
            players: [socket.id],
            gameState: null,
            readyStatus: {
                [socket.id]: false
            },
            countdownStarted: false,
            lastActivity: Date.now(),
            redScore: 0,   // 初始化红方分数
            greenScore: 0,  // 初始化绿方分数
            processedWinner: false  // 添加processedWinner状态
        });
        socket.join(roomId);
        socketToRoom.set(socket, roomId);
        socket.emit('room-created', {roomId, playerRole: 'red'});
    });

    // 修改加入房间逻辑
    socket.on('join-room', (roomId) => {
        if (!rooms.has(roomId)) {
            socket.emit('room-not-found');
            return;
        }

        const room = rooms.get(roomId);
        if (room.players.length >= 2) {
            socket.emit('room-full');
            return;
        }

        room.players.push(socket.id);
        room.readyStatus[socket.id] = false;
        socket.join(roomId);
        socket.emit('room-joined', {roomId, playerRole: 'green'});

        // 通知房间内所有玩家游戏可以开始
        io.to(roomId).emit('players-updated', {
            players: room.players
        });
    });

    // 修改玩家准备逻辑 - 添加processedWinner重置
    socket.on('player-ready', (roomId) => {
        if (!rooms.has(roomId)) return;

        const room = rooms.get(roomId);
        room.readyStatus[socket.id] = true;
        room.lastActivity = Date.now();

        // 检查是否所有玩家都准备就绪
        const allReady = Object.values(room.readyStatus).every(Boolean);
        if (allReady && room.players.length === 2 && !room.countdownStarted) {
            room.countdownStarted = true;

            // 新增:在开始新游戏前重置processedWinner
            room.processedWinner = false;

            io.to(roomId).emit('all-players-ready');
        }
    });

    // 添加取消准备处理
    socket.on('player-cancel-ready', (roomId) => {
        if (!rooms.has(roomId)) return;

        const room = rooms.get(roomId);
        room.readyStatus[socket.id] = false;
        room.countdownStarted = false;
        io.to(roomId).emit('player-cancel-ready', {playerId: socket.id});
    });

    // 玩家移动
    socket.on('player-move', (data) => {
        socket.to(data.roomId).emit('opponent-move', {
            col: data.col,
            player: data.player
        });
    });

    // 修改游戏结束处理逻辑
    socket.on('game-over', (data) => {
        if (rooms.has(data.roomId)) {
            const room = rooms.get(data.roomId);
            room.gameState = null;

            // 重置所有玩家准备状态
            room.players.forEach(playerId => {
                room.readyStatus[playerId] = false;
            });
            room.countdownStarted = false;

            // 仅在有效胜利时增加分数,并且未处理过
            if (data.winner !== 'draw') {
                const winnerScoreKey = data.winner === 'red' ? 'redScore' : 'greenScore';
                // 确保初始化分数
                if (typeof room[winnerScoreKey] !== 'number') {
                    room[winnerScoreKey] = 0;
                }

                // 仅当processedWinner不存在或为false时处理加分
                if (!room.hasOwnProperty('processedWinner') || !room.processedWinner) {
                    // 确保每次只加1分
                    console.log(`判断前分数 ${room[winnerScoreKey]} 获得分数`)
                    room[winnerScoreKey] = Math.max(0, room[winnerScoreKey]) +1;
                    console.log(`判断后分数 ${room[winnerScoreKey]} 获得分数`)

                    // 标记为已处理
                    room.processedWinner = true;
                }
            }

            // 广播游戏结束事件
            io.to(data.roomId).emit('game-ended', {
                winner: data.winner,
                redScore: room.redScore || 0,
                greenScore: room.greenScore || 0
            });

            // 通知重置准备状态
            io.to(data.roomId).emit('reset-ready-status');
        }
    });

    // 修改断开连接处理
    socket.on('disconnect', (reason) => {
        console.log(`玩家断开连接: ${socket.id}, 原因: ${reason}`);
        const roomId = socketToRoom.get(socket);

        if (roomId && rooms.has(roomId)) {
            const room = rooms.get(roomId);
            room.players = room.players.filter(id => id !== socket.id);

            // 增加详细的断开原因处理
            if (reason === 'ping timeout') {
                socket.to(roomId).emit('player-disconnected', {
                    playerId: socket.id,
                    reason: '连接超时'
                });
            } else if (reason === 'transport close') {
                socket.to(roomId).emit('player-disconnected', {
                    playerId: socket.id, 
                    reason: '网络断开'
                });
            } else {
                socket.to(roomId).emit('player-left', {
                    playerId: socket.id
                });
            }

            if (room.players.length === 0) {
                rooms.delete(roomId);
                console.log(`已清理空房间: ${roomId}`);
            }
        }
    });
});

const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
    console.log(`服务器运行在: http://localhost:${PORT}`);
});

styles.css

body {
    font-family: 'Arial', sans-serif;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: #f0f8ff;
    margin: 0;
}

.game-container {
    text-align: center;
}

.board-container {
    margin-top: 20px;
}

.board {
    display: grid;
    grid-template-columns: repeat(7, 50px);
    grid-template-rows: repeat(6, 50px);
    gap: 2px;
    background-color: #add8e6;
    border-radius: 10px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    padding: 5px;
    border: 5px solid #b0e0e6; /* 添加边框 */
}

.cell {
    width: 50px;
    height: 50px;
    border-radius: 50%;
    background-color: white;
    display: flex;
    justify-content: center;
    align-items: center;
    transition: background-color 0.3s;
    cursor: pointer;
}

.cell:hover {
    background-color: #e0ffff;
}

.player-info {
    margin: 20px 0;
    font-size: 1.2em;
    background-color: #fffaf0;
    padding: 10px 20px;
    border-radius: 20px;
}

#current-player {
    font-weight: bold;
}

.red-piece {
    background-color: #ff69b4; /* 粉色 */
}

.green-piece {
    background-color: #32cd32; /* 绿色 */
}

.preview-red {
    background-color: rgb(255, 154, 158); /* 半透明淡粉色预览 */
}

.preview-green {
    background-color: rgb(160, 231, 160); /* 半透明淡绿色预览 */
}

/* 修改预览颜色样式 */
.current-red .preview {
    background-color: rgba(255, 154, 158, 0.7); /* 半透明淡粉色预览 */
}

.current-green .preview {
    background-color: rgba(160, 231, 160, 0.7); /* 半透明淡绿色预览 */
}

.preview {
    background-color: rgb(255, 255, 255);
}

.player-red {
    color: #ff69b4; /* 粉色文字 */
}

.player-green {
    color: #32cd32; /* 绿色文字 */
}

.waiting {
    color: #333; /* 默认颜色 */
}


/* 添加以下新样式 */
.game-mode-selector {
    margin: 15px 0;
    padding: 10px;
    background-color: #fffaf0;
    border-radius: 10px;
}

.game-mode-selector select {
    padding: 8px 15px;
    font-size: 1em;
    border-radius: 5px;
    border: 1px solid #ddd;
    background-color: white;
}

#restart-game {
    margin-top: 10px;
    padding: 10px 20px;
    font-size: 1em;
    background-color: #f0ad4e;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    transition: background-color 0.3s;
}

#restart-game:hover {
    background-color: #ec971f;
}

.game-options {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 15px;
}

.game-option {
    padding: 12px 20px;
    background-color: #f0f0f0;
    border-radius: 5px;
    cursor: pointer;
    transition: background-color 0.3s;
    text-align: center;
}

.game-option:hover {
    background-color: #e0e0e0;
}
/* 返回按钮样式 */
.back-button {
    position: fixed;
    top: 20px;
    left: 20px;
    padding: 10px 15px;
    background-color: #4CAF50;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 16px;
    z-index: 1000;
    transition: background-color 0.3s;
}

.back-button:hover {
    background-color: #45a049;
}

.scoreboard {
    display: flex;
    justify-content: space-around;
    margin: 20px 0;
    font-size: 18px;
    font-weight: bold;
}

.score {
    padding: 10px 20px;
    border-radius: 5px;
}

.red-score {
    background-color: #ffdddd;
    color: #ff4444;
}

.green-score {
    background-color: #ddffdd;
    color: #44aa44;
}

/* 新增: 响应式设计 */
@media (max-width: 768px) {
    body {
        padding: 10px;
        height: auto;
        min-height: 100vh;
    }

    .board {
        grid-template-columns: repeat(7, 40px);
        grid-template-rows: repeat(6, 40px);
        gap: 1px;
    }

    .cell {
        width: 40px;
        height: 40px;
    }

    .game-container {
        width: 100%;
        max-width: 320px;
        margin: 0 auto;
    }

    .player-info, .game-mode-selector {
        padding: 8px;
        font-size: 1em;
    }

    h1 {
        font-size: 1.3em;
    }
}

/* 新增: 淡色系主题 */
body {
    background-color: #f9f7f7;
}

.board {
    background-color: #dbe2ef;
    border-color: #3f72af;
}

.red-piece {
    background-color: #ff9a9e; /* 淡粉色 */
}

.green-piece {
    background-color: #a0e7a0; /* 淡绿色 */
}

.player-info, .game-mode-selector {
    background-color: #f7fbfc;
    border: 1px solid #dbe2ef;
}

.score {
    background-color: rgba(255,255,255,0.8);
    box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}

.red-score {
    background-color: #ffebee;
    color: #ef9a9a;
}

.green-score {
    background-color: #e8f5e9;
    color: #81c784;
}

.game-option {
    background-color: #e3f2fd;
    border: 1px solid #bbdefb;
}

.game-option:hover {
    background-color: #bbdefb;
}

.back-button {
    background-color: #7986cb;
}

.back-button:hover {
    background-color: #5c6bc0;
}

.ready-controls button {
    background-color: #80deea;
    border: none;
    padding: 8px 16px;
    border-radius: 20px;
    color: #00838f;
}

/* 修改按钮样式为淡色系 */
.room-button {
    background-color: #e3f2fd;
    border: 1px solid #bbdefb;
    color: #1976d2;
    padding: 8px 16px;
    border-radius: 20px;
    transition: all 0.3s ease;
}

.room-button:hover {
    background-color: #bbdefb;
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}

.ready-button {
    background-color: #e8f5e9;
    border: 1px solid #c8e6c9;
    color: #2e7d32;
    padding: 8px 16px;
    border-radius: 20px;
    transition: all 0.3s ease;
}

.ready-button:hover {
    background-color: #c8e6c9;
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}

/* 修改文字颜色为淡色 */
.scoreboard {
    color: #5c6bc0;
    font-weight: 500;
}

.game-info {
    color: #616161;
    background-color: #f5f5f5;
    padding: 10px;
    border-radius: 10px;
}

#current-player {
    color: #5d4037;
}

/* 修改输入框样式 */
.room-input {
    background-color: #f5f5f5;
    border: 1px solid #e0e0e0;
    color: #424242;
    padding: 8px 12px;
    border-radius: 20px;
}

.room-input:focus {
    outline: none;
    border-color: #90caf9;
    box-shadow: 0 0 0 2px rgba(144, 202, 249, 0.3);
}

测试功能有哪些缺失

整体的一个代码就预选择做的不太好,预选都是一个浅蓝色半透明的样式, 玩家可能会不知道归谁下棋, 以及玩家在对战的过程中房间自动销毁了,但是对战仍然可以继续,只不过打完一局以后需要重新创建房间。

如何改进

这里我就选择让落棋的用户有一个预选,而还没有到落棋的用户不能够进行预选就是点击不了棋盘,鼠标滑动在上面也没有效果。最终就完成了这么一个网页四子棋的开发。