|
|
class TypingLogic:
|
|
|
def __init__(self, learning_content: str):
|
|
|
"""
|
|
|
初始化打字逻辑状态。。。。
|
|
|
- 存储学习材料
|
|
|
- 初始化当前索引为0
|
|
|
- 初始化错误计数为0
|
|
|
"""
|
|
|
self.learning_content = learning_content
|
|
|
self.current_index = 0
|
|
|
self.error_count = 0
|
|
|
self.total_chars = len(learning_content)
|
|
|
self.typed_chars = 0
|
|
|
self.image_positions = [] # 存储图片位置信息
|
|
|
self.image_data = {} # 存储图片数据 {图片名称: 二进制数据}
|
|
|
self.image_display_queue = [] # 待显示的图片队列
|
|
|
|
|
|
def check_input(self, user_text: str) -> dict:
|
|
|
"""
|
|
|
检查用户输入与学习材料的匹配情况
|
|
|
- 逐字符比较逻辑
|
|
|
- 支持中文整词匹配
|
|
|
- 进度跟踪
|
|
|
- 准确率计算
|
|
|
- 返回字典包含:
|
|
|
* correct: 布尔值,当前输入是否正确
|
|
|
* expected: 字符串,当前期望的字符
|
|
|
* position: 整数,当前位置
|
|
|
* completed: 布尔值,是否完成
|
|
|
* accuracy: 浮点数,准确率
|
|
|
"""
|
|
|
# 保存当前索引用于返回
|
|
|
current_position = len(user_text)
|
|
|
|
|
|
# 临时保存原始的typed_chars值用于准确率计算
|
|
|
original_typed_chars = self.typed_chars
|
|
|
|
|
|
# 更新已输入字符数
|
|
|
self.typed_chars = len(user_text)
|
|
|
|
|
|
# 如果用户输入的字符数超过了学习材料的长度,截取到相同长度
|
|
|
if len(user_text) > self.total_chars:
|
|
|
user_text = user_text[:self.total_chars]
|
|
|
current_position = len(user_text)
|
|
|
|
|
|
# 检查当前输入是否正确(支持中文整词匹配)
|
|
|
correct = True
|
|
|
expected_char = ''
|
|
|
if self.current_index < self.total_chars:
|
|
|
expected_char = self.learning_content[self.current_index]
|
|
|
|
|
|
# 中文整词匹配优化
|
|
|
# 如果当前位置是中文文本的开始,尝试进行整词匹配
|
|
|
if self._is_chinese_text_start(self.current_index):
|
|
|
# 获取期望的中文词组
|
|
|
expected_word = self._get_chinese_word_at(self.current_index)
|
|
|
# 获取用户输入的对应部分
|
|
|
user_word = user_text[self.current_index:min(self.current_index + len(expected_word), len(user_text))]
|
|
|
|
|
|
# 如果用户输入的词组与期望词组匹配,则认为是正确的
|
|
|
if user_word == expected_word:
|
|
|
correct = True
|
|
|
else:
|
|
|
# 如果整词不匹配,回退到逐字符匹配
|
|
|
if len(user_text) > self.current_index and user_text[self.current_index] != expected_char:
|
|
|
correct = False
|
|
|
else:
|
|
|
# 非中文词组开始位置,使用逐字符匹配
|
|
|
if len(user_text) > self.current_index and user_text[self.current_index] != expected_char:
|
|
|
correct = False
|
|
|
else:
|
|
|
# 已经完成所有输入
|
|
|
# 恢复原始的typed_chars值用于准确率计算
|
|
|
accuracy = self._calculate_accuracy()
|
|
|
self.typed_chars = original_typed_chars
|
|
|
return {
|
|
|
"correct": True,
|
|
|
"expected": "",
|
|
|
"position": self.current_index,
|
|
|
"completed": True,
|
|
|
"accuracy": accuracy
|
|
|
}
|
|
|
|
|
|
# 检查是否完成
|
|
|
completed = current_position >= self.total_chars
|
|
|
|
|
|
# 计算准确率
|
|
|
accuracy = self._calculate_accuracy()
|
|
|
# 恢复原始的typed_chars值
|
|
|
self.typed_chars = original_typed_chars
|
|
|
|
|
|
return {
|
|
|
"correct": correct,
|
|
|
"expected": expected_char,
|
|
|
"position": current_position,
|
|
|
"completed": completed,
|
|
|
"accuracy": accuracy
|
|
|
}
|
|
|
|
|
|
def update_position(self, user_text: str) -> dict:
|
|
|
"""
|
|
|
更新当前位置并计算错误数
|
|
|
- 支持中文整词匹配
|
|
|
- 逐字符比较逻辑(回退机制)
|
|
|
- 错误计数
|
|
|
- 位置更新
|
|
|
- 返回字典包含:
|
|
|
* new_position: 整数,新的位置
|
|
|
* error_count: 整数,错误数
|
|
|
* completed: 布尔值,是否完成
|
|
|
"""
|
|
|
# 更新当前索引位置
|
|
|
self.current_index = len(user_text)
|
|
|
|
|
|
# 重置错误计数
|
|
|
self.error_count = 0
|
|
|
|
|
|
# 使用中文整词匹配优化错误计算
|
|
|
self._calculate_errors_with_chinese_support(user_text)
|
|
|
|
|
|
# 检查是否完成
|
|
|
completed = self.current_index >= self.total_chars
|
|
|
|
|
|
return {
|
|
|
"new_position": self.current_index,
|
|
|
"error_count": self.error_count,
|
|
|
"completed": completed
|
|
|
}
|
|
|
|
|
|
def get_expected_text(self, length: int = 10) -> str:
|
|
|
"""
|
|
|
获取用户接下来应该输入的内容
|
|
|
- 返回从当前位置开始的一定长度文本(如10个字符)
|
|
|
- 处理文本结束情况
|
|
|
"""
|
|
|
start_pos = self.current_index
|
|
|
end_pos = min(start_pos + length, self.total_chars)
|
|
|
return self.learning_content[start_pos:end_pos]
|
|
|
|
|
|
def get_progress(self) -> dict:
|
|
|
"""
|
|
|
获取当前学习进度统计
|
|
|
- current: 整数,当前位置
|
|
|
- total: 整数,总长度
|
|
|
- percentage: 浮点数,完成百分比
|
|
|
- remaining: 整数,剩余字符数
|
|
|
"""
|
|
|
current = self.current_index
|
|
|
total = self.total_chars
|
|
|
percentage = (current / total * 100) if total > 0 else 0
|
|
|
remaining = max(0, total - current)
|
|
|
|
|
|
return {
|
|
|
"current": current,
|
|
|
"total": total,
|
|
|
"percentage": percentage,
|
|
|
"remaining": remaining
|
|
|
}
|
|
|
|
|
|
def reset(self, new_content: str = None):
|
|
|
"""
|
|
|
重置打字状态
|
|
|
- 重置当前索引为0
|
|
|
- 重置错误计数
|
|
|
- 如果提供了新内容,更新学习材料
|
|
|
"""
|
|
|
if new_content is not None:
|
|
|
self.learning_content = new_content
|
|
|
self.total_chars = len(new_content)
|
|
|
self.current_index = 0
|
|
|
self.error_count = 0
|
|
|
self.typed_chars = 0
|
|
|
self.image_positions = [] # 重置图片位置信息
|
|
|
self.image_data = {} # 重置图片数据
|
|
|
self.image_display_queue = [] # 重置图片显示队列
|
|
|
|
|
|
def get_statistics(self) -> dict:
|
|
|
"""
|
|
|
获取打字统计信息
|
|
|
- total_chars: 整数,总字符数
|
|
|
- typed_chars: 整数,已输入字符数
|
|
|
- error_count: 整数,错误次数
|
|
|
- accuracy_rate: 浮点数,准确率
|
|
|
"""
|
|
|
return {
|
|
|
"total_chars": self.total_chars,
|
|
|
"typed_chars": self.typed_chars,
|
|
|
"error_count": self.error_count,
|
|
|
"accuracy_rate": self._calculate_accuracy()
|
|
|
}
|
|
|
|
|
|
def set_image_positions(self, image_positions: list):
|
|
|
"""
|
|
|
设置图片位置信息
|
|
|
- image_positions: 列表,包含图片位置信息
|
|
|
"""
|
|
|
self.image_positions = image_positions
|
|
|
|
|
|
def get_current_image_info(self, position: int) -> dict:
|
|
|
"""
|
|
|
获取当前位置的图片信息
|
|
|
- position: 整数,当前输入位置
|
|
|
- 返回字典包含图片信息,如果没有图片返回None
|
|
|
"""
|
|
|
for img_info in self.image_positions:
|
|
|
if img_info['start_pos'] <= position <= img_info['end_pos']:
|
|
|
return img_info
|
|
|
return None
|
|
|
|
|
|
def set_image_data(self, image_data: dict):
|
|
|
"""
|
|
|
设置图片数据
|
|
|
- image_data: 字典,{图片名称: 二进制数据}
|
|
|
"""
|
|
|
self.image_data = image_data
|
|
|
|
|
|
def get_images_to_display(self, current_position: int) -> list:
|
|
|
"""
|
|
|
获取在当前位置需要显示的图片
|
|
|
- current_position: 整数,当前输入位置
|
|
|
- 返回图片信息列表
|
|
|
"""
|
|
|
images_to_display = []
|
|
|
for img_info in self.image_positions:
|
|
|
if img_info['start_pos'] <= current_position <= img_info['end_pos']:
|
|
|
# 尝试获取图片名称(支持多种键名)
|
|
|
image_name = img_info.get('image_name', '') or img_info.get('filename', '')
|
|
|
if image_name in self.image_data:
|
|
|
img_info_copy = img_info.copy()
|
|
|
img_info_copy['image_data'] = self.image_data[image_name]
|
|
|
images_to_display.append(img_info_copy)
|
|
|
return images_to_display
|
|
|
|
|
|
def should_show_image(self, current_position: int) -> bool:
|
|
|
"""
|
|
|
检查在当前位置是否应该显示图片
|
|
|
- current_position: 整数,当前输入位置
|
|
|
- 返回布尔值
|
|
|
"""
|
|
|
return len(self.get_images_to_display(current_position)) > 0
|
|
|
|
|
|
def check_image_at_position(self, position: int) -> bool:
|
|
|
"""
|
|
|
检查指定位置是否有图片
|
|
|
- position: 整数,位置索引
|
|
|
- 返回布尔值,该位置是否有图片
|
|
|
"""
|
|
|
return self.get_current_image_info(position) is not None
|
|
|
|
|
|
def _calculate_accuracy(self) -> float:
|
|
|
"""
|
|
|
计算准确率
|
|
|
"""
|
|
|
# 防止递归的保护措施
|
|
|
if hasattr(self, '_calculating_accuracy') and self._calculating_accuracy:
|
|
|
return 0.0
|
|
|
|
|
|
if self.typed_chars == 0:
|
|
|
return 0.0
|
|
|
|
|
|
# 设置递归保护标志
|
|
|
self._calculating_accuracy = True
|
|
|
|
|
|
try:
|
|
|
# 准确率 = (已输入字符数 - 错误次数) / 已输入字符数
|
|
|
accuracy = (self.typed_chars - self.error_count) / self.typed_chars
|
|
|
return max(0.0, min(1.0, accuracy)) # 确保准确率在0.0到1.0之间
|
|
|
except (ZeroDivisionError, RecursionError):
|
|
|
return 0.0
|
|
|
finally:
|
|
|
# 清除递归保护标志
|
|
|
self._calculating_accuracy = False
|
|
|
|
|
|
def _is_chinese_text_start(self, index: int) -> bool:
|
|
|
"""
|
|
|
判断指定位置是否是中文文本的开始
|
|
|
- 检查当前字符是否为中文
|
|
|
- 检查前一个字符是否为非中文或空格
|
|
|
"""
|
|
|
if index < 0 or index >= len(self.learning_content):
|
|
|
return False
|
|
|
|
|
|
current_char = self.learning_content[index]
|
|
|
|
|
|
# 检查当前字符是否为中文
|
|
|
if not self._is_chinese_char(current_char):
|
|
|
return False
|
|
|
|
|
|
# 如果是第一个字符,或者是紧跟在非中文字符后的中文,则认为是中文文本的开始
|
|
|
if index == 0:
|
|
|
return True
|
|
|
|
|
|
prev_char = self.learning_content[index - 1]
|
|
|
return not self._is_chinese_char(prev_char) or prev_char.isspace()
|
|
|
|
|
|
def _is_chinese_char(self, char: str) -> bool:
|
|
|
"""
|
|
|
判断字符是否为中文
|
|
|
- 使用Unicode范围判断中文字符
|
|
|
"""
|
|
|
return '\u4e00' <= char <= '\u9fff' or '\u3400' <= char <= '\u4dbf'
|
|
|
|
|
|
def _get_chinese_word_at(self, index: int) -> str:
|
|
|
"""
|
|
|
获取指定位置开始的中文词组
|
|
|
- 从当前位置开始,连续获取中文字符
|
|
|
- 遇到非中文字符或字符串结束则停止
|
|
|
"""
|
|
|
if index < 0 or index >= len(self.learning_content):
|
|
|
return ""
|
|
|
|
|
|
word = ""
|
|
|
current_index = index
|
|
|
|
|
|
while current_index < len(self.learning_content):
|
|
|
char = self.learning_content[current_index]
|
|
|
if self._is_chinese_char(char):
|
|
|
word += char
|
|
|
current_index += 1
|
|
|
else:
|
|
|
break
|
|
|
|
|
|
return word
|
|
|
|
|
|
def _calculate_errors_with_chinese_support(self, user_text: str) -> None:
|
|
|
"""
|
|
|
使用中文整词匹配优化错误计算
|
|
|
- 优先尝试中文整词匹配
|
|
|
- 整词匹配失败时回退到逐字符匹配
|
|
|
"""
|
|
|
i = 0
|
|
|
while i < min(len(user_text), len(self.learning_content)):
|
|
|
# 检查当前位置是否为中文文本开始
|
|
|
if self._is_chinese_text_start(i):
|
|
|
# 获取期望的中文词组
|
|
|
expected_word = self._get_chinese_word_at(i)
|
|
|
# 获取用户输入的对应部分
|
|
|
user_word = user_text[i:min(i + len(expected_word), len(user_text))]
|
|
|
|
|
|
# 如果整词匹配成功,跳过整个词组
|
|
|
if user_word == expected_word:
|
|
|
i += len(expected_word)
|
|
|
continue
|
|
|
else:
|
|
|
# 整词匹配失败,回退到逐字符匹配
|
|
|
if user_text[i] != self.learning_content[i]:
|
|
|
self.error_count += 1
|
|
|
else:
|
|
|
# 非中文文本,使用逐字符匹配
|
|
|
if user_text[i] != self.learning_content[i]:
|
|
|
self.error_count += 1
|
|
|
|
|
|
i += 1 |