什么是四连棋/规则是怎样的
四连棋是一款经典的策略型桌面游戏,也被称为"四子棋"、"连珠棋"或"connect 4"。它的规则简单易懂,适合各年龄段玩家,核心目标是通过策略布局,使己方的棋子在横、竖、斜方向上形成连续的四个,从而获胜。
基本规则
1. 棋盘与棋子
- 棋盘:通常是一个垂直放置的网格,标准尺寸为7列×6行(共42个格子),底部有支架支撑,棋子可从上方放入列中并垂直下落。
- 棋子:双方各使用一种颜色的棋子(如红色和黄色),轮流放置。
2. 回合与放置方式
- 轮流行动:玩家交替进行,每次将一枚棋子放入棋盘顶部的任意一列(棋子会自动落到该列的最底层空格里)。
- 禁止操作:不能跳过空格或横向、纵向移动已放置的棋子,只能从列的顶端放入。
3. 胜利条件
- 四连珠:当己方的棋子在以下任意方向形成连续的四个时,立即获胜:
- 横向:同一行中连续四个棋子
- 纵向:同一列中连续四个棋子
- 斜向:左上至右下或右上至左下的斜线上连续四个棋子
- 平局:若棋盘全部填满且无人达成四连珠,则判定为平局。
网上有哪些四连棋卖
社细立体四子棋四连棋
特点:可用于儿童玩具空间对战,也适合双人桌面游戏,能够锻炼玩家的空间思维和对战能力。
魔圣魔方玩具趣味立体四子棋四连棋重力五子棋
特点:将四子棋、五子棋与重力元素相结合,增加了游戏的趣味性和挑战性,适合儿童棋类玩具益智亲子双人桌游。
小卡尼儿童四连环棋
特点:专门为儿童设计,外观可能比较时尚,适合小学生游戏,能够培养儿童的益智能力和策略思维。
如何指导AI实现四连棋
开发流程
-
需求分析
- 确定需要的功能模块
- 规划开发步骤
-
构建基础页面 打开以后差不多就像下面这样。
但是点击按钮跳转到的页面是没有东西的,我们接着往下写提示词。
帮我实现四连棋的小游戏,将公共部分提取,方便多模式直接使用。
然后就会生成一个js文件,里面是四子棋的一个基本的规则,以及判断胜负之类的代码,接下来将你的LV0_双人手动下棋.html文件塞还有生成的js文件一并给ai,给提示词说
帮我实现双人手动下棋页面功能,要求,在一个页面中,点击开始游戏以后开始,红方先手下棋,下完以后到绿方,直到一方获胜,规则参考四连棋,并且添加一个返回标题按钮用于返回home.html。
ai写完以后,再次打开home页面,点击第一个模式可能会报错,但是没关系,点击f12打开控制台,将报错信息复制下来,塞给ai,让ai帮你修复就行。最终效果差不多就和下面差不多,分数是我最终的成品,你们跟着写的没有也没关系
接下来实现第二个页面,给ai提示词,依旧将你要写的文件塞给ai,以及js代码,之前完成的第一个页面文件也可以塞给ai,让ai参考。
帮我写一个低级人机对战功能,要求,点击开始游戏以后,用户下棋,下完以后低级人机自动下棋,并且随机下1-7。
一般第一个页面实现了以后第二个会变得简单很多。最终就是下面这个效果
低级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,并且说明你遇到的错误,他就会自己帮你修复。最终的一个效果就如下
完成到这,那么最难的部分完成了,接下来就是高级人机对战,我们继续将写完的全部文件塞给ai
帮我完成高级人机对战页面,要求高级人机能够预判用户的行动,获胜优先,防止用户获胜其次,规则参考四子棋。
ai帮你写完了这个页面以后,你去试会发现,ai并不是很强,碰到很多种情况都没有办法应对,但是没有关系,我们直接用ai去指导我们的人机,就是用ai和人机下棋,并且将ai的每一次获胜结果发给ai,让ai帮忙加强。最终效果就是能够预判用户的7步棋,已经是非常强悍了,一般的人都打不过我们的高级人机了。
这里我找了一个人来测试我的高级ai,让他熟悉了规则以后来挑战高级ai下面就是整个对战过程,可以看到我们的高级人机也是非常给力
最终完成了之后,可以让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);
}
测试功能有哪些缺失
整体的一个代码就预选择做的不太好,预选都是一个浅蓝色半透明的样式, 玩家可能会不知道归谁下棋, 以及玩家在对战的过程中房间自动销毁了,但是对战仍然可以继续,只不过打完一局以后需要重新创建房间。
如何改进
这里我就选择让落棋的用户有一个预选,而还没有到落棋的用户不能够进行预选就是点击不了棋盘,鼠标滑动在上面也没有效果。最终就完成了这么一个网页四子棋的开发。