拼图小游戏:从零开始实现一个交互式拼图游戏
1. 引言拼图游戏是一种经典的益智游戏它将一张完整的图片分割成若干小块玩家需要通过拖动和旋转这些小碎片将它们重新组合成完整的图片。这种游戏不仅能够锻炼玩家的空间想象力和逻辑思维能力还能带来完成挑战后的成就感。随着前端技术的发展使用 HTML、CSS 和 JavaScript 在浏览器中实现一个交互式的拼图游戏已经变得非常简单。本文将带领大家从零开始一步步实现一个功能完整的拼图小游戏涵盖图片分割、碎片拖动、自动吸附、计时和计步等核心功能。2. 项目结构与准备首先我们创建一个简单的项目结构!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title拼图小游戏/title style /* 样式将在后续步骤中添加 */ /style /head body div idgame-container h1拼图小游戏/h1 div idcontrols button idstart-btn开始游戏/button button idreset-btn重置/button span idtimer时间: 00:00/span span idsteps步数: 0/span /div div idpuzzle-container/div div idoriginal-image-container h3原图参考/h3 img idoriginal-image srcpuzzle-image.jpg alt拼图原图 /div /div script // JavaScript 代码将在后续步骤中添加 /script /body /html准备一张用于拼图的图片例如puzzle-image.jpg并将其放在项目目录中。3. 核心功能实现3.1 图片分割与碎片生成我们需要将原图分割成 3x3 或 4x4 的网格并生成对应的碎片元素。以下是实现这一功能的 JavaScript 代码class PuzzleGame { constructor(containerId, imageSrc, rows 3, cols 3) { this.container document.getElementById(containerId); this.imageSrc imageSrc; this.rows rows; this.cols cols; this.totalPieces rows * cols; this.pieces []; this.emptyIndex this.totalPieces - 1; // 最后一个位置作为空位 this.steps 0; this.time 0; this.timerInterval null; this.isPlaying false; this.init(); } init() { this.createPuzzleContainer(); this.loadImageAndCreatePieces(); this.setupEventListeners(); } createPuzzleContainer() { // 创建拼图容器 const puzzleContainer document.createElement(div); puzzleContainer.id puzzle-board; puzzleContainer.style.display grid; puzzleContainer.style.gridTemplateColumns repeat(${this.cols}, 1fr); puzzleContainer.style.gridTemplateRows repeat(${this.rows}, 1fr); puzzleContainer.style.width 400px; puzzleContainer.style.height 400px; puzzleContainer.style.border 2px solid #333; puzzleContainer.style.margin 20px auto; this.container.appendChild(puzzleContainer); this.board puzzleContainer; } loadImageAndCreatePieces() { const img new Image(); img.src this.imageSrc; img.onload () { this.image img; this.createPieces(); this.shufflePieces(); }; } createPieces() { const pieceWidth this.image.width / this.cols; const pieceHeight this.image.height / this.rows; for (let row 0; row this.rows; row) { for (let col 0; col this.cols; col) { const pieceIndex row * this.cols col; const isLastPiece pieceIndex this.emptyIndex; const piece document.createElement(div); piece.className puzzle-piece; piece.dataset.index pieceIndex; piece.dataset.correctRow row; piece.dataset.correctCol col; if (!isLastPiece) { // 创建 canvas 来绘制图片片段 const canvas document.createElement(canvas); const ctx canvas.getContext(2d); canvas.width pieceWidth; canvas.height pieceHeight; // 绘制图片的对应部分 ctx.drawImage( this.image, col * pieceWidth, row * pieceHeight, pieceWidth, pieceHeight, 0, 0, pieceWidth, pieceHeight ); piece.style.backgroundImage url(${canvas.toDataURL()}); piece.style.backgroundSize ${this.cols * 100}% ${this.rows * 100}%; piece.style.backgroundPosition -${col * pieceWidth}px -${row * pieceHeight}px; // 添加拖动功能 piece.draggable true; } else { // 最后一个碎片作为空位 piece.className puzzle-piece empty; piece.style.backgroundColor #f0f0f0; } piece.style.width 100%; piece.style.height 100%; piece.style.border 1px solid #ddd; piece.style.boxSizing border-box; piece.style.cursor grab; piece.style.userSelect none; this.board.appendChild(piece); this.pieces.push(piece); } } } }3.2 碎片拖动与交换逻辑接下来实现碎片的拖动功能和与相邻空位的交换逻辑// 在 PuzzleGame 类中继续添加方法 shufflePieces() { // 实现洗牌算法Fisher-Yates shuffle const indices Array.from({length: this.totalPieces - 1}, (_, i) i); for (let i indices.length - 1; i 0; i--) { const j Math.floor(Math.random() * (i 1)); [indices[i], indices[j]] [indices[j], indices[i]]; } // 将洗牌后的碎片重新排列最后一个位置保持为空 indices.push(this.emptyIndex); // 清空棋盘并重新添加碎片 this.board.innerHTML ; indices.forEach(index { this.board.appendChild(this.pieces[index]); }); this.updatePiecePositions(); } setupEventListeners() { this.board.addEventListener(dragstart, (e) { if (e.target.classList.contains(puzzle-piece) !e.target.classList.contains(empty)) { e.dataTransfer.setData(text/plain, e.target.dataset.index); e.target.style.opacity 0.5; } }); this.board.addEventListener(dragover, (e) { e.preventDefault(); }); this.board.addEventListener(drop, (e) { e.preventDefault(); const draggedIndex e.dataTransfer.getData(text/plain); const targetPiece e.target.closest(.puzzle-piece); if (!targetPiece || !draggedIndex) return; const draggedPiece this.pieces[draggedIndex]; const targetIndex Array.from(this.board.children).indexOf(targetPiece); const draggedPieceIndex Array.from(this.board.children).indexOf(draggedPiece); // 检查是否相邻 if (this.areAdjacent(draggedPieceIndex, targetIndex)) { this.swapPieces(draggedPieceIndex, targetIndex); this.steps; this.updateStepsDisplay(); if (this.checkWin()) { this.gameWin(); } } draggedPiece.style.opacity 1; }); this.board.addEventListener(dragend, (e) { if (e.target.classList.contains(puzzle-piece)) { e.target.style.opacity 1; } }); } areAdjacent(index1, index2) { const row1 Math.floor(index1 / this.cols); const col1 index1 % this.cols; const row2 Math.floor(index2 / this.cols); const col2 index2 % this.cols; // 检查是否相邻上下左右 return (Math.abs(row1 - row2) 1 col1 col2) || (Math.abs(col1 - col2) 1 row1 row2); } swapPieces(index1, index2) { const children Array.from(this.board.children); const temp children[index1]; children[index1] children[index2]; children[index2] temp; // 更新 DOM this.board.innerHTML ; children.forEach(child this.board.appendChild(child)); this.updatePiecePositions(); } updatePiecePositions() { const children Array.from(this.board.children); children.forEach((piece, index) { piece.dataset.currentIndex index; }); }3.3 游戏状态与界面更新实现计时器、步数统计和游戏胜利检测// 在 PuzzleGame 类中继续添加方法 startGame() { if (this.isPlaying) return; this.isPlaying true; this.steps 0; this.time 0; this.updateStepsDisplay(); this.updateTimerDisplay(); this.timerInterval setInterval(() { this.time; this.updateTimerDisplay(); }, 1000); this.shufflePieces(); } resetGame() { this.isPlaying false; clearInterval(this.timerInterval); this.steps 0; this.time 0; this.updateStepsDisplay(); this.updateTimerDisplay(); this.resetPieces(); } resetPieces() { // 将碎片按正确顺序排列 const sortedPieces [...this.pieces].sort((a, b) { return parseInt(a.dataset.index) - parseInt(b.dataset.index); }); this.board.innerHTML ; sortedPieces.forEach(piece { this.board.appendChild(piece); }); this.updatePiecePositions(); } updateStepsDisplay() { const stepsElement document.getElementById(steps); if (stepsElement) { stepsElement.textContent 步数: ${this.steps}; } } updateTimerDisplay() { const timerElement document.getElementById(timer); if (timerElement) { const minutes Math.floor(this.time / 60).toString().padStart(2, 0); const seconds (this.time % 60).toString().padStart(2, 0); timerElement.textContent 时间: ${minutes}:${seconds}; } } checkWin() { const children Array.from(this.board.children); for (let i 0; i children.length - 1; i) { const piece children[i]; const currentIndex parseInt(piece.dataset.currentIndex); const correctIndex parseInt(piece.dataset.index); if (currentIndex ! correctIndex) { return false; } } return true; } gameWin() { this.isPlaying false; clearInterval(this.timerInterval); alert(恭喜你完成了拼图\n用时: ${this.formatTime(this.time)}\n步数: ${this.steps}); } formatTime(seconds) { const minutes Math.floor(seconds / 60); const remainingSeconds seconds % 60; return ${minutes.toString().padStart(2, 0)}:${remainingSeconds.toString().padStart(2, 0)}; }4. 完整样式与交互优化添加完整的 CSS 样式让游戏界面更加美观#game-container { max-width: 800px; margin: 0 auto; padding: 20px; font-family: Arial, sans-serif; text-align: center; } #controls { margin: 20px 0; display: flex; justify-content: center; gap: 20px; align-items: center; } button { padding: 10px 20px; font-size: 16px; background-color: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; transition: background-color 0.3s; } button:hover { background-color: #45a049; } #reset-btn { background-color: #f44336; } #reset-btn:hover { background-color: #d32f2f; } #timer, #steps { font-size: 18px; font-weight: bold; color: #333; padding: 10px; background-color: #f5f5f5; border-radius: 5px; min-width: 120px; display: inline-block; } #puzzle-board { width: 400px; height: 400px; border: 2px solid #333; margin: 20px auto; background-color: #f9f9f9; box-shadow: 0 4px 8px rgba(0,0,0,0.1); } .puzzle-piece { transition: transform 0.2s, opacity 0.2s; } .puzzle-piece:hover:not(.empty) { transform: scale(1.02); box-shadow: 0 2px 4px rgba(0,0,0,0.2); z-index: 1; } .puzzle-piece.dragging { opacity: 0.7; } #original-image-container { margin-top: 30px; padding: 20px; background-color: #f5f5f5; border-radius: 10px; } #original-image { max-width: 200px; max-height: 200px; border: 2px solid #ddd; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } h1 { color: #333; margin-bottom: 10px; } h3 { color: #666; margin-bottom: 10px; }5. 游戏初始化与功能扩展最后初始化游戏并添加一些扩展功能// 页面加载完成后初始化游戏 document.addEventListener(DOMContentLoaded, () { const game new PuzzleGame(puzzle-container, puzzle-image.jpg, 3, 3); // 绑定按钮事件 document.getElementById(start-btn).addEventListener(click, () { game.startGame(); }); document.getElementById(reset-btn).addEventListener(click, () { game.resetGame(); }); // 添加难度选择 const difficultySelect document.createElement(select); difficultySelect.id difficulty-select; difficultySelect.innerHTML option value3简单 (3x3)/option option value4中等 (4x4)/option option value5困难 (5x5)/option ; difficultySelect.addEventListener(change, (e) { const size parseInt(e.target.value); game.resetGame(); // 这里可以重新初始化游戏但需要重新加载图片 // 实际项目中可能需要更复杂的重新初始化逻辑 }); document.getElementById(controls).appendChild(difficultySelect); // 添加提示功能 const hintBtn document.createElement(button); hintBtn.id hint-btn; hintBtn.textContent 提示; hintBtn.addEventListener(click, () { // 实现提示逻辑高亮可以移动的碎片 const emptyIndex game.emptyIndex; const children Array.from(game.board.children); children.forEach((piece, index) { if (game.areAdjacent(index, emptyIndex) !piece.classList.contains(empty)) { piece.style.boxShadow 0 0 10px yellow; setTimeout(() { piece.style.boxShadow ; }, 1000); } }); }); document.getElementById(controls).appendChild(hintBtn); }); // 添加触摸屏支持 PuzzleGame.prototype.setupTouchEvents function() { this.board.addEventListener(touchstart, (e) { const touch e.touches[0]; const piece document.elementFromPoint(touch.clientX, touch.clientY); if (piece piece.classList.contains(puzzle-piece) !piece.classList.contains(empty)) { this.touchDraggedPiece

