Update 2025-04-13_16:41:14

This commit is contained in:
root
2025-04-13 16:41:15 +02:00
commit c6711f49e4
2229 changed files with 406880 additions and 0 deletions

163
static/app.js Normal file
View File

@ -0,0 +1,163 @@
let currentLevel = 1;
let score = 0;
let activeField = null;
const maxScorePerLevel = 10;
function saveProgress(level, score) {
localStorage.setItem("nummatch_level", level);
localStorage.setItem("nummatch_score", score);
updateStatus();
}
function loadProgress() {
const level = parseInt(localStorage.getItem("nummatch_level")) || 1;
const score = parseInt(localStorage.getItem("nummatch_score")) || 0;
return { level, score };
}
function updateStatus() {
document.getElementById("level-info").textContent = `Level: ${currentLevel} Punkte: ${score}`;
}
function generateTarget(min, max) {
const a = Math.floor(Math.random() * (max - min + 1)) + min;
const b = Math.floor(Math.random() * (max - min + 1)) + min;
return a * b;
}
async function getLevelData(levelNumber) {
try {
const response = await fetch(`/get_level/${levelNumber}`);
if (response.ok) {
return await response.json();
} else {
console.error(`Failed to fetch level data: ${response.status}`);
return { multiplier_range: [1, 10] };
}
} catch (error) {
console.error("Error fetching level data:", error);
return { multiplier_range: [1, 10] };
}
}
async function startGame(levelNumber) {
const level = await getLevelData(levelNumber);
const [min, max] = level.multiplier_range;
document.getElementById("target").textContent = generateTarget(min, max);
const leftInput = document.getElementById("left");
const rightInput = document.getElementById("right");
const feedback = document.getElementById("feedback");
leftInput.textContent = "";
rightInput.textContent = "";
leftInput.dataset.value = "";
rightInput.dataset.value = "";
leftInput.classList.remove("ok", "fail");
rightInput.classList.remove("ok", "fail");
feedback.textContent = "";
feedback.classList.remove("feedback-ok", "feedback-fail");
activeField = leftInput;
activeField.classList.add("active");
rightInput.classList.remove("active");
leftInput.focus();
updateStatus();
}
function checkAnswer() {
const leftInput = document.getElementById("left");
const rightInput = document.getElementById("right");
const [left, right] = getValues("left", "right");
const target = parseInt(document.getElementById("target").textContent);
const feedback = document.getElementById("feedback");
feedback.classList.remove("feedback-ok", "feedback-fail");
leftInput.classList.remove("ok", "fail");
rightInput.classList.remove("ok", "fail");
if (left * right === target) {
score++;
feedback.textContent = "✅ Richtig!";
feedback.classList.add("feedback-ok");
leftInput.classList.add("ok");
rightInput.classList.add("ok");
submitScore(score, currentLevel);
if (score >= maxScorePerLevel) {
currentLevel++;
score = 0;
}
saveProgress(currentLevel, score);
setTimeout(() => {
feedback.textContent = "";
feedback.classList.remove("feedback-ok");
startGame(currentLevel);
}, 2000);
} else {
feedback.textContent = "❌ Versuchs nochmal.";
feedback.classList.add("feedback-fail");
leftInput.classList.add("fail");
rightInput.classList.add("fail");
setTimeout(() => {
feedback.textContent = "";
feedback.classList.remove("feedback-fail");
saveProgress(currentLevel, score);
startGame(currentLevel);
}, 2000);
}
}
async function submitScore(score, level) {
try {
await fetch("/submit_score/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
player_name: "Anonymous",
score: score,
level_number: level,
date: new Date().toISOString().split("T")[0],
}),
});
} catch (error) {
console.error("Error submitting score:", error);
}
}
window.addEventListener("load", () => {
const checkBtn = document.getElementById("check-btn");
if (checkBtn) {
checkBtn.addEventListener("click", checkAnswer);
} else {
console.error("Check button not found!");
}
const leftInput = document.getElementById("left");
leftInput.classList.add("active");
activeField = leftInput;
leftInput.focus();
createNumBlock("numpad-container");
setupNumInputs("left", "right");
insertButton("game-container-buttons", "restart-btn", "🔁 Neustart", () => {
localStorage.removeItem("nummatch_level");
localStorage.removeItem("nummatch_score");
score = 0;
currentLevel = 1;
activeField = document.getElementById("left");
activeField.classList.add("active");
activeField.focus();
saveProgress(currentLevel, score);
startGame(currentLevel);
});
const progress = loadProgress();
currentLevel = progress.level;
score = progress.score;
startGame(currentLevel);
});

17
static/button.js Normal file
View File

@ -0,0 +1,17 @@
// button.js
// Modularer Button-Helper für dynamische UI-Erweiterung
function createButton(id, label, onClick) {
const btn = document.createElement("button");
btn.id = id;
btn.textContent = label;
btn.addEventListener("click", onClick);
return btn;
}
function insertButton(containerId, id, label, onClick) {
const container = document.getElementById(containerId);
if (!container) return;
const btn = createButton(id, label, onClick);
container.appendChild(btn);
}

