Initial commit - Kinderspiele
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
FROM nginx:alpine
|
||||
|
||||
# Kopiere alle Spiele in den Webserver
|
||||
COPY public/ /usr/share/nginx/html/
|
||||
|
||||
# Erstelle Health-Check
|
||||
RUN echo '{"status":"ok","service":"kinderspiele"}' > /usr/share/nginx/html/health.json
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -0,0 +1 @@
|
||||
{"name": "kinderspiele", "version": "1.0.0", "dependencies": {"express": "^4.18.2", "socket.io": "^4.6.1", "redis": "^4.6.5", "uuid": "^9.0.0"}}
|
||||
@@ -0,0 +1,268 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>KinderWelt - Lernen & Spielen</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; -webkit-tap-highlight-color: transparent; }
|
||||
body {
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh; overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Lock Screen */
|
||||
#lockScreen {
|
||||
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
|
||||
display: flex; flex-direction: column; justify-content: center; align-items: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
#lockScreen h1 { color: white; font-size: 48px; margin-bottom: 20px; }
|
||||
#lockScreen p { color: white; font-size: 20px; margin-bottom: 30px; }
|
||||
|
||||
.pin-display { text-align: center; margin-bottom: 30px; }
|
||||
.pin-dots { display: flex; gap: 15px; justify-content: center; margin-bottom: 15px; }
|
||||
.pin-dot { width: 20px; height: 20px; border: 3px solid white; border-radius: 50%; transition: all 0.3s; }
|
||||
.pin-dot.filled { background: white; }
|
||||
.pin-text { color: white; font-size: 20px; min-height: 30px; }
|
||||
.pin-text.pin-error { color: #ff3333; font-weight: bold; }
|
||||
|
||||
.pin-pad { display: grid; grid-template-columns: repeat(3, 80px); gap: 15px; }
|
||||
.pin-btn {
|
||||
background: rgba(255,255,255,0.9); border: none; border-radius: 50%;
|
||||
width: 80px; height: 80px; font-size: 28px; font-weight: bold;
|
||||
cursor: pointer; transition: all 0.1s; touch-action: manipulation;
|
||||
}
|
||||
.pin-btn:active { transform: scale(0.95); background: white; }
|
||||
.pin-btn.unlock { background: #4ade80; color: white; }
|
||||
.pin-btn.unlock:active { background: #22c55e; }
|
||||
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-10px); }
|
||||
75% { transform: translateX(10px); }
|
||||
}
|
||||
|
||||
/* Game Screen */
|
||||
#gameScreen { display: none; padding: 20px; }
|
||||
#gameScreen.active { display: block; }
|
||||
|
||||
.menu {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 20px; max-width: 900px; margin: 0 auto;
|
||||
}
|
||||
.game-card {
|
||||
background: white; border-radius: 20px; padding: 25px 15px;
|
||||
display: flex; flex-direction: column; justify-content: center; align-items: center;
|
||||
cursor: pointer; transition: transform 0.2s; text-decoration: none; color: inherit;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
.game-card:hover { transform: translateY(-5px); }
|
||||
.game-card:active { transform: scale(0.95); }
|
||||
.game-card span { font-size: 50px; margin-bottom: 10px; }
|
||||
.game-card h3 { font-size: 18px; color: #333; text-align: center; }
|
||||
|
||||
.menu-title { text-align: center; color: white; margin-bottom: 30px; }
|
||||
.menu-title h2 { font-size: 32px; margin-bottom: 10px; }
|
||||
.menu-title p { font-size: 16px; opacity: 0.9; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.pin-pad { grid-template-columns: repeat(3, 70px); gap: 12px; }
|
||||
.pin-btn { width: 70px; height: 70px; font-size: 24px; }
|
||||
#lockScreen h1 { font-size: 36px; }
|
||||
.game-card span { font-size: 40px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Lock Screen -->
|
||||
<div id="lockScreen">
|
||||
<h1>🔒 KinderWelt</h1>
|
||||
<p>Eltern-PIN eingeben</p>
|
||||
<div class="pin-display">
|
||||
<div class="pin-dots">
|
||||
<div class="pin-dot" id="dot0"></div>
|
||||
<div class="pin-dot" id="dot1"></div>
|
||||
<div class="pin-dot" id="dot2"></div>
|
||||
<div class="pin-dot" id="dot3"></div>
|
||||
</div>
|
||||
<p class="pin-text" id="pinText">Bitte PIN eingeben</p>
|
||||
</div>
|
||||
<div class="pin-pad">
|
||||
<button class="pin-btn" onclick="enterPin(1)">1</button>
|
||||
<button class="pin-btn" onclick="enterPin(2)">2</button>
|
||||
<button class="pin-btn" onclick="enterPin(3)">3</button>
|
||||
<button class="pin-btn" onclick="enterPin(4)">4</button>
|
||||
<button class="pin-btn" onclick="enterPin(5)">5</button>
|
||||
<button class="pin-btn" onclick="enterPin(6)">6</button>
|
||||
<button class="pin-btn" onclick="enterPin(7)">7</button>
|
||||
<button class="pin-btn" onclick="enterPin(8)">8</button>
|
||||
<button class="pin-btn" onclick="enterPin(9)">9</button>
|
||||
<button class="pin-btn" onclick="clearPin()">⌫</button>
|
||||
<button class="pin-btn" onclick="enterPin(0)">0</button>
|
||||
<button class="pin-btn unlock" onclick="checkPin()">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Menu -->
|
||||
<div id="gameScreen">
|
||||
<div class="menu-title">
|
||||
<h2>🎮 Spiele-Menü</h2>
|
||||
<p>Wähle ein Spiel!</p>
|
||||
</div>
|
||||
<div class="menu">
|
||||
<a href="spiele/tennis.html" class="game-card">
|
||||
<span>🎾</span>
|
||||
<h3>Tennis</h3>
|
||||
</a>
|
||||
<a href="spiele/tetris.html" class="game-card">
|
||||
<span>🧱</span>
|
||||
<h3>Tetris</h3>
|
||||
</a>
|
||||
<a href="spiele/memory.html" class="game-card">
|
||||
<span>🃏</span>
|
||||
<h3>Memory</h3>
|
||||
</a>
|
||||
<a href="spiele/simonsays.html" class="game-card">
|
||||
<span>🎵</span>
|
||||
<h3>Simon Says</h3>
|
||||
</a>
|
||||
<a href="spiele/buchstaben.html" class="game-card">
|
||||
<span>🔤</span>
|
||||
<h3>Buchstaben</h3>
|
||||
</a>
|
||||
<a href="spiele/zahlen.html" class="game-card">
|
||||
<span>🔢</span>
|
||||
<h3>Zahlen</h3>
|
||||
</a>
|
||||
<a href="spiele/snake.html" class="game-card">
|
||||
<span>🐍</span>
|
||||
<h3>Snake</h3>
|
||||
</a>
|
||||
<a href="spiele/zahlenraten.html" class="game-card">
|
||||
<span>🤔</span>
|
||||
<h3>Zahlen raten</h3>
|
||||
</a>
|
||||
<a href="spiele/malen.html" class="game-card">
|
||||
<span>🎨</span>
|
||||
<h3>Malen</h3>
|
||||
</a>
|
||||
<a href="spiele/simonsays.html" class="game-card">
|
||||
<span>🎵</span>
|
||||
<h3>Simon Says</h3>
|
||||
</a>
|
||||
<a href="spiele/tierlaute.html" class="game-card">
|
||||
<span>🦁</span>
|
||||
<h3>Tierlaute</h3>
|
||||
</a>
|
||||
<a href="spiele/puzzle.html" class="game-card">
|
||||
<span>🧩</span>
|
||||
<h3>Puzzle</h3>
|
||||
</a>
|
||||
<a href="spiele/reaktion.html" class="game-card">
|
||||
<span>⚡</span>
|
||||
<h3>Reaktion</h3>
|
||||
</a>
|
||||
<a href="spiele/zielscheibe.html" class="game-card">
|
||||
<span>🎯</span>
|
||||
<h3>Zielscheibe</h3>
|
||||
</a>
|
||||
<a href="spiele/wuerfel.html" class="game-card">
|
||||
<span>🎲</span>
|
||||
<h3>Würfel</h3>
|
||||
</a>
|
||||
<a href="spiele/klavier.html" class="game-card">
|
||||
<span>🎹</span>
|
||||
<h3>Klavier</h3>
|
||||
</a>
|
||||
<a href="spiele/autorennen.html" class="game-card">
|
||||
<span>🏎️</span>
|
||||
<h3>Autorennen</h3>
|
||||
</a>
|
||||
<a href="spiele/maedn.html" class="game-card">
|
||||
<span>🎲</span>
|
||||
<h3>Mensch ärgere</h3>
|
||||
</a>
|
||||
<a href="spiele/leiterspiel.html" class="game-card">
|
||||
<span>🐍</span>
|
||||
<h3>Leiterspiel</h3>
|
||||
</a>
|
||||
<a href="spiele/schach.html" class="game-card">
|
||||
<span>♟️</span>
|
||||
<h3>Schach</h3>
|
||||
</a>
|
||||
<a href="spiele/halligalli.html" class="game-card">
|
||||
<span>🔔</span>
|
||||
<h3>Halli Galli</h3>
|
||||
</a>
|
||||
<a href="spiele/minesweeper.html" class="game-card">
|
||||
<span>💣</span>
|
||||
<h3>Minesweeper</h3>
|
||||
</a>
|
||||
<a href="spiele/uno.html" class="game-card">
|
||||
<span>🎴</span>
|
||||
<h3>Uno</h3>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Session Check
|
||||
if (sessionStorage.getItem('kinderWelt unlocked') === 'true') {
|
||||
document.getElementById('lockScreen').style.display = 'none';
|
||||
document.getElementById('gameScreen').classList.add('active');
|
||||
}
|
||||
|
||||
// PIN
|
||||
const CORRECT_PIN = '2603';
|
||||
let currentPin = '';
|
||||
|
||||
function enterPin(num) {
|
||||
if (currentPin.length < 4) {
|
||||
currentPin += num;
|
||||
updatePinDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
function clearPin() {
|
||||
currentPin = currentPin.slice(0, -1);
|
||||
updatePinDisplay();
|
||||
}
|
||||
|
||||
function updatePinDisplay() {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
document.getElementById('dot' + i).classList.toggle('filled', i < currentPin.length);
|
||||
}
|
||||
document.getElementById('pinText').textContent = currentPin.length === 0 ? 'Bitte PIN eingeben' : '';
|
||||
document.getElementById('pinText').classList.remove('pin-error');
|
||||
}
|
||||
|
||||
function checkPin() {
|
||||
if (currentPin === CORRECT_PIN) {
|
||||
sessionStorage.setItem('kinderWelt unlocked', 'true');
|
||||
document.getElementById('lockScreen').style.display = 'none';
|
||||
document.getElementById('gameScreen').classList.add('active');
|
||||
} else {
|
||||
document.getElementById('pinText').textContent = 'Falscher PIN!';
|
||||
document.getElementById('pinText').classList.add('pin-error');
|
||||
document.querySelector('.pin-display').style.animation = 'shake 0.5s';
|
||||
setTimeout(() => {
|
||||
currentPin = '';
|
||||
updatePinDisplay();
|
||||
document.querySelector('.pin-display').style.animation = '';
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Keyboard support
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key >= '0' && e.key <= '9') enterPin(parseInt(e.key));
|
||||
if (e.key === 'Backspace') clearPin();
|
||||
if (e.key === 'Enter') checkPin();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,204 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>KinderWelt - Lernen & Spielen</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh; overflow-x: hidden;
|
||||
}
|
||||
|
||||
#lockScreen {
|
||||
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
|
||||
display: flex; flex-direction: column; justify-content: center; align-items: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
#lockScreen h1 { color: white; font-size: 48px; margin-bottom: 20px; }
|
||||
#lockScreen p { color: white; font-size: 20px; margin-bottom: 30px; }
|
||||
|
||||
.pin-display { text-align: center; margin-bottom: 30px; }
|
||||
.pin-dots { display: flex; gap: 15px; justify-content: center; margin-bottom: 15px; }
|
||||
.pin-dot { width: 20px; height: 20px; border: 3px solid white; border-radius: 50%; transition: all 0.3s; }
|
||||
.pin-dot.filled { background: white; }
|
||||
.pin-text { color: white; font-size: 20px; min-height: 30px; }
|
||||
.pin-text.pin-error { color: #ff3333; font-weight: bold; }
|
||||
|
||||
.pin-pad { display: grid; grid-template-columns: repeat(3, 80px); gap: 15px; }
|
||||
.pin-btn {
|
||||
background: rgba(255,255,255,0.9); border: none; border-radius: 50%;
|
||||
width: 80px; height: 80px; font-size: 28px; font-weight: bold;
|
||||
cursor: pointer; transition: all 0.1s; touch-action: manipulation;
|
||||
}
|
||||
.pin-btn:active { transform: scale(0.95); background: white; }
|
||||
.pin-btn.unlock { background: #4ade80; color: white; }
|
||||
.pin-btn.unlock:active { background: #22c55e; }
|
||||
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-10px); }
|
||||
75% { transform: translateX(10px); }
|
||||
}
|
||||
|
||||
#gameScreen { display: none; padding: 20px; }
|
||||
#gameScreen.active { display: block; }
|
||||
|
||||
.menu { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 20px; max-width: 800px; margin: 0 auto; }
|
||||
.game-card {
|
||||
background: white; border-radius: 20px; padding: 30px;
|
||||
display: flex; flex-direction: column; justify-content: center; align-items: center;
|
||||
cursor: pointer; transition: transform 0.2s; text-decoration: none; color: inherit;
|
||||
}
|
||||
.game-card:active { transform: scale(0.95); }
|
||||
.game-card span { font-size: 50px; margin-bottom: 10px; }
|
||||
.game-card h3 { font-size: 20px; color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Lock Screen -->
|
||||
<div id="lockScreen">
|
||||
<h1>🔒 KinderWelt</h1>
|
||||
<p>Eltern-PIN eingeben</p>
|
||||
<div class="pin-display">
|
||||
<div class="pin-dots">
|
||||
<div class="pin-dot" id="dot0"></div>
|
||||
<div class="pin-dot" id="dot1"></div>
|
||||
<div class="pin-dot" id="dot2"></div>
|
||||
<div class="pin-dot" id="dot3"></div>
|
||||
</div>
|
||||
<p class="pin-text" id="pinText">Bitte PIN eingeben</p>
|
||||
</div>
|
||||
<div class="pin-pad">
|
||||
<button class="pin-btn" onclick="enterPin(1)">1</button>
|
||||
<button class="pin-btn" onclick="enterPin(2)">2</button>
|
||||
<button class="pin-btn" onclick="enterPin(3)">3</button>
|
||||
<button class="pin-btn" onclick="enterPin(4)">4</button>
|
||||
<button class="pin-btn" onclick="enterPin(5)">5</button>
|
||||
<button class="pin-btn" onclick="enterPin(6)">6</button>
|
||||
<button class="pin-btn" onclick="enterPin(7)">7</button>
|
||||
<button class="pin-btn" onclick="enterPin(8)">8</button>
|
||||
<button class="pin-btn" onclick="enterPin(9)">9</button>
|
||||
<button class="pin-btn" onclick="clearPin()">⌫</button>
|
||||
<button class="pin-btn" onclick="enterPin(0)">0</button>
|
||||
<button class="pin-btn unlock" onclick="checkPin()">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Menu -->
|
||||
<div id="gameScreen">
|
||||
<div class="menu">
|
||||
<a href="spiele/tennis.html" class="game-card">
|
||||
<span>🎾</span>
|
||||
<h3>Tennis</h3>
|
||||
</a>
|
||||
<a href="spiele/tetris.html" class="game-card">
|
||||
<span>🧱</span>
|
||||
<h3>Tetris</h3>
|
||||
</a>
|
||||
<a href="spiele/memory.html" class="game-card">
|
||||
<span>🧠</span>
|
||||
<h3>Memory</h3>
|
||||
</a>
|
||||
<a href="spiele/buchstaben.html" class="game-card">
|
||||
<span>🔤</span>
|
||||
<h3>Buchstaben</h3>
|
||||
</a>
|
||||
<a href="spiele/zahlen.html" class="game-card">
|
||||
<span>🔢</span>
|
||||
<h3>Zahlen</h3>
|
||||
</a>
|
||||
<a href="spiele/snake.html" class="game-card">
|
||||
<span>🐍</span>
|
||||
<h3>Snake</h3>
|
||||
</a>
|
||||
<a href="spiele/raten.html" class="game-card">
|
||||
<span>❓</span>
|
||||
<h3>Zahlen raten</h3>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// PIN System
|
||||
let pin = '';
|
||||
const CORRECT_PIN = '2603';
|
||||
|
||||
function enterPin(digit) {
|
||||
if (pin.length < 4) {
|
||||
pin += digit;
|
||||
updatePinDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
function clearPin() {
|
||||
pin = pin.slice(0, -1);
|
||||
updatePinDisplay();
|
||||
}
|
||||
|
||||
function updatePinDisplay() {
|
||||
// Update dots
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const dot = document.getElementById('dot' + i);
|
||||
if (i < pin.length) {
|
||||
dot.classList.add('filled');
|
||||
} else {
|
||||
dot.classList.remove('filled');
|
||||
}
|
||||
}
|
||||
|
||||
// Update text
|
||||
const text = document.getElementById('pinText');
|
||||
if (pin.length === 0) {
|
||||
text.textContent = 'Bitte PIN eingeben';
|
||||
text.className = 'pin-text';
|
||||
} else if (pin.length < 4) {
|
||||
text.textContent = 'PIN eingeben...';
|
||||
text.className = 'pin-text';
|
||||
} else {
|
||||
text.textContent = '✓ PIN vollständig';
|
||||
text.className = 'pin-text';
|
||||
}
|
||||
}
|
||||
|
||||
function checkPin() {
|
||||
if (pin === CORRECT_PIN) {
|
||||
// Unlock!
|
||||
document.getElementById('lockScreen').style.display = 'none';
|
||||
document.getElementById('gameScreen').classList.add('active');
|
||||
} else {
|
||||
// Wrong PIN
|
||||
const text = document.getElementById('pinText');
|
||||
text.textContent = '✗ Falscher PIN!';
|
||||
text.className = 'pin-text pin-error';
|
||||
|
||||
// Shake animation
|
||||
const display = document.querySelector('.pin-display');
|
||||
display.style.animation = 'shake 0.5s';
|
||||
|
||||
setTimeout(() => {
|
||||
pin = '';
|
||||
updatePinDisplay();
|
||||
display.style.animation = '';
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Keyboard support
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (document.getElementById('lockScreen').style.display === 'none') return;
|
||||
|
||||
if (e.key >= '0' && e.key <= '9') {
|
||||
enterPin(e.key);
|
||||
} else if (e.key === 'Backspace') {
|
||||
clearPin();
|
||||
} else if (e.key === 'Enter') {
|
||||
checkPin();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,273 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Autorennen - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #1a472a 0%, #0f2918 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 15px;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 10px; font-size: 24px; }
|
||||
.track-container {
|
||||
background: #2d3748; border-radius: 15px; padding: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.5); margin: 15px 0;
|
||||
}
|
||||
.track {
|
||||
width: 320px; height: 60px; background: linear-gradient(90deg, #4a5568 0%, #718096 50%, #4a5568 100%);
|
||||
border-radius: 10px; margin: 10px 0; position: relative;
|
||||
border: 3px solid #e2e8f0; overflow: hidden;
|
||||
}
|
||||
.track::before {
|
||||
content: ''; position: absolute; top: 50%; left: 0; right: 0;
|
||||
height: 4px; background: repeating-linear-gradient(90deg, #fff 0px, #fff 20px, transparent 20px, transparent 40px);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.finish-line {
|
||||
position: absolute; right: 10px; top: 0; bottom: 0; width: 8px;
|
||||
background: repeating-linear-gradient(180deg, #fff 0px, #fff 10px, #000 10px, #000 20px);
|
||||
}
|
||||
.car {
|
||||
position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
|
||||
font-size: 40px; transition: left 0.5s ease-out;
|
||||
filter: drop-shadow(2px 2px 4px rgba(0,0,0,0.5));
|
||||
}
|
||||
.car.moving { animation: bounce 0.3s ease-in-out; }
|
||||
@keyframes bounce {
|
||||
0%, 100% { transform: translateY(-50%) scale(1); }
|
||||
50% { transform: translateY(-60%) scale(1.1); }
|
||||
}
|
||||
|
||||
.dice-container {
|
||||
perspective: 1000px; margin: 20px 0;
|
||||
}
|
||||
.dice {
|
||||
width: 80px; height: 80px; position: relative;
|
||||
transform-style: preserve-3d; cursor: pointer;
|
||||
}
|
||||
.dice.rolling { animation: rollDice 0.8s ease-out; }
|
||||
@keyframes rollDice {
|
||||
0% { transform: rotateX(0) rotateY(0) rotateZ(0); }
|
||||
100% { transform: rotateX(720deg) rotateY(720deg) rotateZ(360deg); }
|
||||
}
|
||||
.face {
|
||||
position: absolute; width: 80px; height: 80px;
|
||||
background: white; border: 2px solid #333; border-radius: 12px;
|
||||
display: flex; justify-content: center; align-items: center;
|
||||
font-size: 40px; font-weight: bold;
|
||||
}
|
||||
.face-1 { transform: rotateY(0deg) translateZ(40px); }
|
||||
.face-2 { transform: rotateY(90deg) translateZ(40px); }
|
||||
.face-3 { transform: rotateY(180deg) translateZ(40px); }
|
||||
.face-4 { transform: rotateY(-90deg) translateZ(40px); }
|
||||
.face-5 { transform: rotateX(90deg) translateZ(40px); }
|
||||
.face-6 { transform: rotateX(-90deg) translateZ(40px); }
|
||||
|
||||
.dot-grid { display: grid; gap: 5px; }
|
||||
.dot-grid.col-2 { grid-template-columns: 1fr 1fr; }
|
||||
.dot { width: 12px; height: 12px; background: #333; border-radius: 50%; }
|
||||
|
||||
.status { color: white; font-size: 20px; margin: 15px 0; text-align: center; }
|
||||
.turn-indicator {
|
||||
background: rgba(255,255,255,0.2); padding: 10px 20px; border-radius: 20px;
|
||||
color: white; margin: 10px 0; font-size: 18px;
|
||||
}
|
||||
.turn-indicator .player { font-weight: bold; }
|
||||
.turn-indicator .player.red { color: #ff6b6b; }
|
||||
.turn-indicator .player.blue { color: #4dabf7; }
|
||||
|
||||
.controls { display: flex; gap: 15px; margin-top: 20px; flex-wrap: wrap; justify-content: center; }
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 12px 25px;
|
||||
font-size: 16px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-roll { background: #4ade80; color: white; font-size: 18px; padding: 15px 40px; }
|
||||
.btn-roll:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; }
|
||||
|
||||
.winner {
|
||||
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
||||
background: white; padding: 30px 50px; border-radius: 20px;
|
||||
text-align: center; display: none; z-index: 100;
|
||||
box-shadow: 0 10px 50px rgba(0,0,0,0.5);
|
||||
}
|
||||
.winner h2 { color: #333; margin-bottom: 15px; }
|
||||
.winner-emoji { font-size: 60px; }
|
||||
|
||||
.instructions { color: #aaa; text-align: center; margin: 10px 0; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🏎️ Autorennen</h1>
|
||||
|
||||
<p class="instructions">Würfle und bewege dein Auto! Wer zuerst im Ziel ist, gewinnt!</p>
|
||||
|
||||
<div class="turn-indicator" id="turnIndicator">
|
||||
<span class="player red">🔴 Rot</span> ist dran!
|
||||
</div>
|
||||
|
||||
<div class="track-container">
|
||||
<div class="track">
|
||||
<div class="finish-line"></div>
|
||||
<div class="car" id="car1">🔴</div>
|
||||
</div>
|
||||
<div class="track">
|
||||
<div class="finish-line"></div>
|
||||
<div class="car" id="car2">🔵</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status" id="status">Klicke auf den Würfel!</div>
|
||||
|
||||
<div class="dice-container">
|
||||
<div class="dice" id="dice" onclick="roll()">
|
||||
<div class="face face-1"><div class="dot-grid"><div class="dot"></div></div></div>
|
||||
<div class="face face-2"><div class="dot-grid col-2"><div class="dot"></div><div class="dot"></div></div></div>
|
||||
<div class="face face-3"><div class="dot-grid col-2"><div class="dot"></div><div class="dot"></div><div class="dot"></div></div></div>
|
||||
<div class="face face-4"><div class="dot-grid col-2"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div></div></div>
|
||||
<div class="face face-5"><div class="dot-grid col-2"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div></div></div>
|
||||
<div class="face face-6"><div class="dot-grid col-2"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn btn-roll" id="rollBtn" onclick="roll()">🎲 Würfeln</button>
|
||||
<button class="btn" onclick="resetGame()">🔄 Neustart</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<div class="winner" id="winner">
|
||||
<h2>🎉 Gewinner! 🎉</h2>
|
||||
<div class="winner-emoji" id="winnerEmoji"></div>
|
||||
<button class="btn btn-roll" onclick="resetGame()" style="margin-top: 20px">🔄 Nochmal</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const TRACK_LENGTH = 280; // Pixel bis zum Ziel
|
||||
const STEPS_TO_FINISH = 20; // 20 Felder bis zum Ziel
|
||||
|
||||
let positions = [0, 0]; // Position der Autos (in Schritten)
|
||||
let currentPlayer = 0; // 0 = Rot, 1 = Blau
|
||||
let rolling = false;
|
||||
let gameEnded = false;
|
||||
|
||||
const cars = [document.getElementById('car1'), document.getElementById('car2')];
|
||||
const dice = document.getElementById('dice');
|
||||
const status = document.getElementById('status');
|
||||
const turnIndicator = document.getElementById('turnIndicator');
|
||||
|
||||
const rotations = {
|
||||
1: 'rotateX(0deg) rotateY(0deg)',
|
||||
2: 'rotateX(0deg) rotateY(-90deg)',
|
||||
3: 'rotateX(0deg) rotateY(180deg)',
|
||||
4: 'rotateX(0deg) rotateY(90deg)',
|
||||
5: 'rotateX(-90deg) rotateY(0deg)',
|
||||
6: 'rotateX(90deg) rotateY(0deg)'
|
||||
};
|
||||
|
||||
function roll() {
|
||||
if (rolling || gameEnded) return;
|
||||
rolling = true;
|
||||
|
||||
// Animation
|
||||
dice.classList.add('rolling');
|
||||
|
||||
// Zufällige Zahl
|
||||
const result = Math.floor(Math.random() * 6) + 1;
|
||||
|
||||
setTimeout(() => {
|
||||
dice.classList.remove('rolling');
|
||||
dice.style.transform = rotations[result];
|
||||
|
||||
// Auto bewegen
|
||||
moveCar(currentPlayer, result);
|
||||
|
||||
setTimeout(() => {
|
||||
rolling = false;
|
||||
}, 500);
|
||||
|
||||
}, 800);
|
||||
}
|
||||
|
||||
function moveCar(player, steps) {
|
||||
const car = cars[player];
|
||||
car.classList.add('moving');
|
||||
|
||||
positions[player] += steps;
|
||||
|
||||
// Position berechnen (in Pixel)
|
||||
const pixelPos = Math.min(positions[player] / STEPS_TO_FINISH * TRACK_LENGTH, TRACK_LENGTH);
|
||||
car.style.left = (10 + pixelPos) + 'px';
|
||||
|
||||
// Status aktualisieren
|
||||
const playerName = player === 0 ? '🔴 Rot' : '🔵 Blau';
|
||||
status.textContent = playerName + ' würfelt ' + steps + '!';
|
||||
|
||||
// TTS
|
||||
if ('speechSynthesis' in window) {
|
||||
const utterance = new SpeechSynthesisUtterance(playerName.replace('🔴', 'Rot').replace('🔵', 'Blau') + ' würfelt ' + steps);
|
||||
utterance.lang = 'de-DE';
|
||||
speechSynthesis.speak(utterance);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
car.classList.remove('moving');
|
||||
|
||||
// Prüfen ob gewonnen
|
||||
if (positions[player] >= STEPS_TO_FINISH) {
|
||||
endGame(player);
|
||||
} else {
|
||||
// Nächster Spieler
|
||||
switchPlayer();
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function switchPlayer() {
|
||||
currentPlayer = currentPlayer === 0 ? 1 : 0;
|
||||
|
||||
if (currentPlayer === 0) {
|
||||
turnIndicator.innerHTML = '<span class="player red">🔴 Rot</span> ist dran!';
|
||||
} else {
|
||||
turnIndicator.innerHTML = '<span class="player blue">🔵 Blau</span> ist dran!';
|
||||
}
|
||||
}
|
||||
|
||||
function endGame(winner) {
|
||||
gameEnded = true;
|
||||
const winnerEmoji = winner === 0 ? '🔴' : '🔵';
|
||||
const winnerName = winner === 0 ? 'Rot' : 'Blau';
|
||||
|
||||
document.getElementById('winnerEmoji').textContent = winnerEmoji + ' ' + winnerName + ' gewinnt!';
|
||||
document.getElementById('winner').style.display = 'block';
|
||||
|
||||
if ('speechSynthesis' in window) {
|
||||
const utterance = new SpeechSynthesisUtterance(winnerName + ' gewinnt das Rennen!');
|
||||
utterance.lang = 'de-DE';
|
||||
speechSynthesis.speak(utterance);
|
||||
}
|
||||
}
|
||||
|
||||
function resetGame() {
|
||||
positions = [0, 0];
|
||||
currentPlayer = 0;
|
||||
gameEnded = false;
|
||||
rolling = false;
|
||||
|
||||
cars.forEach(car => {
|
||||
car.style.left = '10px';
|
||||
car.classList.remove('moving');
|
||||
});
|
||||
|
||||
dice.style.transform = rotations[1];
|
||||
status.textContent = 'Klicke auf den Würfel!';
|
||||
turnIndicator.innerHTML = '<span class="player red">🔴 Rot</span> ist dran!';
|
||||
|
||||
document.getElementById('winner').style.display = 'none';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,246 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Buchstaben nachmalen - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 10px;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 10px; font-size: 22px; }
|
||||
.instruction { color: white; font-size: 18px; margin-bottom: 10px; text-align: center; }
|
||||
.main-area {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 15px;
|
||||
}
|
||||
.letter-section {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 10px;
|
||||
}
|
||||
.target-letter {
|
||||
font-size: 80px; background: white; border-radius: 15px;
|
||||
padding: 10px 30px; box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
.arrow { font-size: 30px; color: white; }
|
||||
.drawing-area { position: relative; }
|
||||
#drawCanvas {
|
||||
background: white; border: 3px solid #4ade80; border-radius: 15px;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.2); touch-action: none;
|
||||
}
|
||||
.template-overlay {
|
||||
position: absolute; top: 0; left: 0; pointer-events: none;
|
||||
font-size: 200px; color: rgba(200,200,200,0.3); text-align: center;
|
||||
line-height: 300px; width: 300px; height: 300px;
|
||||
}
|
||||
.letter-grid {
|
||||
display: grid; grid-template-columns: repeat(10, 1fr);
|
||||
gap: 5px; max-width: 500px; width: 100%; margin: 10px 0;
|
||||
}
|
||||
.letter-btn {
|
||||
background: rgba(255,255,255,0.9); border: none; border-radius: 8px;
|
||||
padding: 10px 5px; font-size: 20px; cursor: pointer; font-family: inherit;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.letter-btn:hover { background: white; transform: scale(1.1); }
|
||||
.letter-btn.active { background: #4ade80; color: white; }
|
||||
.controls { display: flex; gap: 15px; margin-top: 10px; flex-wrap: wrap; justify-content: center; }
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 12px 25px;
|
||||
font-size: 16px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-check { background: #4ade80; color: white; }
|
||||
.btn-clear { background: #feca57; }
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; }
|
||||
.feedback {
|
||||
font-size: 24px; margin: 10px 0; min-height: 40px; font-weight: bold;
|
||||
color: white; text-align: center;
|
||||
}
|
||||
.score { color: white; font-size: 18px; margin-top: 5px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🔤 Buchstaben nachmalen</h1>
|
||||
|
||||
<div class="instruction" id="instruction">Male den Buchstaben nach!</div>
|
||||
|
||||
<div class="main-area">
|
||||
<div class="letter-section">
|
||||
<div class="target-letter" id="targetLetter">A</div>
|
||||
<div class="arrow">⬇️</div>
|
||||
|
||||
<div class="drawing-area">
|
||||
<canvas id="drawCanvas" width="300" height="300"></canvas>
|
||||
<div class="template-overlay" id="templateOverlay">A</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feedback" id="feedback"></div>
|
||||
<div class="score">Richtig: <span id="correctCount">0</span></div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn btn-check" onclick="checkDrawing()">✓ Fertig!</button>
|
||||
<button class="btn btn-clear" onclick="clearCanvas()">🗑️ Löschen</button>
|
||||
<button class="btn" onclick="nextLetter()">🔄 Nächster</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<div class="letter-grid" id="letterGrid"></div>
|
||||
|
||||
<script>
|
||||
const letters = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','Ä','Ö','Ü'];
|
||||
const synth = window.speechSynthesis;
|
||||
|
||||
let currentLetter = 'A';
|
||||
let currentIndex = 0;
|
||||
let isDrawing = false;
|
||||
let correctCount = 0;
|
||||
let hasDrawn = false;
|
||||
|
||||
const canvas = document.getElementById('drawCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const templateOverlay = document.getElementById('templateOverlay');
|
||||
|
||||
// Setup canvas
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.lineWidth = 8;
|
||||
ctx.strokeStyle = '#333';
|
||||
|
||||
function initGrid() {
|
||||
const grid = document.getElementById('letterGrid');
|
||||
letters.forEach((letter, index) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'letter-btn';
|
||||
btn.textContent = letter;
|
||||
btn.onclick = () => selectLetter(index);
|
||||
grid.appendChild(btn);
|
||||
});
|
||||
}
|
||||
|
||||
function selectLetter(index) {
|
||||
currentIndex = index;
|
||||
currentLetter = letters[index];
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
function updateDisplay() {
|
||||
document.getElementById('targetLetter').textContent = currentLetter;
|
||||
templateOverlay.textContent = currentLetter;
|
||||
document.getElementById('instruction').textContent = `Male den Buchstaben "${currentLetter}" nach!`;
|
||||
|
||||
// Highlight
|
||||
document.querySelectorAll('.letter-btn').forEach((btn, i) => {
|
||||
btn.classList.toggle('active', i === currentIndex);
|
||||
});
|
||||
|
||||
clearCanvas();
|
||||
speak(`Male den Buchstaben ${currentLetter}`);
|
||||
}
|
||||
|
||||
function nextLetter() {
|
||||
currentIndex = (currentIndex + 1) % letters.length;
|
||||
selectLetter(currentIndex);
|
||||
}
|
||||
|
||||
// Drawing functions
|
||||
function getPos(e) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
||||
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
|
||||
return {
|
||||
x: clientX - rect.left,
|
||||
y: clientY - rect.top
|
||||
};
|
||||
}
|
||||
|
||||
function startDraw(e) {
|
||||
isDrawing = true;
|
||||
hasDrawn = true;
|
||||
const pos = getPos(e);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pos.x, pos.y);
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function draw(e) {
|
||||
if (!isDrawing) return;
|
||||
const pos = getPos(e);
|
||||
ctx.lineTo(pos.x, pos.y);
|
||||
ctx.stroke();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function stopDraw() {
|
||||
isDrawing = false;
|
||||
}
|
||||
|
||||
function clearCanvas() {
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
hasDrawn = false;
|
||||
document.getElementById('feedback').textContent = '';
|
||||
}
|
||||
|
||||
function checkDrawing() {
|
||||
if (!hasDrawn) {
|
||||
document.getElementById('feedback').textContent = '⚠️ Male erst etwas!';
|
||||
return;
|
||||
}
|
||||
|
||||
// Einfache Prüfung: Hat der Benutzer genug gezeichnet?
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const pixels = imageData.data;
|
||||
let drawnPixels = 0;
|
||||
|
||||
// Zähle nicht-weiße Pixel
|
||||
for (let i = 3; i < pixels.length; i += 4) {
|
||||
if (pixels[i] > 0) drawnPixels++;
|
||||
}
|
||||
|
||||
// Mindestens 500 Pixel müssen gezeichnet sein
|
||||
const threshold = 500;
|
||||
|
||||
if (drawnPixels > threshold) {
|
||||
correctCount++;
|
||||
document.getElementById('correctCount').textContent = correctCount;
|
||||
document.getElementById('feedback').textContent = '🌟 Sehr gut! Super gemalt!';
|
||||
document.getElementById('feedback').style.color = '#4ade80';
|
||||
speak(`Sehr gut! Du hast den Buchstaben ${currentLetter} toll gemalt!`);
|
||||
|
||||
// Automatisch nächster nach 2 Sekunden
|
||||
setTimeout(nextLetter, 2000);
|
||||
} else {
|
||||
document.getElementById('feedback').textContent = '✏️ Versuch es nochmal! Male den Buchstaben größer.';
|
||||
document.getElementById('feedback').style.color = '#feca57';
|
||||
speak('Versuch es nochmal. Male den Buchstaben größer und deutlicher.');
|
||||
}
|
||||
}
|
||||
|
||||
function speak(text) {
|
||||
if (!synth) return;
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.lang = 'de-DE';
|
||||
utterance.rate = 0.8;
|
||||
utterance.pitch = 1.2;
|
||||
synth.speak(utterance);
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
canvas.addEventListener('mousedown', startDraw);
|
||||
canvas.addEventListener('mousemove', draw);
|
||||
canvas.addEventListener('mouseup', stopDraw);
|
||||
canvas.addEventListener('mouseout', stopDraw);
|
||||
canvas.addEventListener('touchstart', startDraw);
|
||||
canvas.addEventListener('touchmove', draw);
|
||||
canvas.addEventListener('touchend', stopDraw);
|
||||
|
||||
// Init
|
||||
initGrid();
|
||||
clearCanvas();
|
||||
updateDisplay();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,424 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Halli Galli - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 15px;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 5px; font-size: 24px; }
|
||||
|
||||
.age-hint {
|
||||
color: #FEF3C7; font-size: 14px; margin-bottom: 10px; text-align: center;
|
||||
}
|
||||
|
||||
.difficulty-select {
|
||||
display: flex; gap: 8px; margin: 10px 0; flex-wrap: wrap; justify-content: center;
|
||||
}
|
||||
.diff-btn {
|
||||
background: rgba(255,255,255,0.2); border: 2px solid white; color: white;
|
||||
padding: 8px 15px; border-radius: 20px; cursor: pointer; font-family: inherit;
|
||||
font-size: 13px; transition: all 0.2s;
|
||||
}
|
||||
.diff-btn.active {
|
||||
background: white; color: #667eea; font-weight: bold;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.diff-btn:hover { background: rgba(255,255,255,0.4); }
|
||||
|
||||
.diff-desc {
|
||||
color: #FEF3C7; font-size: 12px; text-align: center; margin: 5px 0; max-width: 350px;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.game-area {
|
||||
display: flex; gap: 20px; margin: 15px 0; flex-wrap: wrap; justify-content: center;
|
||||
}
|
||||
|
||||
.card-display {
|
||||
width: 130px; height: 180px; background: white; border-radius: 15px;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3); position: relative; border: 4px solid #333;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.card-display.match {
|
||||
animation: pulse-match 0.5s ease-in-out;
|
||||
border-color: #FFD700;
|
||||
box-shadow: 0 0 30px #FFD700;
|
||||
}
|
||||
@keyframes pulse-match {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
}
|
||||
|
||||
.card-emoji { font-size: 60px; }
|
||||
.card-number {
|
||||
font-size: 36px; font-weight: bold; color: #333;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.card-count {
|
||||
position: absolute; bottom: 10px; font-size: 12px; color: #666; font-weight: bold;
|
||||
}
|
||||
.player-name {
|
||||
position: absolute; top: 10px; font-size: 11px; color: #999; font-weight: bold;
|
||||
}
|
||||
|
||||
.big-hint {
|
||||
position: absolute; top: -40px; left: 50%; transform: translateX(-50%);
|
||||
background: #FFD700; color: #333; padding: 5px 15px; border-radius: 20px;
|
||||
font-size: 14px; font-weight: bold; display: none; animation: bounce 0.5s infinite;
|
||||
}
|
||||
@keyframes bounce {
|
||||
0%, 100% { transform: translateX(-50%) translateY(0); }
|
||||
50% { transform: translateX(-50%) translateY(-5px); }
|
||||
}
|
||||
|
||||
.bell-area {
|
||||
width: 160px; height: 160px; background: linear-gradient(135deg, #FFD700, #FFA500);
|
||||
border-radius: 50%; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
cursor: pointer; box-shadow: 0 10px 30px rgba(0,0,0,0.4);
|
||||
border: 8px solid #FF8C00; transition: all 0.1s; position: relative;
|
||||
}
|
||||
.bell-area:active { transform: scale(0.95); }
|
||||
.bell { font-size: 80px; }
|
||||
.bell.ringing { animation: ring 0.3s ease-in-out; }
|
||||
@keyframes ring {
|
||||
0%, 100% { transform: rotate(0deg); }
|
||||
25% { transform: rotate(-15deg); }
|
||||
75% { transform: rotate(15deg); }
|
||||
}
|
||||
.bell-hint {
|
||||
position: absolute; bottom: 15px; font-size: 11px; color: #333; font-weight: bold;
|
||||
}
|
||||
|
||||
.score-board {
|
||||
display: flex; gap: 30px; margin: 10px 0; color: white; font-size: 16px;
|
||||
}
|
||||
.score-item { text-align: center; }
|
||||
.score-value { font-size: 28px; font-weight: bold; color: #feca57; }
|
||||
|
||||
.status {
|
||||
color: white; font-size: 18px; margin: 10px 0; text-align: center;
|
||||
min-height: 50px; font-weight: bold;
|
||||
}
|
||||
|
||||
.fruit-counter {
|
||||
display: flex; gap: 10px; margin: 10px 0; flex-wrap: wrap; justify-content: center;
|
||||
}
|
||||
.fruit-box {
|
||||
background: rgba(255,255,255,0.2); padding: 8px 12px; border-radius: 10px;
|
||||
color: white; font-size: 13px; text-align: center; min-width: 70px;
|
||||
}
|
||||
.fruit-box.highlight {
|
||||
background: #FFD700; color: #333; transform: scale(1.1);
|
||||
box-shadow: 0 0 15px rgba(255,215,0,0.5);
|
||||
}
|
||||
.fruit-count { font-size: 20px; font-weight: bold; }
|
||||
|
||||
.controls { display: flex; gap: 15px; margin-top: 15px; }
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 12px 25px;
|
||||
font-size: 16px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-start { background: #4ade80; color: white; }
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; }
|
||||
|
||||
.game-over {
|
||||
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
||||
background: white; padding: 30px; border-radius: 20px; text-align: center;
|
||||
display: none; z-index: 100; box-shadow: 0 10px 50px rgba(0,0,0,0.5);
|
||||
}
|
||||
.game-over h2 { margin-bottom: 15px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🔔 Halli Galli</h1>
|
||||
<div class="age-hint">👶 Kindermodus: Große Zahlen, visuelle Hilfen, mehr Zeit</div>
|
||||
|
||||
<div class="difficulty-select" id="diffSelect">
|
||||
<button class="diff-btn active" onclick="setDifficulty('baby')">👶 Baby (5 J.)</button>
|
||||
<button class="diff-btn" onclick="setDifficulty('easy')">🟢 Einfach</button>
|
||||
<button class="diff-btn" onclick="setDifficulty('medium')">🟡 Mittel</button>
|
||||
<button class="diff-btn" onclick="setDifficulty('hard')">🔴 Schwer</button>
|
||||
</div>
|
||||
<div class="diff-desc" id="diffDesc">Visuelle Hilfen + 4 Sekunden Zeit zum Reagieren</div>
|
||||
|
||||
<div class="score-board">
|
||||
<div class="score-item">
|
||||
<div>Deine Karten</div>
|
||||
<div class="score-value" id="playerCards">20</div>
|
||||
</div>
|
||||
<div class="score-item">
|
||||
<div>Computer</div>
|
||||
<div class="score-value" id="botCards">20</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="game-area">
|
||||
<div class="card-display" id="playerCard">
|
||||
<div class="big-hint" id="playerHint">🔔 KLINGELN!</div>
|
||||
<div class="player-name">DU</div>
|
||||
<div class="card-emoji" id="playerEmoji">❓</div>
|
||||
<div class="card-number" id="playerNum"></div>
|
||||
<div class="card-count" id="playerCount">20 Karten</div>
|
||||
</div>
|
||||
|
||||
<div class="bell-area" id="bell" onclick="ringBell()">
|
||||
<div class="bell">🔔</div>
|
||||
<div class="bell-hint" id="bellHint">Klick mich!</div>
|
||||
</div>
|
||||
|
||||
<div class="card-display" id="botCard">
|
||||
<div class="player-name">COMPUTER</div>
|
||||
<div class="card-emoji" id="botEmoji">❓</div>
|
||||
<div class="card-number" id="botNum"></div>
|
||||
<div class="card-count" id="botCount">20 Karten</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status" id="status">Wähle den Schwierigkeitsgrad für deine Tochter! 👧</div>
|
||||
|
||||
<div class="fruit-counter" id="fruitCounter">
|
||||
<div class="fruit-box" id="appleBox">
|
||||
<div>🍎</div>
|
||||
<div class="fruit-count" id="appleCount">0</div>
|
||||
</div>
|
||||
<div class="fruit-box" id="bananaBox">
|
||||
<div>🍌</div>
|
||||
<div class="fruit-count" id="bananaCount">0</div>
|
||||
</div>
|
||||
<div class="fruit-box" id="grapeBox">
|
||||
<div>🍇</div>
|
||||
<div class="fruit-count" id="grapeCount">0</div>
|
||||
</div>
|
||||
<div class="fruit-box" id="strawberryBox">
|
||||
<div>🍓</div>
|
||||
<div class="fruit-count" id="strawberryCount">0</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn btn-start" id="startBtn" onclick="startGame()">🎮 Start</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<div class="game-over" id="gameOver">
|
||||
<h2 id="winnerText">🎉 Gewonnen!</h2>
|
||||
<button class="btn btn-start" onclick="resetGame()">🔄 Nochmal</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const FRUITS = ['🍎', '🍌', '🍇', '🍓'];
|
||||
const DIFFICULTY = {
|
||||
baby: { time: 4000, showHint: true, showCounter: true, desc: '👶 Baby: 4 Sekunden + große Hilfen' },
|
||||
easy: { time: 2500, showHint: true, showCounter: true, desc: '🟢 Einfach: 2.5 Sekunden + Hilfen' },
|
||||
medium: { time: 1500, showHint: false, showCounter: true, desc: '🟡 Mittel: 1.5 Sekunden + Zähler' },
|
||||
hard: { time: 800, showHint: false, showCounter: false, desc: '🔴 Schwer: 0.8 Sekunden, alles selbst!' }
|
||||
};
|
||||
|
||||
let difficulty = 'baby';
|
||||
let playerDeck = [], botDeck = [];
|
||||
let playerCard = null, botCard = null;
|
||||
let gameRunning = false, canRing = false;
|
||||
let botTimer = null;
|
||||
|
||||
function setDifficulty(diff) {
|
||||
difficulty = diff;
|
||||
document.querySelectorAll('.diff-btn').forEach(b => b.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
document.getElementById('diffDesc').textContent = DIFFICULTY[diff].desc;
|
||||
}
|
||||
|
||||
function createDeck() {
|
||||
const deck = [];
|
||||
FRUITS.forEach(fruit => {
|
||||
for (let i = 1; i <= 5; i++) deck.push({ fruit, num: i });
|
||||
});
|
||||
// Doppelte Menge für 40 Karten insgesamt
|
||||
return shuffle([...deck, ...deck]);
|
||||
}
|
||||
|
||||
function shuffle(a) {
|
||||
for (let i = a.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[a[i], a[j]] = [a[j], a[i]];
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
const deck = createDeck();
|
||||
playerDeck = deck.slice(0, 20);
|
||||
botDeck = deck.slice(20);
|
||||
gameRunning = true; canRing = false;
|
||||
|
||||
document.getElementById('startBtn').style.display = 'none';
|
||||
document.getElementById('diffSelect').style.display = 'none';
|
||||
document.getElementById('diffDesc').style.display = 'none';
|
||||
document.getElementById('fruitCounter').style.display =
|
||||
DIFFICULTY[difficulty].showCounter ? 'flex' : 'none';
|
||||
|
||||
updateDisplay();
|
||||
nextTurn();
|
||||
}
|
||||
|
||||
function nextTurn() {
|
||||
if (!gameRunning) return;
|
||||
if (playerDeck.length === 0) { endGame(false); return; }
|
||||
if (botDeck.length === 0) { endGame(true); return; }
|
||||
|
||||
// Verstecke Hinweise
|
||||
document.getElementById('playerHint').style.display = 'none';
|
||||
document.getElementById('bellHint').style.display = 'none';
|
||||
document.querySelectorAll('.fruit-box').forEach(b => b.classList.remove('highlight'));
|
||||
document.querySelectorAll('.card-display').forEach(c => c.classList.remove('match'));
|
||||
|
||||
playerCard = playerDeck.shift();
|
||||
botCard = botDeck.shift();
|
||||
|
||||
updateDisplay();
|
||||
updateCounters();
|
||||
|
||||
const counts = { '🍎': 0, '🍌': 0, '🍇': 0, '🍓': 0 };
|
||||
if (playerCard) counts[playerCard.fruit] += playerCard.num;
|
||||
if (botCard) counts[botCard.fruit] += botCard.num;
|
||||
|
||||
const hasFive = Object.values(counts).includes(5);
|
||||
|
||||
if (hasFive) {
|
||||
canRing = true;
|
||||
document.getElementById('status').textContent = '🔔 FÜNF! Schnell klingeln!';
|
||||
document.getElementById('status').style.color = '#FFD700';
|
||||
|
||||
// Visuelle Hilfen für Kinder
|
||||
if (DIFFICULTY[difficulty].showHint) {
|
||||
document.getElementById('playerHint').style.display = 'block';
|
||||
document.getElementById('bellHint').style.display = 'block';
|
||||
document.getElementById('playerCard').classList.add('match');
|
||||
document.getElementById('botCard').classList.add('match');
|
||||
|
||||
// Highlight richtige Frucht
|
||||
for (let [fruit, count] of Object.entries(counts)) {
|
||||
if (count === 5) {
|
||||
const boxId = fruit === '🍎' ? 'appleBox' :
|
||||
fruit === '🍌' ? 'bananaBox' :
|
||||
fruit === '🍇' ? 'grapeBox' : 'strawberryBox';
|
||||
document.getElementById(boxId).classList.add('highlight');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bot reagiert
|
||||
botTimer = setTimeout(() => {
|
||||
if (gameRunning && canRing) botRing();
|
||||
}, DIFFICULTY[difficulty].time);
|
||||
} else {
|
||||
canRing = false;
|
||||
document.getElementById('status').textContent = 'Keine 5 gleichen... Nächste Karte!';
|
||||
document.getElementById('status').style.color = 'white';
|
||||
setTimeout(nextTurn, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
function updateCounters() {
|
||||
const counts = { '🍎': 0, '🍌': 0, '🍇': 0, '🍓': 0 };
|
||||
if (playerCard) counts[playerCard.fruit] += playerCard.num;
|
||||
if (botCard) counts[botCard.fruit] += botCard.num;
|
||||
|
||||
document.getElementById('appleCount').textContent = counts['🍎'];
|
||||
document.getElementById('bananaCount').textContent = counts['🍌'];
|
||||
document.getElementById('grapeCount').textContent = counts['🍇'];
|
||||
document.getElementById('strawberryCount').textContent = counts['🍓'];
|
||||
}
|
||||
|
||||
function updateDisplay() {
|
||||
document.getElementById('playerCards').textContent = playerDeck.length;
|
||||
document.getElementById('botCards').textContent = botDeck.length;
|
||||
document.getElementById('playerCount').textContent = (playerDeck.length + 1) + ' Karten';
|
||||
document.getElementById('botCount').textContent = (botDeck.length + 1) + ' Karten';
|
||||
|
||||
if (playerCard) {
|
||||
document.getElementById('playerEmoji').textContent = playerCard.fruit;
|
||||
document.getElementById('playerNum').textContent = playerCard.num;
|
||||
}
|
||||
if (botCard) {
|
||||
document.getElementById('botEmoji').textContent = botCard.fruit;
|
||||
document.getElementById('botNum').textContent = botCard.num;
|
||||
}
|
||||
}
|
||||
|
||||
function ringBell() {
|
||||
if (!canRing || !gameRunning) {
|
||||
// Falsch geklingelt - Strafe!
|
||||
if (gameRunning) {
|
||||
document.getElementById('status').textContent = '❌ Zu früh! Computer bekommt deine Karten!';
|
||||
botDeck.push(...playerDeck.splice(0, Math.min(3, playerDeck.length)));
|
||||
updateDisplay();
|
||||
setTimeout(nextTurn, 2000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(botTimer);
|
||||
canRing = false;
|
||||
|
||||
document.querySelector('.bell').classList.add('ringing');
|
||||
setTimeout(() => document.querySelector('.bell').classList.remove('ringing'), 300);
|
||||
|
||||
// Gewinner bekommt alle Karten
|
||||
const wonCards = [playerCard, botCard, ...playerDeck.splice(0, 2), ...botDeck.splice(0, 2)];
|
||||
playerDeck.push(...wonCards.filter(c => c));
|
||||
|
||||
document.getElementById('status').textContent = '🎉 Super! Du hast ' + wonCards.length + ' Karten gewonnen!';
|
||||
updateDisplay();
|
||||
|
||||
setTimeout(nextTurn, 2000);
|
||||
}
|
||||
|
||||
function botRing() {
|
||||
if (!canRing || !gameRunning) return;
|
||||
canRing = false;
|
||||
|
||||
document.querySelector('.bell').classList.add('ringing');
|
||||
setTimeout(() => document.querySelector('.bell').classList.remove('ringing'), 300);
|
||||
|
||||
const wonCards = [playerCard, botCard, ...playerDeck.splice(0, 2), ...botDeck.splice(0, 2)];
|
||||
botDeck.push(...wonCards.filter(c => c));
|
||||
|
||||
document.getElementById('status').textContent = '🤖 Computer war schneller!';
|
||||
updateDisplay();
|
||||
|
||||
setTimeout(nextTurn, 2000);
|
||||
}
|
||||
|
||||
function endGame(playerWon) {
|
||||
gameRunning = false;
|
||||
document.getElementById('winnerText').innerHTML = playerWon ?
|
||||
'🎉 Du hast gewonnen!' : '🤖 Computer gewinnt!';
|
||||
document.getElementById('gameOver').style.display = 'block';
|
||||
}
|
||||
|
||||
function resetGame() {
|
||||
document.getElementById('gameOver').style.display = 'none';
|
||||
document.getElementById('startBtn').style.display = 'block';
|
||||
document.getElementById('diffSelect').style.display = 'flex';
|
||||
document.getElementById('diffDesc').style.display = 'block';
|
||||
document.getElementById('fruitCounter').style.display = 'none';
|
||||
document.getElementById('status').textContent = 'Wähle den Schwierigkeitsgrad!';
|
||||
document.getElementById('status').style.color = 'white';
|
||||
|
||||
playerCard = null; botCard = null;
|
||||
document.getElementById('playerEmoji').textContent = '❓';
|
||||
document.getElementById('playerNum').textContent = '';
|
||||
document.getElementById('botEmoji').textContent = '❓';
|
||||
document.getElementById('botNum').textContent = '';
|
||||
updateDisplay();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,471 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Klavier - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #2d1b4e 0%, #1a1a2e 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 15px;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 5px; font-size: 24px; }
|
||||
.mode-tabs { display: flex; gap: 10px; margin-bottom: 15px; }
|
||||
.mode-btn {
|
||||
background: rgba(255,255,255,0.2); border: 2px solid white; color: white;
|
||||
padding: 8px 20px; border-radius: 20px; cursor: pointer; font-family: inherit;
|
||||
}
|
||||
.mode-btn.active { background: white; color: #2d1b4e; font-weight: bold; }
|
||||
|
||||
.practice-display {
|
||||
background: rgba(255,255,255,0.1); border-radius: 15px; padding: 15px;
|
||||
margin: 10px 0; color: white; min-height: 80px; width: 350px; text-align: center;
|
||||
}
|
||||
.practice-note {
|
||||
font-size: 48px; display: inline-block; margin: 0 5px; transition: all 0.3s;
|
||||
}
|
||||
.practice-note.highlight {
|
||||
transform: scale(1.3); text-shadow: 0 0 20px #4ade80;
|
||||
animation: bounce 0.5s infinite alternate;
|
||||
}
|
||||
@keyframes bounce { from { transform: scale(1.3); } to { transform: scale(1.5); } }
|
||||
.practice-note.correct { color: #4ade80; }
|
||||
.practice-note.wrong { color: #ff6b6b; }
|
||||
|
||||
.piano {
|
||||
display: flex; position: relative; margin: 15px 0;
|
||||
background: #333; padding: 10px; border-radius: 10px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
|
||||
}
|
||||
.white-key {
|
||||
width: 45px; height: 180px; background: white;
|
||||
border: 2px solid #333; border-radius: 0 0 5px 5px;
|
||||
display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
|
||||
padding-bottom: 8px; cursor: pointer; font-size: 12px; color: #666;
|
||||
transition: all 0.1s;
|
||||
}
|
||||
.white-key:active, .white-key.active, .white-key.highlight {
|
||||
background: #ddd; transform: translateY(2px);
|
||||
}
|
||||
.white-key.highlight { background: #feca57 !important; }
|
||||
.white-key.correct { background: #86efac !important; }
|
||||
.black-key {
|
||||
width: 28px; height: 110px; background: linear-gradient(180deg, #333 0%, #000 100%);
|
||||
border-radius: 0 0 3px 3px; position: absolute; top: 10px;
|
||||
display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
|
||||
padding-bottom: 6px; cursor: pointer; color: #aaa; font-size: 9px;
|
||||
transition: all 0.1s; z-index: 10;
|
||||
}
|
||||
.black-key:active, .black-key.active, .black-key.highlight {
|
||||
background: #222; transform: translateY(2px);
|
||||
}
|
||||
.black-key.highlight { background: #feca57 !important; }
|
||||
.black-1 { left: 36px; } .black-2 { left: 81px; }
|
||||
.black-3 { left: 171px; } .black-4 { left: 216px; }
|
||||
.black-5 { left: 261px; }
|
||||
|
||||
.songs-grid {
|
||||
display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;
|
||||
max-width: 400px; margin: 10px 0;
|
||||
}
|
||||
.song-card {
|
||||
background: rgba(255,255,255,0.1); border: 2px solid white; border-radius: 15px;
|
||||
padding: 15px; color: white; cursor: pointer; transition: all 0.2s; text-align: center;
|
||||
}
|
||||
.song-card:hover { background: rgba(255,255,255,0.3); transform: translateY(-3px); }
|
||||
.song-card:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.song-emoji { font-size: 30px; margin-bottom: 5px; }
|
||||
.song-title { font-weight: bold; font-size: 14px; }
|
||||
.song-desc { font-size: 11px; color: #aaa; margin-top: 5px; }
|
||||
|
||||
.exercise-controls {
|
||||
display: flex; gap: 10px; margin: 15px 0; flex-wrap: wrap; justify-content: center;
|
||||
}
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 10px 20px;
|
||||
font-size: 14px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-start { background: #4ade80; color: white; }
|
||||
.btn-hint { background: #feca57; color: #333; }
|
||||
.btn-stop { background: #ff6b6b; color: white; }
|
||||
.btn-back { background: #6b7280; color: white; text-decoration: none; }
|
||||
|
||||
.progress {
|
||||
width: 350px; height: 10px; background: rgba(255,255,255,0.2); border-radius: 5px;
|
||||
margin: 10px 0; overflow: hidden;
|
||||
}
|
||||
.progress-bar {
|
||||
height: 100%; background: linear-gradient(90deg, #4ade80, #22c55e);
|
||||
width: 0%; transition: width 0.3s;
|
||||
}
|
||||
.feedback { font-size: 24px; margin: 10px 0; min-height: 30px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🎹 Klavier</h1>
|
||||
|
||||
<div class="mode-tabs">
|
||||
<button class="mode-btn active" id="freeBtn" onclick="setMode('free')">🎵 Frei spielen</button>
|
||||
<button class="mode-btn" id="practiceBtn" onclick="setMode('practice')">📚 Üben</button>
|
||||
</div>
|
||||
|
||||
<!-- Übungs-Display -->
|
||||
<div class="practice-display" id="practiceDisplay" style="display:none">
|
||||
<div id="practiceNotes">Wähle ein Lied zum Üben!</div>
|
||||
<div class="progress"><div class="progress-bar" id="progressBar"></div></div>
|
||||
<div class="feedback" id="feedback"></div>
|
||||
</div>
|
||||
|
||||
<!-- Lieder-Auswahl -->
|
||||
<div class="songs-grid" id="songsGrid">
|
||||
<div class="song-card" onclick="selectSong('twinkle')">
|
||||
<div class="song-emoji">⭐</div>
|
||||
<div class="song-title">Twinkle Twinkle</div>
|
||||
<div class="song-desc">C-C-G-G-A-A-G</div>
|
||||
</div>
|
||||
<div class="song-card" onclick="selectSong('entchen')">
|
||||
<div class="song-emoji">🦆</div>
|
||||
<div class="song-title">Alle meine Entchen</div>
|
||||
<div class="song-desc">C-D-E-F-G-G</div>
|
||||
</div>
|
||||
<div class="song-card" onclick="selectSong('haenschen')">
|
||||
<div class="song-emoji">🐰</div>
|
||||
<div class="song-title">Hänschen klein</div>
|
||||
<div class="song-desc">G-E-E-F-D-D-C</div>
|
||||
</div>
|
||||
<div class="song-card" onclick="selectSong('happy')">
|
||||
<div class="song-emoji">🎂</div>
|
||||
<div class="song-title">Happy Birthday</div>
|
||||
<div class="song-desc">C-C-D-C-F-E</div>
|
||||
</div>
|
||||
<div class="song-card" onclick="selectSong('mary')">
|
||||
<div class="song-emoji">🐑</div>
|
||||
<div class="song-title">Mary Had a Lamb</div>
|
||||
<div class="song-desc">E-D-C-D-E-E-E</div>
|
||||
</div>
|
||||
<div class="song-card" onclick="selectSong('schlaf')">
|
||||
<div class="song-emoji">🌙</div>
|
||||
<div class="song-title">Schlaf Kindlein</div>
|
||||
<div class="song-desc">G-G-E-E-F-F-D</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Übungs-Controls -->
|
||||
<div class="exercise-controls" id="practiceControls" style="display:none">
|
||||
<button class="btn btn-start" onclick="startPractice()">▶️ Start</button>
|
||||
<button class="btn btn-hint" onclick="showHint()">💡 Tipp</button>
|
||||
<button class="btn btn-stop" onclick="stopPractice()">⏹️ Stop</button>
|
||||
</div>
|
||||
|
||||
<!-- Piano -->
|
||||
<div class="piano" id="piano">
|
||||
<div class="white-key" data-note="C4" data-key="a">C<br><span style="font-size:9px;color:#999">A</span></div>
|
||||
<div class="white-key" data-note="D4" data-key="s">D<br><span style="font-size:9px;color:#999">S</span></div>
|
||||
<div class="white-key" data-note="E4" data-key="d">E<br><span style="font-size:9px;color:#999">D</span></div>
|
||||
<div class="white-key" data-note="F4" data-key="f">F<br><span style="font-size:9px;color:#999">F</span></div>
|
||||
<div class="white-key" data-note="G4" data-key="g">G<br><span style="font-size:9px;color:#999">G</span></div>
|
||||
<div class="white-key" data-note="A4" data-key="h">A<br><span style="font-size:9px;color:#999">H</span></div>
|
||||
<div class="white-key" data-note="B4" data-key="j">B<br><span style="font-size:9px;color:#999">J</span></div>
|
||||
<div class="white-key" data-note="C5" data-key="k">C<br><span style="font-size:9px;color:#999">K</span></div>
|
||||
|
||||
<div class="black-key black-1" data-note="C#4" data-key="w">C#<br><span style="font-size:8px">W</span></div>
|
||||
<div class="black-key black-2" data-note="D#4" data-key="e">D#<br><span style="font-size:8px">E</span></div>
|
||||
<div class="black-key black-3" data-note="F#4" data-key="t">F#<br><span style="font-size:8px">T</span></div>
|
||||
<div class="black-key black-4" data-note="G#4" data-key="z">G#<br><span style="font-size:8px">Z</span></div>
|
||||
<div class="black-key black-5" data-note="A#4" data-key="u">A#<br><span style="font-size:8px">U</span></div>
|
||||
</div>
|
||||
|
||||
<div class="exercise-controls">
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
|
||||
const frequencies = {
|
||||
'C4': 261.63, 'C#4': 277.18, 'D4': 293.66, 'D#4': 311.13,
|
||||
'E4': 329.63, 'F4': 349.23, 'F#4': 369.99, 'G4': 392.00,
|
||||
'G#4': 415.30, 'A4': 440.00, 'A#4': 466.16, 'B4': 493.88, 'C5': 523.25
|
||||
};
|
||||
|
||||
const noteEmojis = {
|
||||
'C4': '🔴', 'C#4': '⚫', 'D4': '🟠', 'D#4': '⚫',
|
||||
'E4': '🟡', 'F4': '🟢', 'F#4': '⚫', 'G4': '🔵',
|
||||
'G#4': '⚫', 'A4': '🟣', 'A#4': '⚫', 'B4': '⚪', 'C5': '🔴'
|
||||
};
|
||||
|
||||
const songs = {
|
||||
twinkle: {
|
||||
notes: ['C4', 'C4', 'G4', 'G4', 'A4', 'A4', 'G4', 'F4', 'F4', 'E4', 'E4', 'D4', 'D4', 'C4'],
|
||||
durations: [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1]
|
||||
},
|
||||
entchen: {
|
||||
notes: ['C4', 'D4', 'E4', 'F4', 'G4', 'G4', 'A4', 'A4', 'A4', 'A4', 'G4', 'F4', 'F4', 'F4', 'F4', 'E4', 'E4', 'D4', 'D4', 'D4', 'D4', 'C4'],
|
||||
durations: [0.4, 0.4, 0.4, 0.4, 0.6, 0.6, 0.3, 0.3, 0.3, 0.3, 0.8, 0.3, 0.3, 0.3, 0.3, 0.4, 0.4, 0.3, 0.3, 0.3, 0.3, 0.8]
|
||||
},
|
||||
haenschen: {
|
||||
notes: ['G4', 'E4', 'E4', 'F4', 'D4', 'D4', 'C4', 'G4', 'G4', 'E4', 'E4', 'F4', 'D4', 'D4', 'C4'],
|
||||
durations: [0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.8, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.8]
|
||||
},
|
||||
happy: {
|
||||
notes: ['C4', 'C4', 'D4', 'C4', 'F4', 'E4', 'C4', 'C4', 'D4', 'C4', 'G4', 'F4'],
|
||||
durations: [0.4, 0.4, 0.4, 0.4, 0.4, 0.8, 0.4, 0.4, 0.4, 0.4, 0.4, 0.8]
|
||||
},
|
||||
mary: {
|
||||
notes: ['E4', 'D4', 'C4', 'D4', 'E4', 'E4', 'E4', 'D4', 'D4', 'D4', 'E4', 'G4', 'G4'],
|
||||
durations: [0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.6, 0.4, 0.4, 0.6, 0.4, 0.4, 0.6]
|
||||
},
|
||||
schlaf: {
|
||||
notes: ['G4', 'G4', 'E4', 'E4', 'F4', 'F4', 'D4', 'G4', 'G4', 'E4', 'E4', 'f4', 'f4', 'd4'],
|
||||
durations: [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1]
|
||||
}
|
||||
};
|
||||
|
||||
let currentMode = 'free';
|
||||
let currentSong = null;
|
||||
let currentNoteIndex = 0;
|
||||
let practiceActive = false;
|
||||
let waitingForNote = false;
|
||||
|
||||
function setMode(mode) {
|
||||
currentMode = mode;
|
||||
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
|
||||
document.getElementById(mode + 'Btn').classList.add('active');
|
||||
|
||||
if (mode === 'practice') {
|
||||
document.getElementById('practiceDisplay').style.display = 'block';
|
||||
document.getElementById('practiceControls').style.display = 'flex';
|
||||
} else {
|
||||
document.getElementById('practiceDisplay').style.display = 'none';
|
||||
document.getElementById('practiceControls').style.display = 'none';
|
||||
stopPractice();
|
||||
}
|
||||
}
|
||||
|
||||
function selectSong(songName) {
|
||||
currentSong = songName;
|
||||
const song = songs[songName];
|
||||
|
||||
// Noten anzeigen
|
||||
const display = document.getElementById('practiceNotes');
|
||||
display.innerHTML = song.notes.map((note, i) =>
|
||||
`<span class="practice-note" id="note-${i}">${noteEmojis[note]}</span>`
|
||||
).join(' ');
|
||||
|
||||
currentNoteIndex = 0;
|
||||
updateProgress();
|
||||
|
||||
// Demo abspielen
|
||||
playSong(songName);
|
||||
}
|
||||
|
||||
function startPractice() {
|
||||
if (!currentSong) {
|
||||
document.getElementById('feedback').textContent = '❌ Wähle zuerst ein Lied!';
|
||||
return;
|
||||
}
|
||||
|
||||
practiceActive = true;
|
||||
currentNoteIndex = 0;
|
||||
highlightNextNote();
|
||||
document.getElementById('feedback').textContent = '🎵 Spiel die markierte Note!';
|
||||
}
|
||||
|
||||
function highlightNextNote() {
|
||||
if (!practiceActive || !currentSong) return;
|
||||
|
||||
const song = songs[currentSong];
|
||||
|
||||
// Alte Markierung entfernen
|
||||
document.querySelectorAll('.practice-note').forEach(n => {
|
||||
n.classList.remove('highlight', 'correct', 'wrong');
|
||||
});
|
||||
|
||||
if (currentNoteIndex >= song.notes.length) {
|
||||
// Fertig!
|
||||
document.getElementById('feedback').textContent = '🎉 Super! Lied geschafft!';
|
||||
playNote('C4', 0.3);
|
||||
setTimeout(() => playNote('E4', 0.3), 200);
|
||||
setTimeout(() => playNote('G4', 0.5), 400);
|
||||
practiceActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Nächste Note markieren
|
||||
const noteEl = document.getElementById('note-' + currentNoteIndex);
|
||||
if (noteEl) noteEl.classList.add('highlight');
|
||||
|
||||
// Taste auf Piano markieren
|
||||
const expectedNote = song.notes[currentNoteIndex];
|
||||
highlightKey(expectedNote);
|
||||
|
||||
waitingForNote = true;
|
||||
updateProgress();
|
||||
}
|
||||
|
||||
function highlightKey(note) {
|
||||
document.querySelectorAll('.white-key, .black-key').forEach(k => {
|
||||
k.classList.remove('highlight');
|
||||
});
|
||||
|
||||
const key = document.querySelector(`[data-note="${note}"]`);
|
||||
if (key) key.classList.add('highlight');
|
||||
}
|
||||
|
||||
function checkPracticeNote(playedNote) {
|
||||
if (!practiceActive || !waitingForNote || !currentSong) return false;
|
||||
|
||||
const song = songs[currentSong];
|
||||
const expectedNote = song.notes[currentNoteIndex];
|
||||
|
||||
const noteEl = document.getElementById('note-' + currentNoteIndex);
|
||||
|
||||
if (playedNote === expectedNote) {
|
||||
// Richtig!
|
||||
noteEl.classList.remove('highlight');
|
||||
noteEl.classList.add('correct');
|
||||
document.getElementById('feedback').textContent = '✅ Richtig!';
|
||||
|
||||
currentNoteIndex++;
|
||||
waitingForNote = false;
|
||||
|
||||
setTimeout(() => highlightNextNote(), 500);
|
||||
return true;
|
||||
} else {
|
||||
// Falsch
|
||||
noteEl.classList.add('wrong');
|
||||
document.getElementById('feedback').textContent = '❌ Das war ' + playedNote + ', versuche ' + expectedNote;
|
||||
|
||||
setTimeout(() => {
|
||||
noteEl.classList.remove('wrong');
|
||||
if (practiceActive) highlightNextNote();
|
||||
}, 1000);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function showHint() {
|
||||
if (!practiceActive || !currentSong) return;
|
||||
|
||||
const song = songs[currentSong];
|
||||
const expectedNote = song.notes[currentNoteIndex];
|
||||
|
||||
// Note blinkt
|
||||
const key = document.querySelector(`[data-note="${expectedNote}"]`);
|
||||
if (key) {
|
||||
key.style.animation = 'bounce 0.5s 3';
|
||||
setTimeout(() => key.style.animation = '', 1500);
|
||||
}
|
||||
|
||||
document.getElementById('feedback').textContent = '💡 Schau auf die gelbe Taste!';
|
||||
}
|
||||
|
||||
function stopPractice() {
|
||||
practiceActive = false;
|
||||
waitingForNote = false;
|
||||
currentNoteIndex = 0;
|
||||
|
||||
document.querySelectorAll('.practice-note').forEach(n => {
|
||||
n.classList.remove('highlight', 'correct', 'wrong');
|
||||
});
|
||||
|
||||
document.querySelectorAll('.white-key, .black-key').forEach(k => {
|
||||
k.classList.remove('highlight');
|
||||
});
|
||||
|
||||
document.getElementById('progressBar').style.width = '0%';
|
||||
document.getElementById('feedback').textContent = '';
|
||||
}
|
||||
|
||||
function updateProgress() {
|
||||
if (!currentSong) return;
|
||||
const song = songs[currentSong];
|
||||
const progress = (currentNoteIndex / song.notes.length) * 100;
|
||||
document.getElementById('progressBar').style.width = progress + '%';
|
||||
}
|
||||
|
||||
function playNote(note, duration = 0.3) {
|
||||
if (!frequencies[note]) return;
|
||||
|
||||
const osc = audioCtx.createOscillator();
|
||||
const gain = audioCtx.createGain();
|
||||
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
|
||||
osc.frequency.value = frequencies[note];
|
||||
osc.type = 'sine';
|
||||
|
||||
gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
|
||||
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime + duration);
|
||||
|
||||
// Visuelles Feedback
|
||||
const key = document.querySelector(`[data-note="${note}"]`);
|
||||
if (key) {
|
||||
key.classList.add('active');
|
||||
setTimeout(() => key.classList.remove('active'), 200);
|
||||
}
|
||||
}
|
||||
|
||||
async function playSong(songName) {
|
||||
const song = songs[songName];
|
||||
if (!song) return;
|
||||
|
||||
for (let i = 0; i < song.notes.length; i++) {
|
||||
playNote(song.notes[i], song.durations[i]);
|
||||
await sleep(song.durations[i] * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// Tasten-Events
|
||||
document.querySelectorAll('.white-key, .black-key').forEach(key => {
|
||||
key.addEventListener('mousedown', () => {
|
||||
const note = key.dataset.note;
|
||||
playNote(note);
|
||||
|
||||
if (currentMode === 'practice') {
|
||||
checkPracticeNote(note);
|
||||
}
|
||||
});
|
||||
|
||||
key.addEventListener('touchstart', (e) => {
|
||||
e.preventDefault();
|
||||
const note = key.dataset.note;
|
||||
playNote(note);
|
||||
|
||||
if (currentMode === 'practice') {
|
||||
checkPracticeNote(note);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Keyboard
|
||||
document.addEventListener('keydown', (e) => {
|
||||
const key = document.querySelector(`[data-key="${e.key.toLowerCase()}"]`);
|
||||
if (key && !key.classList.contains('active')) {
|
||||
const note = key.dataset.note;
|
||||
playNote(note);
|
||||
key.classList.add('active');
|
||||
|
||||
if (currentMode === 'practice') {
|
||||
checkPracticeNote(note);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keyup', (e) => {
|
||||
const key = document.querySelector(`[data-key="${e.key.toLowerCase()}"]`);
|
||||
if (key) key.classList.remove('active');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,387 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Leiterspiel - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #4a5568 0%, #2d3748 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 10px;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 10px; font-size: 22px; }
|
||||
|
||||
.board-container {
|
||||
background: #8B7355; padding: 10px; border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.board {
|
||||
display: grid; grid-template-columns: repeat(10, 30px); gap: 2px;
|
||||
}
|
||||
|
||||
.cell {
|
||||
width: 30px; height: 30px; border-radius: 4px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 9px; font-weight: bold; position: relative;
|
||||
}
|
||||
|
||||
/* Schachbrett-Muster */
|
||||
.cell:nth-child(even) { background: #F5DEB3; }
|
||||
.cell:nth-child(odd) { background: #DEB887; }
|
||||
|
||||
.cell.start { background: #90EE90 !important; }
|
||||
.cell.finish { background: #FFD700 !important; border: 2px solid #FFA500; }
|
||||
.cell.ladder { background: #87CEEB !important; }
|
||||
.cell.snake { background: #ff9999 !important; }
|
||||
|
||||
.cell-number { position: absolute; top: 2px; left: 2px; font-size: 7px; }
|
||||
|
||||
.piece {
|
||||
width: 16px; height: 16px; border-radius: 50%;
|
||||
position: absolute; border: 1px solid white;
|
||||
box-shadow: 1px 1px 3px rgba(0,0,0,0.5);
|
||||
z-index: 10; font-size: 8px; display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.piece.p1 { background: #ff4444; color: white; }
|
||||
.piece.p2 { background: #4444ff; color: white; }
|
||||
|
||||
/* Emoji auf Feldern */
|
||||
.cell-icon { font-size: 14px; }
|
||||
|
||||
/* UI */
|
||||
.turn-indicator {
|
||||
background: rgba(255,255,255,0.2); padding: 10px 20px; border-radius: 20px;
|
||||
color: white; margin: 10px 0; font-size: 16px;
|
||||
}
|
||||
|
||||
.status { color: white; font-size: 14px; margin: 10px 0; text-align: center; max-width: 320px; }
|
||||
|
||||
/* Würfel */
|
||||
.dice-area { margin: 15px 0; }
|
||||
.dice {
|
||||
width: 60px; height: 60px; background: white; border-radius: 10px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 30px; cursor: pointer; border: 3px solid #333;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
||||
}
|
||||
.dice.rolling { animation: shake 0.5s ease-in-out; }
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: rotate(0deg); }
|
||||
25% { transform: rotate(-10deg); }
|
||||
75% { transform: rotate(10deg); }
|
||||
}
|
||||
|
||||
.controls { display: flex; gap: 10px; margin-top: 15px; }
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 10px 20px;
|
||||
font-size: 14px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-roll { background: #4ade80; color: white; }
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; }
|
||||
|
||||
.legend {
|
||||
display: flex; gap: 15px; margin: 10px 0; flex-wrap: wrap; justify-content: center;
|
||||
}
|
||||
.legend-item { display: flex; align-items: center; gap: 5px; color: white; font-size: 12px; }
|
||||
.legend-box { width: 18px; height: 18px; border-radius: 3px; }
|
||||
|
||||
.winner {
|
||||
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
||||
background: white; padding: 30px; border-radius: 20px;
|
||||
text-align: center; display: none; z-index: 100;
|
||||
box-shadow: 0 10px 50px rgba(0,0,0,0.5);
|
||||
}
|
||||
.winner h2 { margin-bottom: 15px; }
|
||||
.winner-emoji { font-size: 40px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🐍 Leiterspiel 🪜</h1>
|
||||
|
||||
<div class="turn-indicator" id="turnIndicator">
|
||||
🔴 Spieler 1 ist dran!
|
||||
</div>
|
||||
|
||||
<div class="board-container">
|
||||
<div class="board" id="board"></div>
|
||||
</div>
|
||||
|
||||
<div class="legend">
|
||||
<div class="legend-item">
|
||||
<div class="legend-box" style="background:#87CEEB"></div>
|
||||
🪜 Leiter
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-box" style="background:#ff9999"></div>
|
||||
🐍 Schlange
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status" id="status">Würfle und klettere hoch! Aber pass auf vor den Schlangen! 🐍</div>
|
||||
|
||||
<div class="dice-area">
|
||||
<div class="dice" id="dice" onclick="roll()">🎲</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn btn-roll" onclick="roll()">🎲 Würfeln</button>
|
||||
<button class="btn" onclick="resetGame()">🔄 Neustart</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<div class="winner" id="winner">
|
||||
<h2>🎉 Gewinner! 🎉</h2>
|
||||
<div class="winner-emoji" id="winnerText"></div>
|
||||
<button class="btn btn-roll" onclick="resetGame()" style="margin-top: 15px">🔄 Nochmal</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Leitern: von -> nach oben
|
||||
const LADDERS = {
|
||||
4: 14, // 4 -> 14
|
||||
9: 31, // 9 -> 31
|
||||
20: 38, // 20 -> 38
|
||||
28: 84, // 28 -> 84
|
||||
40: 59, // 40 -> 59
|
||||
51: 67, // 51 -> 67
|
||||
63: 81, // 63 -> 81
|
||||
71: 91 // 71 -> 91
|
||||
};
|
||||
|
||||
// Schlangen: von -> nach unten
|
||||
const SNAKES = {
|
||||
17: 7, // 17 -> 7
|
||||
54: 34, // 54 -> 34
|
||||
62: 19, // 62 -> 19
|
||||
64: 60, // 64 -> 60
|
||||
87: 24, // 87 -> 24
|
||||
93: 73, // 93 -> 73
|
||||
95: 75, // 95 -> 75
|
||||
99: 78 // 99 -> 78
|
||||
};
|
||||
|
||||
let positions = [1, 1]; // Spieler 1 und 2
|
||||
let currentPlayer = 0; // 0 = Spieler 1, 1 = Spieler 2
|
||||
let rolling = false;
|
||||
let gameEnded = false;
|
||||
|
||||
function initBoard() {
|
||||
const board = document.getElementById('board');
|
||||
board.innerHTML = '';
|
||||
|
||||
// Brett: Zeile 10 (100-91), Zeile 9 (81-90), etc.
|
||||
for (let row = 9; row >= 0; row--) {
|
||||
const isEvenRow = row % 2 === 0;
|
||||
const startNum = row * 10 + 1;
|
||||
const endNum = (row + 1) * 10;
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const num = isEvenRow ? startNum + i : endNum - i;
|
||||
|
||||
const cell = document.createElement('div');
|
||||
cell.className = 'cell';
|
||||
cell.dataset.num = num;
|
||||
|
||||
// Start
|
||||
if (num === 1) {
|
||||
cell.classList.add('start');
|
||||
cell.innerHTML = '<span class="cell-icon">🏠</span>';
|
||||
}
|
||||
// Ziel
|
||||
else if (num === 100) {
|
||||
cell.classList.add('finish');
|
||||
cell.innerHTML = '<span class="cell-icon">🏆</span>';
|
||||
}
|
||||
// Leiter
|
||||
else if (LADDERS[num]) {
|
||||
cell.classList.add('ladder');
|
||||
cell.innerHTML = '<span class="cell-icon">🪜</span>';
|
||||
cell.title = 'Nach ' + LADDERS[num];
|
||||
}
|
||||
// Schlange
|
||||
else if (SNAKES[num]) {
|
||||
cell.classList.add('snake');
|
||||
cell.innerHTML = '<span class="cell-icon">🐍</span>';
|
||||
cell.title = 'Nach ' + SNAKES[num];
|
||||
}
|
||||
else {
|
||||
cell.innerHTML = '<span class="cell-number">' + num + '</span>';
|
||||
}
|
||||
|
||||
board.appendChild(cell);
|
||||
}
|
||||
}
|
||||
|
||||
updatePieces();
|
||||
}
|
||||
|
||||
function updatePieces() {
|
||||
// Alte Figuren entfernen
|
||||
document.querySelectorAll('.piece').forEach(p => p.remove());
|
||||
|
||||
// Neue Figuren setzen
|
||||
positions.forEach((pos, idx) => {
|
||||
const cell = document.querySelector(`[data-num="${pos}"]`);
|
||||
if (cell) {
|
||||
const piece = document.createElement('div');
|
||||
piece.className = 'piece p' + (idx + 1);
|
||||
piece.textContent = (idx + 1);
|
||||
|
||||
// Position innerhalb der Zelle
|
||||
if (idx === 0) {
|
||||
piece.style.top = '2px';
|
||||
piece.style.left = '2px';
|
||||
} else {
|
||||
piece.style.bottom = '2px';
|
||||
piece.style.right = '2px';
|
||||
}
|
||||
|
||||
cell.appendChild(piece);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function roll() {
|
||||
if (rolling || gameEnded) return;
|
||||
rolling = true;
|
||||
|
||||
const dice = document.getElementById('dice');
|
||||
dice.classList.add('rolling');
|
||||
|
||||
// Zufällige Zahlen zeigen während des Rollens
|
||||
let rollInterval = setInterval(() => {
|
||||
dice.textContent = Math.floor(Math.random() * 6) + 1;
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
clearInterval(rollInterval);
|
||||
dice.classList.remove('rolling');
|
||||
|
||||
const result = Math.floor(Math.random() * 6) + 1;
|
||||
dice.textContent = result;
|
||||
|
||||
movePlayer(result);
|
||||
|
||||
setTimeout(() => {
|
||||
rolling = false;
|
||||
}, 600);
|
||||
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function movePlayer(rollNum) {
|
||||
const playerName = currentPlayer === 0 ? '🔴 Spieler 1' : '🔵 Spieler 2';
|
||||
let currentPos = positions[currentPlayer];
|
||||
let newPos = currentPos + rollNum;
|
||||
|
||||
const status = document.getElementById('status');
|
||||
|
||||
// Über 100?
|
||||
if (newPos > 100) {
|
||||
status.textContent = playerName + ' muss genau auf 100 kommen! Bleibt auf ' + currentPos;
|
||||
speak(playerName + ' bleibt auf ' + currentPos);
|
||||
switchPlayer();
|
||||
return;
|
||||
}
|
||||
|
||||
// Bewegen
|
||||
status.textContent = playerName + ' würfelt ' + rollNum + ' und geht zu Feld ' + newPos;
|
||||
speak(playerName.replace('🔴', '').replace('🔵', '') + ' zieht auf Feld ' + newPos);
|
||||
|
||||
positions[currentPlayer] = newPos;
|
||||
updatePieces();
|
||||
|
||||
// Prüfen auf Leiter oder Schlange
|
||||
setTimeout(() => {
|
||||
checkSpecial(newPos);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function checkSpecial(pos) {
|
||||
const status = document.getElementById('status');
|
||||
const playerName = currentPlayer === 0 ? '🔴 Spieler 1' : '🔵 Spieler 2';
|
||||
|
||||
// Leiter?
|
||||
if (LADDERS[pos]) {
|
||||
const target = LADDERS[pos];
|
||||
positions[currentPlayer] = target;
|
||||
status.innerHTML = '🎉 ' + playerName + ' findet eine 🪜 Leiter! Hoch zu Feld ' + target + '!';
|
||||
speak('Leiter! Hoch zu Feld ' + target);
|
||||
updatePieces();
|
||||
|
||||
checkWin();
|
||||
}
|
||||
// Schlange?
|
||||
else if (SNAKES[pos]) {
|
||||
const target = SNAKES[pos];
|
||||
positions[currentPlayer] = target;
|
||||
status.innerHTML = '😱 ' + playerName + ' trifft auf eine 🐍 Schlange! Runter zu Feld ' + target + '!';
|
||||
speak('Schlange! Runter zu Feld ' + target);
|
||||
updatePieces();
|
||||
|
||||
checkWin();
|
||||
}
|
||||
// Gewonnen?
|
||||
else if (pos === 100) {
|
||||
checkWin();
|
||||
}
|
||||
else {
|
||||
switchPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
function checkWin() {
|
||||
if (positions[currentPlayer] === 100) {
|
||||
gameEnded = true;
|
||||
const playerName = currentPlayer === 0 ? '🔴 Spieler 1' : '🔵 Spieler 2';
|
||||
|
||||
document.getElementById('winnerText').innerHTML = playerName + '<br><small>hat gewonnen! 🏆</small>';
|
||||
document.getElementById('winner').style.display = 'block';
|
||||
|
||||
speak(playerName + ' hat gewonnen!');
|
||||
} else {
|
||||
switchPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
function switchPlayer() {
|
||||
currentPlayer = currentPlayer === 0 ? 1 : 0;
|
||||
|
||||
const indicator = document.getElementById('turnIndicator');
|
||||
if (currentPlayer === 0) {
|
||||
indicator.innerHTML = '🔴 Spieler 1 ist dran!';
|
||||
} else {
|
||||
indicator.innerHTML = '🔵 Spieler 2 ist dran!';
|
||||
}
|
||||
}
|
||||
|
||||
function resetGame() {
|
||||
positions = [1, 1];
|
||||
currentPlayer = 0;
|
||||
gameEnded = false;
|
||||
rolling = false;
|
||||
|
||||
document.getElementById('dice').textContent = '🎲';
|
||||
document.getElementById('status').textContent = 'Würfle und klettere hoch! Aber pass auf vor den Schlangen! 🐍';
|
||||
document.getElementById('turnIndicator').innerHTML = '🔴 Spieler 1 ist dran!';
|
||||
document.getElementById('winner').style.display = 'none';
|
||||
|
||||
initBoard();
|
||||
}
|
||||
|
||||
function speak(text) {
|
||||
if ('speechSynthesis' in window) {
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.lang = 'de-DE';
|
||||
speechSynthesis.speak(utterance);
|
||||
}
|
||||
}
|
||||
|
||||
// Init
|
||||
initBoard();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,661 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Mensch ärgere dich nicht - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #FEF3C7 0%, #FDE68A 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 10px;
|
||||
}
|
||||
h1 { color: #92400E; margin-bottom: 5px; font-size: 22px; text-align: center; }
|
||||
|
||||
.setup-screen {
|
||||
background: white; padding: 20px; border-radius: 15px; max-width: 400px;
|
||||
width: 95%; box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
}
|
||||
.setup-screen h2 { color: #333; margin-bottom: 15px; text-align: center; font-size: 20px; }
|
||||
|
||||
.player-setup { margin: 12px 0; }
|
||||
.player-label {
|
||||
display: flex; align-items: center; padding: 8px 12px; border-radius: 8px;
|
||||
font-weight: bold; margin-bottom: 5px; font-size: 14px;
|
||||
}
|
||||
.player-label.red { background: #FEE2E2; color: #991B1B; }
|
||||
.player-label.blue { background: #DBEAFE; color: #1E40AF; }
|
||||
.player-label.green { background: #D1FAE5; color: #065F46; }
|
||||
.player-label.yellow { background: #FEF3C7; color: #92400E; }
|
||||
|
||||
.player-setup input[type="text"] {
|
||||
width: 100%; padding: 10px; border: 2px solid #E5E7EB; border-radius: 8px;
|
||||
font-family: inherit; font-size: 14px;
|
||||
}
|
||||
.player-setup input.name-red { border-color: #EF4444; }
|
||||
.player-setup input.name-blue { border-color: #3B82F6; }
|
||||
.player-setup input.name-green { border-color: #10B981; }
|
||||
.player-setup input.name-yellow { border-color: #F59E0B; }
|
||||
|
||||
.checkbox-wrapper { display: flex; align-items: center; gap: 8px; margin-top: 5px; font-size: 13px; }
|
||||
.checkbox-wrapper input { width: 18px; height: 18px; }
|
||||
|
||||
.rules-check {
|
||||
background: #F3F4F6; padding: 12px; border-radius: 8px; margin: 15px 0;
|
||||
}
|
||||
.rules-check label { display: flex; align-items: center; gap: 8px; font-size: 14px; cursor: pointer; }
|
||||
|
||||
.btn {
|
||||
background: #10B981; color: white; border: none; border-radius: 10px;
|
||||
padding: 12px 30px; font-size: 16px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
width: 100%; margin-top: 10px;
|
||||
}
|
||||
.btn:hover { background: #059669; }
|
||||
.btn-back { background: #EF4444; text-decoration: none; display: inline-block; text-align: center; }
|
||||
|
||||
/* Spielbrett */
|
||||
.game-container { display: none; flex-direction: column; align-items: center; }
|
||||
|
||||
.board {
|
||||
display: grid; grid-template-columns: repeat(11, 32px); gap: 1px;
|
||||
background: #D1D5DB; padding: 8px; border-radius: 10px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.cell {
|
||||
width: 32px; height: 32px; background: white; border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
position: relative; font-size: 10px;
|
||||
}
|
||||
|
||||
/* Home-Bereiche (Ecken) */
|
||||
.home-red { background: #FEE2E2; }
|
||||
.home-blue { background: #DBEAFE; }
|
||||
.home-green { background: #D1FAE5; }
|
||||
.home-yellow { background: #FEF3C7; }
|
||||
|
||||
/* Ziel-Bereiche */
|
||||
.goal-red { background: #FECACA; }
|
||||
.goal-blue { background: #BFDBFE; }
|
||||
.goal-green { background: #A7F3D0; }
|
||||
.goal-yellow { background: #FDE68A; }
|
||||
|
||||
/* Startfelder */
|
||||
.start-red { background: #EF4444; }
|
||||
.start-blue { background: #3B82F6; }
|
||||
.start-green { background: #10B981; }
|
||||
.start-yellow { background: #F59E0B; }
|
||||
|
||||
/* Figuren */
|
||||
.piece {
|
||||
width: 26px; height: 26px; border-radius: 50%; position: absolute;
|
||||
border: 2px solid white; box-shadow: 2px 2px 4px rgba(0,0,0,0.4);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 11px; font-weight: bold; color: white; cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.piece:hover { transform: scale(1.2); }
|
||||
.piece.red { background: #DC2626; }
|
||||
.piece.blue { background: #2563EB; }
|
||||
.piece.green { background: #059669; }
|
||||
.piece.yellow { background: #D97706; color: white; }
|
||||
|
||||
.piece.selected { box-shadow: 0 0 0 3px #FFD700, 0 0 15px #FFD700; }
|
||||
.piece.movable { animation: pulse 1s infinite; }
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.1); }
|
||||
}
|
||||
|
||||
/* Würfel */
|
||||
.dice-area { margin: 15px 0; }
|
||||
.dice {
|
||||
width: 70px; height: 70px; background: white; border-radius: 12px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 40px; cursor: pointer; border: 3px solid #374151;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
||||
}
|
||||
.dice.rolling { animation: shake 0.5s ease-in-out; }
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: rotate(0deg); }
|
||||
25% { transform: rotate(-10deg); }
|
||||
75% { transform: rotate(10deg); }
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
background: white; padding: 12px 20px; border-radius: 25px;
|
||||
margin: 10px 0; font-size: 15px; text-align: center;
|
||||
box-shadow: 0 3px 10px rgba(0,0,0,0.1); max-width: 350px;
|
||||
}
|
||||
.current-player { font-weight: bold; font-size: 17px; }
|
||||
|
||||
.throw-indicator {
|
||||
color: #059669; font-weight: bold; margin-top: 5px;
|
||||
}
|
||||
|
||||
.controls { display: flex; gap: 10px; margin-top: 15px; flex-wrap: wrap; justify-content: center; }
|
||||
.game-btn {
|
||||
padding: 10px 20px; border: none; border-radius: 8px;
|
||||
font-size: 14px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-roll { background: #10B981; color: white; }
|
||||
.btn-skip { background: #6B7280; color: white; }
|
||||
|
||||
.game-over {
|
||||
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
||||
background: white; padding: 30px; border-radius: 20px; text-align: center;
|
||||
display: none; z-index: 100; box-shadow: 0 10px 50px rgba(0,0,0,0.5);
|
||||
}
|
||||
.game-over h2 { margin-bottom: 15px; color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🎲 Mensch ärgere dich nicht</h1>
|
||||
|
||||
<!-- Setup -->
|
||||
<div class="setup-screen" id="setupScreen">
|
||||
<h2>👥 Spieler einrichten</h2>
|
||||
|
||||
<div class="player-setup">
|
||||
<div class="player-label red">🔴 Spieler 1 (Rot)</div>
|
||||
<input type="text" id="name1" class="name-red" placeholder="Name..." value="Peter">
|
||||
</div>
|
||||
|
||||
<div class="player-setup">
|
||||
<div class="player-label blue">🔵 Spieler 2 (Blau)</div>
|
||||
<input type="text" id="name2" class="name-blue" placeholder="Name..." value="Anna">
|
||||
<div class="checkbox-wrapper">
|
||||
<input type="checkbox" id="bot2" onchange="toggleBot(2)">
|
||||
<label for="bot2">🤖 Computer</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="player-setup">
|
||||
<div class="player-label green">🟢 Spieler 3 (Grün)</div>
|
||||
<input type="text" id="name3" class="name-green" placeholder="Name...">
|
||||
<div class="checkbox-wrapper">
|
||||
<input type="checkbox" id="bot3" onchange="toggleBot(3)">
|
||||
<label for="bot3">🤖 Computer</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="player-setup">
|
||||
<div class="player-label yellow">🟡 Spieler 4 (Gelb)</div>
|
||||
<input type="text" id="name4" class="name-yellow" placeholder="Name...">
|
||||
<div class="checkbox-wrapper">
|
||||
<input type="checkbox" id="bot4" onchange="toggleBot(4)">
|
||||
<label for="bot4">🤖 Computer</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rules-check">
|
||||
<label>
|
||||
<input type="checkbox" id="forceKick" checked>
|
||||
<span>🎯 Schmeißen ist Pflicht</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button class="btn" onclick="startGame()">🎮 Spiel starten</button>
|
||||
<a href="../index.html" class="btn btn-back" style="margin-top:10px;display:block">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<!-- Spiel -->
|
||||
<div class="game-container" id="gameScreen">
|
||||
<div class="status-bar" id="statusBar">
|
||||
<div class="current-player" id="currentPlayerName">Peter ist dran!</div>
|
||||
<div id="gameStatus">Würfle eine 6 zum Starten!</div>
|
||||
<div class="throw-indicator" id="throwIndicator"></div>
|
||||
</div>
|
||||
|
||||
<div class="board" id="board"></div>
|
||||
|
||||
<div class="dice-area">
|
||||
<div class="dice" id="dice" onclick="roll()">🎲</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="game-btn btn-roll" onclick="roll()">🎲 Würfeln</button>
|
||||
<button class="game-btn btn-skip" onclick="skipTurn()">⏭️ Zug beenden</button>
|
||||
<button class="game-btn" onclick="resetGame()">🔄 Neustart</button>
|
||||
<a href="../index.html" class="game-btn btn-back">⬅️ Menü</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="game-over" id="gameOver">
|
||||
<h2 id="winnerText">🎉 Gewinner!</h2>
|
||||
<button class="btn" onclick="resetGame()" style="width:auto">🔄 Nochmal</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Spielbrett-Layout (11x11 Grid)
|
||||
// Positionen: -4 bis -1 = Home, 0-39 = Weg, 40-43 = Ziel
|
||||
const PLAYERS = [
|
||||
{ color: 'red', name: 'Rot', emoji: '🔴', startField: 0 },
|
||||
{ color: 'blue', name: 'Blau', emoji: '🔵', startField: 10 },
|
||||
{ color: 'green', name: 'Grün', emoji: '🟢', startField: 20 },
|
||||
{ color: 'yellow', name: 'Gelb', emoji: '🟡', startField: 30 }
|
||||
];
|
||||
|
||||
let players = []; // { name, color, isBot, pieces: [pos, pos, pos, pos], finished }
|
||||
let currentPlayer = 0;
|
||||
let currentRoll = 0;
|
||||
let throwsLeft = 0; // 3 Würfe wenn alle im Start
|
||||
let selectedPiece = null;
|
||||
let gameRunning = false;
|
||||
let forceKickRule = true;
|
||||
let hasRolled = false;
|
||||
|
||||
function toggleBot(num) {
|
||||
const checkbox = document.getElementById('bot' + num);
|
||||
const input = document.getElementById('name' + num);
|
||||
if (checkbox.checked) {
|
||||
input.value = '🤖 Computer ' + (num-1);
|
||||
input.disabled = true;
|
||||
} else {
|
||||
input.value = '';
|
||||
input.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
forceKickRule = document.getElementById('forceKick').checked;
|
||||
|
||||
players = [];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const name = document.getElementById('name' + (i+1)).value.trim();
|
||||
if (name) {
|
||||
players.push({
|
||||
id: i,
|
||||
name: name,
|
||||
color: PLAYERS[i].color,
|
||||
isBot: document.getElementById('bot' + (i+1))?.checked || false,
|
||||
pieces: [-1, -1, -1, -1], // -1 = Home, 0-39 = Weg, 40-43 = Ziel
|
||||
finished: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (players.length < 2) {
|
||||
alert('Mindestens 2 Spieler!');
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('setupScreen').style.display = 'none';
|
||||
document.getElementById('gameScreen').style.display = 'flex';
|
||||
|
||||
initBoard();
|
||||
currentPlayer = 0;
|
||||
throwsLeft = calculateThrows();
|
||||
updateStatus();
|
||||
gameRunning = true;
|
||||
|
||||
if (players[0].isBot) setTimeout(botTurn, 1000);
|
||||
}
|
||||
|
||||
function calculateThrows() {
|
||||
const p = players[currentPlayer];
|
||||
const inStart = p.pieces.filter(pos => pos === -1).length;
|
||||
return (inStart === 4) ? 3 : 1;
|
||||
}
|
||||
|
||||
function initBoard() {
|
||||
const board = document.getElementById('board');
|
||||
board.innerHTML = '';
|
||||
|
||||
// 11x11 Grid erstellen
|
||||
for (let row = 0; row < 11; row++) {
|
||||
for (let col = 0; col < 11; col++) {
|
||||
const cell = document.createElement('div');
|
||||
cell.className = 'cell';
|
||||
cell.dataset.row = row;
|
||||
cell.dataset.col = col;
|
||||
|
||||
// Home-Bereiche (Ecken)
|
||||
if (row < 4 && col < 4) {
|
||||
cell.classList.add('home-red');
|
||||
cell.dataset.home = '0';
|
||||
} else if (row < 4 && col > 6) {
|
||||
cell.classList.add('home-blue');
|
||||
cell.dataset.home = '1';
|
||||
} else if (row > 6 && col < 4) {
|
||||
cell.classList.add('home-green');
|
||||
cell.dataset.home = '2';
|
||||
} else if (row > 6 && col > 6) {
|
||||
cell.classList.add('home-yellow');
|
||||
cell.dataset.home = '3';
|
||||
}
|
||||
// Ziel-Bereiche
|
||||
else if (row === 5 && col === 5) {
|
||||
cell.style.background = '#FEF3C7'; // Mitte
|
||||
}
|
||||
else if (row === 5 && col >= 1 && col <= 4) {
|
||||
cell.classList.add('goal-red');
|
||||
}
|
||||
else if (col === 5 && row >= 1 && row <= 4) {
|
||||
cell.classList.add('goal-blue');
|
||||
}
|
||||
else if (row === 5 && col >= 6 && col <= 9) {
|
||||
cell.classList.add('goal-green');
|
||||
}
|
||||
else if (col === 5 && row >= 6 && row <= 9) {
|
||||
cell.classList.add('goal-yellow');
|
||||
}
|
||||
// Weg mit Startfeldern
|
||||
else if (isPath(row, col)) {
|
||||
const pathIdx = getPathIndex(row, col);
|
||||
if (pathIdx !== null) {
|
||||
// Startfelder markieren
|
||||
if (pathIdx === 0) cell.classList.add('start-red');
|
||||
else if (pathIdx === 10) cell.classList.add('start-blue');
|
||||
else if (pathIdx === 20) cell.classList.add('start-green');
|
||||
else if (pathIdx === 30) cell.classList.add('start-yellow');
|
||||
cell.dataset.path = pathIdx;
|
||||
}
|
||||
}
|
||||
|
||||
board.appendChild(cell);
|
||||
}
|
||||
}
|
||||
|
||||
updatePieces();
|
||||
}
|
||||
|
||||
function isPath(row, col) {
|
||||
// Äußerer Ring + Verbindungen
|
||||
return (row === 0 || row === 10 || col === 0 || col === 10 ||
|
||||
(row === 4 || row === 6) && col >= 4 && col <= 6 ||
|
||||
(col === 4 || col === 6) && row >= 4 && row <= 6);
|
||||
}
|
||||
|
||||
function getPathIndex(row, col) {
|
||||
// Vereinfachte Pfad-Berechnung
|
||||
const paths = [
|
||||
[4,0], [4,1], [4,2], [4,3], [4,4], // Rot Start
|
||||
[3,4], [2,4], [1,4], [0,4], [0,5], [0,6], // Oben
|
||||
[1,6], [2,6], [3,6], [4,6], // Blau Start
|
||||
[4,7], [4,8], [4,9], [4,10], [5,10], [6,10], // Rechts
|
||||
[6,9], [6,8], [6,7], [6,6], // Grün Start
|
||||
[7,6], [8,6], [9,6], [10,6], [10,5], [10,4], // Unten
|
||||
[9,4], [8,4], [7,4], [6,4], // Gelb Start
|
||||
[6,3], [6,2], [6,1], [6,0], [5,0], // Links
|
||||
[5,1], [5,2], [5,3] // Ziel rot
|
||||
];
|
||||
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
if (paths[i][0] === row && paths[i][1] === col) return i;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function updatePieces() {
|
||||
document.querySelectorAll('.piece').forEach(p => p.remove());
|
||||
|
||||
players.forEach((player, pIdx) => {
|
||||
player.pieces.forEach((pos, pieceIdx) => {
|
||||
let cell;
|
||||
|
||||
if (pos === -1) {
|
||||
// Home - finde freie Position
|
||||
const homeCells = document.querySelectorAll(`[data-home="${pIdx}"]`);
|
||||
cell = homeCells[pieceIdx] || homeCells[0];
|
||||
} else if (pos >= 0 && pos < 40) {
|
||||
// Auf dem Weg
|
||||
const pathCells = document.querySelectorAll('[data-path]');
|
||||
// Position relativ zum Startfeld
|
||||
const playerStart = PLAYERS[pIdx].startField;
|
||||
const adjustedPos = (pos + playerStart) % 40;
|
||||
pathCells.forEach(pc => {
|
||||
if (parseInt(pc.dataset.path) === adjustedPos) cell = pc;
|
||||
});
|
||||
} else if (pos >= 40) {
|
||||
// Im Ziel
|
||||
const goalPos = pos - 40;
|
||||
const goalCells = document.querySelectorAll(`[class*="goal-${player.color}"]`);
|
||||
cell = goalCells[goalPos];
|
||||
}
|
||||
|
||||
if (cell) {
|
||||
const piece = document.createElement('div');
|
||||
piece.className = `piece ${player.color}`;
|
||||
piece.textContent = pieceIdx + 1;
|
||||
piece.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
selectPiece(pIdx, pieceIdx);
|
||||
};
|
||||
|
||||
// Markieren wenn bewegbar
|
||||
if (pIdx === currentPlayer && hasRolled && canMovePiece(pIdx, pieceIdx)) {
|
||||
piece.classList.add('movable');
|
||||
}
|
||||
|
||||
cell.appendChild(piece);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function roll() {
|
||||
if (!gameRunning || hasRolled) return;
|
||||
|
||||
const dice = document.getElementById('dice');
|
||||
dice.classList.add('rolling');
|
||||
|
||||
setTimeout(() => {
|
||||
dice.classList.remove('rolling');
|
||||
currentRoll = Math.floor(Math.random() * 6) + 1;
|
||||
dice.textContent = ['⚀','⚁','⚂','⚃','⚄','⚅'][currentRoll-1];
|
||||
|
||||
hasRolled = true;
|
||||
handleRoll(currentRoll);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleRoll(roll) {
|
||||
const player = players[currentPlayer];
|
||||
const inStart = player.pieces.filter(p => p === -1).length;
|
||||
|
||||
document.getElementById('gameStatus').textContent =
|
||||
`${player.name} würfelt ${roll}!`;
|
||||
|
||||
throwsLeft--;
|
||||
|
||||
// Prüfen ob Zug möglich
|
||||
const movable = player.pieces.map((pos, idx) => ({ pos, idx }))
|
||||
.filter(({ pos, idx }) => canMove(player, idx, roll));
|
||||
|
||||
if (movable.length === 0) {
|
||||
if (throwsLeft > 0) {
|
||||
document.getElementById('gameStatus').textContent +=
|
||||
` Kein Zug möglich. Noch ${throwsLeft} Versuch(e).`;
|
||||
hasRolled = false;
|
||||
} else {
|
||||
document.getElementById('gameStatus').textContent += ' Kein Zug möglich.';
|
||||
setTimeout(nextPlayer, 1500);
|
||||
}
|
||||
} else {
|
||||
document.getElementById('gameStatus').textContent +=
|
||||
` Wähle eine Figur!`;
|
||||
updatePieces(); // Markiere bewegliche
|
||||
|
||||
if (player.isBot) {
|
||||
setTimeout(() => botSelectPiece(movable), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
updateThrowIndicator();
|
||||
}
|
||||
|
||||
function canMove(player, pieceIdx, roll) {
|
||||
const pos = player.pieces[pieceIdx];
|
||||
|
||||
if (pos === -1) {
|
||||
// Starten nur mit 6
|
||||
return roll === 6;
|
||||
} else if (pos >= 0 && pos < 40) {
|
||||
const newPos = pos + roll;
|
||||
// Kann ins Ziel oder weiter
|
||||
if (newPos <= 43) return true;
|
||||
// Prüfen ob exakt ins Ziel
|
||||
const goalEntry = 40 + (player.id * 4); // vereinfacht
|
||||
return newPos <= goalEntry + 3;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function selectPiece(playerIdx, pieceIdx) {
|
||||
if (playerIdx !== currentPlayer || !hasRolled) return;
|
||||
|
||||
const player = players[currentPlayer];
|
||||
if (!canMove(player, pieceIdx, currentRoll)) {
|
||||
speak('Diese Figur kann nicht bewegt werden!');
|
||||
return;
|
||||
}
|
||||
|
||||
movePiece(pieceIdx);
|
||||
}
|
||||
|
||||
function movePiece(pieceIdx) {
|
||||
const player = players[currentPlayer];
|
||||
const oldPos = player.pieces[pieceIdx];
|
||||
let newPos;
|
||||
|
||||
if (oldPos === -1) {
|
||||
// Starten
|
||||
newPos = 0;
|
||||
} else {
|
||||
newPos = oldPos + currentRoll;
|
||||
}
|
||||
|
||||
player.pieces[pieceIdx] = newPos;
|
||||
|
||||
// Schmeißen?
|
||||
if (forceKickRule) {
|
||||
checkKick(player, newPos, pieceIdx);
|
||||
}
|
||||
|
||||
// Gewonnen?
|
||||
const inGoal = player.pieces.filter(p => p >= 40).length;
|
||||
if (inGoal === 4) {
|
||||
endGame(player);
|
||||
return;
|
||||
}
|
||||
|
||||
updatePieces();
|
||||
|
||||
if (currentRoll === 6) {
|
||||
throwsLeft = calculateThrows();
|
||||
hasRolled = false;
|
||||
document.getElementById('gameStatus').textContent = 'Nochmal würfeln!';
|
||||
} else {
|
||||
setTimeout(nextPlayer, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function checkKick(player, pos, pieceIdx) {
|
||||
if (pos >= 40) return; // Im Ziel nicht schmeißen
|
||||
|
||||
players.forEach((other, idx) => {
|
||||
if (idx === currentPlayer) return;
|
||||
|
||||
other.pieces.forEach((otherPos, otherIdx) => {
|
||||
if (otherPos === pos && otherPos >= 0 && otherPos < 40) {
|
||||
other.pieces[otherIdx] = -1;
|
||||
speak(`${player.name} schmeißt ${other.name} raus!`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function botSelectPiece(movable) {
|
||||
// Priorität: Kann schmeißen > ins Ziel > vorne
|
||||
let best = movable[0];
|
||||
|
||||
movable.forEach(({ pos, idx }) => {
|
||||
const newPos = pos === -1 ? 0 : pos + currentRoll;
|
||||
|
||||
// Kann schmeißen?
|
||||
players.forEach((other, oIdx) => {
|
||||
if (oIdx === currentPlayer) return;
|
||||
if (other.pieces.includes(newPos)) {
|
||||
best = { pos, idx };
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
movePiece(best.idx);
|
||||
}
|
||||
|
||||
function skipTurn() {
|
||||
if (!gameRunning) return;
|
||||
nextPlayer();
|
||||
}
|
||||
|
||||
function nextPlayer() {
|
||||
currentPlayer = (currentPlayer + 1) % players.length;
|
||||
currentRoll = 0;
|
||||
hasRolled = false;
|
||||
throwsLeft = calculateThrows();
|
||||
selectedPiece = null;
|
||||
|
||||
updateStatus();
|
||||
updatePieces();
|
||||
|
||||
if (players[currentPlayer].isBot) {
|
||||
setTimeout(botTurn, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function botTurn() {
|
||||
if (!gameRunning) return;
|
||||
roll();
|
||||
}
|
||||
|
||||
function updateStatus() {
|
||||
const p = players[currentPlayer];
|
||||
document.getElementById('currentPlayerName').textContent =
|
||||
`${p.emoji} ${p.name} ist dran!`;
|
||||
document.getElementById('currentPlayerName').style.color =
|
||||
p.color === 'red' ? '#DC2626' :
|
||||
p.color === 'blue' ? '#2563EB' :
|
||||
p.color === 'green' ? '#059669' : '#D97706';
|
||||
|
||||
throwsLeft = calculateThrows();
|
||||
updateThrowIndicator();
|
||||
}
|
||||
|
||||
function updateThrowIndicator() {
|
||||
const indicator = document.getElementById('throwIndicator');
|
||||
if (throwsLeft > 1) {
|
||||
indicator.textContent = `🎲 ${throwsLeft} Würfe verbleibend`;
|
||||
} else if (throwsLeft === 1) {
|
||||
indicator.textContent = `🎲 Letzter Wurf`;
|
||||
} else {
|
||||
indicator.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
function endGame(winner) {
|
||||
gameRunning = false;
|
||||
document.getElementById('winnerText').innerHTML =
|
||||
`🎉 ${winner.emoji} ${winner.name} hat gewonnen!`;
|
||||
document.getElementById('gameOver').style.display = 'block';
|
||||
}
|
||||
|
||||
function resetGame() {
|
||||
document.getElementById('gameScreen').style.display = 'none';
|
||||
document.getElementById('setupScreen').style.display = 'block';
|
||||
document.getElementById('gameOver').style.display = 'none';
|
||||
gameRunning = false;
|
||||
}
|
||||
|
||||
function speak(text) {
|
||||
if ('speechSynthesis' in window) {
|
||||
const u = new SpeechSynthesisUtterance(text);
|
||||
u.lang = 'de-DE';
|
||||
speechSynthesis.speak(u);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,217 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Malen - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 10px;
|
||||
}
|
||||
h1 { color: white; margin: 10px 0; font-size: 24px; }
|
||||
.canvas-container {
|
||||
background: white; border-radius: 10px; padding: 10px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||||
}
|
||||
#paintCanvas {
|
||||
background: white; border: 2px solid #ddd; border-radius: 5px;
|
||||
cursor: crosshair; touch-action: none;
|
||||
}
|
||||
.tools {
|
||||
display: flex; flex-wrap: wrap; justify-content: center; gap: 10px;
|
||||
margin: 15px 0; max-width: 600px;
|
||||
}
|
||||
.color-btn {
|
||||
width: 50px; height: 50px; border: 3px solid white; border-radius: 50%;
|
||||
cursor: pointer; box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.color-btn:hover { transform: scale(1.1); }
|
||||
.color-btn.active {
|
||||
box-shadow: 0 0 0 4px #4ade80, 0 2px 5px rgba(0,0,0,0.2);
|
||||
transform: scale(1.15);
|
||||
}
|
||||
.size-slider {
|
||||
-webkit-appearance: none; width: 150px; height: 10px;
|
||||
background: rgba(255,255,255,0.3); border-radius: 5px; outline: none;
|
||||
}
|
||||
.size-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none; width: 25px; height: 25px; background: white;
|
||||
border-radius: 50%; cursor: pointer;
|
||||
}
|
||||
.tool-btn {
|
||||
background: white; border: none; border-radius: 10px;
|
||||
padding: 10px 20px; font-size: 20px; cursor: pointer;
|
||||
}
|
||||
.tool-btn.active { background: #4ade80; }
|
||||
.controls { display: flex; gap: 15px; margin-top: 10px; flex-wrap: wrap; justify-content: center; }
|
||||
.btn {
|
||||
background: linear-gradient(135deg, #4ade80, #22c55e); border: none; border-radius: 10px;
|
||||
padding: 12px 25px; font-size: 16px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
.btn-back { background: #ff6b6b; text-decoration: none; }
|
||||
.btn-clear { background: #feca57; }
|
||||
.size-label { color: white; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🎨 Malen</h1>
|
||||
|
||||
<div class="canvas-container">
|
||||
<canvas id="paintCanvas" width="600" height="400"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="tools">
|
||||
<button class="color-btn active" style="background: #000000;" onclick="setColor('#000000')" title="Schwarz"></button>
|
||||
<button class="color-btn" style="background: #ff0000;" onclick="setColor('#ff0000')" title="Rot"></button>
|
||||
<button class="color-btn" style="background: #00ff00;" onclick="setColor('#00ff00')" title="Grün"></button>
|
||||
<button class="color-btn" style="background: #0000ff;" onclick="setColor('#0000ff')" title="Blau"></button>
|
||||
<button class="color-btn" style="background: #ffff00;" onclick="setColor('#ffff00')" title="Gelb"></button>
|
||||
<button class="color-btn" style="background: #ff00ff;" onclick="setColor('#ff00ff')" title="Magenta"></button>
|
||||
<button class="color-btn" style="background: #00ffff;" onclick="setColor('#00ffff')" title="Cyan"></button>
|
||||
<button class="color-btn" style="background: #ffa500;" onclick="setColor('#ffa500')" title="Orange"></button>
|
||||
<button class="color-btn" style="background: #800080;" onclick="setColor('#800080')" title="Lila"></button>
|
||||
<button class="color-btn" style="background: #ffffff; border: 2px solid #ccc;" onclick="setColor('#ffffff')" title="Weiß"></button>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 15px; flex-wrap: wrap; justify-content: center;">
|
||||
<div class="size-label">Pinselgröße:</div>
|
||||
<input type="range" class="size-slider" id="brushSize" min="1" max="50" value="5">
|
||||
<button class="tool-btn active" id="brushBtn" onclick="setTool('brush')">🖌️ Pinsel</button>
|
||||
<button class="tool-btn" id="eraserBtn" onclick="setTool('eraser')">🧹 Radiergummi</button>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn btn-clear" onclick="clearCanvas()">🗑️ Alles löschen</button>
|
||||
<button class="btn" onclick="saveImage()">💾 Speichern</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const canvas = document.getElementById('paintCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Responsive canvas
|
||||
function resizeCanvas() {
|
||||
const maxWidth = Math.min(window.innerWidth - 40, 800);
|
||||
const scale = maxWidth / 600;
|
||||
canvas.style.width = maxWidth + 'px';
|
||||
canvas.style.height = (400 * scale) + 'px';
|
||||
}
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
// Drawing state
|
||||
let isDrawing = false;
|
||||
let currentColor = '#000000';
|
||||
let currentSize = 5;
|
||||
let currentTool = 'brush';
|
||||
let lastX = 0;
|
||||
let lastY = 0;
|
||||
|
||||
// Set canvas background to white
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
function getMousePos(e) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const scaleX = canvas.width / rect.width;
|
||||
const scaleY = canvas.height / rect.height;
|
||||
|
||||
if (e.touches) {
|
||||
return {
|
||||
x: (e.touches[0].clientX - rect.left) * scaleX,
|
||||
y: (e.touches[0].clientY - rect.top) * scaleY
|
||||
};
|
||||
}
|
||||
return {
|
||||
x: (e.clientX - rect.left) * scaleX,
|
||||
y: (e.clientY - rect.top) * scaleY
|
||||
};
|
||||
}
|
||||
|
||||
function startDrawing(e) {
|
||||
isDrawing = true;
|
||||
const pos = getMousePos(e);
|
||||
lastX = pos.x;
|
||||
lastY = pos.y;
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function draw(e) {
|
||||
if (!isDrawing) return;
|
||||
|
||||
const pos = getMousePos(e);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(lastX, lastY);
|
||||
ctx.lineTo(pos.x, pos.y);
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.lineWidth = currentSize;
|
||||
|
||||
if (currentTool === 'eraser') {
|
||||
ctx.strokeStyle = '#ffffff';
|
||||
} else {
|
||||
ctx.strokeStyle = currentColor;
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
|
||||
lastX = pos.x;
|
||||
lastY = pos.y;
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function stopDrawing() {
|
||||
isDrawing = false;
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
canvas.addEventListener('mousedown', startDrawing);
|
||||
canvas.addEventListener('mousemove', draw);
|
||||
canvas.addEventListener('mouseup', stopDrawing);
|
||||
canvas.addEventListener('mouseout', stopDrawing);
|
||||
|
||||
canvas.addEventListener('touchstart', startDrawing);
|
||||
canvas.addEventListener('touchmove', draw);
|
||||
canvas.addEventListener('touchend', stopDrawing);
|
||||
|
||||
// Tools
|
||||
function setColor(color) {
|
||||
currentColor = color;
|
||||
document.querySelectorAll('.color-btn').forEach(btn => btn.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
setTool('brush');
|
||||
}
|
||||
|
||||
function setTool(tool) {
|
||||
currentTool = tool;
|
||||
document.getElementById('brushBtn').classList.toggle('active', tool === 'brush');
|
||||
document.getElementById('eraserBtn').classList.toggle('active', tool === 'eraser');
|
||||
}
|
||||
|
||||
document.getElementById('brushSize').addEventListener('input', (e) => {
|
||||
currentSize = e.target.value;
|
||||
});
|
||||
|
||||
function clearCanvas() {
|
||||
if (confirm('Möchtest du wirklich alles löschen?')) {
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
}
|
||||
|
||||
function saveImage() {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'malerei-' + new Date().toISOString().slice(0, 10) + '.png';
|
||||
link.href = canvas.toDataURL();
|
||||
link.click();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,164 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Memory - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 10px;
|
||||
}
|
||||
h1 { color: white; margin: 10px 0; font-size: 24px; }
|
||||
.info { color: white; font-size: 16px; margin-bottom: 10px; }
|
||||
.level-select { margin-bottom: 15px; }
|
||||
.level-select button {
|
||||
background: white; border: none; border-radius: 8px; padding: 8px 15px;
|
||||
margin: 0 5px; font-size: 14px; cursor: pointer; font-family: inherit;
|
||||
}
|
||||
.level-select button.active { background: #4ade80; color: white; }
|
||||
#gameBoard {
|
||||
display: grid; gap: 10px; margin: 10px 0;
|
||||
}
|
||||
.card {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
border-radius: 10px; cursor: pointer; display: flex; justify-content: center; align-items: center;
|
||||
font-size: 40px; transition: transform 0.3s; box-shadow: 0 4px 10px rgba(0,0,0,0.2);
|
||||
}
|
||||
.card:hover { transform: scale(1.05); }
|
||||
.card.flipped { background: white; }
|
||||
.card.matched { background: #4ade80; pointer-events: none; }
|
||||
.card.hidden { font-size: 0; }
|
||||
.controls { margin-top: 20px; display: flex; gap: 15px; }
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 12px 25px;
|
||||
font-size: 16px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🃏 Memory</h1>
|
||||
<div class="info">Versuche: <span id="moves">0</span> | Paare: <span id="pairs">0</span>/<span id="totalPairs">0</span></div>
|
||||
|
||||
<div class="level-select">
|
||||
<button onclick="setLevel(4)" id="lvl4">Leicht (4)</button>
|
||||
<button onclick="setLevel(6)" id="lvl6">Mittel (6)</button>
|
||||
<button onclick="setLevel(8)" id="lvl8">Schwer (8)</button>
|
||||
<button onclick="setLevel(12)" id="lvl12">Profis (12)</button>
|
||||
</div>
|
||||
|
||||
<div id="gameBoard"></div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn" onclick="initGame()">🔄 Neues Spiel</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const emojis = ['🐶','🐱','🐭','🐹','🐰','🦊','🐻','🐼','🐨','🐯','🦁','🐷','🐸','🐵','🐔','🐧','🐦','🐤','🦆','🦅','🦉','🦇','🐺','🐗','🐴','🦄'];
|
||||
|
||||
let cards = [];
|
||||
let flippedCards = [];
|
||||
let matchedPairs = 0;
|
||||
let moves = 0;
|
||||
let gameLocked = false;
|
||||
let currentLevel = 6;
|
||||
|
||||
function setLevel(n) {
|
||||
currentLevel = n;
|
||||
document.querySelectorAll('.level-select button').forEach(b => b.classList.remove('active'));
|
||||
document.getElementById('lvl' + n).classList.add('active');
|
||||
initGame();
|
||||
}
|
||||
|
||||
function initGame() {
|
||||
const board = document.getElementById('gameBoard');
|
||||
board.innerHTML = '';
|
||||
|
||||
// Berechne Grid
|
||||
const totalCards = currentLevel * 2;
|
||||
const cols = currentLevel <= 6 ? 4 : currentLevel <= 8 ? 4 : 6;
|
||||
const rows = Math.ceil(totalCards / cols);
|
||||
|
||||
board.style.gridTemplateColumns = `repeat(${cols}, minmax(70px, 1fr))`;
|
||||
|
||||
// Wähle Emojis
|
||||
const gameEmojis = emojis.slice(0, currentLevel);
|
||||
cards = [...gameEmojis, ...gameEmojis].sort(() => Math.random() - 0.5);
|
||||
|
||||
// Erstelle Karten
|
||||
cards.forEach((emoji, index) => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card hidden';
|
||||
card.dataset.index = index;
|
||||
card.dataset.emoji = emoji;
|
||||
card.textContent = emoji;
|
||||
card.style.height = currentLevel >= 8 ? '80px' : '100px';
|
||||
card.onclick = () => flipCard(card);
|
||||
board.appendChild(card);
|
||||
});
|
||||
|
||||
flippedCards = [];
|
||||
matchedPairs = 0;
|
||||
moves = 0;
|
||||
gameLocked = false;
|
||||
updateStats();
|
||||
document.getElementById('totalPairs').textContent = currentLevel;
|
||||
}
|
||||
|
||||
function flipCard(card) {
|
||||
if (gameLocked || card.classList.contains('flipped') || card.classList.contains('matched')) return;
|
||||
|
||||
card.classList.remove('hidden');
|
||||
card.classList.add('flipped');
|
||||
flippedCards.push(card);
|
||||
|
||||
if (flippedCards.length === 2) {
|
||||
moves++;
|
||||
updateStats();
|
||||
checkMatch();
|
||||
}
|
||||
}
|
||||
|
||||
function checkMatch() {
|
||||
gameLocked = true;
|
||||
const [card1, card2] = flippedCards;
|
||||
|
||||
if (card1.dataset.emoji === card2.dataset.emoji) {
|
||||
setTimeout(() => {
|
||||
card1.classList.add('matched');
|
||||
card2.classList.add('matched');
|
||||
matchedPairs++;
|
||||
updateStats();
|
||||
flippedCards = [];
|
||||
gameLocked = false;
|
||||
|
||||
if (matchedPairs === currentLevel) {
|
||||
setTimeout(() => alert('🎉 Gewonnen! Du hast ' + moves + ' Versuche gebraucht.'), 300);
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
card1.classList.remove('flipped');
|
||||
card1.classList.add('hidden');
|
||||
card2.classList.remove('flipped');
|
||||
card2.classList.add('hidden');
|
||||
flippedCards = [];
|
||||
gameLocked = false;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function updateStats() {
|
||||
document.getElementById('moves').textContent = moves;
|
||||
document.getElementById('pairs').textContent = matchedPairs;
|
||||
}
|
||||
|
||||
// Init
|
||||
setLevel(6);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,442 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Minesweeper - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 15px;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 10px; font-size: 24px; }
|
||||
|
||||
.difficulty-select {
|
||||
display: flex; gap: 10px; margin: 10px 0; flex-wrap: wrap; justify-content: center;
|
||||
}
|
||||
.diff-btn {
|
||||
background: rgba(255,255,255,0.2); border: 2px solid white; color: white;
|
||||
padding: 8px 15px; border-radius: 20px; cursor: pointer; font-family: inherit; font-size: 14px;
|
||||
}
|
||||
.diff-btn.active { background: white; color: #2d3748; font-weight: bold; }
|
||||
|
||||
.stats {
|
||||
display: flex; gap: 30px; margin: 10px 0; color: white;
|
||||
}
|
||||
.stat { text-align: center; }
|
||||
.stat-label { font-size: 12px; color: #aaa; }
|
||||
.stat-value { font-size: 24px; font-weight: bold; color: #feca57; }
|
||||
|
||||
.board-container {
|
||||
background: #718096; padding: 10px; border-radius: 10px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.board {
|
||||
display: grid; gap: 2px;
|
||||
}
|
||||
|
||||
.cell {
|
||||
width: 35px; height: 35px; background: #A0AEC0;
|
||||
border: 3px solid; border-color: #E2E8F0 #718096 #718096 #E2E8F0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 18px; font-weight: bold; cursor: pointer;
|
||||
transition: all 0.1s;
|
||||
}
|
||||
.cell:hover:not(.revealed):not(.flagged) {
|
||||
background: #B8C5D6;
|
||||
}
|
||||
.cell.revealed {
|
||||
background: #E2E8F0; border: 1px solid #CBD5E0;
|
||||
}
|
||||
.cell.flagged {
|
||||
background: #FC8181;
|
||||
}
|
||||
.cell.mine {
|
||||
background: #E53E3E;
|
||||
}
|
||||
.cell.exploded {
|
||||
background: #C53030;
|
||||
animation: explode 0.3s ease-out;
|
||||
}
|
||||
@keyframes explode {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.2); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
.cell.number-1 { color: #3182CE; }
|
||||
.cell.number-2 { color: #38A169; }
|
||||
.cell.number-3 { color: #E53E3E; }
|
||||
.cell.number-4 { color: #805AD5; }
|
||||
.cell.number-5 { color: #B7791F; }
|
||||
.cell.number-6 { color: #00B5D8; }
|
||||
.cell.number-7 { color: #2D3748; }
|
||||
.cell.number-8 { color: #718096; }
|
||||
|
||||
.instructions {
|
||||
color: #ccc; margin: 10px 0; text-align: center; font-size: 14px; max-width: 350px;
|
||||
}
|
||||
|
||||
.controls { display: flex; gap: 15px; margin-top: 20px; }
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 10px 25px;
|
||||
font-size: 16px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-new { background: #4ade80; color: white; }
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; }
|
||||
|
||||
.game-over {
|
||||
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
||||
background: white; padding: 30px; border-radius: 20px; text-align: center;
|
||||
display: none; z-index: 100; box-shadow: 0 10px 50px rgba(0,0,0,0.5);
|
||||
}
|
||||
.game-over h2 { margin-bottom: 15px; }
|
||||
.game-over-emoji { font-size: 60px; }
|
||||
|
||||
.smiley { font-size: 40px; margin: 10px 0; cursor: pointer; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>💣 Minesweeper</h1>
|
||||
|
||||
<div class="difficulty-select" id="diffSelect">
|
||||
<button class="diff-btn active" onclick="setDifficulty('easy')">🟢 Einfach (8×8)</button>
|
||||
<button class="diff-btn" onclick="setDifficulty('medium')">🟡 Mittel (12×12)</button>
|
||||
<button class="diff-btn" onclick="setDifficulty('hard')">🔴 Schwer (16×16)</button>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat">
|
||||
<div class="stat-label">Minen</div>
|
||||
<div class="stat-value" id="mineCount">10</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Zeit</div>
|
||||
<div class="stat-value" id="timer">000</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="smiley" id="smiley" onclick="newGame()">😊</div>
|
||||
|
||||
<div class="board-container">
|
||||
<div class="board" id="board"></div>
|
||||
</div>
|
||||
|
||||
<div class="instructions">
|
||||
🔍 <strong>Linksklick:</strong> Feld aufdecken <br>
|
||||
🚩 <strong>Rechtsklick/Langklick:></strong> Flagge setzen
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn btn-new" onclick="newGame()">🔄 Neues Spiel</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<div class="game-over" id="gameOver">
|
||||
<h2 id="gameOverTitle">💥 Boom!</h2>
|
||||
<div class="game-over-emoji" id="gameOverEmoji">💣</div>
|
||||
<div id="gameOverText" style="margin:15px 0">Du hast eine Mine getroffen!</div>
|
||||
|
||||
<button class="btn btn-new" onclick="newGame()" style="margin-top:15px">🔄 Nochmal</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const DIFFICULTY = {
|
||||
easy: { rows: 8, cols: 8, mines: 10 },
|
||||
medium: { rows: 12, cols: 12, mines: 20 },
|
||||
hard: { rows: 16, cols: 16, mines: 40 }
|
||||
};
|
||||
|
||||
let difficulty = 'easy';
|
||||
let board = [];
|
||||
let gameRunning = false;
|
||||
let timer = 0;
|
||||
let timerInterval = null;
|
||||
let revealedCount = 0;
|
||||
let flaggedCount = 0;
|
||||
let firstClick = true;
|
||||
|
||||
function setDifficulty(diff) {
|
||||
difficulty = diff;
|
||||
document.querySelectorAll('.diff-btn').forEach(b => b.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
newGame();
|
||||
}
|
||||
|
||||
function initBoard() {
|
||||
const config = DIFFICULTY[difficulty];
|
||||
const boardEl = document.getElementById('board');
|
||||
boardEl.innerHTML = '';
|
||||
boardEl.style.gridTemplateColumns = `repeat(${config.cols}, 35px)`;
|
||||
|
||||
board = [];
|
||||
for (let row = 0; row < config.rows; row++) {
|
||||
board[row] = [];
|
||||
for (let col = 0; col < config.cols; col++) {
|
||||
board[row][col] = {
|
||||
isMine: false,
|
||||
isRevealed: false,
|
||||
isFlagged: false,
|
||||
neighborMines: 0
|
||||
};
|
||||
|
||||
const cell = document.createElement('div');
|
||||
cell.className = 'cell';
|
||||
cell.dataset.row = row;
|
||||
cell.dataset.col = col;
|
||||
cell.onclick = (e) => handleClick(row, col, e);
|
||||
cell.oncontextmenu = (e) => {
|
||||
e.preventDefault();
|
||||
handleRightClick(row, col);
|
||||
};
|
||||
|
||||
// Touch-Handling für Flagge
|
||||
let touchTimer;
|
||||
cell.ontouchstart = (e) => {
|
||||
touchTimer = setTimeout(() => {
|
||||
e.preventDefault();
|
||||
handleRightClick(row, col);
|
||||
}, 500);
|
||||
};
|
||||
cell.ontouchend = () => clearTimeout(touchTimer);
|
||||
|
||||
boardEl.appendChild(cell);
|
||||
}
|
||||
}
|
||||
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
function placeMines(excludeRow, excludeCol) {
|
||||
const config = DIFFICULTY[difficulty];
|
||||
let minesPlaced = 0;
|
||||
|
||||
while (minesPlaced < config.mines) {
|
||||
const row = Math.floor(Math.random() * config.rows);
|
||||
const col = Math.floor(Math.random() * config.cols);
|
||||
|
||||
// Nicht auf dem ersten Klick
|
||||
if (row === excludeRow && col === excludeCol) continue;
|
||||
// Nicht schon eine Mine
|
||||
if (board[row][col].isMine) continue;
|
||||
|
||||
board[row][col].isMine = true;
|
||||
minesPlaced++;
|
||||
}
|
||||
|
||||
// Nachbar-Minen zählen
|
||||
for (let row = 0; row < config.rows; row++) {
|
||||
for (let col = 0; col < config.cols; col++) {
|
||||
if (!board[row][col].isMine) {
|
||||
board[row][col].neighborMines = countNeighborMines(row, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function countNeighborMines(row, col) {
|
||||
const config = DIFFICULTY[difficulty];
|
||||
let count = 0;
|
||||
|
||||
for (let dr = -1; dr <= 1; dr++) {
|
||||
for (let dc = -1; dc <= 1; dc++) {
|
||||
const newRow = row + dr;
|
||||
const newCol = col + dc;
|
||||
if (newRow >= 0 && newRow < config.rows &&
|
||||
newCol >= 0 && newCol < config.cols) {
|
||||
if (board[newRow][newCol].isMine) count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
function handleClick(row, col, event) {
|
||||
if (!gameRunning) {
|
||||
if (firstClick) {
|
||||
startGame(row, col);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const cell = board[row][col];
|
||||
if (cell.isRevealed || cell.isFlagged) return;
|
||||
|
||||
// Bei rechtem Mausklick (Ctrl+Klick)
|
||||
if (event.ctrlKey) {
|
||||
handleRightClick(row, col);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cell.isMine) {
|
||||
// Mine getroffen!
|
||||
cell.isRevealed = true;
|
||||
revealAllMines();
|
||||
gameOver(false);
|
||||
} else {
|
||||
// Feld aufdecken
|
||||
revealCell(row, col);
|
||||
|
||||
// Gewonnen?
|
||||
const config = DIFFICULTY[difficulty];
|
||||
if (revealedCount === config.rows * config.cols - config.mines) {
|
||||
gameOver(true);
|
||||
}
|
||||
}
|
||||
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
function handleRightClick(row, col) {
|
||||
if (!gameRunning) return;
|
||||
|
||||
const cell = board[row][col];
|
||||
if (cell.isRevealed) return;
|
||||
|
||||
cell.isFlagged = !cell.isFlagged;
|
||||
flaggedCount += cell.isFlagged ? 1 : -1;
|
||||
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
function revealCell(row, col) {
|
||||
const config = DIFFICULTY[difficulty];
|
||||
const cell = board[row][col];
|
||||
|
||||
if (cell.isRevealed || cell.isFlagged) return;
|
||||
|
||||
cell.isRevealed = true;
|
||||
revealedCount++;
|
||||
|
||||
// Wenn 0 Nachbarn, rekursiv aufdecken
|
||||
if (cell.neighborMines === 0) {
|
||||
for (let dr = -1; dr <= 1; dr++) {
|
||||
for (let dc = -1; dc <= 1; dc++) {
|
||||
const newRow = row + dr;
|
||||
const newCol = col + dc;
|
||||
if (newRow >= 0 && newRow < config.rows &&
|
||||
newCol >= 0 && newCol < config.cols) {
|
||||
revealCell(newRow, newCol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function revealAllMines() {
|
||||
const config = DIFFICULTY[difficulty];
|
||||
for (let row = 0; row < config.rows; row++) {
|
||||
for (let col = 0; col < config.cols; col++) {
|
||||
if (board[row][col].isMine) {
|
||||
board[row][col].isRevealed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateDisplay() {
|
||||
const config = DIFFICULTY[difficulty];
|
||||
const cells = document.querySelectorAll('.cell');
|
||||
|
||||
cells.forEach((cellEl, idx) => {
|
||||
const row = Math.floor(idx / config.cols);
|
||||
const col = idx % config.cols;
|
||||
const cell = board[row][col];
|
||||
|
||||
cellEl.className = 'cell';
|
||||
cellEl.textContent = '';
|
||||
|
||||
if (cell.isRevealed) {
|
||||
cellEl.classList.add('revealed');
|
||||
if (cell.isMine) {
|
||||
cellEl.classList.add('mine');
|
||||
cellEl.textContent = '💣';
|
||||
} else if (cell.neighborMines > 0) {
|
||||
cellEl.classList.add(`number-${cell.neighborMines}`);
|
||||
cellEl.textContent = cell.neighborMines;
|
||||
}
|
||||
} else if (cell.isFlagged) {
|
||||
cellEl.classList.add('flagged');
|
||||
cellEl.textContent = '🚩';
|
||||
}
|
||||
});
|
||||
|
||||
// Minen-Zähler
|
||||
document.getElementById('mineCount').textContent = config.mines - flaggedCount;
|
||||
}
|
||||
|
||||
function startGame(firstRow, firstCol) {
|
||||
gameRunning = true;
|
||||
firstClick = false;
|
||||
|
||||
placeMines(firstRow, firstCol);
|
||||
revealCell(firstRow, firstCol);
|
||||
|
||||
// Timer starten
|
||||
timerInterval = setInterval(() => {
|
||||
timer++;
|
||||
document.getElementById('timer').textContent = timer.toString().padStart(3, '0');
|
||||
}, 1000);
|
||||
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
function gameOver(won) {
|
||||
gameRunning = false;
|
||||
clearInterval(timerInterval);
|
||||
|
||||
const modal = document.getElementById('gameOver');
|
||||
const title = document.getElementById('gameOverTitle');
|
||||
const emoji = document.getElementById('gameOverEmoji');
|
||||
const text = document.getElementById('gameOverText');
|
||||
|
||||
if (won) {
|
||||
title.textContent = '🎉 Gewonnen!';
|
||||
emoji.textContent = '😎';
|
||||
text.textContent = `Du hast alle Minen gefunden in ${timer} Sekunden!`;
|
||||
document.getElementById('smiley').textContent = '😎';
|
||||
speak('Glückwunsch! Du hast gewonnen!');
|
||||
} else {
|
||||
title.textContent = '💥 Boom!';
|
||||
emoji.textContent = '💣';
|
||||
text.textContent = 'Du hast eine Mine getroffen!';
|
||||
document.getElementById('smiley').textContent = '😵';
|
||||
speak('Oh nein! Mine getroffen!');
|
||||
}
|
||||
|
||||
modal.style.display = 'block';
|
||||
}
|
||||
|
||||
function newGame() {
|
||||
gameRunning = false;
|
||||
clearInterval(timerInterval);
|
||||
|
||||
timer = 0;
|
||||
revealedCount = 0;
|
||||
flaggedCount = 0;
|
||||
firstClick = true;
|
||||
|
||||
document.getElementById('timer').textContent = '000';
|
||||
document.getElementById('smiley').textContent = '😊';
|
||||
document.getElementById('gameOver').style.display = 'none';
|
||||
|
||||
initBoard();
|
||||
}
|
||||
|
||||
function speak(text) {
|
||||
if ('speechSynthesis' in window) {
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.lang = 'de-DE';
|
||||
speechSynthesis.speak(utterance);
|
||||
}
|
||||
}
|
||||
|
||||
// Init
|
||||
initBoard();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,305 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Puzzle - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 10px;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 10px; font-size: 24px; }
|
||||
.level-select { display: flex; gap: 10px; margin-bottom: 15px; }
|
||||
.level-btn {
|
||||
background: rgba(255,255,255,0.2); border: 2px solid white; border-radius: 10px;
|
||||
padding: 8px 15px; color: white; font-size: 14px; cursor: pointer; font-family: inherit;
|
||||
}
|
||||
.level-btn.active { background: white; color: #f5576c; }
|
||||
.image-select { display: flex; gap: 10px; margin-bottom: 15px; flex-wrap: wrap; justify-content: center; }
|
||||
.img-btn {
|
||||
background: white; border: 3px solid transparent; border-radius: 10px;
|
||||
padding: 5px; cursor: pointer; font-size: 30px;
|
||||
}
|
||||
.img-btn.active { border-color: #4ade80; }
|
||||
.puzzle-container {
|
||||
display: grid; gap: 2px; background: rgba(0,0,0,0.2); padding: 5px;
|
||||
border-radius: 10px; touch-action: none;
|
||||
}
|
||||
.puzzle-piece {
|
||||
background-size: 300px 300px; cursor: pointer; border-radius: 5px;
|
||||
transition: all 0.2s; position: relative;
|
||||
}
|
||||
.puzzle-piece:hover { transform: scale(1.02); }
|
||||
.puzzle-piece.selected { box-shadow: 0 0 10px #4ade80; z-index: 10; }
|
||||
.puzzle-piece.correct {
|
||||
animation: pulse 0.5s;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.1); }
|
||||
}
|
||||
.preview {
|
||||
background: white; border-radius: 10px; padding: 10px; margin: 10px 0;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
.preview-img { font-size: 80px; }
|
||||
.controls { display: flex; gap: 15px; margin-top: 15px; flex-wrap: wrap; justify-content: center; }
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 12px 25px;
|
||||
font-size: 16px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; }
|
||||
.status { color: white; font-size: 18px; margin: 10px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🧩 Puzzle</h1>
|
||||
|
||||
<div class="level-select">
|
||||
<button class="level-btn" onclick="setLevel(2)" id="l2">2×2 (Leicht)</button>
|
||||
<button class="level-btn active" onclick="setLevel(3)" id="l3">3×3 (Mittel)</button>
|
||||
<button class="level-btn" onclick="setLevel(4)" id="l4">4×4 (Schwer)</button>
|
||||
</div>
|
||||
|
||||
<div class="image-select" id="imageSelect"></div>
|
||||
|
||||
<div class="preview">
|
||||
<div class="preview-img" id="previewImg">🐶</div>
|
||||
<p style="color:#666;font-size:12px">So soll es aussehen!</p>
|
||||
</div>
|
||||
|
||||
<div class="status" id="status">Klicke zwei Teile zum Tauschen!</div>
|
||||
|
||||
<div class="puzzle-container" id="puzzleContainer"></div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn" onclick="shufflePuzzle()">🔀 Mischen</button>
|
||||
<button class="btn" onclick="showHint()">💡 Tipp</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const emojis = ['🐶','🐱','🐭','🐹','🐰','🦊','🐻','🐼','🐨','🐯','🦁','🐷','🐸','🐵','🐔'];
|
||||
|
||||
let gridSize = 3;
|
||||
let selectedPiece = null;
|
||||
let pieces = [];
|
||||
let currentEmoji = '🐶';
|
||||
let solved = false;
|
||||
|
||||
function init() {
|
||||
// Bildauswahl
|
||||
const imgSelect = document.getElementById('imageSelect');
|
||||
emojis.slice(0, 8).forEach((emoji, i) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'img-btn' + (i === 0 ? ' active' : '');
|
||||
btn.textContent = emoji;
|
||||
btn.onclick = () => selectImage(emoji, btn);
|
||||
imgSelect.appendChild(btn);
|
||||
});
|
||||
|
||||
createPuzzle();
|
||||
}
|
||||
|
||||
function setLevel(n) {
|
||||
gridSize = n;
|
||||
document.querySelectorAll('.level-btn').forEach(b => b.classList.remove('active'));
|
||||
document.getElementById('l' + n).classList.add('active');
|
||||
createPuzzle();
|
||||
}
|
||||
|
||||
function selectImage(emoji, btn) {
|
||||
currentEmoji = emoji;
|
||||
document.getElementById('previewImg').textContent = emoji;
|
||||
document.querySelectorAll('.img-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
createPuzzle();
|
||||
}
|
||||
|
||||
function createPuzzle() {
|
||||
const container = document.getElementById('puzzleContainer');
|
||||
container.innerHTML = '';
|
||||
container.style.gridTemplateColumns = `repeat(${gridSize}, 80px)`;
|
||||
|
||||
pieces = [];
|
||||
const totalPieces = gridSize * gridSize;
|
||||
|
||||
// Canvas für Emoji-Bild
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 300;
|
||||
canvas.height = 300;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Emoji zeichnen
|
||||
ctx.fillStyle = '#f0f0f0';
|
||||
ctx.fillRect(0, 0, 300, 300);
|
||||
ctx.font = '200px Arial';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(currentEmoji, 150, 150);
|
||||
|
||||
const imageData = canvas.toDataURL();
|
||||
|
||||
// Teile erstellen
|
||||
for (let i = 0; i < totalPieces; i++) {
|
||||
const correctPos = i;
|
||||
const piece = {
|
||||
id: i,
|
||||
correctPos: correctPos,
|
||||
currentPos: i
|
||||
};
|
||||
pieces.push(piece);
|
||||
}
|
||||
|
||||
// Rendern
|
||||
renderPuzzle(imageData);
|
||||
|
||||
// Kurz warten dann mischen
|
||||
setTimeout(shufflePuzzle, 500);
|
||||
}
|
||||
|
||||
function renderPuzzle(imageData) {
|
||||
const container = document.getElementById('puzzleContainer');
|
||||
container.innerHTML = '';
|
||||
|
||||
const pieceSize = 80;
|
||||
|
||||
// Nach currentPos sortieren für Anzeige
|
||||
const displayPieces = [...pieces].sort((a, b) => a.currentPos - b.currentPos);
|
||||
|
||||
displayPieces.forEach((piece, index) => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'puzzle-piece';
|
||||
div.style.width = pieceSize + 'px';
|
||||
div.style.height = pieceSize + 'px';
|
||||
div.style.backgroundImage = `url(${imageData})`;
|
||||
|
||||
// Hintergrund-Position basierend auf correctPos
|
||||
const row = Math.floor(piece.correctPos / gridSize);
|
||||
const col = piece.correctPos % gridSize;
|
||||
div.style.backgroundPosition = `-${col * pieceSize}px -${row * pieceSize}px`;
|
||||
div.style.backgroundSize = `${gridSize * pieceSize}px ${gridSize * pieceSize}px`;
|
||||
|
||||
div.onclick = () => selectPiece(piece);
|
||||
container.appendChild(div);
|
||||
});
|
||||
|
||||
checkWin();
|
||||
}
|
||||
|
||||
function selectPiece(piece) {
|
||||
if (solved) return;
|
||||
|
||||
const container = document.getElementById('puzzleContainer');
|
||||
const divs = container.querySelectorAll('.puzzle-piece');
|
||||
|
||||
// Index in display order finden
|
||||
const displayIndex = pieces.findIndex(p => p.id === piece.id);
|
||||
|
||||
if (!selectedPiece) {
|
||||
selectedPiece = piece;
|
||||
divs[displayIndex].classList.add('selected');
|
||||
document.getElementById('status').textContent = 'Wähle ein anderes Teil zum Tauschen!';
|
||||
} else if (selectedPiece.id === piece.id) {
|
||||
// Gleiches Teil = abwählen
|
||||
selectedPiece = null;
|
||||
divs[displayIndex].classList.remove('selected');
|
||||
document.getElementById('status').textContent = 'Klicke zwei Teile zum Tauschen!';
|
||||
} else {
|
||||
// Tauschen
|
||||
const tempPos = selectedPiece.currentPos;
|
||||
selectedPiece.currentPos = piece.currentPos;
|
||||
piece.currentPos = tempPos;
|
||||
|
||||
selectedPiece = null;
|
||||
|
||||
// Neu rendern
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 300; canvas.height = 300;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = '#f0f0f0';
|
||||
ctx.fillRect(0, 0, 300, 300);
|
||||
ctx.font = '200px Arial';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(currentEmoji, 150, 150);
|
||||
renderPuzzle(canvas.toDataURL());
|
||||
}
|
||||
}
|
||||
|
||||
function shufflePuzzle() {
|
||||
if (solved) solved = false;
|
||||
|
||||
// Fisher-Yates Shuffle für currentPos
|
||||
for (let i = pieces.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
const temp = pieces[i].currentPos;
|
||||
pieces[i].currentPos = pieces[j].currentPos;
|
||||
pieces[j].currentPos = temp;
|
||||
}
|
||||
|
||||
// Neu rendern
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 300; canvas.height = 300;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = '#f0f0f0';
|
||||
ctx.fillRect(0, 0, 300, 300);
|
||||
ctx.font = '200px Arial';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(currentEmoji, 150, 150);
|
||||
renderPuzzle(canvas.toDataURL());
|
||||
|
||||
document.getElementById('status').textContent = 'Klicke zwei Teile zum Tauschen!';
|
||||
}
|
||||
|
||||
function checkWin() {
|
||||
const isSolved = pieces.every(p => p.currentPos === p.correctPos);
|
||||
|
||||
if (isSolved && !solved) {
|
||||
solved = true;
|
||||
document.getElementById('status').textContent = '🎉 Super! Puzzle gelöst!';
|
||||
|
||||
// Alle Teile grün blinken
|
||||
const divs = document.querySelectorAll('.puzzle-piece');
|
||||
divs.forEach(div => {
|
||||
div.style.boxShadow = '0 0 20px #4ade80';
|
||||
});
|
||||
|
||||
if ('speechSynthesis' in window) {
|
||||
const utterance = new SpeechSynthesisUtterance('Super! Du hast das Puzzle gelöst!');
|
||||
utterance.lang = 'de-DE';
|
||||
speechSynthesis.speak(utterance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showHint() {
|
||||
// Zeige für 2 Sekunden die richtige Position an
|
||||
const container = document.getElementById('puzzleContainer');
|
||||
const divs = container.querySelectorAll('.puzzle-piece');
|
||||
|
||||
divs.forEach((div, index) => {
|
||||
const piece = pieces.find(p => p.currentPos === index);
|
||||
if (piece && piece.correctPos === index) {
|
||||
div.style.boxShadow = '0 0 10px #4ade80';
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
divs.forEach(div => {
|
||||
div.style.boxShadow = '';
|
||||
});
|
||||
}, 2000);
|
||||
|
||||
document.getElementById('status').textContent = '💡 Grün markierte Teile sind richtig!';
|
||||
}
|
||||
|
||||
// Init
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,231 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Reaktionstest - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 20px;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 15px; font-size: 26px; }
|
||||
.game-area {
|
||||
width: 300px; height: 300px; border-radius: 20px;
|
||||
display: flex; justify-content: center; align-items: center;
|
||||
cursor: pointer; transition: all 0.1s; margin: 20px 0;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||||
}
|
||||
.game-area.wait { background: #ff6b6b; }
|
||||
.game-area.ready { background: #4ade80; animation: pulse 0.5s infinite; }
|
||||
.game-area.too-soon { background: #feca57; }
|
||||
.game-area.start { background: #3b82f6; }
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
}
|
||||
.game-text {
|
||||
color: white; font-size: 28px; font-weight: bold; text-align: center;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
.stats {
|
||||
display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px;
|
||||
margin: 20px 0; max-width: 300px;
|
||||
}
|
||||
.stat-box {
|
||||
background: white; border-radius: 15px; padding: 15px; text-align: center;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
.stat-label { color: #666; font-size: 12px; }
|
||||
.stat-value { color: #333; font-size: 24px; font-weight: bold; }
|
||||
.stat-value.best { color: #4ade80; }
|
||||
.controls { display: flex; gap: 15px; margin-top: 20px; }
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 12px 25px;
|
||||
font-size: 16px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; }
|
||||
.instructions { color: white; text-align: center; max-width: 400px; margin: 10px 0; }
|
||||
.reaction-list { background: white; border-radius: 15px; padding: 15px; margin: 10px 0; }
|
||||
.reaction-item { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #eee; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>⚡ Reaktionstest</h1>
|
||||
|
||||
<p class="instructions">
|
||||
Warte bis es GRÜN wird, dann so schnell wie möglich klicken!
|
||||
<br>Aber nicht zu früh...
|
||||
</p>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-box">
|
||||
<div class="stat-label">Letzte Zeit</div>
|
||||
<div class="stat-value" id="lastTime">-</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="stat-label">Bestzeit</div>
|
||||
<div class="stat-value best" id="bestTime">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="game-area start" id="gameArea" onclick="handleClick()">
|
||||
<div class="game-text" id="gameText">
|
||||
Klicken zum
|
||||
<br>Starten
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="reaction-list" id="reactionList" style="display:none">
|
||||
<p style="font-weight:bold;margin-bottom:10px">Letzte Versuche:</p>
|
||||
<div id="reactionHistory"></div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn" onclick="resetBest()">🗑️ Reset Bestzeit</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Zustände: start, waiting, ready, too_soon, result
|
||||
let state = 'start';
|
||||
let startTime = 0;
|
||||
let waitTimer = null;
|
||||
let bestTime = localStorage.getItem('reaktion_bestzeit');
|
||||
let lastTime = null;
|
||||
let reactionHistory = [];
|
||||
|
||||
const gameArea = document.getElementById('gameArea');
|
||||
const gameText = document.getElementById('gameText');
|
||||
|
||||
// Bestzeit anzeigen
|
||||
if (bestTime) {
|
||||
document.getElementById('bestTime').textContent = bestTime + ' ms';
|
||||
}
|
||||
|
||||
function handleClick() {
|
||||
if (state === 'start') {
|
||||
// Spiel starten
|
||||
startWaiting();
|
||||
} else if (state === 'waiting') {
|
||||
// Zu früh!
|
||||
tooSoon();
|
||||
} else if (state === 'ready') {
|
||||
// Reaktion messen
|
||||
measureReaction();
|
||||
} else if (state === 'too_soon' || state === 'result') {
|
||||
// Neu starten
|
||||
resetToStart();
|
||||
}
|
||||
}
|
||||
|
||||
function startWaiting() {
|
||||
state = 'waiting';
|
||||
startTime = 0;
|
||||
gameArea.className = 'game-area wait';
|
||||
gameText.innerHTML = 'Warte auf<br>GRÜN...';
|
||||
|
||||
// Zufällige Zeit 1-4 Sekunden
|
||||
const delay = 1000 + Math.random() * 3000;
|
||||
|
||||
waitTimer = setTimeout(() => {
|
||||
if (state === 'waiting') {
|
||||
state = 'ready';
|
||||
gameArea.className = 'game-area ready';
|
||||
gameText.innerHTML = 'JETZT!<br>🖱️ Klick!';
|
||||
startTime = Date.now();
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
|
||||
function tooSoon() {
|
||||
clearTimeout(waitTimer);
|
||||
state = 'too_soon';
|
||||
gameArea.className = 'game-area too-soon';
|
||||
gameText.innerHTML = 'Zu früh!<br>😅<br><small>Nochmal klicken</small>';
|
||||
}
|
||||
|
||||
function measureReaction() {
|
||||
const endTime = Date.now();
|
||||
const reactionTime = endTime - startTime;
|
||||
state = 'result';
|
||||
|
||||
// Bewertung
|
||||
let message = '';
|
||||
let emoji = '';
|
||||
if (reactionTime < 200) {
|
||||
message = 'Unglaublich!';
|
||||
emoji = '🚀';
|
||||
} else if (reactionTime < 300) {
|
||||
message = 'Super schnell!';
|
||||
emoji = '⚡';
|
||||
} else if (reactionTime < 400) {
|
||||
message = 'Sehr gut!';
|
||||
emoji = '👍';
|
||||
} else if (reactionTime < 600) {
|
||||
message = 'Gut!';
|
||||
emoji = '👌';
|
||||
} else {
|
||||
message = 'Übe mehr!';
|
||||
emoji = '💪';
|
||||
}
|
||||
|
||||
gameArea.className = 'game-area wait';
|
||||
gameText.innerHTML = reactionTime + ' ms<br>' + emoji + '<br><small>' + message + '</small>';
|
||||
|
||||
// Speichern
|
||||
lastTime = reactionTime;
|
||||
document.getElementById('lastTime').textContent = reactionTime + ' ms';
|
||||
|
||||
if (!bestTime || reactionTime < bestTime) {
|
||||
bestTime = reactionTime;
|
||||
localStorage.setItem('reaktion_bestzeit', bestTime);
|
||||
document.getElementById('bestTime').textContent = bestTime + ' ms';
|
||||
}
|
||||
|
||||
// Historie
|
||||
reactionHistory.unshift({ time: reactionTime });
|
||||
if (reactionHistory.length > 5) reactionHistory.pop();
|
||||
updateHistory();
|
||||
|
||||
// TTS
|
||||
if ('speechSynthesis' in window) {
|
||||
const utterance = new SpeechSynthesisUtterance(reactionTime + ' Millisekunden. ' + message);
|
||||
utterance.lang = 'de-DE';
|
||||
utterance.rate = 0.9;
|
||||
speechSynthesis.speak(utterance);
|
||||
}
|
||||
}
|
||||
|
||||
function resetToStart() {
|
||||
state = 'start';
|
||||
startTime = 0;
|
||||
gameArea.className = 'game-area start';
|
||||
gameText.innerHTML = 'Klicken zum<br>Starten';
|
||||
}
|
||||
|
||||
function resetBest() {
|
||||
localStorage.removeItem('reaktion_bestzeit');
|
||||
bestTime = null;
|
||||
document.getElementById('bestTime').textContent = '-';
|
||||
}
|
||||
|
||||
function updateHistory() {
|
||||
const list = document.getElementById('reactionHistory');
|
||||
document.getElementById('reactionList').style.display = reactionHistory.length > 0 ? 'block' : 'none';
|
||||
|
||||
list.innerHTML = reactionHistory.map((r, i) => {
|
||||
return '<div class="reaction-item"><span>' + (i+1) + '. Versuch</span><span>' + r.time + ' ms</span></div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Touch für Mobile
|
||||
gameArea.addEventListener('touchstart', function(e) {
|
||||
e.preventDefault();
|
||||
handleClick();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,588 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Schach - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 10px;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 10px; font-size: 24px; }
|
||||
|
||||
.board-container {
|
||||
background: #5D4037; padding: 10px; border-radius: 10px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.chess-board {
|
||||
display: grid; grid-template-columns: repeat(8, 45px); gap: 0;
|
||||
border: 3px solid #3E2723;
|
||||
}
|
||||
|
||||
.square {
|
||||
width: 45px; height: 45px; display: flex;
|
||||
align-items: center; justify-content: center; position: relative;
|
||||
cursor: pointer; transition: all 0.15s;
|
||||
}
|
||||
|
||||
.square.light { background: #F0D9B5; }
|
||||
.square.dark { background: #B58863; }
|
||||
|
||||
.square.selected { background: #7B61FF !important; }
|
||||
.square.valid-move::after {
|
||||
content: ''; position: absolute; width: 12px; height: 12px;
|
||||
background: rgba(123, 97, 255, 0.8); border-radius: 50%;
|
||||
}
|
||||
.square.valid-capture { background: #FF6B6B !important; }
|
||||
.square.last-move { box-shadow: inset 0 0 0 3px #FFD93D; }
|
||||
.square.check {
|
||||
background: #FF4444 !important;
|
||||
animation: pulse-check 0.5s infinite;
|
||||
}
|
||||
@keyframes pulse-check {
|
||||
0%, 100% { box-shadow: inset 0 0 0 rgba(255,0,0,0); }
|
||||
50% { box-shadow: inset 0 0 15px rgba(255,0,0,0.8); }
|
||||
}
|
||||
|
||||
.piece {
|
||||
font-size: 32px; cursor: grab; transition: transform 0.15s;
|
||||
text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
|
||||
}
|
||||
.piece:hover { transform: scale(1.15); }
|
||||
.piece.white {
|
||||
filter: drop-shadow(0 0 3px #fff) drop-shadow(0 0 5px #FFD700);
|
||||
}
|
||||
.piece.black {
|
||||
filter: drop-shadow(0 0 3px #000);
|
||||
}
|
||||
|
||||
.coordinates {
|
||||
display: flex; justify-content: space-around; width: 360px;
|
||||
color: white; font-size: 11px; margin-top: 3px;
|
||||
}
|
||||
|
||||
.status {
|
||||
background: rgba(255,255,255,0.15); padding: 12px 25px; border-radius: 25px;
|
||||
color: white; margin: 12px 0; text-align: center; font-size: 15px; min-height: 45px;
|
||||
}
|
||||
.turn-white { color: #FFD700; font-weight: bold; }
|
||||
.turn-black { color: #AAA; font-weight: bold; }
|
||||
|
||||
.mode-select { margin: 10px 0; }
|
||||
.mode-btn {
|
||||
background: rgba(255,255,255,0.2); border: 2px solid white; color: white;
|
||||
padding: 8px 20px; margin: 0 5px; border-radius: 20px; cursor: pointer;
|
||||
}
|
||||
.mode-btn.active { background: white; color: #2d3748; }
|
||||
|
||||
.hint-btn {
|
||||
background: #F59E0B; color: white; padding: 10px 20px;
|
||||
border: none; border-radius: 10px; cursor: pointer;
|
||||
font-family: inherit; font-weight: bold; font-size: 14px;
|
||||
}
|
||||
|
||||
.promotion-modal {
|
||||
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
||||
background: white; padding: 25px; border-radius: 15px; display: none; z-index: 100;
|
||||
}
|
||||
.promotion-piece { font-size: 50px; margin: 0 10px; cursor: pointer; }
|
||||
.promotion-piece:hover { transform: scale(1.2); }
|
||||
|
||||
.controls { display: flex; gap: 10px; margin-top: 15px; flex-wrap: wrap; justify-content: center; }
|
||||
.btn {
|
||||
padding: 10px 20px; border: none; border-radius: 10px; cursor: pointer;
|
||||
font-family: inherit; font-weight: bold; font-size: 14px;
|
||||
}
|
||||
.btn-new { background: #4ade80; color: white; }
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; }
|
||||
|
||||
.exercise-btn {
|
||||
background: rgba(255,255,255,0.2); border: 2px solid white; color: white;
|
||||
padding: 8px 15px; margin: 5px; border-radius: 10px; cursor: pointer;
|
||||
}
|
||||
.exercise-btn:hover { background: rgba(255,255,255,0.4); }
|
||||
|
||||
.game-over {
|
||||
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
||||
background: white; padding: 30px; border-radius: 20px; display: none; z-index: 100;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>♟️ Schach</h1>
|
||||
|
||||
<div class="mode-select">
|
||||
<button class="mode-btn active" onclick="setMode('free')">Freies Spiel</button>
|
||||
<button class="mode-btn" onclick="setMode('mate1')">Matt in 1</button>
|
||||
<button class="mode-btn" onclick="setMode('fork')">Gabel</button>
|
||||
</div>
|
||||
|
||||
<div class="status" id="status"><span class="turn-white">Weiß</span> ist am Zug</div>
|
||||
|
||||
<div class="board-container">
|
||||
<div class="chess-board" id="board"></div>
|
||||
</div>
|
||||
|
||||
<div class="coordinates">
|
||||
<span>a</span><span>b</span><span>c</span><span>d</span>
|
||||
<span>e</span><span>f</span><span>g</span><span>h</span>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="hint-btn" onclick="showHint()">💡 Tipp</button>
|
||||
<button class="btn btn-new" onclick="newGame()">🔄 Neues Spiel</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<div class="promotion-modal" id="promotionModal">
|
||||
<h3>Wähle Figur:</h3>
|
||||
<div id="promotionOptions"></div>
|
||||
</div>
|
||||
|
||||
<div class="game-over" id="gameOver">
|
||||
<h2 id="winnerText">Schachmatt!</h2>
|
||||
<button class="btn btn-new" onclick="newGame()">🔄 Nochmal</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const PIECES = {
|
||||
'K': '♔', 'Q': '♕', 'R': '♖', 'B': '♗', 'N': '♘', 'P': '♙',
|
||||
'k': '♚', 'q': '♛', 'r': '♜', 'b': '♝', 'n': '♞', 'p': '♟'
|
||||
};
|
||||
|
||||
let board = [];
|
||||
let selected = null;
|
||||
let validMoves = [];
|
||||
let currentPlayer = 'white';
|
||||
let lastMove = null;
|
||||
let waitingPromotion = null;
|
||||
let currentExercise = null;
|
||||
|
||||
const START_POS = [
|
||||
'rnbqkbnr',
|
||||
'pppppppp',
|
||||
' ',
|
||||
' ',
|
||||
' ',
|
||||
' ',
|
||||
'PPPPPPPP',
|
||||
'RNBQKBNR'
|
||||
];
|
||||
|
||||
const EXERCISES = {
|
||||
mate1: [
|
||||
{
|
||||
pos: [
|
||||
' ',
|
||||
' ',
|
||||
' ',
|
||||
' k ',
|
||||
' ',
|
||||
' Q ',
|
||||
' ',
|
||||
' K'
|
||||
],
|
||||
solution: { from: {r: 5, c: 5}, to: {r: 3, c: 5} }, // Qf6-f7 Matt
|
||||
hint: 'Setze die Dame neben den König auf f7!'
|
||||
},
|
||||
{
|
||||
pos: [
|
||||
' ',
|
||||
' ',
|
||||
' ',
|
||||
' k ',
|
||||
' ',
|
||||
' ',
|
||||
'R ',
|
||||
' K'
|
||||
],
|
||||
solution: { from: {r: 6, c: 0}, to: {r: 0, c: 0} },
|
||||
hint: 'Der Turm schließt den König auf der a-Linie ein!'
|
||||
},
|
||||
{
|
||||
pos: [
|
||||
'k ',
|
||||
' ',
|
||||
' ',
|
||||
' ',
|
||||
' ',
|
||||
' ',
|
||||
'R R',
|
||||
' K'
|
||||
],
|
||||
solution: { from: {r: 6, c: 0}, to: {r: 0, c: 0} },
|
||||
hint: 'Ein Turm geht auf die 8. Reihe für Matt!'
|
||||
}
|
||||
],
|
||||
fork: [
|
||||
{
|
||||
pos: [
|
||||
' ',
|
||||
' q ',
|
||||
' ',
|
||||
' ',
|
||||
' k ',
|
||||
' ',
|
||||
' N',
|
||||
' K'
|
||||
],
|
||||
solution: { from: {r: 6, c: 7}, to: {r: 4, c: 5} },
|
||||
hint: 'Der Springer greift König und Dame gleichzeitig an!'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
function initBoard() {
|
||||
const boardEl = document.getElementById('board');
|
||||
boardEl.innerHTML = '';
|
||||
board = [];
|
||||
|
||||
for (let row = 0; row < 8; row++) {
|
||||
board[row] = [];
|
||||
for (let col = 0; col < 8; col++) {
|
||||
const cell = document.createElement('div');
|
||||
cell.className = 'square ' + ((row + col) % 2 === 0 ? 'light' : 'dark');
|
||||
cell.dataset.r = row;
|
||||
cell.dataset.c = col;
|
||||
cell.onclick = () => clickSquare(row, col);
|
||||
|
||||
const piece = START_POS[row][col];
|
||||
if (piece !== ' ') {
|
||||
setPiece(cell, piece);
|
||||
board[row][col] = piece;
|
||||
}
|
||||
|
||||
boardEl.appendChild(cell);
|
||||
}
|
||||
}
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
function setPiece(cell, piece) {
|
||||
const span = document.createElement('span');
|
||||
span.className = 'piece ' + (piece === piece.toUpperCase() ? 'white' : 'black');
|
||||
span.textContent = PIECES[piece];
|
||||
cell.innerHTML = '';
|
||||
cell.appendChild(span);
|
||||
}
|
||||
|
||||
function clickSquare(row, col) {
|
||||
if (waitingPromotion) return;
|
||||
|
||||
const piece = board[row][col];
|
||||
const isOwn = piece &&
|
||||
((currentPlayer === 'white' && piece === piece.toUpperCase()) ||
|
||||
(currentPlayer === 'black' && piece === piece.toLowerCase()));
|
||||
|
||||
if (!selected && isOwn) {
|
||||
selected = { r: row, c: col };
|
||||
validMoves = getMoves(row, col, piece);
|
||||
highlightSquare(row, col, 'selected');
|
||||
highlightMoves();
|
||||
} else if (selected) {
|
||||
const move = validMoves.find(m => m.r === row && m.c === col);
|
||||
if (move) {
|
||||
makeMove(selected.r, selected.c, row, col);
|
||||
}
|
||||
clearHighlights();
|
||||
selected = null;
|
||||
validMoves = [];
|
||||
}
|
||||
}
|
||||
|
||||
function getMoves(r, c, piece) {
|
||||
const moves = [];
|
||||
const isWhite = piece === piece.toUpperCase();
|
||||
const p = piece.toLowerCase();
|
||||
|
||||
if (p === 'p') {
|
||||
const dir = isWhite ? -1 : 1;
|
||||
const startRow = isWhite ? 6 : 1;
|
||||
|
||||
if (isEmpty(r + dir, c)) {
|
||||
moves.push({ r: r + dir, c });
|
||||
if (r === startRow && isEmpty(r + 2*dir, c)) {
|
||||
moves.push({ r: r + 2*dir, c });
|
||||
}
|
||||
}
|
||||
[-1, 1].forEach(dc => {
|
||||
if (canCapture(r + dir, c + dc, isWhite)) {
|
||||
moves.push({ r: r + dir, c: c + dc });
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (p === 'n') {
|
||||
[[-2,-1],[-2,1],[-1,-2],[-1,2],[1,-2],[1,2],[2,-1],[2,1]].forEach(([dr, dc]) => {
|
||||
if (canMove(r + dr, c + dc, isWhite)) moves.push({ r: r + dr, c: c + dc });
|
||||
});
|
||||
}
|
||||
else if (p === 'k') {
|
||||
[[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]].forEach(([dr, dc]) => {
|
||||
if (canMove(r + dr, c + dc, isWhite)) moves.push({ r: r + dr, c: c + dc });
|
||||
});
|
||||
}
|
||||
else if (p === 'r') {
|
||||
addLineMoves(moves, r, c, [[0,1],[0,-1],[1,0],[-1,0]], isWhite);
|
||||
}
|
||||
else if (p === 'b') {
|
||||
addLineMoves(moves, r, c, [[1,1],[1,-1],[-1,1],[-1,-1]], isWhite);
|
||||
}
|
||||
else if (p === 'q') {
|
||||
addLineMoves(moves, r, c, [[0,1],[0,-1],[1,0],[-1,0],[1,1],[1,-1],[-1,1],[-1,-1]], isWhite);
|
||||
}
|
||||
|
||||
return moves;
|
||||
}
|
||||
|
||||
function addLineMoves(moves, r, c, dirs, isWhite) {
|
||||
dirs.forEach(([dr, dc]) => {
|
||||
for (let i = 1; i < 8; i++) {
|
||||
const nr = r + dr * i, nc = c + dc * i;
|
||||
if (!inBounds(nr, nc)) break;
|
||||
if (isEmpty(nr, nc)) {
|
||||
moves.push({ r: nr, c: nc });
|
||||
} else if (canCapture(nr, nc, isWhite)) {
|
||||
moves.push({ r: nr, c: nc });
|
||||
break;
|
||||
} else break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function inBounds(r, c) { return r >= 0 && r < 8 && c >= 0 && c < 8; }
|
||||
function isEmpty(r, c) { return inBounds(r, c) && !board[r][c]; }
|
||||
function canCapture(r, c, isWhite) {
|
||||
if (!inBounds(r, c)) return false;
|
||||
const target = board[r][c];
|
||||
return target && (isWhite ? target === target.toLowerCase() : target === target.toUpperCase());
|
||||
}
|
||||
function canMove(r, c, isWhite) {
|
||||
return inBounds(r, c) && (isEmpty(r, c) || canCapture(r, c, isWhite));
|
||||
}
|
||||
|
||||
function makeMove(fr, fc, tr, tc) {
|
||||
const piece = board[fr][fc];
|
||||
const captured = board[tr][tc];
|
||||
|
||||
board[tr][tc] = piece;
|
||||
board[fr][fc] = null;
|
||||
|
||||
// König geschlagen = Spielende
|
||||
if (captured === 'K' || captured === 'k') {
|
||||
endGame(captured === 'K' ? 'Schwarz' : 'Weiß');
|
||||
return;
|
||||
}
|
||||
|
||||
// Bauern-Umwandlung
|
||||
if (piece.toLowerCase() === 'p' && (tr === 0 || tr === 7)) {
|
||||
showPromotion(tr, tc, piece === piece.toUpperCase());
|
||||
return;
|
||||
}
|
||||
|
||||
finishMove(fr, fc, tr, tc, piece);
|
||||
}
|
||||
|
||||
function showPromotion(r, c, isWhite) {
|
||||
waitingPromotion = { r, c, isWhite };
|
||||
const modal = document.getElementById('promotionModal');
|
||||
const opts = document.getElementById('promotionOptions');
|
||||
opts.innerHTML = '';
|
||||
|
||||
const pieces = isWhite ? ['Q','R','B','N'] : ['q','r','b','n'];
|
||||
pieces.forEach(p => {
|
||||
const btn = document.createElement('span');
|
||||
btn.className = 'promotion-piece';
|
||||
btn.textContent = PIECES[p];
|
||||
btn.onclick = () => {
|
||||
board[r][c] = p;
|
||||
modal.style.display = 'none';
|
||||
waitingPromotion = null;
|
||||
finishMove(null, null, null, null, p);
|
||||
};
|
||||
opts.appendChild(btn);
|
||||
});
|
||||
|
||||
modal.style.display = 'block';
|
||||
}
|
||||
|
||||
function finishMove(fr, fc, tr, tc, piece) {
|
||||
lastMove = { fr, fc, tr, tc };
|
||||
updateBoard();
|
||||
|
||||
// Prüfe Übungslösung
|
||||
if (currentExercise) {
|
||||
const sol = currentExercise.solution;
|
||||
if (lastMove.fr === sol.from.r && lastMove.fc === sol.from.c &&
|
||||
lastMove.tr === sol.to.r && lastMove.tc === sol.to.c) {
|
||||
setTimeout(() => {
|
||||
alert('🎉 Richtig! ' + currentExercise.hint);
|
||||
currentExercise = null;
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
switchPlayer();
|
||||
}
|
||||
|
||||
function switchPlayer() {
|
||||
currentPlayer = currentPlayer === 'white' ? 'black' : 'white';
|
||||
updateStatus();
|
||||
|
||||
// Prüfe Schach
|
||||
if (isInCheck(currentPlayer)) {
|
||||
highlightKing(currentPlayer);
|
||||
if (isCheckmate(currentPlayer)) {
|
||||
endGame(currentPlayer === 'white' ? 'Schwarz' : 'Weiß');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isInCheck(color) {
|
||||
const king = color === 'white' ? 'K' : 'k';
|
||||
let kr, kc;
|
||||
|
||||
for (let r = 0; r < 8; r++) {
|
||||
for (let c = 0; c < 8; c++) {
|
||||
if (board[r][c] === king) { kr = r; kc = c; }
|
||||
}
|
||||
}
|
||||
|
||||
for (let r = 0; r < 8; r++) {
|
||||
for (let c = 0; c < 8; c++) {
|
||||
const p = board[r][c];
|
||||
if (p && (color === 'white' ? p === p.toLowerCase() : p === p.toUpperCase())) {
|
||||
const moves = getMoves(r, c, p);
|
||||
if (moves.some(m => m.r === kr && m.c === kc)) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isCheckmate(color) {
|
||||
// Einfach: Keine legalen Züge + Schach
|
||||
if (!isInCheck(color)) return false;
|
||||
|
||||
for (let r = 0; r < 8; r++) {
|
||||
for (let c = 0; c < 8; c++) {
|
||||
const p = board[r][c];
|
||||
if (p && (color === 'white' ? p === p.toUpperCase() : p === p.toLowerCase())) {
|
||||
if (getMoves(r, c, p).length > 0) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function highlightKing(color) {
|
||||
const king = color === 'white' ? 'K' : 'k';
|
||||
for (let r = 0; r < 8; r++) {
|
||||
for (let c = 0; c < 8; c++) {
|
||||
if (board[r][c] === king) {
|
||||
document.querySelector(`[data-r="${r}"][data-c="${c}"]`).classList.add('check');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateBoard() {
|
||||
for (let r = 0; r < 8; r++) {
|
||||
for (let c = 0; c < 8; c++) {
|
||||
const cell = document.querySelector(`[data-r="${r}"][data-c="${c}"]`);
|
||||
const p = board[r][c];
|
||||
cell.innerHTML = '';
|
||||
cell.classList.remove('last-move', 'check');
|
||||
if (p) setPiece(cell, p);
|
||||
if (lastMove && ((r === lastMove.fr && c === lastMove.fc) || (r === lastMove.tr && c === lastMove.tc))) {
|
||||
cell.classList.add('last-move');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateStatus() {
|
||||
const status = document.getElementById('status');
|
||||
const cls = currentPlayer === 'white' ? 'turn-white' : 'turn-black';
|
||||
status.innerHTML = `<span class="${cls}">${currentPlayer === 'white' ? 'Weiß' : 'Schwarz'}</span> ist am Zug`;
|
||||
}
|
||||
|
||||
function highlightSquare(r, c, cls) {
|
||||
document.querySelector(`[data-r="${r}"][data-c="${c}"]`).classList.add(cls);
|
||||
}
|
||||
|
||||
function highlightMoves() {
|
||||
validMoves.forEach(m => {
|
||||
const cell = document.querySelector(`[data-r="${m.r}"][data-c="${m.c}"]`);
|
||||
if (board[m.r][m.c]) cell.classList.add('valid-capture');
|
||||
else cell.classList.add('valid-move');
|
||||
});
|
||||
}
|
||||
|
||||
function clearHighlights() {
|
||||
document.querySelectorAll('.square').forEach(s => {
|
||||
s.classList.remove('selected', 'valid-move', 'valid-capture');
|
||||
});
|
||||
}
|
||||
|
||||
function setMode(mode) {
|
||||
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
|
||||
if (mode === 'free') {
|
||||
currentExercise = null;
|
||||
newGame();
|
||||
} else if (EXERCISES[mode]) {
|
||||
const ex = EXERCISES[mode][Math.floor(Math.random() * EXERCISES[mode].length)];
|
||||
currentExercise = ex;
|
||||
loadPosition(ex.pos);
|
||||
}
|
||||
}
|
||||
|
||||
function loadPosition(pos) {
|
||||
board = [];
|
||||
for (let r = 0; r < 8; r++) {
|
||||
board[r] = [];
|
||||
for (let c = 0; c < 8; c++) {
|
||||
board[r][c] = pos[r][c] === ' ' ? null : pos[r][c];
|
||||
}
|
||||
}
|
||||
currentPlayer = 'white';
|
||||
updateBoard();
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
function showHint() {
|
||||
if (!currentExercise) {
|
||||
alert('💡 Tipps sind nur im Übungsmodus verfügbar!');
|
||||
return;
|
||||
}
|
||||
alert('💡 ' + currentExercise.hint);
|
||||
}
|
||||
|
||||
function endGame(winner) {
|
||||
document.getElementById('winnerText').textContent = `🎉 ${winner} gewinnt!`;
|
||||
document.getElementById('gameOver').style.display = 'block';
|
||||
}
|
||||
|
||||
function newGame() {
|
||||
document.getElementById('gameOver').style.display = 'none';
|
||||
document.getElementById('promotionModal').style.display = 'none';
|
||||
waitingPromotion = null;
|
||||
selected = null;
|
||||
validMoves = [];
|
||||
lastMove = null;
|
||||
currentPlayer = 'white';
|
||||
currentExercise = null;
|
||||
initBoard();
|
||||
}
|
||||
|
||||
// Start
|
||||
initBoard();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,210 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Simon Says - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 20px;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 10px; font-size: 28px; }
|
||||
.score { color: #4ade80; font-size: 20px; margin-bottom: 20px; }
|
||||
.game-board {
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
|
||||
width: 300px; height: 300px; margin: 20px 0;
|
||||
}
|
||||
.color-btn {
|
||||
border: none; border-radius: 20px; cursor: pointer;
|
||||
opacity: 0.7; transition: all 0.1s;
|
||||
}
|
||||
.color-btn.active { opacity: 1; transform: scale(0.95); box-shadow: 0 0 30px currentColor; }
|
||||
.color-btn:active { transform: scale(0.9); }
|
||||
.green { background: #22c55e; }
|
||||
.red { background: #ef4444; }
|
||||
.yellow { background: #eab308; }
|
||||
.blue { background: #3b82f6; }
|
||||
|
||||
.center-btn {
|
||||
position: absolute; width: 80px; height: 80px;
|
||||
background: #1a1a2e; border: 4px solid white; border-radius: 50%;
|
||||
color: white; font-size: 14px; cursor: pointer;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.game-area { position: relative; }
|
||||
|
||||
.status { color: white; font-size: 24px; margin: 20px 0; min-height: 40px; }
|
||||
.controls { display: flex; gap: 15px; margin-top: 20px; }
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 15px 30px;
|
||||
font-size: 18px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; }
|
||||
.btn-start { background: #4ade80; color: white; }
|
||||
|
||||
.highscore { color: #feca57; margin-top: 10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🎵 Simon Says</h1>
|
||||
<div class="score">Level: <span id="level">0</span></div>
|
||||
<div class="highscore">Rekord: <span id="highscore">0</span></div>
|
||||
|
||||
<div class="game-area">
|
||||
<div class="game-board">
|
||||
<button class="color-btn green" data-color="0" onclick="playerClick(0)"></button>
|
||||
<button class="color-btn red" data-color="1" onclick="playerClick(1)"></button>
|
||||
<button class="color-btn yellow" data-color="2" onclick="playerClick(2)"></button>
|
||||
<button class="color-btn blue" data-color="3" onclick="playerClick(3)"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status" id="status">Drücke Start!</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn btn-start" id="startBtn" onclick="startGame()">▶️ Start</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const colors = [
|
||||
{ name: 'green', sound: 523.25 }, // C5
|
||||
{ name: 'red', sound: 659.25 }, // E5
|
||||
{ name: 'yellow', sound: 783.99 }, // G5
|
||||
{ name: 'blue', sound: 987.77 } // B5
|
||||
];
|
||||
|
||||
let sequence = [];
|
||||
let playerSequence = [];
|
||||
let level = 0;
|
||||
let highscore = localStorage.getItem('simon_highscore') || 0;
|
||||
let gameActive = false;
|
||||
let audioCtx = null;
|
||||
|
||||
document.getElementById('highscore').textContent = highscore;
|
||||
|
||||
function initAudio() {
|
||||
if (!audioCtx) {
|
||||
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
}
|
||||
}
|
||||
|
||||
function playTone(freq, duration = 300) {
|
||||
if (!audioCtx) return;
|
||||
const osc = audioCtx.createOscillator();
|
||||
const gain = audioCtx.createGain();
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
osc.frequency.value = freq;
|
||||
gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration / 1000);
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime + duration / 1000);
|
||||
}
|
||||
|
||||
function flashColor(colorIndex, duration = 400) {
|
||||
const btn = document.querySelector(`[data-color="${colorIndex}"]`);
|
||||
btn.classList.add('active');
|
||||
playTone(colors[colorIndex].sound);
|
||||
setTimeout(() => btn.classList.remove('active'), duration);
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
initAudio();
|
||||
sequence = [];
|
||||
playerSequence = [];
|
||||
level = 0;
|
||||
gameActive = true;
|
||||
document.getElementById('level').textContent = level;
|
||||
document.getElementById('status').textContent = 'Schau genau hin!';
|
||||
document.getElementById('startBtn').textContent = '🔄 Neustart';
|
||||
|
||||
setTimeout(nextRound, 1000);
|
||||
}
|
||||
|
||||
function nextRound() {
|
||||
playerSequence = [];
|
||||
level++;
|
||||
document.getElementById('level').textContent = level;
|
||||
document.getElementById('status').textContent = `Level ${level} - Beobachte!`;
|
||||
|
||||
sequence.push(Math.floor(Math.random() * 4));
|
||||
|
||||
let i = 0;
|
||||
const playSequence = () => {
|
||||
if (i < sequence.length) {
|
||||
flashColor(sequence[i], 500);
|
||||
i++;
|
||||
setTimeout(playSequence, 700);
|
||||
} else {
|
||||
document.getElementById('status').textContent = 'Dein Zug!';
|
||||
}
|
||||
};
|
||||
setTimeout(playSequence, 500);
|
||||
}
|
||||
|
||||
function playerClick(color) {
|
||||
if (!gameActive) {
|
||||
flashColor(color, 200);
|
||||
return;
|
||||
}
|
||||
|
||||
flashColor(color, 300);
|
||||
playerSequence.push(color);
|
||||
|
||||
const currentIndex = playerSequence.length - 1;
|
||||
|
||||
if (playerSequence[currentIndex] !== sequence[currentIndex]) {
|
||||
// Falsch
|
||||
gameOver();
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerSequence.length === sequence.length) {
|
||||
// Richtig!
|
||||
document.getElementById('status').textContent = '🎉 Super!';
|
||||
setTimeout(nextRound, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function gameOver() {
|
||||
gameActive = false;
|
||||
document.getElementById('status').textContent = `💥 Game Over! Level ${level}`;
|
||||
|
||||
// Fehler-Ton
|
||||
if (audioCtx) {
|
||||
const osc = audioCtx.createOscillator();
|
||||
const gain = audioCtx.createGain();
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
osc.frequency.value = 150;
|
||||
gain.gain.setValueAtTime(0.5, audioCtx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.5);
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime + 0.5);
|
||||
}
|
||||
|
||||
if (level > highscore) {
|
||||
highscore = level;
|
||||
localStorage.setItem('simon_highscore', highscore);
|
||||
document.getElementById('highscore').textContent = highscore;
|
||||
document.getElementById('status').textContent = `🏆 Neuer Rekord! Level ${level}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Tastatur-Steuerung
|
||||
document.addEventListener('keydown', (e) => {
|
||||
switch(e.key) {
|
||||
case 'ArrowUp': playerClick(0); break;
|
||||
case 'ArrowRight': playerClick(1); break;
|
||||
case 'ArrowDown': playerClick(2); break;
|
||||
case 'ArrowLeft': playerClick(3); break;
|
||||
case ' ': startGame(); break;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,302 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Snake - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; touch-action: manipulation; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #1a472a 0%, #2d5016 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 5px;
|
||||
}
|
||||
h1 { color: #4ade80; margin: 5px 0; font-size: 22px; }
|
||||
.score-board { color: white; font-size: 16px; margin-bottom: 10px; text-align: center; }
|
||||
#gameCanvas {
|
||||
background: #0f291e; border: 3px solid #4ade80; border-radius: 8px;
|
||||
box-shadow: 0 0 15px rgba(74, 222, 128, 0.3); display: block; margin: 0 auto;
|
||||
}
|
||||
.main-area { display: flex; flex-direction: column; align-items: center; gap: 10px; }
|
||||
|
||||
.controls {
|
||||
display: flex; gap: 10px; margin: 10px 0; flex-wrap: wrap; justify-content: center;
|
||||
}
|
||||
.btn {
|
||||
background: linear-gradient(135deg, #4ade80, #22c55e); border: none; border-radius: 8px;
|
||||
padding: 12px 20px; font-size: 16px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
color: white; box-shadow: 0 3px 8px rgba(0,0,0,0.3);
|
||||
}
|
||||
.btn:active { transform: scale(0.95); }
|
||||
.btn-back { background: #ff6b6b; text-decoration: none; }
|
||||
|
||||
/* Big Touch Controls */
|
||||
.touch-controls-area {
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
gap: 5px; margin-top: 10px;
|
||||
}
|
||||
.d-pad {
|
||||
display: grid;
|
||||
grid-template-columns: 80px 80px 80px;
|
||||
gap: 8px;
|
||||
}
|
||||
.d-btn {
|
||||
width: 80px; height: 80px;
|
||||
background: rgba(255,255,255,0.15);
|
||||
border: 3px solid #4ade80;
|
||||
border-radius: 15px;
|
||||
font-size: 36px;
|
||||
color: #4ade80;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
.d-btn:active {
|
||||
background: rgba(74, 222, 128, 0.4);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
.d-btn.center {
|
||||
background: rgba(74, 222, 128, 0.25);
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
@media (min-width: 700px) {
|
||||
.main-area { flex-direction: row; align-items: flex-start; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🐍 Snake</h1>
|
||||
<div class="score-board">Punkte: <span id="score">0</span> | Highscore: <span id="highscore">0</span></div>
|
||||
|
||||
<div class="main-area">
|
||||
<canvas id="gameCanvas" width="360" height="360"></canvas>
|
||||
|
||||
|
||||
<div class="controls-panel">
|
||||
<div class="controls">
|
||||
<button class="btn" id="startBtn" onclick="startGame()">▶️ Start</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<!-- Touch Controls -->
|
||||
<div class="touch-controls-area">
|
||||
<div class="d-pad">
|
||||
<div></div>
|
||||
<button class="d-btn" ontouchstart="changeDirection('up'); return false;" onclick="changeDirection('up')">▲</button>
|
||||
<div></div>
|
||||
|
||||
<button class="d-btn" ontouchstart="changeDirection('left'); return false;" onclick="changeDirection('left')">◀</button>
|
||||
<button class="d-btn center" onclick="startGame()">●</button>
|
||||
<button class="d-btn" ontouchstart="changeDirection('right'); return false;" onclick="changeDirection('right')">▶</button>
|
||||
|
||||
<div></div>
|
||||
<button class="d-btn" ontouchstart="changeDirection('down'); return false;" onclick="changeDirection('down')">▼</button>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const canvas = document.getElementById('gameCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Game variables
|
||||
const gridSize = 18;
|
||||
const tileCount = canvas.width / gridSize;
|
||||
|
||||
let snake = [];
|
||||
let food = {};
|
||||
let direction = 'right';
|
||||
let nextDirection = 'right';
|
||||
let score = 0;
|
||||
let highscore = localStorage.getItem('snake_highscore') || 0;
|
||||
let gameRunning = false;
|
||||
let gameLoopId;
|
||||
|
||||
document.getElementById('highscore').textContent = highscore;
|
||||
|
||||
function initGame() {
|
||||
snake = [
|
||||
{x: 5, y: 10},
|
||||
{x: 4, y: 10},
|
||||
{x: 3, y: 10}
|
||||
];
|
||||
direction = 'right';
|
||||
nextDirection = 'right';
|
||||
score = 0;
|
||||
document.getElementById('score').textContent = score;
|
||||
spawnFood();
|
||||
}
|
||||
|
||||
function spawnFood() {
|
||||
food = {
|
||||
x: Math.floor(Math.random() * tileCount),
|
||||
y: Math.floor(Math.random() * tileCount)
|
||||
};
|
||||
for (let segment of snake) {
|
||||
if (segment.x === food.x && segment.y === food.y) {
|
||||
spawnFood();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function changeDirection(newDir) {
|
||||
if (!gameRunning) {
|
||||
startGame();
|
||||
return;
|
||||
}
|
||||
|
||||
const opposites = {up: 'down', down: 'up', left: 'right', right: 'left'};
|
||||
if (opposites[newDir] !== direction) {
|
||||
nextDirection = newDir;
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
if (!gameRunning) return;
|
||||
|
||||
direction = nextDirection;
|
||||
|
||||
const head = {...snake[0]};
|
||||
|
||||
switch(direction) {
|
||||
case 'up': head.y--; break;
|
||||
case 'down': head.y++; break;
|
||||
case 'left': head.x--; break;
|
||||
case 'right': head.x++; break;
|
||||
}
|
||||
|
||||
// Wall collision
|
||||
if (head.x < 0 || head.x >= tileCount || head.y < 0 || head.y >= tileCount) {
|
||||
gameOver();
|
||||
return;
|
||||
}
|
||||
|
||||
// Self collision
|
||||
for (let segment of snake) {
|
||||
if (head.x === segment.x && head.y === segment.y) {
|
||||
gameOver();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
snake.unshift(head);
|
||||
|
||||
// Check food
|
||||
if (head.x === food.x && head.y === food.y) {
|
||||
score += 10;
|
||||
document.getElementById('score').textContent = score;
|
||||
spawnFood();
|
||||
} else {
|
||||
snake.pop();
|
||||
}
|
||||
}
|
||||
|
||||
function draw() {
|
||||
// Background
|
||||
ctx.fillStyle = '#0f291e';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Food
|
||||
ctx.fillStyle = '#ff6b6b';
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
food.x * gridSize + gridSize/2,
|
||||
food.y * gridSize + gridSize/2,
|
||||
gridSize/2 - 2,
|
||||
0, Math.PI * 2
|
||||
);
|
||||
ctx.fill();
|
||||
|
||||
// Snake
|
||||
snake.forEach((segment, index) => {
|
||||
ctx.fillStyle = index === 0 ? '#4ade80' : '#22c55e';
|
||||
ctx.fillRect(
|
||||
segment.x * gridSize + 1,
|
||||
segment.y * gridSize + 1,
|
||||
gridSize - 2,
|
||||
gridSize - 2
|
||||
);
|
||||
|
||||
// Eyes for head
|
||||
if (index === 0) {
|
||||
ctx.fillStyle = '#0f291e';
|
||||
let eyeX1 = segment.x * gridSize + 4;
|
||||
let eyeY1 = segment.y * gridSize + 5;
|
||||
let eyeX2 = segment.x * gridSize + 11;
|
||||
let eyeY2 = segment.y * gridSize + 5;
|
||||
|
||||
if (direction === 'up') { eyeY1 -= 2; eyeY2 -= 2; }
|
||||
if (direction === 'down') { eyeY1 += 2; eyeY2 += 2; }
|
||||
if (direction === 'left') { eyeX1 -= 2; eyeX2 -= 2; }
|
||||
if (direction === 'right') { eyeX1 += 2; eyeX2 += 2; }
|
||||
|
||||
ctx.fillRect(eyeX1, eyeY1, 3, 3);
|
||||
ctx.fillRect(eyeX2, eyeY2, 3, 3);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function gameLoop() {
|
||||
update();
|
||||
draw();
|
||||
gameLoopId = setTimeout(gameLoop, 100);
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
if (gameRunning) {
|
||||
// Neustart
|
||||
clearTimeout(gameLoopId);
|
||||
}
|
||||
initGame();
|
||||
gameRunning = true;
|
||||
document.getElementById('startBtn').textContent = '🔄 Neustart';
|
||||
gameLoop();
|
||||
}
|
||||
|
||||
function gameOver() {
|
||||
gameRunning = false;
|
||||
clearTimeout(gameLoopId);
|
||||
|
||||
if (score > highscore) {
|
||||
highscore = score;
|
||||
localStorage.setItem('snake_highscore', highscore);
|
||||
document.getElementById('highscore').textContent = highscore;
|
||||
}
|
||||
|
||||
// Draw Game Over
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.7)';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.font = 'bold 30px Comic Sans MS';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('Game Over!', canvas.width/2, canvas.height/2 - 20);
|
||||
ctx.font = '20px Comic Sans MS';
|
||||
ctx.fillText('Punkte: ' + score, canvas.width/2, canvas.height/2 + 20);
|
||||
|
||||
document.getElementById('startBtn').textContent = '▶️ Nochmal';
|
||||
}
|
||||
|
||||
// Keyboard controls
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (!gameRunning) return;
|
||||
|
||||
switch(e.key) {
|
||||
case 'ArrowUp': case 'w': e.preventDefault(); changeDirection('up'); break;
|
||||
case 'ArrowDown': case 's': e.preventDefault(); changeDirection('down'); break;
|
||||
case 'ArrowLeft': case 'a': e.preventDefault(); changeDirection('left'); break;
|
||||
case 'ArrowRight': case 'd': e.preventDefault(); changeDirection('right'); break;
|
||||
}
|
||||
});
|
||||
|
||||
// Initial draw
|
||||
initGame();
|
||||
draw();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,207 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Tennis - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; overflow: hidden;
|
||||
}
|
||||
h1 { color: white; margin: 10px 0; font-size: 24px; }
|
||||
.score { color: white; font-size: 20px; margin-bottom: 10px; }
|
||||
#gameCanvas {
|
||||
background: #4ade80; border: 4px solid white; border-radius: 10px;
|
||||
box-shadow: 0 0 20px rgba(0,0,0,0.3);
|
||||
}
|
||||
.controls {
|
||||
display: flex; gap: 20px; margin-top: 15px; flex-wrap: wrap; justify-content: center;
|
||||
}
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 15px 30px;
|
||||
font-size: 20px; cursor: pointer; box-shadow: 0 4px 10px rgba(0,0,0,0.2);
|
||||
font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn:active { transform: scale(0.95); }
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; display: inline-block; }
|
||||
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.instructions { color: white; margin-top: 10px; text-align: center; opacity: 0.9; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🎾 Tennis</h1>
|
||||
<div class="score">Spieler: <span id="playerScore">0</span> | Computer: <span id="computerScore">0</span></div>
|
||||
<canvas id="gameCanvas"></canvas>
|
||||
<div class="controls">
|
||||
<button class="btn" id="startBtn" onclick="startGame()">▶️ Start</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
<p class="instructions">💡 Klicke/Tippe auf den Bildschirm, um das Paddle zu bewegen!</p>
|
||||
|
||||
<script>
|
||||
const canvas = document.getElementById('gameCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Responsive Größe
|
||||
function resizeCanvas() {
|
||||
const maxWidth = Math.min(window.innerWidth - 20, 600);
|
||||
const maxHeight = Math.min(window.innerHeight - 200, 400);
|
||||
canvas.width = maxWidth;
|
||||
canvas.height = maxHeight;
|
||||
}
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
// Spiel-Variablen
|
||||
let gameRunning = false;
|
||||
let playerScore = 0;
|
||||
let computerScore = 0;
|
||||
let animationId;
|
||||
|
||||
// Paddle
|
||||
const paddleWidth = 10;
|
||||
const paddleHeight = 80;
|
||||
let playerY = canvas.height / 2 - paddleHeight / 2;
|
||||
let computerY = canvas.height / 2 - paddleHeight / 2;
|
||||
const computerSpeed = 3;
|
||||
|
||||
// Ball
|
||||
const ballSize = 10;
|
||||
let ballX = canvas.width / 2;
|
||||
let ballY = canvas.height / 2;
|
||||
let ballSpeedX = 5;
|
||||
let ballSpeedY = 3;
|
||||
|
||||
// Maus/Touch Position
|
||||
let targetY = canvas.height / 2;
|
||||
|
||||
// Input-Handler
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
targetY = e.clientY - rect.top - paddleHeight / 2;
|
||||
});
|
||||
|
||||
canvas.addEventListener('touchmove', (e) => {
|
||||
e.preventDefault();
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
targetY = e.touches[0].clientY - rect.top - paddleHeight / 2;
|
||||
}, { passive: false });
|
||||
|
||||
canvas.addEventListener('click', () => {
|
||||
if (!gameRunning) startGame();
|
||||
});
|
||||
|
||||
function resetBall() {
|
||||
ballX = canvas.width / 2;
|
||||
ballY = canvas.height / 2;
|
||||
ballSpeedX = (Math.random() > 0.5 ? 1 : -1) * (4 + Math.random() * 2);
|
||||
ballSpeedY = (Math.random() * 2 - 1) * 4;
|
||||
}
|
||||
|
||||
function update() {
|
||||
if (!gameRunning) return;
|
||||
|
||||
// Spieler-Paddle bewegen (smooth)
|
||||
playerY += (targetY - playerY) * 0.2;
|
||||
playerY = Math.max(0, Math.min(canvas.height - paddleHeight, playerY));
|
||||
|
||||
// Computer-Paddle bewegen
|
||||
const computerCenter = computerY + paddleHeight / 2;
|
||||
if (computerCenter < ballY - 10) computerY += computerSpeed;
|
||||
else if (computerCenter > ballY + 10) computerY -= computerSpeed;
|
||||
computerY = Math.max(0, Math.min(canvas.height - paddleHeight, computerY));
|
||||
|
||||
// Ball bewegen
|
||||
ballX += ballSpeedX;
|
||||
ballY += ballSpeedY;
|
||||
|
||||
// Wände
|
||||
if (ballY <= 0 || ballY >= canvas.height - ballSize) {
|
||||
ballSpeedY = -ballSpeedY;
|
||||
}
|
||||
|
||||
// Spieler-Paddle Kollision
|
||||
if (ballX <= paddleWidth + ballSize &&
|
||||
ballY > playerY && ballY < playerY + paddleHeight) {
|
||||
ballSpeedX = -ballSpeedX * 1.05;
|
||||
ballSpeedY += (ballY - (playerY + paddleHeight / 2)) * 0.1;
|
||||
}
|
||||
|
||||
// Computer-Paddle Kollision
|
||||
if (ballX >= canvas.width - paddleWidth - ballSize &&
|
||||
ballY > computerY && ballY < computerY + paddleHeight) {
|
||||
ballSpeedX = -ballSpeedX * 1.05;
|
||||
ballSpeedY += (ballY - (computerY + paddleHeight / 2)) * 0.1;
|
||||
}
|
||||
|
||||
// Punkt
|
||||
if (ballX < 0) {
|
||||
computerScore++;
|
||||
document.getElementById('computerScore').textContent = computerScore;
|
||||
resetBall();
|
||||
} else if (ballX > canvas.width) {
|
||||
playerScore++;
|
||||
document.getElementById('playerScore').textContent = playerScore;
|
||||
resetBall();
|
||||
}
|
||||
}
|
||||
|
||||
function draw() {
|
||||
// Hintergrund
|
||||
ctx.fillStyle = '#4ade80';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Mittellinie
|
||||
ctx.strokeStyle = 'rgba(255,255,255,0.5)';
|
||||
ctx.setLineDash([10, 10]);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(canvas.width / 2, 0);
|
||||
ctx.lineTo(canvas.width / 2, canvas.height);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// Paddles
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillRect(0, playerY, paddleWidth, paddleHeight);
|
||||
ctx.fillRect(canvas.width - paddleWidth, computerY, paddleWidth, paddleHeight);
|
||||
|
||||
// Ball
|
||||
ctx.beginPath();
|
||||
ctx.arc(ballX, ballY, ballSize, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#ff6b6b';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = 'white';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
// Start-Hinweis
|
||||
if (!gameRunning) {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.5)';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.font = 'bold 24px Comic Sans MS';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('Klicke auf Start!', canvas.width / 2, canvas.height / 2);
|
||||
}
|
||||
}
|
||||
|
||||
function gameLoop() {
|
||||
update();
|
||||
draw();
|
||||
animationId = requestAnimationFrame(gameLoop);
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
gameRunning = true;
|
||||
document.getElementById('startBtn').textContent = '🔄 Neustart';
|
||||
resetBall();
|
||||
}
|
||||
|
||||
// Start
|
||||
gameLoop();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,341 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Tetris - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; touch-action: manipulation; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 5px;
|
||||
}
|
||||
h1 { color: white; margin: 5px 0; font-size: 20px; }
|
||||
.score-board { color: #4ade80; font-size: 16px; margin-bottom: 5px; }
|
||||
.game-container { display: flex; gap: 10px; align-items: flex-start; flex-wrap: wrap; justify-content: center; }
|
||||
#gameCanvas {
|
||||
background: #0f0f23; border: 3px solid #4ade80; border-radius: 8px;
|
||||
box-shadow: 0 0 15px rgba(74, 222, 128, 0.3); touch-action: none;
|
||||
}
|
||||
.side-panel { display: flex; flex-direction: column; gap: 10px; }
|
||||
.next-piece {
|
||||
background: #1a1a2e; border: 2px solid #4ade80; border-radius: 8px;
|
||||
padding: 8px; text-align: center;
|
||||
}
|
||||
.next-piece h3 { color: white; margin-bottom: 5px; font-size: 12px; }
|
||||
#nextCanvas { background: #0f0f23; }
|
||||
.controls-panel {
|
||||
display: flex; flex-direction: column; gap: 8px;
|
||||
}
|
||||
.btn {
|
||||
background: linear-gradient(135deg, #4ade80, #22c55e); border: none; border-radius: 8px;
|
||||
padding: 10px 15px; font-size: 14px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
color: white; box-shadow: 0 3px 8px rgba(0,0,0,0.3);
|
||||
}
|
||||
.btn:active { transform: scale(0.95); }
|
||||
.btn-back { background: #ff6b6b; text-decoration: none; display: inline-block; text-align: center; }
|
||||
|
||||
/* Touch Controls - D-Pad Style */
|
||||
.touch-controls {
|
||||
display: grid;
|
||||
grid-template-columns: 60px 60px 60px;
|
||||
gap: 5px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.touch-btn {
|
||||
width: 60px; height: 60px;
|
||||
background: rgba(255,255,255,0.15);
|
||||
border: 2px solid #4ade80;
|
||||
border-radius: 10px;
|
||||
font-size: 28px;
|
||||
color: #4ade80;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
.touch-btn:active {
|
||||
background: rgba(74, 222, 128, 0.3);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
.touch-btn.action {
|
||||
background: rgba(74, 222, 128, 0.25);
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
h1 { font-size: 24px; }
|
||||
.score-board { font-size: 18px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🧱 Tetris</h1>
|
||||
<div class="score-board">Punkte: <span id="score">0</span> | Level: <span id="level">1</span></div>
|
||||
|
||||
<div class="game-container">
|
||||
<canvas id="gameCanvas" width="240" height="480"></canvas>
|
||||
|
||||
<div class="side-panel">
|
||||
<div class="next-piece">
|
||||
<h3>Nächstes</h3>
|
||||
<canvas id="nextCanvas" width="80" height="80"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="controls-panel">
|
||||
<button class="btn" id="startBtn" onclick="startGame()">▶️ Start</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<!-- Touch Controls -->
|
||||
<div class="touch-controls">
|
||||
<div></div>
|
||||
<button class="touch-btn" ontouchstart="rotatePiece(); return false;" onclick="rotatePiece()">↻</button>
|
||||
<div></div>
|
||||
<button class="touch-btn" ontouchstart="movePiece(-1, 0); return false;" onclick="movePiece(-1, 0)">◀</button>
|
||||
<button class="touch-btn action" ontouchstart="hardDrop(); return false;" onclick="hardDrop()">⏬</button>
|
||||
<button class="touch-btn" ontouchstart="movePiece(1, 0); return false;" onclick="movePiece(1, 0)">▶</button>
|
||||
<div></div>
|
||||
<button class="touch-btn" ontouchstart="movePiece(0, 1); return false;" onclick="movePiece(0, 1)">▼</button>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const canvas = document.getElementById('gameCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const nextCanvas = document.getElementById('nextCanvas');
|
||||
const nextCtx = nextCanvas.getContext('2d');
|
||||
|
||||
// Game variables
|
||||
const COLS = 10;
|
||||
const ROWS = 20;
|
||||
const BLOCK_SIZE = 24;
|
||||
|
||||
let board = Array(ROWS).fill(null).map(() => Array(COLS).fill(0));
|
||||
let score = 0;
|
||||
let level = 1;
|
||||
let gameRunning = false;
|
||||
let dropInterval = 1000;
|
||||
let lastDrop = 0;
|
||||
let animationId;
|
||||
|
||||
// Tetrominos
|
||||
const PIECES = [
|
||||
[[1,1,1,1]], [[1,1],[1,1]], [[0,1,0],[1,1,1]],
|
||||
[[1,1,0],[0,1,1]], [[0,1,1],[1,1,0]],
|
||||
[[1,0,0],[1,1,1]], [[0,0,1],[1,1,1]]
|
||||
];
|
||||
|
||||
const COLORS = ['#00f0f0', '#f0f000', '#a000f0', '#00f000', '#f00000', '#f0a000', '#0000f0'];
|
||||
|
||||
let currentPiece = null;
|
||||
let nextPiece = null;
|
||||
let currentX = 0, currentY = 0, currentColor = 0;
|
||||
|
||||
function createPiece() {
|
||||
const type = Math.floor(Math.random() * PIECES.length);
|
||||
return { shape: PIECES[type], color: type };
|
||||
}
|
||||
|
||||
function spawnPiece() {
|
||||
currentPiece = nextPiece || createPiece();
|
||||
nextPiece = createPiece();
|
||||
currentX = Math.floor(COLS / 2) - Math.floor(currentPiece.shape[0].length / 2);
|
||||
currentY = 0;
|
||||
currentColor = currentPiece.color;
|
||||
|
||||
drawNext();
|
||||
|
||||
if (collision(currentPiece.shape, currentX, currentY)) {
|
||||
gameOver();
|
||||
}
|
||||
}
|
||||
|
||||
function drawNext() {
|
||||
nextCtx.fillStyle = '#0f0f23';
|
||||
nextCtx.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
|
||||
|
||||
if (!nextPiece) return;
|
||||
|
||||
const blockSize = 18;
|
||||
const offsetX = (nextCanvas.width - nextPiece.shape[0].length * blockSize) / 2;
|
||||
const offsetY = (nextCanvas.height - nextPiece.shape.length * blockSize) / 2;
|
||||
|
||||
nextCtx.fillStyle = COLORS[nextPiece.color];
|
||||
nextCtx.strokeStyle = 'white';
|
||||
|
||||
for (let y = 0; y < nextPiece.shape.length; y++) {
|
||||
for (let x = 0; x < nextPiece.shape[y].length; x++) {
|
||||
if (nextPiece.shape[y][x]) {
|
||||
nextCtx.fillRect(offsetX + x * blockSize, offsetY + y * blockSize, blockSize - 1, blockSize - 1);
|
||||
nextCtx.strokeRect(offsetX + x * blockSize, offsetY + y * blockSize, blockSize - 1, blockSize - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collision(shape, x, y) {
|
||||
for (let row = 0; row < shape.length; row++) {
|
||||
for (let col = 0; col < shape[row].length; col++) {
|
||||
if (shape[row][col]) {
|
||||
const newX = x + col;
|
||||
const newY = y + row;
|
||||
if (newX < 0 || newX >= COLS || newY >= ROWS) return true;
|
||||
if (newY >= 0 && board[newY][newX]) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function mergePiece() {
|
||||
for (let y = 0; y < currentPiece.shape.length; y++) {
|
||||
for (let x = 0; x < currentPiece.shape[y].length; x++) {
|
||||
if (currentPiece.shape[y][x]) {
|
||||
const boardY = currentY + y;
|
||||
const boardX = currentX + x;
|
||||
if (boardY >= 0) board[boardY][boardX] = currentColor + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearLines() {
|
||||
let linesCleared = 0;
|
||||
for (let y = ROWS - 1; y >= 0; y--) {
|
||||
if (board[y].every(cell => cell !== 0)) {
|
||||
board.splice(y, 1);
|
||||
board.unshift(Array(COLS).fill(0));
|
||||
linesCleared++;
|
||||
y++;
|
||||
}
|
||||
}
|
||||
|
||||
if (linesCleared > 0) {
|
||||
score += linesCleared * 100 * level;
|
||||
level = Math.floor(score / 1000) + 1;
|
||||
dropInterval = Math.max(100, 1000 - (level - 1) * 80);
|
||||
document.getElementById('score').textContent = score;
|
||||
document.getElementById('level').textContent = level;
|
||||
}
|
||||
}
|
||||
|
||||
function rotatePiece() {
|
||||
if (!gameRunning || !currentPiece) return;
|
||||
|
||||
const rotated = currentPiece.shape[0].map((_, i) =>
|
||||
currentPiece.shape.map(row => row[i]).reverse()
|
||||
);
|
||||
|
||||
if (!collision(rotated, currentX, currentY)) {
|
||||
currentPiece.shape = rotated;
|
||||
}
|
||||
}
|
||||
|
||||
function movePiece(dx, dy) {
|
||||
if (!gameRunning || !currentPiece) return;
|
||||
|
||||
const newX = currentX + dx;
|
||||
const newY = currentY + dy;
|
||||
|
||||
if (!collision(currentPiece.shape, newX, newY)) {
|
||||
currentX = newX;
|
||||
currentY = newY;
|
||||
} else if (dy > 0) {
|
||||
mergePiece();
|
||||
clearLines();
|
||||
spawnPiece();
|
||||
}
|
||||
}
|
||||
|
||||
function hardDrop() {
|
||||
if (!gameRunning) return;
|
||||
while (!collision(currentPiece.shape, currentX, currentY + 1)) {
|
||||
currentY++;
|
||||
score += 2;
|
||||
}
|
||||
document.getElementById('score').textContent = score;
|
||||
movePiece(0, 1);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
// Hintergrund
|
||||
ctx.fillStyle = '#0f0f23';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Board
|
||||
for (let y = 0; y < ROWS; y++) {
|
||||
for (let x = 0; x < COLS; x++) {
|
||||
if (board[y][x]) {
|
||||
ctx.fillStyle = COLORS[board[y][x] - 1];
|
||||
ctx.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Aktuelles Piece
|
||||
if (currentPiece) {
|
||||
ctx.fillStyle = COLORS[currentColor];
|
||||
for (let y = 0; y < currentPiece.shape.length; y++) {
|
||||
for (let x = 0; x < currentPiece.shape[y].length; x++) {
|
||||
if (currentPiece.shape[y][x]) {
|
||||
const px = (currentX + x) * BLOCK_SIZE;
|
||||
const py = (currentY + y) * BLOCK_SIZE;
|
||||
ctx.fillRect(px, py, BLOCK_SIZE - 1, BLOCK_SIZE - 1);
|
||||
ctx.strokeStyle = 'rgba(255,255,255,0.3)';
|
||||
ctx.strokeRect(px, py, BLOCK_SIZE - 1, BLOCK_SIZE - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function gameLoop(timestamp) {
|
||||
if (gameRunning) {
|
||||
if (timestamp - lastDrop > dropInterval) {
|
||||
movePiece(0, 1);
|
||||
lastDrop = timestamp;
|
||||
}
|
||||
draw();
|
||||
}
|
||||
animationId = requestAnimationFrame(gameLoop);
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
board = Array(ROWS).fill(null).map(() => Array(COLS).fill(0));
|
||||
score = 0;
|
||||
level = 1;
|
||||
dropInterval = 1000;
|
||||
gameRunning = true;
|
||||
document.getElementById('score').textContent = score;
|
||||
document.getElementById('level').textContent = level;
|
||||
document.getElementById('startBtn').textContent = '🔄 Neustart';
|
||||
spawnPiece();
|
||||
}
|
||||
|
||||
function gameOver() {
|
||||
gameRunning = false;
|
||||
alert('Game Over! Punkte: ' + score);
|
||||
}
|
||||
|
||||
// Keyboard controls
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (!gameRunning) return;
|
||||
|
||||
switch(e.key) {
|
||||
case 'ArrowLeft': case 'a': movePiece(-1, 0); break;
|
||||
case 'ArrowRight': case 'd': movePiece(1, 0); break;
|
||||
case 'ArrowDown': case 's': movePiece(0, 1); break;
|
||||
case 'ArrowUp': case 'w': case ' ': rotatePiece(); break;
|
||||
}
|
||||
});
|
||||
|
||||
// Start
|
||||
gameLoop(0);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,188 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Tierlaute - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 20px;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 15px; font-size: 28px; }
|
||||
.question { color: white; font-size: 22px; margin: 20px 0; text-align: center; }
|
||||
.sound-btn {
|
||||
background: linear-gradient(135deg, #fbbf24, #f59e0b); border: none; border-radius: 50%;
|
||||
width: 120px; height: 120px; font-size: 60px; cursor: pointer;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3); margin: 20px 0;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.sound-btn:hover { transform: scale(1.1); }
|
||||
.sound-btn:active { transform: scale(0.9); }
|
||||
.animals-grid {
|
||||
display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px;
|
||||
max-width: 400px; width: 100%; margin: 20px 0;
|
||||
}
|
||||
.animal-card {
|
||||
background: white; border-radius: 20px; padding: 20px;
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
cursor: pointer; transition: all 0.2s; box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
.animal-card:hover { transform: translateY(-5px); }
|
||||
.animal-card:active { transform: scale(0.95); }
|
||||
.animal-card.correct { background: #86efac; }
|
||||
.animal-card.wrong { background: #fca5a5; }
|
||||
.animal-emoji { font-size: 50px; margin-bottom: 5px; }
|
||||
.animal-name { font-size: 18px; color: #333; font-weight: bold; }
|
||||
.score { color: white; font-size: 20px; margin: 10px 0; }
|
||||
.feedback { font-size: 36px; margin: 10px 0; min-height: 50px; }
|
||||
.controls { display: flex; gap: 15px; margin-top: 20px; }
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 12px 25px;
|
||||
font-size: 16px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🦁 Tierlaute</h1>
|
||||
|
||||
<div class="score">Richtig: <span id="correct">0</span> | Versuche: <span id="total">0</span></div>
|
||||
|
||||
<div class="question">Welches Tier macht dieses Geräusch?</div>
|
||||
|
||||
<button class="sound-btn" onclick="playCurrentSound()">🔊</button>
|
||||
|
||||
<div class="feedback" id="feedback"></div>
|
||||
|
||||
<div class="animals-grid" id="animalsGrid"></div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn" onclick="nextQuestion()">🔄 Nächstes Tier</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const animals = [
|
||||
{ emoji: '🐶', name: 'Hund', sound: 'Wau wau!' },
|
||||
{ emoji: '🐱', name: 'Katze', sound: 'Miau!' },
|
||||
{ emoji: '🐮', name: 'Kuh', sound: 'Muh!' },
|
||||
{ emoji: '🐷', name: 'Schwein', sound: 'Oink oink!' },
|
||||
{ emoji: '🐔', name: 'Huhn', sound: 'Gack gack!' },
|
||||
{ emoji: '🐴', name: 'Pferd', sound: 'Wieher!' },
|
||||
{ emoji: '🐸', name: 'Frosch', sound: 'Quak quak!' },
|
||||
{ emoji: '🦆', name: 'Ente', sound: 'Quack quack!' },
|
||||
{ emoji: '🐘', name: 'Elefant', sound: 'Törööö!' },
|
||||
{ emoji: '🦁', name: 'Löwe', sound: 'Roooar!' },
|
||||
{ emoji: '🐍', name: 'Schlange', sound: 'Zisch!' },
|
||||
{ emoji: '🦉', name: 'Eule', sound: 'Huhuhu!' }
|
||||
];
|
||||
|
||||
let currentAnimal = null;
|
||||
let correctCount = 0;
|
||||
let totalCount = 0;
|
||||
let answered = false;
|
||||
const synth = window.speechSynthesis;
|
||||
|
||||
function initGame() {
|
||||
nextQuestion();
|
||||
}
|
||||
|
||||
function nextQuestion() {
|
||||
answered = false;
|
||||
document.getElementById('feedback').textContent = '';
|
||||
|
||||
// Zufälliges Tier
|
||||
currentAnimal = animals[Math.floor(Math.random() * animals.length)];
|
||||
|
||||
// 4 Optionen (inkl. richtige)
|
||||
let options = [currentAnimal];
|
||||
while (options.length < 4) {
|
||||
const random = animals[Math.floor(Math.random() * animals.length)];
|
||||
if (!options.find(o => o.name === random.name)) {
|
||||
options.push(random);
|
||||
}
|
||||
}
|
||||
|
||||
// Mischen
|
||||
options.sort(() => Math.random() - 0.5);
|
||||
|
||||
// Grid bauen
|
||||
const grid = document.getElementById('animalsGrid');
|
||||
grid.innerHTML = '';
|
||||
|
||||
options.forEach(animal => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'animal-card';
|
||||
card.innerHTML = `
|
||||
<div class="animal-emoji">${animal.emoji}</div>
|
||||
<div class="animal-name">${animal.name}</div>
|
||||
`;
|
||||
card.onclick = () => checkAnswer(animal, card);
|
||||
grid.appendChild(card);
|
||||
});
|
||||
|
||||
// Auto-play nach kurzer Zeit
|
||||
setTimeout(playCurrentSound, 500);
|
||||
}
|
||||
|
||||
function playCurrentSound() {
|
||||
if (!currentAnimal) return;
|
||||
|
||||
// TTS für Tier-Sound
|
||||
const utterance = new SpeechSynthesisUtterance(currentAnimal.sound);
|
||||
utterance.lang = 'de-DE';
|
||||
utterance.rate = 0.8;
|
||||
utterance.pitch = 1.2;
|
||||
synth.speak(utterance);
|
||||
}
|
||||
|
||||
function checkAnswer(selected, cardElement) {
|
||||
if (answered) return;
|
||||
answered = true;
|
||||
|
||||
totalCount++;
|
||||
document.getElementById('total').textContent = totalCount;
|
||||
|
||||
if (selected.name === currentAnimal.name) {
|
||||
correctCount++;
|
||||
document.getElementById('correct').textContent = correctCount;
|
||||
cardElement.classList.add('correct');
|
||||
document.getElementById('feedback').textContent = '🎉 Richtig!';
|
||||
|
||||
const utterance = new SpeechSynthesisUtterance('Richtig! ' + currentAnimal.sound);
|
||||
utterance.lang = 'de-DE';
|
||||
utterance.rate = 0.8;
|
||||
synth.speak(utterance);
|
||||
|
||||
setTimeout(nextQuestion, 2000);
|
||||
} else {
|
||||
cardElement.classList.add('wrong');
|
||||
document.getElementById('feedback').textContent = '❌ Das ist ' + selected.name;
|
||||
|
||||
// Richtige hervorheben
|
||||
const cards = document.querySelectorAll('.animal-card');
|
||||
cards.forEach(card => {
|
||||
if (card.textContent.includes(currentAnimal.name)) {
|
||||
card.classList.add('correct');
|
||||
}
|
||||
});
|
||||
|
||||
const utterance = new SpeechSynthesisUtterance('Das ist ' + selected.name + '. Höre nochmal!');
|
||||
utterance.lang = 'de-DE';
|
||||
synth.speak(utterance);
|
||||
|
||||
setTimeout(() => {
|
||||
answered = false;
|
||||
playCurrentSound();
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
// Init
|
||||
initGame();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,600 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Uno - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #1a202c 0%, #2d3748 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 10px;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 5px; font-size: 24px; }
|
||||
|
||||
.game-info {
|
||||
display: flex; gap: 20px; margin: 10px 0; color: white; font-size: 14px;
|
||||
}
|
||||
.info-item { text-align: center; }
|
||||
.info-value { font-size: 20px; font-weight: bold; color: #feca57; }
|
||||
|
||||
/* Karten-Stapel */
|
||||
.deck-area {
|
||||
display: flex; gap: 30px; margin: 15px 0; align-items: center;
|
||||
}
|
||||
|
||||
.card-pile {
|
||||
width: 80px; height: 120px; background: linear-gradient(135deg, #333, #555);
|
||||
border-radius: 10px; border: 3px solid #fff;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 30px; cursor: pointer; position: relative;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.5);
|
||||
}
|
||||
.card-pile::after {
|
||||
content: ''; position: absolute; top: -3px; left: -3px;
|
||||
width: 80px; height: 120px; background: linear-gradient(135deg, #333, #555);
|
||||
border-radius: 10px; border: 3px solid #fff; z-index: -1;
|
||||
}
|
||||
|
||||
.discard-pile {
|
||||
width: 80px; height: 120px; border-radius: 10px; border: 3px solid #fff;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
font-size: 32px; font-weight: bold; box-shadow: 0 5px 15px rgba(0,0,0,0.5);
|
||||
color: white;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
||||
}
|
||||
.discard-pile.red { background: #E53E3E; }
|
||||
.discard-pile.yellow { background: #D69E2E; }
|
||||
.discard-pile.green { background: #38A169; }
|
||||
.discard-pile.blue { background: #3182CE; }
|
||||
.discard-pile.wild {
|
||||
background: linear-gradient(45deg, #E53E3E, #D69E2E, #38A169, #3182CE);
|
||||
}
|
||||
|
||||
/* Bot-Hand */
|
||||
.bot-hand {
|
||||
display: flex; gap: -20px; margin: 10px 0; flex-wrap: wrap; justify-content: center;
|
||||
}
|
||||
.bot-card {
|
||||
width: 50px; height: 75px; background: linear-gradient(135deg, #333, #000);
|
||||
border: 2px solid #fff; border-radius: 8px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 20px; margin-left: -20px;
|
||||
box-shadow: 2px 2px 5px rgba(0,0,0,0.3);
|
||||
}
|
||||
.bot-card:first-child { margin-left: 0; }
|
||||
|
||||
/* Spieler-Hand */
|
||||
.player-hand {
|
||||
display: flex; gap: 10px; margin: 15px 0; flex-wrap: wrap; justify-content: center;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 70px; height: 100px; border-radius: 10px; border: 3px solid #fff;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
font-size: 28px; font-weight: bold; cursor: pointer;
|
||||
transition: all 0.2s; box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
||||
position: relative;
|
||||
}
|
||||
.card:hover { transform: translateY(-10px); }
|
||||
.card.disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.card:not(.disabled):hover { transform: translateY(-10px) scale(1.1); }
|
||||
|
||||
/* Farben */
|
||||
.card.red { background: #E53E3E; color: white; }
|
||||
.card.yellow { background: #D69E2E; color: white; }
|
||||
.card.green { background: #38A169; color: white; }
|
||||
.card.blue { background: #3182CE; color: white; }
|
||||
.card.wild { background: linear-gradient(45deg, #E53E3E, #D69E2E, #38A169, #3182CE); color: white; }
|
||||
|
||||
.card-value { font-size: 32px; }
|
||||
.card-corner {
|
||||
position: absolute; font-size: 12px;
|
||||
top: 5px; left: 5px;
|
||||
}
|
||||
.card-corner-bottom {
|
||||
position: absolute; font-size: 12px;
|
||||
bottom: 5px; right: 5px; transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.status {
|
||||
color: white; font-size: 16px; margin: 10px 0; text-align: center;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
||||
background: white; padding: 20px; border-radius: 15px;
|
||||
display: none; z-index: 100; text-align: center;
|
||||
box-shadow: 0 10px 50px rgba(0,0,0,0.5);
|
||||
}
|
||||
.color-picker h3 { margin-bottom: 15px; }
|
||||
.color-options { display: flex; gap: 10px; }
|
||||
.color-option {
|
||||
width: 60px; height: 60px; border-radius: 10px; cursor: pointer;
|
||||
border: 3px solid transparent;
|
||||
}
|
||||
.color-option:hover { transform: scale(1.1); border-color: #333; }
|
||||
|
||||
.direction-indicator {
|
||||
font-size: 30px; margin: 5px 0;
|
||||
}
|
||||
|
||||
.controls { display: flex; gap: 15px; margin-top: 15px; }
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 10px 20px;
|
||||
font-size: 14px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-draw { background: #4ade80; color: white; }
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; }
|
||||
|
||||
.game-over {
|
||||
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
||||
background: white; padding: 30px; border-radius: 20px; text-align: center;
|
||||
display: none; z-index: 100; box-shadow: 0 10px 50px rgba(0,0,0,0.5);
|
||||
}
|
||||
.game-over h2 { margin-bottom: 15px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🎴 Uno</h1>
|
||||
|
||||
<div class="game-info">
|
||||
<div class="info-item">
|
||||
<div>Computer</div>
|
||||
<div class="info-value" id="botCardCount">7</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div>Stapel</div>
|
||||
<div class="info-value" id="deckCount">79</div>
|
||||
</div>
|
||||
|
||||
<div class="info-item">
|
||||
<div>Richtung</div>
|
||||
<div class="direction-indicator" id="direction">➡️</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bot-hand" id="botHand"></div>
|
||||
|
||||
<div class="deck-area">
|
||||
<div class="card-pile" id="deck" onclick="drawCard()">
|
||||
🎴
|
||||
</div>
|
||||
|
||||
<div class="discard-pile" id="discard">
|
||||
<span id="topCard">🎴</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="status" id="status">Wähle eine Karte oder ziehe vom Stapel!</div>
|
||||
|
||||
|
||||
<div class="player-hand" id="playerHand"></div>
|
||||
|
||||
|
||||
<div class="color-picker" id="colorPicker">
|
||||
<h3>🎨 Wähle eine Farbe:</h3>
|
||||
<div class="color-options">
|
||||
<div class="color-option" style="background:#E53E3E" onclick="selectColor('red')"></div>
|
||||
<div class="color-option" style="background:#D69E2E" onclick="selectColor('yellow')"></div>
|
||||
<div class="color-option" style="background:#38A169" onclick="selectColor('green')"></div>
|
||||
<div class="color-option" style="background:#3182CE" onclick="selectColor('blue')"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn btn-draw" onclick="drawCard()">📥 Karte ziehen</button>
|
||||
<button class="btn" onclick="newGame()">🔄 Neustart</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="game-over" id="gameOver">
|
||||
<h2 id="winnerText">🎉 Gewonnen!</h2>
|
||||
<button class="btn btn-draw" onclick="newGame()">🔄 Nochmal</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const COLORS = ['red', 'yellow', 'green', 'blue'];
|
||||
const VALUES = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'skip', 'reverse', '+2'];
|
||||
const EMOJIS = {
|
||||
'0': '0', '1': '1', '2': '2', '3': '3', '4': '4',
|
||||
'5': '5', '6': '6', '7': '7', '8': '8', '9': '9',
|
||||
'skip': '🚫', 'reverse': '🔄', '+2': '+2', '+4': '+4', 'wild': '🌈'
|
||||
};
|
||||
|
||||
let deck = [];
|
||||
let playerHand = [];
|
||||
let botHand = [];
|
||||
let discardPile = [];
|
||||
let currentColor = null;
|
||||
let currentValue = null;
|
||||
let isPlayerTurn = true;
|
||||
let direction = 1; // 1 = vorwärts, -1 = rückwärts
|
||||
let cardsToDraw = 0;
|
||||
let waitingForColor = false;
|
||||
let pendingWildCard = null;
|
||||
|
||||
function createDeck() {
|
||||
deck = [];
|
||||
|
||||
// Farbkarten (jede Farbe 0-9, +2, skip, reverse)
|
||||
COLORS.forEach(color => {
|
||||
// Eine 0 pro Farbe
|
||||
deck.push({ color, value: '0', emoji: '0' });
|
||||
|
||||
// Zwei von jeder anderen Karte
|
||||
for (let i = 1; i <= 9; i++) {
|
||||
deck.push({ color, value: i.toString(), emoji: i.toString() });
|
||||
deck.push({ color, value: i.toString(), emoji: i.toString() });
|
||||
}
|
||||
|
||||
// Spezialkarten
|
||||
['skip', 'reverse', '+2'].forEach(val => {
|
||||
deck.push({ color, value: val, emoji: EMOJIS[val] });
|
||||
deck.push({ color, value: val, emoji: EMOJIS[val] });
|
||||
});
|
||||
});
|
||||
|
||||
// Joker (wild und +4)
|
||||
for (let i = 0; i < 4; i++) {
|
||||
deck.push({ color: 'wild', value: 'wild', emoji: '🌈' });
|
||||
deck.push({ color: 'wild', value: '+4', emoji: '+4' });
|
||||
}
|
||||
|
||||
return shuffle(deck);
|
||||
}
|
||||
|
||||
function shuffle(array) {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
function initGame() {
|
||||
deck = createDeck();
|
||||
playerHand = [];
|
||||
botHand = [];
|
||||
discardPile = [];
|
||||
direction = 1;
|
||||
cardsToDraw = 0;
|
||||
waitingForColor = false;
|
||||
|
||||
// Karten austeilen
|
||||
for (let i = 0; i < 7; i++) {
|
||||
playerHand.push(deck.pop());
|
||||
botHand.push(deck.pop());
|
||||
}
|
||||
|
||||
// Startkarte (kein Joker)
|
||||
let startCard;
|
||||
do {
|
||||
startCard = deck.pop();
|
||||
} while (startCard.color === 'wild');
|
||||
|
||||
discardPile.push(startCard);
|
||||
currentColor = startCard.color;
|
||||
currentValue = startCard.value;
|
||||
|
||||
isPlayerTurn = true;
|
||||
|
||||
updateDisplay();
|
||||
document.getElementById('gameOver').style.display = 'none';
|
||||
}
|
||||
|
||||
function updateDisplay() {
|
||||
// Bot-Hand
|
||||
const botHandEl = document.getElementById('botHand');
|
||||
botHandEl.innerHTML = '';
|
||||
botHand.forEach(() => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'bot-card';
|
||||
card.textContent = '🎴';
|
||||
botHandEl.appendChild(card);
|
||||
});
|
||||
|
||||
// Spieler-Hand
|
||||
const playerHandEl = document.getElementById('playerHand');
|
||||
playerHandEl.innerHTML = '';
|
||||
playerHand.forEach((card, idx) => {
|
||||
const cardEl = document.createElement('div');
|
||||
cardEl.className = `card ${card.color}`;
|
||||
|
||||
// Prüfen ob spielbar
|
||||
const canPlay = isPlayerTurn && !waitingForColor &&
|
||||
(card.color === currentColor || card.value === currentValue || card.color === 'wild');
|
||||
|
||||
if (!canPlay) cardEl.classList.add('disabled');
|
||||
|
||||
cardEl.innerHTML = `
|
||||
<span class="card-corner">${card.emoji}</span>
|
||||
<span class="card-value">${card.emoji}</span>
|
||||
<span class="card-corner-bottom">${card.emoji}</span>
|
||||
`;
|
||||
|
||||
if (canPlay) {
|
||||
cardEl.onclick = () => playCard(idx);
|
||||
}
|
||||
|
||||
playerHandEl.appendChild(cardEl);
|
||||
});
|
||||
|
||||
// Oberste Karte
|
||||
const topCard = discardPile[discardPile.length - 1];
|
||||
const discardEl = document.getElementById('discard');
|
||||
const cardColor = topCard.color === 'wild' ? currentColor : topCard.color;
|
||||
discardEl.className = `discard-pile ${cardColor}`;
|
||||
|
||||
// Zeige Farbnamen und Wert
|
||||
const colorNames = { red: '🔴 ROT', yellow: '🟡 GELB', green: '🟢 GRÜN', blue: '🔵 BLAU' };
|
||||
const displayText = topCard.color === 'wild' ? topCard.emoji : `${topCard.emoji}`;
|
||||
document.getElementById('topCard').innerHTML = `
|
||||
<div style="font-size:28px;margin-bottom:5px">${displayText}</div>
|
||||
<div style="font-size:10px;text-transform:uppercase">${colorNames[cardColor] || cardColor}</div>
|
||||
`;
|
||||
|
||||
// Info
|
||||
document.getElementById('botCardCount').textContent = botHand.length;
|
||||
document.getElementById('deckCount').textContent = deck.length;
|
||||
document.getElementById('direction').textContent = direction === 1 ? '➡️' : '⬅️';
|
||||
|
||||
// Status
|
||||
const statusEl = document.getElementById('status');
|
||||
if (waitingForColor) {
|
||||
statusEl.textContent = 'Wähle eine Farbe für den Joker!';
|
||||
} else if (cardsToDraw > 0) {
|
||||
statusEl.textContent = `Du musst ${cardsToDraw} Karten ziehen!`;
|
||||
} else if (isPlayerTurn) {
|
||||
statusEl.textContent = 'Du bist dran! Wähle eine Karte.';
|
||||
} else {
|
||||
statusEl.textContent = 'Computer denkt...';
|
||||
}
|
||||
}
|
||||
|
||||
function playCard(handIdx) {
|
||||
if (!isPlayerTurn || waitingForColor) return;
|
||||
|
||||
const card = playerHand[handIdx];
|
||||
|
||||
// Prüfen ob Karte spielbar
|
||||
if (!canPlayCard(card)) {
|
||||
speak('Diese Karte kannst du nicht spielen!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Karte spielen
|
||||
playerHand.splice(handIdx, 1);
|
||||
discardPile.push(card);
|
||||
|
||||
// Joker?
|
||||
if (card.color === 'wild') {
|
||||
waitingForColor = true;
|
||||
pendingWildCard = card;
|
||||
document.getElementById('colorPicker').style.display = 'block';
|
||||
updateDisplay();
|
||||
return;
|
||||
}
|
||||
|
||||
// Normale Karte
|
||||
currentColor = card.color;
|
||||
currentValue = card.value;
|
||||
|
||||
applyCardEffect(card);
|
||||
|
||||
// Gewonnen?
|
||||
if (playerHand.length === 0) {
|
||||
endGame(true);
|
||||
return;
|
||||
}
|
||||
|
||||
updateDisplay();
|
||||
|
||||
if (!isPlayerTurn) {
|
||||
setTimeout(botTurn, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
function canPlayCard(card) {
|
||||
if (cardsToDraw > 0) {
|
||||
// Muss +2 oder +4 spielen
|
||||
return card.value === '+2' || card.value === '+4';
|
||||
}
|
||||
return card.color === currentColor ||
|
||||
card.value === currentValue ||
|
||||
card.color === 'wild';
|
||||
}
|
||||
|
||||
function selectColor(color) {
|
||||
if (!waitingForColor) return;
|
||||
|
||||
currentColor = color;
|
||||
currentValue = pendingWildCard.value;
|
||||
waitingForColor = false;
|
||||
document.getElementById('colorPicker').style.display = 'none';
|
||||
|
||||
applyCardEffect(pendingWildCard);
|
||||
pendingWildCard = null;
|
||||
|
||||
updateDisplay();
|
||||
|
||||
if (!isPlayerTurn) {
|
||||
setTimeout(botTurn, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function applyCardEffect(card) {
|
||||
switch(card.value) {
|
||||
case 'skip':
|
||||
speak('Aussetzen!');
|
||||
// Gegner überspringen
|
||||
break;
|
||||
case 'reverse':
|
||||
speak('Richtungswechsel!');
|
||||
direction *= -1;
|
||||
break;
|
||||
case '+2':
|
||||
speak('+2! Computer zieht 2 Karten!');
|
||||
cardsToDraw = 2;
|
||||
break;
|
||||
case '+4':
|
||||
speak('+4! Computer zieht 4 Karten!');
|
||||
cardsToDraw = 4;
|
||||
break;
|
||||
default:
|
||||
speak(card.emoji);
|
||||
}
|
||||
|
||||
isPlayerTurn = !isPlayerTurn;
|
||||
}
|
||||
|
||||
function drawCard() {
|
||||
if (!isPlayerTurn || waitingForColor) return;
|
||||
|
||||
if (deck.length === 0) {
|
||||
// Stapel neu mischen
|
||||
if (discardPile.length <= 1) return;
|
||||
const topCard = discardPile.pop();
|
||||
deck = shuffle(discardPile);
|
||||
discardPile = [topCard];
|
||||
}
|
||||
|
||||
// Karte ziehen
|
||||
const drawn = deck.pop();
|
||||
|
||||
if (cardsToDraw > 0) {
|
||||
// Muss Karten ziehen
|
||||
playerHand.push(drawn);
|
||||
cardsToDraw--;
|
||||
|
||||
if (cardsToDraw === 0) {
|
||||
// Nächster Spieler
|
||||
isPlayerTurn = false;
|
||||
setTimeout(botTurn, 1000);
|
||||
}
|
||||
} else {
|
||||
playerHand.push(drawn);
|
||||
// Nach Ziehen: Nächster Spieler
|
||||
isPlayerTurn = false;
|
||||
setTimeout(botTurn, 1000);
|
||||
}
|
||||
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
function botTurn() {
|
||||
if (isPlayerTurn || waitingForColor) return;
|
||||
|
||||
// Spielbare Karten finden
|
||||
const playableCards = botHand.map((card, idx) => ({ card, idx }))
|
||||
.filter(({ card }) => canPlayCard(card));
|
||||
|
||||
if (playableCards.length > 0) {
|
||||
// Beste Karte wählen (Priorität: +4, +2, Spezial, Zahlen)
|
||||
let bestIdx = playableCards[0].idx;
|
||||
let bestPriority = -1;
|
||||
|
||||
playableCards.forEach(({ card, idx }) => {
|
||||
let priority = 0;
|
||||
if (card.value === '+4') priority = 4;
|
||||
else if (card.value === '+2') priority = 3;
|
||||
else if (['skip', 'reverse'].includes(card.value)) priority = 2;
|
||||
else if (card.color === 'wild') priority = 1;
|
||||
|
||||
if (priority > bestPriority) {
|
||||
bestPriority = priority;
|
||||
bestIdx = idx;
|
||||
}
|
||||
});
|
||||
|
||||
const card = botHand[bestIdx];
|
||||
botHand.splice(bestIdx, 1);
|
||||
discardPile.push(card);
|
||||
|
||||
// Farbe wählen (meiste in Hand)
|
||||
if (card.color === 'wild') {
|
||||
const colorCounts = {};
|
||||
botHand.forEach(c => {
|
||||
if (c.color !== 'wild') colorCounts[c.color] = (colorCounts[c.color] || 0) + 1;
|
||||
});
|
||||
const bestColor = Object.entries(colorCounts)
|
||||
.sort((a, b) => b[1] - a[1])[0]?.[0] || 'red';
|
||||
currentColor = bestColor;
|
||||
currentValue = card.value;
|
||||
} else {
|
||||
currentColor = card.color;
|
||||
currentValue = card.value;
|
||||
}
|
||||
|
||||
speak(`Computer spielt ${card.emoji}`);
|
||||
applyCardEffect(card);
|
||||
|
||||
if (botHand.length === 0) {
|
||||
endGame(false);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Karte ziehen
|
||||
if (deck.length === 0) {
|
||||
const topCard = discardPile.pop();
|
||||
deck = shuffle(discardPile);
|
||||
discardPile = [topCard];
|
||||
}
|
||||
|
||||
const drawn = deck.pop();
|
||||
|
||||
if (cardsToDraw > 0) {
|
||||
for (let i = 0; i < cardsToDraw && deck.length > 0; i++) {
|
||||
botHand.push(deck.pop());
|
||||
}
|
||||
speak(`Computer zieht ${cardsToDraw} Karten!`);
|
||||
cardsToDraw = 0;
|
||||
} else {
|
||||
botHand.push(drawn);
|
||||
speak('Computer zieht eine Karte');
|
||||
}
|
||||
|
||||
isPlayerTurn = true;
|
||||
}
|
||||
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
function endGame(playerWon) {
|
||||
const modal = document.getElementById('gameOver');
|
||||
const winnerText = document.getElementById('winnerText');
|
||||
|
||||
if (playerWon) {
|
||||
winnerText.textContent = '🎉 Du hast gewonnen!';
|
||||
speak('Glückwunsch! Du hast gewonnen!');
|
||||
} else {
|
||||
winnerText.textContent = '🤖 Computer hat gewonnen!';
|
||||
speak('Computer hat gewonnen!');
|
||||
}
|
||||
|
||||
modal.style.display = 'block';
|
||||
}
|
||||
|
||||
function newGame() {
|
||||
initGame();
|
||||
}
|
||||
|
||||
function speak(text) {
|
||||
if ('speechSynthesis' in window) {
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.lang = 'de-DE';
|
||||
speechSynthesis.speak(utterance);
|
||||
}
|
||||
}
|
||||
|
||||
// Init
|
||||
initGame();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,251 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Würfel - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 20px;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 15px; font-size: 26px; }
|
||||
.dice-container {
|
||||
perspective: 1000px; margin: 30px 0;
|
||||
}
|
||||
.dice {
|
||||
width: 100px; height: 100px; position: relative;
|
||||
transform-style: preserve-3d; transition: transform 1s ease-out;
|
||||
}
|
||||
.dice.rolling { animation: roll 1s ease-out; }
|
||||
@keyframes roll {
|
||||
0% { transform: rotateX(0) rotateY(0) rotateZ(0); }
|
||||
100% { transform: rotateX(720deg) rotateY(720deg) rotateZ(360deg); }
|
||||
}
|
||||
.face {
|
||||
position: absolute; width: 100px; height: 100px;
|
||||
background: white; border: 3px solid #333; border-radius: 15px;
|
||||
box-shadow: inset 0 0 20px rgba(0,0,0,0.2);
|
||||
}
|
||||
.face-1 { transform: rotateY(0deg) translateZ(50px); }
|
||||
.face-2 { transform: rotateY(90deg) translateZ(50px); }
|
||||
.face-3 { transform: rotateY(180deg) translateZ(50px); }
|
||||
.face-4 { transform: rotateY(-90deg) translateZ(50px); }
|
||||
.face-5 { transform: rotateX(90deg) translateZ(50px); }
|
||||
.face-6 { transform: rotateX(-90deg) translateZ(50px); }
|
||||
|
||||
.dot {
|
||||
width: 20px; height: 20px; background: #333; border-radius: 50%;
|
||||
position: absolute; box-shadow: inset 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
/* Seite 1: Eins in der Mitte */
|
||||
.face-1 .dot1 { top: 50%; left: 50%; transform: translate(-50%, -50%); }
|
||||
|
||||
/* Seite 2: Zwei in diagonalen Ecken */
|
||||
.face-2 .dot1 { top: 20%; left: 20%; }
|
||||
.face-2 .dot2 { bottom: 20%; right: 20%; }
|
||||
|
||||
/* Seite 3: Drei diagonal */
|
||||
.face-3 .dot1 { top: 20%; left: 20%; }
|
||||
.face-3 .dot2 { top: 50%; left: 50%; transform: translate(-50%, -50%); }
|
||||
.face-3 .dot3 { bottom: 20%; right: 20%; }
|
||||
|
||||
/* Seite 4: Vier in allen Ecken */
|
||||
.face-4 .dot1 { top: 20%; left: 20%; }
|
||||
.face-4 .dot2 { top: 20%; right: 20%; }
|
||||
.face-4 .dot3 { bottom: 20%; left: 20%; }
|
||||
.face-4 .dot4 { bottom: 20%; right: 20%; }
|
||||
|
||||
/* Seite 5: Fünf (Vier Ecken + Mitte) */
|
||||
.face-5 .dot1 { top: 20%; left: 20%; } /* Oben links */
|
||||
.face-5 .dot2 { top: 20%; right: 20%; } /* Oben rechts */
|
||||
.face-5 .dot3 { top: 50%; left: 50%; transform: translate(-50%, -50%); } /* Mitte */
|
||||
.face-5 .dot4 { bottom: 20%; left: 20%; } /* Unten links */
|
||||
.face-5 .dot5 { bottom: 20%; right: 20%; } /* Unten rechts */
|
||||
|
||||
/* Seite 6: Sechs (zwei Reihen zu je drei) */
|
||||
.face-6 .dot1 { top: 20%; left: 25%; }
|
||||
.face-6 .dot2 { top: 20%; right: 25%; }
|
||||
.face-6 .dot3 { top: 50%; left: 25%; transform: translateY(-50%); }
|
||||
.face-6 .dot4 { top: 50%; right: 25%; transform: translateY(-50%); }
|
||||
.face-6 .dot5 { bottom: 20%; left: 25%; }
|
||||
.face-6 .dot6 { bottom: 20%; right: 25%; }
|
||||
|
||||
.result {
|
||||
color: white; font-size: 48px; margin: 20px 0; min-height: 60px;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3); font-weight: bold;
|
||||
}
|
||||
.stats {
|
||||
color: white; font-size: 18px; margin: 10px 0;
|
||||
}
|
||||
.history {
|
||||
display: flex; gap: 10px; flex-wrap: wrap; justify-content: center;
|
||||
max-width: 300px; margin: 15px 0;
|
||||
}
|
||||
.history-item {
|
||||
width: 40px; height: 40px; background: white; border-radius: 8px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 20px; font-weight: bold; box-shadow: 0 3px 10px rgba(0,0,0,0.3);
|
||||
}
|
||||
.controls { display: flex; gap: 15px; margin-top: 20px; }
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 15px 40px;
|
||||
font-size: 18px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-roll { background: #4ade80; color: white; }
|
||||
.btn-roll:hover { transform: scale(1.05); }
|
||||
.btn-roll:active { transform: scale(0.95); }
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; padding: 15px 25px; }
|
||||
|
||||
.space-hint { color: #ccc; margin-top: 10px; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🎲 Würfel</h1>
|
||||
|
||||
<div class="dice-container">
|
||||
<div class="dice" id="dice" onclick="roll()">
|
||||
<!-- Seite 1: 1 Punkt -->
|
||||
<div class="face face-1">
|
||||
<div class="dot dot1"></div>
|
||||
</div>
|
||||
|
||||
<!-- Seite 2: 2 Punkte -->
|
||||
<div class="face face-2">
|
||||
<div class="dot dot1"></div>
|
||||
<div class="dot dot2"></div>
|
||||
</div>
|
||||
|
||||
<!-- Seite 3: 3 Punkte -->
|
||||
<div class="face face-3">
|
||||
<div class="dot dot1"></div>
|
||||
<div class="dot dot2"></div>
|
||||
<div class="dot dot3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Seite 4: 4 Punkte -->
|
||||
<div class="face face-4">
|
||||
<div class="dot dot1"></div>
|
||||
<div class="dot dot2"></div>
|
||||
<div class="dot dot3"></div>
|
||||
<div class="dot dot4"></div>
|
||||
</div>
|
||||
|
||||
<!-- Seite 5: 5 Punkte (Vier Ecken + Mitte) -->
|
||||
<div class="face face-5">
|
||||
<div class="dot dot1"></div>
|
||||
<div class="dot dot2"></div>
|
||||
<div class="dot dot3"></div>
|
||||
<div class="dot dot4"></div>
|
||||
<div class="dot dot5"></div>
|
||||
</div>
|
||||
|
||||
<!-- Seite 6: 6 Punkte -->
|
||||
<div class="face face-6">
|
||||
<div class="dot dot1"></div>
|
||||
<div class="dot dot2"></div>
|
||||
<div class="dot dot3"></div>
|
||||
<div class="dot dot4"></div>
|
||||
<div class="dot dot5"></div>
|
||||
<div class="dot dot6"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result" id="result">Klicke oder drücke Leertaste!</div>
|
||||
|
||||
<div class="stats">Würfe: <span id="rolls">0</span> | Summe: <span id="total">0</span></div>
|
||||
|
||||
<div class="history" id="history"></div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn btn-roll" id="rollBtn" onclick="roll()">🎲 Würfeln</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<p class="space-hint">💡 Tipp: Du kannst auch die LEERTASTE drücken!</p>
|
||||
|
||||
<script>
|
||||
let rolling = false;
|
||||
let rollCount = 0;
|
||||
let total = 0;
|
||||
|
||||
const rotations = {
|
||||
1: 'rotateX(0deg) rotateY(0deg)',
|
||||
2: 'rotateX(0deg) rotateY(-90deg)',
|
||||
3: 'rotateX(0deg) rotateY(180deg)',
|
||||
4: 'rotateX(0deg) rotateY(90deg)',
|
||||
5: 'rotateX(-90deg) rotateY(0deg)',
|
||||
6: 'rotateX(90deg) rotateY(0deg)'
|
||||
};
|
||||
|
||||
const dice = document.getElementById('dice');
|
||||
const result = document.getElementById('result');
|
||||
const rollBtn = document.getElementById('rollBtn');
|
||||
|
||||
function roll() {
|
||||
if (rolling) return;
|
||||
rolling = true;
|
||||
rollBtn.disabled = true;
|
||||
|
||||
// Animation
|
||||
dice.classList.add('rolling');
|
||||
result.textContent = '🎲...';
|
||||
|
||||
// Zufällige Zahl
|
||||
const roll = Math.floor(Math.random() * 6) + 1;
|
||||
|
||||
setTimeout(() => {
|
||||
dice.classList.remove('rolling');
|
||||
dice.style.transform = rotations[roll];
|
||||
|
||||
// Ergebnis anzeigen
|
||||
result.innerHTML = `<span style="font-size:60px">${roll}</span>`;
|
||||
|
||||
// Statistik
|
||||
rollCount++;
|
||||
total += roll;
|
||||
document.getElementById('rolls').textContent = rollCount;
|
||||
document.getElementById('total').textContent = total;
|
||||
|
||||
// Historie
|
||||
const history = document.getElementById('history');
|
||||
const item = document.createElement('div');
|
||||
item.className = 'history-item';
|
||||
item.textContent = roll;
|
||||
// Farben: Grün für 5-6, Gelb für 3-4, Rot für 1-2
|
||||
if (roll >= 5) item.style.background = '#86efac';
|
||||
else if (roll >= 3) item.style.background = '#feca57';
|
||||
else item.style.background = '#fca5a5';
|
||||
|
||||
history.insertBefore(item, history.firstChild);
|
||||
if (history.children.length > 10) {
|
||||
history.removeChild(history.lastChild);
|
||||
}
|
||||
|
||||
// TTS
|
||||
if ('speechSynthesis' in window) {
|
||||
const utterance = new SpeechSynthesisUtterance(roll.toString());
|
||||
utterance.lang = 'de-DE';
|
||||
speechSynthesis.speak(utterance);
|
||||
}
|
||||
|
||||
rolling = false;
|
||||
rollBtn.disabled = false;
|
||||
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Leertaste
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.code === 'Space') {
|
||||
e.preventDefault();
|
||||
roll();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,223 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Wie viele? - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 20px;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 10px; }
|
||||
.question { color: white; font-size: 22px; margin: 10px 0; text-align: center; }
|
||||
.objects-area {
|
||||
background: white; border-radius: 20px; padding: 30px; margin: 15px 0;
|
||||
min-height: 200px; min-width: 300px; display: flex; flex-wrap: wrap;
|
||||
justify-content: center; align-items: center; gap: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||||
}
|
||||
.object-item { font-size: 50px; }
|
||||
.answer-section { margin: 20px 0; text-align: center; }
|
||||
.answer-input {
|
||||
font-size: 48px; width: 100px; text-align: center; padding: 10px;
|
||||
border: 3px solid white; border-radius: 15px; background: rgba(255,255,255,0.9);
|
||||
font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.pin-pad {
|
||||
display: grid; grid-template-columns: repeat(3, 70px); gap: 10px;
|
||||
justify-content: center; margin: 20px 0;
|
||||
}
|
||||
.pin-btn {
|
||||
background: rgba(255,255,255,0.9); border: none; border-radius: 15px;
|
||||
width: 70px; height: 70px; font-size: 28px; font-weight: bold;
|
||||
cursor: pointer; font-family: inherit;
|
||||
}
|
||||
.pin-btn:active { transform: scale(0.95); background: white; }
|
||||
.pin-btn.action { background: #4ade80; color: white; }
|
||||
.pin-btn.clear { background: #ff6b6b; color: white; }
|
||||
.feedback {
|
||||
font-size: 36px; margin: 15px 0; min-height: 50px; font-weight: bold;
|
||||
}
|
||||
.feedback.correct { color: #4ade80; }
|
||||
.feedback.wrong { color: #ff6b6b; }
|
||||
.score { color: white; font-size: 20px; margin-bottom: 10px; }
|
||||
.controls { display: flex; gap: 15px; margin-top: 15px; flex-wrap: wrap; justify-content: center; }
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 12px 25px;
|
||||
font-size: 16px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; }
|
||||
.range-select { margin-bottom: 15px; }
|
||||
.range-select button {
|
||||
background: rgba(255,255,255,0.2); border: 2px solid white; border-radius: 10px;
|
||||
padding: 8px 15px; margin: 0 5px; color: white; font-size: 14px; cursor: pointer;
|
||||
}
|
||||
.range-select button.active { background: white; color: #667eea; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🔢 Wie viele siehst du?</h1>
|
||||
|
||||
<div class="range-select">
|
||||
<button onclick="setRange(5)" id="r5" class="active">Bis 5</button>
|
||||
<button onclick="setRange(10)" id="r10">Bis 10</button>
|
||||
<button onclick="setRange(20)" id="r20">Bis 20</button>
|
||||
</div>
|
||||
|
||||
<div class="score">Richtig: <span id="correctCount">0</span> | Versuche: <span id="totalCount">0</span></div>
|
||||
|
||||
<div class="question" id="questionText">Wie viele 🍎 siehst du?</div>
|
||||
|
||||
<div class="objects-area" id="objectsArea"></div>
|
||||
|
||||
<div class="answer-section">
|
||||
<input type="text" class="answer-input" id="answerInput" readonly placeholder="?">
|
||||
</div>
|
||||
|
||||
<div class="feedback" id="feedback"></div>
|
||||
|
||||
<div class="pin-pad">
|
||||
<button class="pin-btn" onclick="enterDigit(1)">1</button>
|
||||
<button class="pin-btn" onclick="enterDigit(2)">2</button>
|
||||
<button class="pin-btn" onclick="enterDigit(3)">3</button>
|
||||
<button class="pin-btn" onclick="enterDigit(4)">4</button>
|
||||
<button class="pin-btn" onclick="enterDigit(5)">5</button>
|
||||
<button class="pin-btn" onclick="enterDigit(6)">6</button>
|
||||
<button class="pin-btn" onclick="enterDigit(7)">7</button>
|
||||
<button class="pin-btn" onclick="enterDigit(8)">8</button>
|
||||
<button class="pin-btn" onclick="enterDigit(9)">9</button>
|
||||
<button class="pin-btn clear" onclick="clearAnswer()">⌫</button>
|
||||
<button class="pin-btn" onclick="enterDigit(0)">0</button>
|
||||
<button class="pin-btn action" onclick="checkAnswer()">✓</button>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn" onclick="nextQuestion()">🔄 Nächste Aufgabe</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const objects = [
|
||||
{ emoji: '🍎', name: 'Äpfel' },
|
||||
{ emoji: '🚗', name: 'Autos' },
|
||||
{ emoji: '⭐', name: 'Sterne' },
|
||||
{ emoji: '🐱', name: 'Katzen' },
|
||||
{ emoji: '🌸', name: 'Blumen' },
|
||||
{ emoji: '🎈', name: 'Ballons' },
|
||||
{ emoji: '🐶', name: 'Hunde' },
|
||||
{ emoji: '🦋', name: 'Schmetterlinge' },
|
||||
{ emoji: '🍕', name: 'Pizzas' },
|
||||
{ emoji: '🚀', name: 'Raketen' },
|
||||
{ emoji: '🐘', name: 'Elefanten' },
|
||||
{ emoji: '🌈', name: 'Regenbögen' }
|
||||
];
|
||||
|
||||
let currentCount = 0;
|
||||
let currentObject = null;
|
||||
let userAnswer = '';
|
||||
let maxRange = 5;
|
||||
let correctCount = 0;
|
||||
let totalCount = 0;
|
||||
const synth = window.speechSynthesis;
|
||||
|
||||
function setRange(range) {
|
||||
maxRange = range;
|
||||
document.querySelectorAll('.range-select button').forEach(btn => btn.classList.remove('active'));
|
||||
document.getElementById('r' + range).classList.add('active');
|
||||
nextQuestion();
|
||||
}
|
||||
|
||||
function nextQuestion() {
|
||||
// Zufällige Anzahl und Objekt
|
||||
currentCount = Math.floor(Math.random() * maxRange) + 1;
|
||||
currentObject = objects[Math.floor(Math.random() * objects.length)];
|
||||
|
||||
// Anzeigen
|
||||
const area = document.getElementById('objectsArea');
|
||||
area.innerHTML = '';
|
||||
|
||||
// Objekte verteilen
|
||||
for (let i = 0; i < currentCount; i++) {
|
||||
const span = document.createElement('span');
|
||||
span.className = 'object-item';
|
||||
span.textContent = currentObject.emoji;
|
||||
span.style.animationDelay = (i * 0.1) + 's';
|
||||
area.appendChild(span);
|
||||
}
|
||||
|
||||
// Frage setzen
|
||||
document.getElementById('questionText').textContent =
|
||||
`Wie viele ${currentObject.name} siehst du?`;
|
||||
|
||||
// Zurücksetzen
|
||||
userAnswer = '';
|
||||
document.getElementById('answerInput').value = '';
|
||||
document.getElementById('feedback').textContent = '';
|
||||
document.getElementById('feedback').className = 'feedback';
|
||||
|
||||
// Vorlesen
|
||||
speak(`Wie viele ${currentObject.name} siehst du?`);
|
||||
}
|
||||
|
||||
function enterDigit(digit) {
|
||||
if (userAnswer.length < 2) {
|
||||
userAnswer += digit;
|
||||
document.getElementById('answerInput').value = userAnswer;
|
||||
}
|
||||
}
|
||||
|
||||
function clearAnswer() {
|
||||
userAnswer = '';
|
||||
document.getElementById('answerInput').value = '';
|
||||
}
|
||||
|
||||
function checkAnswer() {
|
||||
if (!userAnswer) return;
|
||||
|
||||
const answer = parseInt(userAnswer);
|
||||
totalCount++;
|
||||
|
||||
const feedback = document.getElementById('feedback');
|
||||
|
||||
if (answer === currentCount) {
|
||||
correctCount++;
|
||||
feedback.textContent = '🎉 Richtig! Sehr gut!';
|
||||
feedback.className = 'feedback correct';
|
||||
speak(`Richtig! Es sind ${currentCount} ${currentObject.name}. Sehr gut!`);
|
||||
} else {
|
||||
feedback.textContent = `❌ Fast! Es waren ${currentCount} ${currentObject.name}.`;
|
||||
feedback.className = 'feedback wrong';
|
||||
speak(`Fast! Es waren ${currentCount} ${currentObject.name}.`);
|
||||
}
|
||||
|
||||
document.getElementById('correctCount').textContent = correctCount;
|
||||
document.getElementById('totalCount').textContent = totalCount;
|
||||
|
||||
// Nach 2 Sekunden nächste Frage
|
||||
setTimeout(nextQuestion, 2500);
|
||||
}
|
||||
|
||||
function speak(text) {
|
||||
if (!synth) return;
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.lang = 'de-DE';
|
||||
utterance.rate = 0.8;
|
||||
utterance.pitch = 1.2;
|
||||
synth.speak(utterance);
|
||||
}
|
||||
|
||||
// Keyboard
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key >= '0' && e.key <= '9') enterDigit(parseInt(e.key));
|
||||
if (e.key === 'Backspace') clearAnswer();
|
||||
if (e.key === 'Enter') checkAnswer();
|
||||
});
|
||||
|
||||
// Init
|
||||
nextQuestion();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,184 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Zahlen raten - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #a855f7 0%, #6366f1 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 20px;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 15px; }
|
||||
.game-area {
|
||||
background: white; border-radius: 20px; padding: 30px; margin: 15px 0;
|
||||
text-align: center; box-shadow: 0 10px 30px rgba(0,0,0,0.3); max-width: 400px; width: 100%;
|
||||
}
|
||||
.message { font-size: 24px; margin-bottom: 20px; color: #333; }
|
||||
.attempts { color: #666; margin: 10px 0; }
|
||||
.guess-display {
|
||||
background: #f3f4f6; border-radius: 10px; padding: 15px; margin: 15px 0;
|
||||
font-size: 36px; font-weight: bold; color: #333; min-height: 60px;
|
||||
}
|
||||
.pin-pad {
|
||||
display: grid; grid-template-columns: repeat(3, 80px); gap: 10px;
|
||||
justify-content: center; margin: 20px 0;
|
||||
}
|
||||
.pin-btn {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2); border: none; border-radius: 15px;
|
||||
width: 80px; height: 80px; font-size: 28px; color: white; font-weight: bold;
|
||||
cursor: pointer; box-shadow: 0 4px 10px rgba(0,0,0,0.2);
|
||||
}
|
||||
.pin-btn:active { transform: scale(0.95); }
|
||||
.pin-btn.action { background: linear-gradient(135deg, #4ade80, #22c55e); }
|
||||
.pin-btn.clear { background: linear-gradient(135deg, #ff6b6b, #f5576c); }
|
||||
.controls { display: flex; gap: 15px; margin-top: 20px; flex-wrap: wrap; justify-content: center; }
|
||||
.btn {
|
||||
background: linear-gradient(135deg, #4ade80, #22c55e); border: none; border-radius: 10px;
|
||||
padding: 12px 25px; font-size: 16px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
.btn-back { background: #ff6b6b; text-decoration: none; }
|
||||
.range-select { margin-bottom: 15px; }
|
||||
.range-select button {
|
||||
background: rgba(255,255,255,0.2); border: 2px solid white; border-radius: 10px;
|
||||
padding: 8px 15px; margin: 0 5px; color: white; font-size: 14px; cursor: pointer;
|
||||
}
|
||||
.range-select button.active { background: white; color: #a855f7; }
|
||||
.hint { color: #ff6b6b; font-size: 20px; margin-top: 10px; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🤔 Zahlen raten</h1>
|
||||
|
||||
<div class="range-select">
|
||||
<button onclick="setRange(10)" id="r10" class="active">1-10</button>
|
||||
<button onclick="setRange(20)" id="r20">1-20</button>
|
||||
<button onclick="setRange(50)" id="r50">1-50</button>
|
||||
<button onclick="setRange(100)" id="r100">1-100</button>
|
||||
</div>
|
||||
|
||||
<div class="game-area">
|
||||
<div class="message" id="message">Ich denke an eine Zahl...</div>
|
||||
<div class="attempts" id="attempts">Versuch 1</div>
|
||||
|
||||
<div class="guess-display" id="guessDisplay">?</div>
|
||||
|
||||
<div class="hint" id="hint"></div>
|
||||
|
||||
<div class="pin-pad">
|
||||
<button class="pin-btn" onclick="enterDigit(1)">1</button>
|
||||
<button class="pin-btn" onclick="enterDigit(2)">2</button>
|
||||
<button class="pin-btn" onclick="enterDigit(3)">3</button>
|
||||
<button class="pin-btn" onclick="enterDigit(4)">4</button>
|
||||
<button class="pin-btn" onclick="enterDigit(5)">5</button>
|
||||
<button class="pin-btn" onclick="enterDigit(6)">6</button>
|
||||
<button class="pin-btn" onclick="enterDigit(7)">7</button>
|
||||
<button class="pin-btn" onclick="enterDigit(8)">8</button>
|
||||
<button class="pin-btn" onclick="enterDigit(9)">9</button>
|
||||
<button class="pin-btn clear" onclick="clearGuess()">⌫</button>
|
||||
<button class="pin-btn" onclick="enterDigit(0)">0</button>
|
||||
<button class="pin-btn action" onclick="checkGuess()">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn" onclick="initGame()">🔄 Neues Spiel</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let targetNumber = 0;
|
||||
let currentGuess = '';
|
||||
let attempts = 0;
|
||||
let maxRange = 10;
|
||||
const synth = window.speechSynthesis;
|
||||
|
||||
function setRange(range) {
|
||||
maxRange = range;
|
||||
document.querySelectorAll('.range-select button').forEach(btn => btn.classList.remove('active'));
|
||||
document.getElementById('r' + range).classList.add('active');
|
||||
initGame();
|
||||
}
|
||||
|
||||
function initGame() {
|
||||
targetNumber = Math.floor(Math.random() * maxRange) + 1;
|
||||
currentGuess = '';
|
||||
attempts = 0;
|
||||
document.getElementById('message').textContent = `Ich denke an eine Zahl zwischen 1 und ${maxRange}...`;
|
||||
document.getElementById('attempts').textContent = 'Versuch 1';
|
||||
document.getElementById('guessDisplay').textContent = '?';
|
||||
document.getElementById('hint').textContent = '';
|
||||
speak(`Ich denke an eine Zahl zwischen 1 und ${maxRange}. Welche Zahl ist es?`);
|
||||
}
|
||||
|
||||
function enterDigit(digit) {
|
||||
if (currentGuess.length < 3) {
|
||||
currentGuess += digit;
|
||||
document.getElementById('guessDisplay').textContent = currentGuess;
|
||||
speak(currentGuess);
|
||||
}
|
||||
}
|
||||
|
||||
function clearGuess() {
|
||||
currentGuess = '';
|
||||
document.getElementById('guessDisplay').textContent = '?';
|
||||
}
|
||||
|
||||
function checkGuess() {
|
||||
if (!currentGuess) return;
|
||||
|
||||
const guess = parseInt(currentGuess);
|
||||
attempts++;
|
||||
document.getElementById('attempts').textContent = `Versuch ${attempts + 1}`;
|
||||
|
||||
if (guess === targetNumber) {
|
||||
document.getElementById('message').textContent = '🎉 Richtig! Super!';
|
||||
document.getElementById('hint').textContent = `Du hast ${attempts} Versuche gebraucht.`;
|
||||
document.getElementById('guessDisplay').style.background = '#4ade80';
|
||||
speak(`Richtig! Die Zahl war ${targetNumber}. Du hast ${attempts} Versuche gebraucht.`);
|
||||
setTimeout(() => {
|
||||
document.getElementById('guessDisplay').style.background = '#f3f4f6';
|
||||
}, 2000);
|
||||
} else if (guess < targetNumber) {
|
||||
document.getElementById('message').textContent = `${guess} ist zu klein!`;
|
||||
document.getElementById('hint').textContent = '⬆️ Die Zahl ist größer!';
|
||||
speak(`${guess} ist zu klein. Die Zahl ist größer.`);
|
||||
currentGuess = '';
|
||||
setTimeout(() => {
|
||||
document.getElementById('guessDisplay').textContent = '?';
|
||||
}, 1000);
|
||||
} else {
|
||||
document.getElementById('message').textContent = `${guess} ist zu groß!`;
|
||||
document.getElementById('hint').textContent = '⬇️ Die Zahl ist kleiner!';
|
||||
speak(`${guess} ist zu groß. Die Zahl ist kleiner.`);
|
||||
currentGuess = '';
|
||||
setTimeout(() => {
|
||||
document.getElementById('guessDisplay').textContent = '?';
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function speak(text) {
|
||||
if (!synth) return;
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.lang = 'de-DE';
|
||||
utterance.rate = 0.8;
|
||||
utterance.pitch = 1.1;
|
||||
synth.speak(utterance);
|
||||
}
|
||||
|
||||
// Keyboard support
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key >= '0' && e.key <= '9') enterDigit(parseInt(e.key));
|
||||
if (e.key === 'Backspace') clearGuess();
|
||||
if (e.key === 'Enter') checkGuess();
|
||||
});
|
||||
|
||||
// Init
|
||||
initGame();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,272 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Zielscheibe - KinderWelt</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
|
||||
body {
|
||||
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
|
||||
min-height: 100vh; display: flex; flex-direction: column; align-items: center;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif; padding: 20px; overflow: hidden;
|
||||
}
|
||||
h1 { color: white; margin-bottom: 10px; font-size: 26px; }
|
||||
.score-board {
|
||||
display: flex; gap: 30px; margin-bottom: 15px; color: white; font-size: 18px;
|
||||
}
|
||||
.score-item { text-align: center; }
|
||||
.score-value { font-size: 32px; font-weight: bold; color: #feca57; }
|
||||
.game-container {
|
||||
position: relative; width: 350px; height: 350px;
|
||||
background: rgba(255,255,255,0.1); border-radius: 20px;
|
||||
overflow: hidden; cursor: crosshair;
|
||||
}
|
||||
.target {
|
||||
position: absolute; border-radius: 50%; cursor: pointer;
|
||||
animation: appear 0.3s ease-out;
|
||||
}
|
||||
@keyframes appear {
|
||||
from { transform: scale(0); }
|
||||
to { transform: scale(1); }
|
||||
}
|
||||
.target.hit { animation: hit 0.3s ease-out forwards; }
|
||||
@keyframes hit {
|
||||
to { transform: scale(1.5); opacity: 0; }
|
||||
}
|
||||
/* Ringe der Zielscheibe */
|
||||
.ring-1 { width: 60px; height: 60px; background: radial-gradient(circle, #ff6b6b 30%, transparent 30%); }
|
||||
.ring-2 { width: 80px; height: 80px; background: radial-gradient(circle, #ff6b6b 23%, #feca57 23%, #feca57 38%, transparent 38%); }
|
||||
.ring-3 { width: 100px; height: 100px; background: radial-gradient(circle, #ff6b6b 18%, #feca57 18%, #feca57 30%, #4ade80 30%, #4ade80 45%, transparent 45%); }
|
||||
.ring-4 { width: 120px; height: 120px; background: radial-gradient(circle, #ff6b6b 15%, #feca57 15%, #feca57 25%, #4ade80 25%, #4ade80 38%, #3b82f6 38%, #3b82f6 50%, transparent 50%); }
|
||||
.ring-5 { width: 140px; height: 140px; background: radial-gradient(circle, #ff6b6b 13%, #feca57 13%, #feca57 21%, #4ade80 21%, #4ade80 33%, #3b82f6 33%, #3b82f6 43%, #fff 43%, #fff 50%, transparent 50%); }
|
||||
|
||||
.hit-text {
|
||||
position: absolute; font-weight: bold; font-size: 24px;
|
||||
animation: floatUp 1s ease-out forwards; pointer-events: none;
|
||||
}
|
||||
@keyframes floatUp {
|
||||
0% { transform: translateY(0) scale(1); opacity: 1; }
|
||||
100% { transform: translateY(-50px) scale(1.5); opacity: 0; }
|
||||
}
|
||||
.hit-10 { color: #ff6b6b; }
|
||||
.hit-8 { color: #feca57; }
|
||||
.hit-6 { color: #4ade80; }
|
||||
.hit-4 { color: #3b82f6; }
|
||||
.hit-2 { color: #a0aec0; }
|
||||
|
||||
.timer { color: white; font-size: 48px; margin: 10px 0; font-weight: bold; }
|
||||
.controls { display: flex; gap: 15px; margin-top: 15px; }
|
||||
.btn {
|
||||
background: white; border: none; border-radius: 10px; padding: 12px 25px;
|
||||
font-size: 16px; cursor: pointer; font-family: inherit; font-weight: bold;
|
||||
}
|
||||
.btn-start { background: #4ade80; color: white; }
|
||||
.btn-back { background: #ff6b6b; color: white; text-decoration: none; }
|
||||
.game-over {
|
||||
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
||||
background: white; padding: 30px; border-radius: 20px; text-align: center;
|
||||
display: none; z-index: 100;
|
||||
}
|
||||
.game-over h2 { color: #333; margin-bottom: 15px; }
|
||||
.final-score { font-size: 48px; color: #4ade80; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🎯 Zielscheibe</h1>
|
||||
|
||||
<div class="score-board">
|
||||
<div class="score-item">
|
||||
<div>Punkte</div>
|
||||
<div class="score-value" id="score">0</div>
|
||||
</div>
|
||||
<div class="score-item">
|
||||
<div>Treffer</div>
|
||||
<div class="score-value" id="hits">0</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timer" id="timer">30</div>
|
||||
|
||||
<div class="game-container" id="gameContainer">
|
||||
<div class="game-over" id="gameOver">
|
||||
<h2>🎉 Zeit abgelaufen!</h2>
|
||||
<div class="final-score" id="finalScore">0</div>
|
||||
<div style="margin-top: 15px; color: #666">Punkte</div>
|
||||
<button class="btn btn-start" onclick="startGame()" style="margin-top: 20px">🔄 Nochmal</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn btn-start" id="startBtn" onclick="startGame()">▶️ Start</button>
|
||||
<a href="../index.html" class="btn btn-back">⬅️ Zurück</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let score = 0;
|
||||
let hits = 0;
|
||||
let timeLeft = 30;
|
||||
let gameActive = false;
|
||||
let timerInterval = null;
|
||||
let targetInterval = null;
|
||||
let highscore = localStorage.getItem('zielscheibe_highscore') || 0;
|
||||
|
||||
const container = document.getElementById('gameContainer');
|
||||
const scoreEl = document.getElementById('score');
|
||||
const hitsEl = document.getElementById('hits');
|
||||
const timerEl = document.getElementById('timer');
|
||||
const gameOverEl = document.getElementById('gameOver');
|
||||
const finalScoreEl = document.getElementById('finalScore');
|
||||
|
||||
function startGame() {
|
||||
score = 0;
|
||||
hits = 0;
|
||||
timeLeft = 30;
|
||||
gameActive = true;
|
||||
|
||||
scoreEl.textContent = '0';
|
||||
hitsEl.textContent = '0';
|
||||
timerEl.textContent = '30';
|
||||
gameOverEl.style.display = 'none';
|
||||
document.getElementById('startBtn').style.display = 'none';
|
||||
|
||||
// Container leeren
|
||||
container.innerHTML = '';
|
||||
container.appendChild(gameOverEl);
|
||||
|
||||
// Erste Zielscheibe
|
||||
spawnTarget();
|
||||
|
||||
// Timer
|
||||
timerInterval = setInterval(() => {
|
||||
timeLeft--;
|
||||
timerEl.textContent = timeLeft;
|
||||
|
||||
if (timeLeft <= 10) {
|
||||
timerEl.style.color = '#ff6b6b';
|
||||
}
|
||||
|
||||
if (timeLeft <= 0) {
|
||||
endGame();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// Neue Zielscheiben automatisch
|
||||
targetInterval = setInterval(() => {
|
||||
if (gameActive && document.querySelectorAll('.target').length < 3) {
|
||||
spawnTarget();
|
||||
}
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
function spawnTarget() {
|
||||
const target = document.createElement('div');
|
||||
const ringSize = Math.floor(Math.random() * 5) + 1;
|
||||
target.className = 'target ring-' + ringSize;
|
||||
|
||||
// Zufällige Position (nicht zu nah am Rand)
|
||||
const maxX = container.offsetWidth - 150;
|
||||
const maxY = container.offsetHeight - 150;
|
||||
const x = 20 + Math.random() * maxX;
|
||||
const y = 20 + Math.random() * maxY;
|
||||
|
||||
target.style.left = x + 'px';
|
||||
target.style.top = y + 'px';
|
||||
|
||||
// Punkte basierend auf Ring
|
||||
const points = ringSize === 1 ? 10 : ringSize === 2 ? 8 : ringSize === 3 ? 6 : ringSize === 4 ? 4 : 2;
|
||||
|
||||
target.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
if (!gameActive) return;
|
||||
|
||||
hitTarget(target, points, e.clientX, e.clientY);
|
||||
};
|
||||
|
||||
// Automatisch entfernen nach 3 Sekunden
|
||||
setTimeout(() => {
|
||||
if (target.parentNode) {
|
||||
target.remove();
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
container.appendChild(target);
|
||||
}
|
||||
|
||||
function hitTarget(target, points, x, y) {
|
||||
score += points;
|
||||
hits++;
|
||||
scoreEl.textContent = score;
|
||||
hitsEl.textContent = hits;
|
||||
|
||||
// Hit-Text anzeigen
|
||||
const hitText = document.createElement('div');
|
||||
hitText.className = 'hit-text hit-' + points;
|
||||
hitText.textContent = '+' + points;
|
||||
hitText.style.left = (target.offsetLeft + target.offsetWidth/2 - 20) + 'px';
|
||||
hitText.style.top = target.offsetTop + 'px';
|
||||
container.appendChild(hitText);
|
||||
|
||||
setTimeout(() => hitText.remove(), 1000);
|
||||
|
||||
// Animation
|
||||
target.classList.add('hit');
|
||||
|
||||
// Sound via TTS
|
||||
if ('speechSynthesis' in window && points >= 8) {
|
||||
const utterance = new SpeechSynthesisUtterance(points + ' Punkte');
|
||||
utterance.lang = 'de-DE';
|
||||
utterance.rate = 1.2;
|
||||
speechSynthesis.speak(utterance);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
target.remove();
|
||||
// Neue Zielscheibe sofort
|
||||
if (gameActive) spawnTarget();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function endGame() {
|
||||
gameActive = false;
|
||||
clearInterval(timerInterval);
|
||||
clearInterval(targetInterval);
|
||||
|
||||
finalScoreEl.textContent = score;
|
||||
gameOverEl.style.display = 'block';
|
||||
timerEl.style.color = 'white';
|
||||
|
||||
// Highscore
|
||||
if (score > highscore) {
|
||||
highscore = score;
|
||||
localStorage.setItem('zielscheibe_highscore', highscore);
|
||||
finalScoreEl.innerHTML = score + '<br><small style="font-size:20px">🏆 Neuer Rekord!</small>';
|
||||
}
|
||||
|
||||
if ('speechSynthesis' in window) {
|
||||
const utterance = new SpeechSynthesisUtterance('Spiel vorbei. Du hast ' + score + ' Punkte erreicht.');
|
||||
utterance.lang = 'de-DE';
|
||||
speechSynthesis.speak(utterance);
|
||||
}
|
||||
}
|
||||
|
||||
// Verfehlte Klicks = -1 Punkt
|
||||
container.addEventListener('click', (e) => {
|
||||
if (!gameActive || e.target.classList.contains('target')) return;
|
||||
|
||||
score = Math.max(0, score - 1);
|
||||
scoreEl.textContent = score;
|
||||
|
||||
// Visuelles Feedback
|
||||
const missText = document.createElement('div');
|
||||
missText.className = 'hit-text';
|
||||
missText.style.color = '#ff6b6b';
|
||||
missText.textContent = '-1';
|
||||
missText.style.left = (e.offsetX - 10) + 'px';
|
||||
missText.style.top = (e.offsetY - 20) + 'px';
|
||||
container.appendChild(missText);
|
||||
setTimeout(() => missText.remove(), 800);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,71 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const app = express();
|
||||
|
||||
app.use(express.static('public'));
|
||||
app.use(express.json());
|
||||
|
||||
// Session Management für Freigabe
|
||||
const sessions = new Map();
|
||||
|
||||
// Gesundheitscheck für APK
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'online', timestamp: Date.now() });
|
||||
});
|
||||
|
||||
// Session erstellen (für QR-Code)
|
||||
app.post('/api/session/create', (req, res) => {
|
||||
const id = Math.random().toString(36).substring(7);
|
||||
sessions.set(id, {
|
||||
status: 'pending',
|
||||
created: Date.now(),
|
||||
expires: Date.now() + (30 * 60 * 1000) // 30 Minuten
|
||||
});
|
||||
res.json({ sessionId: id, qrData: `kids://auth/${id}` });
|
||||
});
|
||||
|
||||
// Session Status prüfen
|
||||
app.get('/api/session/:id', (req, res) => {
|
||||
const session = sessions.get(req.params.id);
|
||||
if (!session) return res.status(404).json({ error: 'not found' });
|
||||
if (Date.now() > session.expires) {
|
||||
sessions.delete(req.params.id);
|
||||
return res.json({ status: 'expired' });
|
||||
}
|
||||
res.json(session);
|
||||
});
|
||||
|
||||
// Freigabe durch Eltern
|
||||
app.post('/api/session/:id/approve', (req, res) => {
|
||||
const { duration = 30, jellyfinUser = 'kind-default' } = req.body;
|
||||
sessions.set(req.params.id, {
|
||||
status: 'approved',
|
||||
approvedAt: Date.now(),
|
||||
expires: Date.now() + (duration * 60000),
|
||||
jellyfinUser
|
||||
});
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// Spiele-Liste
|
||||
app.get('/api/games', (req, res) => {
|
||||
res.json([
|
||||
{ id: 'bubbles', name: 'Blubber-Blasen', type: 'touch', age: 2 },
|
||||
{ id: 'memory', name: 'Memory', type: 'touch', age: 4 },
|
||||
{ id: 'drawing', name: 'Malen', type: 'touch', age: 3 },
|
||||
{ id: 'nes-mario', name: 'Super Mario', type: 'emulator', system: 'nes', age: 5 },
|
||||
{ id: 'snes-mario', name: 'Super Mario World', type: 'emulator', system: 'snes', age: 5 },
|
||||
{ id: 'megadrive-sonic', name: 'Sonic', type: 'emulator', system: 'megadrive', age: 5 }
|
||||
]);
|
||||
});
|
||||
|
||||
// Jellyfin Proxy (kindersicher)
|
||||
app.get('/api/media/list', async (req, res) => {
|
||||
// Hier Jellyfin API aufrufen und filtern
|
||||
res.json([
|
||||
{ id: 1, title: 'Peppa Wutz', thumb: '/covers/peppa.jpg', age: 2 },
|
||||
{ id: 2, title: 'Sendung mit der Maus', thumb: '/covers/maus.jpg', age: 3 }
|
||||
]);
|
||||
});
|
||||
|
||||
app.listen(3000, () => console.log('Kids Game Server on :3000'));
|
||||
Reference in New Issue
Block a user