HTML实现五子棋游戏及胜负判断逻辑详解
五子棋是一款经典的双人对弈棋类游戏,使用HTML、CSS和JavaScript即可实现完整的网页版五子棋。本文将逐步讲解页面结构搭建、棋盘绘制、落子逻辑以及核心的胜负判断算法实现。
一、页面基础结构搭建
首先我们需要搭建五子棋游戏的HTML基础结构,包含棋盘容器、状态提示区域和控制按钮。注意正文中提到的HTML标签需要转义,例如画布使用<canvas>标签实现:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>网页版五子棋</title> <link rel="stylesheet" href="style.css"> </head> <body> <div class="container"> <h3>五子棋对战</h3> <div class="status" id="status">当前轮到:黑子落子</div> <div class="board-wrapper"> <canvas id="chessBoard" width="600" height="600"></canvas> </div> <div class="controls"> <button id="restartBtn">重新开始</button> <button id="undoBtn">悔棋</button> </div> </div> <script src="chess.js"></script> </body> </html>
二、棋盘与落子逻辑实现
我们使用<canvas>标签绘制15x15的标准五子棋棋盘,通过监听鼠标点击事件获取落子位置,同时记录每一步的落子信息用于后续胜负判断和悔棋功能。
以下是核心JavaScript逻辑的代码实现:
// 初始化棋盘和状态
const canvas = document.getElementById('chessBoard');
const ctx = canvas.getContext('2d');
const statusEl = document.getElementById('status');
const restartBtn = document.getElementById('restartBtn');
const undoBtn = document.getElementById('undoBtn');
// 棋盘配置
const BOARD_SIZE = 15; // 15x15棋盘
const CELL_SIZE = 40; // 每个格子大小
const PIECE_RADIUS = 18; // 棋子半径
let board = []; // 棋盘状态数组,0为空,1为黑子,2为白子
let currentPlayer = 1; // 当前玩家,1黑2白
let history = []; // 落子历史记录,用于悔棋
// 初始化棋盘状态
function initBoard() {
board = [];
for (let i = 0; i < BOARD_SIZE; i++) {
board[i] = [];
for (let j = 0; j < BOARD_SIZE; j++) {
board[i][j] = 0;
}
}
history = [];
currentPlayer = 1;
statusEl.textContent = '当前轮到:黑子落子';
drawBoard();
}
// 绘制棋盘
function drawBoard() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制横线
for (let i = 0; i < BOARD_SIZE; i++) {
ctx.beginPath();
ctx.moveTo(CELL_SIZE, CELL_SIZE + i * CELL_SIZE);
ctx.lineTo(CELL_SIZE + (BOARD_SIZE - 1) * CELL_SIZE, CELL_SIZE + i * CELL_SIZE);
ctx.stroke();
}
// 绘制竖线
for (let i = 0; i < BOARD_SIZE; i++) {
ctx.beginPath();
ctx.moveTo(CELL_SIZE + i * CELL_SIZE, CELL_SIZE);
ctx.lineTo(CELL_SIZE + i * CELL_SIZE, CELL_SIZE + (BOARD_SIZE - 1) * CELL_SIZE);
ctx.stroke();
}
// 绘制已有棋子
for (let i = 0; i < BOARD_SIZE; i++) {
for (let j = 0; j < BOARD_SIZE; j++) {
if (board[i][j] !== 0) {
drawPiece(i, j, board[i][j]);
}
}
}
}
// 绘制单个棋子
function drawPiece(x, y, player) {
ctx.beginPath();
ctx.arc(
CELL_SIZE + x * CELL_SIZE,
CELL_SIZE + y * CELL_SIZE,
PIECE_RADIUS,
0,
Math.PI * 2
);
ctx.closePath();
// 设置棋子颜色
if (player === 1) {
ctx.fillStyle = '#000000';
} else {
ctx.fillStyle = '#ffffff';
ctx.strokeStyle = '#000000';
ctx.stroke();
}
ctx.fill();
}
// 处理落子事件
canvas.addEventListener('click', function(e) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// 计算点击的棋盘坐标
const boardX = Math.round((x - CELL_SIZE) / CELL_SIZE);
const boardY = Math.round((y - CELL_SIZE) / CELL_SIZE);
// 校验点击位置是否有效
if (boardX < 0 || boardX >= BOARD_SIZE || boardY < 0 || boardY >= BOARD_SIZE) return;
if (board[boardX][boardY] !== 0) return;
// 落子
board[boardX][boardY] = currentPlayer;
history.push({x: boardX, y: boardY, player: currentPlayer});
drawPiece(boardX, boardY, currentPlayer);
// 判断胜负
if (checkWin(boardX, boardY, currentPlayer)) {
statusEl.textContent = currentPlayer === 1 ? '黑子获胜!' : '白子获胜!';
canvas.removeEventListener('click', arguments.callee);
return;
}
// 切换玩家
currentPlayer = currentPlayer === 1 ? 2 : 1;
statusEl.textContent = currentPlayer === 1 ? '当前轮到:黑子落子' : '当前轮到:白子落子';
});
// 重新开始
restartBtn.addEventListener('click', initBoard);
// 悔棋
undoBtn.addEventListener('click', function() {
if (history.length === 0) return;
const lastStep = history.pop();
board[lastStep.x][lastStep.y] = 0;
currentPlayer = lastStep.player;
statusEl.textContent = currentPlayer === 1 ? '当前轮到:黑子落子' : '当前轮到:白子落子';
drawBoard();
});
// 初始化游戏
initBoard();三、五子棋胜负判断算法
五子棋的胜负判断核心是:从当前落子的位置出发,向四个方向(水平、垂直、左上到右下、右上到左下)分别统计连续相同棋子的数量,如果任意方向连续数量达到5个,则当前落子方获胜。
以下是完整的胜负判断函数实现:
/**
* 检查当前落子是否获胜
* @param {number} x - 落子横坐标
* @param {number} y - 落子纵坐标
* @param {number} player - 落子玩家,1黑2白
* @returns {boolean} 是否获胜
*/
function checkWin(x, y, player) {
// 四个方向的偏移量:水平、垂直、左上到右下、右上到左下
const directions = [
[[1, 0], [-1, 0]], // 水平
[[0, 1], [0, -1]], // 垂直
[[1, 1], [-1, -1]], // 左上到右下
[[1, -1], [-1, 1]] // 右上到左下
];
// 遍历每个方向
for (let dir of directions) {
let count = 1; // 当前落子算1个
// 遍历正向和反向
for (let [dx, dy] of dir) {
let nx = x + dx;
let ny = y + dy;
// 统计连续相同棋子的数量
while (
nx >= 0 && nx < BOARD_SIZE &&
ny >= 0 && ny < BOARD_SIZE &&
board[nx][ny] === player
) {
count++;
nx += dx;
ny += dy;
}
}
// 任意方向达到5个则获胜
if (count >= 5) {
return true;
}
}
return false;
}四、核心逻辑说明
胜负判断算法的核心思路是以当前落子点为中心,向八个方向(四个直线方向的往返)扩展统计,这样做的时间复杂度为O(1),不需要遍历整个棋盘,性能高效。
需要注意的边界情况:
落子位置超出棋盘范围时直接返回,不做统计
统计时需要同时判断正向和反向的连续棋子,避免遗漏
当棋盘下满仍未分出胜负时,可补充平局判断逻辑,例如校验history长度是否等于BOARD_SIZE * BOARD_SIZE
如果需要查看完整的示例效果,可以访问https://www.ipipp.com查看在线演示版本,或者参考上述代码自行搭建本地项目运行。