commit 7a1f71588949b932515d2338264b1e7fe16de6c7 Author: Student Date: Thu Jan 8 21:14:46 2026 +0800 Complete Course Design Project diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..56a8178 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +.venv/ +__pycache__/ +.DS_Store \ No newline at end of file diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e0c20e --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# 英语学习日常场景对话练习系统 + +## 2.1 团队成员与贡献 (必填) +| 姓名 | 学号 | 主要贡献 (具体分工) | +|------|------|-------------------| +| 张扬 | 2411020205 | 核心逻辑开发、Prompt 编写 | +| 刘诣卓 | 2411020209 | 创意提供、网页测试 | +| 顾浩晨 | 2411020108 | 文档撰写、测试与 Bug 修复 | + +## 2.2 项目简介 & 运行指南 +### 简介 +本项目是一个基于网页的英语学习系统,通过模拟真实日常场景对话,提供交互式的选择题练习,帮助学习者提升英语口语表达能力和场景应用能力。 + +### 如何运行 +1. 安装依赖:本项目使用 Python 的 http.server 模块,无需额外安装依赖 +2. 进入项目目录:`cd english-learning-uv` +3. 启动服务器:`python -m http.server 8000` +4. 访问网页:在浏览器中输入 `http://localhost:8000` + +## 2.3 开发心得 + +### 选题思考 +在英语学习过程中,很多学习者面临着"学了不会用"的痛点。传统的英语学习往往注重词汇和语法的记忆,而缺乏真实场景的应用练习。我们的项目旨在解决这一问题,通过模拟餐厅点餐、超市购物、问路等真实日常场景,让学习者在交互式对话中练习英语,提高实际应用能力。 + +### AI 协作体验 +作为第一次尝试使用 AI 辅助开发的团队,我们的体验可以说是充满惊喜和挑战。 + +最初使用 AI 写代码时,感觉就像是拥有了一个经验丰富的编程助手。只需要描述我们的需求,AI 就能快速生成基础代码框架,大大提高了开发效率。特别是在处理一些重复性工作时,比如创建多个相似的场景数据,AI 能够快速生成模板并根据需求进行调整。 + +让我们印象最深刻的"牛逼"Prompt 是:"帮我创建一个英语学习网页,包含日常场景对话和选择题练习功能,使用 uv 进行包管理"。AI 不仅生成了完整的 HTML、CSS 和 JavaScript 代码,还考虑了用户体验和代码结构的合理性,为我们的项目提供了良好的起点。 + +当然,也有一些让我们感到挫败的时刻。比如在处理图片加载逻辑时,我们的 Prompt 描述不够精确,导致 AI 生成的代码虽然能运行,但存在性能问题。还有一次,我们要求 AI 实现选项随机排序功能,结果生成的代码打乱了选项但没有正确跟踪正确答案的位置,导致答题逻辑出错。这些经历让我们认识到,与 AI 协作需要清晰、精确的指令,并且需要对生成的代码进行仔细的检查和测试。 + +### 自我反思 +在使用 AI 辅助开发的过程中,我们深刻地思考了一个问题:AI 时代,程序员的核心竞争力到底是什么? + +通过这次项目,我们认识到,AI 确实能够快速生成基础代码,处理一些常规的编程任务。但它缺乏对项目整体架构的理解,无法把握业务逻辑的本质,也不能创造性地解决复杂问题。作为程序员,我们的核心竞争力应该在于: + +1. **问题分析能力**:能够深入理解用户需求,将实际问题转化为清晰的技术方案。 +2. **系统设计能力**:能够设计合理的系统架构,考虑代码的可维护性、可扩展性和性能。 +3. **创造性思维**:能够创造性地解决复杂问题,提出创新的解决方案。 +4. **代码质量意识**:能够编写高质量、可读性强、易于维护的代码。 +5. **持续学习能力**:能够不断学习新技术,适应快速变化的技术环境。 + +AI 是一个强大的工具,但它不能替代程序员的核心能力。相反,它让我们能够从繁琐的重复性工作中解放出来,将更多的精力放在创造性的思考和复杂问题的解决上。在未来的开发工作中,我们应该学会与 AI 协作,充分发挥它的优势,同时不断提升自己的核心竞争力。 + +通过这次项目,我们不仅完成了一个功能完整的英语学习系统,更重要的是获得了宝贵的 AI 协作经验和对程序员职业发展的深刻思考。这些收获将对我们未来的学习和工作产生积极的影响。 \ No newline at end of file diff --git a/data.js b/data.js new file mode 100644 index 0000000..c130ab1 --- /dev/null +++ b/data.js @@ -0,0 +1,690 @@ +// 日常英语学习数据 +const englishData = { + scenes: [ + { + id: 0, + title: "餐厅点餐", + description: "在餐厅点餐的日常对话练习", + image: "", // 更简单的图片导入: + // 方式1:将图片放在 images 目录下,命名为场景ID+扩展名(推荐) + // 例如:0.jpg, 1.png, 2.jpeg 等 + // 方式2:将图片放在 images 目录下,命名为场景标题拼音+扩展名 + // 例如:餐厅点餐 -> cantingdiancan.jpg + // 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg + // 不需要手动修改此字段,系统会自动查找所有可能的图片 + conversation: [ + { + speaker: "服务员", + text: "Good afternoon! Welcome to our restaurant. May I take your order?", + type: "other" + }, + { + speaker: "我", + text: "I'd like to see the menu first, please.", + type: "user" + }, + { + speaker: "服务员", + text: "Sure, here you are. Take your time.", + type: "other" + }, + { + speaker: "服务员", + text: "Are you ready to order now?", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "Yes, I'll have the steak, please.", + "No, I don't like this restaurant.", + "I want to go home.", + "The menu is too expensive." + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "服务员", + text: "How would you like your steak cooked?", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "Medium rare, please.", + "With fries and salad.", + "I want a drink.", + "That's all." + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "服务员", + text: "Would you like anything to drink?", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "A glass of water, please.", + "No, I'm not hungry.", + "I'll pay now.", + "Thank you." + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "服务员", + text: "Okay, your order will be ready in 15 minutes.", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "Thank you very much.", + "Hurry up!", + "I want it now.", + "That's too long." + ], + correctAnswer: 0, + type: "user_question" + } + ] + }, + { + id: 1, + title: "超市购物", + description: "在超市购物的日常对话练习", + image: "", // 更简单的图片导入: + // 方式1:将图片放在 images 目录下,命名为场景ID+扩展名(推荐) + // 例如:0.jpg, 1.png, 2.jpeg 等 + // 方式2:将图片放在 images 目录下,命名为场景标题拼音+扩展名 + // 例如:餐厅点餐 -> cantingdiancan.jpg + // 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg + // 不需要手动修改此字段,系统会自动查找所有可能的图片 + conversation: [ + { + speaker: "顾客", + text: "Excuse me, where can I find the milk?", + type: "user" + }, + { + speaker: "超市员工", + text: "It's in aisle 5, next to the bread.", + type: "other" + }, + { + speaker: "顾客", + question: "请选择合适的回答:", + options: [ + "Thank you very much.", + "I don't like milk.", + "Where is aisle 5?", + "This store is too big." + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "超市员工", + text: "You're welcome. Let me know if you need anything else.", + type: "other" + }, + { + speaker: "顾客", + text: "Actually, do you have any organic vegetables?", + type: "user" + }, + { + speaker: "超市员工", + text: "Yes, they're in the fresh produce section at the back of the store.", + type: "other" + }, + { + speaker: "顾客", + question: "请选择合适的回答:", + options: [ + "Great, thank you.", + "I don't want organic.", + "That's too far.", + "Why are they there?" + ], + correctAnswer: 0, + type: "user_question" + } + ] + }, + { + id: 2, + title: "问路", + description: "向陌生人问路的日常对话练习", + image: "", // 更简单的图片导入: + // 方式1:将图片放在 images 目录下,命名为场景ID+扩展名(推荐) + // 例如:0.jpg, 1.png, 2.jpeg 等 + // 方式2:将图片放在 images 目录下,命名为场景标题拼音+扩展名 + // 例如:餐厅点餐 -> cantingdiancan.jpg + // 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg + // 不需要手动修改此字段,系统会自动查找所有可能的图片 + conversation: [ + { + speaker: "我", + text: "Excuse me, could you tell me how to get to the nearest subway station?", + type: "user" + }, + { + speaker: "路人", + text: "Sure! Go straight for two blocks, then turn left at the traffic light. You'll see it on your right.", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "Is it far from here?", + "I don't want to go there.", + "That's too complicated.", + "I'll take a taxi instead." + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "路人", + text: "No, it's only about a 5-minute walk.", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "Perfect! Thank you so much.", + "5 minutes is too long.", + "I can't walk that far.", + "Do you have a map?" + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "路人", + text: "You're welcome! Have a nice day.", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "You too!", + "Bye, I'm in a hurry.", + "I hope I don't get lost.", + "This city is confusing." + ], + correctAnswer: 0, + type: "user_question" + } + ] + }, + { + id: 3, + title: "医院就诊", + description: "在医院看病的日常对话练习", + image: "", // 更简单的图片导入: + // 方式1:将图片放在 images 目录下,命名为场景ID+扩展名(推荐) + // 例如:0.jpg, 1.png, 2.jpeg 等 + // 方式2:将图片放在 images 目录下,命名为场景标题拼音+扩展名 + // 例如:餐厅点餐 -> cantingdiancan.jpg + // 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg + // 不需要手动修改此字段,系统会自动查找所有可能的图片 + conversation: [ + { + speaker: "护士", + text: "Good morning, how can I help you today?", + type: "other" + }, + { + speaker: "我", + text: "I'm not feeling well. I have a fever and a headache.", + type: "user" + }, + { + speaker: "护士", + text: "I'm sorry to hear that. Please fill out this form and take a seat. The doctor will see you shortly.", + type: "other" + }, + { + speaker: "医生", + text: "Hello, I'm Dr. Smith. What seems to be the problem?", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "I've had a fever and headache for two days.", + "I don't like doctors.", + "How much does this cost?", + "Can I leave now?" + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "医生", + text: "Let me check your temperature and blood pressure.", + type: "other" + }, + { + speaker: "医生", + text: "You have a slight fever. I'll prescribe some medicine for you.", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "How often should I take it?", + "I don't want any medicine.", + "Is it expensive?", + "When will I get better?" + ], + correctAnswer: 0, + type: "user_question" + } + ] + }, + { + id: 4, + title: "银行办理业务", + description: "在银行办理业务的日常对话练习", + image: "", // 更简单的图片导入: + // 方式1:将图片放在 images 目录下,命名为场景ID+扩展名(推荐) + // 例如:0.jpg, 1.png, 2.jpeg 等 + // 方式2:将图片放在 images 目录下,命名为场景标题拼音+扩展名 + // 例如:餐厅点餐 -> cantingdiancan.jpg + // 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg + // 不需要手动修改此字段,系统会自动查找所有可能的图片 + conversation: [ + { + speaker: "银行职员", + text: "Good morning! How can I assist you today?", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "I'd like to open a savings account.", + "I want to rob the bank.", + "Your bank is too slow.", + "I need a loan immediately." + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "银行职员", + text: "Certainly! Could you please provide your ID and proof of address?", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "Here you are.", + "I don't have any ID.", + "Why do you need that?", + "This is too麻烦." + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "银行职员", + text: "Thank you. Please fill out this form and sign here.", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "Okay, thank you.", + "I can't read this.", + "This is taking too long.", + "I changed my mind." + ], + correctAnswer: 0, + type: "user_question" + } + ] + }, + { + id: 5, + title: "酒店入住", + description: "酒店入住登记的日常对话练习", + image: "", // 更简单的图片导入: + // 方式1:将图片放在 images 目录下,命名为场景ID+扩展名(推荐) + // 例如:0.jpg, 1.png, 2.jpeg 等 + // 方式2:将图片放在 images 目录下,命名为场景标题拼音+扩展名 + // 例如:餐厅点餐 -> cantingdiancan.jpg + // 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg + // 不需要手动修改此字段,系统会自动查找所有可能的图片 + conversation: [ + { + speaker: "前台接待", + text: "Good evening! Welcome to our hotel. Do you have a reservation?", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "Yes, I have a reservation under the name Zhang.", + "No, I want the best room.", + "How much is a room?", + "I'm here to visit a friend." + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "前台接待", + text: "Let me check... Yes, Mr. Zhang. You booked a single room for two nights.", + type: "other" + }, + { + speaker: "前台接待", + text: "Could you please show me your ID and credit card?", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "Here they are.", + "I don't have a credit card.", + "Why do you need that?", + "Can I pay cash?" + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "前台接待", + text: "Thank you. Here's your room key. You're in room 302 on the third floor.", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "Thank you! Where is the elevator?", + "The room is too small.", + "I want a better view.", + "Can I check out late?" + ], + correctAnswer: 0, + type: "user_question" + } + ] + }, + { + id: 6, + title: "电话预约", + description: "用电话预约的日常对话练习", + image: "", // 更简单的图片导入: + // 方式1:将图片放在 images 目录下,命名为场景ID+扩展名(推荐) + // 例如:0.jpg, 1.png, 2.jpeg 等 + // 方式2:将图片放在 images 目录下,命名为场景标题拼音+扩展名 + // 例如:餐厅点餐 -> cantingdiancan.jpg + // 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg + // 不需要手动修改此字段,系统会自动查找所有可能的图片 + conversation: [ + { + speaker: "接听者", + text: "Hello, this is the dental clinic. How can I help you?", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "I'd like to make an appointment for a check-up.", + "I have a toothache.", + "Can I speak to Dr. Wang?", + "What are your opening hours?" + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "接听者", + text: "Certainly! When would you like to come in?", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "Is tomorrow morning available?", + "I want to come now.", + "How much does it cost?", + "I'm scared of dentists." + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "接听者", + text: "Yes, we have openings at 9:00 and 10:30. Which one works better for you?", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "9:00 would be perfect.", + "Both times are bad.", + "Can I come at noon?", + "I'll call back later." + ], + correctAnswer: 0, + type: "user_question" + } + ] + }, + { + id: 7, + title: "机场登机", + description: "机场办理登机手续的日常对话练习", + image: "", // 更简单的图片导入: + // 方式1:将图片放在 images 目录下,命名为场景ID+扩展名(推荐) + // 例如:0.jpg, 1.png, 2.jpeg 等 + // 方式2:将图片放在 images 目录下,命名为场景标题拼音+扩展名 + // 例如:餐厅点餐 -> cantingdiancan.jpg + // 支持的格式:.jpg, .jpeg, .png, .gif, .webp, .bmp, .svg + // 不需要手动修改此字段,系统会自动查找所有可能的图片 + conversation: [ + { + speaker: "工作人员", + text: "Good morning! May I see your passport and ticket, please?", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "Here they are.", + "I lost my ticket.", + "When is boarding time?", + "Where is my gate?" + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "工作人员", + text: "Thank you. Would you like a window seat or an aisle seat?", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "A window seat, please.", + "I want the best seat.", + "How much extra?", + "I don't care." + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "工作人员", + text: "Here's your boarding pass. Your gate is B12, and boarding starts at 10:30.", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "Thank you very much.", + "Where is gate B12?", + "Is the flight on time?", + "Can I bring food on board?" + ], + correctAnswer: 0, + type: "user_question" + } + ] + }, + { + id: 8, + title: "公交车让座", + description: "在公交车上让座的日常对话练习", + image: "", // 简单图片导入:将图片放在 images 目录下,可使用以下命名规则之一 + // 1. 按场景ID命名:8.jpg, 8.png + // 2. 按场景标题命名:公交车让座.jpg, bus.jpg + // 不需要手动修改此字段,系统会自动查找 + conversation: [ + { + speaker: "老人", + text: "Excuse me, is this seat taken?", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "No, please sit here.", + "Yes, it's taken.", + "I'm tired too.", + "Find another seat." + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "老人", + text: "Thank you so much, young man/lady.", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "You're welcome. It's my pleasure.", + "It's okay.", + "Don't mention it.", + "Sit down quickly." + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "老人", + text: "You're such a kind person.", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "Thank you for your kind words.", + "It's nothing special.", + "I have to get off soon.", + "I do this every day." + ], + correctAnswer: 0, + type: "user_question" + } + ] + }, + { + id: 9, + title: "图书馆借书", + description: "在图书馆借书的日常对话练习", + image: "", // 简单图片导入:将图片放在 images 目录下,可使用以下命名规则之一 + // 1. 按场景ID命名:9.jpg, 9.png + // 2. 按场景标题命名:图书馆借书.jpg, library.jpg + // 不需要手动修改此字段,系统会自动查找 + conversation: [ + { + speaker: "我", + text: "Excuse me, how can I borrow books from the library?", + type: "user" + }, + { + speaker: "图书馆职员", + text: "You need a library card. Do you have one?", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "No, how can I get one?", + "I forgot it at home.", + "Can I use my ID instead?", + "Why do I need one?" + ], + correctAnswer: 0, + type: "user_question" + }, + { + speaker: "图书馆职员", + text: "You can apply for one at the information desk with your ID.", + type: "other" + }, + { + speaker: "我", + text: "Great, thank you. And how many books can I borrow at a time?", + type: "user" + }, + { + speaker: "图书馆职员", + text: "You can borrow up to 5 books for 3 weeks.", + type: "other" + }, + { + speaker: "我", + question: "请选择合适的回答:", + options: [ + "Perfect, that's enough for me.", + "That's too few.", + "3 weeks is too short.", + "Can I renew them online?" + ], + correctAnswer: 0, + type: "user_question" + } + ] + } + ] +}; \ No newline at end of file diff --git a/images/0.png b/images/0.png new file mode 100644 index 0000000..cd8826d Binary files /dev/null and b/images/0.png differ diff --git a/images/1.png b/images/1.png new file mode 100644 index 0000000..e14d521 Binary files /dev/null and b/images/1.png differ diff --git a/images/2.png b/images/2.png new file mode 100644 index 0000000..fedd73b Binary files /dev/null and b/images/2.png differ diff --git a/images/3.png b/images/3.png new file mode 100644 index 0000000..25706c5 Binary files /dev/null and b/images/3.png differ diff --git a/images/4.png b/images/4.png new file mode 100644 index 0000000..899c063 Binary files /dev/null and b/images/4.png differ diff --git a/images/5.png b/images/5.png new file mode 100644 index 0000000..c7e4a47 Binary files /dev/null and b/images/5.png differ diff --git a/images/6.png b/images/6.png new file mode 100644 index 0000000..9a492a7 Binary files /dev/null and b/images/6.png differ diff --git a/images/7.png b/images/7.png new file mode 100644 index 0000000..e100e5d Binary files /dev/null and b/images/7.png differ diff --git a/images/8.png b/images/8.png new file mode 100644 index 0000000..460fe69 Binary files /dev/null and b/images/8.png differ diff --git a/images/9.png b/images/9.png new file mode 100644 index 0000000..24dce7e Binary files /dev/null and b/images/9.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..07033a2 --- /dev/null +++ b/index.html @@ -0,0 +1,67 @@ + + + + + + 日常英语学习 - 场景对话练习 + + + +
+
+

