HOOOS

手把手教你用JavaScript实现网页版五子棋:棋盘绘制与胜负判定

0 17 码农小李 JavaScript五子棋Canvas
Apple

想用JavaScript做一个网页版的五子棋游戏,却卡在棋盘绘制和胜负判定上了?别担心,这篇教程就来帮你解决这两个核心问题。我们会一步步讲解如何用 canvas 绘制棋盘,以及如何高效地判断胜负,并提供完整的代码示例。

1. 棋盘绘制:Canvas 的妙用

首先,我们需要一个 HTML 文件,引入 JavaScript 代码,并创建一个 canvas 元素作为棋盘:

<!DOCTYPE html>
<html>
<head>
    <title>五子棋</title>
    <style>
        canvas {
            border: 1px solid black;
        }
    </style>
</head>
<body>
    <canvas id="chessboard" width="450" height="450"></canvas>
    <script src="script.js"></script>
</body>
</html>

接下来,在 script.js 文件中,我们获取 canvas 元素,并获取其 2D 渲染上下文:

const canvas = document.getElementById('chessboard');
const ctx = canvas.getContext('2d');

const gridSize = 30; // 每个格子的尺寸
const boardSize = 15; // 棋盘大小

// 绘制棋盘
function drawChessboard() {
    for (let i = 0; i < boardSize; i++) {
        // 绘制横线
        ctx.beginPath();
        ctx.moveTo(gridSize / 2, gridSize / 2 + i * gridSize);
        ctx.lineTo(gridSize / 2 + (boardSize - 1) * gridSize, gridSize / 2 + i * gridSize);
        ctx.stroke();

        // 绘制竖线
        ctx.beginPath();
        ctx.moveTo(gridSize / 2 + i * gridSize, gridSize / 2);
        ctx.lineTo(gridSize / 2 + i * gridSize, gridSize / 2 + (boardSize - 1) * gridSize);
        ctx.stroke();
    }
}

drawChessboard();

这段代码的核心在于 drawChessboard 函数,它使用 canvasbeginPathmoveTolineTo 方法绘制横纵交错的直线,构成棋盘的网格。

关键点:

  • gridSize 定义了每个格子的像素尺寸,可以根据需要调整。
  • boardSize 定义了棋盘的大小(例如,15x15)。
  • ctx.stroke() 用于实际绘制线条。

2. 落子与棋子绘制

现在,我们需要监听 canvas 的点击事件,获取点击位置,并绘制棋子。首先,定义一个二维数组来存储棋盘状态:

let chessData = [];
for (let i = 0; i < boardSize; i++) {
    chessData[i] = [];
    for (let j = 0; j < boardSize; j++) {
        chessData[i][j] = 0; // 0 表示该位置为空
    }
}

let isBlack = true; // 轮到黑子下

然后,监听点击事件:

canvas.addEventListener('click', function(event) {
    const x = event.offsetX;
    const y = event.offsetY;

    const i = Math.floor(x / gridSize);
    const j = Math.floor(y / gridSize);

    if (chessData[i][j] === 0) {
        chessData[i][j] = isBlack ? 1 : 2; // 1 表示黑子,2 表示白子
        drawChess(i, j, isBlack);
        isBlack = !isBlack;

        // TODO: 胜负判定
    }
});

这段代码首先获取点击位置的坐标 xy,然后计算出对应的棋盘格子的索引 ij。如果该位置为空 (chessData[i][j] === 0),则在该位置落子,并调用 drawChess 函数绘制棋子,最后切换到另一方。

接下来,实现 drawChess 函数:

function drawChess(i, j, isBlack) {
    const x = gridSize / 2 + i * gridSize;
    const y = gridSize / 2 + j * gridSize;

    ctx.beginPath();
    ctx.arc(x, y, gridSize / 2 - 2, 0, 2 * Math.PI); // 绘制圆形棋子
    ctx.fillStyle = isBlack ? 'black' : 'white';
    ctx.fill();
}

drawChess 函数使用 canvasarc 方法绘制圆形棋子,并根据 isBlack 的值设置填充颜色。

关键点:

  • event.offsetXevent.offsetY 获取相对于 canvas 元素的点击坐标。
  • Math.floor(x / gridSize)Math.floor(y / gridSize) 计算出棋盘格子的索引。
  • ctx.arc 用于绘制圆形。
  • ctx.fillStyle 用于设置填充颜色。
  • ctx.fill 用于实际填充颜色。

3. 胜负判定:核心算法

胜负判定是五子棋游戏的核心。我们需要检查当前落子位置的横向、纵向、左斜向和右斜向四个方向,看是否有连续五个相同颜色的棋子。

function checkWin(i, j, color) {
    // 横向
    let count = 1;
    for (let k = i - 1; k >= 0 && chessData[k][j] === color; k--) {
        count++;
    }
    for (let k = i + 1; k < boardSize && chessData[k][j] === color; k++) {
        count++;
    }
    if (count >= 5) return true;

    // 纵向
    count = 1;
    for (let k = j - 1; k >= 0 && chessData[i][k] === color; k--) {
        count++;
    }
    for (let k = j + 1; k < boardSize && chessData[i][k] === color; k++) {
        count++;
    }
    if (count >= 5) return true;

    // 左斜向
    count = 1;
    for (let k = i - 1, l = j - 1; k >= 0 && l >= 0 && chessData[k][l] === color; k--, l--) {
        count++;
    }
    for (let k = i + 1, l = j + 1; k < boardSize && l < boardSize && chessData[k][l] === color; k++, l++) {
        count++;
    }
    if (count >= 5) return true;

    // 右斜向
    count = 1;
    for (let k = i - 1, l = j + 1; k >= 0 && l < boardSize && chessData[k][l] === color; k--, l++) {
        count++;
    }
    for (let k = i + 1, l = j - 1; k < boardSize && l >= 0 && chessData[k][l] === color; k++, l--) {
        count++;
    }
    if (count >= 5) return true;

    return false;
}

