klocka / app.py
SsebaA's picture
Create app.py
a4d77b9 verified
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'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" stroke="#555" stroke-width="3" stroke-linecap="round"/>'
# 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'<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
# โ”€โ”€โ”€ 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'<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
# โ”€โ”€โ”€ 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("""
<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")
# 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()