相关新闻

HarmonyOS应用开发实战:猫猫大作战-merge-chain 循环合并链

HarmonyOS应用开发实战:猫猫大作战-merge-chain 循环合并链

前言 在「猫猫大作战」中,当三只同级猫咪合并为一只高级猫后,新猫可能与周围的同级猫再次触发合并——这就是循环合并链。递归合并是本游戏的核心爽感来源之一,一次投放可能触发 3-5 次连锁合并。 一、递归合并实现 private tryMergeAt(x:…

2026/7/30 5:32:23 阅读更多 →
KKCE:路由追踪技术实战从网络诊断到安全防御的全场景应用-快快测

KKCE:路由追踪技术实战从网络诊断到安全防御的全场景应用-快快测

在分布式架构日益普及的今天,业务系统的稳定性往往不再取决于单台服务器的性能,而是受制于错综复杂的网络链路。很多开发者都遇到过这样的场景:本地测试一切正常,代码逻辑无懈可击,但一旦部署到生产环境,用…

2026/7/30 5:32:23 阅读更多 →
索尼FE 70-200mm f/2.8 GM II评测:轻量化远摄镜头的全面革新

索尼FE 70-200mm f/2.8 GM II评测:轻量化远摄镜头的全面革新

如果你是一位专业摄影师,最近正在为索尼全画幅微单系统挑选一支"万金油"远摄变焦镜头,那么索尼FE 70-200mm f/2.8 GM II(型号SEL70200G2)这个名字一定反复出现在你的备选清单中。作为索尼G大师镜头家族中最重要的专业级…

2026/7/30 5:31:23 阅读更多 →

最新新闻

小绿鲸助你完成sci

小绿鲸助你完成sci

暑假只剩最后4周,现在开始写SCI还来得及吗?当然来得及。核心就一句:直接拆顶刊、抄路径。站在前人的肩膀上搞科研,永远比自己瞎摸索更快。这套傻瓜式实操路线,我已经按4周给你排好了,照着做就行。 第一周&a…

2026/7/30 5:38:26 阅读更多 →
卡通语音中文翻配技术解析:从音频提取到音视频合成全流程

卡通语音中文翻配技术解析:从音频提取到音视频合成全流程

如果你是一位视频创作者,特别是专注于游戏解说、动画配音或二次元内容制作,那么最近在B站和抖音上流行的"卡通/Toon 1x1x1x1语音中文翻配"现象,很可能已经引起了你的注意。这个看似简单的标题背后,实际上代表了一种新兴…

2026/7/30 5:38:26 阅读更多 →
全景航拍揽全城,以高空视角重塑数字化展示体验

全景航拍揽全城,以高空视角重塑数字化展示体验

全景航拍一览全城,俯瞰城市天际线新视角,这份极具冲击力的高空视觉体验,早已不再只是风光摄影的专属,更是各行各业数字化宣传升级的核心利器。深耕 VR 全景数字化领域多年的酷雷曼,将航拍实景拍摄技术与自研全景系统深…

2026/7/30 5:38:26 阅读更多 →
Windows下Python导入OpenCV报DLL加载失败:系统级依赖排查与修复指南

Windows下Python导入OpenCV报DLL加载失败:系统级依赖排查与修复指南

1. 问题定位:当OpenCV的DLL加载失败时,我们到底在面对什么?如果你在Windows上用Python导入OpenCV(import cv2)时,突然弹出一个“ImportError: DLL load failed while importing cv2: 找不到指定的模块。”&…

2026/7/30 5:38:26 阅读更多 →
华为USG6000V防火墙三种核心管理方式详解:Web、CLI与NCE-Campus

华为USG6000V防火墙三种核心管理方式详解:Web、CLI与NCE-Campus

1. 从零开始:为什么需要关注防火墙的管理方式?如果你刚接手一台华为USG6000V防火墙,或者正准备部署它,第一件让你头疼的事大概率不是策略怎么配,而是“我该怎么连上它”。这听起来像个基础问题,但恰恰是很多…

2026/7/30 5:38:25 阅读更多 →
Matlab中A*算法仿真:从高效实现到系统集成实践

Matlab中A*算法仿真:从高效实现到系统集成实践

1. 从“寻路”到“仿真”:为什么A*算法值得用Matlab深究?在机器人路径规划、游戏AI寻路或者物流配送优化里,你肯定听过A*(A-Star)算法的大名。它被称作启发式搜索的“黄金标准”,在效率和最优性之间取得了绝…

2026/7/30 5:37:25 阅读更多 →

日新闻

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南 【免费下载链接】DriverStoreExplorer Driver Store Explorer 项目地址: https://gitcode.com/gh_mirrors/dr/DriverStoreExplorer 您是否曾因Windows系统盘空间不足而烦恼?是否遇到过设…

2026/7/30 0:00:13 阅读更多 →
如何3步掌握Video Download Helper:网页视频下载的完整实战指南

如何3步掌握Video Download Helper:网页视频下载的完整实战指南

如何3步掌握Video Download Helper:网页视频下载的完整实战指南 【免费下载链接】VideoDownloadHelper Chrome Extension to Help Download Video for Some Video Sites. 项目地址: https://gitcode.com/gh_mirrors/vi/VideoDownloadHelper 你是否曾经在浏览…

2026/7/30 0:00:13 阅读更多 →
“双减”后首个AI备课压力测试报告:覆盖32所中小学的176节AI辅助课,暴露4大隐性增负节点

“双减”后首个AI备课压力测试报告:覆盖32所中小学的176节AI辅助课,暴露4大隐性增负节点

更多请点击: https://intelliparadigm.com 第一章:AI 教师备课辅助 AI 教师备课辅助系统正逐步成为教育数字化转型的核心支撑工具,它并非替代教师,而是通过语义理解、知识图谱与多模态生成能力,将教师从重复性劳动中解…

2026/7/30 0:00:13 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/29 22:18:20 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/29 14:34:28 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/29 15:00:03 阅读更多 →

月新闻