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