Compare commits
No commits in common. "master" and "main" have entirely different histories.
4
.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
.env
|
||||
.venv/
|
||||
__pycache__/
|
||||
.DS_Store
|
||||
@ -1 +0,0 @@
|
||||
3.13
|
||||
47
README.md
@ -1,47 +0,0 @@
|
||||
# 英语学习日常场景对话练习系统
|
||||
|
||||
## 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 协作经验和对程序员职业发展的深刻思考。这些收获将对我们未来的学习和工作产生积极的影响。
|
||||
690
data.js
@ -1,690 +0,0 @@
|
||||
// 日常英语学习数据
|
||||
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 troublesome."
|
||||
],
|
||||
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
232
data.py
@ -1,232 +0,0 @@
|
||||
# 英语学习数据(Python版本)
|
||||
|
||||
english_data = {
|
||||
"scenes": [
|
||||
{
|
||||
"id": 0,
|
||||
"title": "餐厅点餐",
|
||||
"description": "在餐厅点餐的日常对话练习",
|
||||
"image": "",
|
||||
"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": "",
|
||||
"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": "",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "机场值机",
|
||||
"description": "在机场办理值机手续的日常对话练习",
|
||||
"image": "",
|
||||
"conversation": [
|
||||
{
|
||||
"speaker": "值机人员",
|
||||
"text": "Good morning! How can I help you today?",
|
||||
"type": "other"
|
||||
},
|
||||
{
|
||||
"speaker": "我",
|
||||
"text": "I need to check in for my flight, please.",
|
||||
"type": "user"
|
||||
},
|
||||
{
|
||||
"speaker": "值机人员",
|
||||
"text": "Certainly! What's your flight number and destination?",
|
||||
"type": "other"
|
||||
},
|
||||
{
|
||||
"speaker": "我",
|
||||
"question": "请输入您的航班号和目的地(例如:CA1234 to Beijing):",
|
||||
"type": "user_question"
|
||||
},
|
||||
{
|
||||
"speaker": "值机人员",
|
||||
"text": "Thank you! Do you have any baggage to check?",
|
||||
"type": "other"
|
||||
},
|
||||
{
|
||||
"speaker": "我",
|
||||
"question": "请回答是否有行李要托运(例如:Yes, one suitcase please. 或 No, just carry-on.):",
|
||||
"type": "user_question"
|
||||
},
|
||||
{
|
||||
"speaker": "值机人员",
|
||||
"text": "Perfect! Here's your boarding pass...",
|
||||
"type": "other"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
1
english-learning-uv
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 5db1a62e0f38a65f5c393f2102ea72ec7f5c0813
|
||||
@ -1,66 +0,0 @@
|
||||
# HTML与Python集成指南
|
||||
|
||||
## 技术限制说明
|
||||
|
||||
HTML文件**不能直接引用或执行Python文件**,这是因为:
|
||||
|
||||
1. **运行环境不同**:
|
||||
- HTML/JavaScript 运行在浏览器端(客户端)
|
||||
- Python 运行在服务器端或本地命令行环境
|
||||
|
||||
2. **执行方式不同**:
|
||||
- JavaScript 是解释型语言,浏览器内置了JavaScript引擎
|
||||
- Python 需要安装Python解释器才能执行
|
||||
|
||||
3. **语法和API完全不同**:
|
||||
- HTML只能识别并加载JavaScript文件(通过`<script>`标签)
|
||||
- Python文件(.py)无法被浏览器直接解析和执行
|
||||
|
||||
## 可行的解决方案
|
||||
|
||||
### 方案1:使用PyScript(推荐)
|
||||
|
||||
PyScript是一个允许在浏览器中直接运行Python代码的框架。它提供了以下功能:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Python in HTML</title>
|
||||
<!-- 引入PyScript -->
|
||||
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css">
|
||||
<script defer src="https://pyscript.net/latest/pyscript.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 直接在HTML中编写Python代码 -->
|
||||
<py-script>
|
||||
print("Hello from Python in HTML!")
|
||||
# 可以导入Python模块和使用Python语法
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 方案2:创建Web应用(高级)
|
||||
|
||||
使用Python Web框架(如Flask、Django)创建完整的Web应用:
|
||||
|
||||
1. **服务器端**:使用Python处理业务逻辑和API调用
|
||||
2. **客户端**:使用HTML/JavaScript构建用户界面
|
||||
3. **通信**:通过AJAX/API实现前后端数据交互
|
||||
|
||||
### 方案3:保留现有两个版本
|
||||
|
||||
- **网页版**:继续使用index.html + data.js + script.js
|
||||
- **命令行版**:使用Python版(main.py + data.py + script.py)
|
||||
|
||||
## 建议
|
||||
|
||||
如果您希望:
|
||||
- **快速使用**:保留两个版本,分别使用不同的方式运行
|
||||
- **网页界面+Python功能**:尝试PyScript方案
|
||||
- **完整Web应用**:学习并使用Python Web框架
|
||||
|
||||
请根据您的需求选择合适的方案。
|
||||
BIN
images/0.png
|
Before Width: | Height: | Size: 2.3 MiB |
BIN
images/1.png
|
Before Width: | Height: | Size: 3.8 MiB |
BIN
images/2.png
|
Before Width: | Height: | Size: 3.1 MiB |
BIN
images/3.png
|
Before Width: | Height: | Size: 2.7 MiB |
BIN
images/4.png
|
Before Width: | Height: | Size: 2.5 MiB |
BIN
images/5.png
|
Before Width: | Height: | Size: 2.3 MiB |
BIN
images/6.png
|
Before Width: | Height: | Size: 1.7 MiB |
BIN
images/7.png
|
Before Width: | Height: | Size: 3.0 MiB |
BIN
images/8.png
|
Before Width: | Height: | Size: 4.6 MiB |
BIN
images/9.png
|
Before Width: | Height: | Size: 2.9 MiB |
104
index.html
@ -3,77 +3,83 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>日常英语学习 - 场景对话练习</title>
|
||||
<title>斗地主游戏</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<h1>日常英语学习</h1>
|
||||
<p class="subtitle">场景对话练习</p>
|
||||
<div class="game-container">
|
||||
<header class="game-header">
|
||||
<h1>斗地主游戏</h1>
|
||||
<div class="game-info">
|
||||
<span id="game-status">等待开始游戏</span>
|
||||
<span id="current-player">当前玩家:</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="status-bar">
|
||||
<div class="score">
|
||||
<span id="score">得分: 0</span>
|
||||
<main class="game-table">
|
||||
<!-- 地主牌区域 -->
|
||||
<div class="landlord-cards">
|
||||
<div id="landlord-indicator">地主牌:</div>
|
||||
<div id="landlord-cards-area"></div>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<span id="progress">进度: 1/10</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="scene-info">
|
||||
<h2 id="scene-title">餐厅点餐</h2>
|
||||
<p id="scene-description">在餐厅点餐的日常对话练习</p>
|
||||
<div class="scene-image-container">
|
||||
<img id="scene-image" src="" alt="场景插图" />
|
||||
<!-- 电脑玩家1(上) -->
|
||||
<div class="player player-1">
|
||||
<div class="player-info">
|
||||
<span class="player-name">电脑玩家1</span>
|
||||
<span id="player-1-cards-count" class="cards-count">17</span>
|
||||
<span id="player-1-role" class="player-role"></span>
|
||||
</div>
|
||||
<div id="player-1-cards" class="player-cards"></div>
|
||||
<div id="player-1-last-play" class="last-play"></div>
|
||||
</div>
|
||||
|
||||
<div class="conversation-area">
|
||||
<div id="conversation" class="conversation">
|
||||
<!-- 对话内容将通过JavaScript动态添加 -->
|
||||
<!-- 电脑玩家2(右) -->
|
||||
<div class="player player-2">
|
||||
<div class="player-info">
|
||||
<span class="player-name">电脑玩家2</span>
|
||||
<span id="player-2-cards-count" class="cards-count">17</span>
|
||||
<span id="player-2-role" class="player-role"></span>
|
||||
</div>
|
||||
<div id="player-2-cards" class="player-cards"></div>
|
||||
<div id="player-2-last-play" class="last-play"></div>
|
||||
</div>
|
||||
|
||||
<div id="question-area" class="question-area" style="display: none;">
|
||||
<div class="question">
|
||||
<h3 id="question-text">请选择合适的回答:</h3>
|
||||
<div class="options">
|
||||
<button class="option" data-index="0">选项1</button>
|
||||
<button class="option" data-index="1">选项2</button>
|
||||
<button class="option" data-index="2">选项3</button>
|
||||
<button class="option" data-index="3">选项4</button>
|
||||
</div>
|
||||
<!-- 当前出牌区域 -->
|
||||
<div class="current-play-area">
|
||||
<div id="current-play-info">当前出牌:</div>
|
||||
<div id="current-play-cards"></div>
|
||||
</div>
|
||||
|
||||
<!-- 玩家自己(下) -->
|
||||
<div class="player player-self">
|
||||
<div class="player-info">
|
||||
<span class="player-name">我</span>
|
||||
<span id="self-cards-count" class="cards-count">17</span>
|
||||
<span id="self-role" class="player-role"></span>
|
||||
</div>
|
||||
<div id="self-cards" class="player-cards"></div>
|
||||
<div id="self-last-play" class="last-play"></div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div id="feedback" class="feedback" style="display: none;">
|
||||
<p id="feedback-text">反馈信息</p>
|
||||
<div class="game-controls">
|
||||
<button id="start-game">开始游戏</button>
|
||||
<div class="landlord-buttons" style="display: none;">
|
||||
<button id="call-landlord">叫地主</button>
|
||||
<button id="no-call">不叫</button>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button id="start-button" class="btn btn-primary">开始学习</button>
|
||||
<button id="reset-button" class="btn btn-secondary">重新开始</button>
|
||||
<button id="next-scene-button" class="btn btn-success" disabled>下一个场景</button>
|
||||
<div class="play-buttons" style="display: none;">
|
||||
<button id="play-cards">出牌</button>
|
||||
<button id="pass">不出</button>
|
||||
</div>
|
||||
|
||||
<!-- 实时对话功能区域 -->
|
||||
<div class="real-time-chat" id="real-time-chat">
|
||||
<h3>实时英语对话</h3>
|
||||
<div id="chat-history" class="chat-history">
|
||||
<!-- 实时对话历史 -->
|
||||
</div>
|
||||
<div class="chat-input">
|
||||
<input type="text" id="chat-message" placeholder="输入英文句子进行实时对话..." />
|
||||
<button id="send-button" class="btn btn-primary">发送</button>
|
||||
</div>
|
||||
<div class="game-stats">
|
||||
<span>积分:</span>
|
||||
<span id="score">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="data.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
33
main.py
@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
英语学习程序入口文件
|
||||
"""
|
||||
|
||||
from script import EnglishLearningApp
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("英语学习程序")
|
||||
print("=" * 50)
|
||||
print("这是一个交互式的英语对话练习程序")
|
||||
print("通过模拟日常场景,帮助您提高英语对话能力")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
# 创建应用实例
|
||||
app = EnglishLearningApp()
|
||||
|
||||
# 启动应用
|
||||
app.start()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n程序已中断")
|
||||
print("感谢使用英语学习程序!")
|
||||
except Exception as e:
|
||||
print(f"\n\n程序发生错误: {e}")
|
||||
print("请检查配置或联系技术支持")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,7 +0,0 @@
|
||||
[project]
|
||||
name = "english-learning-uv"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
329
script.py
@ -1,329 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
英语学习程序主脚本(Python版本)
|
||||
实现对话流程、API集成和用户交互功能
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
import os
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
# 导入数据
|
||||
from data import english_data
|
||||
|
||||
class GameState:
|
||||
"""游戏状态管理类"""
|
||||
def __init__(self):
|
||||
self.current_scene_index = 0
|
||||
self.current_dialog_index = 0
|
||||
self.score = 0
|
||||
self.total_questions = 0
|
||||
self.correct_answers = 0
|
||||
|
||||
def reset(self):
|
||||
"""重置游戏状态"""
|
||||
self.current_scene_index = 0
|
||||
self.current_dialog_index = 0
|
||||
self.score = 0
|
||||
self.total_questions = 0
|
||||
self.correct_answers = 0
|
||||
|
||||
class EnglishLearningApp:
|
||||
"""英语学习应用主类"""
|
||||
def __init__(self):
|
||||
self.game_state = GameState()
|
||||
self.current_scene = None
|
||||
self.api_key = os.getenv("DEEPSEEK_API_KEY", "your-api-key-here")
|
||||
self.api_url = "https://api.deepseek.com/v1/chat/completions"
|
||||
|
||||
def load_scene(self, scene_index: int) -> bool:
|
||||
"""加载指定场景"""
|
||||
if scene_index < 0 or scene_index >= len(english_data["scenes"]):
|
||||
print("所有场景已完成!")
|
||||
return False
|
||||
|
||||
self.current_scene = english_data["scenes"][scene_index]
|
||||
self.game_state.current_scene_index = scene_index
|
||||
self.game_state.current_dialog_index = 0
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print(f"场景:{self.current_scene['title']}")
|
||||
print(f"描述:{self.current_scene['description']}")
|
||||
print("=" * 50)
|
||||
|
||||
# 加载场景图片(在命令行环境下仅显示图片路径)
|
||||
self._load_scene_image()
|
||||
|
||||
# 开始对话
|
||||
self.show_next_dialog()
|
||||
return True
|
||||
|
||||
def _load_scene_image(self):
|
||||
"""加载场景图片"""
|
||||
scene = self.current_scene
|
||||
image_path = scene.get("image", "")
|
||||
|
||||
if not image_path:
|
||||
# 自动查找图片
|
||||
possible_extensions = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".svg"]
|
||||
base_name_id = str(scene["id"])
|
||||
|
||||
# 简单的拼音转换(仅保留字母数字)
|
||||
base_name_title = ''.join(c for c in scene["title"] if c.isalnum()).lower()
|
||||
|
||||
all_possible_paths = [
|
||||
# 优先使用ID命名
|
||||
*[f"images/{base_name_id}{ext}" for ext in possible_extensions],
|
||||
# 支持标题命名
|
||||
*[f"images/{base_name_title}{ext}" for ext in possible_extensions]
|
||||
]
|
||||
|
||||
for path in all_possible_paths:
|
||||
if os.path.exists(path):
|
||||
image_path = path
|
||||
break
|
||||
|
||||
if image_path and os.path.exists(image_path):
|
||||
print(f"图片:{image_path}")
|
||||
else:
|
||||
print("图片:无")
|
||||
|
||||
def show_next_dialog(self):
|
||||
"""显示下一条对话"""
|
||||
if not self.current_scene:
|
||||
return
|
||||
|
||||
conversation = self.current_scene["conversation"]
|
||||
dialog_index = self.game_state.current_dialog_index
|
||||
|
||||
if dialog_index >= len(conversation):
|
||||
# 当前场景对话结束
|
||||
self._scene_complete()
|
||||
return
|
||||
|
||||
dialog = conversation[dialog_index]
|
||||
|
||||
if dialog["type"] == "user_question":
|
||||
# 区分选择题和自由回答题
|
||||
if "options" in dialog:
|
||||
self._show_question(dialog)
|
||||
else:
|
||||
self._show_dynamic_question(dialog)
|
||||
else:
|
||||
# 显示普通对话
|
||||
self._add_message(dialog)
|
||||
|
||||
# 自动显示下一个对话
|
||||
self.game_state.current_dialog_index += 1
|
||||
time.sleep(1)
|
||||
self.show_next_dialog()
|
||||
|
||||
def _add_message(self, dialog: Dict):
|
||||
"""添加对话消息"""
|
||||
speaker = dialog["speaker"]
|
||||
text = dialog["text"]
|
||||
print(f"\n{speaker}: {text}")
|
||||
|
||||
def _show_question(self, dialog: Dict):
|
||||
"""显示选择题"""
|
||||
print(f"\n{dialog['question']}")
|
||||
|
||||
for i, option in enumerate(dialog["options"]):
|
||||
print(f"{i + 1}. {option}")
|
||||
|
||||
# 获取用户输入
|
||||
try:
|
||||
user_choice = int(input("请输入选项编号:")) - 1
|
||||
|
||||
if 0 <= user_choice < len(dialog["options"]):
|
||||
self._check_answer(dialog, user_choice)
|
||||
else:
|
||||
print("无效的选项,请重新选择!")
|
||||
self._show_question(dialog)
|
||||
except ValueError:
|
||||
print("请输入有效的数字!")
|
||||
self._show_question(dialog)
|
||||
|
||||
def _show_dynamic_question(self, dialog: Dict):
|
||||
"""显示动态问题(使用API调用)"""
|
||||
print(f"\n{dialog['question']}")
|
||||
user_input = input("请输入您的回答:")
|
||||
|
||||
# 保存用户输入到对话历史
|
||||
self._save_user_input(dialog, user_input)
|
||||
|
||||
# 获取AI响应
|
||||
ai_response = self._get_ai_response(user_input)
|
||||
|
||||
# 显示AI响应
|
||||
print(f"\nAI响应: {ai_response}")
|
||||
|
||||
# 继续对话
|
||||
self.game_state.current_dialog_index += 2 # 跳过用户问题和AI响应
|
||||
self.show_next_dialog()
|
||||
|
||||
def _save_user_input(self, dialog: Dict, user_input: str):
|
||||
"""保存用户输入到对话历史"""
|
||||
if not self.current_scene:
|
||||
return
|
||||
|
||||
conversation = self.current_scene["conversation"]
|
||||
dialog_index = self.game_state.current_dialog_index
|
||||
|
||||
# 插入用户输入
|
||||
user_message = {
|
||||
"speaker": "我",
|
||||
"text": user_input,
|
||||
"type": "user"
|
||||
}
|
||||
|
||||
conversation.insert(dialog_index + 1, user_message)
|
||||
|
||||
# 插入AI响应占位符
|
||||
ai_message = {
|
||||
"speaker": "AI",
|
||||
"text": "",
|
||||
"type": "other"
|
||||
}
|
||||
|
||||
conversation.insert(dialog_index + 2, ai_message)
|
||||
|
||||
def _get_ai_response(self, user_input: str) -> str:
|
||||
"""获取AI响应"""
|
||||
if not self.api_key:
|
||||
return "抱歉,API密钥未配置,无法获取AI响应。"
|
||||
|
||||
try:
|
||||
# 构建对话历史
|
||||
messages = [
|
||||
{"role": "system", "content": "你是一位英语学习助手,帮助用户进行日常对话练习。请用英语回复,保持对话自然流畅。"}
|
||||
]
|
||||
|
||||
# 添加当前对话历史
|
||||
if self.current_scene:
|
||||
for dialog in self.current_scene["conversation"][:self.game_state.current_dialog_index + 1]:
|
||||
role = "assistant" if dialog["speaker"] != "我" else "user"
|
||||
content = dialog.get("text", dialog.get("question", ""))
|
||||
messages.append({"role": role, "content": content})
|
||||
|
||||
# 添加用户最新输入
|
||||
messages.append({"role": "user", "content": user_input})
|
||||
|
||||
# 调用API
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"model": "deepseek-chat",
|
||||
"messages": messages,
|
||||
"stream": True
|
||||
}
|
||||
|
||||
response = requests.post(self.api_url, headers=headers, json=payload, stream=True)
|
||||
response.raise_for_status()
|
||||
|
||||
# 处理流式响应
|
||||
ai_response = ""
|
||||
print("\nAI正在思考...", end="", flush=True)
|
||||
|
||||
for chunk in response.iter_content(chunk_size=None):
|
||||
if chunk:
|
||||
decoded_chunk = chunk.decode("utf-8")
|
||||
# 解析SSE格式
|
||||
lines = decoded_chunk.split("\n")
|
||||
for line in lines:
|
||||
if line.startswith("data: "):
|
||||
data = line[6:].strip()
|
||||
if data != "[DONE]":
|
||||
try:
|
||||
json_data = json.loads(data)
|
||||
if "choices" in json_data and json_data["choices"]:
|
||||
delta = json_data["choices"][0].get("delta", {})
|
||||
if "content" in delta:
|
||||
content = delta["content"]
|
||||
ai_response += content
|
||||
print(content, end="", flush=True)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
print() # 换行
|
||||
return ai_response if ai_response else "抱歉,无法生成响应。"
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"\nAPI调用错误: {e}")
|
||||
return "抱歉,API调用失败。"
|
||||
|
||||
def _check_answer(self, dialog: Dict, user_choice: int):
|
||||
"""检查答案"""
|
||||
is_correct = user_choice == dialog["correctAnswer"]
|
||||
|
||||
if is_correct:
|
||||
self.game_state.correct_answers += 1
|
||||
print("\n✅ 回答正确!")
|
||||
else:
|
||||
correct_answer = dialog["options"][dialog["correctAnswer"]]
|
||||
print(f"\n❌ 回答错误!正确答案是:{correct_answer}")
|
||||
|
||||
self.game_state.total_questions += 1
|
||||
self.game_state.score += 1 if is_correct else 0
|
||||
|
||||
# 更新进度
|
||||
self.update_progress()
|
||||
|
||||
# 继续对话
|
||||
self.game_state.current_dialog_index += 1
|
||||
time.sleep(1)
|
||||
self.show_next_dialog()
|
||||
|
||||
def _scene_complete(self):
|
||||
"""场景完成"""
|
||||
print("\n" + "=" * 50)
|
||||
print("当前场景对话已完成!")
|
||||
print("=" * 50)
|
||||
|
||||
if self.game_state.current_scene_index < len(english_data["scenes"]) - 1:
|
||||
# 询问是否进入下一个场景
|
||||
next_scene = input("是否进入下一个场景?(y/n): ")
|
||||
if next_scene.lower() == "y":
|
||||
next_index = self.game_state.current_scene_index + 1
|
||||
self.load_scene(next_index)
|
||||
else:
|
||||
print("\n🎉 所有场景已完成!")
|
||||
self._show_final_score()
|
||||
|
||||
def _show_final_score(self):
|
||||
"""显示最终得分"""
|
||||
if self.game_state.total_questions > 0:
|
||||
accuracy = (self.game_state.correct_answers / self.game_state.total_questions) * 100
|
||||
print(f"\n最终得分: {self.game_state.score}")
|
||||
print(f"正确率: {accuracy:.1f}%")
|
||||
print(f"总题目数: {self.game_state.total_questions}")
|
||||
print(f"正确答案: {self.game_state.correct_answers}")
|
||||
else:
|
||||
print("\n未完成任何题目")
|
||||
|
||||
def update_progress(self):
|
||||
"""更新进度信息"""
|
||||
total_scenes = len(english_data["scenes"])
|
||||
current_scene = self.game_state.current_scene_index + 1
|
||||
print(f"\n进度:场景 {current_scene}/{total_scenes} | 得分:{self.game_state.score}")
|
||||
|
||||
def start(self):
|
||||
"""启动应用"""
|
||||
print("欢迎使用英语学习程序!")
|
||||
print("=" * 50)
|
||||
|
||||
# 加载第一个场景
|
||||
self.load_scene(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 创建应用实例
|
||||
app = EnglishLearningApp()
|
||||
|
||||
# 启动应用
|
||||
app.start()
|
||||
541
style.css
@ -1,4 +1,3 @@
|
||||
/* 全局样式 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@ -7,373 +6,349 @@
|
||||
|
||||
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;
|
||||
background-color: #1a532a;
|
||||
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;
|
||||
min-height: 100vh;
|
||||
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;
|
||||
.game-container {
|
||||
width: 90vw;
|
||||
max-width: 1200px;
|
||||
min-height: 90vh;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 20px;
|
||||
padding: 20px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e9ecef;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.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;
|
||||
.game-header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.options {
|
||||
.game-header h1 {
|
||||
font-size: 2.5em;
|
||||
color: #ffd700;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.game-info {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.game-table {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 500px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 地主牌区域 */
|
||||
.landlord-cards {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#landlord-indicator {
|
||||
font-size: 1.2em;
|
||||
margin-bottom: 10px;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
#landlord-cards-area {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
/* 玩家区域 */
|
||||
.player {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.player-info {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.option {
|
||||
padding: 15px 20px;
|
||||
border: 2px solid #e0e0e0;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
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);
|
||||
.player-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.option.selected {
|
||||
border-color: #4a90e2;
|
||||
background-color: #e3f2fd;
|
||||
.cards-count {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
padding: 5px 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.option.correct {
|
||||
border-color: #4caf50;
|
||||
background-color: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
.player-role {
|
||||
color: #ff6b6b;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.option.incorrect {
|
||||
border-color: #f44336;
|
||||
background-color: #ffebee;
|
||||
color: #c62828;
|
||||
.player-cards {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.option.disabled {
|
||||
cursor: not-allowed;
|
||||
.last-play {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
margin-top: 10px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.option.disabled:hover {
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
/* 自己的玩家区域 */
|
||||
.player-self {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
/* 反馈区域样式 */
|
||||
.feedback {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px 20px;
|
||||
.player-self .player-cards {
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
width: 80px;
|
||||
height: 120px;
|
||||
background-color: white;
|
||||
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;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 5px;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 120px;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #4a90e2;
|
||||
color: white;
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #357abd;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
.card.selected {
|
||||
border-color: #ffd700;
|
||||
background-color: #fff9c4;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #90a4ae;
|
||||
color: white;
|
||||
.card-back {
|
||||
background-color: #424242;
|
||||
background-image: radial-gradient(circle, #666 20%, transparent 20%),
|
||||
radial-gradient(circle, #666 20%, transparent 20%);
|
||||
background-size: 10px 10px;
|
||||
background-position: 0 0, 5px 5px;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #78909c;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
.card .rank-top {
|
||||
font-size: 1.5em;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
.card .suit-middle {
|
||||
font-size: 2em;
|
||||
align-self: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background-color: #388e3c;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
.card .rank-bottom {
|
||||
font-size: 1.5em;
|
||||
align-self: flex-end;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
/* 花色颜色 */
|
||||
.card.spades, .card.clubs {
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
/* 实时对话功能样式 */
|
||||
.real-time-chat {
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 2px solid #e8f0fe;
|
||||
.card.hearts, .card.diamonds {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.real-time-chat h3 {
|
||||
color: #4a90e2;
|
||||
margin-bottom: 15px;
|
||||
/* 大王小王 */
|
||||
.card.joker-big .suit-middle,
|
||||
.card.joker-small .suit-middle {
|
||||
color: black;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.chat-history {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e9ecef;
|
||||
margin-bottom: 15px;
|
||||
/* 当前出牌区域 */
|
||||
.current-play-area {
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
#current-play-info {
|
||||
font-size: 1.2em;
|
||||
margin-bottom: 10px;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
#current-play-cards {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
#chat-message {
|
||||
flex: 1;
|
||||
padding: 12px 15px;
|
||||
border: 2px solid #e0e0e0;
|
||||
/* 游戏控制按钮 */
|
||||
.game-controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 24px;
|
||||
font-size: 1.1em;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#chat-message:focus {
|
||||
outline: none;
|
||||
border-color: #4a90e2;
|
||||
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.chat-input .btn {
|
||||
min-width: 100px;
|
||||
button:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: #cccccc;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* 叫地主按钮区域 */
|
||||
.landlord-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
#call-landlord {
|
||||
background-color: #ff9800;
|
||||
}
|
||||
|
||||
#call-landlord:hover {
|
||||
background-color: #f57c00;
|
||||
}
|
||||
|
||||
#no-call {
|
||||
background-color: #f44336;
|
||||
}
|
||||
|
||||
#no-call:hover {
|
||||
background-color: #da190b;
|
||||
}
|
||||
|
||||
/* 出牌按钮区域 */
|
||||
.play-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
#play-cards {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
|
||||
#play-cards:hover {
|
||||
background-color: #0b7dda;
|
||||
}
|
||||
|
||||
#pass {
|
||||
background-color: #9e9e9e;
|
||||
}
|
||||
|
||||
#pass:hover {
|
||||
background-color: #757575;
|
||||
}
|
||||
|
||||
/* 游戏统计 */
|
||||
.game-stats {
|
||||
margin-left: 30px;
|
||||
font-size: 1.2em;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
.game-container {
|
||||
width: 98vw;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
.game-header h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px;
|
||||
.card {
|
||||
width: 60px;
|
||||
height: 90px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
flex-direction: column;
|
||||
.player-info {
|
||||
font-size: 1em;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 95%;
|
||||
.game-controls {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.card {
|
||||
width: 50px;
|
||||
height: 75px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.game-info {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
.game-controls {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chat-input .btn {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||