日常英语学习

+

场景对话练习

+
+ +
+
+ 得分: 0 +
+
+ 进度: 1/10 +
+
+ +
+
+

餐厅点餐

+

在餐厅点餐的日常对话练习

+
+ 场景插图 +
+
+ +
+
+ +
+
+ + + + + +
+ + + +
+
+
+ + + + + \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..30c69ab --- /dev/null +++ b/main.py @@ -0,0 +1,6 @@ +def main(): + print("Hello from english-learning-uv!") + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..cead843 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "english-learning-uv" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [] diff --git a/script.js b/script.js new file mode 100644 index 0000000..a22ae0b --- /dev/null +++ b/script.js @@ -0,0 +1,399 @@ +// 英语学习网页主脚本 + +// 游戏状态 +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); + setupEventListeners(); + 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 = ` + 学习完成: + + 恭喜您完成了所有场景的学习!
+ 总得分: ${gameState.score}
+ 正确题目: ${gameState.correctAnswers}/${gameState.totalQuestions} +
+ `; + + 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(); + + // 确保englishData已加载 + if (typeof englishData !== 'undefined') { + initGame(); + elements.startButton.style.display = 'block'; + } else { + console.error('英语学习数据未加载'); + } +}); \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..464ea0c --- /dev/null +++ b/style.css @@ -0,0 +1,324 @@ +/* 全局样式 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Arial', sans-serif; + background-color: #f5f7fa; + color: #333; + line-height: 1.6; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +/* 头部样式 */ +.header { + text-align: center; + margin-bottom: 30px; + padding: 20px; + background-color: #4a90e2; + color: white; + border-radius: 10px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +.header h1 { + font-size: 2.5em; + margin-bottom: 10px; +} + +.subtitle { + font-size: 1.2em; + opacity: 0.9; +} + +/* 状态栏样式 */ +.status-bar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + padding: 15px; + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); +} + +.score, .progress { + font-size: 1.1em; + font-weight: bold; + color: #4a90e2; +} + +/* 内容区域样式 */ +.content { + background-color: white; + border-radius: 10px; + padding: 30px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); +} + +/* 场景信息样式 */ +.scene-info { + margin-bottom: 30px; + padding-bottom: 20px; + border-bottom: 2px solid #e8f0fe; +} + +.scene-info h2 { + color: #4a90e2; + margin-bottom: 10px; + font-size: 1.8em; +} + +.scene-info p { + color: #666; + font-size: 1.1em; +} + +/* 场景图片样式 */ +.scene-image-container { + margin-top: 20px; + display: flex; + justify-content: center; + align-items: center; +} + +#scene-image { + max-width: 100%; + height: auto; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + max-height: 300px; + object-fit: cover; +} + +/* 对话区域样式 */ +.conversation-area { + margin-bottom: 30px; +} + +.conversation { + max-height: 400px; + overflow-y: auto; + padding: 20px; + background-color: #f8f9fa; + border-radius: 8px; + border: 1px solid #e9ecef; +} + +.message { + margin-bottom: 15px; + padding: 10px 15px; + border-radius: 18px; + max-width: 80%; + clear: both; +} + +.message.other { + background-color: #e3f2fd; + color: #1565c0; + float: left; + border-bottom-left-radius: 5px; +} + +.message.user { + background-color: #4a90e2; + color: white; + float: right; + border-bottom-right-radius: 5px; +} + +.message .speaker { + font-weight: bold; + margin-right: 10px; +} + +/* 问题区域样式 */ +.question-area { + margin-bottom: 30px; + padding: 20px; + background-color: #f8f9fa; + border-radius: 8px; + border: 1px solid #e9ecef; +} + +.question h3 { + color: #4a90e2; + margin-bottom: 20px; + font-size: 1.3em; +} + +.options { + display: flex; + flex-direction: column; + gap: 15px; +} + +.option { + padding: 15px 20px; + border: 2px solid #e0e0e0; + background-color: white; + border-radius: 8px; + font-size: 1.1em; + cursor: pointer; + transition: all 0.3s ease; + text-align: left; +} + +.option:hover { + border-color: #4a90e2; + background-color: #f0f8ff; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.option.selected { + border-color: #4a90e2; + background-color: #e3f2fd; +} + +.option.correct { + border-color: #4caf50; + background-color: #e8f5e9; + color: #2e7d32; +} + +.option.incorrect { + border-color: #f44336; + background-color: #ffebee; + color: #c62828; +} + +.option.disabled { + cursor: not-allowed; + opacity: 0.7; +} + +.option.disabled:hover { + transform: none; + box-shadow: none; +} + +/* 反馈区域样式 */ +.feedback { + margin-bottom: 20px; + padding: 15px 20px; + border-radius: 8px; + border: 1px solid #ddd; + text-align: center; + font-weight: bold; +} + +.feedback.correct { + background-color: #e8f5e9; + border-color: #4caf50; + color: #2e7d32; +} + +.feedback.incorrect { + background-color: #ffebee; + border-color: #f44336; + color: #c62828; +} + +/* 控制按钮样式 */ +.controls { + display: flex; + gap: 15px; + justify-content: center; + margin-top: 30px; +} + +.btn { + padding: 12px 24px; + border: none; + border-radius: 8px; + font-size: 1.1em; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + min-width: 120px; +} + +.btn-primary { + background-color: #4a90e2; + color: white; +} + +.btn-primary:hover { + background-color: #357abd; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.btn-secondary { + background-color: #90a4ae; + color: white; +} + +.btn-secondary:hover { + background-color: #78909c; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.btn-success { + background-color: #4caf50; + color: white; +} + +.btn-success:hover { + background-color: #388e3c; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .container { + padding: 10px; + } + + .header h1 { + font-size: 2em; + } + + .content { + padding: 20px; + } + + .status-bar { + flex-direction: column; + gap: 10px; + align-items: flex-start; + } + + .message { + max-width: 95%; + } + + .controls { + flex-direction: column; + align-items: center; + } + + .btn { + width: 100%; + max-width: 300px; + } +} \ No newline at end of file