打开/关闭搜索
搜索
打开/关闭菜单
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; // 距离边界至少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 isBoxInDeadEnd(bx, by) { const dirs = [[0, -1], [0, 1], [-1, 0], [1, 0]]; let blockedDirections = 0; for (const [dx, dy] of dirs) { const nx = bx + dx; const ny = by + dy; if (!isWalkable(nx, ny)) { blockedDirections++; } else { const nx2 = bx + dx * 2; const ny2 = by + dy * 2; if (!isWalkable(nx2, ny2) || boxes.some(b => b.x === nx2 && b.y === ny2)) { blockedDirections++; } } } return blockedDirections >= 3; } // ---- 检查箱子是否可到达目标 ---- function canBoxReachTarget(boxesList, targetsList) { for (const box of boxesList) { if (targetsList.some(t => t.x === box.x && t.y === box.y)) continue; if (isBoxInDeadEnd(box.x, box.y)) return false; let hasReachableTarget = false; for (const target of targetsList) { const dist = Math.abs(box.x - target.x) + Math.abs(box.y - target.y); if (dist < 6) { hasReachableTarget = true; break; } } if (!hasReachableTarget) return false; } return true; } // ---- 生成关卡 ---- function generateLevel(lvl) { let attempts = 0; let valid = false; while (!valid && attempts < 100) { 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 < 80 && ( (tx === 1 && ty === 1) || walls.some(w => w.x === tx && w.y === ty) || targets.some(t => t.x === tx && t.y === ty) || !isInsideArea(tx, ty) || (isWalkable(tx - 1, ty) + isWalkable(tx + 1, ty) + isWalkable(tx, ty - 1) + isWalkable(tx, ty + 1) < 2) )); if (attempts2 < 80) { 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 (isInsideArea(nx, ny) && isWalkable(nx, ny) && !boxes.some(b => b.x === nx && b.y === ny) && !targets.some(t => t.x === nx && t.y === ny) && !(nx === 1 && ny === 1) && !isBoxInDeadEnd(nx, ny)) { bx = nx; by = ny; found = true; break; } } // 如果没找到,扩大搜索范围 if (!found) { for (let dx = -3; dx <= 3; dx++) { for (let dy = -3; dy <= 3; dy++) { if (dx === 0 && dy === 0) continue; const nx = target.x + dx; const ny = target.y + 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) && !(nx === 1 && ny === 1) && !isBoxInDeadEnd(nx, ny)) { 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 allInside = true; for (const box of boxes) { if (!isInsideArea(box.x, box.y)) { allInside = false; break; } } if (!allInside) continue; // 检查箱子是否在死角 let hasDeadEnd = false; for (const box of boxes) { if (isBoxInDeadEnd(box.x, box.y)) { hasDeadEnd = true; break; } } if (hasDeadEnd) 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], [4, 7]]; 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