588 lines
22 KiB
HTML
588 lines
22 KiB
HTML
<!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> |