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

news/2025/3/5 6:01:04/
htmledit_views">

目录

1 前言

2 技术实现

2.1 HTML 部分

2.2 CSS 部分

2.3 JavaScript 部分

3 代码解析

3.1 HTML 部分

3.2 JavaScript 部分

3.2.1 全局变量定义

3.2.2 自适应尺寸函数 resizeCanvas()

3.2.3 初始化棋盘函数 initBoard()

3.2.4 绘制棋盘函数 drawBoard()

3.2.5 绘制棋子函数 drawPiece()

3.2.6 检查胜利函数 checkWin()

3.2.7 处理点击事件函数

3.2.8 重置游戏函数 resetGame()

3.2.9 初始化部分

4 完整代码

5 运行结果

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


http://www.ppmy.cn/news/1576746.html

相关文章

【网络安全】——二进制协议 vs 文本协议:从原理到实战的深度解析

目录 引言 一、协议的本质与分类 二、二进制协议详解 1. 核心特点 2. 典型结构示例 3. 常见应用场景 4. 详细介绍 三、文本协议详解 1. 核心特点 2. 典型结构示例 3. 常见应用场景 4.详细介绍 四、关键对比&#xff1a;二进制协议 vs 文本协议 五、实战案例&…

nnUNet报错

nnUNet报错处理 Traceback (most recent call last):File "/opt/conda/envs/nnunet/lib/python3.11/threading.py", line 1045, in _bootstrap_innerself.run()File "/opt/conda/envs/nnunet/lib/python3.11/threading.py", line 982, in runself._target…

[BJDCTF2020]ZJCTF,不过如此1 [php://filter][正则表达式get输入数据][捕获组反向引用][php中单双引号]

题目&#xff1a; 我仿佛见到了一位故人。。。也难怪&#xff0c;题目就是ZJCTF 按要求提交/?textdata://,I have a dream&filenext.php后&#xff1a; ......不太行&#xff0c;好像得用filephp://filter/convert.base64-encode/resourcenext.php 耶&#xff1f;那 f…

android接入rocketmq

一 前言 RocketMQ 作为一个功能强大的消息队列系统&#xff0c;不仅支持基本的消息发布与订阅&#xff0c;还提供了顺序消息、延时消息、事务消息等高级功能&#xff0c;适应了复杂的分布式系统需求。其高可用性架构、多副本机制、完善的运维管理工具&#xff0c;以及安全控制…

关于常规模式下运行VScode无法正确执行“pwsh”问题

前言&#xff1a; pwsh在系统环境中正确配置&#xff0c;且可以运行在cmd&#xff0c; powshell&#xff08;5.1&#xff09;--- 都需要在管理员权限下运行 &#xff08;打开setting&#xff09; 打开setting.json &#xff08;在vscode中添加 powershell 7 路径&…

字节旗下两款AI编程工具

Trae 和 MarsCode 是字节跳动推出的两款 AI 编程工具,旨在通过人工智能技术提升开发效率和质量。以下是它们的详细介绍: 1 Trae Trae 是字节跳动于 2025 年 1 月推出的 AI 原生集成开发环境(IDE),由旗下新加坡公司 SPRING PTE 开发。它主打“用自然语言生成代码”,具备…

贪心算法+题目

贪心算法 跳跃游戏跳跃游戏2 跳跃游戏 题目 拿到题目就暴力穷举&#xff0c;我用的是dfs&#xff0c;加上备忘录之后还是超出时间限制。就考虑一下贪心算法。你想 我在[0,n-2]位置遍历求出可以跳跃的最远距离&#xff0c;用farthest更新最大值&#xff0c;如果>终点就返回t…

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_conf_read_token

ngx_conf_read_token 定义在src\core\ngx_conf_file.c static ngx_int_t ngx_conf_read_token(ngx_conf_t *cf) {u_char *start, ch, *src, *dst;off_t file_size;size_t len;ssize_t n, size;ngx_uint_t found, need_space, last_space, sharp_comm…