28
static/index.html Normal file
View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<title>Nummatch</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div id="game-container">
<div id="level-info"></div>
<div class="task-row">
<div id="left" class="num-slot" contenteditable="true"></div>
<div class="operator">×</div>
<div id="right" class="num-slot" contenteditable="true"></div>
<div class="equals">=</div>
<div id="target" class="result-block"></div>
</div>
<div id="feedback"></div>
<div class="restart-container">
<div id="game-container-buttons"></div>
</div>
<button id="check-btn">Check</button>
<div id="numpad-container"></div>
</div>
<script src="/static/keypad.js"></script>
<script src="/static/button.js"></script>
<script src="/static/app.js"></script>
</body>
</html>

42
static/keypad.js Normal file
View File

@ -0,0 +1,42 @@
function createNumBlock(containerId) {
const container = document.getElementById(containerId);
if (!container) return;
const grid = document.createElement("div");
grid.className = "num-grid";
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0].forEach(n => {
const btn = document.createElement("button");
btn.textContent = n;
btn.className = "num-key";
btn.addEventListener("click", () => {
if (activeField) {
activeField.textContent = n;
activeField.dataset.value = n;
}
});
grid.appendChild(btn);
});
container.appendChild(grid);
}
function setupNumInputs(leftId, rightId) {
const left = document.getElementById(leftId);
const right = document.getElementById(rightId);
[left, right].forEach(el => {
el.addEventListener("click", () => {
activeField = el;
left.classList.remove("active");
right.classList.remove("active");
el.classList.add("active");
});
});
}
function getValues(leftId, rightId) {
const left = document.getElementById(leftId).dataset.value;
const right = document.getElementById(rightId).dataset.value;
return [parseInt(left), parseInt(right)];
}

23
static/message.js Normal file
View File

@ -0,0 +1,23 @@
// Einfache Feedback-Verwaltung
let feedbackTimer;
export function showFeedback(message, success = true) {
const feedback = document.getElementById("feedback");
feedback.textContent = message;
feedback.classList.remove("feedback-ok", "feedback-fail");
feedback.classList.add(success ? "feedback-ok" : "feedback-fail");
clearTimeout(feedbackTimer);
feedbackTimer = setTimeout(() => {
feedback.textContent = "";
feedback.classList.remove("feedback-ok", "feedback-fail");
}, 2000);
}
export function clearFeedback() {
const feedback = document.getElementById("feedback");
feedback.textContent = "";
feedback.classList.remove("feedback-ok", "feedback-fail");
}

145
static/style.css Normal file
View File

@ -0,0 +1,145 @@
#game-container {
max-width: 960px;
text-align: center;
}
.restart-container {
margin-bottom: 1rem;
}
.task-row {
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
}
.num-slot {
display: flex;
align-items: center;
justify-content: center;
width: 3rem;
height: 3rem;
font-size: 1.5rem;
text-align: center;
border: 2px solid #ccc;
border-radius: 6px;
cursor: pointer;
user-select: none;
}
.num-slot.active {
border-color: #88b;
background-color: #eef;
}
.num-slot.ok {
border-color: green;
background-color: #e0ffe0;
}
.num-slot.fail {
border-color: red;
background-color: #ffe0e0;
}
.num-grid {
display: grid;
grid-template-columns: repeat(3, 3rem);
gap: 0.5rem;
justify-content: center;
}
.num-key {
padding: 1rem;
font-size: 1.2rem;
cursor: pointer;
border: 1px solid #999;
border-radius: 4px;
background-color: #f9f9f9;
}
.num-key:hover {
background-color: #eef;
}
.operator,
.equals {
font-size: 1.5rem;
}
.result-block {
font-size: 2rem;
padding: 0.5rem 1rem;
border: 2px solid #ccc;
border-radius: 8px;
min-width: 3rem;
}
button {
padding: 0.5rem 1rem;
font-size: 1rem;
cursor: pointer;
}
button:hover {
background-color: #eef;
}
#feedback {
margin-top: 1rem;
font-size: 1.2rem;
font-weight: bold;
color: #444;
min-height: 1.5em;
transition: color 0.3s ease, transform 0.15s ease;
}
.feedback-ok {
color: green;
transform: scale(1.03);
}
.feedback-fail {
color: red;
transform: scale(1.03);
}
#level-info {
font-size: 1.5rem;
margin-top: 1rem;
margin-bottom: 1rem;
font-weight: bold;
color: #225;
}
canvas {
max-width: 960px;
margin: 1rem auto;
display: block;
}
table {
width: 80%;
margin: 0 auto;
border-collapse: collapse;
}
th,
td {
padding: 0.8rem;
text-align: left;
border: 1px solid #ccc;
}
th {
background-color: #f1f1f1;
}
#restart-btn {
margin-top: 1rem;
}
#check-btn {
margin-bottom: 1rem;
}