commit 69427b62cc238d058bf297135a6a945378cdbd75 Author: root Date: Thu Apr 17 20:31:52 2025 +0200 Update 2025-04-17_20:31:51 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b89c079 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Bytecode-Dateien +__pycache__/ +*.py[cod] + +# Virtuelle Umgebungen +.venv/ +venv/ + +# Betriebssystem-Dateien +.DS_Store +Thumbs.db + +# Logfiles und Dumps +*.log +*.bak +*.swp +*.tmp + +# IDEs und Editoren +.vscode/ +.idea/ diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/main.py new file mode 100644 index 0000000..2b56d98 --- /dev/null +++ b/main.py @@ -0,0 +1,57 @@ +# main.py +# FastAPI backend for EmojiRiddle – static frontend + riddle check API + +from fastapi import FastAPI, Request +from fastapi.staticfiles import StaticFiles +from pathlib import Path +from fastapi.responses import JSONResponse, HTMLResponse +from pydantic import BaseModel +from random import randint, choice + +app = FastAPI() + +# Serve frontend files from /static +app.mount("/static", StaticFiles(directory="static"), name="static") + +# Pydantic model for the riddle answer submission +class RiddleSubmission(BaseModel): + guess: dict[str, int] # e.g. {"🐶": 4, "🐱": 2} + +# Store the current solution in memory (will be overwritten every /api/riddle call) +current_solution = {} + +@app.get("/", response_class=HTMLResponse) +async def root_html(request: Request): + html = Path("static/index.html").read_text(encoding="utf-8") + return HTMLResponse(content=html, status_code=200) + +@app.post("/api/check") +async def check_riddle(submission: RiddleSubmission): + if submission.guess == current_solution: + return JSONResponse({"result": "✅ Correct!"}) + return JSONResponse({"result": "❌ Incorrect!"}) + +@app.get("/api/riddle") +async def get_riddle(): + global current_solution + emoji_pool = ["🐶", "🐱", "🐰", "🐵", "🦊", "🐸"] + e1, e2 = choice(emoji_pool), choice(emoji_pool) + while e2 == e1: + e2 = choice(emoji_pool) + + # assign random values + v1 = randint(1, 9) + v2 = randint(1, 9) + + current_solution = {e1: v1, e2: v2} + + equations = [ + {"left": [e1, e1], "op": "+", "right": v1 + v1}, + {"left": [e1, e2], "op": "+", "right": v1 + v2} + ] + + return { + "equations": equations, + "emojis": [e1, e2] + } + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0af110b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +fastapi +uvicorn + diff --git a/static/app.js b/static/app.js new file mode 100644 index 0000000..5036135 --- /dev/null +++ b/static/app.js @@ -0,0 +1,110 @@ +import { createEmojiButton } from './button.js' + +const gameBoard = document.getElementById("game-board") + +fetch("/api/riddle") + .then(res => res.json()) + .then(data => { + renderEquations(data.equations) + renderInputForm(data.emojis); + setTimeout(() => document.querySelector('input')?.focus(), 10) + }) + +function renderEquations(equations) { + const container = document.createElement("div") + container.id = "equation-block" + equations.forEach(eq => { + const row = document.createElement("div") + row.innerText = `${eq.left[0]} + ${eq.left[1]} = ${eq.right}` + row.style.fontSize = "1.5rem" + row.style.margin = "0.3rem" + container.appendChild(row) + }) + gameBoard.appendChild(container) +} + +function renderInputForm(emojis) { + const wrapper = document.createElement("div") + wrapper.id = "guess-wrapper" + + const form = document.createElement("form") + form.id = "guess-form" + + emojis.forEach(emoji => { + const label = document.createElement("label") + label.classList.add("guess-label") + + const emojiSpan = document.createElement("span") + emojiSpan.textContent = emoji + emojiSpan.style.marginRight = "0.5rem" + + const input = document.createElement("input") + input.type = "number" + input.name = emoji + input.required = true + input.classList.add("input-field") + + label.appendChild(emojiSpan) + label.appendChild(input) + form.appendChild(label) + }) + + const btn = document.createElement("button") + btn.type = "submit" + btn.innerText = "Check ✅" + btn.classList.add("btn-check") + form.appendChild(btn) + + const feedback = document.createElement("div") + feedback.id = "feedback" + wrapper.appendChild(feedback) + + form.querySelector("input")?.focus() + + form.onsubmit = async (e) => { + e.preventDefault() + try { + const formData = new FormData(form) + const guess = {} + for (let [key, value] of formData.entries()) { + guess[key] = parseInt(value) + } + + const res = await fetch("/api/check", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ guess }) + }) + + if (!res.ok) throw new Error("Server returned " + res.status) + + const result = await res.json() + feedback.textContent = result.result + feedback.style.color = result.result.includes("✅") ? "#1e7c1e" : "#cc3333" + feedback.style.opacity = 0 + + if (result.result.includes("❌")) { + feedback.classList.remove("shake") + void feedback.offsetWidth + feedback.classList.add("shake") + } + + setTimeout(() => { + feedback.style.opacity = 1 + form.querySelector("input")?.focus() + if (feedback.textContent.includes("✅")) { + feedback.textContent += " 🎉 Neue Runde startet in 3 Sekunden..." + setTimeout(() => location.reload(), 3000) + } + }, 50) + } catch (err) { + feedback.textContent = "⚠️ Error: " + err.message + feedback.style.color = "#cc3333" + feedback.style.opacity = 1 + } + } + + wrapper.appendChild(form) + gameBoard.appendChild(wrapper) +} + diff --git a/static/button.js b/static/button.js new file mode 100644 index 0000000..d0cde1f --- /dev/null +++ b/static/button.js @@ -0,0 +1,7 @@ +export function createEmojiButton() { + const btn = document.createElement('button') + btn.className = 'emoji-btn' + btn.textContent = '❓' + return btn +} + diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..99f6c79 --- /dev/null +++ b/static/index.html @@ -0,0 +1,15 @@ + + + + + EmojiRiddle + + + +

