10 Commits

Author SHA1 Message Date
30eb45e1cb 更新自述文件 2025-08-06 07:52:42 +08:00
2a30f136cb 更新自述文件 2025-08-06 07:52:15 +08:00
051c4847b2 更新自述文件 2025-08-06 07:43:36 +08:00
0873caa5fc 若干善后改进 2025-08-06 07:42:43 +08:00
6d3d2e665c 实装自动评分系统 2025-08-06 06:46:30 +08:00
edf2f0868a 改进 2025-08-06 06:30:41 +08:00
d5ef5e84d0 规范代码 2025-08-06 06:11:30 +08:00
dd74dddf00 规范代码 2025-08-06 06:11:22 +08:00
2cf2cdb33f 更新版本号 2025-08-06 05:31:05 +08:00
385a86eb2c 更新版本号 2025-08-06 05:30:30 +08:00
18 changed files with 409 additions and 598 deletions

View File

@@ -0,0 +1,7 @@
# 贡献指南
## 使用 Nuitka 静态编译
运行
```bash
nuitka --clang --jobs=6 --standalone --onefile main.py
```

View File

@@ -3,8 +3,7 @@
## 概述
"潜进" (HeurAMS, 中文含义: 启发式辅助记忆软件) 是为习题册, 古诗词, 及其他问答/记忆/理解型题目设计的记忆辅助软件, 提供优化记忆方案
"潜进" (HeurAMS, 中文含义: 启发式辅助记忆软件) 是为习题册, 古诗词, 及其他问答/记忆/理解型知识设计的辅助记忆软件, 提供动态规划的优化记忆方案
## 技术集成与特性
@@ -13,10 +12,10 @@
- 采用经实证的 SM-2 间隔迭代算法, 此算法亦用作 Anki 闪卡记忆软件的默认闪卡调度器
> 计划: 将添加 FSRS 算法 (Anki 的新可选闪卡调度器) 与一种 SM-15 变体算法作为后续替代
> 参考 https://github.com/slaypni/SM-15
> 为什么使用 SM-15 的变体?
> SM-2 后续算法仅有论文, 无具体方程, 故使用一种基于 SM-15 描述实现的变体算法
- 动态优化每首诗词的记忆间隔时间表
- 实时跟踪记忆曲线,优化长期记忆保留率与稳定性
> 使用 SM-15 的变体:
> SM-2 后续算法并非完全开放, 故使用一种基于 SM-15 描述实现的变体算法
- 动态规划每个记忆单元的记忆间隔时间表
- 动态跟踪记忆反馈数据,优化长期记忆保留率与稳定性
### 学习进程优化
- 逐字解析:支持逐字详细释义解析
@@ -30,12 +29,19 @@
- 简洁直观的复习流程设计
## 屏幕截图
![scrshot2](readme_src/image_2.png)
![scrshot1](readme_src/image_1.png)
> 单击图片以放大
<img src="./readme_src/img1.png" alt="img1" style="zoom: 33%;" />
<img src="./readme_src/img2.png" alt="img2" style="zoom:33%;" />
<img src="./readme_src/img3.png" alt="img3" style="zoom:33%;" />
<img src="./readme_src/img4.png" alt="img4" style="zoom:33%;" />
## 技术架构
> 有关技术与实现的细节, 请参阅 CONTRIBUTING.md
> 提交拉取请求以参与到此开放源代码项目
``` mermaid
graph TD
subgraph 后端
@@ -62,10 +68,4 @@ graph TD
## 系统要求
- 平台支持Windows / macOS / Linux / Android (需要 Termux 或 Linux) (终端或浏览器)
- 网络连接:可预缓存语音文件, 需联网使用大模型服务功能
## 使用 Nuitka 静态编译
运行
```bash
nuitka --clang --jobs=6 --standalone --onefile main.py
```
- 网络连接:可预缓存语音文件, 需联网使用大模型服务功能

View File

@@ -1,33 +1,46 @@
import time
import pathlib
import toml
import typing
class ConfigFile():
def __init__(self, path):
class ConfigFile:
def __init__(self, path: str):
self.path = pathlib.Path(path)
if self.path.exists() == 0:
if not self.path.exists():
self.path.touch()
self.data = dict()
self._load()
def _load(self):
"""从文件加载配置数据"""
with open(self.path, 'r') as f:
self.data = toml.load(f)
def modify(self, key, value):
try:
self.data = toml.load(f)
except toml.TomlDecodeError:
self.data = {}
def modify(self, key: str, value: typing.Any):
"""修改配置值并保存"""
self.data[key] = value
self.save()
def save(self, path=""):
if path == "":
path = self.path
with open(path, 'w') as f:
def save(self, path: typing.Union[str, pathlib.Path] = ""):
"""保存配置到文件"""
save_path = pathlib.Path(path) if path else self.path
with open(save_path, 'w') as f:
toml.dump(self.data, f)
def get(self, key, default = None):
def get(self, key: str, default: typing.Any = None) -> typing.Any:
"""获取配置值,如果不存在返回默认值"""
return self.data.get(key, default)
def get_daystamp() -> int:
"""获取当前日戳(以天为单位的整数时间戳)"""
config = ConfigFile("config.toml")
time_override = config.get("time_override", -1)
if time_override is not None and time_override != -1:
#print(f"TIME OVERRIDEED TO {time_override}")
if time_override != -1:
return int(time_override)
return int(time.time() // (24 * 3600))

View File

@@ -1,6 +1,16 @@
from textual.app import App, ComposeResult
from textual.events import Event
from textual.widgets import Collapsible, Header, Footer, Markdown, ListView, ListItem, Label, Static, Button
from textual.widgets import (
Collapsible,
Header,
Footer,
Markdown,
ListView,
ListItem,
Label,
Static,
Button,
)
from textual.containers import Container, Horizontal, Center
from textual.screen import Screen
from textual.widget import Widget
@@ -12,56 +22,70 @@ import re
import random
import copy
class Composition():
def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict] = pt.Atom.placeholder()):
class Composition:
def __init__(
self,
screen: Screen,
reactor,
atom: Tuple[pt.Electron, pt.Nucleon, Dict] = pt.Atom.placeholder(),
):
self.screen = screen
self.atom = atom
self.atom = atom
from reactor import Reactor
self.reactor: Reactor = reactor
self.reg = dict()
def regid(self, id_):
self.reg[id_] = id_ + str(uuid.uuid4())
return self.reg[id_]
def getid(self, id_):
if id_ not in self.reg.keys():
return "None"
return self.reg[id_]
def recid(self, id_):
return id_[:-36]
def compose(self):
yield Label("示例标签", id="testlabel")
yield Button("示例按钮", id="testbtn")
def handler(self, event, type_):
return 1
#if hasattr(event, "button"):
#print(event.button.id)
# self.screen.query_one("#testlabel", Label).update("hi")
class Finished(Composition):
def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict]):
super().__init__(screen, reactor, atom)
def compose(self):
yield Label("本次记忆进程结束", id=self.regid("msg"))
#yield Button("示例按钮", id="testbtn")
class Placeholder(Composition):
def __init__(self, screen: Screen):
self.screen = screen
def compose(self):
yield Label("示例标签", id="testlabel")
yield Button("示例按钮", id="testbtn", classes="choice")
def handler(self, event, type_):
#print(event.button.id)
self.screen.query_one("#testlabel", Label).update("hi")
class Recognition(Composition):
def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict]):
super().__init__(screen, reactor, atom)
def compose(self):
with Center():
yield Static(f"[dim]{self.atom[1]['translation']}[/]")
yield Label(f"")
s = str(self.atom[1]['content'])
s = str(self.atom[1]["content"])
replace_dict = {
", ": ",",
". ": ".",
@@ -75,132 +99,156 @@ class Recognition(Composition):
}
for old, new in replace_dict.items():
s = s.replace(old, new)
result = re.split(r"(?<=[,;:|])", s.replace('/', ' '))
result = re.split(r"(?<=[,;:|])", s.replace("/", " "))
for i in result:
with Center():
yield Label(f"[b][b]{i.replace("/", " ")}[/][/]", id=self.regid("sentence"+str(hash(i)))) # 致敬传奇去重串 uuid
#with Collapsible(title="附加信息", collapsed=True):
yield Label(
f"[b][b]{i.replace('/', ' ')}[/][/]",
id=self.regid("sentence" + str(hash(i))),
)
for i in self.atom[2]["testdata"]["additional_inf"]:
if self.atom[1][i]:
#print(type(self.atom[1][i]))
#print(self.atom[1][i])
if isinstance(self.atom[1][i], list):
for j in self.atom[1][i]:
yield Markdown(f"### {self.atom[2]['keydata'][i]}: {j}")
continue
if isinstance(self.atom[1][i], Dict):
t = ""
for j, k in self.atom[1][i].items():
for j, k in self.atom[1][i].items(): # type: ignore
# 弱智的 Pylance 类型推导
t += f"> **{j}**: {k} \n"
yield Markdown(t, id=self.regid("tran"))
with Center():
yield Button("我已知晓", id=self.regid("ok"))
def handler(self, event, type_):
##print(event)
if type_ == "button":
#print(event.button.id)
if event.button.id == self.getid("ok"):
#print(1)
""""""#assessment = self.reactor.report(self.reactor.current_atom, -1)
return 0
if type_ == 1:
pass
self.reactor.report(self.atom, 5)
return 0
return -1
class BasicEvaluation(Composition):
# 不再使用, 仅作为测试
def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict]):
super().__init__(screen, reactor, atom)
def compose(self):
yield Label(self.atom[1]["content"], id="sentence")
with Container(id="button_container"):
btn = {}
btn['5'] = Button("完美回想", variant="success", id=self.regid("feedback5"), classes="choice")
btn['4'] = Button("犹豫后正确", variant="success", id=self.regid("feedback4"), classes="choice")
btn['3'] = Button("困难地正确", variant="warning", id=self.regid("feedback3"), classes="choice")
btn['2'] = Button("错误但熟悉", variant="warning", id=self.regid("feedback2"), classes="choice")
btn['1'] = Button("错误且不熟", variant="error", id=self.regid("feedback1"), classes="choice")
btn['0'] = Button("完全空白", variant="error", id=self.regid("feedback0"), classes="choice")
yield Horizontal(btn['5'], btn['4'])
yield Horizontal(btn['3'], btn['2'])
yield Horizontal(btn['1'], btn['0'])
btn["5"] = Button(
"完美回想", variant="success", id=self.regid("feedback5"), classes="choice"
)
btn["4"] = Button(
"犹豫后正确", variant="success", id=self.regid("feedback4"), classes="choice"
)
btn["3"] = Button(
"困难地正确", variant="warning", id=self.regid("feedback3"), classes="choice"
)
btn["2"] = Button(
"错误但熟悉", variant="warning", id=self.regid("feedback2"), classes="choice"
)
btn["1"] = Button(
"错误且不熟", variant="error", id=self.regid("feedback1"), classes="choice"
)
btn["0"] = Button(
"完全空白", variant="error", id=self.regid("feedback0"), classes="choice"
)
yield Horizontal(btn["5"], btn["4"])
yield Horizontal(btn["3"], btn["2"])
yield Horizontal(btn["1"], btn["0"])
def handler(self, event, type_):
if "feedback" in event.button.id:
#print(self.recid(event.button.id)[8:9])
assess = int(self.recid(event.button.id)[8:9])
ret = self.reactor.report(self.atom, assess)
return ret
class FillBlank(Composition):
def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict]):
super().__init__(screen, reactor, atom)
self.inputlist = []
self.hashtable = {}
self._work()
def _work(self):
self.puzzle = pz.BlankPuzzle(self.atom[1]["content"], 4)
self.puzzle.refresh()
self.ans = copy.copy(self.puzzle.answer)
random.shuffle(self.ans)
def compose(self):
yield Label(self.puzzle.wording, id=self.regid("sentence"))
yield Label(f"当前输入: {self.inputlist}", id=self.regid("inputpreview"))
#yield Label(renderable=f"答案: {self.puzzle.answer}", id=self.regid("ans"))
for i in self.ans:
self.hashtable[str(hash(i))] = i
yield Button(i, id=self.regid(f"select{hash(i)}"))
yield Button("退格", id=self.regid(f"delete"))
def handler(self, event, type_):
if type_ == "button":
if self.recid(event.button.id) == "delete" and len(self.inputlist) > 0:
self.inputlist.pop()
if self.recid(event.button.id) == "delete":
if len(self.inputlist) > 0:
self.inputlist.pop()
else:
return 1
else:
self.inputlist.append(self.hashtable[self.recid(event.button.id)[6:]])
if len(self.inputlist) < len(self.puzzle.answer):
return 1
else:
if self.inputlist == self.puzzle.answer:
print("ok")
self.reactor.report(self.atom, 4)
return 0
else:
self.inputlist = []
self.reactor.report(self.atom, 2)
return 1
class DrawCard(Composition):
def __init__(self, screen: Screen, reactor, atom: Tuple[pt.Electron, pt.Nucleon, Dict]):
super().__init__(screen, reactor, atom)
self.inputlist = []
self.hashtable = {}
self._work()
def _work(self):
self.puzzle = pz.SelectionPuzzle(self.atom[1]["keyword_note"], [], 2, "选择正确词义: ")
self.puzzle = pz.SelectionPuzzle(self.atom[1]["keyword_note"], [], 2, "选择正确词义: ") # type: ignore
self.puzzle.refresh()
def compose(self):
print(len(self.inputlist))
yield Label(self.atom[1].content, id=self.regid("sentence"))
yield Label(self.atom[1].content.replace("/",""), id=self.regid("sentence"))
yield Label(self.puzzle.wording[len(self.inputlist)], id=self.regid("puzzle"))
yield Label(f"当前输入: {self.inputlist}", id=self.regid("inputpreview"))
yield Label(renderable=f"答案: {self.puzzle.answer}", id=self.regid("ans"))
for i in self.puzzle.options[len(self.inputlist)]:
self.hashtable[str(hash(i))] = i
yield Button(i, id=self.regid(f"select{hash(i)}"))
yield Button("退格", id=self.regid(f"delete"))
def handler(self, event, type_):
if type_ == "button":
if self.recid(event.button.id) == "delete" and len(self.inputlist) > 0:
self.inputlist.pop()
if self.recid(event.button.id) == "delete":
if len(self.inputlist) > 0:
self.inputlist.pop()
else:
return 1
else:
self.inputlist.append(self.hashtable[self.recid(event.button.id)[6:]])
if len(self.inputlist) < len(self.puzzle.answer):
return 1
else:
if self.inputlist == self.puzzle.answer:
print("ok")
self.reactor.report(self.atom, 4)
return 0
else:
self.inputlist = []
self.reactor.report(self.atom, 2)
return 1
registry = {
"sample": Composition,
"recognition": Recognition,
@@ -210,16 +258,16 @@ registry = {
}
# TEST
class TestScreen(Screen):
def __init__(self):
super().__init__(name=None, id=None, classes=None)
self.comp = Recognition(self, None, pt.Atom.advanced_placeholder())
self.comp = Recognition(self, None, pt.Atom.advanced_placeholder())
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
yield from self.comp.compose()
yield Footer()
def on_mount(self) -> None:
pass
@@ -229,9 +277,10 @@ class TestScreen(Screen):
def action_quit_app(self) -> None:
self.app.exit()
class AppLauncher(App):
CSS_PATH = "styles.tcss"
TITLE = '测试布局'
TITLE = "测试布局"
BINDINGS = [("escape", "quit", "退出"), ("d", "toggle_dark", "改变色调")]
SCREENS = {
"testscreen": TestScreen,
@@ -241,6 +290,7 @@ class AppLauncher(App):
self.action_toggle_dark()
self.push_screen("testscreen")
if __name__ == "__main__":
app = AppLauncher()
app.run()
app.run()

View File

@@ -1,8 +1,14 @@
# [调试] 将更改保存到文件
save = 1
# [调试] 覆写时间
time_override = 10
time_override = -1
# [调试] 一键通过
quick_pass = 0
# 对于每个项目的新记忆核子数量
tasked_number = 8
# 竖屏适配
# 竖屏适配 (未完成)
mobile_mode = 1

View File

@@ -1,279 +0,0 @@
["臣/密/言: /臣/以/险衅/, 夙/遭/闵凶./"]
efactor = 2.5
real_rept = 2
rept = 0
interval = 1
last_date = 10
next_date = 11
is_activated = 1
last_modify = 1754244217.8045535
["生孩/六月/, 慈父/见背/; /行年/四岁/, 舅/夺/母志./"]
efactor = 2.5
real_rept = 2
rept = 0
interval = 1
last_date = 10
next_date = 11
is_activated = 1
last_modify = 1754244218.3349776
["祖母/刘/愍/臣/孤弱/, 躬亲/抚养./"]
efactor = 2.5
real_rept = 2
rept = 0
interval = 1
last_date = 10
next_date = 11
is_activated = 1
last_modify = 1754244218.9444783
["臣/少/多/疾病/, 九岁/不行/, 零丁/孤苦/, 至于/成立./"]
efactor = 2.5
real_rept = 2
rept = 0
interval = 1
last_date = 10
next_date = 11
is_activated = 1
last_modify = 1754244219.28863
["既/无/伯叔/, 终/鲜/兄弟/, 门/衰/祚/薄/, 晚/有/儿息./"]
efactor = 2.5
real_rept = 2
rept = 0
interval = 1
last_date = 10
next_date = 11
is_activated = 1
last_modify = 1754244219.6544538
["外/无/期功/强近/之亲/, 内/无/应门/五尺/之僮/, 茕茕/孑立/, 形影/相吊./"]
efactor = 2.5
real_rept = 2
rept = 0
interval = 1
last_date = 10
next_date = 11
is_activated = 1
last_modify = 1754244220.0074852
["而/刘/夙/婴/疾病/, 常/在/床蓐/, 臣/侍/汤药/, 未曾/废离./"]
efactor = 2.5
real_rept = 2
rept = 0
interval = 1
last_date = 10
next_date = 11
is_activated = 1
last_modify = 1754244220.3606052
["逮/奉/圣朝/, 沐浴/清化./"]
efactor = 2.5
real_rept = 17
rept = 0
interval = 1
last_date = 10
next_date = 11
is_activated = 1
last_modify = 1754244226.386384
["前/太守/臣/逵/察/臣/孝廉/; /后/刺史/臣/荣/举/臣/秀才./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.007183
["臣/以/供养/无主/, 辞/不赴命./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.0071847
["诏书/特下/, 拜/臣/郎中/, 寻/蒙/国恩/, 除/臣/洗马./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.0071867
["猥/以/微贱/, 当/侍/东宫/, 非/臣/陨首/所能/上报./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.0071883
["臣/具/以表/闻/, 辞/不就职./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.0071898
["诏书/切峻/, 责/臣/逋慢/; /郡县/逼迫/, 催/臣/上道/; /州司/临门/, 急于/星火./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.007191
["臣/欲/奉诏/奔驰/, 则/刘/病/日笃/, 欲/苟/顺/私情/, 则/告诉/不许./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.0071924
["臣/之/进退/, 实为/狼狈./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.0071936
["伏惟/圣朝/以/孝/治/天下/, 凡/在/故老/, 犹/蒙/矜育/, 况/臣/孤苦/, 特为/尤甚./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.007195
["且/臣/少/仕/伪朝/, 历职/郎署/, 本图/宦达/, 不矜/名节./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.0071962
["今/臣/亡国/贱俘/, 至微/至陋/, 过/蒙/拔擢/, 宠命/优渥/, 岂敢/盘桓/, 有所/希冀!/"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.0071974
["但/以/刘/日薄/西山/, 气息/奄奄/, 人命/危浅/, 朝不/虑夕./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.0072057
["臣/无/祖母/, 无以/至今日/, 祖母/无/臣/, 无以/终余年./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.007207
["母孙/二人/, 更相/为命/, 是以/区区/不能/废远./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.0072083
["臣/密/今年/四十/有四/, 祖母/今年/九十/有六/, 是/臣/尽节/于/陛下/之日/长/, 报养/刘/之日/短./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.0072105
["乌鸟/私情/, 愿/乞/终养./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.0072117
["臣/之/辛苦/, 非独/蜀之/人士/及/二州/牧伯/所见/明知/, 皇天/后土/, 实所/共鉴./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.0072134
["愿/陛下/矜悯/愚诚/, 听/臣/微志/, 庶/刘/侥幸/, 保/卒/余年./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.0072145
["臣/生/当/陨首/, 死/当/结草./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.0072162
["臣/不胜/犬马/怖惧/之情/, 谨/拜表/以闻./"]
efactor = 2.5
real_rept = 0
rept = 0
interval = 0
last_date = 0
next_date = 0
is_activated = 0
last_modify = 1754244183.007218

196
main.py
View File

@@ -1,86 +1,76 @@
from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, ListView, ProgressBar, DirectoryTree, ListItem, Label, Static, Button
from textual.widgets import (
Header,
Footer,
ListView,
ProgressBar,
DirectoryTree,
ListItem,
Label,
Static,
Button,
)
from textual.containers import Container, Horizontal, Center
from textual.screen import Screen
import pathlib
import threading
import edge_tts as tts
from playsound import playsound
import particles as pt
from reactor import Reactor, Apparatus
import auxiliary as aux
import compositions as compo
import builtins
# 保存原始的 open 函数
# Hook python 的 open() 函数, 使用 utf-8 (兼容 Windows 万年 GBK)
_original_open = builtins.open
# 定义新的 open 函数,默认使用 UTF-8
def _open(*args, **kwargs):
if 'encoding' not in kwargs:
kwargs['encoding'] = 'utf-8'
if "encoding" not in kwargs:
kwargs["encoding"] = "utf-8"
return _original_open(*args, **kwargs)
# 替换全局的 open
builtins.open = _open
ver = '0.3.0'
ver = "0.3.0"
config = aux.ConfigFile("config.toml")
class MemScreen(Screen):
BINDINGS = [
("d", "toggle_dark", "改变色调"),
("q", "pop_screen", "返回主菜单"),
("v", "play_voice", "朗读"),
("0", "press('q0')", None),
("1", "press('q1')", None),
("2", "press('q2')", None),
("3", "press('q3')", None),
("4", "press('q4')", None),
("5", "press('q5')", None),
("[", "press('q5')", None),
("]", "press('q4')", None),
(";", "press('q3')", None),
("'", "press('q2')", None),
(".", "press('q1')", None),
("/", "press('q0')", None),
]
if config.get("quick_pass"):
BINDINGS.append(("k", "quick_pass", "快速通过[调试]"))
btn = dict()
def __init__(
self,
nucleon_file: pt.NucleonUnion,
electron_file: pt.ElectronUnion,
tasked_num
tasked_num,
):
super().__init__(name=None, id=None, classes=None)
self.reactor = Reactor(nucleon_file, electron_file, self, tasked_num)
self.stage = 1
self.stage += self.reactor.set_round_templated(self.stage)
##print(self.reactor.procession)
self.reactor.forward()
#self.compo:compo.Composition = compo.Placeholder(self)
self.compo = next(self.reactor.current_appar)
def compose(self) -> ComposeResult:
#print(self.compo)
yield Header(show_clock=True)
with Center():
yield Static(f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}")
#yield ProgressBar(total=len(self.reactor.procession) - 1, show_percentage=False, show_eta=False, id="progress")
yield Static(
f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}"
)
yield from self.compo.compose()
yield Footer()
"""
def _get_progress_text(self):
return f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}"
"""
def on_mount(self):
# 首次挂载时调用
pass
def on_button_pressed(self, event):
@@ -90,13 +80,12 @@ class MemScreen(Screen):
def _forward_judge(self, ret):
if ret == -1:
return
if ret == 0: # 成功
if ret == 0:
try:
self.compo = next(self.reactor.current_appar)
self.refresh_ui()
except StopIteration:
nxt = self.reactor.forward(1)
#print(2)
try:
self.compo = next(self.reactor.current_appar)
except:
@@ -104,15 +93,15 @@ class MemScreen(Screen):
if nxt == -1:
if self.reactor.round_set == 0:
if self.stage == 4:
#if config.get("save"):
#self.reactor.save()
self.compo = compo.Finished(self, None, pt.Atom.placeholder())
if config.get("save"):
self.reactor.save()
self.compo = compo.Finished(
self, None, pt.Atom.placeholder()
)
self.refresh_ui()
#self._show_finished_screen("今日目标已完成")
else:
self.reactor.set_round_templated(self.stage)
self.reactor.forward(1)
#self._update_ui()
self.stage += 1
self.compo = next(self.reactor.current_appar)
self.refresh_ui()
@@ -121,71 +110,46 @@ class MemScreen(Screen):
else:
self.refresh_ui()
return
if ret == 1: # 不允许前进
if ret == 1:
self.refresh_ui()
return
def refresh_ui(self):
self.call_later(self.recompose)
#self.call_later(lambda: self.query_one("#progress", expect_type=ProgressBar).advance(self.reactor.index))
##print(area.children)
#for child in list(area.children):
# child.remove() # 致敬传奇组件树 DOM
##print(1,list(self.compo.compose()))
#area.mount(*list(self.compo.compose()))
def report(self, quality):
assessment = self.reactor.report(self.reactor.current_atom, quality)
return assessment
"""if assessment == 1:
# 需要复习
feedback_label.update(f"评分为 {quality}, 已经加入至复习, 请重复记忆")
else:
ret = self.reactor.forward(1)
if ret == -1:
if self.reactor.round_set == 0:
if self.stage == 4:
# NOTE #
if config.get("save"):
self.reactor.save()
self._show_finished_screen("今日目标已完成")
else:
self.reactor.set_round_templated(self.stage)
self.reactor.forward(1)
self._update_ui()
self.stage += 1
return
#feedback_label.update("") # 清除反馈消息
self._update_ui()"""
#def action_press(self, btnid):
# self.on_button_pressed(btnid)
def action_play_voice(self):
def play():
cache_dir = pathlib.Path(f"./cache/voice/")
cache_dir.mkdir(parents = True, exist_ok = True)
cache = cache_dir / f"{self.reactor.current_atom[1].content.replace("/","")}.wav"
cache_dir.mkdir(parents=True, exist_ok=True)
cache = cache_dir / f"{self.reactor.current_atom[1].content.replace('/','')}.wav"
if not cache.exists():
communicate = tts.Communicate(self.reactor.current_atom[1].content.replace("/",""), "zh-CN-YunjianNeural")
communicate.save_sync(f"./cache/voice/{self.reactor.current_atom[1].content.replace("/","")}.wav")
communicate = tts.Communicate(
self.reactor.current_atom[1].content.replace("/", ""),
"zh-CN-YunjianNeural",
)
communicate.save_sync(
f"./cache/voice/{self.reactor.current_atom[1].content.replace('/','')}.wav"
)
playsound(str(cache))
threading.Thread(target=play).start()
def action_quick_pass(self):
self.reactor.report(self.reactor.current_atom, 5)
self._forward_judge(0)
def action_toggle_dark(self):
self.app.action_toggle_dark()
def action_pop_screen(self):
"""返回到上一个屏幕"""
self.app.pop_screen()
class PreparationScreen(Screen):
BINDINGS = [
("q", "go_back", "返回"),
("escape", "quit_app", "退出")
]
def __init__(self, nucleon_file: pt.NucleonUnion, electron_file: pt.ElectronUnion) -> None:
class PreparationScreen(Screen):
BINDINGS = [("q", "go_back", "返回"), ("escape", "quit_app", "退出")]
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
@@ -194,18 +158,27 @@ class PreparationScreen(Screen):
yield Header(show_clock=True)
with Container(id="learning_screen_container"):
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"子文件对象: ./nucleon/[b]{self.nucleon_file.name}[/b].toml"
)
yield Label(
f"电子文件对象: ./electron/[b]{self.electron_file.name}[/b].toml"
)
yield Label(f"核子数量:{len(self.nucleon_file)}")
yield Button("开始记忆", id="start_memorizing_button", variant="primary", classes="start-button")
yield Button(
"开始记忆",
id="start_memorizing_button",
variant="primary",
classes="start-button",
)
yield Static(f"\n全文如下:\n")
yield Static(self._get_full_content(), classes="full")
yield Static(self._get_full_content().replace("/", ""), classes="full")
yield Footer()
def _get_full_content(self):
content = ""
for i in self.nucleon_file.nucleons:
content += i['content']
content += i["content"]
return content
def action_go_back(self):
@@ -215,76 +188,79 @@ class PreparationScreen(Screen):
self.app.exit()
def on_button_pressed(self, event: Button.Pressed) -> None:
pass
if event.button.id == "start_memorizing_button":
#init_file(Path(self.atom_file).name)
newscr = MemScreen(self.nucleon_file, self.electron_file, config.get("tasked_number", 8))
self.app.push_screen(
newscr
newscr = MemScreen(
self.nucleon_file, self.electron_file, config.get("tasked_number", 8)
)
#if event.button.id == "edit_metadata_button":
# init_file(Path(self.atom_file).name)
# os.system("reset;nano ./data/" + str(Path(self.atom_file).name.replace(".txt", "_atoms.json")))
self.app.push_screen(newscr)
class FileSelectorScreen(Screen):
global ver
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
yield Container(
Label(f'欢迎使用 "潜进" 辅助记忆软件, 版本 {ver}', classes="title-label"),
Label("选择要学习的文件:", classes="title-label"),
ListView(id="file-list", classes="file-list-view")
ListView(id="file-list", classes="file-list-view"),
)
yield Footer()
def on_mount(self) -> None:
file_list_widget = self.query_one("#file-list", ListView)
nucleon_path = pathlib.Path("./nucleon")
nucleon_files = sorted([f.name for f in nucleon_path.iterdir() if f.suffix == ".toml"])
nucleon_files = sorted(
[f.name for f in nucleon_path.iterdir() if f.suffix == ".toml"]
)
if nucleon_files:
for filename in nucleon_files:
file_list_widget.append(ListItem(Label(filename)))
else:
file_list_widget.append(ListItem(Static("在 ./nucleon/ 中未找到任何核子文件. 请放置文件后重启应用.")))
file_list_widget.append(
ListItem(Static("在 ./nucleon/ 中未找到任何核子文件. 请放置文件后重启应用."))
)
file_list_widget.disabled = True
def on_list_view_selected(self, event: ListView.Selected) -> None:
if not isinstance(event.item, ListItem):
self.notify("无法选择此项。", severity="error")
return
selected_label = event.item.query_one(Label)
if "未找到任何 .toml 文件" in str(selected_label.renderable):
self.notify("请先在 `./atoms/` 目录中放置 .toml 文件。", severity="warning")
return
selected_filename = str(selected_label.renderable)
nucleon_file = pt.NucleonUnion(pathlib.Path("./nucleon") / selected_filename)
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.ElectronUnion(pathlib.Path("./electron") / selected_filename)
# self.notify(f"已选择: {selected_filename}", timeout=2)
electron_file = pt.ElectronUnion(
pathlib.Path("./electron") / selected_filename
)
self.app.push_screen(PreparationScreen(nucleon_file, electron_file))
def action_quit_app(self) -> None:
self.app.exit()
class AppLauncher(App):
CSS_PATH = "styles.tcss"
TITLE = '潜进 - 辅助记忆程序'
TITLE = "潜进 - 辅助记忆程序"
BINDINGS = [("escape", "quit", "退出"), ("d", "toggle_dark", "改变色调")]
SCREENS = {
"file_selection_screen": FileSelectorScreen,
}
def on_mount(self) -> None:
#self.action_toggle_dark()
self.push_screen("file_selection_screen")
if __name__ == "__main__":
app = AppLauncher()
app.run()
app.run()

View File

@@ -8,7 +8,7 @@ translation = "语句翻译"
["testdata"]
# 记忆时显示的额外信息
additional_inf = ["translation","keyword_note", "note"]
# 填空测试, content指代键名
# 填空测试, content 指代键名
fill_blank_test = {"from"=["content"], "hint"=["translation"]}
# 选择题测试
draw_card_test = {"from"=["keyword_note"]}

View File

@@ -3,37 +3,36 @@ import toml
import time
import auxiliary as aux
import time
class Electron():
class Electron:
"""电子: 记忆分析元数据及算法"""
algorithm = "SM-2" # 暂时使用 SM-2 算法进行记忆拟合, 考虑 SM-15 替代
algorithm = "SM-2" # 暂时使用 SM-2 算法进行记忆拟合, 考虑 SM-15 替代
def __init__(self, content: str, metadata: dict):
self.content = content
self.metadata = metadata
if metadata == {}:
#print("NULL")
# 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, # 激活状态
'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时间戳)
'last_modify': time.time() # 最后修改时间戳(此处是UNIX时间戳)
}
self.metadata = defaults
def activate(self):
self.metadata['is_activated'] = 1
self.metadata['last_modify'] = time.time()
def modify(self, var: str, value):
if var in self.metadata:
self.metadata[var] = value
@@ -41,7 +40,7 @@ class Electron():
else:
print(f"警告: '{var}' 非已知元数据字段")
def revisor(self, quality: int = 5, is_new_activation: bool = False):
def revisor(self, quality: int = 5, is_new_activation: bool = False):
"""SM-2 算法迭代决策机制实现
根据 quality(0 ~ 5) 进行参数迭代最佳间隔
quality 由主程序评估
@@ -49,43 +48,50 @@ class Electron():
Args:
quality (int): 记忆保留率量化参数
"""
print(f"REVISOR: {quality}, {is_new_activation}")
if quality == -1:
return -1
self.metadata['efactor'] = self.metadata['efactor'] + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02))
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.metadata['rept'] = 0
self.metadata['interval'] = 0 # 设为0以便下面重新计算 I(1)
self.metadata['rept'] = 0
self.metadata['interval'] = 0 # 设为0以便下面重新计算 I(1)
else:
self.metadata['rept'] += 1
self.metadata['real_rept'] += 1
if is_new_activation: # 初次激活
if is_new_activation: # 初次激活
self.metadata['rept'] = 0
self.metadata['efactor'] = 2.5
if self.metadata['rept'] == 0: # 刚被重置或初次激活后复习
self.metadata['interval'] = 1 # I(1)
if self.metadata['rept'] == 0: # 刚被重置或初次激活后复习
self.metadata['interval'] = 1 # I(1)
elif self.metadata['rept'] == 1:
self.metadata['interval'] = 6 # I(2) 经验公式
self.metadata['interval'] = 6 # I(2) 经验公式
else:
self.metadata['interval'] = round(self.metadata['interval'] * self.metadata['efactor'])
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.metadata['efactor']:.2f} \n"
f"已经重复的次数: {self.metadata['rept']} \n"
f"下次间隔: {self.metadata['interval']} \n"
f"下次复习日期时间戳: {self.metadata['next_date']}")
return (
f"记忆单元预览 \n"
f"内容: '{self.content}' \n"
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:
@@ -94,7 +100,7 @@ class Electron():
def __hash__(self):
return hash(self.content)
def __getitem__(self, key):
if key == "content":
return self.content
@@ -102,14 +108,10 @@ class Electron():
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()
@@ -118,14 +120,15 @@ class Electron():
def __len__(self):
return len(self.metadata)
@staticmethod
def placeholder():
return Electron("电子对象样例内容", {})
class Nucleon:
"""核子: 材料元数据"""
def __init__(self, content: str, data: dict):
self.metadata = data
self.content = content
@@ -151,7 +154,8 @@ class Nucleon:
def placeholder():
return Nucleon("核子对象样例内容", {})
class NucleonUnion():
class NucleonUnion:
"""
替代原有 NucleonFile 类, 支持复杂逻辑
@@ -166,6 +170,7 @@ class NucleonUnion():
Parameters:
path (Path): 包含核子数据的文件路径。
"""
def __init__(self, path):
self.path = path
self.name = path.name.replace(path.suffix, "")
@@ -189,8 +194,10 @@ class NucleonUnion():
tmp = {i.content: i.metadata for i in self.nucleons}
toml.dump(tmp, f)
class ElectronUnion():
"取代原有 ElectronFile 类, 以支持复杂逻辑"
class ElectronUnion:
"""取代原有 ElectronFile 类, 以支持复杂逻辑"""
def __init__(self, path):
self.path = path
self.name = path.name.replace(path.suffix, "")
@@ -201,19 +208,21 @@ class ElectronUnion():
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)
# print(1)
with open(self.path, 'w') as f:
tmp = {i.content: i.metadata for i in self.electrons}
#print(tmp)
# print(tmp)
toml.dump(tmp, f)
"""class AtomicFile():
"""
class AtomicFile():
def __init__(self, path, type_="unknown"):
self.path = path
self.type_ = type_
@@ -237,30 +246,42 @@ class ElectronUnion():
return ""
def get_len(self):
return len(self.datalist)
"""
class Atom():
class Atom:
@staticmethod
def placeholder():
return (Electron.placeholder(), Nucleon.placeholder(), {})
@staticmethod
def advanced_placeholder():
return (
Electron("两只黄鹤鸣翠柳", {}),
Nucleon("两只黄鹤鸣翠柳", {"note": [],
"translation": "臣子李密陈言:我因命运不好,小时候遭遇到了不幸",
"keyword_note": {"险衅":"凶险祸患(这里指命运不好)", "":"早时,这里指年幼的时候", "":"'',指可忧患的事", "":"不幸,指丧父"}}),
Nucleon(
"两只黄鹤鸣翠柳",
{
"note": [],
"translation": "臣子李密陈言:我因命运不好,小时候遭遇到了不幸",
"keyword_note": {
"险衅": "凶险祸患(这里指命运不好)",
"": "早时,这里指年幼的时候",
"": "'',指可忧患的事",
"": "不幸,指丧父"
}
}
),
{
"keydata":{
"keydata": {
"note": "笔记",
"keyword_note": "关键词翻译",
"translation": "语句翻译"},
"testdata":{
"translation": "语句翻译"
},
"testdata": {
"additional_inf": ["translation", "note", "keyword_note"],
"fill_blank_test": ["translation"],
"draw_card_test": ["keyword_note"]
},
"is_new_activation": 0
})
}
)

View File

@@ -4,40 +4,49 @@ import edge_tts as tts
from pathlib import Path
import shutil
def precache(text):
cache_dir = Path(f"./cache/voice/")
cache_dir.mkdir(parents = True, exist_ok = True)
def precache(text: str):
"""预缓存单个文本的音频"""
cache_dir = Path("./cache/voice/")
cache_dir.mkdir(parents=True, exist_ok=True)
cache = cache_dir / f"{text}.wav"
if not cache.exists():
communicate = tts.Communicate(text, "zh-CN-YunjianNeural")
communicate.save_sync(f"./cache/voice/{text}.wav")
def proc_file(path):
def proc_file(path: Path):
"""处理单个文件"""
nu = pt.NucleonUnion(path)
c = 0
for i in nu.nucleons:
c += 1
print(f"预缓存 [{nu.name}] ({c}/{len(nu)}): {i["content"]}")
precache(f"{i["content"]}")
print(f"预缓存 [{nu.name}] ({c}/{len(nu)}): {i['content']}")
precache(i['content'])
def walk(path_str):
def walk(path_str: str):
"""遍历目录处理所有文件"""
path = Path(path_str)
print(f"正在遍历目录: {path}")
for item in path.iterdir():
if item.is_file():
if item.suffix == ".toml":
print(f"正预缓存文件: {item.name}")
proc_file(item)
if item.is_file() and item.suffix == ".toml":
print(f"正预缓存文件: {item.name}")
proc_file(item)
elif item.is_dir():
print(f"进入目录: {item.name}")
print("音频预缓存实用程序")
print("需要?")
print("全部缓存: A")
print("清空缓存: C")
choice = input("输入选项 $ ")
if choice == "a" or choice == "A":
walk("./nucleon")
if choice == "c" or choice == "C":
shutil.rmtree("./cache/voice")
if __name__ == "__main__":
print("音频预缓存实用程序")
print("A: 全部缓存")
print("C: 清空缓存")
choice = input("输入选项 $ ").upper()
if choice == "A":
walk("./nucleon")
elif choice == "C":
shutil.rmtree("./cache/voice", ignore_errors=True)
print("缓存已清空")

View File

@@ -1,8 +1,10 @@
import random
class Puzzle():
class Puzzle:
pass
class BlankPuzzle(Puzzle):
"""填空题谜题生成器
@@ -10,20 +12,21 @@ class BlankPuzzle(Puzzle):
text: 原始字符串(需要 "/" 分割句子, 末尾应有 "/")
min_denominator: 最小概率倒数(如占所有可生成填空数的 1/7 中的 7, 若期望值小于 1, 则取 1)
"""
def __init__(self, text, min_denominator):
def __init__(self, text: str, min_denominator: int):
self.text = text
self.min_denominator = min_denominator
self.wording = "填空题 - 尚未刷新谜题"
self.answer = ["填空题 - 尚未刷新谜题"]
def refresh(self): # 刷新谜题
placeholder = "___SLASH___"
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))
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)
@@ -31,15 +34,13 @@ class BlankPuzzle(Puzzle):
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)
self.wording = "".join(blanked_words)
def __str__(self):
return f"{self.wording}\n{str(self.answer)}"
class SelectionPuzzle(Puzzle):
"""选择题谜题生成器
@@ -49,12 +50,20 @@ class SelectionPuzzle(Puzzle):
max_riddles_num: 最大生成谜题数 (默认2个)
prefix: 问题前缀
"""
def __init__(self, mapping, jammer: list, max_riddles_num: int = 2, prefix: str = ""):
jammer += ["1","2","3","4"]
def __init__(
self,
mapping: dict,
jammer: list,
max_riddles_num: int = 2,
prefix: str = ""
):
self.prefix = prefix
self.mapping = mapping
self.jammer = list(set(jammer + list(mapping.values()))) # 合并干扰项和正确答案并去重
self.max_riddles_num = max(1, min(max_riddles_num, 5)) # 限制1-5个谜题
self.jammer = list(set(jammer + list(mapping.values())))
while len(self.jammer) < 4:
self.jammer.append(" ")
self.max_riddles_num = max(1, min(max_riddles_num, 5))
self.wording = "选择题 - 尚未刷新谜题"
self.answer = ["选择题 - 尚未刷新谜题"]
self.options = []
@@ -66,49 +75,47 @@ class SelectionPuzzle(Puzzle):
self.answer = ["无答案"]
self.options = []
return
# 确定实际生成的谜题数量
num_questions = min(self.max_riddles_num, len(self.mapping))
questions = random.sample(list(self.mapping.items()), num_questions)
# 生成谜题
puzzles = []
answers = []
all_options = []
for question, correct_answer in questions:
# 生成选项 (正确答案 + 3个干扰项)
options = [correct_answer]
available_jammers = [j for j in self.jammer if j != correct_answer]
available_jammers = [
j for j in self.jammer if j != correct_answer
]
if len(available_jammers) >= 3:
selected_jammers = random.sample(available_jammers, 3)
else:
selected_jammers = random.choices(available_jammers, k=3)
options.extend(selected_jammers)
random.shuffle(options)
puzzles.append(question)
answers.append(correct_answer)
all_options.append(options)
question_texts = []
for i, (puzzle, options) in enumerate(zip(puzzles, all_options)):
#options_text = "\n".join([f" {chr(97+j)}. {opt}" for j, opt in enumerate(options)])
for i, puzzle in enumerate(puzzles):
question_texts.append(f"{self.prefix}:\n {i+1}. {puzzle}")
self.wording = question_texts
self.answer = answers
self.options = all_options
def __str__(self):
return f"{self.wording}\n正确答案: {', '.join(self.answer)}"
puz = SelectionPuzzle({"1+1":"2", "1+2":"3", "1+3": "4"}, ["2","5","0"], 3, '求值: ')
puz.refresh()
print(puz.wording)
print(puz.answer)
print(puz.options)
if __name__ == "__main__":
puz = SelectionPuzzle(
{"1+1": "2", "1+2": "3", "1+3": "4"},
["2", "5", "0"],
3,
'求值: '
)
puz.refresh()
print(puz.wording)
print(puz.answer)
print(puz.options)

View File

@@ -32,7 +32,6 @@ class Reactor():
"""反应堆对象, 处理和分配一次文件记忆流程的资源与策略"""
def __init__(self, nucleon_file: pt.NucleonUnion, electron_file: pt.ElectronUnion, screen, tasked_num):
# 导入原子对象
self.reported = set()
self.nucleon_file = nucleon_file
self.electron_file = electron_file
self.tasked_num = tasked_num
@@ -41,6 +40,7 @@ class Reactor():
counter = self.tasked_num
self.screen = screen
self.electron_dict = electron_file.electrons_dict
self.quality_dict = {}
def electron_dict_get_fallback(key) -> pt.Electron:
value = self.electron_dict.get(key)
# 如果值不存在,则设置默认值
@@ -71,7 +71,6 @@ class Reactor():
self.procession: list
self.failed: list
self.round_title: str
self.reported: set
self.current_atom: typing.Tuple[pt.Electron, pt.Nucleon, dict]
self.round_set = 0
self.current_atom = pt.Atom.placeholder()
@@ -127,23 +126,25 @@ class Reactor():
return 0
def save(self):
self._deploy_report()
print("Progress saved")
# self.nucleon_file.save()
self.electron_file.save()
def _deploy_report(self):
"部署所有 _report"
for e, q in self.quality_dict.items():
if q == -1:
e.revisor(5, True)
continue
e.revisor(q)
def report(self, atom, quality):
"""
0: 初次激活/通过
1: 不通过
"""
"向反应器和最低质量记录汇报"
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])
self.quality_dict[atom[0]] = -1
print(self.quality_dict)
return
self.quality_dict[atom[0]] = min(quality, self.quality_dict.get(atom[0], 5))
if quality <= 3:
self.failed.append(atom)
return 1
else:
return 0
print(self.quality_dict)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

BIN
readme_src/img1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
readme_src/img2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 KiB

BIN
readme_src/img3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

BIN
readme_src/img4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB