基于 HTML、CSS 和 JavaScript 的五子棋游戏

目录

[1 前言](#1 前言)

[2 技术实现](#2 技术实现)

[2.1 HTML 部分](#2.1 HTML 部分)

[2.2 CSS 部分](#2.2 CSS 部分)

[2.3 JavaScript 部分](#2.3 JavaScript 部分)

[3 代码解析](#3 代码解析)

[3.1 HTML 部分](#3.1 HTML 部分)

[3.2 JavaScript 部分](#3.2 JavaScript 部分)

[3.2.1 全局变量定义](#3.2.1 全局变量定义)

[3.2.2 自适应尺寸函数 resizeCanvas()](#3.2.2 自适应尺寸函数 resizeCanvas())

[3.2.3 初始化棋盘函数 initBoard()](#3.2.3 初始化棋盘函数 initBoard())

[3.2.4 绘制棋盘函数 drawBoard()](#3.2.4 绘制棋盘函数 drawBoard())

[3.2.5 绘制棋子函数 drawPiece()](#3.2.5 绘制棋子函数 drawPiece())

[3.2.6 检查胜利函数 checkWin()](#3.2.6 检查胜利函数 checkWin())

[3.2.7 处理点击事件函数](#3.2.7 处理点击事件函数)

[3.2.8 重置游戏函数 resetGame()](#3.2.8 重置游戏函数 resetGame())

[3.2.9 初始化部分](#3.2.9 初始化部分)

[4 完整代码](#4 完整代码)

[5 运行结果](#5 运行结果)

[6 总结](#6 总结)


1 前言

在前端开发的世界里,我们常常希望通过实际项目来巩固所学的知识。今天,我将为大家介绍一个使用 HTML、CSS 和 JavaScript 实现的五子棋游戏,它不仅具备基本的游戏功能,还拥有良好的用户界面和自适应布局。

这个五子棋游戏采用了 19x19 的标准棋盘规格,支持两人轮流对战。游戏界面简洁美观,棋盘采用了木纹背景,棋子具有立体效果,给玩家带来更好的视觉体验。同时,游戏还支持自适应布局,无论在何种设备上打开,都能完美适配。

2 技术实现

2.1 HTML 部分

HTML 部分主要负责搭建游戏的基本结构,包括一个 canvas 元素用于绘制棋盘和棋子,一个 div 元素用于显示游戏状态,以及一个按钮用于重新开始游戏。通过合理的布局和样式设置,使游戏界面更加美观。

2.2 CSS 部分

CSS 部分主要用于设置游戏界面的样式,包括 body 的布局、canvas 的边框、背景和阴影,以及状态显示区域和按钮的样式。通过使用 flexbox 布局,实现了页面内容的居中显示。同时,为按钮添加了 :hover 效果,增强了用户交互性。

2.3 JavaScript 部分

JavaScript 部分是游戏的核心,实现了游戏的主要逻辑。以下是一些关键功能的实现:

  • 自适应布局 :通过 resizeCanvas() 函数,根据窗口大小动态计算棋盘格子的大小,并调整 canvas 的尺寸,确保游戏在不同设备上都能正常显示。
  • 棋盘初始化initBoard() 函数将棋盘数组初始化为全 0,表示所有位置为空,并设置游戏初始状态。
  • 绘制棋盘和棋子drawBoard() 函数负责绘制棋盘的网格线、中心点和角点,以及根据棋盘数组的状态绘制棋子。drawPiece() 函数用于绘制单个棋子,并为白棋添加了立体效果。
  • 胜利判断checkWin() 函数通过遍历四个方向,检查是否有连续五个相同颜色的棋子,从而判断是否有玩家胜利。
  • 点击事件处理 :通过监听 canvas 的点击事件,处理玩家的落子操作,并在每次落子后检查是否有玩家胜利,若有则更新游戏状态,否则切换玩家。

3 代码解析

3.1 HTML 部分

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>五子棋</title>
    <!-- 样式部分 -->
    <style>
        body {
            margin: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background-color: #f0f0f0;
        }
        .container {
            text-align: center;
        }
        canvas {
            border: 2px solid #8B4513; /* 深棕色边框 */
            border-radius: 10px;
            background: url('https://www.transparenttextures.com/patterns/wood-pattern.png  '); /* 木纹背景 */
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
        }
        #status {
            font-size: 24px;
            margin: 20px 0;
            color: #333;
            font-family: Arial, sans-serif;
        }
        button {
            padding: 10px 20px;
            font-size: 18px;
            background-color: #8B4513;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        button:hover {
            background-color: #A0522D;
        }
    </style>
</head>
<body>
    <div class="container">
        <canvas id="chessboard"></canvas>
        <div id="status">当前玩家:黑棋</div>
        <button onclick="resetGame()">重新开始</button>
    </div>
    <!-- JavaScript 代码 -->
    <script>
        // ...
    </script>
</body>
</html>
  • 整体结构 :这是一个标准的 HTML5 文档,包含 headbody 部分。
  • head 部分
    • meta charset="UTF-8":设置字符编码为 UTF - 8,确保中文等字符能正确显示。
    • title:设置网页标题为 "五子棋"。
    • style 标签:定义了页面的样式,包括 body 的布局、canvas 的样式(边框、背景、阴影等)、状态显示区域的样式和按钮的样式。
  • body 部分
    • div.container:作为一个容器,用于居中显示内容。
    • canvas#chessboard:用于绘制五子棋棋盘和棋子。
    • div#status:用于显示当前游戏状态,如当前玩家或胜利信息。
    • button:点击该按钮会调用 resetGame() 函数来重新开始游戏。

3.2 JavaScript 部分

3.2.1 全局变量定义

javascript 复制代码
const canvas = document.getElementById('chessboard');
const ctx = canvas.getContext('2d');
const status = document.getElementById('status');
const BOARD_SIZE = 19; // 19x19棋盘,更大的标准规格
let GRID_SIZE; // 动态计算格子大小
let board = [];
let currentPlayer = 1; // 1为黑棋,2为白棋
let gameOver = false;
  • canvas:获取 HTML 中的 canvas 元素。
  • ctx:获取 canvas 的 2D 绘图上下文。
  • status:获取显示游戏状态的 div 元素。
  • BOARD_SIZE:定义棋盘的大小为 19x19。
  • GRID_SIZE:用于存储每个格子的大小,会动态计算。
  • board:二维数组,用于存储棋盘上每个位置的棋子状态(0 表示空位,1 表示黑棋,2 表示白棋)。
  • currentPlayer:表示当前玩家,1 为黑棋,2 为白棋。
  • gameOver:表示游戏是否结束。

3.2.2 自适应尺寸函数 resizeCanvas()

javascript 复制代码
function resizeCanvas() {
    const minDimension = Math.min(window.innerWidth * 0.8, window.innerHeight * 0.8);
    GRID_SIZE = Math.floor(minDimension / BOARD_SIZE);
    canvas.width = GRID_SIZE * BOARD_SIZE;
    canvas.height = GRID_SIZE * BOARD_SIZE;
    drawBoard();
}
  • 计算窗口宽度和高度的 80% 中的较小值,作为棋盘的最大可用尺寸。
  • 根据 BOARD_SIZE 计算每个格子的大小 GRID_SIZE
  • 设置 canvas 的宽度和高度。
  • 调用 drawBoard() 函数重新绘制棋盘。

3.2.3 初始化棋盘函数 initBoard()

javascript 复制代码
function initBoard() {
    board = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(0));
    gameOver = false;
    currentPlayer = 1;
    status.textContent = "当前玩家:黑棋";
    drawBoard();
}
  • board 数组初始化为全 0,表示棋盘上所有位置为空。
  • gameOver 设为 false,表示游戏未结束。
  • currentPlayer 设为 1,即黑棋先手。
  • 更新状态显示区域的文本。
  • 调用 drawBoard() 函数绘制棋盘。

3.2.4 绘制棋盘函数 drawBoard()

javascript 复制代码
function drawBoard() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 绘制网格线
    ctx.strokeStyle = "#333"; // 深色线条
    ctx.lineWidth = 1;
    for (let i = 0; i < BOARD_SIZE; i++) {
        ctx.beginPath();
        ctx.moveTo(GRID_SIZE / 2 + i * GRID_SIZE, GRID_SIZE / 2);
        ctx.lineTo(GRID_SIZE / 2 + i * GRID_SIZE, canvas.height - GRID_SIZE / 2);
        ctx.moveTo(GRID_SIZE / 2, GRID_SIZE / 2 + i * GRID_SIZE);
        ctx.lineTo(canvas.width - GRID_SIZE / 2, GRID_SIZE / 2 + i * GRID_SIZE);
        ctx.stroke();
    }

    // 绘制中心点和角点(传统棋盘标记)
    const points = [
        [3, 3], [3, 15], [15, 3], [15, 15], [9, 9] // 四个角落和中心
    ];
    ctx.fillStyle = "#333";
    points.forEach(([x, y]) => {
        ctx.beginPath();
        ctx.arc(GRID_SIZE / 2 + x * GRID_SIZE, GRID_SIZE / 2 + y * GRID_SIZE, 3, 0, Math.PI * 2);
        ctx.fill();
    });

    // 绘制棋子
    for (let i = 0; i < BOARD_SIZE; i++) {
        for (let j = 0; j < BOARD_SIZE; j++) {
            if (board[i][j] === 1) {
                drawPiece(i, j, "#000"); // 黑棋
            } else if (board[i][j] === 2) {
                drawPiece(i, j, "#fff"); // 白棋
            }
        }
    }
}
  • 清除 canvas 上的所有内容。
  • 绘制棋盘的网格线。
  • 绘制棋盘的中心点和四个角点。
  • 遍历 board 数组,根据每个位置的状态调用 drawPiece() 函数绘制棋子。

3.2.5 绘制棋子函数 drawPiece()

javascript 复制代码
function drawPiece(x, y, color) {
    const centerX = GRID_SIZE / 2 + x * GRID_SIZE;
    const centerY = GRID_SIZE / 2 + y * GRID_SIZE;
    ctx.beginPath();
    ctx.arc(centerX, centerY, GRID_SIZE / 2 - 2, 0, Math.PI * 2);
    ctx.fillStyle = color;
    ctx.fill();
    ctx.strokeStyle = "#333";
    ctx.lineWidth = 1;
    ctx.stroke();

    // 添加立体效果
    if (color === "#fff") {
        ctx.beginPath();
        ctx.arc(centerX, centerY, GRID_SIZE / 2 - 4, 0, Math.PI * 2);
        ctx.strokeStyle = "rgba(0, 0, 0, 0.2)";
        ctx.stroke();
    }
}
  • 计算棋子的中心点坐标。
  • 绘制一个圆形表示棋子,并填充相应的颜色。
  • 绘制棋子的边框。
  • 如果是白棋,添加一个浅灰色的边框,以实现立体效果。

3.2.6 检查胜利函数 checkWin()

javascript 复制代码
function checkWin(x, y) {
    const directions = [[1, 0], [0, 1], [1, 1], [1, -1]];
    const player = board[x][y];

    for (let [dx, dy] of directions) {
        let count = 1;
        for (let i = 1; i < 5; i++) {
            const newX = x + dx * i;
            const newY = y + dy * i;
            if (newX >= 0 && newX < BOARD_SIZE && newY >= 0 && newY < BOARD_SIZE && board[newX][newY] === player) {
                count++;
            } else break;
        }
        for (let i = 1; i < 5; i++) {
            const newX = x - dx * i;
            const newY = y - dy * i;
            if (newX >= 0 && newX < BOARD_SIZE && newY >= 0 && newY < BOARD_SIZE && board[newX][newY] === player) {
                count++;
            } else break;
        }
        if (count >= 5) return true;
    }
    return false;
}
  • 定义四个方向:水平、垂直、正对角线和反对角线。
  • 遍历每个方向,从当前位置向正方向和反方向检查连续相同颜色的棋子数量。
  • 如果某个方向上连续相同颜色的棋子数量达到或超过 5 个,则返回 true,表示该玩家胜利。

3.2.7 处理点击事件函数

javascript 复制代码
canvas.addEventListener('click', (e) => {
    if (gameOver) return;

    const rect = canvas.getBoundingClientRect();
    const x = Math.floor((e.clientX - rect.left) / GRID_SIZE);
    const y = Math.floor((e.clientY - rect.top) / GRID_SIZE);

    if (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board[x][y] === 0) {
        board[x][y] = currentPlayer;
        drawBoard();

        if (checkWin(x, y)) {
            status.textContent = `${currentPlayer === 1 ? '黑棋' : '白棋'} 胜利!`;
            gameOver = true;
        } else {
            currentPlayer = currentPlayer === 1 ? 2 : 1;
            status.textContent = `当前玩家:${currentPlayer === 1 ? '黑棋' : '白棋'}`;
        }
    }
});
  • 监听 canvas 的点击事件。
  • 如果游戏已经结束,则不做任何处理。
  • 计算点击位置对应的棋盘坐标。
  • 如果该位置为空,则在该位置放置当前玩家的棋子,并重新绘制棋盘。
  • 检查是否有玩家胜利,如果有则更新状态显示区域的文本,并将 gameOver 设为 true;否则切换玩家,并更新状态显示区域的文本。

3.2.8 重置游戏函数 resetGame()

javascript 复制代码
function resetGame() {
    initBoard();
}
  • 调用 initBoard() 函数重新初始化棋盘。

3.2.9 初始化部分

javascript 复制代码
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
initBoard();
  • 监听窗口大小变化事件,当窗口大小改变时调用 resizeCanvas() 函数。
  • 调用 resizeCanvas() 函数初始化棋盘尺寸。
  • 调用 initBoard() 函数初始化棋盘。

4 完整代码

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>五子棋</title>
    <style>
        body {
            margin: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background-color: #f0f0f0;
        }
        .container {
            text-align: center;
        }
        canvas {
            border: 2px solid #8B4513; /* 深棕色边框 */
            border-radius: 10px;
            background: url('https://www.transparenttextures.com/patterns/wood-pattern.png'); /* 木纹背景 */
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
        }
        #status {
            font-size: 24px;
            margin: 20px 0;
            color: #333;
            font-family: Arial, sans-serif;
        }
        button {
            padding: 10px 20px;
            font-size: 18px;
            background-color: #8B4513;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        button:hover {
            background-color: #A0522D;
        }
    </style>
</head>
<body>
    <div class="container">
        <canvas id="chessboard"></canvas>
        <div id="status">当前玩家:黑棋</div>
        <button onclick="resetGame()">重新开始</button>
    </div>

    <script>
        const canvas = document.getElementById('chessboard');
        const ctx = canvas.getContext('2d');
        const status = document.getElementById('status');
        const BOARD_SIZE = 19; // 19x19棋盘,更大的标准规格
        let GRID_SIZE; // 动态计算格子大小
        let board = [];
        let currentPlayer = 1; // 1为黑棋,2为白棋
        let gameOver = false;

        // 设置自适应尺寸
        function resizeCanvas() {
            const minDimension = Math.min(window.innerWidth * 0.8, window.innerHeight * 0.8);
            GRID_SIZE = Math.floor(minDimension / BOARD_SIZE);
            canvas.width = GRID_SIZE * BOARD_SIZE;
            canvas.height = GRID_SIZE * BOARD_SIZE;
            drawBoard();
        }

        // 初始化棋盘
        function initBoard() {
            board = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(0));
            gameOver = false;
            currentPlayer = 1;
            status.textContent = "当前玩家:黑棋";
            drawBoard();
        }

        // 绘制棋盘
        function drawBoard() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            // 绘制网格线
            ctx.strokeStyle = "#333"; // 深色线条
            ctx.lineWidth = 1;
            for (let i = 0; i < BOARD_SIZE; i++) {
                ctx.beginPath();
                ctx.moveTo(GRID_SIZE / 2 + i * GRID_SIZE, GRID_SIZE / 2);
                ctx.lineTo(GRID_SIZE / 2 + i * GRID_SIZE, canvas.height - GRID_SIZE / 2);
                ctx.moveTo(GRID_SIZE / 2, GRID_SIZE / 2 + i * GRID_SIZE);
                ctx.lineTo(canvas.width - GRID_SIZE / 2, GRID_SIZE / 2 + i * GRID_SIZE);
                ctx.stroke();
            }

            // 绘制中心点和角点(传统棋盘标记)
            const points = [
                [3, 3], [3, 15], [15, 3], [15, 15], [9, 9] // 四个角落和中心
            ];
            ctx.fillStyle = "#333";
            points.forEach(([x, y]) => {
                ctx.beginPath();
                ctx.arc(GRID_SIZE / 2 + x * GRID_SIZE, GRID_SIZE / 2 + y * GRID_SIZE, 3, 0, Math.PI * 2);
                ctx.fill();
            });

            // 绘制棋子
            for (let i = 0; i < BOARD_SIZE; i++) {
                for (let j = 0; j < BOARD_SIZE; j++) {
                    if (board[i][j] === 1) {
                        drawPiece(i, j, "#000"); // 黑棋
                    } else if (board[i][j] === 2) {
                        drawPiece(i, j, "#fff"); // 白棋
                    }
                }
            }
        }

        // 绘制棋子
        function drawPiece(x, y, color) {
            const centerX = GRID_SIZE / 2 + x * GRID_SIZE;
            const centerY = GRID_SIZE / 2 + y * GRID_SIZE;
            ctx.beginPath();
            ctx.arc(centerX, centerY, GRID_SIZE / 2 - 2, 0, Math.PI * 2);
            ctx.fillStyle = color;
            ctx.fill();
            ctx.strokeStyle = "#333";
            ctx.lineWidth = 1;
            ctx.stroke();

            // 添加立体效果
            if (color === "#fff") {
                ctx.beginPath();
                ctx.arc(centerX, centerY, GRID_SIZE / 2 - 4, 0, Math.PI * 2);
                ctx.strokeStyle = "rgba(0, 0, 0, 0.2)";
                ctx.stroke();
            }
        }

        // 检查胜利
        function checkWin(x, y) {
            const directions = [[1, 0], [0, 1], [1, 1], [1, -1]];
            const player = board[x][y];

            for (let [dx, dy] of directions) {
                let count = 1;
                for (let i = 1; i < 5; i++) {
                    const newX = x + dx * i;
                    const newY = y + dy * i;
                    if (newX >= 0 && newX < BOARD_SIZE && newY >= 0 && newY < BOARD_SIZE && board[newX][newY] === player) {
                        count++;
                    } else break;
                }
                for (let i = 1; i < 5; i++) {
                    const newX = x - dx * i;
                    const newY = y - dy * i;
                    if (newX >= 0 && newX < BOARD_SIZE && newY >= 0 && newY < BOARD_SIZE && board[newX][newY] === player) {
                        count++;
                    } else break;
                }
                if (count >= 5) return true;
            }
            return false;
        }

        // 处理点击事件
        canvas.addEventListener('click', (e) => {
            if (gameOver) return;

            const rect = canvas.getBoundingClientRect();
            const x = Math.floor((e.clientX - rect.left) / GRID_SIZE);
            const y = Math.floor((e.clientY - rect.top) / GRID_SIZE);

            if (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board[x][y] === 0) {
                board[x][y] = currentPlayer;
                drawBoard();

                if (checkWin(x, y)) {
                    status.textContent = `${currentPlayer === 1 ? '黑棋' : '白棋'} 胜利!`;
                    gameOver = true;
                } else {
                    currentPlayer = currentPlayer === 1 ? 2 : 1;
                    status.textContent = `当前玩家:${currentPlayer === 1 ? '黑棋' : '白棋'}`;
                }
            }
        });

        // 重置游戏
        function resetGame() {
            initBoard();
        }

        // 初始化
        window.addEventListener('resize', resizeCanvas);
        resizeCanvas();
        initBoard();
    </script>
</body>
</html>

5 运行结果

初始化界面
白棋胜利界面

6 总结

通过这个五子棋游戏的实现,我们可以看到 HTML、CSS 和 JavaScript 在前端开发中的强大功能。它们不仅可以创建出美观的用户界面,还能实现复杂的交互逻辑。希望这个项目能为大家提供一些灵感,让大家在前端开发的道路上越走越远。

相关推荐
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax