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"] a_ext1 = ["g", "z", "d", "b"] a_ext2 = ["p"] b_base = ["a", "i", "u", "e", "o"] b_ext = ["ya", "yo", "yu"] c_opt = ["濁音", "半濁音", "拗音"] invalid = ["yi", "ye", "wi", "wu", "we"] spec_map = {"cya": "cha", "cyu": "chu", "cyo": "cho"} def next_question(hira, kata, c, quiz_list): info_flag = False if not quiz_list: info_flag = True quiz_list = init_question(hira, kata, c) if not quiz_list: gr.Info("請選擇至少一項測驗!") return None, quiz_list if info_flag: gr.Info("已完成一輪測試!") return quiz_list.pop(), quiz_list def init_question(hira, kata, c): curr_hira = deepcopy(hira) curr_hira += deepcopy(a_ext1) if "濁音" in c else [] curr_hira += deepcopy(a_ext2) if "半濁音" in c else [] curr_kata = deepcopy(kata) if curr_kata: curr_kata += deepcopy(a_ext1) if "濁音" in c else [] curr_kata += deepcopy(a_ext2) if "半濁音" in c else [] curr_b = deepcopy(b_base) curr_b += deepcopy(b_ext) if "拗音" in c else [] hira_list = [to_hira(a, b) for a in curr_hira for b in curr_b if is_valid(a, b)] kata_list = [to_kata(a, b) for a in curr_kata for b in curr_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 is_valid(aa: str, bb: str): if f"{aa}{bb}" in invalid: return False if aa == "y": if bb[0] == "y": return False if bb[0] == "w": return False if bb == "": 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): 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 return correct, total, msg, "" def reset_score(): return 0, 0, "0/0", "" def select_all(): return a_base, a_base, c_opt def deselect_all(): return [], [], [] font = gr.themes.GoogleFont("NotoSans CJK") theme = gr.themes.Soft(font=font) with gr.Blocks(theme=theme, title="假名小測驗") as app: correct = gr.State(0) total = gr.State(0) quiz_list = gr.State(init_question(a_base[:5], [], [])) with gr.Row(): with gr.Column(): with gr.Tab("測驗"): with gr.Row(): desc = "完成設定後按下「開始測驗」" question = gr.Textbox(placeholder=desc, label="題目", interactive=False) score = gr.Textbox("0/0", label="分數") answer = gr.Textbox(label="作答") with gr.Column(): with gr.Tab("設定"): setting_hira = gr.CheckboxGroup(a_base, value=a_base[:5], label="平假名") setting_kata = gr.CheckboxGroup(a_base, label="片假名") setting_c = gr.CheckboxGroup(c_opt, label="延伸") with gr.Row(): select_all_btn = gr.Button("全選") deselect_all_btn = gr.Button("全不選") apply_btn = gr.Button("開始測驗") chk_inn = [question, answer, correct, total] chk_out = [correct, total, score, answer] chk_arg = dict(fn=check, inputs=chk_inn, outputs=chk_out, show_progress="hidden") nq_inn = [setting_hira, setting_kata, setting_c, 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_kata, setting_c] ini_out = [quiz_list] 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") opt_out = [setting_hira, setting_kata, setting_c] answer.submit(**chk_arg).then(**nq_arg) apply_btn.click(**ini_arg).then(**reset_arg).then(**nq_arg) select_all_btn.click(select_all, outputs=opt_out, show_progress="hidden") deselect_all_btn.click(deselect_all, outputs=opt_out, show_progress="hidden") app.launch(favicon_path="icon.png")