// ==================== 游戏入口 ==================== import { createInitialState, resetForRebirth } from './engine/state.js'; import { setCareerConfig } from './engine/careerEngine.js'; import { setEventConfig } from './engine/eventEngine.js'; import { setShopConfig } from './engine/shopEngine.js'; import { startGameLoop, stopGameLoop, setSpeed } from './engine/tickLoop.js'; import { applyEffect } from './engine/effectApplier.js'; import { processDeath } from './engine/deathEngine.js'; import { evaluateCondition } from './engine/conditionEvaluator.js'; import { buyItem, canBuyItem } from './engine/shopEngine.js'; import { initRenderer, renderAll, showDeathScreen, hideChoiceEvent, hideTitleScreen, showToast, updatePauseButton, updateSpeedButton, updateTimer } from './ui/renderer.js'; const state = createInitialState(); let _configs = null; let _gameStarted = false; let _soundEnabled = false; let _bgmAudio = null; let _bgmBattle = null; let _eventTimerId = null; let _eventSecondsLeft = 0; function getOnTick() { return () => renderAll(state, _configs.careers, _configs.shop, _configs.talents); } // ==================== 音频 ==================== function initAudio() { _bgmAudio = new Audio('assets/audio/bgm_main.mp3'); _bgmAudio.loop = true; _bgmAudio.volume = 0.4; _bgmBattle = new Audio('assets/audio/bgm_battle.mp3'); _bgmBattle.loop = true; _bgmBattle.volume = 0.4; } function toggleSound() { _soundEnabled = !_soundEnabled; const btn = document.getElementById('sound-btn'); if (btn) btn.textContent = _soundEnabled ? '🔊' : '🔇'; if (_soundEnabled) { _bgmAudio?.play().catch(() => {}); } else { _bgmAudio?.pause(); _bgmBattle?.pause(); } } function playBattleMusic() { if (!_soundEnabled) return; _bgmAudio?.pause(); _bgmBattle?.play().catch(() => {}); } function playMainMusic() { if (!_soundEnabled) return; _bgmBattle?.pause(); _bgmAudio?.play().catch(() => {}); } // ==================== 配置加载 ==================== async function loadConfigs() { const [careers, events, shop, identities, talents] = await Promise.all([ fetch('./src/config/careers.json').then(r => r.json()), fetch('./src/config/events.json').then(r => r.json()), fetch('./src/config/shop.json').then(r => r.json()), fetch('./src/config/identities.json').then(r => r.json()), fetch('./src/config/talents.json').then(r => r.json()), ]); setCareerConfig(careers); setEventConfig(events); setShopConfig(shop); return { careers, events, shop, identities, talents }; } // ==================== 新游戏 ==================== function startNewGame(configs) { resetForRebirth(state); state.speed = state.speed || 0; updateSpeedButton(state.speed); const available = configs.identities.identities.filter(id => { if (!id.unlockConditions || id.unlockConditions.length === 0) return true; return id.unlockConditions.every(c => evaluateCondition(c, state)); }); const identity = available[Math.floor(Math.random() * available.length)]; state.identity = identity.id; if (identity.startingStats) Object.assign(state.stats, identity.startingStats); if (identity.startingItems) { for (const itemId of identity.startingItems) { applyEffect({ type: 'addItem', itemId }, state); } } startGameLoop(state, configs.careers, configs.events, configs.shop, getOnTick()); playMainMusic(); } // ==================== 事件倒计时 ==================== function startEventTimer() { clearEventTimer(); _eventSecondsLeft = 10; updateTimer(_eventSecondsLeft); _eventTimerId = setInterval(() => { _eventSecondsLeft--; updateTimer(_eventSecondsLeft); if (_eventSecondsLeft <= 0) { clearEventTimer(); // 自动选择第一个可用选项,否则选最后一个 autoSelectChoice(); } }, 1000); } function clearEventTimer() { if (_eventTimerId) { clearInterval(_eventTimerId); _eventTimerId = null; } _eventSecondsLeft = 0; updateTimer(0); } function autoSelectChoice() { if (!state.currentEvent || !state.currentEvent.choices) { state.paused = false; state.currentEvent = null; hideChoiceEvent(); return; } const choices = state.currentEvent.choices; let chosenIdx = choices.length - 1; for (let i = 0; i < choices.length; i++) { if (!choices[i].requirements || evaluateCondition(choices[i].requirements, state)) { chosenIdx = i; break; } } handleChoice(chosenIdx); } // ==================== 选择事件处理 ==================== function handleChoice(choiceIndex) { if (!state.currentEvent) return; const choice = state.currentEvent.choices[choiceIndex]; if (!choice) return; if (choice.effects) { for (const effect of choice.effects) applyEffect(effect, state); } state.paused = false; state.currentEvent = null; clearEventTimer(); hideChoiceEvent(); } // ==================== 商铺购买 ==================== function handleShopBuy(itemId) { if (!_configs) return; if (!canBuyItem(_configs.shop, state, itemId)) { showToast('银两不足或条件不满足'); return; } const result = buyItem(_configs.shop, state, itemId); if (result) { showToast('购买成功'); renderAll(state, _configs.careers, _configs.shop, _configs.talents); } else { showToast('购买失败'); } } // ==================== UI事件绑定 ==================== function bindUIEvents() { // 标题画面开始 const titleStartBtn = document.getElementById('title-start-btn'); if (titleStartBtn) { titleStartBtn.addEventListener('click', () => { hideTitleScreen(); if (!_gameStarted) { _gameStarted = true; startNewGame(_configs); } }); } // 速度按钮 document.querySelectorAll('.speed-btn').forEach(btn => { btn.addEventListener('click', () => { const level = parseInt(btn.dataset.speed, 10); setSpeed(state, level, _configs.careers, _configs.events, _configs.shop, getOnTick()); updateSpeedButton(level); }); }); // 暂停按钮 const pauseBtn = document.getElementById('pause-btn'); if (pauseBtn) { pauseBtn.addEventListener('click', () => { state.paused = !state.paused; updatePauseButton(state.paused); }); } // 声音按钮 const soundBtn = document.getElementById('sound-btn'); if (soundBtn) { soundBtn.addEventListener('click', toggleSound); } // 选择按钮委托 const eventChoices = document.getElementById('event-choices'); if (eventChoices) { eventChoices.addEventListener('click', (e) => { const btn = e.target.closest('[data-index]'); if (!btn) return; const idx = parseInt(btn.dataset.index, 10); if (!isNaN(idx)) { handleChoice(idx); } }); } // 商铺购买委托 const shopPanel = document.getElementById('shop-panel'); if (shopPanel) { shopPanel.addEventListener('click', (e) => { const btn = e.target.closest('.shop-buy-btn'); if (!btn) return; const itemId = btn.dataset.itemId; if (itemId) { handleShopBuy(itemId); } }); } // 重生按钮(死亡界面) document.addEventListener('click', (e) => { if (e.target.id === 'rebirth-btn') { location.reload(); } }); } // ==================== 主流程 ==================== async function init() { const configs = await loadConfigs(); _configs = configs; initRenderer(); initAudio(); bindUIEvents(); let _hadEvent = false; setInterval(() => { // 检查事件触发,启动倒计时 if (state.currentEvent && !_hadEvent) { _hadEvent = true; startEventTimer(); } else if (!state.currentEvent && _hadEvent) { _hadEvent = false; clearEventTimer(); } if (!state.alive && state.deathReason) { stopGameLoop(); processDeath(state, configs.careers, configs.talents); showDeathScreen(state, configs.careers); } }, 500); } init().catch(console.error);