Files
HeurAMS/src/heurams/interface/widgets/cloze_puzzle.py
2026-04-23 00:13:57 +08:00

138 lines
4.0 KiB
Python

import copy
import random
from typing import TypedDict
from textual.containers import ScrollableContainer
from textual.widget import Widget
from textual.widgets import Button, Label, Markdown
from textual.events import Key
import heurams.kernel.particles as pt
import heurams.kernel.puzzles as pz
from heurams.services.hasher import hash
from heurams.services.logger import get_logger
from .base_puzzle_widget import BasePuzzleWidget
logger = get_logger(__name__)
class Setting(TypedDict):
__origin__: str
__hint__: str
text: str
delimiter: str
min_denominator: str
class ClozePuzzle(BasePuzzleWidget):
def __init__(
self,
*children: Widget,
atom: pt.Atom,
alia: str = "",
name: str | None = None,
id: str | None = None,
classes: str | None = None,
disabled: bool = False,
markup: bool = True,
) -> None:
super().__init__(
*children,
atom=atom,
name=name,
id=id,
classes=classes,
disabled=disabled,
markup=markup,
)
self.inputlist = list()
self.hashtable = {}
self.alia = alia
self._load()
self.btn_shortcuts = {}
self.hashmap = dict()
def _load(self):
setting = self.atom.registry["nucleon"]["puzzles"][self.alia]
self.puzzle = pz.ClozePuzzle(
text=setting["text"],
delimiter=setting["delimiter"],
min_denominator=int(setting["min_denominator"]),
)
self.puzzle.refresh()
self.ans = copy.copy(self.puzzle.answer) # 乱序
random.shuffle(self.ans)
def compose(self):
yield Label(self.puzzle.wording, id="sentence")
yield Markdown(f"> {self.listprint(self.inputlist)}", id="inputpreview")
# 渲染当前问题的选项
with ScrollableContainer(id="btn-container") as s:
c = 0
for i in self.ans:
h = str(hash(i))
if hash(i) in self.hashmap.keys():
continue
c += 1
self.hashmap[h] = i
btnid = f"sel000-{h}"
logger.debug(f"建立按钮 {btnid}")
self.btn_shortcuts[f"{c}"] = btnid
yield Button(f"[{c}] {i}", id=f"{btnid}")
s.focus()
yield Button("退格", id="delete")
self.btn_shortcuts[f"0"] = "delete"
self.btn_shortcuts[f"backspace"] = "delete"
self.btn_shortcuts[f"delete"] = "delete"
def listprint(self, lst):
s = ""
if lst:
lastone = lst[-1]
for i in lst[:-1]:
s += i + " "
s += f" `{lastone}`"
return s
def update_display(self):
preview = self.query_one("#inputpreview")
preview.update(f"> {self.listprint(self.inputlist)}") # type: ignore
def on_button_pressed(self, event: Button.Pressed) -> None:
button_id = event.button.id
if button_id == "delete":
if len(self.inputlist) > 0:
self.inputlist.pop()
self.update_display()
else:
answer_text = self.hashmap[button_id[7:]] # type: ignore
self.inputlist.append(answer_text)
self.update_display()
if len(self.inputlist) >= len(self.puzzle.answer):
is_correct = self.inputlist == self.puzzle.answer
rating = 4 if is_correct else 2
self.handler(rating)
self.screen.rating = rating # type: ignore
if not is_correct:
self.inputlist = []
self.update_display()
def handler(self, rating):
if self.atom.lock():
pass
else:
self.atom.minimize(rating)
def on_key(self, event: Key) -> None:
self.notify(event.key)
if event.key in self.btn_shortcuts:
btn_id = self.btn_shortcuts.get(event.key)
btn_id = "#" + btn_id
self.query_one(btn_id, Button).press()