| import gradio as gr |
| import random |
| import math |
|
|
| |
| NUMBERS_SV = { |
| 1: "ett", 2: "tvรฅ", 3: "tre", 4: "fyra", |
| 5: "fem", 6: "sex", 7: "sju", 8: "รฅtta", |
| 9: "nio", 10: "tio", 11: "elva", 12: "tolv" |
| } |
|
|
| |
| NICE_MINUTES = [0, 15, 30, 45] |
|
|
| def get_swedish_time(hour: int, minute: int) -> str: |
| h12 = hour % 12 or 12 |
| next_h = (h12 % 12) + 1 |
| if minute == 0: |
| return f"Klockan รคr {NUMBERS_SV[h12]}" |
| elif minute == 15: |
| return f"Kvart รถver {NUMBERS_SV[h12]}" |
| elif minute == 30: |
| return f"Halv {NUMBERS_SV[next_h]}" |
| elif minute == 45: |
| return f"Kvart i {NUMBERS_SV[next_h]}" |
| else: |
| return f"Klockan รคr {NUMBERS_SV[h12]}" |
|
|
| |
| def draw_clock(hour: int, minute: int) -> str: |
| cx, cy, r = 150, 150, 130 |
|
|
| |
| min_angle = math.radians((minute / 60) * 360 - 90) |
| hour_angle = math.radians(((hour % 12 + minute / 60) / 12) * 360 - 90) |
|
|
| |
| mx = cx + int(r * 0.85 * math.cos(min_angle)) |
| my = cy + int(r * 0.85 * math.sin(min_angle)) |
| hx = cx + int(r * 0.55 * math.cos(hour_angle)) |
| hy = cy + int(r * 0.55 * math.sin(hour_angle)) |
|
|
| |
| ticks = "" |
| for i in range(1, 13): |
| a = math.radians(i * 30 - 90) |
| x1 = cx + int((r - 10) * math.cos(a)) |
| y1 = cy + int((r - 10) * math.sin(a)) |
| x2 = cx + int(r * math.cos(a)) |
| y2 = cy + int(r * math.sin(a)) |
| ticks += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" stroke="#555" stroke-width="3" stroke-linecap="round"/>' |
|
|
| |
| labels = "" |
| for i in range(1, 13): |
| a = math.radians(i * 30 - 90) |
| lx = cx + int((r - 28) * math.cos(a)) |
| ly = cy + int((r - 28) * math.sin(a)) |
| labels += f'<text x="{lx}" y="{ly}" text-anchor="middle" dominant-baseline="central" font-size="16" font-family="Arial" font-weight="bold" fill="#333">{i}</text>' |
|
|
| svg = f""" |
| <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg"> |
| <!-- background --> |
| <defs> |
| <radialGradient id="bg" cx="50%" cy="40%" r="60%"> |
| <stop offset="0%" stop-color="#ffffff"/> |
| <stop offset="100%" stop-color="#e8f4f8"/> |
| </radialGradient> |
| <filter id="shadow"> |
| <feDropShadow dx="0" dy="4" stdDeviation="6" flood-opacity="0.15"/> |
| </filter> |
| </defs> |
| <circle cx="{cx}" cy="{cy}" r="{r+10}" fill="#4a9eff" opacity="0.15"/> |
| <circle cx="{cx}" cy="{cy}" r="{r}" fill="url(#bg)" stroke="#4a9eff" stroke-width="6" filter="url(#shadow)"/> |
| {ticks} |
| {labels} |
| <!-- minute hand --> |
| <line x1="{cx}" y1="{cy}" x2="{mx}" y2="{my}" |
| stroke="#2563eb" stroke-width="4" stroke-linecap="round"/> |
| <!-- hour hand --> |
| <line x1="{cx}" y1="{cy}" x2="{hx}" y2="{hy}" |
| stroke="#1e3a5f" stroke-width="6" stroke-linecap="round"/> |
| <!-- center dot --> |
| <circle cx="{cx}" cy="{cy}" r="7" fill="#e53e3e"/> |
| <circle cx="{cx}" cy="{cy}" r="3" fill="#fff"/> |
| </svg> |
| """ |
| return svg |
|
|
| |
| def new_question(): |
| hour = random.randint(1, 12) |
| minute = random.choice(NICE_MINUTES) |
| correct = get_swedish_time(hour, minute) |
|
|
| |
| wrong_pool = set() |
| while len(wrong_pool) < 3: |
| wh = random.randint(1, 12) |
| wm = random.choice(NICE_MINUTES) |
| w = get_swedish_time(wh, wm) |
| if w != correct: |
| wrong_pool.add(w) |
|
|
| options = list(wrong_pool) + [correct] |
| random.shuffle(options) |
| return hour, minute, correct, options |
|
|
| |
| def render_question(score, total, hour, minute, options, feedback=""): |
| clock_svg = draw_clock(hour, minute) |
| stars = "โญ" * score |
|
|
| feedback_html = "" |
| if feedback: |
| color = "#16a34a" if "โ
" in feedback else "#dc2626" |
| feedback_html = f'<div style="font-size:1.4rem;font-weight:bold;color:{color};margin-top:8px">{feedback}</div>' |
|
|
| html = f""" |
| <div style="font-family:'Segoe UI',Arial,sans-serif;max-width:520px;margin:auto; |
| background:linear-gradient(135deg,#e0f2fe,#fef9c3); |
| border-radius:24px;padding:28px;box-shadow:0 8px 32px rgba(0,0,0,0.12);"> |
| |
| <h1 style="text-align:center;font-size:1.9rem;color:#1e3a5f;margin:0 0 4px"> |
| ๐ Lรคr dig klockan! |
| </h1> |
| <p style="text-align:center;color:#475569;font-size:1rem;margin:0 0 16px"> |
| Hur mycket รคr klockan? |
| </p> |
| |
| <!-- Score bar --> |
| <div style="display:flex;justify-content:space-between;align-items:center; |
| background:white;border-radius:12px;padding:8px 16px;margin-bottom:18px"> |
| <span style="font-size:1rem;color:#64748b">Poรคng: <b style="color:#2563eb">{score}/{total}</b></span> |
| <span style="font-size:1.1rem">{stars if stars else "โ"}</span> |
| </div> |
| |
| <!-- Clock --> |
| <div style="display:flex;justify-content:center;margin-bottom:16px"> |
| {clock_svg} |
| </div> |
| |
| {feedback_html} |
| </div> |
| """ |
| return html |
|
|
| |
| def start_game(): |
| score, total = 0, 0 |
| hour, minute, correct, options = new_question() |
| html = render_question(score, total, hour, minute, options) |
| return ( |
| html, |
| gr.update(choices=options, value=None, visible=True), |
| gr.update(visible=True), |
| gr.update(visible=False), |
| score, total, hour, minute, correct, options |
| ) |
|
|
| def check_answer(choice, score, total, hour, minute, correct, options): |
| total += 1 |
| if choice == correct: |
| score += 1 |
| feedback = "โ
Rรคtt svar! Bra jobbat! ๐" |
| else: |
| feedback = f"โ Fel! Rรคtt svar var: {correct}" |
|
|
| html = render_question(score, total, hour, minute, options, feedback) |
| return ( |
| html, |
| gr.update(choices=options, value=None, visible=False), |
| gr.update(visible=False), |
| gr.update(visible=True), |
| score, total, hour, minute, correct, options |
| ) |
|
|
| def next_question(score, total): |
| hour, minute, correct, options = new_question() |
| html = render_question(score, total, hour, minute, options) |
| return ( |
| html, |
| gr.update(choices=options, value=None, visible=True), |
| gr.update(visible=True), |
| gr.update(visible=False), |
| score, total, hour, minute, correct, options |
| ) |
|
|
| |
| with gr.Blocks( |
| title="๐ Lรคr dig klockan!", |
| theme=gr.themes.Soft(primary_hue="blue", font=["Segoe UI", "Arial"]) |
| ) as demo: |
|
|
| |
| st_score = gr.State(0) |
| st_total = gr.State(0) |
| st_hour = gr.State(1) |
| st_minute = gr.State(0) |
| st_correct = gr.State("") |
| st_options = gr.State([]) |
|
|
| gr.HTML(""" |
| <div style="text-align:center;padding:12px 0 4px; |
| font-family:'Segoe UI',Arial,sans-serif"> |
| <h1 style="font-size:2.2rem;color:#1e3a5f;margin:0">๐ Lรคr dig klockan!</h1> |
| <p style="color:#475569;font-size:1.1rem;margin:4px 0 0"> |
| Ett roligt spel fรถr barn โ lรคr dig svenska klockor! |
| </p> |
| </div> |
| """) |
|
|
| display = gr.HTML(label="") |
| radio = gr.Radio(choices=[], label="Vรคlj rรคtt svar:", visible=False, interactive=True) |
| btn_check = gr.Button("โ
Kontrollera svaret", variant="primary", visible=False) |
| btn_next = gr.Button("โก๏ธ Nรคsta frรฅga", variant="secondary", visible=False) |
| btn_start = gr.Button("๐ฎ Starta spelet!", variant="primary", size="lg") |
|
|
| |
| btn_start.click( |
| start_game, [], |
| [display, radio, btn_check, btn_next, |
| st_score, st_total, st_hour, st_minute, st_correct, st_options] |
| ) |
|
|
| btn_check.click( |
| check_answer, |
| [radio, st_score, st_total, st_hour, st_minute, st_correct, st_options], |
| [display, radio, btn_check, btn_next, |
| st_score, st_total, st_hour, st_minute, st_correct, st_options] |
| ) |
|
|
| btn_next.click( |
| next_question, |
| [st_score, st_total], |
| [display, radio, btn_check, btn_next, |
| st_score, st_total, st_hour, st_minute, st_correct, st_options] |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |