Update 2025-04-13_16:41:14
This commit is contained in:
163
static/app.js
Normal file
163
static/app.js
Normal 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 = "❌ Versuch’s 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
17
static/button.js
Normal 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
28
static/index.html
Normal 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
42
static/keypad.js
Normal 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
23
static/message.js
Normal 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
145
static/style.css
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user