游戏吃豆人

<!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;
  /* 移除overflow: hidden,允许页面滚动 */
}

/* 游戏主容器 - 确保在视窗正中 */
#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;

  // 初始化自定义设置UI
  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();
}

// ================= 自定义设置UI初始化 =================
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;
  
  // 更新UI
  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;
    }

    // BOSS型特殊处理
    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: // BOSS型
          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);
    }
  });
}

// ================= UI更新 =================
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>
posted @ 2026-02-25 21:50  bz02_2023f2  阅读(2)  评论(0)    收藏  举报  来源