From a1a4090fa16b5670e155628932fa4be3cc4ae847 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 17 Apr 2025 20:32:52 +0200 Subject: [PATCH] Update 2025-04-17_20:32:52 --- .gitignore | 21 ++++++++++ .gitkeep | 0 main.py | 45 ++++++++++++++++++++ requirements.txt | 3 ++ static/app.js | 104 ++++++++++++++++++++++++++++++++++++++++++++++ static/index.html | 24 +++++++++++ static/style.css | 46 ++++++++++++++++++++ 7 files changed, 243 insertions(+) create mode 100644 .gitignore create mode 100644 .gitkeep create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 static/app.js create mode 100644 static/index.html create mode 100644 static/style.css 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..2a77a4c --- /dev/null +++ b/main.py @@ -0,0 +1,45 @@ +from fastapi import FastAPI, Form +from fastapi.responses import FileResponse, JSONResponse +from fastapi.staticfiles import StaticFiles +import random + +app = FastAPI() + +app.mount("/static", StaticFiles(directory="static"), name="static") + +user_state = {} + +@app.get("/") +def index(): + return FileResponse("static/index.html") + +@app.get("/api/challenge") +def challenge(username: str): + a1, b1 = random.randint(1,10), random.randint(1,10) + a2, b2 = random.randint(1,10), random.randint(1,10) + result = a1 * b1 + a2 * b2 + user_state[username] = {"sum": result} + return {"task1": f"{a1} × {b1}", "task2": f"{a2} × {b2}"} + +@app.post("/api/submit") +def submit(username: str = Form(...), answer: str = Form(...)): + print("[DEBUG] submit() called") + print("[DEBUG] username:", username) + print("[DEBUG] answer:", answer) + try: + parsed = int(answer) + except ValueError: + return JSONResponse(content={"result": "error", "message": "Answer must be a number"}, status_code=400) + + user = user_state.get(username) + if not user or "sum" not in user: + return JSONResponse(content={"result": "error", "message": "No challenge found"}, status_code=400) + + expected = user["sum"] + correct = parsed == expected + + return JSONResponse(content={ + "result": "correct" if correct else "wrong", + "correct": expected + }) + 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..e9bf6cb --- /dev/null +++ b/static/app.js @@ -0,0 +1,104 @@ +const username = "guest"; +let currentScore = parseInt(localStorage.getItem("trisolve_score"), 10) || 0; + +window.onload = () => { + loadHighscore(); + getChallenge(); + updateScoreDisplay(); + focusAnswerField(); +} + +function focusAnswerField() { + document.getElementById("answer").focus(); +} + +function nextRound() { + setTimeout(() => { + getChallenge(); + focusAnswerField(); + }, 3000); +} + +function loadHighscore() { + const stored = localStorage.getItem("trisolve_highscore"); + if (stored === null) { + localStorage.setItem("trisolve_highscore", "0"); + } +} + +function getHighscore() { + return parseInt(localStorage.getItem("trisolve_highscore"), 10) || 0; +} + +function maybeUpdateHighscore() { + const high = getHighscore(); + if (currentScore > high) { + localStorage.setItem("trisolve_highscore", String(currentScore)); + } + localStorage.setItem("trisolve_score", String(currentScore)); +} + +function updateScoreDisplay() { + document.getElementById("score").textContent = + `📊 Score: ${currentScore} | 🏆 Highscore: ${getHighscore()}`; +} + +async function getChallenge() { + try { + const r = await fetch(`/api/challenge?username=${username}`); + const d = await r.json(); + document.getElementById("task").textContent = `🎯 ${d.task1} and ${d.task2}`; + document.getElementById("feedback").textContent = ""; + document.getElementById("answer").value = ""; + focusAnswerField(); + } catch (e) { + console.error("Failed to load challenge:", e); + } +} + +async function submitAnswer() { + const a = document.getElementById("answer").value; + if (!a.trim()) { + alert("Enter an answer"); + return; + } + const parsed = Number(a); + if (isNaN(parsed)) { + alert("Please enter a valid number"); + return; + } + const f = new FormData(); + f.append("username", username); + f.append("answer", parsed); + try { + const r = await fetch("/api/submit", { method: "POST", body: f }); + const text = await r.text(); + + let d; + try { + d = JSON.parse(text); + } catch (err) { + console.error("Invalid JSON:", text); + document.getElementById("feedback").textContent = "⚠️ Unexpected server response."; + return; + } + + if (d.result === "correct") { + currentScore += 1; + maybeUpdateHighscore(); + document.getElementById("feedback").textContent = "✅ Correct!"; + updateScoreDisplay(); + nextRound(); + } else if (d.result === "wrong") { + document.getElementById("feedback").textContent = `❌ Wrong. Was ${d.correct}`; + updateScoreDisplay(); + nextRound(); + } else { + document.getElementById("feedback").textContent = `⚠️ ${d.message}`; + } + } catch (e) { + console.error("Submission failed:", e); + document.getElementById("feedback").textContent = "⚠️ Submission error."; + } +} + diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..bef6512 --- /dev/null +++ b/static/index.html @@ -0,0 +1,24 @@ + + + + + TriSolve + + + +

🧠 TriSolve

+ +
+

+
+ + +
+

+

+
+ + + + + diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..8420b59 --- /dev/null +++ b/static/style.css @@ -0,0 +1,46 @@ +body { + padding-top: 1rem; + font-family: sans-serif; + max-width: 960px; + margin: 0 auto; + padding: 2rem; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 100vh; + background-color: #fff; +} + +h1 { + margin-bottom: 1.5rem; + text-align: center; +} + +#game { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + width: 100%; + max-width: 400px; +} + +input, button { + padding: 0.5rem; + font-size: 1rem; + width: 100%; + box-sizing: border-box; + margin-bottom: 0.5rem; +} + +#task, #feedback, #score { + font-size: 1.2rem; + text-align: center; +} + +#feedback { + margin-top: 0.5em; + font-weight: bold; +} +