拼图游戏,无疑是经典的益智游戏之一。玩家需要将一张完整的图片分割成多个小块,然后通过拖动这些拼图块,拼接成原本的图案。这个游戏不仅考验玩家的观察力和空间想象力,还能带来无限的乐趣。
虽然拼图游戏的玩法看似简单,但要实现一个流畅的拖动拼接、检查拼图是否完成等功能,背后需要处理不少的逻辑和细节。过去,要实现拼图游戏,通常需要编写复杂的图片切割算法、拼图块的位置调整和碰撞检测等。
不过,Trae IDE 完全解决了这些问题。通过简单的指令,Trae 能自动帮你生成拼图游戏的规则和UI设计,让你轻松实现这一经典益智游戏。接下来,我就来分享如何通过 Trae 快速生成拼图游戏,让你轻松拥有一个既好玩又具挑战性的拼图游戏。
💡 我的需求其实很简单
我的需求非常明确:制作一个拼图游戏,功能要求如下:
- 图片分割:将一张图片分割成多个小块,打乱它们的位置。
- 拖动拼接:玩家可以通过拖动拼图块,将它们重新拼接成完整的图案。
- 拼图完成判断:当所有拼图块正确排列时,游戏自动判定拼图完成。
- 流畅的UI:游戏界面简洁,操作直观,拼图块拖动和拼接流畅自然。
虽然功能简单,但涉及到图片分割、拼图块的拖动逻辑和拼接完成判断,手动实现这些功能依然需要不少时间。
✨ Trae 如何理解需求并生成代码?
我只需要在 Trae 中输入一句话:
"生成一个拼图游戏,将图片分割成多个块,玩家拖动拼接成完整的图案。"

Trae 会自动解析并生成完整的拼图游戏代码,包括:
- 图片分割:Trae 自动将上传的图片分割成多个小块,确保每个拼图块能够准确对应到原始图案。
- 拼图块拖动逻辑:玩家可以通过鼠标拖动拼图块到空白位置,游戏自动处理拼图块的对齐与碰撞检测。
- 拼图完成检测:当所有拼图块正确排列时,系统会检测到拼图完成,并给出提示。
- 简洁的UI设计:拼图游戏界面简洁、直观,玩家可以轻松操作并享受游戏的乐趣。

