Spaces:
Running
Running
File size: 7,920 Bytes
9b1f754 8cef97c 9b1f754 d811247 9b1f754 259f80e d811247 9b1f754 f6a4c89 9b1f754 259f80e 8cef97c 259f80e d811247 8cef97c 9b1f754 259f80e 9b1f754 259f80e 9b1f754 259f80e 9b1f754 259f80e 9b1f754 d811247 9b1f754 259f80e 9b1f754 2a30efc de14a07 9b1f754 de14a07 9b1f754 de14a07 9b1f754 2a30efc 259f80e 9b1f754 8cef97c 9b1f754 d811247 9b1f754 259f80e 47e16f4 1dbaa4c 47e16f4 9b1f754 1dbaa4c 9b1f754 259f80e 9b1f754 8cef97c 259f80e 8cef97c d811247 259f80e d811247 259f80e d811247 f6a4c89 259f80e 9b1f754 259f80e 9b1f754 259f80e 9b1f754 2a30efc 9b1f754 259f80e d811247 259f80e 9b1f754 f6a4c89 259f80e 9b1f754 259f80e 8cef97c 9b1f754 259f80e 8cef97c 9b1f754 8cef97c 259f80e 9b1f754 2a30efc |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
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")
|