🧠 EmojiRiddle

+
+ + + + + diff --git a/static/routes.js b/static/routes.js new file mode 100644 index 0000000..0b728d9 --- /dev/null +++ b/static/routes.js @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +router = APIRouter(prefix="/api") + +@router.get("/ping") +def ping(): + return {"status": "ok"} + diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..59db437 --- /dev/null +++ b/static/style.css @@ -0,0 +1,81 @@ +body { + max-width: 960px; + margin: auto; + font-family: sans-serif; + text-align: center; +} + +#game-board { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 1rem; +} + +#guess-form { + margin-top: 1rem; + display: flex; + flex-direction: column; + align-items: center; +} + +.input-field { + font-size: 1.5rem; + line-height: 1.5rem; + width: 100px; + text-align: center; + border: 1px solid #ccc; + border-radius: 4px; + background-color: #f9f9f9; + padding: 0.3rem 0.5rem; + box-sizing: border-box; + appearance: textfield; +} + +.guess-label { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 0.5rem; + font-size: 1.5rem; +} + +.btn-check { + margin-top: 1rem; + font-size: 1.5rem; +} + +#feedback { + margin-top: 1rem; + font-size: 1.2rem; + font-weight: bold; + transition: opacity 0.4s ease-in-out; + opacity: 0; +} + +#feedback.shake { + animation: shake 0.3s; +} + +@keyframes shake { + 0% { transform: translateX(0); } + 25% { transform: translateX(-4px); } + 50% { transform: translateX(4px); } + 75% { transform: translateX(-4px); } + 100% { transform: translateX(0); } +} + +button.emoji-btn { + font-size: 2rem; + width: 80px; + height: 80px; + margin: 0.5rem; + border: 2px solid #ccc; + background: white; + cursor: pointer; +} + +button.emoji-btn:hover { + background: #eee; +} +