checkWin 函数接收当前落子的坐标 ij,以及棋子的颜色 color,然后分别在四个方向上统计连续相同颜色棋子的数量。如果任何一个方向的数量达到 5,则返回 true,表示该颜色获胜。

关键点:

  • 循环遍历四个方向,统计连续相同颜色棋子的数量。
  • 注意边界条件的判断,防止数组越界。

现在,将 checkWin 函数加入到 canvas 的点击事件处理函数中:

canvas.addEventListener('click', function(event) {
    const x = event.offsetX;
    const y = event.offsetY;

    const i = Math.floor(x / gridSize);
    const j = Math.floor(y / gridSize);

    if (chessData[i][j] === 0) {
        const color = isBlack ? 1 : 2;
        chessData[i][j] = color;
        drawChess(i, j, isBlack);

        if (checkWin(i, j, color)) {
            alert(isBlack ? '黑棋胜!' : '白棋胜!');
        }

        isBlack = !isBlack;
    }
});

4. 完整代码

以下是 script.js 文件的完整代码:

const canvas = document.getElementById('chessboard');
const ctx = canvas.getContext('2d');

const gridSize = 30;
const boardSize = 15;

let chessData = [];
for (let i = 0; i < boardSize; i++) {
    chessData[i] = [];
    for (let j = 0; j < boardSize; j++) {
        chessData[i][j] = 0;
    }
}

let isBlack = true;

function drawChessboard() {
    for (let i = 0; i < boardSize; i++) {
        ctx.beginPath();
        ctx.moveTo(gridSize / 2, gridSize / 2 + i * gridSize);
        ctx.lineTo(gridSize / 2 + (boardSize - 1) * gridSize, gridSize / 2 + i * gridSize);
        ctx.stroke();

        ctx.beginPath();
        ctx.moveTo(gridSize / 2 + i * gridSize, gridSize / 2);
        ctx.lineTo(gridSize / 2 + i * gridSize, gridSize / 2 + (boardSize - 1) * gridSize);
        ctx.stroke();
    }
}

function drawChess(i, j, isBlack) {
    const x = gridSize / 2 + i * gridSize;
    const y = gridSize / 2 + j * gridSize;

    ctx.beginPath();
    ctx.arc(x, y, gridSize / 2 - 2, 0, 2 * Math.PI);
    ctx.fillStyle = isBlack ? 'black' : 'white';
    ctx.fill();
}

function checkWin(i, j, color) {
    // 横向
    let count = 1;
    for (let k = i - 1; k >= 0 && chessData[k][j] === color; k--) {
        count++;
    }
    for (let k = i + 1; k < boardSize && chessData[k][j] === color; k++) {
        count++;
    }
    if (count >= 5) return true;

    // 纵向
    count = 1;
    for (let k = j - 1; k >= 0 && chessData[i][k] === color; k--) {
        count++;
    }
    for (let k = j + 1; k < boardSize && chessData[i][k] === color; k++) {
        count++;
    }
    if (count >= 5) return true;

    // 左斜向
    count = 1;
    for (let k = i - 1, l = j - 1; k >= 0 && l >= 0 && chessData[k][l] === color; k--, l--) {
        count++;
    }
    for (let k = i + 1, l = j + 1; k < boardSize && l < boardSize && chessData[k][l] === color; k++, l++) {
        count++;
    }
    if (count >= 5) return true;

    // 右斜向
    count = 1;
    for (let k = i - 1, l = j + 1; k >= 0 && l < boardSize && chessData[k][l] === color; k--, l++) {
        count++;
    }
    for (let k = i + 1, l = j - 1; k < boardSize && l >= 0 && chessData[k][l] === color; k++, l--) {
        count++;
    }
    if (count >= 5) return true;

    return false;
}

canvas.addEventListener('click', function(event) {
    const x = event.offsetX;
    const y = event.offsetY;

    const i = Math.floor(x / gridSize);
    const j = Math.floor(y / gridSize);

    if (chessData[i][j] === 0) {
        const color = isBlack ? 1 : 2;
        chessData[i][j] = color;
        drawChess(i, j, isBlack);

        if (checkWin(i, j, color)) {
            alert(isBlack ? '黑棋胜!' : '白棋胜!');
        }

        isBlack = !isBlack;
    }
});

drawChessboard();

5. 总结与扩展

通过这篇教程,你应该已经掌握了如何使用 JavaScript 和 canvas 绘制五子棋棋盘,以及如何判断胜负。你可以根据自己的需求,对代码进行扩展,例如:

  • 添加悔棋功能。
  • 实现人机对战,使用 AI 算法来决定电脑的落子位置。
  • 美化棋盘和棋子的样式。
  • 使用 WebSocket 实现多人在线对战。

希望这篇教程对你有所帮助! 祝你游戏开发愉快! 别忘了点个赞哦!

点评评价

captcha
健康