9 Commits

Author SHA1 Message Date
2eeb60c75b 更新版本号 2025-07-30 01:34:06 +08:00
2c870377a6 修复 ElectronUnion 对象初始化问题 2025-07-30 01:32:31 +08:00
36562323b7 使用 NucleonUnion & ElectronUnion 取代繁琐的 AtomicFiles 2025-07-30 01:11:03 +08:00
b91176241b 小改动 2025-07-28 16:48:46 +08:00
edfeb0b40b README.md 2025-07-27 21:54:50 +08:00
9d658ea646 README.md 2025-07-27 21:54:27 +08:00
5d3c354d8f 中途改进 2025-07-27 21:39:34 +08:00
d27d353374 更新自述文件, 更新 gitignore 2025-07-25 23:57:58 +08:00
3988b55f1f v0.2.5 修复初次激活相关问题 2025-07-25 23:50:07 +08:00
19 changed files with 345 additions and 134 deletions

6
.gitignore vendored
View File

@@ -1 +1,5 @@
.vscode
.vscode
.directory
__pycache__/
scripts/
.idea

0
CONTRIBUTING.md Normal file
View File

View File

@@ -1,21 +1,26 @@
# 潜进 (HeurAMS) - 开放源代码实验型辅助记忆程序
# 潜进 (HeurAMS) - 实验型辅助记忆程序
> 形人而我无形,**则我专而敌分**
## 概述
"潜进" (HeurAMS, 中文含义: 启发式辅助记忆软件) 是一款为古诗词设计的记忆辅助软件, 集成记忆拟合算法、自然语音技术与生成式人工智能, 提供科学的记忆训练解决方案
"潜进" (HeurAMS, 中文含义: 启发式辅助记忆软件) 是为习题册, 古诗词, 及其他问答/记忆/理解型题目设计的记忆辅助软件, 提供优化记忆方案
## 核心特性
### 科学记忆拟合算法
## 技术集成与特性
- 采用经实证的 SM-2 间隔重复算法
### 间隔迭代算法
> 许多出版物都广泛讨论了不同重复间隔对学习效果的影响。特别是,间隔效应被认为是一种普遍现象。间隔效应是指,如果重复的间隔是分散/稀疏的,而不是集中重复,那么学习任务的表现会更好。因此,有观点提出,学习中使用的最佳重复间隔是**最长的、但不会导致遗忘的间隔**。
- 采用经实证的 SM-2 间隔迭代算法, 此算法亦用作 Anki 闪卡记忆软件的默认闪卡调度器
> 计划: 将添加 FSRS 算法 (Anki 的新可选闪卡调度器) 与一种 SM-15 变体算法作为后续替代
> 参考 https://github.com/slaypni/SM-15
> 为什么使用 SM-15 的变体?
> SM-2 后续算法仅有论文, 无具体方程, 故使用一种基于 SM-15 描述实现的变体算法
- 动态优化每首诗词的记忆间隔时间表
- 实时跟踪记忆曲线,最大化长期记忆保留率与稳定性
- 实时跟踪记忆曲线,化长期记忆保留率与稳定性
### 技术集成与优化
- 逐字解析:支持点击查看每个字的详细释义
- 语法分析:接入生成式人工智能, 支持古文结构**交互式**解析
### 学习进程优化
- 逐字解析:支持逐字详细释义解析
- 语法分析:接入生成式人工智能, 支持古文结构交互式解析
- 自然语音:集成微软神经网络文本转语音 (TTS) 技术
### 现代用户界面
@@ -24,34 +29,43 @@
- 支持触屏/鼠标/键盘多操作模式
- 简洁直观的复习流程设计
## 技术架构
## 屏幕截图
![scrshot2](readme_src/image_2.png)
![scrshot1](readme_src/image_1.png)
## 技术架构
> 有关技术与实现的细节, 请参阅 CONTRIBUTING.md
> 提交拉取请求以参与到此开放源代码项目
``` mermaid
graph TD
subgraph 后端
A[SM-2算法] --> B[间隔预测引擎]
B --> C[个性化记忆模型]
A[SM-2 算法] --> B[间隔迭代算法]
B --> C[迭代记忆参数]
end
subgraph 用户界面
D[诗词展示模块] --> E[交互复习界面]
D[展示模块] --> E[用户界面]
E --> F[进度追踪面板]
end
subgraph 外部服务
G[词义解析]
H[语法分析]
I[语音合成]
G[LLM]
H[TTS]
end
C --> D
F -->|用户数据| C
D --> G
D --> H
D --> I
```
## 系统要求
- 平台支持Windows / macOS / Linux / Android(需要 Termux) (终端或浏览器)
- 平台支持Windows / macOS / Linux / Android (需要 Termux 或 Linux) (终端或浏览器)
- 网络连接:可预缓存语音文件, 需联网使用大模型服务功能
## 使用 Nuitka 静态编译
运行
```bash
nuitka --clang --jobs=6 --standalone --onefile main.py
```

