Initial commit - Kinderspiele

This commit is contained in:
OpenClaw
2026-04-26 09:44:19 +02:00
commit 35817527c8
29 changed files with 10949 additions and 0 deletions
+600
View File
@@ -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>