微件:推箱子:修订间差异
来自植物大战僵尸杂交版Wiki
更多操作
创建页面,内容为“<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;…” |
无编辑摘要 |
||
| 第105行: | 第105行: | ||
const H = ROWS * CELL; | const H = ROWS * CELL; | ||
// | // 边界限制:箱子不能出现在边缘 | ||
const EDGE_MARGIN = 2; | const EDGE_MARGIN = 2; | ||
// 游戏状态 | // 游戏状态 | ||
| 第118行: | 第118行: | ||
let animationId = null; | let animationId = null; | ||
// ---- | // ---- 检查位置是否在内部区域 ---- | ||
function isInsideArea(x, y) { | function isInsideArea(x, y) { | ||
return x >= EDGE_MARGIN && x < COLS - EDGE_MARGIN && | return x >= EDGE_MARGIN && x < COLS - EDGE_MARGIN && | ||
| 第124行: | 第124行: | ||
} | } | ||
// ---- | // ---- 检查位置是否可通行 ---- | ||
function isWalkable(x, y) { | function isWalkable(x, y) { | ||
if (x < 0 || x >= COLS || y < 0 || y >= ROWS) return false; | if (x < 0 || x >= COLS || y < 0 || y >= ROWS) return false; | ||
| 第131行: | 第131行: | ||
} | } | ||
// ---- | // ---- 检查位置是否可站立(用于箱子摆放) ---- | ||
function isBoxInDeadEnd(bx, by) { | 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]]; | const dirs = [[0, -1], [0, 1], [-1, 0], [1, 0]]; | ||
let | 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) { | for (const [dx, dy] of dirs) { | ||
const nx = bx + dx; | const nx = bx + dx; | ||
const ny = by + dy; | const ny = by + dy; | ||
if ( | if (isWalkable(nx, ny) && !boxList.some(b => b.x === nx && b.y === ny)) { | ||
openDirs.push([dx, dy]); | |||
} | } | ||
} | |||
if (openDirs.length === 1) { | |||
if (!isWalkable( | 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 | |||
return false; | |||
} | } | ||
// ---- | // ---- 检查箱子是否可到达目标(BFS模拟) ---- | ||
function canBoxReachTarget( | function canBoxReachTarget(boxList, targetList) { | ||
for (const box of | for (const box of boxList) { | ||
if ( | if (targetList.some(t => t.x === box.x && t.y === box.y)) continue; | ||
if (isBoxInDeadEnd(box.x, box.y)) return false; | // 使用更严格的死角检测 | ||
if (isBoxInDeadEnd(box.x, box.y, boxList, targetList)) { | |||
let | return false; | ||
for (const target of | } | ||
// 检查是否有目标在合理范围内 | |||
let hasTarget = false; | |||
for (const target of targetList) { | |||
const dist = Math.abs(box.x - target.x) + Math.abs(box.y - target.y); | const dist = Math.abs(box.x - target.x) + Math.abs(box.y - target.y); | ||
if (dist < | if (dist < 8) { | ||
hasTarget = true; | |||
break; | break; | ||
} | } | ||
} | } | ||
if (! | if (!hasTarget) return false; | ||
} | } | ||
return true; | 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; | |||
} | } | ||
| 第176行: | 第288行: | ||
let valid = false; | let valid = false; | ||
while (!valid && attempts < | while (!valid && attempts < 200) { | ||
attempts++; | attempts++; | ||
walls = []; | walls = []; | ||
| 第192行: | 第304行: | ||
} | } | ||
// | // 生成内部障碍物(内部区域) | ||
const numWalls = 3 + (lvl % 4); | const numWalls = 3 + (lvl % 4); | ||
for (let i = 0; i < numWalls; i++) { | for (let i = 0; i < numWalls; i++) { | ||
| 第210行: | 第322行: | ||
} | } | ||
// | // 生成目标(内部区域,且不在死角) | ||
const numBoxes = Math.min(3 + Math.floor(lvl / 2), 4); | const numBoxes = Math.min(3 + Math.floor(lvl / 2), 4); | ||
for (let i = 0; i < numBoxes; i++) { | for (let i = 0; i < numBoxes; i++) { | ||
let tx, ty; | let tx, ty; | ||
| 第221行: | 第331行: | ||
ty = EDGE_MARGIN + Math.floor(Math.random() * (ROWS - EDGE_MARGIN * 2)); | ty = EDGE_MARGIN + Math.floor(Math.random() * (ROWS - EDGE_MARGIN * 2)); | ||
attempts2++; | attempts2++; | ||
} while (attempts2 < | } while (attempts2 < 100 && !isTargetValid(tx, ty, targets)); | ||
if (attempts2 < 100) { | |||
if (attempts2 < | |||
targets.push({ x: tx, y: ty }); | targets.push({ x: tx, y: ty }); | ||
} | } | ||
| 第236行: | 第339行: | ||
if (targets.length < numBoxes) continue; | if (targets.length < numBoxes) continue; | ||
// | // 生成箱子(在目标周围,且不在死角) | ||
for (let i = 0; i < targets.length; i++) { | for (let i = 0; i < targets.length; i++) { | ||
let bx, by; | let bx, by; | ||
| 第249行: | 第352行: | ||
} | } | ||
// 先尝试近距离 | |||
for (const [dx, dy] of dirs) { | for (const [dx, dy] of dirs) { | ||
const nx = target.x + dx; | const nx = target.x + dx; | ||
const ny = target.y + dy; | const ny = target.y + dy; | ||
if ( | 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; | |||
} | |||
} | } | ||
} | } | ||
| 第267行: | 第371行: | ||
// 如果没找到,扩大搜索范围 | // 如果没找到,扩大搜索范围 | ||
if (!found) { | if (!found) { | ||
for (let dx = - | const searchRadius = 4; | ||
for (let dy = - | for (let dx = -searchRadius; dx <= searchRadius; dx++) { | ||
for (let dy = -searchRadius; dy <= searchRadius; dy++) { | |||
if (dx === 0 && dy === 0) continue; | if (dx === 0 && dy === 0) continue; | ||
const nx = target.x + dx; | const nx = target.x + dx; | ||
const ny = target.y + dy; | const ny = target.y + dy; | ||
if ( | 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; | |||
} | |||
} | } | ||
} | } | ||
| 第295行: | 第399行: | ||
if (boxes.length < targets.length) continue; | if (boxes.length < targets.length) continue; | ||
// | // 最终验证:所有箱子都不在死角 | ||
let | let allValid = true; | ||
for (const box of boxes) { | for (const box of boxes) { | ||
if (!isInsideArea(box.x, box.y)) { | if (!isInsideArea(box.x, box.y)) { | ||
allValid = false; | |||
break; | break; | ||
} | } | ||
if (isBoxInDeadEnd(box.x, box.y, boxes, targets)) { | |||
allValid = false; | |||
if (isBoxInDeadEnd(box.x, box.y)) { | |||
break; | break; | ||
} | } | ||
} | } | ||
if ( | if (!allValid) continue; | ||
// | // 验证箱子可到达目标 | ||
if (!canBoxReachTarget(boxes, targets)) continue; | if (!canBoxReachTarget(boxes, targets)) continue; | ||
| 第334行: | 第432行: | ||
walls.push({ x: COLS - 1, y: i }); | walls.push({ x: COLS - 1, y: i }); | ||
} | } | ||
// | // 预设内部墙 | ||
const presetWalls = [[3, 2], [6, 2], [2, 5], [7, 5 | const presetWalls = [[3, 2], [6, 2], [2, 5], [7, 5]]; | ||
for (const [wx, wy] of presetWalls) { | for (const [wx, wy] of presetWalls) { | ||
if (isInsideArea(wx, wy)) { | if (isInsideArea(wx, wy)) { | ||
| 第341行: | 第439行: | ||
} | } | ||
} | } | ||
// | // 预设箱子和目标(都在内部区域,且不在死角) | ||
const presetBoxes = [[3, 3], [5, 4], [4, 6]]; | const presetBoxes = [[3, 3], [5, 4], [4, 6]]; | ||
const presetTargets = [[6, 3], [3, 5], [7, 6]]; | const presetTargets = [[6, 3], [3, 5], [7, 6]]; | ||
| 第438行: | 第536行: | ||
ctx.clearRect(0, 0, W, H); | ctx.clearRect(0, 0, W, H); | ||
for (let r = 0; r < ROWS; r++) { | for (let r = 0; r < ROWS; r++) { | ||
for (let c = 0; c < COLS; c++) { | for (let c = 0; c < COLS; c++) { | ||
| 第446行: | 第543行: | ||
} | } | ||
for (const wall of walls) { | for (const wall of walls) { | ||
ctx.fillStyle = '#2d2d4a'; | ctx.fillStyle = '#2d2d4a'; | ||
| 第455行: | 第551行: | ||
ctx.shadowBlur = 0; | ctx.shadowBlur = 0; | ||
for (const target of targets) { | for (const target of targets) { | ||
ctx.fillStyle = 'rgba(251, 191, 36, 0.15)'; | ctx.fillStyle = 'rgba(251, 191, 36, 0.15)'; | ||
| 第466行: | 第561行: | ||
} | } | ||
for (const box of boxes) { | for (const box of boxes) { | ||
const onTarget = targets.some(t => t.x === box.x && t.y === box.y); | const onTarget = targets.some(t => t.x === box.x && t.y === box.y); | ||
| 第481行: | 第575行: | ||
} | } | ||
ctx.fillStyle = '#60a5fa'; | ctx.fillStyle = '#60a5fa'; | ||
ctx.shadowColor = '#60a5fa'; | ctx.shadowColor = '#60a5fa'; | ||
| 第504行: | 第597行: | ||
ctx.fill(); | ctx.fill(); | ||
if (gameOver) { | if (gameOver) { | ||
ctx.fillStyle = 'rgba(0,0,0,0.4)'; | ctx.fillStyle = 'rgba(0,0,0,0.4)'; | ||
| 第518行: | 第610行: | ||
} | } | ||
ctx.fillStyle = 'rgba(255,255,255,0.08)'; | ctx.fillStyle = 'rgba(255,255,255,0.08)'; | ||
ctx.font = '12px Arial'; | ctx.font = '12px Arial'; | ||