Spaces:
Sleeping
Sleeping
import random | |
from copy import deepcopy | |
import gradio as gr | |
from romkan import to_hiragana, to_katakana, to_roma | |
a_base = ["a", "k", "s", "t", "n", "h", "m", "y", "r", "w", "nn"] | |
b_base = ["a", "i", "u", "e", "o"] | |
ext = ["g", "z", "d", "b", "p", "ya", "yo", "yu"] | |
invalid = ["yi", "ye", "wi", "wu", "we"] | |
spec_map = { | |
"cya": "cha", | |
"cyu": "chu", | |
"cyo": "cho", | |
"ji": "di", | |
"zu": "du", | |
"ja": "dya", | |
"jo": "dyo", | |
"ju": "dyu", | |
} | |
def next_question(hira, hira_ext, kata, kata_ext, quiz_list: list): | |
if not quiz_list: | |
quiz_list = init_question_list(hira, hira_ext, kata, kata_ext) | |
if not quiz_list: | |
gr.Info("請選擇至少一項測驗!") | |
return None, quiz_list | |
return quiz_list.pop(), quiz_list | |
def init_question_list(hira, hira_ext, kata, kata_ext): | |
hira_ext_a, hira_ext_b = split_ext(hira_ext) | |
kata_ext_a, kata_ext_b = split_ext(kata_ext) | |
curr_hira_a = deepcopy(hira) + deepcopy(hira_ext_a) | |
curr_kata_a = deepcopy(kata) + deepcopy(kata_ext_a) | |
curr_hira_b = deepcopy(b_base) + deepcopy(hira_ext_b) | |
curr_kata_b = deepcopy(b_base) + deepcopy(kata_ext_b) | |
hira_list = [to_hira(a, b) for a in curr_hira_a for b in curr_hira_b if is_valid(a, b)] | |
kata_list = [to_kata(a, b) for a in curr_kata_a for b in curr_kata_b if is_valid(a, b)] | |
quiz_list = hira_list + kata_list | |
quiz_list = list(set(quiz_list)) | |
random.shuffle(quiz_list) | |
return quiz_list | |
def split_ext(ext: list): | |
ext_a = [a for a in ext if a[0] != "y"] | |
ext_b = [b for b in ext if b[0] == "y"] | |
return ext_a, ext_b | |
def is_valid(aa: str, bb: str): | |
if f"{aa}{bb}" in invalid: | |
return False | |
if bb[0] == "y": | |
if aa[0] == "y": | |
return False | |
if aa == "w": | |
return False | |
if aa == "": | |
return False | |
if aa == "a" and bb[0] == "y": | |
return False | |
return True | |
def to_hira(a, b): | |
return to_hiragana(combine(a, b)) | |
def to_kata(a, b): | |
return to_katakana(combine(a, b)) | |
def combine(a, b): | |
if a == "nn": | |
return "n" | |
if a == "a": | |
a = "" | |
return f"{a}{b}" | |
def check(kana: str, roma: str, correct, total, record, quiz_list): | |
roma = roma.lower() | |
kana_roma = to_roma(kana) | |
roma_hira = to_hiragana(roma) | |
roma_kata = to_katakana(roma) | |
spec_roma = spec_map.get(roma, roma) | |
spec_hira = to_hiragana(spec_roma) | |
spec_kata = to_katakana(spec_roma) | |
c0 = kana_roma == roma | |
c1 = roma_hira == kana | |
c2 = roma_kata == kana | |
c3 = spec_hira == kana | |
c4 = spec_kata == kana | |
ans_correct = any((c0, c1, c2, c3, c4)) | |
correct += ans_correct | |
total += 1 | |
info = "正確" if ans_correct else f"錯誤 - {kana} ({kana_roma})" | |
msg = f"{correct}/{total} - " + info | |
if not ans_correct: | |
record = f"{record}{info}\n" | |
tab_idx = gr.Tabs(selected=1) | |
if not quiz_list: | |
record = f"{record}此輪得分 - {correct}/{total}\n\n" | |
correct, total = 0, 0 | |
tab_idx = gr.Tabs(selected=2) | |
return correct, total, msg, None, record, tab_idx | |
def reset_score(): | |
return 0, 0, "0/0", None, None, gr.Tabs() | |
def select_all(): | |
return a_base, ext, a_base, ext | |
def deselect_all(): | |
return [], [], [], [] | |
def select_all_hira(): | |
return a_base, ext, [], [] | |
def select_all_kata(): | |
return [], [], a_base, ext | |
def select_init(): | |
return a_base[:5], [], [], [] | |
def back_to_setting(): | |
return gr.Tabs(selected=0) | |
def read_text(path): | |
with open(path, "rt", encoding="UTF-8") as fp: | |
return fp.read() | |
font = gr.themes.GoogleFont("Noto Sans Mono") | |
theme = gr.themes.Soft(font=font, text_size=gr.themes.sizes.text_lg) | |
with gr.Blocks(theme=theme, title="假名小測驗") as app: | |
correct = gr.State(0) | |
total = gr.State(0) | |
quiz_list = gr.State(init_question_list(*select_init())) | |
with gr.Row(): | |
with gr.Tabs() as tabs: | |
with gr.Tab("設定", id=0): | |
with gr.Tab("平假名"): | |
setting_hira = gr.CheckboxGroup(a_base, value=a_base[:5], label="基本") | |
setting_hira_ext = gr.CheckboxGroup(ext, label="濁音、半濁音、拗音") | |
with gr.Tab("片假名"): | |
setting_kata = gr.CheckboxGroup(a_base, label="基本") | |
setting_kata_ext = gr.CheckboxGroup(ext, label="濁音、半濁音、拗音") | |
with gr.Row(): | |
select_all_hira_btn = gr.Button("平假名全選") | |
select_all_kata_btn = gr.Button("片假名全選") | |
with gr.Row(): | |
select_all_btn = gr.Button("全選") | |
deselect_all_btn = gr.Button("全不選") | |
with gr.Row(): | |
apply_btn = gr.Button("開始測驗") | |
with gr.Tab("測驗", id=1): | |
with gr.Row(): | |
desc = "完成設定後按下「開始測驗」" | |
question = gr.Textbox(placeholder=desc, label="題目", interactive=False) | |
score = gr.Textbox("0/0", label="分數") | |
answer = gr.Textbox(label="作答") | |
with gr.Tab("紀錄", id=2): | |
record = gr.TextArea(show_label=False) | |
with gr.Row(): | |
again_btn = gr.Button("再次測驗") | |
back_to_setting_btn = gr.Button("回到設定") | |
with gr.Tab("對照表"): | |
with gr.Tab("平假名"): | |
with gr.Tab("基本"): | |
gr.Markdown(read_text("info/hiragana-gojuon.md")) | |
with gr.Tab("濁音、半濁音"): | |
gr.Markdown(read_text("info/hiragana-dakuten.md")) | |
with gr.Tab("拗音"): | |
gr.Markdown(read_text("info/hiragana-yoon.md")) | |
with gr.Tab("片假名"): | |
with gr.Tab("基本"): | |
gr.Markdown(read_text("info/katakana-gojuon.md")) | |
with gr.Tab("濁音、半濁音"): | |
gr.Markdown(read_text("info/katakana-dakuten.md")) | |
with gr.Tab("拗音"): | |
gr.Markdown(read_text("info/katakana-yoon.md")) | |
def init_question(set_hira, set_hira_ext, set_kata, set_kata_ext): | |
quiz_list = init_question_list(set_hira, set_hira_ext, set_kata, set_kata_ext) | |
select = gr.Tabs(selected=1) if quiz_list else gr.Tabs(selected=0) | |
return quiz_list, select | |
chk_inn = [question, answer, correct, total, record, quiz_list] | |
chk_out = [correct, total, score, answer, record, tabs] | |
chk_arg = dict(fn=check, inputs=chk_inn, outputs=chk_out, show_progress="hidden") | |
nq_inn = [setting_hira, setting_hira_ext, setting_kata, setting_kata_ext, quiz_list] | |
nq_out = [question, quiz_list] | |
nq_arg = dict(fn=next_question, inputs=nq_inn, outputs=nq_out, show_progress="hidden") | |
ini_inn = [setting_hira, setting_hira_ext, setting_kata, setting_kata_ext] | |
ini_out = [quiz_list, tabs] | |
ini_arg = dict(fn=init_question, inputs=ini_inn, outputs=ini_out, show_progress="hidden") | |
reset_arg = dict(fn=reset_score, outputs=chk_out, show_progress="hidden") | |
answer.submit(**chk_arg).then(**nq_arg) | |
apply_btn.click(**ini_arg).then(**reset_arg).then(**nq_arg) | |
again_btn.click(**ini_arg).then(**reset_arg).then(**nq_arg) | |
select_all_hira_btn.click(select_all_hira, outputs=ini_inn, show_progress="hidden") | |
select_all_kata_btn.click(select_all_kata, outputs=ini_inn, show_progress="hidden") | |
select_all_btn.click(select_all, outputs=ini_inn, show_progress="hidden") | |
deselect_all_btn.click(deselect_all, outputs=ini_inn, show_progress="hidden") | |
back_to_setting_btn.click(back_to_setting, None, tabs, show_progress="hidden") | |
app.launch(favicon_path="icon.png") | |