Update 2025-04-17_20:31:51

This commit is contained in:
root
2025-04-17 20:31:52 +02:00
commit 69427b62cc
9 changed files with 302 additions and 0 deletions

21
.gitignore vendored Normal file
View File

@ -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/

0
.gitkeep Normal file
View File

57
main.py Normal file
View File

@ -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]
}

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
fastapi
uvicorn

110
static/app.js Normal file
View File

@ -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)
}

7
static/button.js Normal file
View File

@ -0,0 +1,7 @@
export function createEmojiButton() {
const btn = document.createElement('button')
btn.className = 'emoji-btn'
btn.textContent = '❓'
return btn
}

15
static/index.html Normal file
View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>EmojiRiddle</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<h1>🧠 EmojiRiddle</h1>
<div id="game-board"></div>
<script type="module" src="/static/app.js"></script>
</body>
</html>

8
static/routes.js Normal file
View File

@ -0,0 +1,8 @@
from fastapi import APIRouter
router = APIRouter(prefix="/api")
@router.get("/ping")
def ping():
return {"status": "ok"}

81
static/style.css Normal file
View File

@ -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;
}