Compare commits

2 Commits

Author SHA1 Message Date
d5ef5e84d0 规范代码 2025-08-06 06:11:30 +08:00
dd74dddf00 规范代码 2025-08-06 06:11:22 +08:00
6 changed files with 344 additions and 261 deletions

View File

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

View File

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

167
main.py
View File

@@ -1,35 +1,43 @@
from textual.app import App, ComposeResult 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.containers import Container, Horizontal, Center
from textual.screen import Screen from textual.screen import Screen
import pathlib import pathlib
import threading import threading
import edge_tts as tts import edge_tts as tts
from playsound import playsound from playsound import playsound
import particles as pt import particles as pt
from reactor import Reactor, Apparatus from reactor import Reactor, Apparatus
import auxiliary as aux import auxiliary as aux
import compositions as compo import compositions as compo
import builtins import builtins
# 保存原始的 open 函数
_original_open = builtins.open _original_open = builtins.open
# 定义新的 open 函数,默认使用 UTF-8
def _open(*args, **kwargs): def _open(*args, **kwargs):
if 'encoding' not in kwargs: if "encoding" not in kwargs:
kwargs['encoding'] = 'utf-8' kwargs["encoding"] = "utf-8"
return _original_open(*args, **kwargs) return _original_open(*args, **kwargs)
# 替换全局的 open
builtins.open = _open builtins.open = _open
ver = '0.3.0b' ver = "0.3.0b"
config = aux.ConfigFile("config.toml") config = aux.ConfigFile("config.toml")
class MemScreen(Screen): class MemScreen(Screen):
BINDINGS = [ BINDINGS = [
("d", "toggle_dark", "改变色调"), ("d", "toggle_dark", "改变色调"),
@@ -49,38 +57,30 @@ class MemScreen(Screen):
("/", "press('q0')", None), ("/", "press('q0')", None),
] ]
btn = dict() btn = dict()
def __init__( def __init__(
self, self,
nucleon_file: pt.NucleonUnion, nucleon_file: pt.NucleonUnion,
electron_file: pt.ElectronUnion, electron_file: pt.ElectronUnion,
tasked_num tasked_num,
): ):
super().__init__(name=None, id=None, classes=None) super().__init__(name=None, id=None, classes=None)
self.reactor = Reactor(nucleon_file, electron_file, self, tasked_num) self.reactor = Reactor(nucleon_file, electron_file, self, tasked_num)
self.stage = 1 self.stage = 1
self.stage += self.reactor.set_round_templated(self.stage) self.stage += self.reactor.set_round_templated(self.stage)
##print(self.reactor.procession)
self.reactor.forward() self.reactor.forward()
#self.compo:compo.Composition = compo.Placeholder(self)
self.compo = next(self.reactor.current_appar) self.compo = next(self.reactor.current_appar)
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
#print(self.compo)
yield Header(show_clock=True) yield Header(show_clock=True)
with Center(): with Center():
yield Static(f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}") yield Static(
#yield ProgressBar(total=len(self.reactor.procession) - 1, show_percentage=False, show_eta=False, id="progress") f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}"
)
yield from self.compo.compose() yield from self.compo.compose()
yield Footer() yield Footer()
"""
def _get_progress_text(self):
return f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}"
"""
def on_mount(self): def on_mount(self):
# 首次挂载时调用
pass pass
def on_button_pressed(self, event): def on_button_pressed(self, event):
@@ -90,13 +90,12 @@ class MemScreen(Screen):
def _forward_judge(self, ret): def _forward_judge(self, ret):
if ret == -1: if ret == -1:
return return
if ret == 0: # 成功 if ret == 0:
try: try:
self.compo = next(self.reactor.current_appar) self.compo = next(self.reactor.current_appar)
self.refresh_ui() self.refresh_ui()
except StopIteration: except StopIteration:
nxt = self.reactor.forward(1) nxt = self.reactor.forward(1)
#print(2)
try: try:
self.compo = next(self.reactor.current_appar) self.compo = next(self.reactor.current_appar)
except: except:
@@ -106,13 +105,13 @@ class MemScreen(Screen):
if self.stage == 4: if self.stage == 4:
if config.get("save"): if config.get("save"):
self.reactor.save() self.reactor.save()
self.compo = compo.Finished(self, None, pt.Atom.placeholder()) self.compo = compo.Finished(
self, None, pt.Atom.placeholder()
)
self.refresh_ui() self.refresh_ui()
#self._show_finished_screen("今日目标已完成")
else: else:
self.reactor.set_round_templated(self.stage) self.reactor.set_round_templated(self.stage)
self.reactor.forward(1) self.reactor.forward(1)
#self._update_ui()
self.stage += 1 self.stage += 1
self.compo = next(self.reactor.current_appar) self.compo = next(self.reactor.current_appar)
self.refresh_ui() self.refresh_ui()
@@ -121,71 +120,47 @@ class MemScreen(Screen):
else: else:
self.refresh_ui() self.refresh_ui()
return return
if ret == 1: # 不允许前进 if ret == 1:
self.refresh_ui() self.refresh_ui()
return return
def refresh_ui(self): def refresh_ui(self):
self.call_later(self.recompose) 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): def report(self, quality):
assessment = self.reactor.report(self.reactor.current_atom, quality) assessment = self.reactor.report(self.reactor.current_atom, quality)
return assessment 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 action_play_voice(self):
def play(): def play():
cache_dir = pathlib.Path(f"./cache/voice/") cache_dir = pathlib.Path(f"./cache/voice/")
cache_dir.mkdir(parents = True, exist_ok = True) cache_dir.mkdir(parents=True, exist_ok=True)
cache = cache_dir / f"{self.reactor.current_atom[1].content.replace("/","")}.wav" cache = cache_dir / f"{self.reactor.current_atom[1].content.replace('/','')}.wav"
if not cache.exists(): if not cache.exists():
communicate = tts.Communicate(self.reactor.current_atom[1].content.replace("/",""), "zh-CN-YunjianNeural") communicate = tts.Communicate(
communicate.save_sync(f"./cache/voice/{self.reactor.current_atom[1].content.replace("/","")}.wav") 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)) playsound(str(cache))
threading.Thread(target=play).start() threading.Thread(target=play).start()
def action_toggle_dark(self): def action_toggle_dark(self):
self.app.action_toggle_dark() self.app.action_toggle_dark()
def action_pop_screen(self): def action_pop_screen(self):
"""返回到上一个屏幕"""
self.app.pop_screen() 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) super().__init__(name=None, id=None, classes=None)
self.nucleon_file = nucleon_file self.nucleon_file = nucleon_file
self.electron_file = electron_file self.electron_file = electron_file
@@ -194,10 +169,19 @@ class PreparationScreen(Screen):
yield Header(show_clock=True) yield Header(show_clock=True)
with Container(id="learning_screen_container"): with Container(id="learning_screen_container"):
yield Label(f"记忆项目: [b]{self.nucleon_file.name}[/b]\n") yield Label(f"记忆项目: [b]{self.nucleon_file.name}[/b]\n")
yield Label(f"核子文件对象: ./nucleon/[b]{self.nucleon_file.name}[/b].toml") yield Label(
yield Label(f"子文件对象: ./electron/[b]{self.electron_file.name}[/b].toml") 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 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(f"\n全文如下:\n")
yield Static(self._get_full_content(), classes="full") yield Static(self._get_full_content(), classes="full")
yield Footer() yield Footer()
@@ -205,7 +189,7 @@ class PreparationScreen(Screen):
def _get_full_content(self): def _get_full_content(self):
content = "" content = ""
for i in self.nucleon_file.nucleons: for i in self.nucleon_file.nucleons:
content += i['content'] content += i["content"]
return content return content
def action_go_back(self): def action_go_back(self):
@@ -215,76 +199,79 @@ class PreparationScreen(Screen):
self.app.exit() self.app.exit()
def on_button_pressed(self, event: Button.Pressed) -> None: def on_button_pressed(self, event: Button.Pressed) -> None:
pass
if event.button.id == "start_memorizing_button": if event.button.id == "start_memorizing_button":
#init_file(Path(self.atom_file).name) newscr = MemScreen(
newscr = MemScreen(self.nucleon_file, self.electron_file, config.get("tasked_number", 8)) self.nucleon_file, self.electron_file, config.get("tasked_number", 8)
self.app.push_screen(
newscr
) )
#if event.button.id == "edit_metadata_button": self.app.push_screen(newscr)
# init_file(Path(self.atom_file).name)
# os.system("reset;nano ./data/" + str(Path(self.atom_file).name.replace(".txt", "_atoms.json")))
class FileSelectorScreen(Screen): class FileSelectorScreen(Screen):
global ver global ver
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header(show_clock=True) yield Header(show_clock=True)
yield Container( yield Container(
Label(f'欢迎使用 "潜进" 辅助记忆软件, 版本 {ver}', classes="title-label"), Label(f'欢迎使用 "潜进" 辅助记忆软件, 版本 {ver}', classes="title-label"),
Label("选择要学习的文件:", 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() yield Footer()
def on_mount(self) -> None: def on_mount(self) -> None:
file_list_widget = self.query_one("#file-list", ListView) file_list_widget = self.query_one("#file-list", ListView)
nucleon_path = pathlib.Path("./nucleon") 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: if nucleon_files:
for filename in nucleon_files: for filename in nucleon_files:
file_list_widget.append(ListItem(Label(filename))) file_list_widget.append(ListItem(Label(filename)))
else: else:
file_list_widget.append(ListItem(Static("在 ./nucleon/ 中未找到任何核子文件. 请放置文件后重启应用."))) file_list_widget.append(
ListItem(Static("在 ./nucleon/ 中未找到任何核子文件. 请放置文件后重启应用."))
)
file_list_widget.disabled = True file_list_widget.disabled = True
def on_list_view_selected(self, event: ListView.Selected) -> None: def on_list_view_selected(self, event: ListView.Selected) -> None:
if not isinstance(event.item, ListItem): if not isinstance(event.item, ListItem):
self.notify("无法选择此项。", severity="error")
return return
selected_label = event.item.query_one(Label) selected_label = event.item.query_one(Label)
if "未找到任何 .toml 文件" in str(selected_label.renderable): if "未找到任何 .toml 文件" in str(selected_label.renderable):
self.notify("请先在 `./atoms/` 目录中放置 .toml 文件。", severity="warning")
return return
selected_filename = str(selected_label.renderable) 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 electron_file_path = pathlib.Path("./electron") / selected_filename
if electron_file_path.exists(): if electron_file_path.exists():
pass pass
else: else:
electron_file_path.touch() electron_file_path.touch()
electron_file = pt.ElectronUnion(pathlib.Path("./electron") / selected_filename) electron_file = pt.ElectronUnion(
# self.notify(f"已选择: {selected_filename}", timeout=2) pathlib.Path("./electron") / selected_filename
)
self.app.push_screen(PreparationScreen(nucleon_file, electron_file)) self.app.push_screen(PreparationScreen(nucleon_file, electron_file))
def action_quit_app(self) -> None: def action_quit_app(self) -> None:
self.app.exit() self.app.exit()
class AppLauncher(App): class AppLauncher(App):
CSS_PATH = "styles.tcss" CSS_PATH = "styles.tcss"
TITLE = '潜进 - 辅助记忆程序' TITLE = "潜进 - 辅助记忆程序"
BINDINGS = [("escape", "quit", "退出"), ("d", "toggle_dark", "改变色调")] BINDINGS = [("escape", "quit", "退出"), ("d", "toggle_dark", "改变色调")]
SCREENS = { SCREENS = {
"file_selection_screen": FileSelectorScreen, "file_selection_screen": FileSelectorScreen,
} }
def on_mount(self) -> None: def on_mount(self) -> None:
#self.action_toggle_dark()
self.push_screen("file_selection_screen") self.push_screen("file_selection_screen")
if __name__ == "__main__": if __name__ == "__main__":
app = AppLauncher() app = AppLauncher()
app.run() app.run()

View File

@@ -3,30 +3,29 @@ import toml
import time import time
import auxiliary as aux 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): def __init__(self, content: str, metadata: dict):
self.content = content self.content = content
self.metadata = metadata self.metadata = metadata
if metadata == {}: if metadata == {}:
#print("NULL") # print("NULL")
self._default_init() self._default_init()
def _default_init(self): def _default_init(self):
defaults = { defaults = {
'efactor': 2.5, # 易度系数, 越大越简单, 最大为5 'efactor': 2.5, # 易度系数, 越大越简单, 最大为5
'real_rept': 0, # (实际)重复次数 'real_rept': 0, # (实际)重复次数
'rept': 0, # (有效)重复次数 'rept': 0, # (有效)重复次数
'interval': 0, # 最佳间隔 'interval': 0, # 最佳间隔
'last_date': 0, # 上一次复习的时间戳 'last_date': 0, # 上一次复习的时间戳
'next_date': 0, # 将要复习的时间戳 'next_date': 0, # 将要复习的时间戳
'is_activated': 0, # 激活状态 'is_activated': 0, # 激活状态
# *NOTE: 此处"时间戳"是以天为单位的整数, 即 UNIX 时间戳除以一天的秒数取整 # *NOTE: 此处"时间戳"是以天为单位的整数, 即 UNIX 时间戳除以一天的秒数取整
'last_modify': time.time() # 最后修改时间戳(此处是UNIX时间戳) 'last_modify': time.time() # 最后修改时间戳(此处是UNIX时间戳)
} }
self.metadata = defaults self.metadata = defaults
@@ -52,40 +51,46 @@ class Electron():
if quality == -1: if quality == -1:
return -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']) self.metadata['efactor'] = max(1.3, self.metadata['efactor'])
if quality < 3: if quality < 3:
# 若保留率低于 3重置重复次数 # 若保留率低于 3重置重复次数
self.metadata['rept'] = 0 self.metadata['rept'] = 0
self.metadata['interval'] = 0 # 设为0以便下面重新计算 I(1) self.metadata['interval'] = 0 # 设为0以便下面重新计算 I(1)
else: else:
self.metadata['rept'] += 1 self.metadata['rept'] += 1
self.metadata['real_rept'] += 1 self.metadata['real_rept'] += 1
if is_new_activation: # 初次激活 if is_new_activation: # 初次激活
self.metadata['rept'] = 0 self.metadata['rept'] = 0
self.metadata['efactor'] = 2.5 self.metadata['efactor'] = 2.5
if self.metadata['rept'] == 0: # 刚被重置或初次激活后复习 if self.metadata['rept'] == 0: # 刚被重置或初次激活后复习
self.metadata['interval'] = 1 # I(1) self.metadata['interval'] = 1 # I(1)
elif self.metadata['rept'] == 1: elif self.metadata['rept'] == 1:
self.metadata['interval'] = 6 # I(2) 经验公式 self.metadata['interval'] = 6 # I(2) 经验公式
else: 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['last_date'] = aux.get_daystamp()
self.metadata['next_date'] = aux.get_daystamp() + self.metadata['interval'] self.metadata['next_date'] = aux.get_daystamp() + self.metadata['interval']
self.metadata['last_modify'] = time.time() self.metadata['last_modify'] = time.time()
def __str__(self): def __str__(self):
return (f"记忆单元预览 \n" return (
f"内容: '{self.content}' \n" f"记忆单元预览 \n"
f"易度系数: {self.metadata['efactor']:.2f} \n" f"内容: '{self.content}' \n"
f"已经重复的次数: {self.metadata['rept']} \n" f"易度系数: {self.metadata['efactor']:.2f} \n"
f"下次间隔: {self.metadata['interval']} \n" f"已经重复的次数: {self.metadata['rept']} \n"
f"下次复习日期时间戳: {self.metadata['next_date']}") f"下次间隔: {self.metadata['interval']}\n"
f"下次复习日期时间戳: {self.metadata['next_date']}"
)
def __eq__(self, other): def __eq__(self, other):
if self.content == other.content: if self.content == other.content:
@@ -106,10 +111,6 @@ class Electron():
def __setitem__(self, key, value): def __setitem__(self, key, value):
if key == "content": if key == "content":
raise AttributeError("content 应为只读") raise AttributeError("content 应为只读")
# 可以在此处添加更复杂的验证逻辑,例如只允许修改预定义的 metadata 键
# 或者根据键进行类型检查等。
self.metadata[key] = value self.metadata[key] = value
self.metadata['last_modify'] = time.time() self.metadata['last_modify'] = time.time()
@@ -123,6 +124,7 @@ class Electron():
def placeholder(): def placeholder():
return Electron("电子对象样例内容", {}) return Electron("电子对象样例内容", {})
class Nucleon: class Nucleon:
"""核子: 材料元数据""" """核子: 材料元数据"""
@@ -151,7 +153,8 @@ class Nucleon:
def placeholder(): def placeholder():
return Nucleon("核子对象样例内容", {}) return Nucleon("核子对象样例内容", {})
class NucleonUnion():
class NucleonUnion:
""" """
替代原有 NucleonFile 类, 支持复杂逻辑 替代原有 NucleonFile 类, 支持复杂逻辑
@@ -166,6 +169,7 @@ class NucleonUnion():
Parameters: Parameters:
path (Path): 包含核子数据的文件路径。 path (Path): 包含核子数据的文件路径。
""" """
def __init__(self, path): def __init__(self, path):
self.path = path self.path = path
self.name = path.name.replace(path.suffix, "") self.name = path.name.replace(path.suffix, "")
@@ -189,8 +193,10 @@ class NucleonUnion():
tmp = {i.content: i.metadata for i in self.nucleons} tmp = {i.content: i.metadata for i in self.nucleons}
toml.dump(tmp, f) toml.dump(tmp, f)
class ElectronUnion():
"取代原有 ElectronFile 类, 以支持复杂逻辑" class ElectronUnion:
"""取代原有 ElectronFile 类, 以支持复杂逻辑"""
def __init__(self, path): def __init__(self, path):
self.path = path self.path = path
self.name = path.name.replace(path.suffix, "") self.name = path.name.replace(path.suffix, "")
@@ -207,13 +213,15 @@ class ElectronUnion():
self.electrons = self.electrons_dict.values() self.electrons = self.electrons_dict.values()
def save(self): def save(self):
#print(1) # print(1)
with open(self.path, 'w') as f: with open(self.path, 'w') as f:
tmp = {i.content: i.metadata for i in self.electrons} tmp = {i.content: i.metadata for i in self.electrons}
#print(tmp) # print(tmp)
toml.dump(tmp, f) toml.dump(tmp, f)
"""class AtomicFile():
"""
class AtomicFile():
def __init__(self, path, type_="unknown"): def __init__(self, path, type_="unknown"):
self.path = path self.path = path
self.type_ = type_ self.type_ = type_
@@ -237,10 +245,10 @@ class ElectronUnion():
return "" return ""
def get_len(self): def get_len(self):
return len(self.datalist) return len(self.datalist)
""" """
class Atom():
class Atom:
@staticmethod @staticmethod
def placeholder(): def placeholder():
return (Electron.placeholder(), Nucleon.placeholder(), {}) return (Electron.placeholder(), Nucleon.placeholder(), {})
@@ -249,18 +257,30 @@ class Atom():
def advanced_placeholder(): def advanced_placeholder():
return ( return (
Electron("两只黄鹤鸣翠柳", {}), Electron("两只黄鹤鸣翠柳", {}),
Nucleon("两只黄鹤鸣翠柳", {"note": [], Nucleon(
"translation": "臣子李密陈言:我因命运不好,小时候遭遇到了不幸", "两只黄鹤鸣翠柳",
"keyword_note": {"险衅":"凶险祸患(这里指命运不好)", "":"早时,这里指年幼的时候", "":"'',指可忧患的事", "":"不幸,指丧父"}}), {
"note": [],
"translation": "臣子李密陈言:我因命运不好,小时候遭遇到了不幸",
"keyword_note": {
"险衅": "凶险祸患(这里指命运不好)",
"": "早时,这里指年幼的时候",
"": "'',指可忧患的事",
"": "不幸,指丧父"
}
}
),
{ {
"keydata":{ "keydata": {
"note": "笔记", "note": "笔记",
"keyword_note": "关键词翻译", "keyword_note": "关键词翻译",
"translation": "语句翻译"}, "translation": "语句翻译"
"testdata":{ },
"testdata": {
"additional_inf": ["translation", "note", "keyword_note"], "additional_inf": ["translation", "note", "keyword_note"],
"fill_blank_test": ["translation"], "fill_blank_test": ["translation"],
"draw_card_test": ["keyword_note"] "draw_card_test": ["keyword_note"]
}, },
"is_new_activation": 0 "is_new_activation": 0
}) }
)

View File

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

View File

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