Update 2025-04-17_20:31:51
This commit is contained in:
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal 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/
|
57
main.py
Normal file
57
main.py
Normal 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
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
|
110
static/app.js
Normal file
110
static/app.js
Normal 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
7
static/button.js
Normal 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
15
static/index.html
Normal 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
8
static/routes.js
Normal 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
81
static/style.css
Normal 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;
|
||||||
|
}
|
||||||
|
|
Reference in New Issue
Block a user