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