330 lines
12 KiB
Python
330 lines
12 KiB
Python
#!/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()
|