2005 lines
66 KiB
JavaScript
2005 lines
66 KiB
JavaScript
// 游戏状态
|
||
const gameState = {
|
||
governor: '', // 玩家名称
|
||
reignTime: 0, // 当前统治时间
|
||
year: 41000, // 当前年份
|
||
stats: {
|
||
loyalty: 50, // 帝国忠诚度
|
||
chaos: 50, // 混沌侵染
|
||
population: 50, // 民众控制
|
||
military: 50, // 军事力量
|
||
resources: 50 // 资源储备
|
||
},
|
||
statusEffects: [], // 状态效果
|
||
currentCardIndex: 0, // 当前卡片索引
|
||
isDealing: false, // 是否正在发牌
|
||
isDragging: false, // 是否正在拖动
|
||
gameOver: false, // 游戏是否结束
|
||
gameOverReason: '', // 游戏结束原因
|
||
characters: [], // 角色数据
|
||
availableCharacters: [], // 可用角色池
|
||
currentEffects: null, // 当前卡片效果
|
||
currentEvent: null, // 当前事件
|
||
currentCharacter: null, // 当前角色
|
||
|
||
// 新增: 事件和状态相关属性
|
||
activeEvent: null, // 当前活跃事件
|
||
eventNode: null, // 当前事件节点
|
||
completedEvents: [], // 已完成事件
|
||
activeStatuses: [], // 当前活跃的状态效果
|
||
|
||
// 开发者模式属性
|
||
devMode: false, // 开发者模式标志
|
||
nextEventId: null, // 下一个要触发的事件ID
|
||
};
|
||
|
||
// DOM 元素
|
||
const startScreen = document.querySelector('.start-screen');
|
||
const startButton = document.querySelector('.start-button');
|
||
const gameContainer = document.querySelector('.game-container');
|
||
const cardContainer = document.getElementById('card-container');
|
||
const speechBubble = document.getElementById('speech-bubble');
|
||
const gameOverDialog = document.getElementById('game-over-dialog');
|
||
const gameOverReason = document.getElementById('game-over-reason');
|
||
const finalYears = document.getElementById('final-years');
|
||
const restartButton = document.getElementById('restart-button');
|
||
const reignYearsElement = document.getElementById('reign-years');
|
||
const currentYearElement = document.getElementById('current-year');
|
||
const playerNameElement = document.getElementById('player-name');
|
||
const statusBars = {
|
||
loyalty: document.getElementById('loyalty-bar'),
|
||
chaos: document.getElementById('chaos-bar'),
|
||
population: document.getElementById('population-bar'),
|
||
military: document.getElementById('military-bar'),
|
||
resources: document.getElementById('resources-bar')
|
||
};
|
||
const statusIcons = {
|
||
loyalty: document.getElementById('loyalty-icon'),
|
||
chaos: document.getElementById('chaos-icon'),
|
||
population: document.getElementById('population-icon'),
|
||
military: document.getElementById('military-icon'),
|
||
resources: document.getElementById('resources-icon')
|
||
};
|
||
|
||
// 新增: 状态和开发者工具相关的DOM元素
|
||
const activeStatusesContainer = document.getElementById('active-statuses');
|
||
const statusDetailsDialog = document.getElementById('status-details-dialog');
|
||
const closeStatusDetailsButton = document.getElementById('close-status-details');
|
||
const devToolsButton = document.getElementById('dev-tools-button');
|
||
const devToolsDialog = document.getElementById('dev-tools-dialog');
|
||
const closeDevToolsButton = document.getElementById('close-dev-tools');
|
||
const triggerEventButton = document.getElementById('trigger-event-btn');
|
||
const eventSelector = document.getElementById('event-selector');
|
||
const statusSelector = document.getElementById('status-selector');
|
||
const addStatusButton = document.getElementById('add-status-btn');
|
||
const removeStatusButton = document.getElementById('remove-status-btn');
|
||
const statSelector = document.getElementById('stat-selector');
|
||
const statValueInput = document.getElementById('stat-value');
|
||
const setStatButton = document.getElementById('set-stat-btn');
|
||
|
||
// 拖动相关变量
|
||
let startX = 0;
|
||
let currentX = 0;
|
||
let activeCard = null;
|
||
|
||
// 加载游戏数据
|
||
function loadGameData() {
|
||
if (gameData && gameData.cards) {
|
||
gameState.characters = gameData.cards.characters;
|
||
|
||
// 初始化可用角色池 - 仅包含常驻角色
|
||
gameState.availableCharacters = gameState.characters
|
||
.filter(char => char.type === 'resident')
|
||
.map(char => char.id);
|
||
}
|
||
|
||
// 获取上次保存的年份
|
||
if (gameData && gameData.lastYear) {
|
||
gameState.year = gameData.lastYear;
|
||
}
|
||
|
||
// 从服务器加载保存的游戏状态
|
||
fetch('/api/load_game')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.game_data) {
|
||
// 恢复保存的游戏状态
|
||
Object.assign(gameState, data.game_data);
|
||
updateUI();
|
||
} else if (data.lastYear) {
|
||
// 如果没有游戏状态但有最后年份,设置年份
|
||
gameState.year = data.lastYear;
|
||
updateUI();
|
||
}
|
||
})
|
||
.catch(error => console.error('加载游戏状态失败:', error));
|
||
|
||
// 设置初始总督名称
|
||
gameState.governor = document.querySelector('#player-name').textContent || '总督';
|
||
|
||
// 检查是否启用开发者模式
|
||
checkDevMode();
|
||
|
||
// 初始化开发者工具中的事件和状态选择器
|
||
initDevTools();
|
||
}
|
||
|
||
// 开始游戏事件
|
||
startButton.addEventListener('click', startGame);
|
||
|
||
// 开始游戏函数
|
||
function startGame() {
|
||
startScreen.style.opacity = '0';
|
||
setTimeout(() => {
|
||
startScreen.style.display = 'none';
|
||
gameContainer.style.opacity = '1';
|
||
initGame();
|
||
}, 1000);
|
||
}
|
||
|
||
// 初始化游戏
|
||
function initGame() {
|
||
// 保留当前年份
|
||
const currentYear = gameState.year;
|
||
|
||
// 重置游戏状态为默认值
|
||
gameState.reignTime = 0;
|
||
gameState.year = currentYear; // 使用之前的年份而非固定值
|
||
gameState.stats = {
|
||
loyalty: 50,
|
||
chaos: 50,
|
||
population: 50,
|
||
military: 50,
|
||
resources: 50
|
||
};
|
||
gameState.statusEffects = [];
|
||
gameState.currentCardIndex = 0;
|
||
gameState.isDealing = false;
|
||
gameState.isDragging = false;
|
||
gameState.gameOver = false;
|
||
gameState.gameOverReason = '';
|
||
gameState.currentEffects = null;
|
||
gameState.currentEvent = null;
|
||
gameState.currentCharacter = null;
|
||
|
||
// 重置事件和状态相关属性
|
||
gameState.activeEvent = null;
|
||
gameState.eventNode = null;
|
||
gameState.completedEvents = [];
|
||
gameState.activeStatuses = [];
|
||
|
||
// 重新洗牌可用角色
|
||
shuffleAvailableCharacters();
|
||
|
||
// 更新UI
|
||
updateUI();
|
||
|
||
// 清空卡容器
|
||
cardContainer.innerHTML = '';
|
||
|
||
// 隐藏对话气泡
|
||
speechBubble.classList.remove('visible');
|
||
speechBubble.textContent = '';
|
||
|
||
// 移除所有效果指示器
|
||
removeAllEffectIndicators();
|
||
|
||
// 清空状态图标区域
|
||
activeStatusesContainer.innerHTML = '';
|
||
|
||
// 开始发牌动画
|
||
startDealingCards();
|
||
}
|
||
|
||
// 检查是否启用开发者模式
|
||
function checkDevMode() {
|
||
if (gameState.governor === '1') {
|
||
gameState.devMode = true;
|
||
devToolsButton.style.display = 'flex';
|
||
} else {
|
||
gameState.devMode = false;
|
||
devToolsButton.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// 初始化开发者工具
|
||
function initDevTools() {
|
||
// 检查数据结构
|
||
if (!gameData) return;
|
||
|
||
// 清空现有选项
|
||
eventSelector.innerHTML = '<option value="">选择事件...</option>';
|
||
statusSelector.innerHTML = '<option value="">选择状态...</option>';
|
||
|
||
// 添加事件选项 - 考虑两种可能的数据结构
|
||
const events = gameData.events || (gameData.cards && gameData.cards.events);
|
||
if (events && events.length > 0) {
|
||
events.forEach(event => {
|
||
const option = document.createElement('option');
|
||
option.value = event.id;
|
||
option.textContent = event.name;
|
||
eventSelector.appendChild(option);
|
||
});
|
||
} else {
|
||
console.warn('未找到事件数据');
|
||
}
|
||
|
||
// 添加状态选项 - 考虑两种可能的数据结构
|
||
const statuses = gameData.statuses || (gameData.cards && gameData.cards.statuses);
|
||
if (statuses && statuses.length > 0) {
|
||
statuses.forEach(status => {
|
||
const option = document.createElement('option');
|
||
option.value = status.id;
|
||
option.textContent = status.name;
|
||
statusSelector.appendChild(option);
|
||
});
|
||
} else {
|
||
console.warn('未找到状态数据');
|
||
}
|
||
|
||
// 记录一下数据结构以进行调试
|
||
console.log('GameData structure:', gameData);
|
||
}
|
||
|
||
// 添加状态影响指示器
|
||
function addEffectIndicators() {
|
||
// 先移除所有现有指示器
|
||
removeAllEffectIndicators();
|
||
|
||
if (!gameState.currentEffects) return;
|
||
|
||
// 为每个状态添加指示器
|
||
for (const stat in gameState.currentEffects) {
|
||
const value = gameState.currentEffects[stat];
|
||
if (value === 0) continue; // 跳过无影响的属性
|
||
|
||
const iconElement = statusIcons[stat].parentElement;
|
||
|
||
// 创建指示器容器
|
||
const indicator = document.createElement('div');
|
||
indicator.className = 'status-indicator';
|
||
indicator.classList.add(value > 0 ? 'positive-effect' : 'negative-effect');
|
||
|
||
// 根据影响值大小决定显示大圆点还是小圆点
|
||
const absValue = Math.abs(value);
|
||
|
||
if (absValue >= 15) {
|
||
// 大影响,显示大圆点
|
||
const dot = document.createElement('div');
|
||
dot.className = 'indicator-dot large';
|
||
indicator.appendChild(dot);
|
||
} else if (absValue >= 10) {
|
||
// 中等影响,显示一个小圆点
|
||
const dot = document.createElement('div');
|
||
dot.className = 'indicator-dot small';
|
||
indicator.appendChild(dot);
|
||
} else {
|
||
// 小影响,不显示圆点
|
||
// 或者也可以显示一个更小的圆点
|
||
const dot = document.createElement('div');
|
||
dot.className = 'indicator-dot small';
|
||
dot.style.width = '5px';
|
||
dot.style.height = '5px';
|
||
indicator.appendChild(dot);
|
||
}
|
||
|
||
// 添加到状态图标元素
|
||
iconElement.appendChild(indicator);
|
||
}
|
||
}
|
||
|
||
// 移除所有效果指示器
|
||
function removeAllEffectIndicators() {
|
||
document.querySelectorAll('.status-indicator').forEach(indicator => {
|
||
indicator.remove();
|
||
});
|
||
}
|
||
|
||
// 重新构建可用角色池,考虑权重
|
||
function shuffleAvailableCharacters() {
|
||
// 清空可用角色池
|
||
gameState.availableCharacters = [];
|
||
|
||
// 筛选常驻角色
|
||
const residentCharacters = gameState.characters.filter(char => char.type === 'resident');
|
||
|
||
// 根据权重添加角色ID到可用池中
|
||
residentCharacters.forEach(character => {
|
||
const weight = character.weight || 100; // 默认权重为100
|
||
// 根据权重添加多次角色ID,增加被选中概率
|
||
for (let i = 0; i < weight; i++) {
|
||
gameState.availableCharacters.push(character.id);
|
||
}
|
||
});
|
||
|
||
// 打乱角色顺序
|
||
gameState.availableCharacters.sort(() => Math.random() - 0.5);
|
||
}
|
||
|
||
// 显示对话气泡
|
||
function showSpeechBubble(text) {
|
||
speechBubble.textContent = text;
|
||
speechBubble.classList.add('visible');
|
||
}
|
||
|
||
// 隐藏对话气泡
|
||
function hideSpeechBubble() {
|
||
speechBubble.classList.remove('visible');
|
||
}
|
||
|
||
// 开始发牌动画
|
||
function startDealingCards() {
|
||
gameState.isDealing = true;
|
||
let cardsDealt = 0;
|
||
const totalCards = 5;
|
||
|
||
// 发5张牌
|
||
for (let i = 0; i < totalCards; i++) {
|
||
setTimeout(() => {
|
||
const card = document.createElement('div');
|
||
card.className = 'card card-back';
|
||
|
||
// 设置初始位置 - 左上角45度
|
||
card.style.transform = 'translate(-150px, -150px) rotate(-45deg) scale(0.8)';
|
||
card.style.opacity = '0';
|
||
card.style.transition = 'none';
|
||
|
||
// 设置层级,越后面的卡片层级越低
|
||
card.style.zIndex = `${5 - i}`;
|
||
|
||
cardContainer.appendChild(card);
|
||
|
||
// 强制重绘以确保过渡效果正常
|
||
void card.offsetWidth;
|
||
|
||
// 添加过渡属性
|
||
card.style.transition = 'transform 0.4s ease, opacity 0.4s ease';
|
||
|
||
// 设置终点位置 - 所有牌都落在中心位置,但有微小偏移以显示后面还有牌
|
||
setTimeout(() => {
|
||
// 计算每张牌的偏移,让它们有细微重叠
|
||
const offsetX = i * 2; // 每张牌向右偏移2px
|
||
const offsetY = i * 2; // 每张牌向下偏移2px
|
||
const randomAngle = (Math.random() - 0.5) * 2; // 添加微小随机角度
|
||
|
||
card.style.transform = `translate(${offsetX}px, ${offsetY}px) rotate(${randomAngle}deg)`;
|
||
card.style.opacity = '1';
|
||
|
||
// 给静态背景牌添加一个类,方便后续识别
|
||
if (i > 0) {
|
||
card.classList.add('static-background-card');
|
||
}
|
||
|
||
// 计数已发的牌
|
||
cardsDealt++;
|
||
|
||
// 全部发完后,等待一小段时间再翻转第一张牌
|
||
if (cardsDealt === totalCards) {
|
||
setTimeout(() => {
|
||
// 翻转最上面的牌
|
||
const topCard = cardContainer.querySelector('.card-back:not(.static-background-card)');
|
||
if (topCard) {
|
||
// 检查是否有指定的下一个事件
|
||
if (gameState.devMode && gameState.nextEventId) {
|
||
startEvent(gameState.nextEventId);
|
||
gameState.nextEventId = null; // 重置
|
||
} else {
|
||
// 检查是否应该触发随机事件
|
||
const eventToTrigger = checkEventTriggers();
|
||
if (eventToTrigger) {
|
||
startEvent(eventToTrigger);
|
||
} else {
|
||
flipTopCard(topCard);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 结束发牌状态
|
||
gameState.isDealing = false;
|
||
}, 600);
|
||
}
|
||
}, 50);
|
||
}, i * 200); // 每张牌间隔200ms发出
|
||
}
|
||
}
|
||
|
||
// 检查是否应该触发事件
|
||
function checkEventTriggers() {
|
||
if (!gameData || !gameData.events) return null;
|
||
|
||
// 筛选可能触发的事件
|
||
const possibleEvents = gameData.events.filter(event => {
|
||
// 跳过已完成的事件
|
||
if (gameState.completedEvents.includes(event.id)) return false;
|
||
|
||
// 检查触发条件
|
||
if (event.trigger) {
|
||
if (event.trigger.type === 'random') {
|
||
// 随机触发事件 - 按概率判断
|
||
return Math.random() < (event.trigger.probability || 0.1);
|
||
} else if (event.trigger.type === 'condition') {
|
||
// 条件触发事件
|
||
const stat = event.trigger.stat;
|
||
const condition = event.trigger.condition;
|
||
const value = event.trigger.value;
|
||
|
||
if (stat && condition && value !== undefined) {
|
||
const currentValue = gameState.stats[stat];
|
||
|
||
switch (condition) {
|
||
case 'less_than':
|
||
return currentValue < value;
|
||
case 'greater_than':
|
||
return currentValue > value;
|
||
case 'equal':
|
||
return currentValue === value;
|
||
}
|
||
}
|
||
} else if (event.trigger.type === 'status') {
|
||
// 状态触发事件
|
||
const statusId = event.trigger.statusId;
|
||
const condition = event.trigger.condition || 'exists';
|
||
|
||
const hasStatus = gameState.activeStatuses.some(s => s.id === statusId);
|
||
return condition === 'exists' ? hasStatus : !hasStatus;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
});
|
||
|
||
// 如果有可能触发的事件,随机选择一个
|
||
if (possibleEvents.length > 0) {
|
||
const randomIndex = Math.floor(Math.random() * possibleEvents.length);
|
||
return possibleEvents[randomIndex].id;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
// 开始一个事件
|
||
function startEvent(eventId) {
|
||
if (!gameData || !gameData.events) return;
|
||
|
||
// 查找事件数据
|
||
const eventData = gameData.events.find(e => e.id === eventId);
|
||
if (!eventData || !eventData.nodes || eventData.nodes.length === 0) return;
|
||
|
||
// 设置当前事件
|
||
gameState.activeEvent = eventData;
|
||
gameState.eventNode = eventData.nodes[0]; // 从第一个节点开始
|
||
|
||
// 显示第一个节点的卡片
|
||
showEventCard(gameState.eventNode);
|
||
}
|
||
|
||
// 显示事件卡片
|
||
function showEventCard(node) {
|
||
if (!node) return;
|
||
|
||
// 查找角色数据
|
||
const character = gameState.characters.find(c => c.id === node.character);
|
||
if (!character) return;
|
||
|
||
// 清理现有卡片和信息
|
||
const existingFrontCard = document.getElementById('active-card');
|
||
if (existingFrontCard) {
|
||
existingFrontCard.remove();
|
||
}
|
||
|
||
const existingCharInfo = document.querySelector('.character-info');
|
||
if (existingCharInfo) {
|
||
existingCharInfo.remove();
|
||
}
|
||
|
||
// 创建角色信息区域
|
||
const characterInfo = document.createElement('div');
|
||
characterInfo.className = 'character-info';
|
||
characterInfo.innerHTML = `
|
||
<div class="character-name">${character.name}</div>
|
||
<div class="character-title">${character.title}</div>
|
||
`;
|
||
characterInfo.style.opacity = '0';
|
||
characterInfo.style.zIndex = '20';
|
||
|
||
// 检查角色是否有图片路径
|
||
const hasImage = character.avatarPath && character.avatarPath.length > 0;
|
||
const portraitContent = hasImage
|
||
? `<img src="/static/${character.avatarPath}" alt="${character.name}">`
|
||
: `<div class="fallback-avatar">${character.avatar}</div>`;
|
||
|
||
// 创建事件卡片
|
||
const eventCard = document.createElement('div');
|
||
eventCard.className = 'card';
|
||
eventCard.id = 'active-card';
|
||
eventCard.dataset.eventId = gameState.activeEvent.id;
|
||
eventCard.dataset.nodeId = node.id;
|
||
eventCard.style.zIndex = '15';
|
||
|
||
// 设置卡片内容 - 包括选项
|
||
eventCard.innerHTML = `
|
||
<div class="character-portrait">${portraitContent}</div>
|
||
<div class="option-overlay right-option-overlay">${node.options[0].text}</div>
|
||
<div class="option-overlay left-option-overlay">${node.options.length > 1 ? node.options[1].text : node.options[0].text}</div>
|
||
`;
|
||
|
||
// 添加到DOM
|
||
cardContainer.appendChild(characterInfo);
|
||
cardContainer.appendChild(eventCard);
|
||
|
||
// 设置卡片初始样式
|
||
eventCard.style.transform = 'rotateY(-90deg)';
|
||
eventCard.style.opacity = '0';
|
||
eventCard.style.transition = 'none';
|
||
|
||
// 强制重绘
|
||
void eventCard.offsetWidth;
|
||
|
||
// 寻找一张背面牌用于翻转效果
|
||
const topBackCard = cardContainer.querySelector('.card-back:not(.static-background-card)');
|
||
if (topBackCard) {
|
||
// 背面牌开始翻转
|
||
topBackCard.style.transition = 'all 0.4s ease';
|
||
topBackCard.style.transform = 'rotateY(90deg)';
|
||
topBackCard.style.opacity = '0';
|
||
topBackCard.style.zIndex = '5';
|
||
}
|
||
|
||
// 等待背面牌翻转到一半后,开始前面牌的翻转
|
||
setTimeout(() => {
|
||
// 前面卡片开始翻转
|
||
eventCard.style.transition = 'all 0.4s ease';
|
||
eventCard.style.transform = 'rotateY(0deg)';
|
||
eventCard.style.opacity = '1';
|
||
|
||
// 设置为当前活动卡片
|
||
activeCard = eventCard;
|
||
|
||
// 显示对话气泡
|
||
showSpeechBubble(node.text);
|
||
|
||
// 显示角色信息
|
||
setTimeout(() => {
|
||
characterInfo.style.opacity = '1';
|
||
|
||
// 添加拖动事件
|
||
setupCardDrag(eventCard, true); // 标记为事件卡片
|
||
|
||
// 移除原来的背面牌
|
||
if (topBackCard && topBackCard.parentNode) {
|
||
topBackCard.remove();
|
||
}
|
||
|
||
// 添加一张新的背面牌到牌堆顶部
|
||
addNewBackCard();
|
||
}, 400);
|
||
}, 200);
|
||
}
|
||
|
||
// 继续事件处理
|
||
function continueEvent(optionIndex) {
|
||
if (!gameState.activeEvent || !gameState.eventNode) {
|
||
console.error("无法继续事件:事件数据缺失");
|
||
return;
|
||
}
|
||
|
||
// 获取选中的选项
|
||
const options = gameState.eventNode.options;
|
||
if (!options || !options[optionIndex]) {
|
||
console.error("无法继续事件:选项数据缺失");
|
||
return;
|
||
}
|
||
|
||
const option = options[optionIndex];
|
||
|
||
// 应用选项的效果
|
||
if (option.effects) {
|
||
applyEffects(option.effects);
|
||
}
|
||
|
||
// 添加状态
|
||
if (option.add_status) {
|
||
addStatus(option.add_status);
|
||
}
|
||
|
||
// 移除状态
|
||
if (option.remove_status) {
|
||
removeStatus(option.remove_status);
|
||
}
|
||
|
||
// 检查下一个节点
|
||
if (option.next_node && option.next_node !== 'end') {
|
||
// 查找下一个节点
|
||
const nextNode = gameState.activeEvent.nodes.find(n => n.id === option.next_node);
|
||
if (nextNode) {
|
||
// 更新当前节点
|
||
gameState.eventNode = nextNode;
|
||
|
||
// 显示下一个节点的卡片
|
||
showEventCard(nextNode);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 如果没有下一个节点或者是结束节点,结束事件
|
||
endEvent();
|
||
}
|
||
|
||
// 结束当前事件
|
||
function endEvent() {
|
||
// 将事件添加到已完成列表
|
||
if (gameState.activeEvent) {
|
||
gameState.completedEvents.push(gameState.activeEvent.id);
|
||
}
|
||
|
||
// 重置事件状态
|
||
gameState.activeEvent = null;
|
||
gameState.eventNode = null;
|
||
|
||
// 继续游戏流程 - 翻转一张新卡片
|
||
setTimeout(() => {
|
||
if (!gameState.gameOver) {
|
||
flipTopCard();
|
||
}
|
||
}, 500);
|
||
}
|
||
|
||
// 找到flipTopCard函数,修改角色选择部分(以下是完整函数)
|
||
function flipTopCard(topBackCard) {
|
||
// 如果没有可用角色,则重新洗牌
|
||
if (gameState.availableCharacters.length === 0) {
|
||
shuffleAvailableCharacters();
|
||
}
|
||
|
||
// 从可用角色池中随机选择一个角色ID(权重已经在池中体现)
|
||
const randomIndex = Math.floor(Math.random() * gameState.availableCharacters.length);
|
||
const characterId = gameState.availableCharacters[randomIndex];
|
||
|
||
// 从可用池中移除该角色ID
|
||
gameState.availableCharacters.splice(randomIndex, 1);
|
||
|
||
// 获取角色数据
|
||
const character = gameState.characters.find(c => c.id === characterId);
|
||
|
||
if (!character || !character.events || character.events.length === 0) {
|
||
// 如果角色无效或没有事件,则跳过并尝试下一个
|
||
console.warn("角色无效或没有事件:", characterId);
|
||
flipTopCard(topBackCard);
|
||
return;
|
||
}
|
||
|
||
// 从角色的事件中随机选择一个
|
||
const eventIndex = Math.floor(Math.random() * character.events.length);
|
||
const event = character.events[eventIndex];
|
||
|
||
// 保存当前事件和角色及其效果
|
||
gameState.currentEvent = event;
|
||
gameState.currentCharacter = character;
|
||
gameState.currentEffects = {
|
||
loyalty: event.optionA.effects.loyalty,
|
||
chaos: event.optionA.effects.chaos,
|
||
population: event.optionA.effects.population,
|
||
military: event.optionA.effects.military,
|
||
resources: event.optionA.effects.resources
|
||
};
|
||
|
||
// 如果没有传入顶部牌,则找到当前最上面的背面牌
|
||
if (!topBackCard) {
|
||
topBackCard = cardContainer.querySelector('.card-back:not(.static-background-card)');
|
||
if (!topBackCard) return; // 如果没有找到,直接返回
|
||
}
|
||
|
||
// 清理可能存在的旧元素
|
||
const existingFrontCard = document.getElementById('active-card');
|
||
if (existingFrontCard) {
|
||
existingFrontCard.remove();
|
||
}
|
||
|
||
const existingCharInfo = document.querySelector('.character-info');
|
||
if (existingCharInfo) {
|
||
existingCharInfo.remove();
|
||
}
|
||
|
||
// 创建角色信息区域
|
||
const characterInfo = document.createElement('div');
|
||
characterInfo.className = 'character-info';
|
||
characterInfo.innerHTML = `
|
||
<div class="character-name">${character.name}</div>
|
||
<div class="character-title">${character.title}</div>
|
||
`;
|
||
characterInfo.style.opacity = '0';
|
||
characterInfo.style.zIndex = '20';
|
||
|
||
// 检查角色是否有图片路径,如果没有则使用表情图标
|
||
const hasImage = character.avatarPath && character.avatarPath.length > 0;
|
||
const portraitContent = hasImage
|
||
? `<img src="/static/${character.avatarPath}" alt="${character.name}">`
|
||
: `<div class="fallback-avatar">${character.avatar}</div>`;
|
||
|
||
// 创建新卡片 (前面)
|
||
const frontCard = document.createElement('div');
|
||
frontCard.className = 'card';
|
||
frontCard.id = 'active-card';
|
||
frontCard.dataset.characterId = character.id;
|
||
frontCard.dataset.eventId = event.id;
|
||
frontCard.style.zIndex = '15';
|
||
|
||
// 设置卡片内容
|
||
frontCard.innerHTML = `
|
||
<div class="character-portrait">${portraitContent}</div>
|
||
<div class="option-overlay right-option-overlay">${event.optionA.text}</div>
|
||
<div class="option-overlay left-option-overlay">${event.optionB.text}</div>
|
||
`;
|
||
|
||
// 添加到DOM
|
||
cardContainer.appendChild(characterInfo);
|
||
cardContainer.appendChild(frontCard);
|
||
|
||
// 重要:先创建元素但不添加到DOM
|
||
// 设置前面卡片初始样式 - 已经旋转到-90度
|
||
frontCard.style.transform = 'rotateY(-90deg)';
|
||
frontCard.style.opacity = '0';
|
||
frontCard.style.transition = 'none';
|
||
|
||
// 强制重绘以确保初始状态被应用
|
||
void frontCard.offsetWidth;
|
||
|
||
// 开始背面卡片的翻转动画
|
||
topBackCard.style.transition = 'all 0.4s ease';
|
||
topBackCard.style.transform = 'rotateY(90deg)';
|
||
topBackCard.style.opacity = '0';
|
||
|
||
// 等待背面卡片翻转到一半后,开始前面卡片的翻转
|
||
setTimeout(() => {
|
||
// 设置过渡效果并开始翻转
|
||
frontCard.style.transition = 'all 0.4s ease';
|
||
frontCard.style.transform = 'rotateY(0deg)';
|
||
frontCard.style.opacity = '1';
|
||
|
||
// 设置为当前活动卡片
|
||
activeCard = frontCard;
|
||
|
||
// 等动画完成后显示角色信息和添加事件
|
||
setTimeout(() => {
|
||
// 显示角色信息
|
||
characterInfo.style.transition = 'opacity 0.3s ease';
|
||
characterInfo.style.opacity = '1';
|
||
|
||
// 显示对话气泡
|
||
showSpeechBubble(event.text);
|
||
|
||
// 添加拖动事件
|
||
setupCardDrag(frontCard);
|
||
|
||
// 移除原来的背面牌
|
||
if (topBackCard.parentNode) {
|
||
topBackCard.remove();
|
||
}
|
||
|
||
// 添加一张新的背面牌到牌堆顶部
|
||
addNewBackCard();
|
||
}, 400);
|
||
}, 200);
|
||
}
|
||
|
||
// 添加一张新的背面牌到牌堆顶部
|
||
function addNewBackCard() {
|
||
// 查找所有背景牌
|
||
const backgroundCards = document.querySelectorAll('.static-background-card');
|
||
if (backgroundCards.length === 0) return;
|
||
|
||
// 创建一张新的背面牌
|
||
const newBackCard = document.createElement('div');
|
||
newBackCard.className = 'card card-back';
|
||
|
||
// 设置位置和样式,与其他背景牌保持一致的错开效果
|
||
// 从第一张背景牌获取位置参考
|
||
const referenceCard = backgroundCards[0];
|
||
const referenceRect = referenceCard.getBoundingClientRect();
|
||
const containerRect = cardContainer.getBoundingClientRect();
|
||
|
||
// 计算相对位置
|
||
const relativeX = referenceRect.left - containerRect.left;
|
||
const relativeY = referenceRect.top - containerRect.top;
|
||
|
||
// 设置新牌的位置,略微比参考牌更靠前
|
||
newBackCard.style.transform = `translate(${relativeX - 2}px, ${relativeY - 2}px)`;
|
||
newBackCard.style.opacity = '1';
|
||
newBackCard.style.zIndex = '5'; // 比背景牌高,但比内容牌低
|
||
|
||
// 添加到容器
|
||
cardContainer.insertBefore(newBackCard, cardContainer.firstChild);
|
||
}
|
||
|
||
// 替换 setupCardDrag 函数以更清晰地设置卡片类型
|
||
function setupCardDrag(card, isEventCard = false) {
|
||
// 标记卡片类型 - 确保这里明确设置了属性
|
||
if (isEventCard) {
|
||
card.dataset.cardType = 'event';
|
||
} else {
|
||
card.dataset.cardType = 'normal';
|
||
}
|
||
|
||
// 为每张卡单独添加事件
|
||
card.addEventListener('mousedown', function(e) {
|
||
onCardDragStart(e, this);
|
||
});
|
||
|
||
card.addEventListener('touchstart', function(e) {
|
||
onCardDragStart(e, this);
|
||
});
|
||
}
|
||
|
||
// 更新 onCardDragStart 不再需要传递 isEventCard 参数
|
||
function onCardDragStart(e, card) {
|
||
// 如果正在发牌或已经在拖动,则不处理
|
||
if (gameState.isDealing || gameState.isDragging || gameState.gameOver) return;
|
||
|
||
e.preventDefault();
|
||
gameState.isDragging = true;
|
||
|
||
// 记录起始位置
|
||
startX = e.type === 'mousedown' ? e.clientX : e.touches[0].clientX;
|
||
currentX = startX;
|
||
|
||
// 移除过渡效果使拖动更平滑
|
||
activeCard.style.transition = 'none';
|
||
|
||
// 添加全局事件
|
||
document.addEventListener('mousemove', onCardDragMove);
|
||
document.addEventListener('touchmove', onCardDragMove, { passive: false });
|
||
document.addEventListener('mouseup', onCardDragEnd);
|
||
document.addEventListener('touchend', onCardDragEnd);
|
||
}
|
||
|
||
// 卡片拖动中
|
||
function onCardDragMove(e) {
|
||
if (!gameState.isDragging) return;
|
||
|
||
// 阻止触摸事件的默认行为(滚动)
|
||
if (e.type === 'touchmove') {
|
||
e.preventDefault();
|
||
}
|
||
|
||
// 计算水平移动距离
|
||
currentX = e.type === 'mousemove' ? e.clientX : e.touches[0].clientX;
|
||
const deltaX = currentX - startX;
|
||
|
||
// 移动卡片,添加旋转效果
|
||
activeCard.style.transform = `translateX(${deltaX}px) rotate(${deltaX * 0.05}deg)`;
|
||
|
||
// 显示选择选项和效果
|
||
updateChoiceDisplay(deltaX);
|
||
}
|
||
|
||
// 更新选择选项显示和效果指示器
|
||
function updateChoiceDisplay(deltaX) {
|
||
if (!activeCard) return;
|
||
|
||
// 获取右上角和左上角选项区域
|
||
// 注意:左滑显示右上角选项,右滑显示左上角选项
|
||
const rightOption = activeCard.querySelector('.right-option-overlay'); // 左滑-同意(蓝色)
|
||
const leftOption = activeCard.querySelector('.left-option-overlay'); // 右滑-拒绝(红色)
|
||
|
||
if (!rightOption || !leftOption) return;
|
||
|
||
// 根据拖动距离计算透明度
|
||
// 左滑(负值)时显示右上角选项(蓝色)
|
||
// 右滑(正值)时显示左上角选项(红色)
|
||
const rightOptionOpacity = deltaX < 0 ? Math.min(1, Math.abs(deltaX) / 100) : 0;
|
||
const leftOptionOpacity = deltaX > 0 ? Math.min(1, Math.abs(deltaX) / 100) : 0;
|
||
|
||
rightOption.style.opacity = rightOptionOpacity;
|
||
leftOption.style.opacity = leftOptionOpacity;
|
||
|
||
// 根据拖动方向显示相应效果
|
||
// 检查是事件卡片还是普通卡片
|
||
const isEventCard = activeCard.dataset.cardType === 'event';
|
||
const isDeathCard = activeCard.classList.contains('death-card');
|
||
|
||
// 死亡卡片不显示效果
|
||
if (isDeathCard) {
|
||
removeAllEffectIndicators();
|
||
return;
|
||
}
|
||
|
||
if (isEventCard) {
|
||
// 事件卡片 - 检查当前节点选项
|
||
const node = gameState.eventNode;
|
||
if (!node) return;
|
||
|
||
if (deltaX < -50) {
|
||
// 左滑 - 选项0的效果
|
||
if (node.options[0].effects) {
|
||
showEventOptionEffects(node.options[0].effects);
|
||
} else {
|
||
removeAllEffectIndicators();
|
||
}
|
||
} else if (deltaX > 50) {
|
||
// 右滑 - 选项1的效果
|
||
const option = node.options.length > 1 ? node.options[1] : node.options[0];
|
||
if (option.effects) {
|
||
showEventOptionEffects(option.effects);
|
||
} else {
|
||
removeAllEffectIndicators();
|
||
}
|
||
} else {
|
||
// 回到中间,移除效果显示
|
||
removeAllEffectIndicators();
|
||
}
|
||
} else {
|
||
// 普通卡片
|
||
if (deltaX < -50) {
|
||
// 左滑 - 显示选项A的效果(蓝色)
|
||
showOptionEffects('A');
|
||
} else if (deltaX > 50) {
|
||
// 右滑 - 显示选项B的效果(红色)
|
||
showOptionEffects('B');
|
||
} else {
|
||
// 回到中间,移除效果显示
|
||
removeAllEffectIndicators();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 显示事件选项效果
|
||
function showEventOptionEffects(effects) {
|
||
if (!effects) return;
|
||
|
||
// 移除现有指示器
|
||
removeAllEffectIndicators();
|
||
|
||
// 添加新指示器
|
||
for (const stat in effects) {
|
||
const value = effects[stat];
|
||
if (value === 0) continue; // 跳过无影响的属性
|
||
|
||
const iconElement = statusIcons[stat].parentElement;
|
||
|
||
// 创建指示器容器
|
||
const indicator = document.createElement('div');
|
||
indicator.className = 'status-indicator';
|
||
indicator.classList.add(value > 0 ? 'positive-effect' : 'negative-effect');
|
||
indicator.style.opacity = '1'; // 确保可见
|
||
|
||
// 根据影响值大小决定显示大圆点还是小圆点
|
||
const absValue = Math.abs(value);
|
||
|
||
if (absValue >= 15) {
|
||
// 大影响,显示大圆点
|
||
const dot = document.createElement('div');
|
||
dot.className = 'indicator-dot large';
|
||
indicator.appendChild(dot);
|
||
} else if (absValue >= 10) {
|
||
// 中等影响,显示一个小圆点
|
||
const dot = document.createElement('div');
|
||
dot.className = 'indicator-dot small';
|
||
indicator.appendChild(dot);
|
||
} else {
|
||
// 小影响,显示一个更小的圆点
|
||
const dot = document.createElement('div');
|
||
dot.className = 'indicator-dot small';
|
||
dot.style.width = '5px';
|
||
dot.style.height = '5px';
|
||
indicator.appendChild(dot);
|
||
}
|
||
|
||
// 添加到状态图标元素
|
||
iconElement.appendChild(indicator);
|
||
}
|
||
}
|
||
|
||
// 显示选项效果
|
||
function showOptionEffects(option) {
|
||
if (!gameState.currentEvent) return;
|
||
|
||
// 获取效果
|
||
const effects = option === 'A' ?
|
||
gameState.currentEvent.optionA.effects :
|
||
gameState.currentEvent.optionB.effects;
|
||
|
||
// 移除现有指示器
|
||
removeAllEffectIndicators();
|
||
|
||
// 添加新指示器
|
||
for (const stat in effects) {
|
||
const value = effects[stat];
|
||
if (value === 0) continue; // 跳过无影响的属性
|
||
|
||
const iconElement = statusIcons[stat].parentElement;
|
||
|
||
// 创建指示器容器
|
||
const indicator = document.createElement('div');
|
||
indicator.className = 'status-indicator';
|
||
indicator.classList.add(value > 0 ? 'positive-effect' : 'negative-effect');
|
||
indicator.style.opacity = '1'; // 确保可见
|
||
|
||
// 根据影响值大小决定显示大圆点还是小圆点
|
||
const absValue = Math.abs(value);
|
||
|
||
if (absValue >= 15) {
|
||
// 大影响,显示大圆点
|
||
const dot = document.createElement('div');
|
||
dot.className = 'indicator-dot large';
|
||
indicator.appendChild(dot);
|
||
} else if (absValue >= 10) {
|
||
// 中等影响,显示一个小圆点
|
||
const dot = document.createElement('div');
|
||
dot.className = 'indicator-dot small';
|
||
indicator.appendChild(dot);
|
||
} else {
|
||
// 小影响,显示一个更小的圆点
|
||
const dot = document.createElement('div');
|
||
dot.className = 'indicator-dot small';
|
||
dot.style.width = '5px';
|
||
dot.style.height = '5px';
|
||
indicator.appendChild(dot);
|
||
}
|
||
|
||
// 添加到状态图标元素
|
||
iconElement.appendChild(indicator);
|
||
}
|
||
}
|
||
|
||
// 替换 onCardDragEnd 函数
|
||
function onCardDragEnd(e, isEventCard) {
|
||
if (!gameState.isDragging || !activeCard) return;
|
||
|
||
// 计算最终移动距离
|
||
const deltaX = currentX - startX;
|
||
|
||
// 添加过渡效果
|
||
activeCard.style.transition = 'transform 0.5s ease, opacity 0.5s ease';
|
||
|
||
// 从数据集获取实际卡片类型,而不是依赖传递的参数
|
||
const cardType = activeCard.dataset.cardType;
|
||
const isCardEvent = cardType === 'event';
|
||
|
||
// 检查是否为死亡卡片
|
||
const isDeathCard = activeCard.classList.contains('death-card');
|
||
|
||
// 如果是死亡卡片,直接调用swipeDeathCard
|
||
if (isDeathCard) {
|
||
const characterInfo = document.querySelector('.character-info');
|
||
swipeDeathCard(activeCard, characterInfo, activeCard.dataset.callback);
|
||
gameState.isDragging = false;
|
||
return;
|
||
}
|
||
|
||
// 处理正常卡片选择
|
||
if (deltaX < -100) {
|
||
// 左滑 - 选择左侧选项
|
||
if (isCardEvent && gameState.eventNode) {
|
||
selectEventChoice(0); // 事件选项0
|
||
} else if (gameState.currentEvent) {
|
||
selectChoice('left'); // 普通卡片左选项
|
||
} else {
|
||
// 恢复原位
|
||
resetCardPosition();
|
||
}
|
||
} else if (deltaX > 100) {
|
||
// 右滑 - 选择右侧选项
|
||
if (isCardEvent && gameState.eventNode) {
|
||
// 对于事件卡片,检查是否有第二个选项
|
||
const optionIndex = gameState.eventNode.options.length > 1 ? 1 : 0;
|
||
selectEventChoice(optionIndex);
|
||
} else if (gameState.currentEvent) {
|
||
selectChoice('right'); // 普通卡片右选项
|
||
} else {
|
||
// 恢复原位
|
||
resetCardPosition();
|
||
}
|
||
} else {
|
||
// 回到原位
|
||
resetCardPosition();
|
||
}
|
||
|
||
// 移除全局事件监听
|
||
document.removeEventListener('mousemove', onCardDragMove);
|
||
document.removeEventListener('touchmove', onCardDragMove);
|
||
document.removeEventListener('mouseup', onCardDragEnd);
|
||
document.removeEventListener('touchend', onCardDragEnd);
|
||
}
|
||
|
||
// 重置卡片位置
|
||
function resetCardPosition() {
|
||
if (!activeCard) return;
|
||
|
||
activeCard.style.transform = 'translateX(0) rotate(0)';
|
||
|
||
// 隐藏选项覆盖层
|
||
const rightOption = activeCard.querySelector('.right-option-overlay');
|
||
const leftOption = activeCard.querySelector('.left-option-overlay');
|
||
if (rightOption) rightOption.style.opacity = 0;
|
||
if (leftOption) leftOption.style.opacity = 0;
|
||
|
||
// 移除效果指示器
|
||
removeAllEffectIndicators();
|
||
|
||
// 重置拖动状态
|
||
gameState.isDragging = false;
|
||
}
|
||
|
||
// 选择事件选项
|
||
function selectEventChoice(optionIndex) {
|
||
if (!gameState.activeEvent || !gameState.eventNode) {
|
||
console.error('当前事件或节点数据丢失');
|
||
resetCardPosition();
|
||
return;
|
||
}
|
||
|
||
// 获取角色信息元素
|
||
const characterInfo = document.querySelector('.character-info');
|
||
|
||
// 选择飞出方向,左滑(选项0)向左,右滑(选项1)向右
|
||
if (optionIndex === 0) {
|
||
activeCard.style.transform = 'translateX(-1000px) rotate(-30deg)';
|
||
} else {
|
||
activeCard.style.transform = 'translateX(1000px) rotate(30deg)';
|
||
}
|
||
|
||
// 淡出并移除
|
||
activeCard.style.opacity = '0';
|
||
|
||
// 同时淡出角色信息和对话气泡
|
||
if (characterInfo) {
|
||
characterInfo.style.opacity = '0';
|
||
}
|
||
hideSpeechBubble();
|
||
|
||
// 移除效果指示器
|
||
removeAllEffectIndicators();
|
||
|
||
// 重置拖动状态
|
||
gameState.isDragging = false;
|
||
|
||
// 延迟后处理选项效果和显示下一张卡片
|
||
setTimeout(() => {
|
||
if (activeCard && activeCard.parentNode) {
|
||
activeCard.remove();
|
||
activeCard = null;
|
||
}
|
||
|
||
if (characterInfo && characterInfo.parentNode) {
|
||
characterInfo.remove();
|
||
}
|
||
|
||
// 继续事件流程
|
||
continueEvent(optionIndex);
|
||
}, 500);
|
||
}
|
||
|
||
// 选择一个选项
|
||
function selectChoice(direction) {
|
||
if (!gameState.currentEvent || !gameState.currentCharacter) {
|
||
console.error('当前事件或角色数据丢失');
|
||
resetCardPosition();
|
||
return;
|
||
}
|
||
|
||
// 应用效果
|
||
const effects = direction === 'left' ?
|
||
gameState.currentEvent.optionA.effects :
|
||
gameState.currentEvent.optionB.effects;
|
||
applyEffects(effects);
|
||
|
||
// 获取角色信息元素
|
||
const characterInfo = document.querySelector('.character-info');
|
||
|
||
// 向左或向右飞出
|
||
if (direction === 'left') {
|
||
activeCard.style.transform = 'translateX(-1000px) rotate(-30deg)';
|
||
} else {
|
||
activeCard.style.transform = 'translateX(1000px) rotate(30deg)';
|
||
}
|
||
|
||
// 淡出并移除
|
||
activeCard.style.opacity = '0';
|
||
|
||
// 同时淡出角色信息和对话气泡
|
||
if (characterInfo) {
|
||
characterInfo.style.opacity = '0';
|
||
}
|
||
hideSpeechBubble();
|
||
|
||
// 移除效果指示器
|
||
removeAllEffectIndicators();
|
||
|
||
// 重置拖动状态
|
||
gameState.isDragging = false;
|
||
|
||
// 延迟后显示下一张卡片
|
||
setTimeout(() => {
|
||
if (activeCard && activeCard.parentNode) {
|
||
activeCard.remove();
|
||
activeCard = null;
|
||
}
|
||
|
||
if (characterInfo && characterInfo.parentNode) {
|
||
characterInfo.remove();
|
||
}
|
||
|
||
// 重置当前事件和角色
|
||
gameState.currentEvent = null;
|
||
gameState.currentCharacter = null;
|
||
|
||
// 检查游戏是否结束
|
||
if (!gameState.gameOver) {
|
||
// 应用状态效果
|
||
applyStatusEffects();
|
||
|
||
// 更新状态持续时间
|
||
updateStatuses();
|
||
|
||
// 检查是否应该触发事件
|
||
if (gameState.devMode && gameState.nextEventId) {
|
||
startEvent(gameState.nextEventId);
|
||
gameState.nextEventId = null;
|
||
} else {
|
||
const eventToTrigger = checkEventTriggers();
|
||
if (eventToTrigger) {
|
||
startEvent(eventToTrigger);
|
||
} else {
|
||
// 翻转牌堆顶部的新牌
|
||
flipTopCard();
|
||
}
|
||
}
|
||
}
|
||
}, 500);
|
||
}
|
||
|
||
// 应用效果
|
||
function applyEffects(effects) {
|
||
// 更新统计数据
|
||
for (const [stat, value] of Object.entries(effects)) {
|
||
gameState.stats[stat] = Math.max(0, Math.min(100, gameState.stats[stat] + value));
|
||
}
|
||
|
||
// 增加年份和统治时间
|
||
gameState.year++;
|
||
gameState.reignTime++;
|
||
|
||
// 更新UI
|
||
updateUI();
|
||
|
||
// 检查游戏结束条件
|
||
checkGameOver();
|
||
|
||
// 每10年保存一次游戏状态
|
||
if (gameState.reignTime % 10 === 0) {
|
||
saveGameState();
|
||
}
|
||
}
|
||
|
||
// 应用状态效果
|
||
function applyStatusEffects() {
|
||
if (gameState.activeStatuses.length === 0) return;
|
||
|
||
// 记录效果总和
|
||
const totalEffects = {
|
||
loyalty: 0,
|
||
chaos: 0,
|
||
population: 0,
|
||
military: 0,
|
||
resources: 0
|
||
};
|
||
|
||
// 收集所有状态的每回合效果
|
||
gameState.activeStatuses.forEach(status => {
|
||
if (status.effects_per_turn) {
|
||
for (const [stat, value] of Object.entries(status.effects_per_turn)) {
|
||
totalEffects[stat] += value;
|
||
}
|
||
}
|
||
});
|
||
|
||
// 应用总效果
|
||
for (const [stat, value] of Object.entries(totalEffects)) {
|
||
if (value !== 0) {
|
||
gameState.stats[stat] = Math.max(0, Math.min(100, gameState.stats[stat] + value));
|
||
}
|
||
}
|
||
|
||
// 更新UI
|
||
updateUI();
|
||
|
||
// 检查游戏结束条件
|
||
checkGameOver();
|
||
}
|
||
|
||
// 更新状态持续时间
|
||
function updateStatuses() {
|
||
// 更新状态持续时间并检查是否结束
|
||
gameState.activeStatuses = gameState.activeStatuses.filter(status => {
|
||
// 减少持续时间
|
||
if (status.remainingDuration > 0) {
|
||
status.remainingDuration--;
|
||
}
|
||
|
||
// 检查是否应该移除状态
|
||
if (status.remainingDuration === 0 && status.duration > 0) {
|
||
// 状态持续时间结束
|
||
return false;
|
||
}
|
||
|
||
// 特定结束条件检查
|
||
if (status.one_time_save && status.save_trigger) {
|
||
// 检查是否已经触发了一次性救援
|
||
if (status.triggered) {
|
||
return false;
|
||
}
|
||
|
||
// 检查触发条件
|
||
if (status.save_trigger === 'military_zero' && gameState.stats.military <= 10) {
|
||
// 军事力量接近0,触发救援
|
||
status.triggered = true;
|
||
|
||
// 应用救援效果
|
||
gameState.stats.military += 30;
|
||
|
||
// 显示救援消息
|
||
showSpeechBubble("阿斯塔特战士如约而至,解除了您的危机!");
|
||
|
||
// 状态仍然保留,直到下一回合被移除
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
});
|
||
|
||
// 更新状态显示
|
||
updateStatusDisplay();
|
||
}
|
||
|
||
// 添加状态
|
||
function addStatus(statusId) {
|
||
if (!gameData || !gameData.statuses) return;
|
||
|
||
// 查找状态数据
|
||
const statusData = gameData.statuses.find(s => s.id === statusId);
|
||
if (!statusData) return;
|
||
|
||
// 检查状态是否已经存在
|
||
const existingIndex = gameState.activeStatuses.findIndex(s => s.id === statusId);
|
||
if (existingIndex >= 0) {
|
||
// 已存在,重置持续时间
|
||
if (statusData.duration > 0) {
|
||
gameState.activeStatuses[existingIndex].remainingDuration = statusData.duration;
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 创建新状态对象
|
||
const newStatus = {
|
||
id: statusData.id,
|
||
name: statusData.name,
|
||
icon: statusData.icon,
|
||
description: statusData.description,
|
||
effects_per_turn: statusData.effects_per_turn,
|
||
one_time_save: statusData.one_time_save,
|
||
save_trigger: statusData.save_trigger,
|
||
startYear: gameState.year,
|
||
triggered: false
|
||
};
|
||
|
||
// 设置持续时间
|
||
if (statusData.duration > 0) {
|
||
newStatus.remainingDuration = statusData.duration;
|
||
newStatus.duration = statusData.duration;
|
||
}
|
||
|
||
// 添加到活跃状态列表
|
||
gameState.activeStatuses.push(newStatus);
|
||
|
||
// 更新状态显示
|
||
updateStatusDisplay();
|
||
}
|
||
|
||
// 移除状态
|
||
function removeStatus(statusId) {
|
||
// 从活跃状态列表中移除指定ID的状态
|
||
gameState.activeStatuses = gameState.activeStatuses.filter(s => s.id !== statusId);
|
||
|
||
// 更新状态显示
|
||
updateStatusDisplay();
|
||
}
|
||
|
||
// 更新状态显示
|
||
function updateStatusDisplay() {
|
||
// 清空状态图标容器
|
||
activeStatusesContainer.innerHTML = '';
|
||
|
||
// 添加所有活跃状态的图标
|
||
gameState.activeStatuses.forEach(status => {
|
||
const statusItem = document.createElement('div');
|
||
statusItem.className = 'status-item';
|
||
statusItem.textContent = status.icon;
|
||
statusItem.dataset.statusId = status.id;
|
||
statusItem.dataset.tooltip = status.name;
|
||
|
||
// 如果是一次性救援状态且满足触发条件,添加警告效果
|
||
if (status.one_time_save && status.save_trigger === 'military_zero' && gameState.stats.military <= 15) {
|
||
statusItem.classList.add('warning');
|
||
}
|
||
|
||
// 点击显示详情
|
||
statusItem.addEventListener('click', () => {
|
||
showStatusDetails(status);
|
||
});
|
||
|
||
// 添加到容器
|
||
activeStatusesContainer.appendChild(statusItem);
|
||
});
|
||
}
|
||
|
||
// 显示状态详情
|
||
function showStatusDetails(status) {
|
||
// 设置详情对话框内容
|
||
document.getElementById('status-details-icon').textContent = status.icon;
|
||
document.getElementById('status-details-name').textContent = status.name;
|
||
document.getElementById('status-details-description').textContent = status.detailed_description || status.description;
|
||
|
||
// 显示效果
|
||
const effectsContainer = document.getElementById('status-details-effects');
|
||
effectsContainer.innerHTML = '';
|
||
|
||
if (status.effects_per_turn) {
|
||
const effectsTitle = document.createElement('div');
|
||
effectsTitle.textContent = '每回合效果:';
|
||
effectsTitle.style.marginBottom = '8px';
|
||
effectsContainer.appendChild(effectsTitle);
|
||
|
||
for (const [stat, value] of Object.entries(status.effects_per_turn)) {
|
||
if (value === 0) continue;
|
||
|
||
const effectItem = document.createElement('div');
|
||
effectItem.className = 'status-effect-item';
|
||
|
||
let statName = '';
|
||
let statIcon = '';
|
||
|
||
switch (stat) {
|
||
case 'loyalty':
|
||
statName = '帝国忠诚度';
|
||
statIcon = '🦅';
|
||
break;
|
||
case 'chaos':
|
||
statName = '混沌侵染';
|
||
statIcon = '🌀';
|
||
break;
|
||
case 'population':
|
||
statName = '民众控制';
|
||
statIcon = '👥';
|
||
break;
|
||
case 'military':
|
||
statName = '军事力量';
|
||
statIcon = '🔫';
|
||
break;
|
||
case 'resources':
|
||
statName = '资源储备';
|
||
statIcon = '⚙️';
|
||
break;
|
||
}
|
||
|
||
effectItem.innerHTML = `
|
||
<span class="icon">${statIcon}</span>
|
||
<span class="${value > 0 ? 'effect-positive' : 'effect-negative'}">${statName} ${value > 0 ? '+' : ''}${value}</span>
|
||
`;
|
||
|
||
effectsContainer.appendChild(effectItem);
|
||
}
|
||
} else if (status.one_time_save) {
|
||
const effectsInfo = document.createElement('div');
|
||
effectsInfo.textContent = '当军事力量接近为0时,将提供一次性救援。';
|
||
effectsContainer.appendChild(effectsInfo);
|
||
} else {
|
||
const noEffectsInfo = document.createElement('div');
|
||
noEffectsInfo.textContent = '此状态没有直接的数值效果。';
|
||
effectsContainer.appendChild(noEffectsInfo);
|
||
}
|
||
|
||
// 显示持续时间
|
||
const durationElement = document.getElementById('status-details-duration');
|
||
if (status.duration) {
|
||
durationElement.textContent = `持续时间: ${status.remainingDuration}/${status.duration} 年`;
|
||
} else if (status.one_time_save) {
|
||
durationElement.textContent = '持续到触发一次救援';
|
||
} else {
|
||
durationElement.textContent = '持续时间: 无限期';
|
||
}
|
||
|
||
// 显示对话框
|
||
statusDetailsDialog.classList.add('show');
|
||
}
|
||
|
||
// 关闭状态详情对话框
|
||
closeStatusDetailsButton.addEventListener('click', () => {
|
||
statusDetailsDialog.classList.remove('show');
|
||
});
|
||
|
||
// 开发者工具按钮点击
|
||
devToolsButton.addEventListener('click', () => {
|
||
devToolsDialog.classList.add('show');
|
||
});
|
||
|
||
// 关闭开发者工具对话框
|
||
closeDevToolsButton.addEventListener('click', () => {
|
||
devToolsDialog.classList.remove('show');
|
||
});
|
||
|
||
// 触发事件按钮点击
|
||
triggerEventButton.addEventListener('click', () => {
|
||
const selectedEventId = eventSelector.value;
|
||
if (!selectedEventId) return;
|
||
|
||
// 设置下一个要触发的事件
|
||
gameState.nextEventId = selectedEventId;
|
||
|
||
// 关闭对话框
|
||
devToolsDialog.classList.remove('show');
|
||
|
||
// 如果当前有活动卡片,则移除它
|
||
if (activeCard) {
|
||
// 获取角色信息元素
|
||
const characterInfo = document.querySelector('.character-info');
|
||
|
||
// 淡出并移除当前卡片
|
||
activeCard.style.transition = 'all 0.3s ease';
|
||
activeCard.style.opacity = '0';
|
||
activeCard.style.transform = 'scale(0.8)';
|
||
|
||
if (characterInfo) {
|
||
characterInfo.style.opacity = '0';
|
||
}
|
||
|
||
hideSpeechBubble();
|
||
|
||
// 延迟后移除元素并开始新事件
|
||
setTimeout(() => {
|
||
if (activeCard && activeCard.parentNode) {
|
||
activeCard.remove();
|
||
activeCard = null;
|
||
}
|
||
|
||
if (characterInfo && characterInfo.parentNode) {
|
||
characterInfo.remove();
|
||
}
|
||
|
||
// 立即开始事件
|
||
startEvent(selectedEventId);
|
||
}, 300);
|
||
} else {
|
||
// 如果没有活动卡片,寻找一张背面牌进行翻转
|
||
const topBackCard = cardContainer.querySelector('.card-back:not(.static-background-card)');
|
||
if (topBackCard) {
|
||
startEvent(selectedEventId);
|
||
}
|
||
}
|
||
});
|
||
|
||
// 添加状态按钮点击
|
||
addStatusButton.addEventListener('click', () => {
|
||
const selectedStatusId = statusSelector.value;
|
||
if (!selectedStatusId) return;
|
||
|
||
// 添加状态
|
||
addStatus(selectedStatusId);
|
||
|
||
// 关闭对话框
|
||
devToolsDialog.classList.remove('show');
|
||
});
|
||
|
||
// 移除状态按钮点击
|
||
removeStatusButton.addEventListener('click', () => {
|
||
const selectedStatusId = statusSelector.value;
|
||
if (!selectedStatusId) return;
|
||
|
||
// 移除状态
|
||
removeStatus(selectedStatusId);
|
||
|
||
// 关闭对话框
|
||
devToolsDialog.classList.remove('show');
|
||
});
|
||
|
||
// 设置属性值按钮点击
|
||
setStatButton.addEventListener('click', () => {
|
||
const selectedStat = statSelector.value;
|
||
const value = parseInt(statValueInput.value);
|
||
|
||
if (!selectedStat || isNaN(value) || value < 0 || value > 100) return;
|
||
|
||
// 设置属性值
|
||
gameState.stats[selectedStat] = value;
|
||
|
||
// 更新UI
|
||
updateUI();
|
||
|
||
// 检查游戏结束条件
|
||
checkGameOver();
|
||
|
||
// 关闭对话框
|
||
devToolsDialog.classList.remove('show');
|
||
});
|
||
|
||
// 更新UI
|
||
function updateUI() {
|
||
// 更新年份和统治时间
|
||
reignYearsElement.textContent = gameState.reignTime;
|
||
currentYearElement.textContent = gameState.year.toLocaleString();
|
||
|
||
// 更新状态条
|
||
for (const [stat, bar] of Object.entries(statusBars)) {
|
||
bar.style.width = `${gameState.stats[stat]}%`;
|
||
|
||
// 根据状态值改变颜色
|
||
if (gameState.stats[stat] < 20) {
|
||
bar.style.opacity = '0.7'; // 危险状态时降低透明度
|
||
} else if (gameState.stats[stat] > 80) {
|
||
bar.style.opacity = '0.7'; // 危险状态时降低透明度
|
||
} else {
|
||
bar.style.opacity = '1';
|
||
}
|
||
}
|
||
|
||
// 更新状态图标
|
||
updateStatusDisplay();
|
||
}
|
||
|
||
// 检查游戏结束条件
|
||
function checkGameOver() {
|
||
// 检查各项指标是否达到极值
|
||
const endTypes = {
|
||
loyalty: {
|
||
low: "loyalty_low",
|
||
high: "loyalty_high"
|
||
},
|
||
chaos: {
|
||
low: "chaos_low",
|
||
high: "chaos_high"
|
||
},
|
||
population: {
|
||
low: "population_low",
|
||
high: "population_high"
|
||
},
|
||
military: {
|
||
low: "military_low",
|
||
high: "military_high"
|
||
},
|
||
resources: {
|
||
low: "resources_low",
|
||
high: "resources_high"
|
||
}
|
||
};
|
||
|
||
// 检查是否有阿斯塔特的守护承诺
|
||
const hasAstartesProtection = gameState.activeStatuses.some(s => s.id === 'astartes_protection' && !s.triggered);
|
||
|
||
// 检查每个指标
|
||
for (const [stat, types] of Object.entries(endTypes)) {
|
||
const value = gameState.stats[stat];
|
||
|
||
// 特殊处理军事力量属性,如果有阿斯塔特保护且未触发,不因为军事力量低结束游戏
|
||
if (stat === 'military' && value <= 10 && hasAstartesProtection) {
|
||
// 触发阿斯塔特保护效果,而不是结束游戏
|
||
let astartes = gameState.activeStatuses.find(s => s.id === 'astartes_protection');
|
||
if (astartes) {
|
||
astartes.triggered = true;
|
||
gameState.stats.military += 30;
|
||
updateUI();
|
||
showSpeechBubble("阿斯塔特战士如约而至,解除了您的危机!");
|
||
setTimeout(() => {
|
||
hideSpeechBubble();
|
||
}, 3000);
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if (value <= 1) {
|
||
showDeathScenario(types.low);
|
||
return;
|
||
} else if (value >= 99) {
|
||
showDeathScenario(types.high);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 新增:显示死亡场景
|
||
function showDeathScenario(deathType) {
|
||
if (gameState.gameOver) return; // 防止多次触发
|
||
|
||
// 标记游戏已结束,但不立即显示游戏结束对话框
|
||
gameState.gameOver = true;
|
||
|
||
console.log(`尝试获取死亡场景: /api/death_scenario/${deathType}`);
|
||
|
||
// 从服务器获取随机死亡场景
|
||
fetch(`/api/death_scenario/${deathType}`)
|
||
.then(response => {
|
||
console.log('API响应状态:', response.status);
|
||
if (!response.ok) {
|
||
throw new Error(`API请求失败: ${response.status}`);
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(scenario => {
|
||
console.log('成功获取死亡场景:', scenario);
|
||
|
||
// 保存死亡原因,后面显示游戏结束对话框时使用
|
||
gameState.gameOverReason = scenario.name;
|
||
|
||
// 显示第一张死亡卡片
|
||
showDeathCard(scenario.first_card, () => {
|
||
// 第一张卡片结束后,显示第二张
|
||
showDeathCard(scenario.second_card, () => {
|
||
// 第二张卡片结束后,显示游戏结束对话框
|
||
finalizeGameOver();
|
||
});
|
||
});
|
||
})
|
||
.catch(error => {
|
||
console.error('获取死亡场景失败:', error);
|
||
endGameWithMessage('游戏结束'); // 如果获取失败,使用简单的结束信息
|
||
});
|
||
}
|
||
|
||
// 显示死亡卡片
|
||
function showDeathCard(cardData, callback) {
|
||
// 获取角色数据
|
||
const character = cardData.character;
|
||
|
||
// 清理现有卡片和信息
|
||
const existingFrontCard = document.getElementById('active-card');
|
||
if (existingFrontCard) {
|
||
existingFrontCard.remove();
|
||
}
|
||
|
||
const existingCharInfo = document.querySelector('.character-info');
|
||
if (existingCharInfo) {
|
||
existingCharInfo.remove();
|
||
}
|
||
|
||
// 创建角色信息区域
|
||
const characterInfo = document.createElement('div');
|
||
characterInfo.className = 'character-info';
|
||
characterInfo.innerHTML = `
|
||
<div class="character-name">${character.name}</div>
|
||
<div class="character-title">${character.title}</div>
|
||
`;
|
||
characterInfo.style.opacity = '0';
|
||
characterInfo.style.zIndex = '20';
|
||
|
||
// 检查角色是否有图片路径
|
||
const hasImage = character.avatar_path && character.avatar_path.length > 0;
|
||
const portraitContent = hasImage
|
||
? `<img src="/static/${character.avatar_path}" alt="${character.name}" onerror="this.parentNode.innerHTML='<div class=\\'fallback-avatar\\'>${character.avatar}</div>'">`
|
||
: `<div class="fallback-avatar">${character.avatar}</div>`;
|
||
|
||
// 创建死亡卡片
|
||
const deathCard = document.createElement('div');
|
||
deathCard.className = 'card death-card';
|
||
deathCard.id = 'active-card';
|
||
deathCard.style.zIndex = '15';
|
||
|
||
// 存储回调函数,供后续使用
|
||
deathCard.dataset.callback = "death_callback_" + Math.random();
|
||
window[deathCard.dataset.callback] = callback;
|
||
|
||
// 设置卡片内容 - 包括选项
|
||
deathCard.innerHTML = `
|
||
<div class="character-portrait death-portrait">${portraitContent}</div>
|
||
<div class="option-overlay right-option-overlay">${cardData.options[0].text}</div>
|
||
<div class="option-overlay left-option-overlay">${cardData.options[1] ? cardData.options[1].text : cardData.options[0].text}</div>
|
||
`;
|
||
|
||
// 添加到DOM
|
||
cardContainer.appendChild(characterInfo);
|
||
cardContainer.appendChild(deathCard);
|
||
|
||
// 设置卡片初始样式
|
||
deathCard.style.transform = 'rotateY(-90deg)';
|
||
deathCard.style.opacity = '0';
|
||
deathCard.style.transition = 'none';
|
||
|
||
// 强制重绘
|
||
void deathCard.offsetWidth;
|
||
|
||
// 寻找一张背面牌用于翻转效果
|
||
const topBackCard = cardContainer.querySelector('.card-back:not(.static-background-card)');
|
||
if (topBackCard) {
|
||
// 背面牌开始翻转
|
||
topBackCard.style.transition = 'all 0.4s ease';
|
||
topBackCard.style.transform = 'rotateY(90deg)';
|
||
topBackCard.style.opacity = '0';
|
||
topBackCard.style.zIndex = '5';
|
||
}
|
||
|
||
// 等待背面牌翻转到一半后,开始前面牌的翻转
|
||
setTimeout(() => {
|
||
// 前面卡片开始翻转
|
||
deathCard.style.transition = 'all 0.4s ease';
|
||
deathCard.style.transform = 'rotateY(0deg)';
|
||
deathCard.style.opacity = '1';
|
||
|
||
// 设置为当前活动卡片
|
||
activeCard = deathCard;
|
||
|
||
// 显示对话气泡
|
||
showSpeechBubble(cardData.text);
|
||
|
||
// 显示角色信息
|
||
setTimeout(() => {
|
||
characterInfo.style.opacity = '1';
|
||
|
||
// 如果第二张卡片没有选项文本,自动划过
|
||
if (cardData.options[0].text === '' && (cardData.options.length < 2 || cardData.options[1].text === '')) {
|
||
// 延迟自动划过,给玩家时间阅读卡片
|
||
setTimeout(() => {
|
||
swipeDeathCard(deathCard, characterInfo, callback);
|
||
}, 3000);
|
||
} else {
|
||
// 添加卡片点击或拖动事件
|
||
setupDeathCardInteraction(deathCard, characterInfo, callback);
|
||
}
|
||
|
||
// 移除原来的背面牌
|
||
if (topBackCard && topBackCard.parentNode) {
|
||
topBackCard.remove();
|
||
}
|
||
}, 400);
|
||
}, 200);
|
||
}
|
||
|
||
// 设置死亡卡片交互
|
||
function setupDeathCardInteraction(card, charInfo, callback) {
|
||
// 点击卡片触发划过
|
||
card.addEventListener('click', () => {
|
||
swipeDeathCard(card, charInfo, callback);
|
||
});
|
||
|
||
// 也可以使用拖动
|
||
setupCardDrag(card);
|
||
}
|
||
|
||
// 划过死亡卡片
|
||
function swipeDeathCard(card, charInfo, callback) {
|
||
// 向右划出
|
||
card.style.transition = 'all 0.5s ease';
|
||
card.style.transform = 'translateX(1000px) rotate(30deg)';
|
||
card.style.opacity = '0';
|
||
|
||
// 隐藏角色信息和对话气泡
|
||
if (charInfo) {
|
||
charInfo.style.opacity = '0';
|
||
}
|
||
hideSpeechBubble();
|
||
|
||
// 等待动画完成后执行回调
|
||
setTimeout(() => {
|
||
if (card && card.parentNode) {
|
||
card.remove();
|
||
}
|
||
|
||
if (charInfo && charInfo.parentNode) {
|
||
charInfo.remove();
|
||
}
|
||
|
||
// 执行回调
|
||
if (callback && typeof callback === 'function') {
|
||
callback();
|
||
} else if (card.dataset.callback && window[card.dataset.callback]) {
|
||
window[card.dataset.callback]();
|
||
// 清理回调引用
|
||
delete window[card.dataset.callback];
|
||
}
|
||
}, 500);
|
||
}
|
||
|
||
// 使用特定消息结束游戏
|
||
function endGameWithMessage(message) {
|
||
gameState.gameOver = true;
|
||
gameState.gameOverReason = message;
|
||
finalizeGameOver();
|
||
}
|
||
|
||
// 完成游戏结束流程
|
||
function finalizeGameOver() {
|
||
// 更新高分
|
||
updateHighScore();
|
||
|
||
// 显示游戏结束对话框
|
||
gameOverReason.textContent = gameState.gameOverReason;
|
||
finalYears.textContent = gameState.reignTime;
|
||
gameOverDialog.classList.add('show');
|
||
}
|
||
|
||
// 更新高分
|
||
function updateHighScore() {
|
||
fetch('/api/update_score', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
score: gameState.reignTime,
|
||
year: gameState.year // 发送当前年份以便服务器保存
|
||
})
|
||
})
|
||
.catch(error => console.error('更新分数失败:', error));
|
||
}
|
||
|
||
// 保存游戏状态
|
||
function saveGameState() {
|
||
// 不保存已结束的游戏
|
||
if (gameState.gameOver) return;
|
||
|
||
// 创建要保存的游戏状态副本
|
||
const gameStateCopy = {
|
||
governor: gameState.governor,
|
||
reignTime: gameState.reignTime,
|
||
year: gameState.year, // 保存当前年份
|
||
stats: { ...gameState.stats },
|
||
statusEffects: [...gameState.statusEffects],
|
||
activeStatuses: [...gameState.activeStatuses],
|
||
completedEvents: [...gameState.completedEvents],
|
||
// 不保存临时状态
|
||
gameOver: false,
|
||
gameOverReason: ''
|
||
// 不要在这里设置achievements字段,避免覆盖服务器上已有的成就
|
||
};
|
||
|
||
console.log("保存游戏状态...");
|
||
|
||
fetch('/api/save_game', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(gameStateCopy)
|
||
})
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('保存游戏状态失败: ' + response.status);
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
console.log("游戏状态保存成功");
|
||
})
|
||
.catch(error => {
|
||
console.error('保存游戏状态失败:', error);
|
||
});
|
||
}
|
||
|
||
// 重启游戏按钮事件
|
||
restartButton.addEventListener('click', () => {
|
||
// 隐藏游戏结束对话框
|
||
gameOverDialog.classList.remove('show');
|
||
|
||
// 重新初始化游戏
|
||
setTimeout(initGame, 500);
|
||
});
|
||
|
||
// 在游戏加载时初始化
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
// 公告横幅关闭按钮
|
||
const announcementBanner = document.querySelector('.announcement-banner');
|
||
const closeAnnouncementBtn = document.querySelector('.announcement-close');
|
||
|
||
if (closeAnnouncementBtn && announcementBanner) {
|
||
closeAnnouncementBtn.addEventListener('click', () => {
|
||
announcementBanner.style.display = 'none';
|
||
});
|
||
}
|
||
|
||
// 原有的初始化代码
|
||
loadGameData();
|
||
});
|
||
|
||
// 当窗口关闭或刷新时保存游戏状态
|
||
window.addEventListener('beforeunload', () => {
|
||
if (!gameState.gameOver) {
|
||
saveGameState();
|
||
}
|
||
}); |