打开/关闭搜索
搜索
打开/关闭菜单
933
1525
112
1.4万
植物大战僵尸杂交版Wiki
导航
首页
最近更改
随机页面
MediaWiki帮助
特殊页面
上传文件
打开/关闭外观设置菜单
notifications
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。
user-interface-preferences
个人工具
创建账号
登录
查看“︁微件:推箱子”︁的源代码
来自植物大战僵尸杂交版Wiki
查看
阅读
查看源代码
查看历史
associated-pages
微件
讨论
更多操作
←
微件:推箱子
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
用户
您没有权限编辑
微件
命名空间内的页面。
您可以查看和复制此页面的源代码。
<includeonly> <div class="tuixiangzi-widget"> <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <title>推箱子 📦</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #0f0e17; font-family: 'Arial', sans-serif; } .container { background: #1a1a2e; padding: 25px 30px 30px; border-radius: 40px; box-shadow: 0 20px 40px rgba(0,0,0,0.7); text-align: center; } canvas { display: block; margin: 0 auto; background: #0a0a1a; border-radius: 20px; border: 3px solid #2d2d44; max-width: 100%; height: auto; touch-action: none; } .info { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; color: #e0e0e0; font-weight: bold; font-size: 18px; flex-wrap: wrap; gap: 12px; } .stat-box { background: #0a0a1a; padding: 6px 20px; border-radius: 30px; border: 2px solid #4a4a6a; } .stat-box.level { border-color: #fbbf24; color: #fbbf24; } .stat-box.moves { border-color: #60a5fa; color: #60a5fa; } button { background: #7c3aed; border: none; padding: 8px 28px; border-radius: 50px; font-size: 16px; font-weight: bold; color: white; cursor: pointer; transition: 0.1s; box-shadow: 0 4px 0 #5b21b6; } button:active { transform: translateY(4px); box-shadow: none; } .hint { color: #5a4a7a; font-size: 13px; margin-top: 6px; } .controls-hint { display: flex; gap: 10px; justify-content: center; color: #6b6b8a; font-size: 13px; margin-top: 4px; } .controls-hint span { background: #1a1a2e; padding: 2px 12px; border-radius: 10px; } </style> </head> <body> <div class="container"> <canvas id="gameCanvas" width="400" height="400"></canvas> <div class="info"> <div class="stat-box level">📦 关卡 <span id="levelDisplay">1</span></div> <div class="stat-box moves">🚶 步数 <span id="movesDisplay">0</span></div> <button id="restartBtn">🔄 重新开始</button> </div> <div class="hint">🎯 将所有箱子推到 <span style="color:#fbbf24;">★</span> 目标位置</div> <div class="controls-hint"> <span>⬆</span><span>⬇</span><span>⬅</span><span>➡</span> <span style="color:#4a4a6a;">或滑动屏幕</span> </div> </div> <script> (function() { const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const levelSpan = document.getElementById('levelDisplay'); const movesSpan = document.getElementById('movesDisplay'); const CELL = 40; const COLS = 10; const ROWS = 10; const W = COLS * CELL; const H = ROWS * CELL; // 边界限制:箱子不能出现在边缘 const EDGE_MARGIN = 2; // 游戏状态 let level = 1; let moves = 0; let gameOver = false; let player = { x: 1, y: 1 }; let boxes = []; let targets = []; let walls = []; let animationId = null; // ---- 检查位置是否在内部区域 ---- function isInsideArea(x, y) { return x >= EDGE_MARGIN && x < COLS - EDGE_MARGIN && y >= EDGE_MARGIN && y < ROWS - EDGE_MARGIN; } // ---- 检查位置是否可通行 ---- function isWalkable(x, y) { if (x < 0 || x >= COLS || y < 0 || y >= ROWS) return false; if (walls.some(w => w.x === x && w.y === y)) return false; return true; } // ---- 检查位置是否可站立(用于箱子摆放) ---- function isStandable(x, y, boxList, targetList) { if (!isWalkable(x, y)) return false; if (!isInsideArea(x, y)) return false; if (boxList.some(b => b.x === x && b.y === y)) return false; if (targetList.some(t => t.x === x && t.y === y)) return false; if (x === 1 && y === 1) return false; return true; } // ---- 深度检测:箱子是否在死角(全面版) ---- function isBoxInDeadEnd(bx, by, boxList, targetList) { // 如果箱子已经在目标上,不算死角 if (targetList.some(t => t.x === bx && t.y === by)) return false; const dirs = [[0, -1], [0, 1], [-1, 0], [1, 0]]; let movableDirs = []; for (const [dx, dy] of dirs) { const nx = bx + dx; const ny = by + dy; // 检查推动方向是否可通行 if (!isWalkable(nx, ny)) continue; // 检查推动后箱子的新位置是否可通行 const nx2 = bx + dx * 2; const ny2 = by + dy * 2; if (!isWalkable(nx2, ny2)) continue; if (boxList.some(b => b.x === nx2 && b.y === ny2)) continue; // 这个方向可以推动 movableDirs.push([dx, dy]); } // 如果没有任何方向可以推动,箱子在死角 if (movableDirs.length === 0) return true; // 如果只有一个方向可以推动,检查这个方向是否通向死路 if (movableDirs.length === 1) { const [dx, dy] = movableDirs[0]; // 检查推动后,箱子是否会进入新的死角 const newX = bx + dx; const newY = by + dy; // 模拟推动后的状态 const tempBoxes = boxList.map(b => ({ ...b })); const idx = tempBoxes.findIndex(b => b.x === bx && b.y === by); if (idx !== -1) { tempBoxes[idx].x = newX; tempBoxes[idx].y = newY; } // 检查推动后箱子是否在死角(递归检测,避免无限循环) if (isBoxInDeadEnd(newX, newY, tempBoxes, targetList)) { return true; } // 检查推动后玩家能否到达箱子后面(推动位置) // 玩家需要站在 (bx - dx, by - dy) 位置来推箱子 const playerPos = { x: bx - dx, y: by - dy }; if (!isWalkable(playerPos.x, playerPos.y)) return true; // 检查玩家是否能到达这个位置(简化检查:位置必须可通行且不被箱子占据) if (tempBoxes.some(b => b.x === playerPos.x && b.y === playerPos.y)) return true; } // 检查箱子是否在角落且两个方向都是墙 let wallCount = 0; for (const [dx, dy] of dirs) { const nx = bx + dx; const ny = by + dy; if (!isWalkable(nx, ny)) wallCount++; } if (wallCount >= 3) return true; // 特殊检查:箱子在两个墙之间(水平或垂直夹住) // 水平方向被夹住 const leftBlocked = !isWalkable(bx - 1, by); const rightBlocked = !isWalkable(bx + 1, by); if (leftBlocked && rightBlocked) return true; // 垂直方向被夹住 const upBlocked = !isWalkable(bx, by - 1); const downBlocked = !isWalkable(bx, by + 1); if (upBlocked && downBlocked) return true; // 检查箱子是否在"凹"形死角中 // 如果三个方向被挡住,且第四个方向的延伸也是死路 const openDirs = []; for (const [dx, dy] of dirs) { const nx = bx + dx; const ny = by + dy; if (isWalkable(nx, ny) && !boxList.some(b => b.x === nx && b.y === ny)) { openDirs.push([dx, dy]); } } if (openDirs.length === 1) { const [dx, dy] = openDirs[0]; // 检查这个方向的尽头是否也是死路 let steps = 0; let cx = bx + dx; let cy = by + dy; while (steps < 5) { steps++; // 检查这个位置是否可通行 if (!isWalkable(cx, cy)) break; // 检查周围的墙数 let aroundWall = 0; for (const [ddx, ddy] of dirs) { const nx2 = cx + ddx; const ny2 = cy + ddy; if (!isWalkable(nx2, ny2)) aroundWall++; } if (aroundWall >= 3) return true; // 继续向前 cx += dx; cy += dy; } } return false; } // ---- 检查箱子是否可到达目标(BFS模拟) ---- function canBoxReachTarget(boxList, targetList) { for (const box of boxList) { if (targetList.some(t => t.x === box.x && t.y === box.y)) continue; // 使用更严格的死角检测 if (isBoxInDeadEnd(box.x, box.y, boxList, targetList)) { return false; } // 检查是否有目标在合理范围内 let hasTarget = false; for (const target of targetList) { const dist = Math.abs(box.x - target.x) + Math.abs(box.y - target.y); if (dist < 8) { hasTarget = true; break; } } if (!hasTarget) return false; } return true; } // ---- 检查目标位置是否合理 ---- function isTargetValid(tx, ty, targetList) { if (!isInsideArea(tx, ty)) return false; if (!isWalkable(tx, ty)) return false; if (targetList.some(t => t.x === tx && t.y === ty)) return false; // 目标不能在角落(至少有两个相邻的可通行方向) let walkableNeighbors = 0; const dirs = [[0, 1], [0, -1], [1, 0], [-1, 0]]; for (const [dx, dy] of dirs) { if (isWalkable(tx + dx, ty + dy)) walkableNeighbors++; } return walkableNeighbors >= 2; } // ---- 生成关卡 ---- function generateLevel(lvl) { let attempts = 0; let valid = false; while (!valid && attempts < 200) { attempts++; walls = []; boxes = []; targets = []; // 基础边界墙 for (let i = 0; i < COLS; i++) { walls.push({ x: i, y: 0 }); walls.push({ x: i, y: ROWS - 1 }); } for (let i = 0; i < ROWS; i++) { walls.push({ x: 0, y: i }); walls.push({ x: COLS - 1, y: i }); } // 生成内部障碍物(内部区域) const numWalls = 3 + (lvl % 4); for (let i = 0; i < numWalls; i++) { let wx, wy; let attempts2 = 0; do { wx = EDGE_MARGIN + 1 + Math.floor(Math.random() * (COLS - EDGE_MARGIN * 2 - 2)); wy = EDGE_MARGIN + 1 + Math.floor(Math.random() * (ROWS - EDGE_MARGIN * 2 - 2)); attempts2++; } while (attempts2 < 30 && ( (wx === 1 && wy === 1) || walls.some(w => w.x === wx && w.y === wy) )); if (attempts2 < 30) { walls.push({ x: wx, y: wy }); } } // 生成目标(内部区域,且不在死角) const numBoxes = Math.min(3 + Math.floor(lvl / 2), 4); for (let i = 0; i < numBoxes; i++) { let tx, ty; let attempts2 = 0; do { tx = EDGE_MARGIN + Math.floor(Math.random() * (COLS - EDGE_MARGIN * 2)); ty = EDGE_MARGIN + Math.floor(Math.random() * (ROWS - EDGE_MARGIN * 2)); attempts2++; } while (attempts2 < 100 && !isTargetValid(tx, ty, targets)); if (attempts2 < 100) { targets.push({ x: tx, y: ty }); } } if (targets.length < numBoxes) continue; // 生成箱子(在目标周围,且不在死角) for (let i = 0; i < targets.length; i++) { let bx, by; let found = false; const target = targets[i]; // 在目标周围4个方向寻找 const dirs = [[0, 1], [0, -1], [1, 0], [-1, 0]]; for (let j = dirs.length - 1; j > 0; j--) { const k = Math.floor(Math.random() * (j + 1)); [dirs[j], dirs[k]] = [dirs[k], dirs[j]]; } // 先尝试近距离 for (const [dx, dy] of dirs) { const nx = target.x + dx; const ny = target.y + dy; if (isStandable(nx, ny, boxes, targets)) { // 检查这个位置是否是死角 const testBoxes = boxes.map(b => ({ ...b })); testBoxes.push({ x: nx, y: ny }); if (!isBoxInDeadEnd(nx, ny, testBoxes, targets)) { bx = nx; by = ny; found = true; break; } } } // 如果没找到,扩大搜索范围 if (!found) { const searchRadius = 4; for (let dx = -searchRadius; dx <= searchRadius; dx++) { for (let dy = -searchRadius; dy <= searchRadius; dy++) { if (dx === 0 && dy === 0) continue; const nx = target.x + dx; const ny = target.y + dy; if (isStandable(nx, ny, boxes, targets)) { const testBoxes = boxes.map(b => ({ ...b })); testBoxes.push({ x: nx, y: ny }); if (!isBoxInDeadEnd(nx, ny, testBoxes, targets)) { bx = nx; by = ny; found = true; break; } } } if (found) break; } } if (found) { boxes.push({ x: bx, y: by }); } } if (boxes.length < targets.length) continue; // 最终验证:所有箱子都不在死角 let allValid = true; for (const box of boxes) { if (!isInsideArea(box.x, box.y)) { allValid = false; break; } if (isBoxInDeadEnd(box.x, box.y, boxes, targets)) { allValid = false; break; } } if (!allValid) continue; // 验证箱子可到达目标 if (!canBoxReachTarget(boxes, targets)) continue; valid = true; } // 保底方案:使用预设关卡 if (!valid) { walls = []; boxes = []; targets = []; for (let i = 0; i < COLS; i++) { walls.push({ x: i, y: 0 }); walls.push({ x: i, y: ROWS - 1 }); } for (let i = 0; i < ROWS; i++) { walls.push({ x: 0, y: i }); walls.push({ x: COLS - 1, y: i }); } // 预设内部墙 const presetWalls = [[3, 2], [6, 2], [2, 5], [7, 5]]; for (const [wx, wy] of presetWalls) { if (isInsideArea(wx, wy)) { walls.push({ x: wx, y: wy }); } } // 预设箱子和目标(都在内部区域,且不在死角) const presetBoxes = [[3, 3], [5, 4], [4, 6]]; const presetTargets = [[6, 3], [3, 5], [7, 6]]; for (const [bx, by] of presetBoxes) { if (isInsideArea(bx, by) && isWalkable(bx, by) && !boxes.some(b => b.x === bx && b.y === by) && !targets.some(t => t.x === bx && t.y === by)) { boxes.push({ x: bx, y: by }); } } for (const [tx, ty] of presetTargets) { if (isInsideArea(tx, ty) && isWalkable(tx, ty) && !targets.some(t => t.x === tx && t.y === ty)) { targets.push({ x: tx, y: ty }); } } } // 确保玩家在(1,1)且该位置未被占用 player.x = 1; player.y = 1; for (const box of boxes) { if (box.x === 1 && box.y === 1) { for (let dx = -1; dx <= 1; dx++) { for (let dy = -1; dy <= 1; dy++) { const nx = 1 + dx; const ny = 1 + dy; if (isInsideArea(nx, ny) && isWalkable(nx, ny) && !boxes.some(b => b.x === nx && b.y === ny) && !targets.some(t => t.x === nx && t.y === ny)) { box.x = nx; box.y = ny; break; } } } } } moves = 0; gameOver = false; updateUI(); draw(); } function updateUI() { levelSpan.textContent = level; movesSpan.textContent = moves; } // ---- 检查是否胜利 ---- function checkWin() { for (const box of boxes) { const onTarget = targets.some(t => t.x === box.x && t.y === box.y); if (!onTarget) return false; } return true; } // ---- 移动 ---- function movePlayer(dx, dy) { if (gameOver) return; const newX = player.x + dx; const newY = player.y + dy; if (walls.some(w => w.x === newX && w.y === newY)) return; const boxIndex = boxes.findIndex(b => b.x === newX && b.y === newY); if (boxIndex !== -1) { const boxNewX = newX + dx; const boxNewY = newY + dy; if (walls.some(w => w.x === boxNewX && w.y === boxNewY)) return; if (boxes.some(b => b.x === boxNewX && b.y === boxNewY)) return; boxes[boxIndex].x = boxNewX; boxes[boxIndex].y = boxNewY; } player.x = newX; player.y = newY; moves++; updateUI(); draw(); if (checkWin()) { gameOver = true; setTimeout(() => { level++; generateLevel(level); }, 1000); } } // ---- 绘制 ---- function draw() { ctx.clearRect(0, 0, W, H); for (let r = 0; r < ROWS; r++) { for (let c = 0; c < COLS; c++) { ctx.fillStyle = (r + c) % 2 === 0 ? '#0f0f23' : '#14142e'; ctx.fillRect(c * CELL, r * CELL, CELL, CELL); } } for (const wall of walls) { ctx.fillStyle = '#2d2d4a'; ctx.shadowColor = '#4a4a6a'; ctx.shadowBlur = 5; ctx.fillRect(wall.x * CELL + 1, wall.y * CELL + 1, CELL - 2, CELL - 2); } ctx.shadowBlur = 0; for (const target of targets) { ctx.fillStyle = 'rgba(251, 191, 36, 0.15)'; ctx.fillRect(target.x * CELL + 2, target.y * CELL + 2, CELL - 4, CELL - 4); ctx.fillStyle = '#fbbf24'; ctx.font = '20px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('★', target.x * CELL + CELL/2, target.y * CELL + CELL/2 + 1); } for (const box of boxes) { const onTarget = targets.some(t => t.x === box.x && t.y === box.y); ctx.fillStyle = onTarget ? '#2cb67d' : '#f25f4c'; ctx.shadowColor = onTarget ? '#2cb67d' : '#f25f4c'; ctx.shadowBlur = 15; ctx.fillRect(box.x * CELL + 3, box.y * CELL + 3, CELL - 6, CELL - 6); ctx.shadowBlur = 0; ctx.fillStyle = onTarget ? 'rgba(255,255,255,0.2)' : 'rgba(255,255,255,0.15)'; ctx.fillRect(box.x * CELL + 6, box.y * CELL + 6, CELL - 12, 4); ctx.fillRect(box.x * CELL + 6, box.y * CELL + CELL - 10, CELL - 12, 4); ctx.fillRect(box.x * CELL + 6, box.y * CELL + 6, 4, CELL - 12); ctx.fillRect(box.x * CELL + CELL - 10, box.y * CELL + 6, 4, CELL - 12); } ctx.fillStyle = '#60a5fa'; ctx.shadowColor = '#60a5fa'; ctx.shadowBlur = 20; ctx.beginPath(); ctx.arc(player.x * CELL + CELL/2, player.y * CELL + CELL/2, CELL * 0.35, 0, 2*Math.PI); ctx.fill(); ctx.shadowBlur = 0; ctx.fillStyle = 'white'; ctx.beginPath(); ctx.arc(player.x * CELL + CELL/2 - 5, player.y * CELL + CELL/2 - 4, 4, 0, 2*Math.PI); ctx.fill(); ctx.beginPath(); ctx.arc(player.x * CELL + CELL/2 + 5, player.y * CELL + CELL/2 - 4, 4, 0, 2*Math.PI); ctx.fill(); ctx.fillStyle = '#0a0a1a'; ctx.beginPath(); ctx.arc(player.x * CELL + CELL/2 - 6, player.y * CELL + CELL/2 - 5, 2, 0, 2*Math.PI); ctx.fill(); ctx.beginPath(); ctx.arc(player.x * CELL + CELL/2 + 4, player.y * CELL + CELL/2 - 5, 2, 0, 2*Math.PI); ctx.fill(); if (gameOver) { ctx.fillStyle = 'rgba(0,0,0,0.4)'; ctx.fillRect(0, 0, W, H); ctx.fillStyle = '#fbbf24'; ctx.font = 'bold 28px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('🎉 过关!', W/2, H/2 - 10); ctx.fillStyle = '#e0e0e0'; ctx.font = '16px Arial'; ctx.fillText(`进入第 ${level + 1} 关`, W/2, H/2 + 35); } ctx.fillStyle = 'rgba(255,255,255,0.08)'; ctx.font = '12px Arial'; ctx.textAlign = 'left'; ctx.textBaseline = 'bottom'; const remaining = boxes.filter(b => !targets.some(t => t.x === b.x && t.y === b.y)).length; ctx.fillText(`📦 ${remaining} / ${boxes.length}`, 8, H - 6); } function gameLoop() { draw(); animationId = requestAnimationFrame(gameLoop); } // ---- 事件绑定 ---- document.addEventListener('keydown', (e) => { const key = e.key; if (key === 'ArrowUp') { e.preventDefault(); movePlayer(0, -1); } else if (key === 'ArrowDown') { e.preventDefault(); movePlayer(0, 1); } else if (key === 'ArrowLeft') { e.preventDefault(); movePlayer(-1, 0); } else if (key === 'ArrowRight') { e.preventDefault(); movePlayer(1, 0); } }); let touchStartX = 0, touchStartY = 0; canvas.addEventListener('touchstart', (e) => { e.preventDefault(); const touch = e.touches[0]; touchStartX = touch.clientX; touchStartY = touch.clientY; }, { passive: false }); canvas.addEventListener('touchmove', (e) => { e.preventDefault(); if (!touchStartX || !touchStartY) return; const touch = e.touches[0]; const dx = touch.clientX - touchStartX; const dy = touch.clientY - touchStartY; if (Math.abs(dx) < 15 && Math.abs(dy) < 15) return; if (Math.abs(dx) > Math.abs(dy)) { movePlayer(dx > 0 ? 1 : -1, 0); } else { movePlayer(0, dy > 0 ? 1 : -1); } touchStartX = touch.clientX; touchStartY = touch.clientY; }, { passive: false }); canvas.addEventListener('touchend', (e) => { e.preventDefault(); touchStartX = 0; touchStartY = 0; }); document.getElementById('restartBtn').addEventListener('click', () => { generateLevel(level); }); // 启动 generateLevel(1); gameLoop(); })(); </script> </body> </html> </div> </includeonly>
返回
微件:推箱子
。
查看“︁微件:推箱子”︁的源代码
来自植物大战僵尸杂交版Wiki