You've already forked HeurAMS-legacy
161 lines
5.5 KiB
Python
161 lines
5.5 KiB
Python
# 单项选择题
|
|
from textual.widgets import (
|
|
Label,
|
|
Button,
|
|
)
|
|
from textual.containers import ScrollableContainer, Container
|
|
from textual.widget import Widget
|
|
import heurams.kernel.particles as pt
|
|
import heurams.kernel.puzzles as pz
|
|
from .base_puzzle_widget import BasePuzzleWidget
|
|
from typing import TypedDict
|
|
from heurams.services.hasher import hash
|
|
from heurams.services.logger import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class Setting(TypedDict):
|
|
__origin__: str
|
|
__hint__: str
|
|
primary: str # 显示的提示文本
|
|
mapping: dict # 谜题到答案的映射
|
|
jammer: list # 干扰项
|
|
max_riddles_num: int # 最大谜题数量
|
|
prefix: str # 提示词前缀
|
|
|
|
|
|
class MCQPuzzle(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 = []
|
|
self.alia = alia
|
|
self.hashmap = dict()
|
|
self.cursor = 0
|
|
self.atom = atom
|
|
self._load()
|
|
|
|
def _load(self):
|
|
cfg = self.atom.registry["orbital"]["puzzles"][self.alia]
|
|
self.puzzle = pz.MCQPuzzle(
|
|
cfg["mapping"], cfg["jammer"], int(cfg["max_riddles_num"]), cfg["prefix"]
|
|
)
|
|
self.puzzle.refresh()
|
|
|
|
def compose(self):
|
|
self.atom.registry["nucleon"].do_eval()
|
|
setting: Setting = self.atom.registry["nucleon"].metadata["orbital"]["puzzles"][
|
|
self.alia
|
|
]
|
|
logger.debug(f"Puzzle Setting: {setting}")
|
|
current_options = self.puzzle.options[len(self.inputlist)]
|
|
yield Label(setting["primary"], id="sentence")
|
|
yield Label(self.puzzle.wording[len(self.inputlist)], id="puzzle")
|
|
yield Label(f"当前输入: {self.inputlist}", id="inputpreview")
|
|
|
|
# 渲染当前问题的选项
|
|
with Container(id="btn-container"):
|
|
for i in current_options:
|
|
self.hashmap[str(hash(i))] = i
|
|
btnid = f"sel{str(self.cursor).zfill(3)}-{hash(i)}"
|
|
logger.debug(f"建立按钮 {btnid}")
|
|
yield Button(i, id=f"{btnid}")
|
|
|
|
yield Button("退格", id="delete")
|
|
|
|
def update_display(self, error=0):
|
|
# 更新预览标签
|
|
preview = self.query_one("#inputpreview")
|
|
preview.update(f"当前输入: {self.inputlist}") # type: ignore
|
|
logger.debug("已经更新预览标签")
|
|
# 更新问题标签
|
|
puzzle_label = self.query_one("#puzzle")
|
|
current_question_index = len(self.inputlist)
|
|
if current_question_index < len(self.puzzle.wording):
|
|
puzzle_label.update(self.puzzle.wording[current_question_index]) # type: ignore
|
|
|
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
"""处理按钮点击事件"""
|
|
event.stop()
|
|
button_id = event.button.id
|
|
|
|
if button_id == "delete":
|
|
# 退格处理
|
|
if len(self.inputlist) > 0:
|
|
self.inputlist.pop()
|
|
self.refresh_buttons()
|
|
self.update_display()
|
|
|
|
elif button_id.startswith("sel"): # type: ignore
|
|
# 选项选择处理
|
|
answer_text = self.hashmap[button_id[7:]] # type: ignore
|
|
self.inputlist.append(answer_text)
|
|
logger.debug(f"{self.inputlist}")
|
|
# 检查是否完成所有题目
|
|
if len(self.inputlist) >= len(self.puzzle.answer):
|
|
is_correct = self.inputlist == self.puzzle.answer
|
|
rating = 4 if is_correct else 2
|
|
|
|
self.screen.rating = rating # type: ignore
|
|
self.handler(rating)
|
|
# 重置输入(如果回答错误)
|
|
if not is_correct:
|
|
self.inputlist = []
|
|
self.refresh_buttons()
|
|
self.update_display()
|
|
else:
|
|
# 进入下一题
|
|
self.refresh_buttons()
|
|
self.update_display()
|
|
|
|
def refresh_buttons(self):
|
|
"""刷新按钮显示(用于题目切换)"""
|
|
# 移除所有选项按钮
|
|
logger.debug("刷新按钮")
|
|
self.cursor += 1
|
|
container = self.query_one("#btn-container")
|
|
buttons_to_remove = [
|
|
child
|
|
for child in container.children
|
|
if hasattr(child, "id") and child.id and child.id.startswith("sel")
|
|
]
|
|
|
|
for button in buttons_to_remove:
|
|
logger.info(button)
|
|
container.remove_children("#" + button.id) # type: ignore
|
|
|
|
# 添加当前题目的选项按钮
|
|
current_question_index = len(self.inputlist)
|
|
if current_question_index < len(self.puzzle.options):
|
|
current_options = self.puzzle.options[current_question_index]
|
|
for option in current_options:
|
|
button_id = f"sel{str(self.cursor).zfill(3)}-{hash(option)}"
|
|
if button_id not in self.hashmap:
|
|
self.hashmap[button_id] = option
|
|
new_button = Button(option, id=button_id)
|
|
container.mount(new_button)
|
|
|
|
def handler(self, rating):
|
|
if self.atom.lock():
|
|
pass
|
|
else:
|
|
self.atom.minimize(rating)
|