zylyzghc/script.js

738 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 英语学习网页主脚本
// 游戏状态
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'),
// 实时对话相关元素
chatHistory: document.getElementById('chat-history'),
chatMessage: document.getElementById('chat-message'),
sendButton: document.getElementById('send-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') {
// 区分选择题和自由回答题
if (dialog.options) {
// 显示选择题
showQuestion(dialog);
} else {
// 使用API调用进行实时对话自由回答题
showDynamicQuestion(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';
}
// 显示动态输入问题使用API
function showDynamicQuestion(dialog) {
elements.questionText.textContent = dialog.question;
// 创建动态输入界面
const optionsContainer = document.querySelector('.options');
optionsContainer.innerHTML = '';
// 创建文本输入框
const inputElement = document.createElement('input');
inputElement.type = 'text';
inputElement.id = 'dynamic-input';
inputElement.placeholder = '请输入您的回答...';
inputElement.className = 'dynamic-input';
// 创建发送按钮
const sendButton = document.createElement('button');
sendButton.textContent = '发送';
sendButton.className = 'btn btn-primary';
sendButton.addEventListener('click', () => {
handleDynamicInput(inputElement.value, dialog);
});
// 添加回车事件监听
inputElement.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
handleDynamicInput(inputElement.value, dialog);
}
});
// 添加到选项容器
optionsContainer.appendChild(inputElement);
optionsContainer.appendChild(sendButton);
// 显示选择题区域
elements.questionArea.style.display = 'block';
elements.feedback.style.display = 'none';
// 自动聚焦到输入框
inputElement.focus();
}
// 处理选项选择
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);
}
// 处理动态输入
async function handleDynamicInput(userInput, dialog) {
if (!userInput.trim()) return;
// 禁用输入和按钮
const inputElement = document.getElementById('dynamic-input');
const sendButton = inputElement.nextElementSibling;
inputElement.disabled = true;
sendButton.disabled = true;
// 添加用户输入到对话
const userMessage = {
speaker: dialog.speaker,
text: userInput,
type: 'user'
};
addMessage(userMessage);
// 将用户输入添加到对话历史数组以便API获取完整上下文
const scene = englishData.scenes[gameState.currentSceneIndex];
scene.conversation.splice(gameState.currentDialogIndex + 1, 0, userMessage);
// 更新进度
gameState.totalQuestions++;
try {
// 获取场景上下文
const scene = englishData.scenes[gameState.currentSceneIndex];
const conversationHistory = scene.conversation.slice(0, gameState.currentDialogIndex + 1);
// 构建API请求消息
const messages = [
{
role: 'system',
content: `You are an English language tutor. Please respond to the user's answer in the context of the daily conversation practice.\n\nScene: ${scene.title}\nDescription: ${scene.description}\n\nCurrent conversation:\n${conversationHistory.map(msg => `${msg.speaker}: ${msg.text}`).join('\n')}\n\nThe user was asked: ${dialog.question}\nThe user answered: ${userInput}\n\nPlease provide: 1) A natural response to continue the conversation, 2) A brief evaluation of the user's answer (whether it's appropriate in this context), and 3) The correct answer if the user's response is not appropriate. Keep your response concise and helpful for language learning.`
},
{
role: 'user',
content: userInput
}
];
// 调用DeepSeek API启用流式响应
const apiKey = 'sk-b5022a18ac184917bc1ea9485c15fda0';
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: messages,
max_tokens: 200,
temperature: 0.7,
stream: true // 启用流式响应
})
});
// 检查响应状态
if (!response.ok) {
if (response.status === 402) {
throw new Error(`API request failed: Insufficient Balance. Please check your DeepSeek API key balance.`);
} else {
throw new Error(`API request failed with status ${response.status}: ${response.statusText}`);
}
}
// 实现流式响应处理
if (!response.body) {
throw new Error('No response body');
}
// 获取可读流
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let aiResponse = '';
// 初始化反馈显示
const feedbackTextElement = elements.feedbackText;
feedbackTextElement.textContent = '';
feedbackTextElement.className = 'api-feedback';
elements.feedback.style.borderColor = '#2196F3';
elements.feedback.style.backgroundColor = '#e3f2fd';
elements.feedback.style.display = 'block';
try {
// 逐块读取响应
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
// 解码并处理数据
const chunk = decoder.decode(value, { stream: true });
// 分割SSE事件
const lines = chunk.split('\n');
for (const line of lines) {
if (!line.trim() || line.startsWith(':')) {
continue; // 忽略空行和注释
}
// 解析SSE事件数据
if (line.startsWith('data: ')) {
const dataStr = line.slice(6).trim();
if (dataStr === '[DONE]') {
break;
}
try {
const data = JSON.parse(dataStr);
// 提取AI生成的内容
if (data.choices && data.choices[0] && data.choices[0].delta) {
const delta = data.choices[0].delta;
if (delta.content) {
aiResponse += delta.content;
// 实时更新UI
feedbackTextElement.textContent = aiResponse;
}
}
} catch (e) {
console.error('Error parsing JSON:', e);
}
}
}
}
// 流式响应结束,增加分数
if (aiResponse) {
gameState.score += 8;
gameState.correctAnswers++;
updateProgress();
} else {
throw new Error('No response from API');
}
} finally {
reader.releaseLock();
}
} catch (error) {
console.error('Error handling dynamic input:', error);
// 显示错误信息
const feedbackTextElement = elements.feedbackText;
feedbackTextElement.textContent = `Error: ${error.message}`;
feedbackTextElement.className = 'error';
elements.feedback.style.borderColor = '#f44336';
elements.feedback.style.backgroundColor = '#ffebee';
elements.feedback.style.display = 'block';
} finally {
// 延迟后显示下一个对话
setTimeout(() => {
gameState.currentDialogIndex++;
elements.questionArea.style.display = 'none';
elements.feedback.style.display = 'none';
showNextDialog();
}, 5000);
}
}
// 添加用户选择到对话
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);
});
// 实时对话事件监听
elements.sendButton.addEventListener('click', sendChatMessage);
elements.chatMessage.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendChatMessage();
}
});
}
// 加载环境变量
function loadEnv() {
// 简单的.env文件解析
return new Promise((resolve) => {
fetch('.env')
.then(response => response.text())
.then(text => {
const envVars = {};
const lines = text.split('\n');
lines.forEach(line => {
const [key, value] = line.split('=');
if (key && value) {
envVars[key.trim()] = value.trim();
}
});
resolve(envVars);
})
.catch(() => {
// 如果.env文件不存在返回空对象
resolve({});
});
});
}
// 添加消息到聊天历史
function addChatMessage(text, isUser = false) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${isUser ? 'user' : 'other'}`;
const speakerSpan = document.createElement('span');
speakerSpan.className = 'speaker';
speakerSpan.textContent = isUser ? '我:' : 'AI:';
const textSpan = document.createElement('span');
textSpan.className = 'text';
textSpan.textContent = text;
messageDiv.appendChild(speakerSpan);
messageDiv.appendChild(textSpan);
elements.chatHistory.appendChild(messageDiv);
// 滚动到底部
elements.chatHistory.scrollTop = elements.chatHistory.scrollHeight;
}
// 发送聊天消息
async function sendChatMessage() {
const message = elements.chatMessage.value.trim();
if (!message) return;
// 显示用户消息
addChatMessage(message, true);
elements.chatMessage.value = '';
elements.sendButton.disabled = true;
try {
// 直接使用API密钥避免跨域问题
const apiKey = 'sk-b5022a18ac184917bc1ea9485c15fda0';
// 调用DeepSeek API
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: [
{
role: 'system',
content: 'You are an English language tutor. Please respond to the user in English, and keep the conversation natural and helpful for English learning.'
},
{
role: 'user',
content: message
}
],
max_tokens: 100,
temperature: 0.7
})
});
// 检查响应状态
if (!response.ok) {
if (response.status === 402) {
throw new Error(`API request failed: Insufficient Balance. Please check your DeepSeek API key balance.`);
} else {
throw new Error(`API request failed with status ${response.status}: ${response.statusText}`);
}
}
const data = await response.json();
console.log('API response:', data);
if (data.choices && data.choices[0]) {
const aiResponse = data.choices[0].message.content;
addChatMessage(aiResponse, false);
} else {
addChatMessage('Sorry, I couldn\'t generate a response. Please try again.', false);
}
} catch (error) {
console.error('Error sending message:', error);
// 显示更详细的错误信息给用户
addChatMessage(`Error: ${error.message}. Please check the browser console for details.`, false);
} finally {
elements.sendButton.disabled = false;
}
}
// 页面加载完成后初始化游戏
document.addEventListener('DOMContentLoaded', () => {
// 获取DOM元素
getDomElements();
// 设置事件监听器(只调用一次)
setupEventListeners();
// 确保englishData已加载
if (typeof englishData !== 'undefined') {
initGame();
elements.startButton.style.display = 'block';
} else {
console.error('英语学习数据未加载');
}
});