技术选型与架构设计
核心技术栈
技术领域 | 技术选择 | 说明 |
---|---|---|
前端渲染 | HTML5 Canvas | 动态绘制棋盘和棋子,支持像素级操作 |
交互逻辑 | JavaScript | 处理用户输入、规则判定、状态管理 |
网络通信 | WebSocket | 实现实时对战(可替换为Socket.IO) |
后端服务 | Node.js + Express | 快速搭建HTTP/WebSocket服务器,处理房间匹配 |
数据存储 | MongoDB/Redis | 存储用户数据、对战记录(可选,本地存储可满足基础需求) |
核心功能实现
棋盘绘制与坐标系统
// 初始化19x19棋盘 const boardSize = 19; const cellSize = 30; // 每个格子的像素尺寸 const canvas = document.getElementById('go-board'); const ctx = canvas.getContext('2d'); // 绘制棋盘线 function drawBoard() { ctx.strokeStyle = '#333'; for (let i = 0; i <= boardSize; i++) { // 横线 ctx.beginPath(); ctx.moveTo(cellSize / 2, cellSize / 2 + i cellSize); ctx.lineTo(boardSize cellSize + cellSize / 2, cellSize / 2 + i cellSize); ctx.stroke(); // 纵线 ctx.beginPath(); ctx.moveTo(cellSize / 2 + i cellSize, cellSize / 2); ctx.lineTo(cellSize / 2 + i cellSize, boardSize cellSize + cellSize / 2); ctx.stroke(); } }
落子交互逻辑
let currentPlayer = 'black'; // 当前落子方 const stones = {}; // 存储已落子坐标 {x:y, ...} canvas.addEventListener('click', (e) => { const rect = canvas.getBoundingClientRect(); const x = Math.floor((e.clientX rect.left) / cellSize); const y = Math.floor((e.clientY rect.top) / cellSize); // 边界检查 if (x < 0 || x >= boardSize || y < 0 || y >= boardSize) return; if (stones[`${x}:${y}`]) return; // 已有棋子 // 绘制棋子 const color = currentPlayer === 'black' ? '#000' : '#fff'; ctx.beginPath(); ctx.arc(x cellSize + cellSize / 2, y cellSize + cellSize / 2, cellSize / 2 0.8, 0, Math.PI 2); ctx.fillStyle = color; ctx.fill(); stones[`${x}:${y}`] = currentPlayer; // 切换玩家 currentPlayer = currentPlayer === 'black' ? 'white' : 'black'; });
禁入点检测(基础规则)
function checkSuicide(x, y) { // 计算气(直接相邻的空点) const liberties = []; [-1, 0, 1].forEach(dx => { [-1, 0, 1].forEach(dy => { if (dx === 0 && dy === 0) return; const nx = x + dx; const ny = y + dy; if (nx >= 0 && nx < boardSize && ny >= 0 && ny < boardSize && !stones[`${nx}:${ny}`]) { liberties.push({x: nx, y: ny}); } }); }); return liberties.length === 0; // 无气则为自杀 }
网络对战扩展方案
功能模块 | 实现方案 |
---|---|
房间匹配 | 服务器维护等待队列,新用户进入时匹配队首用户 |
状态同步 | 每次落子后广播坐标和颜色,客户端需标记己方/敌方棋子 |
断线处理 | 设置超时时间(如30秒),断线后保留棋盘状态,允许重连 |
聊天功能 | WebSocket额外通道传输文本消息,需处理消息格式化和显示 |
性能优化策略
- 分层渲染:仅重绘变化区域(如新增棋子周围3×3范围)
- 请求动画帧:使用
requestAnimationFrame
替代setInterval
进行渲染 - 对象池技术:复用棋子对象减少内存分配
- Web Workers:将复杂计算(如提子判定)移至后台线程
相关问题与解答
Q1:如何实现围棋的提子规则?
A:需递归检测棋子群的气:
function removeDeadGroup(x, y, color) {
const stack = [[x, y]];
const deadStones = [];
const visited = {};
while (stack.length > 0) {
const [cx, cy] = stack.pop();
if (visited[`${cx}:${cy}`]) continue;
visited[`${cx}:${cy}`] = true;
if (stones[`${cx}:${cy}`] !== color) continue;
deadStones.push([cx, cy]);
[-1, 0, 1].forEach(dx => {
[-1, 0, 1].forEach(dy => {
if (dx === 0 && dy === 0) return;
const nx = cx + dx;
const ny = cy + dy;
if (nx >= 0 && nx < boardSize && ny >= 0 && ny < boardSize) {
stack.push([nx, ny]);
}
});
});
}
// 计算总气数
let totalLiberties = 0;
deadStones.forEach(([dx, dy]) => {
[-1, 0, 1].forEach(xx => {
[-1, 0, 1].forEach(yy => {
const nx = dx + xx;
const ny = dy + yy;
if (nx >= 0 && nx < boardSize && ny >= 0 && ny < boardSize && !stones[`${nx}:${ny}`]) {
totalLiberties++;
}
});
});
});
if (totalLiberties === 0) {
deadStones.forEach(([dx, dy]) => {
delete stones[`${dx}:${dy}`];
// 在画布上清除该棋子
ctx.clearRect(dx cellSize, dy cellSize, cellSize, cellSize);
});
return true; // 发生提子
}
return false;
}
Q2:如何优化多人在线时的带宽使用?
A:采用增量式数据传输:
- 状态压缩:只传输变化量(如
delta_x,delta_y,color
)而非全屏数据 - 命令队列:将连续操作合并为批量指令(如
[{x:1,y:2,color:'white'},...]
) - 差异编码:使用相对坐标编码(如相对于前一步的偏移量)
- 数据压缩:启用WebSocket的permessage-def