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

embedded/2025/3/5 22:29:33/
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/embedded/170312.html

相关文章

【音视频】视频基本概念

一、视频的基本概念 1.1 视频码率&#xff08;kb/s&#xff09; 视频码率是指视频文件在单位时间内使用的数据流量&#xff0c;也叫码流率。码率越大&#xff0c;说明单位时间内取样率越大&#xff0c;数据流进度也就越高 1.2 视频帧率&#xff08;fps&#xff09; 视频帧率…

设计模式面试知识点总结

目录 1. 设计原则2. 常用的设计模式2.1 单例模式2.2 简单工厂模式2.3 模板方法2.4 责任链模式2.5 装饰器模式 1. 设计原则 &#xff08;1&#xff09;设计模式是在软件开发中&#xff0c;经过验证的、在特定场景的解决方案。另⼀种说法是扩展隔离变化点&#xff0c;抽象稳定点…

补丁供应链案例:CVE-2024-7254. Protobuf. Google

​查看 github advisories 列表 github.com/advisories/GHSA&#xff0c;定位到 patch 的 java 版本是 3.25.5&#xff0c;打开到 25.x 分支。事后发现相关修复最早出现在 2024 年 7月&#xff0c;merge 到 25.x 分支是 9 月 19&#xff0c;与 advisories 的时间引证。 这个库…

112页精品PPT | DeepSeek行业应用实践报告

这份文件是一份关于DeepSeek行业应用实践的报告&#xff0c;以PPT形式呈现&#xff0c;共112页&#xff0c;详细介绍了DeepSeek及其核心产品DeepSeek-R1的技术特点、市场表现、应用路径以及在多领域的实践案例。报告展示了DeepSeek在市场上的快速崛起&#xff0c;包括其日活用户…

win32汇编环境,对话框中使用IP地址控件示例

;运行效果 ;win32汇编环境,对话框中使用IP地址控件示例 ;演示了如何设置IP控件的IP地址,取得IP控件的地址值的操作,并解释了其原理 ;也可以使用编辑框控件代替,但是需要写更多的东西,比如需要输入数值是否超255,但IP地址控件不用,它已经封装了这些自检功能 ;直接抄进RadA…

Halcon 车牌识别-超精细教程

车牌示例 流程: 读取图片转灰度图阈值分割,找车牌内容将车牌位置设置变换区域形状找到中心点和弧度利用仿射变换,斜切车牌旋转转正,把车牌抠出来利用形态学操作拼接车牌号数字训练ocr开始识别中文车牌 本文章用到的算子(解析) Halcon 算子-承接车牌识别-CSDN博客 rgb1_to_gray…

从ETL到数仓分层:大数据处理的“金字塔”构建之道

在当今数据驱动的时代&#xff0c;大数据处理已成为企业决策和业务优化的核心。而ETL&#xff08;Extract, Transform, Load&#xff09;作为数据处理的基石&#xff0c;其背后的数仓分层理念更是决定了数据处理的效率与质量。本文将深入探讨ETL工作中的数仓分层理念&#xff0…

HTML第三节

一.初识CSS 1.CSS定义 A.内部样式表 B.外部样式表 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title&g…