File size: 6,871 Bytes
9b1f754
8cef97c
9b1f754
 
 
 
d811247
9b1f754
259f80e
d811247
9b1f754
47e16f4
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
 
bed6593
259f80e
9b1f754
 
 
 
259f80e
9b1f754
 
259f80e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9b1f754
2a30efc
 
9b1f754
 
 
259f80e
 
d811247
259f80e
 
9b1f754
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
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"}


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)


font = gr.themes.GoogleFont("Noto Sans")
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("回到設定")

    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")