探索 HTML、CSS 和 JavaScript 实现的五子棋游戏
用 HTML、CSS 和 JavaScript 实现各种有趣的小游戏是一种既实用又有趣的学习方式。今天,我们就来深入探索一个用这三种技术打造的五子棋小游戏
整体项目概述
这个五子棋游戏是一个网页版应用,用户可以在浏览器中直接打开并进行对战。项目的整体结构清晰,由 HTML 构建页面结构,CSS 负责样式美化,JavaScript 实现游戏的核心逻辑。通过三者的协同工作,为玩家提供了一个流畅、有趣的游戏体验。
HTML:搭建游戏的骨架
HTML 文件为游戏搭建了基本的框架,包含了游戏的标题、控制按钮、棋盘区域、游戏信息展示和结果显示等部分。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>五子棋游戏</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- 其他元信息和脚本引入 -->
</head>
<body>
<div class="max-w-5xl w-full bg-white rounded-2xl shadow-xl overflow-hidden">
<!-- 游戏标题 -->
<header class="bg-primary text-white p-4 text-center">
<h1 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold tracking-wide">五子棋</h1>
<p class="text-amber-100 mt-1">落子连五获胜</p>
</header>
<!-- 游戏区域 -->
<main class="p-6 md:p-8">
<!-- 游戏控制区 -->
<div class="flex flex-col lg:flex-row justify-between items-center mb-6 gap-4">
<!-- 玩家信息和计时器 -->
<div class="flex flex-col sm:flex-row items-center gap-3 w-full lg:w-auto">
<div class="flex items-center gap-3">
<div id="current-player" class="w-6 h-6 rounded-full piece-black piece-shadow"></div>
<span class="text-lg font-medium">当前玩家: <span id="player-text" class="text-black font-bold">黑棋</span></span>
</div>
<div class="flex items-center gap-3 mt-2 sm:mt-0">
<span class="text-lg font-medium">游戏时间: <span id="timer" class="text-primary font-bold">00:00</span></span>
</div>
</div>
<!-- 控制按钮 -->
<div class="flex gap-3 w-full lg:w-auto justify-center lg:justify-end">
<button id="restart-btn" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg transition-all flex items-center gap-2">
<i class="fa fa-refresh"></i>
<span>重新开始</span>
</button>
<button id="undo-btn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg transition-all flex items-center gap-2">
<i class="fa fa-undo"></i>
<span>悔棋</span>
</button>
<button id="hint-btn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition-all flex items-center gap-2">
<i class="fa fa-lightbulb-o"></i>
<span>提示</span>
</button>
</div>
</div>
<!-- 游戏主区域 -->
<div class="flex flex-col lg:flex-row gap-6">
<!-- 棋盘容器 -->
<div class="relative mx-auto aspect-square max-w-md w-full bg-board rounded-lg board-shadow overflow-hidden grid-pattern" style="background-size: calc(100% / 14) calc(100% / 14);">
<div id="board" class="absolute inset-0 cursor-pointer"></div>
<!-- 棋盘标记点 -->
<div class="absolute w-2 h-2 bg-dark rounded-full" style="top: calc(3/14 * 100%); left: calc(3/14 * 100%);"></div>
<!-- 其他标记点 -->
</div>
<!-- 游戏信息和历史 -->
<div class="w-full lg:w-80">
<!-- 游戏统计 -->
<div class="bg-gray-50 rounded-xl p-4 mb-4">
<h3 class="text-lg font-semibold mb-3 text-gray-800">游戏统计</h3>
<div class="grid grid-cols-2 gap-2">
<div class="bg-white p-3 rounded-lg shadow-sm">
<div class="text-sm text-gray-500">黑棋胜场</div>
<div id="black-wins" class="text-xl font-bold text-black">0</div>
</div>
<!-- 其他统计信息 -->
</div>
</div>
<!-- 走棋历史 -->
<div class="bg-gray-50 rounded-xl p-4 h-64 overflow-hidden">
<div class="flex justify-between items-center mb-3">
<h3 class="text-lg font-semibold text-gray-800">走棋历史</h3>
<button id="clear-history-btn" class="text-xs text-gray-500 hover:text-primary">
<i class="fa fa-trash-o"></i> 清空
</button>
</div>
<div id="move-history" class="overflow-y-auto h-48 text-sm space-y-1">
<div class="text-gray-500 italic text-center">游戏尚未开始</div>
</div>
</div>
</div>
</div>
<!-- 游戏结果 -->
<div id="result" class="hidden mt-6 text-center p-4 rounded-lg bg-green-100 border border-green-300">
<h2 id="result-text" class="text-xl font-bold text-green-800"></h2>
<div class="mt-2 text-sm text-gray-600">
用时: <span id="game-time">00:00</span> | 步数: <span id="move-count">0</span>
</div>
<button id="new-game-btn" class="mt-3 bg-primary hover:bg-primary/90 text-white px-6 py-2 rounded-lg transition-all">
开始新游戏
</button>
</div>
</main>
<!-- 页脚 -->
<footer class="bg-gray-100 text-gray-600 p-4 text-center text-sm">
<p>© 2025 五子棋游戏 | 简约而不简单</p>
</footer>
</div>
<script>
// JavaScript 代码
</script>
</body>
</html>
从这段代码中可以看出,HTML 页面使用了 Tailwind CSS 框架来快速实现样式布局,同时引入了 Font Awesome 图标库来增强视觉效果。页面结构层次分明,将游戏的各个部分清晰地展示出来,方便用户操作和查看信息。
CSS:美化游戏的外观
CSS 部分主要负责游戏界面的样式设计,通过自定义样式和 Tailwind CSS 的扩展,为游戏增添了丰富的视觉效果。
@layer utilities {
.board-shadow {
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.piece-shadow {
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);
}
.piece-white {
background: radial-gradient(circle at 35% 35%, #ffffff, #e0e0e0);
}
.piece-black {
background: radial-gradient(circle at 35% 35%, #505050, #000000);
}
.grid-pattern {
background-image: linear-gradient(#8B4513 1px, transparent 1px),
linear-gradient(90deg, #8B4513 1px, transparent 1px);
}
.animate-fadeIn {
animation: fadeIn 0.5s ease-in-out;
}
.hover-indicator {
position: absolute;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: rgba(255, 215, 0, 0.7);
opacity: 0;
transition: opacity 0.2s ease;
}
.game-history-item {
padding: 0.5rem;
border-radius: 0.375rem;
transition: background-color 0.2s;
}
.game-history-item:hover {
background-color: rgba(0, 0, 0, 0.05);
}
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
这里定义了棋盘的阴影效果、棋子的渐变背景、网格图案以及动画效果等。例如,piece-white
和 piece-black
类分别为白棋和黑棋设置了不同的径向渐变背景,使其看起来更加立体。而 animate-fadeIn
动画则为游戏结果的显示添加了淡入效果,提升了用户体验。
JavaScript:实现游戏的核心逻辑
JavaScript 是这个五子棋游戏的核心,它负责处理游戏的各种交互和逻辑判断。
游戏状态管理
// 游戏常量
const BOARD_SIZE = 15;
const CELL_SIZE = 100 / (BOARD_SIZE - 1); // 百分比单位
const WINNING_LENGTH = 5;
// 游戏状态
let currentPlayer = 'black';
let gameBoard = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(null));
let gameActive = true;
let moveHistory = [];
let gameStartTime = null;
let gameTimer = null;
let hintTimeout = null;
let hintPosition = null;
// 游戏统计
let stats = {
blackWins: 0,
whiteWins: 0,
totalGames: 0,
totalTime: 0
};
这里定义了游戏的常量和状态变量,包括棋盘大小、获胜条件、当前玩家、游戏棋盘状态、走棋历史等。同时,还使用 stats
对象来记录游戏的统计信息,如黑棋和白棋的胜场数、总对局数和总游戏时间。
初始化棋盘
function initializeBoard() {
boardElement.innerHTML = '';
// 创建交叉点
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
const intersection = document.createElement('div');
intersection.className = 'absolute w-8 h-8 rounded-full flex items-center justify-center transition-all duration-200 hover:bg-black/5 -translate-x-1/2 -translate-y-1/2';
intersection.style.left = `${col * CELL_SIZE}%`;
intersection.style.top = `${row * CELL_SIZE}%`;
intersection.dataset.row = row;
intersection.dataset.col = col;
// 添加鼠标悬停指示器
const hoverIndicator = document.createElement('div');
hoverIndicator.className = 'hover-indicator';
intersection.appendChild(hoverIndicator);
// 添加点击事件
intersection.addEventListener('click', () => handleIntersectionClick(row, col));
// 添加鼠标进入事件
intersection.addEventListener('mouseenter', () => {
if (gameActive && gameBoard[row][col] === null) {
hoverIndicator.style.opacity = '1';
}
});
// 添加鼠标离开事件
intersection.addEventListener('mouseleave', () => {
hoverIndicator.style.opacity = '0';
});
boardElement.appendChild(intersection);
}
}
}
initializeBoard
函数用于创建棋盘的交叉点,并为每个交叉点添加鼠标悬停指示器和点击事件。当鼠标悬停在空闲的交叉点上时,指示器会显示出来,提示玩家可以落子。
处理落子事件
function handleIntersectionClick(row, col) {
// 检查是否可以落子
if (gameBoard[row][col] !== null || !gameActive) {
return;
}
// 清除提示
clearHint();
// 记录历史
moveHistory.push({row, col, player: currentPlayer});
// 更新走棋历史显示
addToMoveHistory(row, col, currentPlayer);
// 落子
placePiece(row, col, currentPlayer);
// 检查是否获胜
if (checkWin(row, col, currentPlayer)) {
gameActive = false;
stopTimer();
// 更新统计
if (currentPlayer === 'black') {
stats.blackWins++;
} else {
stats.whiteWins++;
}
stats.totalGames++;
stats.totalTime += getElapsedTime();
saveStats();
updateStatsDisplay();
showResult(`${currentPlayer === 'black' ? '黑棋' : '白棋'}获胜!`);
return;
}
// 检查是否平局
if (checkDraw()) {
gameActive = false;
stopTimer();
showResult('平局!');
return;
}
// 切换玩家
currentPlayer = currentPlayer === 'black' ? 'white' : 'black';
updatePlayerIndicator();
}
handleIntersectionClick
函数处理玩家点击交叉点的事件。它会先检查该位置是否可以落子,然后记录走棋历史,放置棋子,并检查是否获胜或平局。如果游戏结束,会更新统计信息并显示结果;否则,切换玩家继续游戏。
检查获胜和平局
function checkWin(row, col, player) {
const directions = [
[0, 1], // 水平
[1, 0], // 垂直
[1, 1], // 对角线
[1, -1] // 反对角线
];
for (const [dx, dy] of directions) {
let count = 1; // 当前位置已经有一个棋子
// 正向检查
for (let i = 1; i < WINNING_LENGTH; i++) {
const newRow = row + i * dx;
const newCol = col + i * dy;
if (
newRow >= 0 && newRow < BOARD_SIZE &&
newCol >= 0 && newCol < BOARD_SIZE &&
gameBoard[newRow][newCol] === player
) {
count++;
} else {
break;
}
}
// 反向检查
for (let i = 1; i < WINNING_LENGTH; i++) {
const newRow = row - i * dx;
const newCol = col - i * dy;
if (
newRow >= 0 && newRow < BOARD_SIZE &&
newCol >= 0 && newCol < BOARD_SIZE &&
gameBoard[newRow][newCol] === player
) {
count++;
} else {
break;
}
}
// 检查是否连成五子
if (count >= WINNING_LENGTH) {
// 高亮显示获胜的棋子
highlightWinningPieces(row, col, dx, dy, count);
return true;
}
}
return false;
}
function checkDraw() {
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
if (gameBoard[row][col] === null) {
return false; // 还有空位,不是平局
}
}
}
return true; // 棋盘已满,平局
}
checkWin
函数通过检查四个方向(水平、垂直、对角线和反对角线)上是否有连续五个相同颜色的棋子来判断是否获胜。如果获胜,会调用 highlightWinningPieces
函数高亮显示获胜的棋子。checkDraw
函数则遍历整个棋盘,检查是否还有空位,如果没有则判定为平局。
总结
通过这个五子棋游戏项目,我们可以看到 HTML、CSS 和 JavaScript 是如何协同工作来创建一个完整的 Web 应用的。HTML 提供了页面的结构,CSS 美化了界面,而 JavaScript 实现了游戏的核心逻辑和交互功能。这个项目不仅适合初学者学习 Web 开发的基础知识,还可以作为一个基础,进一步扩展和优化,例如添加 AI 对手、在线对战等功能。