基于 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 在前端开发中的强大功能。它们不仅可以创建出美观的用户界面,还能实现复杂的交互逻辑。希望这个项目能为大家提供一些灵感,让大家在前端开发的道路上越走越远。

相关推荐
yinxiangzhongqing19 分钟前
深入理解JavaScript的执行机制
开发语言·前端·javascript
问道飞鱼23 分钟前
【前端知识】基于Lit的复杂组件开发
前端·web components·lit
小画家~29 分钟前
第五十八:父传子 defineProps
前端·javascript·vue.js
青红光硫化黑32 分钟前
前端基础之组件
前端·javascript
codexu_46122918733 分钟前
Tauri跨端笔记实战(4) - 如何实现系统级截图
前端·笔记·rust·app·tauri
没资格抱怨1 小时前
Element Plus中的树组件的具体用法(持续更新!)
前端·javascript·算法
小周同学:1 小时前
Fiddler抓取App接口-Andriod/IOS配置方法
前端·ios·fiddler
86Eric1 小时前
vue Table 表格自适应窗口高度,表头固定
javascript·vue.js·table 高度自适应·table 表头固定
@阿猫阿狗~1 小时前
在Web开发中,优化资源文件的大小
前端
智绘前端1 小时前
Vue3国际化开发实战:i18n-Ally + vue-i18n@next高效配置教程,项目中文显示
前端·javascript·vue.js