通过这一简单的指令,Trae 就能够为我自动生成一个完整的拼图游戏,并且提供了非常流畅的用户体验。
🧩 游戏操作直观,拼图体验流畅
Trae 生成的拼图游戏操作非常简便,玩家只需点击并拖动拼图块,便能轻松地将其拖动到正确的位置。每当玩家将拼图块拖放到正确的地方,系统会自动更新拼图块的位置,直到所有拼图块正确组合成完整的图案。
整个游戏过程流畅自然,拼图块的拖动效果也十分平滑。玩家不需要任何复杂的操作,拼图完成的提示也非常及时,给玩家带来了极好的游戏体验。
🛠 游戏拓展,功能轻松加
虽然生成的拼图游戏已经非常完备,但 Trae 还支持轻松添加更多功能:
- 难度选择:增加不同的难度等级,例如通过增加拼图块的数量,来增加拼图的挑战性。
- 定时器:为游戏增加一个计时器,记录玩家完成拼图所用的时间,增强游戏的竞争性。
- 提示功能:当玩家遇到困难时,可以提供提示功能,显示拼图的正确位置。
- 音效和动画:为拼图完成时加入音效和动画,增加游戏的互动性和趣味性。
这些功能都可以通过简单的描述,Trae 就会自动为我生成相应的代码并集成到现有的游戏中。
这就是拼图游戏开发的新体验
通过这次拼图游戏的开发,我深刻体验到了 Trae 带来的便利。以前可能需要手动编写大量的代码,涉及到图片切割、拼图块的拖动以及完成判断等,现在只需要通过简单的指令,就能轻松实现这些功能。
对于独立开发者或小团队来说,Trae 不仅节省了开发时间,还能够提供流畅且富有创意的用户体验,让开发者专注于游戏设计而非复杂的代码编写。
结语
如果你也想制作一个拼图游戏,试试 Trae IDE,输入类似的需求:
"生成一个拼图游戏,将图片分割成多个块,玩家拖动拼接成完整的图案。"
几秒钟内,Trae 就会生成完整的拼图游戏代码,带有流畅的拼图块拖动逻辑和自动拼图完成检测。你可以直接将它嵌入到你的项目中,甚至根据需求继续扩展和优化。
快来体验一下 Trae,让你的拼图游戏开发变得更加轻松、有趣!
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>
<style>
body {
font-family: 'Arial', sans-serif;
background-color: #f5f5f5;
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
h1 {
color: #333;
margin-bottom: 20px;
text-align: center;
}
.game-container {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
}
.puzzle-container {
position: relative;
border: 2px solid #333;
margin-bottom: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
background-color: #fff;
}
.puzzle-piece {
position: absolute;
cursor: move;
box-sizing: border-box;
transition: box-shadow 0.2s;
background-size: cover;
border: 1px solid #fff;
}
.puzzle-piece.dragging {
z-index: 100;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
.puzzle-piece.correct {
border: 1px solid #4CAF50;
}
.controls {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
justify-content: center;
}
button {
padding: 10px 15px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
button:hover {
background-color: #45a049;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.difficulty {
margin-bottom: 20px;
}
.difficulty select {
padding: 8px;
font-size: 16px;
border-radius: 4px;
}
.image-select {
margin-bottom: 20px;
}
.image-select select {
padding: 8px;
font-size: 16px;
border-radius: 4px;
}
.timer {
font-size: 18px;
margin-bottom: 10px;
color: #333;
}
.moves {
font-size: 18px;
margin-bottom: 20px;
color: #333;
}
.preview {
margin-bottom: 20px;
text-align: center;
}
.preview img {
max-width: 200px;
border: 1px solid #ccc;
border-radius: 4px;
}
.preview-toggle {
margin-top: 10px;
}
.message {
font-size: 20px;
font-weight: bold;
color: #4CAF50;
height: 30px;
text-align: center;
margin-bottom: 10px;
}
.upload-container {
margin-bottom: 20px;
text-align: center;
}
.upload-container input {
display: none;
}
.upload-label {
padding: 10px 15px;
background-color: #2196F3;
color: white;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
display: inline-block;
transition: background-color 0.3s;
}
.upload-label:hover {
background-color: #0b7dda;
}
@media (max-width: 600px) {
.puzzle-container {
max-width: 90vw;
max-height: 90vw;
}
.preview img {
max-width: 150px;
}
.controls {
flex-direction: column;
}
}
</style>
</head>
<body>
<h1>拼图游戏</h1>
<div class="timer">时间: <span id="minutes">00</span>:<span id="seconds">00</span></div>
<div class="moves">移动次数: <span id="move-count">0</span></div>
<div class="controls">
<div class="difficulty">
<select id="difficulty">
<option value="3">简单 (3x3)</option>
<option value="4" selected>中等 (4x4)</option>
<option value="5">困难 (5x5)</option>
<option value="6">专家 (6x6)</option>
</select>
</div>
<div class="image-select">
<select id="image-select">
<option value="nature">自然风景</option>
<option value="city">城市景观</option>
<option value="animal">可爱动物</option>
<option value="food">美食</option>
</select>
</div>
<button id="upload-btn" class="upload-label">上传图片</button>
<input type="file" id="image-upload" accept="image/*">
</div>
<div class="preview">
<h3>预览图</h3>
<img id="preview-image" src="" alt="预览图">
<div class="preview-toggle">
<button id="toggle-preview">隐藏预览</button>
</div>
</div>
<div class="message" id="message"></div>
<div class="game-container">
<div class="puzzle-container" id="puzzle-container"></div>
<div class="controls">
<button id="start-btn">开始游戏</button>
<button id="shuffle-btn">重新打乱</button>
<button id="solve-btn">显示解答</button>
</div>
</div>
<script>
// 游戏状态变量
let puzzlePieces = [];
let puzzleSize = 4; // 默认4x4
let pieceWidth, pieceHeight;
let puzzleWidth = 400;
let puzzleHeight = 400;
let draggedPiece = null;
let startX, startY;
let timer = null;
let seconds = 0;
let minutes = 0;
let moveCount = 0;
let gameStarted = false;
let currentImage = null;
let previewVisible = true;
// 预定义的图片
const images = {
nature: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MDAgNDAwIj48cmVjdCB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCIgZmlsbD0iIzg3Y2VlYiIvPjxwYXRoIGQ9Ik0wLDI1MEMyMCwyMzAgNDAsMjIwIDYwLDIzMEM4MCwyNDAgMTAwLDI1MCAxMjAsMjQwQzE0MCwyMzAgMTYwLDIyMCAxODAsMjMwQzIwMCwyNDAgMjIwLDI1MCAyNDAsMjQwQzI2MCwyMzAgMjgwLDIyMCAzMDAsMjMwQzMyMCwyNDAgMzQwLDI1MCAzNjAsMjQwQzM4MCwyMzAgNDAwLDIyMCA0MDAsMjIwVjQwMEgwWiIgZmlsbD0iIzFmNzc0MCIvPjxjaXJjbGUgY3g9IjMwMCIgY3k9IjgwIiByPSI0MCIgZmlsbD0iI2ZmZDcwMCIvPjxwYXRoIGQ9Ik0xMjAsMzAwQzEyMCwyNjAgMTYwLDI0MCAyMDAsMjQwQzI0MCwyNDAgMjgwLDI2MCAyODAsMzAwQzI4MCwzNDAgMjQwLDM2MCAyMDAsMzYwQzE2MCwzNjAgMTIwLDM0MCAxMjAsMzAwWiIgZmlsbD0iIzc3NTUzMyIvPjxwYXRoIGQ9Ik0xODAsMTgwQzE4MCwxNDAgMjIwLDEyMCAyNjAsMTIwQzMwMCwxMjAgMzQwLDE0MCAzNDAsMTgwQzM0MCwyMjAgMzAwLDI0MCAyNjAsMjQwQzIyMCwyNDAgMTgwLDIyMCAxODAsMTgwWiIgZmlsbD0iIzFmNzc0MCIvPjxwYXRoIGQ9Ik00MCwxNjBDNDAsMTIwIDgwLDEwMCAxMjAsMTAwQzE2MCwxMDAgMjAwLDEyMCAyMDAsMTYwQzIwMCwyMDAgMTYwLDIyMCAxMjAsMjIwQzgwLDIyMCA0MCwyMDAgNDAsMTYwWiIgZmlsbD0iIzFmNzc0MCIvPjwvc3ZnPg==',
city: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MDAgNDAwIj48cmVjdCB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCIgZmlsbD0iIzg3Y2VlYiIvPjxyZWN0IHg9IjAiIHk9IjI1MCIgd2lkdGg9IjQwMCIgaGVpZ2h0PSIxNTAiIGZpbGw9IiM4ODg4ODgiLz48cmVjdCB4PSIxMCIgeT0iMTUwIiB3aWR0aD0iODAiIGhlaWdodD0iMjUwIiBmaWxsPSIjNTU1NTU1Ii8+PHJlY3QgeD0iMTEwIiB5PSIxMDAiIHdpZHRoPSI4MCIgaGVpZ2h0PSIzMDAiIGZpbGw9IiM3Nzc3NzciLz48cmVjdCB4PSIyMTAiIHk9IjE4MCIgd2lkdGg9IjgwIiBoZWlnaHQ9IjIyMCIgZmlsbD0iIzY2NjY2NiIvPjxyZWN0IHg9IjMxMCIgeT0iNzAiIHdpZHRoPSI4MCIgaGVpZ2h0PSIzMzAiIGZpbGw9IiM0NDQ0NDQiLz48cmVjdCB4PSIyMCIgeT0iMTcwIiB3aWR0aD0iMTUiIGhlaWdodD0iMTUiIGZpbGw9IiNmZmZmMDAiLz48cmVjdCB4PSI2MCIgeT0iMTcwIiB3aWR0aD0iMTUiIGhlaWdodD0iMTUiIGZpbGw9IiNmZmZmMDAiLz48cmVjdCB4PSIyMCIgeT0iMjEwIiB3aWR0aD0iMTUiIGhlaWdodD0iMTUiIGZpbGw9IiNmZmZmMDAiLz48cmVjdCB4PSI2MCIgeT0iMjEwIiB3aWR0aD0iMTUiIGhlaWdodD0iMTUiIGZpbGw9IiNmZmZmMDAiLz48cmVjdCB4PSIxMjAiIHk9IjEyMCIgd2lkdGg9IjE1IiBoZWlnaHQ9IjE1IiBmaWxsPSIjZmZmZjAwIi8+PHJlY3QgeD0iMTYwIiB5PSIxMjAiIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgZmlsbD0iI2ZmZmYwMCIvPjxyZWN0IHg9IjEyMCIgeT0iMTYwIiB3aWR0aD0iMTUiIGhlaWdodD0iMTUiIGZpbGw9IiNmZmZmMDAiLz48cmVjdCB4PSIxNjAiIHk9IjE2MCIgd2lkdGg9IjE1IiBoZWlnaHQ9IjE1IiBmaWxsPSIjZmZmZjAwIi8+PHJlY3QgeD0iMjIwIiB5PSIyMDAiIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgZmlsbD0iI2ZmZmYwMCIvPjxyZWN0IHg9IjI2MCIgeT0iMjAwIiB3aWR0aD0iMTUiIGhlaWdodD0iMTUiIGZpbGw9IiNmZmZmMDAiLz48cmVjdCB4PSIyMjAiIHk9IjI0MCIgd2lkdGg9IjE1IiBoZWlnaHQ9IjE1IiBmaWxsPSIjZmZmZjAwIi8+PHJlY3QgeD0iMjYwIiB5PSIyNDAiIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgZmlsbD0iI2ZmZmYwMCIvPjxyZWN0IHg9IjMyMCIgeT0iOTAiIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgZmlsbD0iI2ZmZmYwMCIvPjxyZWN0IHg9IjM2MCIgeT0iOTAiIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgZmlsbD0iI2ZmZmYwMCIvPjxyZWN0IHg9IjMyMCIgeT0iMTMwIiB3aWR0aD0iMTUiIGhlaWdodD0iMTUiIGZpbGw9IiNmZmZmMDAiLz48cmVjdCB4PSIzNjAiIHk9IjEzMCIgd2lkdGg9IjE1IiBoZWlnaHQ9IjE1IiBmaWxsPSIjZmZmZjAwIi8+PGNpcmNsZSBjeD0iMzAwIiBjeT0iNTAiIHI9IjMwIiBmaWxsPSIjZmZkNzAwIi8+PC9zdmc+',
animal: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MDAgNDAwIj48cmVjdCB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCIgZmlsbD0iI2FhZTZmZiIvPjxyZWN0IHg9IjAiIHk9IjMwMCIgd2lkdGg9IjQwMCIgaGVpZ2h0PSIxMDAiIGZpbGw9IiM3N2RkNzciLz48Y2lyY2xlIGN4PSIyMDAiIGN5PSIyNTAiIHI9IjEwMCIgZmlsbD0iI2JiODg1NSIvPjxjaXJjbGUgY3g9IjE1MCIgY3k9IjIwMCIgcj0iMjUiIGZpbGw9IiNmZmZmZmYiLz48Y2lyY2xlIGN4PSIyNTAiIGN5PSIyMDAiIHI9IjI1IiBmaWxsPSIjZmZmZmZmIi8+PGNpcmNsZSBjeD0iMTUwIiBjeT0iMjAwIiByPSIxMCIgZmlsbD0iIzAwMDAwMCIvPjxjaXJjbGUgY3g9IjI1MCIgY3k9IjIwMCIgcj0iMTAiIGZpbGw9IiMwMDAwMDAiLz48ZWxsaXBzZSBjeD0iMjAwIiBjeT0iMjUwIiByeD0iMzAiIHJ5PSIxNSIgZmlsbD0iI2ZmYWE5OSIvPjxwYXRoIGQ9Ik0xNTAsMTIwIEMxNTAsMTAwIDEyMCw4MCAxMDAsODAgQzgwLDgwIDUwLDEwMCA1MCwxMjAgQzUwLDE0MCA4MCwxNjAgMTAwLDE2MCBDMTIwLDE2MCAxNTAsMTQwIDE1MCwxMjAiIGZpbGw9IiNiYjg4NTUiLz48cGF0aCBkPSJNMjUwLDEyMCBDMjUwLDEwMCAyODAsODAgMzAwLDgwIEMzMjAsODAgMzUwLDEwMCAzNTAsMTIwIEMzNTAsMTQwIDMyMCwxNjAgMzAwLDE2MCBDMjgwLDE2MCAyNTAsMTQwIDI1MCwxMjAiIGZpbGw9IiNiYjg4NTUiLz48L3N2Zz4=',
food: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MDAgNDAwIj48cmVjdCB3aWR0aD0iNDAwIiBoZWlnaHQ9IjQwMCIgZmlsbD0iI2ZmZjVlNiIvPjxjaXJjbGUgY3g9IjEyMCIgY3k9IjE1MCIgcj0iODAiIGZpbGw9IiNmZjZiNmIiLz48Y2lyY2xlIGN4PSIyODAiIGN5PSIxNTAiIHI9IjgwIiBmaWxsPSIjZmZjYzY2Ii8+PGNpcmNsZSBjeD0iMTIwIiBjeT0iMjgwIiByPSI4MCIgZmlsbD0iIzc3ZGQ3NyIvPjxjaXJjbGUgY3g9IjI4MCIgY3k9IjI4MCIgcj0iODAiIGZpbGw9IiM2NmNjZmYiLz48Y2lyY2xlIGN4PSIxMjAiIGN5PSIxNTAiIHI9IjYwIiBmaWxsPSIjZmYzMzMzIi8+PGNpcmNsZSBjeD0iMjgwIiBjeT0iMTUwIiByPSI2MCIgZmlsbD0iI2ZmYmIzMyIvPjxjaXJjbGUgY3g9IjEyMCIgY3k9IjI4MCIgcj0iNjAiIGZpbGw9IiM0NGFhNDQiLz48Y2lyY2xlIGN4PSIyODAiIGN5PSIyODAiIHI9IjYwIiBmaWxsPSIjMzM5OWZmIi8+PC9zdmc+'
};
// DOM 元素
const puzzleContainer = document.getElementById('puzzle-container');
const difficultySelect = document.getElementById('difficulty');
const imageSelect = document.getElementById('image-select');
const startBtn = document.getElementById('start-btn');
const shuffleBtn = document.getElementById('shuffle-btn');
const solveBtn = document.getElementById('solve-btn');
const messageEl = document.getElementById('message');
const minutesEl = document.getElementById('minutes');
const secondsEl = document.getElementById('seconds');
const moveCountEl = document.getElementById('move-count');
const previewImage = document.getElementById('preview-image');
const togglePreviewBtn = document.getElementById('toggle-preview');
const imageUpload = document.getElementById('image-upload');
const uploadBtn = document.getElementById('upload-btn');
// 初始化游戏
function initGame() {
// 设置拼图容器大小
puzzleContainer.style.width = puzzleWidth + 'px';
puzzleContainer.style.height = puzzleHeight + 'px';
// 清空拼图容器
puzzleContainer.innerHTML = '';
puzzlePieces = [];
// 根据难度设置拼图大小
puzzleSize = parseInt(difficultySelect.value);
pieceWidth = puzzleWidth / puzzleSize;
pieceHeight = puzzleHeight / puzzleSize;
// 加载选中的图片
loadImage();
}
// 加载图片
function loadImage() {
const selectedImage = imageSelect.value;
currentImage = images[selectedImage];
previewImage.src = currentImage;
// 创建拼图块
createPuzzlePieces();
}
// 创建拼图块
function createPuzzlePieces() {
puzzleContainer.innerHTML = '';
puzzlePieces = [];
for (let row = 0; row < puzzleSize; row++) {
for (let col = 0; col < puzzleSize; col++) {
const piece = document.createElement('div');
piece.className = 'puzzle-piece';
piece.style.width = pieceWidth + 'px';
piece.style.height = pieceHeight + 'px';
// 设置拼图块的背景图片位置
piece.style.backgroundImage = `url(${currentImage})`;
piece.style.backgroundSize = `${puzzleWidth}px ${puzzleHeight}px`;
piece.style.backgroundPosition = `-${col * pieceWidth}px -${row * pieceHeight}px`;
// 设置拼图块的初始位置(有序)
piece.style.left = (col * pieceWidth) + 'px';
piece.style.top = (row * pieceHeight) + 'px';
// 存储拼图块的正确位置
piece.dataset.row = row;
piece.dataset.col = col;
piece.dataset.correctLeft = col * pieceWidth;
piece.dataset.correctTop = row * pieceHeight;
// 添加拖拽事件
piece.addEventListener('mousedown', startDrag);
piece.addEventListener('touchstart', startDrag, { passive: false });
puzzleContainer.appendChild(piece);
puzzlePieces.push(piece);
}
}
}
// 打乱拼图
function shufflePuzzle() {
if (!gameStarted) return;
// 重置移动次数
moveCount = 0;
moveCountEl.textContent = moveCount;
// 随机排列拼图块
const positions = [];
for (let row = 0; row < puzzleSize; row++) {
for (let col = 0; col < puzzleSize; col++) {
positions.push({ left: col * pieceWidth, top: row * pieceHeight });
}
}
// 打乱位置数组
shuffleArray(positions);
// 应用打乱后的位置
for (let i = 0; i < puzzlePieces.length; i++) {
const piece = puzzlePieces[i];
const position = positions[i];
piece.style.left = position.left + 'px';
piece.style.top = position.top + 'px';
// 移除正确位置标记
piece.classList.remove('correct');
}
// 检查是否已经完成(防止随机排列恰好是正确的)
checkCompletion();
}
// 打乱数组(Fisher-Yates 洗牌算法)
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
// 开始拖拽
function startDrag(e) {
if (!gameStarted) return;
e.preventDefault();
// 获取触摸或鼠标事件的坐标
const clientX = e.clientX || e.touches[0].clientX;
const clientY = e.clientY || e.touches[0].clientY;
draggedPiece = this;
draggedPiece.classList.add('dragging');
// 计算鼠标在拼图块内的位置
const rect = draggedPiece.getBoundingClientRect();
startX = clientX - rect.left;
startY = clientY - rect.top;
// 添加移动和结束拖拽事件
document.addEventListener('mousemove', dragMove);
document.addEventListener('mouseup', dragEnd);
document.addEventListener('touchmove', dragMove, { passive: false });
document.addEventListener('touchend', dragEnd);
}
// 拖拽移动
function dragMove(e) {
if (!draggedPiece) return;
e.preventDefault();
// 获取触摸或鼠标事件的坐标
const clientX = e.clientX || e.touches[0].clientX;
const clientY = e.clientY || e.touches[0].clientY;
// 计算新位置
const puzzleRect = puzzleContainer.getBoundingClientRect();
let left = clientX - puzzleRect.left - startX;
let top = clientY - puzzleRect.top - startY;
// 限制在拼图容器内
left = Math.max(0, Math.min(left, puzzleWidth - pieceWidth));
top = Math.max(0, Math.min(top, puzzleHeight - pieceHeight));
// 更新位置
draggedPiece.style.left = left + 'px';
draggedPiece.style.top = top + 'px';
}
// 结束拖拽
function dragEnd() {
if (!draggedPiece) return;
// 移除拖拽中的样式
draggedPiece.classList.remove('dragging');
// 吸附到网格
snapToGrid(draggedPiece);
// 增加移动次数
moveCount++;
moveCountEl.textContent = moveCount;
// 检查是否完成拼图
checkCompletion();
// 清除拖拽状态
draggedPiece = null;
// 移除事件监听器
document.removeEventListener('mousemove', dragMove);
document.removeEventListener('mouseup', dragEnd);
document.removeEventListener('touchmove', dragMove);
document.removeEventListener('touchend', dragEnd);
}
// 吸附到网格
function snapToGrid(piece) {
const left = parseInt(piece.style.left);
const top = parseInt(piece.style.top);
// 计算最近的网格位置
const col = Math.round(left / pieceWidth);
const row = Math.round(top / pieceHeight);
// 计算吸附位置
const snapLeft = col * pieceWidth;
const snapTop = row * pieceHeight;
// 设置吸附位置
piece.style.left = snapLeft + 'px';
piece.style.top = snapTop + 'px';
// 检查是否在正确位置
const correctLeft = parseInt(piece.dataset.correctLeft);
const correctTop = parseInt(piece.dataset.correctTop);
if (snapLeft === correctLeft && snapTop === correctTop) {
piece.classList.add('correct');
} else {
piece.classList.remove('correct');
}
}
// 检查是否完成拼图
function checkCompletion() {
let completed = true;
for (const piece of puzzlePieces) {
const currentLeft = parseInt(piece.style.left);
const currentTop = parseInt(piece.style.top);
const correctLeft = parseInt(piece.dataset.correctLeft);
const correctTop = parseInt(piece.dataset.correctTop);
if (currentLeft !== correctLeft || currentTop !== correctTop) {
completed = false;
break;
}
}
if (completed && gameStarted) {
// 停止计时器
stopTimer();
// 显示完成消息
messageEl.textContent = `恭喜!你完成了拼图!用时 ${minutes}:${seconds.toString().padStart(2, '0')},移动 ${moveCount} 次`;
// 禁用重新打乱按钮
shuffleBtn.disabled = true;
}
}
// 显示解答
function showSolution() {
if (!gameStarted) return;
// 将每个拼图块放回正确位置
for (const piece of puzzlePieces) {
const correctLeft = parseInt(piece.dataset.correctLeft);
const correctTop = parseInt(piece.dataset.correctTop);
piece.style.left = correctLeft + 'px';
piece.style.top = correctTop + 'px';
piece.classList.add('correct');
}
// 停止计时器
stopTimer();
// 显示消息
messageEl.textContent = '这是拼图的解答。';
// 禁用按钮
shuffleBtn.disabled = true;
solveBtn.disabled = true;
}
// 计时器函数
function startTimer() {
stopTimer();
seconds = 0;
minutes = 0;
updateTimer();
timer = setInterval(() => {
seconds++;
if (seconds === 60) {
seconds = 0;
minutes++;
}
updateTimer();
}, 1000);
}
function stopTimer() {
clearInterval(timer);
}
function updateTimer() {
minutesEl.textContent = minutes.toString().padStart(2, '0');
secondsEl.textContent = seconds.toString().padStart(2, '0');
}
// 开始新游戏
function startGame() {
// 初始化游戏
initGame();
// 开始计时
startTimer();
// 重置移动次数
moveCount = 0;
moveCountEl.textContent = moveCount;
// 清除消息
messageEl.textContent = '';
// 启用按钮
shuffleBtn.disabled = false;
solveBtn.disabled = false;
// 设置游戏状态
gameStarted = true;
// 打乱拼图
shufflePuzzle();
}
// 处理图片上传
function handleImageUpload(e) {
const file = e.target.files[0];
if (!file) return;
// 检查文件类型
if (!file.type.match('image.*')) {
alert('请上传图片文件!');
return;
}
const reader = new FileReader();
reader.onload = function(event) {
// 创建图像对象以获取尺寸
const img = new Image();
img.onload = function() {
// 更新当前图像
currentImage = event.target.result;
previewImage.src = currentImage;
// 如果游戏已经开始,重新开始游戏
if (gameStarted) {
startGame();
} else {
initGame();
}
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
}
// 切换预览图显示/隐藏
function togglePreview() {
previewVisible = !previewVisible;
previewImage.style.display = previewVisible ? 'block' : 'none';
togglePreviewBtn.textContent = previewVisible ? '隐藏预览' : '显示预览';
}
// 事件监听器
startBtn.addEventListener('click', startGame);
shuffleBtn.addEventListener('click', shufflePuzzle);
solveBtn.addEventListener('click', showSolution);
difficultySelect.addEventListener('change', initGame);
imageSelect.addEventListener('change', loadImage);
togglePreviewBtn.addEventListener('click', togglePreview);
imageUpload.addEventListener('change', handleImageUpload);
uploadBtn.addEventListener('click', () => imageUpload.click());
// 窗口大小调整处理
window.addEventListener('resize', function() {
// 在小屏幕上调整拼图大小
if (window.innerWidth < 600) {
puzzleWidth = Math.min(window.innerWidth - 40, 400);
puzzleHeight = puzzleWidth; // 保持正方形
// 更新拼图容器大小
puzzleContainer.style.width = puzzleWidth + 'px';
puzzleContainer.style.height = puzzleHeight + 'px';
// 更新拼图块大小和位置
pieceWidth = puzzleWidth / puzzleSize;
pieceHeight = puzzleHeight / puzzleSize;
for (let i = 0; i < puzzlePieces.length; i++) {
const piece = puzzlePieces[i];
const row = parseInt(piece.dataset.row);
const col = parseInt(piece.dataset.col);
// 更新拼图块大小
piece.style.width = pieceWidth + 'px';
piece.style.height = pieceHeight + 'px';
// 更新背景大小和位置
piece.style.backgroundSize = `${puzzleWidth}px ${puzzleHeight}px`;
piece.style.backgroundPosition = `-${col * pieceWidth}px -${row * pieceHeight}px`;
// 更新正确位置数据
piece.dataset.correctLeft = col * pieceWidth;
piece.dataset.correctTop = row * pieceHeight;
// 如果游戏未开始,更新当前位置
if (!gameStarted) {
piece.style.left = col * pieceWidth + 'px';
piece.style.top = row * pieceHeight + 'px';
}
}
}
});
// 初始化游戏
window.onload = function() {
initGame();
shuffleBtn.disabled = true;
solveBtn.disabled = true;
};
</script>
</body>
</html>