2026-01-08 21:14:46 +08:00
|
|
|
|
// 英语学习网页主脚本
|
|
|
|
|
|
|
|
|
|
|
|
// 游戏状态
|
|
|
|
|
|
let gameState = {
|
|
|
|
|
|
currentSceneIndex: 0,
|
|
|
|
|
|
currentDialogIndex: 0,
|
|
|
|
|
|
score: 0,
|
|
|
|
|
|
totalQuestions: 0,
|
|
|
|
|
|
correctAnswers: 0
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// DOM元素
|
|
|
|
|
|
let elements;
|
|
|
|
|
|
|
|
|
|
|
|
// 在DOM加载完成后获取DOM元素
|
|
|
|
|
|
function getDomElements() {
|
|
|
|
|
|
elements = {
|
|
|
|
|
|
scoreElement: document.getElementById('score'),
|
|
|
|
|
|
progressElement: document.getElementById('progress'),
|
|
|
|
|
|
sceneTitle: document.getElementById('scene-title'),
|
|
|
|
|
|
sceneDescription: document.getElementById('scene-description'),
|
|
|
|
|
|
sceneImage: document.getElementById('scene-image'),
|
|
|
|
|
|
conversation: document.getElementById('conversation'),
|
|
|
|
|
|
questionArea: document.getElementById('question-area'),
|
|
|
|
|
|
questionText: document.getElementById('question-text'),
|
|
|
|
|
|
options: document.querySelectorAll('.option'),
|
|
|
|
|
|
feedback: document.getElementById('feedback'),
|
|
|
|
|
|
feedbackText: document.getElementById('feedback-text'),
|
|
|
|
|
|
startButton: document.getElementById('start-button'),
|
|
|
|
|
|
resetButton: document.getElementById('reset-button'),
|
|
|
|
|
|
nextSceneButton: document.getElementById('next-scene-button')
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化游戏
|
|
|
|
|
|
function initGame() {
|
|
|
|
|
|
resetGameState();
|
|
|
|
|
|
loadScene(gameState.currentSceneIndex);
|
|
|
|
|
|
updateProgress();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置游戏状态
|
|
|
|
|
|
function resetGameState() {
|
|
|
|
|
|
gameState = {
|
|
|
|
|
|
currentSceneIndex: 0,
|
|
|
|
|
|
currentDialogIndex: 0,
|
|
|
|
|
|
score: 0,
|
|
|
|
|
|
totalQuestions: 0,
|
|
|
|
|
|
correctAnswers: 0
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载场景
|
|
|
|
|
|
function loadScene(sceneIndex) {
|
|
|
|
|
|
if (sceneIndex >= englishData.scenes.length) {
|
|
|
|
|
|
showGameComplete();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const scene = englishData.scenes[sceneIndex];
|
|
|
|
|
|
|
|
|
|
|
|
// 更新场景信息
|
|
|
|
|
|
elements.sceneTitle.textContent = scene.title;
|
|
|
|
|
|
elements.sceneDescription.textContent = scene.description;
|
|
|
|
|
|
|
|
|
|
|
|
// 加载场景图片
|
|
|
|
|
|
// 简化的图片加载逻辑:优先使用场景ID作为图片名称
|
|
|
|
|
|
let imagePath = scene.image;
|
|
|
|
|
|
|
|
|
|
|
|
// 确保图片元素存在
|
|
|
|
|
|
if (!elements.sceneImage) {
|
|
|
|
|
|
console.error('场景图片元素不存在');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置图片状态
|
|
|
|
|
|
elements.sceneImage.src = '';
|
|
|
|
|
|
elements.sceneImage.alt = scene.title + '场景插图';
|
|
|
|
|
|
elements.sceneImage.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有指定图片路径,则使用场景ID作为图片名称自动查找
|
|
|
|
|
|
if (!imagePath) {
|
|
|
|
|
|
// 简单的命名规则:images/[场景ID].[扩展名] 或 images/[场景标题拼音].[扩展名]
|
|
|
|
|
|
const possibleExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg'];
|
|
|
|
|
|
const baseNameId = scene.id.toString();
|
|
|
|
|
|
|
|
|
|
|
|
// 将场景标题转换为拼音风格的文件名(去掉空格和特殊字符,转为小写)
|
|
|
|
|
|
const baseNameTitle = scene.title
|
|
|
|
|
|
.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, '') // 保留中英文数字
|
|
|
|
|
|
.toLowerCase();
|
|
|
|
|
|
|
|
|
|
|
|
// 所有可能的图片路径(优先ID命名,然后标题拼音命名)
|
|
|
|
|
|
const allPossiblePaths = [
|
|
|
|
|
|
// 优先使用ID命名
|
|
|
|
|
|
...possibleExtensions.map(ext => `images/${baseNameId}${ext}`),
|
|
|
|
|
|
// 也支持标题拼音命名
|
|
|
|
|
|
...possibleExtensions.map(ext => `images/${baseNameTitle}${ext}`)
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
console.log('尝试查找图片路径:', allPossiblePaths);
|
|
|
|
|
|
|
|
|
|
|
|
// 递归检查图片是否存在
|
|
|
|
|
|
function checkImage(index) {
|
|
|
|
|
|
if (index >= allPossiblePaths.length) {
|
|
|
|
|
|
console.log('未找到任何图片');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const potentialPath = allPossiblePaths[index];
|
|
|
|
|
|
console.log('尝试加载图片:', potentialPath);
|
|
|
|
|
|
|
|
|
|
|
|
const img = new Image();
|
|
|
|
|
|
|
|
|
|
|
|
img.onload = function() {
|
|
|
|
|
|
console.log('图片加载成功:', potentialPath);
|
|
|
|
|
|
// 图片存在,设置路径并显示
|
|
|
|
|
|
elements.sceneImage.src = potentialPath;
|
|
|
|
|
|
elements.sceneImage.style.display = 'block';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
img.onerror = function() {
|
|
|
|
|
|
console.log('图片加载失败:', potentialPath);
|
|
|
|
|
|
// 图片不存在,检查下一个
|
|
|
|
|
|
checkImage(index + 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
img.src = potentialPath;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 开始检查
|
|
|
|
|
|
checkImage(0);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log('使用指定的图片路径:', imagePath);
|
|
|
|
|
|
// 使用指定的图片路径
|
|
|
|
|
|
elements.sceneImage.src = imagePath;
|
|
|
|
|
|
elements.sceneImage.onload = function() {
|
|
|
|
|
|
elements.sceneImage.style.display = 'block';
|
|
|
|
|
|
};
|
|
|
|
|
|
elements.sceneImage.onerror = function() {
|
|
|
|
|
|
console.error('指定的图片路径加载失败:', imagePath);
|
|
|
|
|
|
elements.sceneImage.style.display = 'none';
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清空对话区域
|
|
|
|
|
|
elements.conversation.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
|
|
// 重置对话索引
|
|
|
|
|
|
gameState.currentDialogIndex = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 显示第一个对话
|
|
|
|
|
|
showNextDialog();
|
|
|
|
|
|
|
|
|
|
|
|
// 更新进度
|
|
|
|
|
|
updateProgress();
|
|
|
|
|
|
|
|
|
|
|
|
// 隐藏下一场景按钮
|
|
|
|
|
|
elements.nextSceneButton.disabled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示下一个对话
|
|
|
|
|
|
function showNextDialog() {
|
|
|
|
|
|
const scene = englishData.scenes[gameState.currentSceneIndex];
|
|
|
|
|
|
const conversation = scene.conversation;
|
|
|
|
|
|
|
|
|
|
|
|
if (gameState.currentDialogIndex >= conversation.length) {
|
|
|
|
|
|
// 当前场景对话结束
|
|
|
|
|
|
if (gameState.currentSceneIndex < englishData.scenes.length - 1) {
|
|
|
|
|
|
elements.nextSceneButton.disabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const dialog = conversation[gameState.currentDialogIndex];
|
|
|
|
|
|
|
|
|
|
|
|
if (dialog.type === 'user_question') {
|
|
|
|
|
|
// 显示选择题
|
|
|
|
|
|
showQuestion(dialog);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 显示普通对话
|
|
|
|
|
|
addMessage(dialog);
|
|
|
|
|
|
|
|
|
|
|
|
// 自动显示下一个对话
|
|
|
|
|
|
gameState.currentDialogIndex++;
|
|
|
|
|
|
setTimeout(showNextDialog, 1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加对话消息
|
|
|
|
|
|
function addMessage(dialog) {
|
|
|
|
|
|
const messageDiv = document.createElement('div');
|
|
|
|
|
|
messageDiv.className = `message ${dialog.type}`;
|
|
|
|
|
|
|
|
|
|
|
|
const speakerSpan = document.createElement('span');
|
|
|
|
|
|
speakerSpan.className = 'speaker';
|
|
|
|
|
|
speakerSpan.textContent = dialog.speaker + ':';
|
|
|
|
|
|
|
|
|
|
|
|
const textSpan = document.createElement('span');
|
|
|
|
|
|
textSpan.className = 'text';
|
|
|
|
|
|
textSpan.textContent = dialog.text;
|
|
|
|
|
|
|
|
|
|
|
|
messageDiv.appendChild(speakerSpan);
|
|
|
|
|
|
messageDiv.appendChild(textSpan);
|
|
|
|
|
|
|
|
|
|
|
|
elements.conversation.appendChild(messageDiv);
|
|
|
|
|
|
|
|
|
|
|
|
// 滚动到底部
|
|
|
|
|
|
elements.conversation.scrollTop = elements.conversation.scrollHeight;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示选择题
|
|
|
|
|
|
function showQuestion(dialog) {
|
|
|
|
|
|
elements.questionText.textContent = dialog.question;
|
|
|
|
|
|
|
|
|
|
|
|
// 复制选项数组并打乱顺序
|
|
|
|
|
|
const originalOptions = [...dialog.options];
|
|
|
|
|
|
const shuffledOptions = [...originalOptions];
|
|
|
|
|
|
|
|
|
|
|
|
// 使用Fisher-Yates算法打乱选项
|
|
|
|
|
|
for (let i = shuffledOptions.length - 1; i > 0; i--) {
|
|
|
|
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
|
|
|
|
[shuffledOptions[i], shuffledOptions[j]] = [shuffledOptions[j], shuffledOptions[i]];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算正确答案在打乱后的新位置
|
|
|
|
|
|
const correctAnswerText = originalOptions[dialog.correctAnswer];
|
|
|
|
|
|
const newCorrectIndex = shuffledOptions.indexOf(correctAnswerText);
|
|
|
|
|
|
|
|
|
|
|
|
// 保存原始选项和新的正确答案索引到对话对象中,供后续检查使用
|
|
|
|
|
|
dialog.originalOptions = originalOptions;
|
|
|
|
|
|
dialog.shuffledOptions = shuffledOptions;
|
|
|
|
|
|
dialog.shuffledCorrectAnswer = newCorrectIndex;
|
|
|
|
|
|
|
|
|
|
|
|
// 设置打乱后的选项
|
|
|
|
|
|
elements.options.forEach((option, index) => {
|
|
|
|
|
|
option.textContent = shuffledOptions[index];
|
|
|
|
|
|
option.disabled = false;
|
|
|
|
|
|
option.classList.remove('selected', 'correct', 'incorrect');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 显示选择题区域
|
|
|
|
|
|
elements.questionArea.style.display = 'block';
|
|
|
|
|
|
elements.feedback.style.display = 'none';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理选项选择
|
|
|
|
|
|
function handleOptionSelect(selectedOptionIndex) {
|
|
|
|
|
|
const scene = englishData.scenes[gameState.currentSceneIndex];
|
|
|
|
|
|
const dialog = scene.conversation[gameState.currentDialogIndex];
|
|
|
|
|
|
// 使用打乱后的正确答案索引
|
|
|
|
|
|
const correctAnswerIndex = dialog.shuffledCorrectAnswer !== undefined ? dialog.shuffledCorrectAnswer : dialog.correctAnswer;
|
|
|
|
|
|
|
|
|
|
|
|
// 禁用所有选项
|
|
|
|
|
|
elements.options.forEach(option => {
|
|
|
|
|
|
option.disabled = true;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 标记选择的选项
|
|
|
|
|
|
const selectedOption = elements.options[selectedOptionIndex];
|
|
|
|
|
|
selectedOption.classList.add('selected');
|
|
|
|
|
|
|
|
|
|
|
|
// 检查答案
|
|
|
|
|
|
gameState.totalQuestions++;
|
|
|
|
|
|
let isCorrect = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (selectedOptionIndex === correctAnswerIndex) {
|
|
|
|
|
|
// 正确答案
|
|
|
|
|
|
gameState.score += 10;
|
|
|
|
|
|
gameState.correctAnswers++;
|
|
|
|
|
|
selectedOption.classList.add('correct');
|
|
|
|
|
|
showFeedback(true, dialog);
|
|
|
|
|
|
isCorrect = true;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 错误答案
|
|
|
|
|
|
selectedOption.classList.add('incorrect');
|
|
|
|
|
|
elements.options[correctAnswerIndex].classList.add('correct');
|
|
|
|
|
|
showFeedback(false, dialog);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新得分和进度
|
|
|
|
|
|
updateProgress();
|
|
|
|
|
|
|
|
|
|
|
|
// 添加用户选择的对话
|
|
|
|
|
|
addUserChoiceToConversation(dialog, selectedOptionIndex, isCorrect);
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟后显示下一个对话
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
gameState.currentDialogIndex++;
|
|
|
|
|
|
elements.questionArea.style.display = 'none';
|
|
|
|
|
|
elements.feedback.style.display = 'none';
|
|
|
|
|
|
showNextDialog();
|
|
|
|
|
|
}, 3000);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加用户选择到对话
|
|
|
|
|
|
function addUserChoiceToConversation(dialog, selectedIndex, isCorrect) {
|
|
|
|
|
|
const userMessage = {
|
|
|
|
|
|
speaker: dialog.speaker,
|
|
|
|
|
|
text: dialog.options[selectedIndex],
|
|
|
|
|
|
type: 'user'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
addMessage(userMessage);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示反馈
|
|
|
|
|
|
function showFeedback(isCorrect, dialog) {
|
|
|
|
|
|
const feedbackTextElement = elements.feedbackText;
|
|
|
|
|
|
|
|
|
|
|
|
if (isCorrect) {
|
|
|
|
|
|
// 正确答案
|
|
|
|
|
|
feedbackTextElement.textContent = '正确! 很好的回答。';
|
|
|
|
|
|
feedbackTextElement.className = 'correct';
|
|
|
|
|
|
elements.feedback.style.borderColor = '#4CAF50';
|
|
|
|
|
|
elements.feedback.style.backgroundColor = '#e8f5e8';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 错误答案
|
|
|
|
|
|
// 根据是否有打乱后的选项来选择使用哪个数组
|
|
|
|
|
|
const correctAnswer = dialog.shuffledOptions && dialog.shuffledCorrectAnswer !== undefined
|
|
|
|
|
|
? dialog.shuffledOptions[dialog.shuffledCorrectAnswer]
|
|
|
|
|
|
: dialog.options[dialog.correctAnswer];
|
|
|
|
|
|
|
|
|
|
|
|
feedbackTextElement.textContent = `不正确。正确答案是: ${correctAnswer}`;
|
|
|
|
|
|
feedbackTextElement.className = 'incorrect';
|
|
|
|
|
|
elements.feedback.style.borderColor = '#f44336';
|
|
|
|
|
|
elements.feedback.style.backgroundColor = '#ffebee';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
elements.feedback.style.display = 'block';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新进度
|
|
|
|
|
|
function updateProgress() {
|
|
|
|
|
|
elements.scoreElement.textContent = `得分: ${gameState.score}`;
|
|
|
|
|
|
elements.progressElement.textContent = `进度: ${gameState.currentSceneIndex + 1}/${englishData.scenes.length}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示游戏完成信息
|
|
|
|
|
|
function showGameComplete() {
|
|
|
|
|
|
elements.conversation.innerHTML = '';
|
|
|
|
|
|
elements.questionArea.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
|
|
const completeMessage = document.createElement('div');
|
|
|
|
|
|
completeMessage.className = 'message other';
|
|
|
|
|
|
completeMessage.innerHTML = `
|
|
|
|
|
|
<span class="speaker">学习完成:</span>
|
|
|
|
|
|
<span class="text">
|
|
|
|
|
|
恭喜您完成了所有场景的学习!<br>
|
|
|
|
|
|
总得分: ${gameState.score}<br>
|
|
|
|
|
|
正确题目: ${gameState.correctAnswers}/${gameState.totalQuestions}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
elements.conversation.appendChild(completeMessage);
|
|
|
|
|
|
elements.nextSceneButton.disabled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置事件监听器
|
|
|
|
|
|
function setupEventListeners() {
|
|
|
|
|
|
// 选项按钮点击事件
|
|
|
|
|
|
elements.options.forEach((option, index) => {
|
|
|
|
|
|
option.addEventListener('click', () => {
|
|
|
|
|
|
handleOptionSelect(index);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 开始按钮点击事件
|
|
|
|
|
|
elements.startButton.addEventListener('click', () => {
|
|
|
|
|
|
initGame();
|
|
|
|
|
|
elements.startButton.style.display = 'none';
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 重置按钮点击事件
|
|
|
|
|
|
elements.resetButton.addEventListener('click', () => {
|
|
|
|
|
|
initGame();
|
|
|
|
|
|
elements.startButton.style.display = 'none';
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 下一场景按钮点击事件
|
|
|
|
|
|
elements.nextSceneButton.addEventListener('click', () => {
|
|
|
|
|
|
gameState.currentSceneIndex++;
|
|
|
|
|
|
loadScene(gameState.currentSceneIndex);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 页面加载完成后初始化游戏
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
|
// 获取DOM元素
|
|
|
|
|
|
getDomElements();
|
|
|
|
|
|
|
2026-01-09 01:01:23 +08:00
|
|
|
|
// 设置事件监听器(只调用一次)
|
|
|
|
|
|
setupEventListeners();
|
|
|
|
|
|
|
2026-01-08 21:14:46 +08:00
|
|
|
|
// 确保englishData已加载
|
|
|
|
|
|
if (typeof englishData !== 'undefined') {
|
|
|
|
|
|
initGame();
|
|
|
|
|
|
elements.startButton.style.display = 'block';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('英语学习数据未加载');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|