import gradio as gr
import random
import math
# ─── Swedish vocabulary ───────────────────────────────────────────────────────
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"
}
# Only use "nice" times for children
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]}"
# ─── SVG Clock renderer ───────────────────────────────────────────────────────
def draw_clock(hour: int, minute: int) -> str:
cx, cy, r = 150, 150, 130
# Angles (12 o'clock = -90°)
min_angle = math.radians((minute / 60) * 360 - 90)
hour_angle = math.radians(((hour % 12 + minute / 60) / 12) * 360 - 90)
# Hand endpoints
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))
# Hour markers
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''
# Number labels (1-12)
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'{i}'
svg = f"""
"""
return svg
# ─── Game state helpers ───────────────────────────────────────────────────────
def new_question():
hour = random.randint(1, 12)
minute = random.choice(NICE_MINUTES)
correct = get_swedish_time(hour, minute)
# Generate 3 wrong answers
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
# ─── UI builders ─────────────────────────────────────────────────────────────
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'
{feedback}
'
html = f"""
🕐 Lär dig klockan!
Hur mycket är klockan?
Poäng: {score}/{total}
{stars if stars else "—"}
{clock_svg}
{feedback_html}
"""
return html
# ─── Gradio logic ─────────────────────────────────────────────────────────────
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
)
# ─── Gradio UI ────────────────────────────────────────────────────────────────
with gr.Blocks(
title="🕐 Lär dig klockan!",
theme=gr.themes.Soft(primary_hue="blue", font=["Segoe UI", "Arial"])
) as demo:
# hidden state
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("""
🕐 Lär dig klockan!
Ett roligt spel för barn – lär dig svenska klockor!
""")
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")
# Events
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()