<!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>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Arial, sans-serif;
}
body {
background: #000;
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 20px;
}
#game-wrapper {
position: relative;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
max-width: 90vw;
}
h2 {
color: #ffcc00;
margin-bottom: 15px;
text-align: center;
}
#hud {
display: flex;
gap: 12px;
margin-bottom: 10px;
font-size: 14px;
flex-wrap: wrap;
justify-content: center;
background: #1a1a1a;
padding: 8px 16px;
border-radius: 8px;
}
.panel {
min-width: 75px;
text-align: center;
}
.speed-panel {
color: #00ffcc;
font-weight: bold;
}
#custom-settings {
background: #222;
padding: 12px;
border-radius: 8px;
margin-bottom: 10px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
width: 800px;
max-width: 90vw;
}
.setting-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.setting-group label {
font-size: 13px;
color: #ccc;
}
.setting-group input, .setting-group select {
padding: 4px 8px;
border-radius: 4px;
border: 1px solid #444;
background: #333;
color: #fff;
}
.setting-group button {
background: #0088ff;
color: #fff;
border: none;
padding: 6px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
#controls {
margin-bottom: 10px;
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: center;
}
#speed-controls {
margin-bottom: 10px;
display: flex;
gap: 6px;
justify-content: center;
}
#player-speed-controls {
margin-bottom: 10px;
display: flex;
gap: 6px;
justify-content: center;
}
#map-controls {
margin-bottom: 8px;
display: flex;
gap: 8px;
justify-content: center;
}
.map-control-btn {
background: #ff8800;
color: #fff;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.speed-btn {
background: #3366ff;
color: #fff;
border: none;
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
}
.speed-btn.active {
background: #00ccff;
font-weight: bold;
}
.btn {
background: #ffcc00;
color: #000;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
}
.btn-danger {
background: #ff4444;
color: #fff;
}
#game-container {
position: relative;
border: 2px solid #333;
background: #000;
width: 800px;
height: 600px;
max-width: 90vw;
max-height: 70vh;
overflow: visible;
isolation: isolate;
cursor: grab;
margin: 0 auto;
}
#game-container.grabbing {
cursor: grabbing;
}
#game-canvas {
position: absolute;
top: 0;
left: 0;
transform-origin: center center;
transition: transform 0.1s ease-out;
}
#map-controls-tip {
position: absolute;
top: 10px;
left: 10px;
font-size: 12px;
color: #888;
background: rgba(0,0,0,0.7);
padding: 4px 8px;
border-radius: 4px;
z-index: 10;
}
#center-tip {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 14px;
color: #ccc;
background: rgba(0,0,0,0.8);
padding: 8px 16px;
border-radius: 8px;
z-index: 999;
display: none;
}
.modal {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background: #111;
padding: 24px;
border-radius: 12px;
border: 2px solid #ffcc00;
text-align: center;
z-index: 9999;
min-width: 300px;
max-width: 90vw;
}
.modal h3 {
color: #ffcc00;
margin-bottom: 15px;
}
.modal button {
margin: 5px;
}
.lev-btn {
width: 40px;
height: 40px;
margin: 2px;
}
.hide {
display: none;
}
</style>
</head>
<body>
<div id="game-wrapper">
<h2>超级吃豆人 - 全局居中版</h2>
<div id="custom-settings">
<div class="setting-group">
<label>障碍物占比 (%):</label>
<input type="range" id="wall-percent" min="1" max="50" value="7" step="1">
<span id="wall-percent-value">7%</span>
</div>
<div class="setting-group">
<label>怪物数量:</label>
<input type="number" id="ghost-count" min="1" max="20" value="2">
</div>
<div class="setting-group">
<label>怪物类型组合:</label>
<select id="ghost-type-mode">
<option value="mix">混合类型</option>
<option value="chase">全追逐型</option>
<option value="wander">全游走型</option>
<option value="circle">全绕圈型</option>
<option value="boss">全BOSS型</option>
</select>
</div>
<div class="setting-group">
<label>地图尺寸:</label>
<input type="number" id="map-width" min="30" max="200" value="70"> ×
<input type="number" id="map-height" min="20" max="150" value="50">
</div>
<div class="setting-group">
<button onclick="applyCustomSettings()">应用设置并重启</button>
<button onclick="resetCustomSettings()">重置默认值</button>
</div>
</div>
<div id="hud">
<div class="panel">关卡: <span id="level">1</span></div>
<div class="panel">分数: <span id="score">0</span></div>
<div class="panel">生命: <span id="lives">3</span></div>
<div class="panel">金币: <span id="gold">0</span></div>
<div class="panel">豆子: <span id="dots-eaten">0</span>/<span id="dots-required">0</span></div>
<div class="panel">技能: <span id="skill">无</span></div>
<div class="panel speed-panel">玩家速度: <span id="player-speed-rate">1.0x</span></div>
<div class="panel speed-panel">怪物速度: <span id="ghost-speed-rate">1.0x</span></div>
</div>
<div id="controls">
<button class="btn" onclick="openLevelSelect()">选择关卡</button>
<button class="btn" onclick="openShop()">技能商店</button>
<button class="btn btn-danger" onclick="restartGame()">重新开始</button>
</div>
<div id="map-controls">
<button class="map-control-btn" onclick="centerMap()">📌 地图居中</button>
<button class="map-control-btn" onclick="zoomInMap()">🔍 放大</button>
<button class="map-control-btn" onclick="zoomOutMap()">🔍 缩小</button>
<button class="map-control-btn" onclick="resetMapView()">🔄 重置视图</button>
</div>
<div id="player-speed-controls">
<span style="color:#fff; margin-right:8px;">玩家速度:</span>
<button class="speed-btn" onclick="setPlayerSpeed(0.5)">慢速 (0.5x)</button>
<button class="speed-btn active" onclick="setPlayerSpeed(1.0)">正常 (1.0x)</button>
<button class="speed-btn" onclick="setPlayerSpeed(1.5)">快速 (1.5x)</button>
<button class="speed-btn" onclick="setPlayerSpeed(2.0)">极速 (2.0x)</button>
</div>
<div id="speed-controls">
<span style="color:#fff; margin-right:8px;">怪物速度:</span>
<button class="speed-btn" onclick="setGhostSpeed(0.5)">慢速 (0.5x)</button>
<button class="speed-btn active" onclick="setGhostSpeed(1.0)">正常 (1.0x)</button>
<button class="speed-btn" onclick="setGhostSpeed(1.5)">快速 (1.5x)</button>
<button class="speed-btn" onclick="setGhostSpeed(2.0)">极速 (2.0x)</button>
</div>
<div id="game-container">
<div id="map-controls-tip">鼠标拖拽移动 | 滚轮缩放 | 双击重置视图</div>
<canvas id="game-canvas"></canvas>
</div>
</div>
<div id="center-tip">地图已居中</div>
<div id="level-modal" class="modal hide">
<h3>选择关卡 (1-18)</h3>
<div id="level-buttons" style="display: grid; grid-template-columns: repeat(6, 1fr); gap: 4px; margin-bottom: 15px;"></div>
<button class="btn" onclick="closeAllModals()">关闭</button>
</div>
<div id="shop-modal" class="modal hide">
<h3>技能商店</h3>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 15px;">
<button class="btn" onclick="buySkill('dash')">冲刺 (10金币)</button>
<button class="btn" onclick="buySkill('shield')">护盾 (15金币)</button>
<button class="btn" onclick="buySkill('slow')">减速 (8金币)</button>
<button class="btn" onclick="buySkill('teleport')">传送 (12金币)</button>
<button class="btn" onclick="buySkill('heal')">生命+1 (20金币)</button>
<button class="btn" onclick="buySkill('wallpass')">穿墙 (18金币)</button>
</div>
<button class="btn" onclick="closeAllModals()">关闭</button>
</div>
<div id="win-modal" class="modal hide">
<h3>关卡通关!</h3>
<p>已吃掉所有目标豆子</p>
<button class="btn" onclick="nextLevel()">下一关</button>
<button class="btn" onclick="closeAllModals()">关闭</button>
</div>
<div id="gameover-modal" class="modal hide">
<h3>游戏结束</h3>
<p>最终分数: <span id="final-score">0</span></p>
<button class="btn" onclick="restartGame()">重新开始</button>
</div>
<script>
const CELL_SIZE = 10;
const CONTAINER_WIDTH = 800;
const CONTAINER_HEIGHT = 600;
const MAX_LEVEL = 18;
const CLEAR_PERCENT = 0.8;
const PLAYER_BASE_STEPS_PER_SECOND = 15;
const GHOST_BASE_STEPS_PER_SECOND = 8;
const FRAME_RATE = 60;
let playerSpeedMultiplier = 1.0;
let ghostSpeedMultiplier = 1.0;
let PLAYER_STEP_PER_FRAME = (PLAYER_BASE_STEPS_PER_SECOND * playerSpeedMultiplier) / FRAME_RATE;
let GHOST_STEP_PER_FRAME = (GHOST_BASE_STEPS_PER_SECOND * ghostSpeedMultiplier) / FRAME_RATE;
const mapView = {
scale: 1.0,
offsetX: 0,
offsetY: 0,
minScale: 0.3,
maxScale: 3.0,
isDragging: false,
dragStartX: 0,
dragStartY: 0,
container: null,
canvas: null,
containerCenterX: CONTAINER_WIDTH / 2,
containerCenterY: CONTAINER_HEIGHT / 2,
mapWidth: 0,
mapHeight: 0
};
const customGameConfig = {
wallPercent: 7,
ghostCount: 2,
ghostTypeMode: 'mix',
mapWidth: 70,
mapHeight: 50
};
let canvas, ctx;
let gameLoop;
let currentLevel = 1;
let score = 0;
let lives = 3;
let gold = 0;
let dotsEaten = 0;
let totalDots = 0;
let requiredDots = 0;
let skillActive = "无";
let map = [];
let gameMapWidth, gameMapHeight;
const player = {
x: 0,
y: 0,
dir: null,
nextDir: null,
speed: PLAYER_STEP_PER_FRAME,
shield: false,
dash: 0,
wallPass: 0,
moveX: 0,
moveY: 0
};
let ghosts = [];
let powerMode = false;
let powerModeTimer = 0;
let ghostSlowTimer = 0;
const keys = {
ArrowUp: false,
ArrowDown: false,
ArrowLeft: false,
ArrowRight: false
};
window.addEventListener('DOMContentLoaded', init);
window.addEventListener('resize', () => {
mapView.containerCenterX = mapView.container.clientWidth / 2;
mapView.containerCenterY = mapView.container.clientHeight / 2;
centerMap();
});
function init() {
mapView.container = document.getElementById('game-container');
mapView.canvas = document.getElementById('game-canvas');
canvas = mapView.canvas;
ctx = canvas.getContext('2d');
canvas.width = CONTAINER_WIDTH;
canvas.height = CONTAINER_HEIGHT;
initCustomSettingsUI();
initMapViewControls();
document.addEventListener('keydown', (e) => {
if (keys.hasOwnProperty(e.key)) {
keys[e.key] = true;
if (e.key === 'ArrowUp') player.nextDir = 'up';
else if (e.key === 'ArrowDown') player.nextDir = 'down';
else if (e.key === 'ArrowLeft') player.nextDir = 'left';
else if (e.key === 'ArrowRight') player.nextDir = 'right';
e.preventDefault();
}
});
document.addEventListener('keyup', (e) => {
if (keys.hasOwnProperty(e.key)) {
keys[e.key] = false;
if (!keys.ArrowUp && !keys.ArrowDown && !keys.ArrowLeft && !keys.ArrowRight) {
player.nextDir = null;
player.dir = null;
}
}
});
generateLevelButtons();
applyCustomSettings();
}
function initCustomSettingsUI() {
const wallPercentSlider = document.getElementById('wall-percent');
const wallPercentValue = document.getElementById('wall-percent-value');
wallPercentSlider.value = customGameConfig.wallPercent;
wallPercentValue.textContent = `${customGameConfig.wallPercent}%`;
wallPercentSlider.addEventListener('input', () => {
customGameConfig.wallPercent = parseInt(wallPercentSlider.value);
wallPercentValue.textContent = `${customGameConfig.wallPercent}%`;
});
const ghostCountInput = document.getElementById('ghost-count');
ghostCountInput.value = customGameConfig.ghostCount;
ghostCountInput.addEventListener('change', () => {
customGameConfig.ghostCount = Math.max(1, Math.min(20, parseInt(ghostCountInput.value) || 2));
ghostCountInput.value = customGameConfig.ghostCount;
});
const ghostTypeModeSelect = document.getElementById('ghost-type-mode');
ghostTypeModeSelect.value = customGameConfig.ghostTypeMode;
ghostTypeModeSelect.addEventListener('change', () => {
customGameConfig.ghostTypeMode = ghostTypeModeSelect.value;
});
const mapWidthInput = document.getElementById('map-width');
const mapHeightInput = document.getElementById('map-height');
mapWidthInput.value = customGameConfig.mapWidth;
mapHeightInput.value = customGameConfig.mapHeight;
mapWidthInput.addEventListener('change', () => {
customGameConfig.mapWidth = Math.max(30, Math.min(200, parseInt(mapWidthInput.value) || 70));
mapWidthInput.value = customGameConfig.mapWidth;
});
mapHeightInput.addEventListener('change', () => {
customGameConfig.mapHeight = Math.max(20, Math.min(150, parseInt(mapHeightInput.value) || 50));
mapHeightInput.value = customGameConfig.mapHeight;
});
}
function applyCustomSettings() {
gameMapWidth = customGameConfig.mapWidth;
gameMapHeight = customGameConfig.mapHeight;
mapView.mapWidth = gameMapWidth * CELL_SIZE;
mapView.mapHeight = gameMapHeight * CELL_SIZE;
startLevel(1);
centerMap();
alert('自定义设置已应用!游戏已重启,地图已全局居中。');
}
function resetCustomSettings() {
customGameConfig.wallPercent = 7;
customGameConfig.ghostCount = 2;
customGameConfig.ghostTypeMode = 'mix';
customGameConfig.mapWidth = 70;
customGameConfig.mapHeight = 50;
document.getElementById('wall-percent').value = 7;
document.getElementById('wall-percent-value').textContent = '7%';
document.getElementById('ghost-count').value = 2;
document.getElementById('ghost-type-mode').value = 'mix';
document.getElementById('map-width').value = 70;
document.getElementById('map-height').value = 50;
}
function initMapViewControls() {
const container = mapView.container;
const canvasEl = mapView.canvas;
container.addEventListener('mousedown', (e) => {
if (e.button === 0) {
mapView.isDragging = true;
const rect = container.getBoundingClientRect();
mapView.dragStartX = e.clientX - rect.left - mapView.offsetX;
mapView.dragStartY = e.clientY - rect.top - mapView.offsetY;
container.classList.add('grabbing');
}
});
document.addEventListener('mousemove', (e) => {
if (mapView.isDragging) {
const rect = container.getBoundingClientRect();
mapView.offsetX = e.clientX - rect.left - mapView.dragStartX;
mapView.offsetY = e.clientY - rect.top - mapView.dragStartY;
updateMapViewTransform();
}
});
document.addEventListener('mouseup', () => {
mapView.isDragging = false;
container.classList.remove('grabbing');
});
container.addEventListener('wheel', (e) => {
e.preventDefault();
const rect = container.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
const scaleDelta = e.deltaY > 0 ? -0.1 : 0.1;
const oldScale = mapView.scale;
mapView.scale = Math.max(mapView.minScale, Math.min(mapView.maxScale, mapView.scale + scaleDelta));
mapView.offsetX = mouseX - (mouseX - mapView.offsetX) * (mapView.scale / oldScale);
mapView.offsetY = mouseY - (mouseY - mapView.offsetY) * (mapView.scale / oldScale);
updateMapViewTransform();
});
container.addEventListener('dblclick', (e) => {
e.preventDefault();
resetMapView();
});
}
function centerMap() {
mapView.containerCenterX = mapView.container.clientWidth / 2;
mapView.containerCenterY = mapView.container.clientHeight / 2;
mapView.mapWidth = gameMapWidth * CELL_SIZE;
mapView.mapHeight = gameMapHeight * CELL_SIZE;
const scaledMapCenterX = (mapView.mapWidth * mapView.scale) / 2;
const scaledMapCenterY = (mapView.mapHeight * mapView.scale) / 2;
mapView.offsetX = mapView.containerCenterX - scaledMapCenterX;
mapView.offsetY = mapView.containerCenterY - scaledMapCenterY;
updateMapViewTransform();
const centerTip = document.getElementById('center-tip');
centerTip.style.display = 'block';
setTimeout(() => {
centerTip.style.display = 'none';
}, 1500);
}
function zoomInMap() {
mapView.scale = Math.min(mapView.maxScale, mapView.scale + 0.2);
centerMap();
}
function zoomOutMap() {
mapView.scale = Math.max(mapView.minScale, mapView.scale - 0.2);
centerMap();
}
function updateMapViewTransform() {
const canvasEl = mapView.canvas;
canvasEl.style.transform = `translate(${mapView.offsetX}px, ${mapView.offsetY}px) scale(${mapView.scale})`;
canvasEl.style.transform = canvasEl.style.transform;
}
function resetMapView() {
mapView.scale = 1.0;
centerMap();
}
function setPlayerSpeed(multiplier) {
playerSpeedMultiplier = multiplier;
PLAYER_STEP_PER_FRAME = (PLAYER_BASE_STEPS_PER_SECOND * multiplier) / FRAME_RATE;
player.speed = PLAYER_STEP_PER_FRAME;
document.getElementById('player-speed-rate').textContent = `${multiplier}x`;
const speedButtons = document.querySelectorAll('#player-speed-controls .speed-btn');
speedButtons.forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
}
function setGhostSpeed(multiplier) {
ghostSpeedMultiplier = multiplier;
GHOST_STEP_PER_FRAME = (GHOST_BASE_STEPS_PER_SECOND * multiplier) / FRAME_RATE;
document.getElementById('ghost-speed-rate').textContent = `${multiplier}x`;
const speedButtons = document.querySelectorAll('#speed-controls .speed-btn');
speedButtons.forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
}
function generateLevelButtons() {
const container = document.getElementById('level-buttons');
container.innerHTML = '';
for (let i = 1; i <= MAX_LEVEL; i++) {
const btn = document.createElement('button');
btn.className = 'btn lev-btn';
btn.textContent = i;
btn.onclick = () => {
startLevel(i);
closeAllModals();
centerMap();
};
container.appendChild(btn);
}
}
function startLevel(level) {
if (gameLoop) clearInterval(gameLoop);
currentLevel = level;
score = 0;
lives = 3;
gold = 0;
dotsEaten = 0;
skillActive = "无";
powerMode = false;
powerModeTimer = 0;
ghostSlowTimer = 0;
generateMap();
initPlayerPos();
spawnGhosts(customGameConfig.ghostCount);
totalDots = countTotalDots();
requiredDots = Math.floor(totalDots * CLEAR_PERCENT);
mapView.mapWidth = gameMapWidth * CELL_SIZE;
mapView.mapHeight = gameMapHeight * CELL_SIZE;
updateHUD();
gameLoop = setInterval(gameUpdate, 1000 / FRAME_RATE);
}
function restartGame() {
startLevel(1);
closeAllModals();
setPlayerSpeed(1.0);
setGhostSpeed(1.0);
centerMap();
}
function nextLevel() {
if (currentLevel < MAX_LEVEL) {
startLevel(currentLevel + 1);
} else {
alert('恭喜!通关所有关卡!');
restartGame();
}
closeAllModals();
centerMap();
}
function generateMap() {
map = Array(gameMapHeight).fill().map(() => Array(gameMapWidth).fill(2));
for (let x = 0; x < gameMapWidth; x++) {
map[0][x] = 1;
map[gameMapHeight - 1][x] = 1;
}
for (let y = 0; y < gameMapHeight; y++) {
map[y][0] = 1;
map[y][gameMapWidth - 1] = 1;
}
const totalCells = (gameMapWidth - 4) * (gameMapHeight - 4);
const wallCount = Math.floor(totalCells * (customGameConfig.wallPercent / 100));
for (let i = 0; i < wallCount; i++) {
const x = 2 + Math.floor(Math.random() * (gameMapWidth - 4));
const y = 2 + Math.floor(Math.random() * (gameMapHeight - 4));
map[y][x] = 1;
}
const powerCount = Math.max(2, Math.floor(gameMapWidth * gameMapHeight / 100));
for (let i = 0; i < powerCount; i++) {
const x = 5 + Math.floor(Math.random() * (gameMapWidth - 10));
const y = 5 + Math.floor(Math.random() * (gameMapHeight - 10));
if (map[y][x] === 2) map[y][x] = 3;
}
const itemCount = Math.max(1, Math.floor(gameMapWidth * gameMapHeight / 200));
for (let i = 0; i < itemCount; i++) {
const x = 5 + Math.floor(Math.random() * (gameMapWidth - 10));
const y = 5 + Math.floor(Math.random() * (gameMapHeight - 10));
if (map[y][x] === 2) map[y][x] = 4;
}
}
function countTotalDots() {
let count = 0;
for (let y = 0; y < gameMapHeight; y++) {
for (let x = 0; x < gameMapWidth; x++) {
if ([2, 3, 4].includes(map[y][x])) count++;
}
}
return count;
}
function initPlayerPos() {
const centerX = Math.floor(gameMapWidth / 2);
const centerY = Math.floor(gameMapHeight / 2);
player.x = centerX;
player.y = centerY;
player.dir = null;
player.nextDir = null;
player.shield = false;
player.dash = 0;
player.wallPass = 0;
player.moveX = 0;
player.moveY = 0;
if (map[player.y][player.x] === 1) {
for (let dy = -2; dy <= 2; dy++) {
for (let dx = -2; dx <= 2; dx++) {
const nx = centerX + dx;
const ny = centerY + dy;
if (map[ny][nx] !== 1) {
player.x = nx;
player.y = ny;
return;
}
}
}
}
}
function spawnGhosts(count) {
ghosts = [];
for (let i = 0; i < count; i++) {
let x, y;
do {
x = 5 + Math.floor(Math.random() * (gameMapWidth - 10));
y = 5 + Math.floor(Math.random() * (gameMapHeight - 10));
} while (map[y][x] === 1 || Math.hypot(x - player.x, y - player.y) < 10);
let type;
switch (customGameConfig.ghostTypeMode) {
case 'chase': type = 1; break;
case 'wander': type = 2; break;
case 'circle': type = 3; break;
case 'boss': type = 7; break;
case 'mix':
default: type = i % 6 + 1; break;
}
if (currentLevel >= 15 && i === count - 1) type = 7;
ghosts.push({
x, y,
type,
dir: ['up', 'down', 'left', 'right'][Math.floor(Math.random() * 4)],
moveX: 0,
moveY: 0,
scared: false
});
}
}
function gameUpdate() {
updateSkillTimers();
movePlayer();
moveGhosts();
checkGhostCollision();
checkWinCondition();
drawGame();
updateHUD();
}
function updateSkillTimers() {
if (powerMode) {
powerModeTimer--;
if (powerModeTimer <= 0) powerMode = false;
}
if (player.dash > 0) player.dash--;
if (player.wallPass > 0) player.wallPass--;
if (ghostSlowTimer > 0) ghostSlowTimer--;
}
function movePlayer() {
if (player.nextDir) {
const testX = player.x + (player.nextDir === 'left' ? -1 : player.nextDir === 'right' ? 1 : 0);
const testY = player.y + (player.nextDir === 'up' ? -1 : player.nextDir === 'down' ? 1 : 0);
if (player.wallPass > 0 || map[testY][testX] !== 1) {
player.dir = player.nextDir;
}
}
if (!player.dir) return;
const currentSpeed = player.dash > 0 ? player.speed * 2 : player.speed;
switch (player.dir) {
case 'up': player.moveY -= currentSpeed; break;
case 'down': player.moveY += currentSpeed; break;
case 'left': player.moveX -= currentSpeed; break;
case 'right': player.moveX += currentSpeed; break;
}
let moved = false;
if (Math.abs(player.moveX) >= 1) {
const stepX = Math.sign(player.moveX);
const newX = player.x + stepX;
if (player.wallPass > 0 || map[player.y][newX] !== 1) {
player.x = newX;
moved = true;
}
player.moveX -= stepX;
}
if (Math.abs(player.moveY) >= 1) {
const stepY = Math.sign(player.moveY);
const newY = player.y + stepY;
if (player.wallPass > 0 || map[newY][player.x] !== 1) {
player.y = newY;
moved = true;
}
player.moveY -= stepY;
}
if (moved) {
const cell = map[player.y][player.x];
if ([2, 3, 4].includes(cell)) {
dotsEaten++;
switch (cell) {
case 2: score += 10; gold += 1; break;
case 3: score += 50; gold += 3; powerMode = true; powerModeTimer = 600; skillActive = "吞噬模式"; break;
case 4: score += 100; gold += 5; skillActive = "道具奖励"; break;
}
map[player.y][player.x] = 0;
}
}
}
function moveGhosts() {
let slowFactor = ghostSlowTimer > 0 ? 0.5 : 1;
const finalSpeed = GHOST_STEP_PER_FRAME * ghostSpeedMultiplier * slowFactor;
ghosts.forEach(ghost => {
ghost.scared = powerMode;
if (Math.random() < 0.02) {
switch (ghost.type) {
case 1:
if (player.x < ghost.x) ghost.dir = 'left';
else if (player.x > ghost.x) ghost.dir = 'right';
else if (player.y < ghost.y) ghost.dir = 'up';
else ghost.dir = 'down';
break;
case 2:
ghost.dir = ['up', 'down', 'left', 'right'][Math.floor(Math.random() * 4)];
break;
case 3:
const dirs = ['up', 'right', 'down', 'left'];
const currIdx = dirs.indexOf(ghost.dir);
ghost.dir = dirs[(currIdx + 1) % 4];
break;
case 7:
if (Math.random() < 0.01) {
ghost.x = 5 + Math.floor(Math.random() * (gameMapWidth - 10));
ghost.y = 5 + Math.floor(Math.random() * (gameMapHeight - 10));
}
break;
}
}
switch (ghost.dir) {
case 'up': ghost.moveY -= finalSpeed; break;
case 'down': ghost.moveY += finalSpeed; break;
case 'left': ghost.moveX -= finalSpeed; break;
case 'right': ghost.moveX += finalSpeed; break;
}
if (Math.abs(ghost.moveX) >= 1) {
const stepX = Math.sign(ghost.moveX);
const newX = ghost.x + stepX;
if (map[ghost.y][newX] !== 1) {
ghost.x = newX;
} else {
ghost.dir = ['up', 'down', 'left', 'right'][Math.floor(Math.random() * 4)];
}
ghost.moveX -= stepX;
}
if (Math.abs(ghost.moveY) >= 1) {
const stepY = Math.sign(ghost.moveY);
const newY = ghost.y + stepY;
if (map[newY][ghost.x] !== 1) {
ghost.y = newY;
} else {
ghost.dir = ['up', 'down', 'left', 'right'][Math.floor(Math.random() * 4)];
}
ghost.moveY -= stepY;
}
});
}
function checkGhostCollision() {
ghosts.forEach(ghost => {
const distance = Math.hypot(player.x - ghost.x, player.y - ghost.y);
if (distance < 1) {
if (powerMode) {
score += 300;
gold += 10;
ghost.x = 5 + Math.floor(Math.random() * (gameMapWidth - 10));
ghost.y = 5 + Math.floor(Math.random() * (gameMapHeight - 10));
} else if (player.shield) {
player.shield = false;
skillActive = "护盾抵挡";
} else {
lives--;
if (lives <= 0) {
clearInterval(gameLoop);
document.getElementById('final-score').textContent = score;
document.getElementById('gameover-modal').classList.remove('hide');
} else {
initPlayerPos();
skillActive = "重生";
}
}
}
});
}
function checkWinCondition() {
if (dotsEaten >= requiredDots) {
clearInterval(gameLoop);
document.getElementById('win-modal').classList.remove('hide');
}
}
function drawGame() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let y = 0; y < gameMapHeight; y++) {
for (let x = 0; x < gameMapWidth; x++) {
const px = x * CELL_SIZE;
const py = y * CELL_SIZE;
switch (map[y][x]) {
case 1:
ctx.fillStyle = '#0055ff';
ctx.fillRect(px, py, CELL_SIZE, CELL_SIZE);
ctx.strokeStyle = '#0077ff';
ctx.lineWidth = 1;
ctx.strokeRect(px, py, CELL_SIZE, CELL_SIZE);
break;
case 2:
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(px + CELL_SIZE/2, py + CELL_SIZE/2, 2, 0, Math.PI * 2);
ctx.fill();
break;
case 3:
ctx.fillStyle = '#ff00ff';
ctx.beginPath();
ctx.arc(px + CELL_SIZE/2, py + CELL_SIZE/2, 4, 0, Math.PI * 2);
ctx.fill();
break;
case 4:
ctx.fillStyle = '#00ffcc';
ctx.fillRect(px + 2, py + 2, CELL_SIZE - 4, CELL_SIZE - 4);
break;
}
}
}
const playerX = player.x * CELL_SIZE;
const playerY = player.y * CELL_SIZE;
ctx.fillStyle = '#ffdd00';
ctx.beginPath();
ctx.arc(playerX + CELL_SIZE/2, playerY + CELL_SIZE/2, CELL_SIZE/2 - 1, 0, Math.PI * 2);
ctx.fill();
if (player.shield) {
ctx.strokeStyle = '#00ccff';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(playerX + CELL_SIZE/2, playerY + CELL_SIZE/2, CELL_SIZE/2, 0, Math.PI * 2);
ctx.stroke();
}
ghosts.forEach(ghost => {
const ghostX = ghost.x * CELL_SIZE;
const ghostY = ghost.y * CELL_SIZE;
if (ghost.scared) {
ctx.fillStyle = '#2222ff';
} else {
const colors = ['#ff2222', '#ff88dd', '#22ffff', '#ff8822', '#aa22ff', '#22ff88', '#ff0088'];
ctx.fillStyle = colors[ghost.type - 1];
}
ctx.beginPath();
ctx.roundRect(ghostX + 1, ghostY + 1, CELL_SIZE - 2, CELL_SIZE - 2, [4, 4, 1, 1]);
ctx.fill();
if (ghost.type === 7 && !ghost.scared) {
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1;
ctx.strokeRect(ghostX + 1, ghostY + 1, CELL_SIZE - 2, CELL_SIZE - 2);
}
});
}
function updateHUD() {
document.getElementById('level').textContent = currentLevel;
document.getElementById('score').textContent = score;
document.getElementById('lives').textContent = lives;
document.getElementById('gold').textContent = gold;
document.getElementById('dots-eaten').textContent = dotsEaten;
document.getElementById('dots-required').textContent = requiredDots;
document.getElementById('skill').textContent = skillActive;
document.getElementById('player-speed-rate').textContent = `${playerSpeedMultiplier}x`;
document.getElementById('ghost-speed-rate').textContent = `${ghostSpeedMultiplier}x`;
}
function openLevelSelect() {
closeAllModals();
document.getElementById('level-modal').classList.remove('hide');
}
function openShop() {
closeAllModals();
document.getElementById('shop-modal').classList.remove('hide');
}
function closeAllModals() {
document.querySelectorAll('.modal').forEach(modal => {
modal.classList.add('hide');
});
}
function buySkill(skill) {
switch (skill) {
case 'dash':
if (gold >= 10) {
gold -= 10;
player.dash = 300;
skillActive = "冲刺生效";
} else alert('金币不足!');
break;
case 'shield':
if (gold >= 15) {
gold -= 15;
player.shield = true;
skillActive = "护盾生效";
} else alert('金币不足!');
break;
case 'slow':
if (gold >= 8) {
gold -= 8;
ghostSlowTimer = 400;
skillActive = "怪物减速";
} else alert('金币不足!');
break;
case 'teleport':
if (gold >= 12) {
gold -= 12;
player.x = 5 + Math.floor(Math.random() * (gameMapWidth - 10));
player.y = 5 + Math.floor(Math.random() * (gameMapHeight - 10));
skillActive = "已传送";
} else alert('金币不足!');
break;
case 'heal':
if (gold >= 20) {
gold -= 20;
lives++;
skillActive = "生命+1";
} else alert('金币不足!');
break;
case 'wallpass':
if (gold >= 18) {
gold -= 18;
player.wallPass = 300;
skillActive = "穿墙生效";
} else alert('金币不足!');
break;
}
updateHUD();
}
</script>
</body>
</html>