Binary file not shown.

View File

@@ -27,7 +27,7 @@ def get_daystamp() -> int:
time_override = config.get("time_override", -1)
if time_override is not None and time_override != -1:
print(f"TIME OVERRIDEED TO {time_override}")
#print(f"TIME OVERRIDEED TO {time_override}")
return int(time_override)
return int(time.time() // (24 * 3600))

View File

@@ -1,7 +1,7 @@
# [调试] 将更改保存到文件
save = 1
# [调试] 覆写时间
time_override = 10
time_override = 11
# 对于每个项目的新记忆核子数量
tasked_number = 12
# 竖屏适配

35
main.py
View File

@@ -12,7 +12,7 @@ import particles as pt
from reactor import Reactor
import auxiliary as aux
ver = '0.2.4'
ver = '0.2.9'
config = aux.ConfigFile("config.toml")
@@ -37,8 +37,8 @@ class MemScreen(Screen):
btn = dict()
def __init__(
self,
nucleon_file: pt.AtomicFile,
electron_file: pt.AtomicFile,
nucleon_file: pt.NucleonUnion,
electron_file: pt.ElectronUnion,
tasked_num
):
super().__init__(name=None, id=None, classes=None)
@@ -57,12 +57,13 @@ class MemScreen(Screen):
yield Static("", id="feedback") # 用于显示反馈
yield Label(self._get_progress_text(), id="progress")
with Container(id="button_container"):
self.btn['5'] = Button("完美回想", variant="success", id="q5", classes="choice")
self.btn['4'] = Button("犹豫后正确", variant="success", id="q4", classes="choice")
self.btn['3'] = Button("困难地正确", variant="warning", id="q3", classes="choice")
self.btn['2'] = Button("错误但熟悉", variant="warning", id="q2", classes="choice")
self.btn['1'] = Button("错误且不熟", variant="error", id="q1", classes="choice")
self.btn['0'] = Button("完全空白", variant="error", id="q0", classes="choice")
if 1:
self.btn['5'] = Button("完美回想", variant="success", id="q5", classes="choice")
self.btn['4'] = Button("犹豫后正确", variant="success", id="q4", classes="choice")
self.btn['3'] = Button("困难地正确", variant="warning", id="q3", classes="choice")
self.btn['2'] = Button("错误但熟悉", variant="warning", id="q2", classes="choice")
self.btn['1'] = Button("错误且不熟", variant="error", id="q1", classes="choice")
self.btn['0'] = Button("完全空白", variant="error", id="q0", classes="choice")
yield Horizontal(self.btn['5'], self.btn['4'])
yield Horizontal(self.btn['3'], self.btn['2'])
yield Horizontal(self.btn['1'], self.btn['0'])
@@ -146,7 +147,7 @@ class PreparationScreen(Screen):
("escape", "quit_app", "退出")
]
def __init__(self, nucleon_file: pt.AtomicFile, electron_file: pt.AtomicFile) -> None:
def __init__(self, nucleon_file: pt.NucleonUnion, electron_file: pt.ElectronUnion) -> None:
super().__init__(name=None, id=None, classes=None)
self.nucleon_file = nucleon_file
self.electron_file = electron_file
@@ -157,12 +158,18 @@ class PreparationScreen(Screen):
yield Label(f"记忆项目: [b]{self.nucleon_file.name}[/b]\n")
yield Label(f"核子文件对象: ./nucleon/[b]{self.nucleon_file.name}[/b].toml")
yield Label(f"电子文件对象: ./electron/[b]{self.electron_file.name}[/b].toml")
yield Label(f"核子数量:{self.nucleon_file.get_len()}")
yield Label(f"核子数量:{len(self.nucleon_file)}")
yield Button("开始记忆", id="start_memorizing_button", variant="primary", classes="start-button")
yield Static(f"\n全文如下:\n")
yield Static(self.nucleon_file.get_full_content(), classes="full")
yield Static(self._get_full_content(), classes="full")
yield Footer()
def _get_full_content(self):
content = ""
for i in self.nucleon_file.nucleons:
content += i['content']
return content
def action_go_back(self):
self.app.pop_screen()
@@ -215,13 +222,13 @@ class FileSelectorScreen(Screen):
return
selected_filename = str(selected_label.renderable)
nucleon_file = pt.AtomicFile(pathlib.Path("./nucleon") / selected_filename, "nucleon")
nucleon_file = pt.NucleonUnion(pathlib.Path("./nucleon") / selected_filename)
electron_file_path = pathlib.Path("./electron") / selected_filename
if electron_file_path.exists():
pass
else:
electron_file_path.touch()
electron_file = pt.AtomicFile(pathlib.Path("./electron") / selected_filename, "electron")
electron_file = pt.ElectronUnion(pathlib.Path("./electron") / selected_filename)
# self.notify(f"已选择: {selected_filename}", timeout=2)
self.app.push_screen(PreparationScreen(nucleon_file, electron_file))

View File

@@ -1,3 +1,18 @@
# 散列表的键翻译
["keydata"]
note = "笔记"
keyword_note = "关键词翻译"
translation = "语句翻译"
# 测试项目元数据
["testdata"]
# 记忆时显示的额外信息
additional_inf = ["translation", "note"]
# 填空测试
fill_blank = ["translation"]
# 选择题测试
draw_card = ["keyword_note"]
["臣密言:臣以险衅, 夙遭闵凶."]
note = []
translation = "臣子李密陈言:我因命运不好,小时候遭遇到了不幸"

View File

@@ -3,51 +3,45 @@ import toml
import time
import auxiliary as aux
import time
class Electron():
"""电子: 记忆分析元数据及算法"""
algorithm = "SM-2" # 暂时使用 SM-2 算法进行记忆拟合, 考虑 SM-15 替代
"""
content = "" # 内容
efactor = 2.5 # 易度系数, 越大越简单, 最大为5
real_rept = 0 # (实际)重复次数
rept = 0 # (有效)重复次数
interval = 0 # 最佳间隔
last_date = 0 # 上一次复习的时间戳
next_date = 0 # 将要复习的时间戳
is_activated = 0 # 激活状态
# *NOTE: 此处"时间戳"是以天为单位的整数, 即 UNIX 时间戳除以一天的秒数取整
last_modify = 0 # 最后修改时间戳(此处是UNIX时间戳)
"""
def __init__(self, content: str, data: dict):
def __init__(self, content: str, metadata: dict):
self.content = content
self.efactor = data.get('efactor', 2.5)
self.real_rept = data.get('real_rept', 0)
self.rept = data.get('rept', 0)
self.interval = data.get('interval', 0)
self.last_date = data.get('last_date', 0)
self.next_date = data.get('next_date', 0)
self.is_activated = data.get('is_activated', 0)
self.last_modify = time.time()
self.metadata = metadata
if metadata == {}:
#print("NULL")
self._default_init()
def _default_init(self):
defaults = {
'efactor': 2.5, # 易度系数, 越大越简单, 最大为5
'real_rept': 0, # (实际)重复次数
'rept': 0, # (有效)重复次数
'interval': 0, # 最佳间隔
'last_date': 0, # 上一次复习的时间戳
'next_date': 0, # 将要复习的时间戳
'is_activated': 0, # 激活状态
# *NOTE: 此处"时间戳"是以天为单位的整数, 即 UNIX 时间戳除以一天的秒数取整
'last_modify': time.time() # 最后修改时间戳(此处是UNIX时间戳)
}
self.metadata = defaults
def activate(self):
self.is_activated = 1
self.metadata['is_activated'] = 1
self.metadata['last_modify'] = time.time()
def modify(self, var: str, value):
setattr(self, var, value)
self.last_modify = time.time()
def export_data(self):
return {
'efactor': self.efactor,
'real_rept': self.real_rept,
'rept': self.rept,
'interval': self.interval,
'last_date': self.last_date,
'next_date': self.next_date,
'is_activated': self.is_activated
}
if var in self.metadata:
self.metadata[var] = value
self.metadata['last_modify'] = time.time()
else:
print(f"警告: '{var}' 非已知元数据字段")
def revisor(self, quality):
def revisor(self, quality: int = 5, is_new_activation: bool = False):
"""SM-2 算法迭代决策机制实现
根据 quality(0 ~ 5) 进行参数迭代最佳间隔
quality 由主程序评估
@@ -58,87 +52,168 @@ class Electron():
if quality == -1:
return -1
self.efactor = self.efactor + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02))
self.efactor = max(1.3, self.efactor)
self.metadata['efactor'] = self.metadata['efactor'] + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02))
self.metadata['efactor'] = max(1.3, self.metadata['efactor'])
if quality < 3:
# 若保留率低于 3重置重复次数
self.rept = 0
self.interval = 0 # 设为0以便下面重新计算 I(1)
self.metadata['rept'] = 0
self.metadata['interval'] = 0 # 设为0以便下面重新计算 I(1)
else:
self.rept += 1
self.real_rept += 1
self.metadata['rept'] += 1
if self.rept == 0: # 刚被重置或初次激活后复习
self.interval = 1 # I(1)
elif self.rept == 1:
self.interval = 6 # I(2) 经验公式
else:
self.interval = round(self.interval * self.efactor)
self.metadata['real_rept'] += 1
self.last_date = aux.get_daystamp()
self.next_date = aux.get_daystamp() + self.interval
if is_new_activation: # 初次激活
self.metadata['rept'] = 0
self.metadata['efactor'] = 2.5
if self.metadata['rept'] == 0: # 刚被重置或初次激活后复习
self.metadata['interval'] = 1 # I(1)
elif self.metadata['rept'] == 1:
self.metadata['interval'] = 6 # I(2) 经验公式
else:
self.metadata['interval'] = round(self.metadata['interval'] * self.metadata['efactor'])
self.metadata['last_date'] = aux.get_daystamp()
self.metadata['next_date'] = aux.get_daystamp() + self.metadata['interval']
self.metadata['last_modify'] = time.time()
def __str__(self):
return (f"记忆单元预览 \n"
f"内容: '{self.content}' \n"
f"易度系数: {self.efactor:.2f} \n"
f"已经重复的次数: {self.rept} \n"
f"下次间隔: {self.interval}\n"
f"下次复习日期时间戳: {self.next_date}")
f"易度系数: {self.metadata['efactor']:.2f} \n"
f"已经重复的次数: {self.metadata['rept']} \n"
f"下次间隔: {self.metadata['interval']}\n"
f"下次复习日期时间戳: {self.metadata['next_date']}")
def __eq__(self, other):
if self.content == other.content:
return 1
return 0
return True
return False
def __hash__(self):
return hash(self.content)
def __getitem__(self, key):
if key == "content":
return self.content
if key in self.metadata:
return self.metadata[key]
else:
raise KeyError(f"Key '{key}' not found in metadata.")
def __setitem__(self, key, value):
if key == "content":
raise AttributeError("content 应为只读")
# 可以在此处添加更复杂的验证逻辑,例如只允许修改预定义的 metadata 键
# 或者根据键进行类型检查等。
self.metadata[key] = value
self.metadata['last_modify'] = time.time()
def __iter__(self):
yield from self.metadata.keys()
def __len__(self):
return len(self.metadata)
@staticmethod
def placeholder():
return Electron("电子对象样例内容", {})
@staticmethod
def import_from_file(path: pathlib.Path):
name = path.name.replace(path.suffix, "")
with open(path, 'r') as f:
all = toml.load(f)
lst = list()
for i in all.keys():
lst.append(Electron(i, all[i]))
return (name, lst)
@staticmethod
def save_to_file(electron_dictized, path: pathlib.Path):
with open(path, 'w') as f:
toml.dump(electron_dictized, f)
class Nucleon():
class Nucleon:
"""核子: 材料元数据"""
def __init__(self, content: str, data: dict):
self.metadata = data
self.content = content
@staticmethod
def import_from_file(path: pathlib.Path):
name = path.name.replace(path.suffix, "")
with open(path, 'r') as f:
all = toml.load(f)
lst = list()
for i in all.keys():
lst.append(Nucleon(i, all[i]))
return (name, lst)
@staticmethod
def save_to_file(nucleon_dictized, path: pathlib.Path):
with open(path, 'w') as f:
toml.dump(nucleon_dictized, f)
def __getitem__(self, key):
if key == "content":
return self.content
if key in self.metadata:
return self.metadata[key]
else:
raise KeyError(f"Key '{key}' not found in metadata.")
def __iter__(self):
yield from self.metadata.keys()
def __len__(self):
return len(self.metadata)
def __hash__(self):
return hash(self.content)
@staticmethod
def placeholder():
return Nucleon("核子对象样例内容", {})
class AtomicFile():
class NucleonUnion():
"""
替代原有 NucleonFile 类, 支持复杂逻辑
Attributes:
path (Path): 对应于 NucleonUnion 实例的文件路径。
name (str): 核联对象的显示名称,从文件名中派生。
nucleons (list): 内部核子对象的列表。
nucleons_dict (dict): 内部核子对象的字典,以核子内容作为键。
keydata (dict): 核子对象字典键名的翻译。
testdata (dict): 记忆测试项目的元数据。
Parameters:
path (Path): 包含核子数据的文件路径。
"""
def __init__(self, path):
self.path = path
self.name = path.name.replace(path.suffix, "")
with open(path, 'r') as f:
all = toml.load(f)
lst = list()
for i in all.keys():
if "data" in i:
continue
lst.append(Nucleon(i, all[i]))
self.keydata = all["keydata"]
self.testdata = all["testdata"]
self.nucleons = lst
self.nucleons_dict = {i.content: i for i in lst}
def __len__(self):
return len(self.nucleons)
def save(self):
with open(self.path, 'w') as f:
tmp = {i.content: i.metadata for i in self.nucleons}
toml.dump(tmp, f)
class ElectronUnion():
"取代原有 ElectronFile 类, 以支持复杂逻辑"
def __init__(self, path):
self.path = path
self.name = path.name.replace(path.suffix, "")
with open(path, 'r') as f:
all = toml.load(f)
lst = list()
for i in all.keys():
lst.append(Electron(i, all[i]))
self.electrons = lst
self.electrons_dict = {i.content: i for i in lst}
def sync(self):
"""同步 electrons_dict 中新增对到 electrons 中"""
self.electrons = self.electrons_dict.values()
def save(self):
#print(1)
with open(self.path, 'w') as f:
tmp = {i.content: i.metadata for i in self.electrons}
#print(tmp)
toml.dump(tmp, f)
"""class AtomicFile():
def __init__(self, path, type_="unknown"):
self.path = path
self.type_ = type_
@@ -163,6 +238,7 @@ class AtomicFile():
def get_len(self):
return len(self.datalist)
"""
class Atom():
@staticmethod

0
puzzles.py Normal file
View File

View File

@@ -7,7 +7,7 @@ class Parser():
class Reactor():
"""反应堆对象, 用于全面解析文件, 并处理和分配一次文件记忆流程的资源与策略"""
def __init__(self, nucleon_file: pt.AtomicFile, electron_file: pt.AtomicFile, tasked_num):
def __init__(self, nucleon_file: pt.NucleonUnion, electron_file: pt.ElectronUnion, tasked_num):
# 导入原子对象
self.reported = set()
self.nucleon_file = nucleon_file
@@ -17,7 +17,7 @@ class Reactor():
self.atoms_review = list()
counter = self.tasked_num
self.electron_dict = {elect.content: elect for elect in electron_file.datalist}
self.electron_dict = electron_file.electrons_dict
def electron_dict_get_fallback(key) -> pt.Electron:
value = self.electron_dict.get(key)
@@ -26,20 +26,20 @@ class Reactor():
if value is None:
value = pt.Electron(key, {}) # 获取默认值
self.electron_dict[key] = value # 将默认值存入字典
value = self.electron_dict[key]
electron_file.sync()
return value # 返回获取的值(可能是默认值)
for nucleon in nucleon_file.datalist:
for nucleon in nucleon_file.nucleons:
atom = (electron_dict_get_fallback(nucleon.content), nucleon)
if atom[0].is_activated == 0:
if atom[0]["is_activated"] == 0:
if counter > 0:
atom[0].is_activated = 1
atom[0]["is_activated"] = 1
self.atoms_new.append(atom)
counter -= 1
else:
if atom[0].next_date <= aux.get_daystamp():
atom[0].last_date = aux.get_daystamp()
if int(atom[0]["next_date"]) <= aux.get_daystamp():
atom[0]["last_date"] = aux.get_daystamp()
self.atoms_review.append(atom)
# 设置运行时
self.index: int
@@ -64,6 +64,9 @@ class Reactor():
2: "新记忆模式",
3: "总复习模式"
}
print("Atoms New:", self.atoms_new)
print("Atoms Review:", self.atoms_review)
processions = {
1: self.atoms_review,
2: self.atoms_new,
@@ -96,13 +99,12 @@ class Reactor():
def save(self):
print("Progress saved")
# self.nucleon_file.save()
temp = list()
for i in self.electron_dict.keys():
temp.append(self.electron_dict[i])
self.electron_file.datalist = temp
self.electron_file.save()
def report(self, atom, quality):
if atom in self.atoms_new:
atom[0].revisor(quality, True)
return 0
if atom[0] not in self.reported:
atom[0].revisor(quality)
self.reported.add(atom[0])

BIN
readme_src/image_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
readme_src/image_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

45
testfield/blank_maker.py Normal file
View File

@@ -0,0 +1,45 @@
import random
class BlankPuzzle():
"""填空题谜题生成器
Args:
text: 原始字符串(需要 "/" 分割句子, 末尾应有 "/")
min_denominator: 最小概率倒数(如占所有可生成填空数的 1/7 中的 7, 若期望值小于 1, 则取 1)
"""
def __init__(self, text, min_denominator):
self.text = text
self.min_denominator = min_denominator
self.wording = "填空题 - 尚未刷新谜题"
self.answer = ["填空题 - 尚未刷新谜题"]
def refresh(self): # 刷新谜题
placeholder = "___SLASH___"
tmp_text = self.text.replace("/", placeholder)
words = tmp_text.split(placeholder)
if not words:
return ""
words = [word for word in words if word]
num_blanks = min(max(1, len(words) // self.min_denominator), len(words))
indices_to_blank = random.sample(range(len(words)), num_blanks)
indices_to_blank.sort()
blanked_words = list(words)
answer = list()
for index in indices_to_blank:
blanked_words[index] = "__" * len(words[index])
answer.append(words[index])
result = []
for word in blanked_words:
result.append(word)
self.answer = answer
self.wording = "".join(result)
def __str__(self):
return f"{self.wording}\n{str(self.answer)}"
# demo
text = """我联合国人民/同兹/决心/: /欲免/后世/再遭/今代人类/两度/身历/惨不堪言/之战祸/.../"""
riddle = BlankPuzzle(text, 3)
print(riddle)
riddle.refresh()
print(riddle)

View File

@@ -0,0 +1,45 @@
import random
class SelectionPuzzle():
"""选择题谜题生成器
Args:
text: 原始字符串(需要 "/" 分割句子, 末尾应有 "/")
min_denominator: 最小概率倒数(如占所有可生成填空数的 1/7 中的 7, 若期望值小于 1, 则取 1)
"""
def __init__(self, prefix_text, origin_dict, min_denominator):
self.text = text
self.min_denominator = min_denominator
self.wording = "填空题 - 尚未刷新谜题"
self.answer = ["填空题 - 尚未刷新谜题"]
def refresh(self): # 刷新谜题
placeholder = "___SLASH___"
tmp_text = self.text.replace("/", placeholder)
words = tmp_text.split(placeholder)
if not words:
return ""
words = [word for word in words if word]
num_blanks = min(max(1, len(words) // self.min_denominator), len(words))
indices_to_blank = random.sample(range(len(words)), num_blanks)
indices_to_blank.sort()
blanked_words = list(words)
answer = list()
for index in indices_to_blank:
blanked_words[index] = "__" * len(words[index])
answer.append(words[index])
result = []
for word in blanked_words:
result.append(word)
self.answer = answer
self.wording = "".join(result)
def __str__(self):
return f"{self.wording}\n{str(self.answer)}"
# demo
text = """我联合国人民/同兹/决心/: /欲免/后世/再遭/今代人类/两度/身历/惨不堪言/之战祸/.../"""
riddle = BlankPuzzle(text, 3)
print(riddle)
riddle.refresh()
print(riddle)

3
todo.md Normal file
View File

@@ -0,0 +1,3 @@
- [] 基于释义的评估
> 使用 EFACTOR 取最低值方法
- [] 附加属性