1
0
Files
PonyWave-Tools/lights_out/index.html
2025-12-27 19:41:14 +01:00

757 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lights Out | PonyWave Tools</title>
<!-- Open Graph Meta Tags -->
<meta property="og:title" content="Lights Out | PonyWave Tools">
<meta property="og:description" content="Klassisches Lights Out Puzzle - Schalte alle Lichter aus!">
<meta property="og:type" content="website">
<meta property="og:url" content="https://tools.ponywave.de/lights_out">
<meta property="og:image" content="https://tools.ponywave.de/header.png">
<!-- Favicons -->
<link rel="icon" type="image/png" href="https://tools.ponywave.de/favicon.png">
<link rel="apple-touch-icon" href="https://tools.ponywave.de/favicon.png">
<!-- Umami Tracking -->
<script defer src="https://stats.ponywave.de/script"
data-website-id="9ef713d2-adb9-4906-9df5-708d8a8b9131"
data-tag="lights_out"></script>
<style>
/* CSS Variables für Theme-System */
:root {
--primary-color: #7F006E;
--secondary-color: #FF7FED;
--bg-gradient: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
--text-color: #2c3e50;
--cell-size: 60px;
--cell-bg-off: #333333;
--cell-bg-on: #FFD700;
--glow-color: rgba(255, 215, 0, 0.8);
--cell-border: #555555;
--game-bg: rgba(255, 255, 255, 0.95);
--shadow-color: rgba(0, 0, 0, 0.1);
--footer-color: rgba(255, 255, 255, 0.8);
--button-bg: #f0f0f0;
--button-hover: #e0e0e0;
--button-active: var(--primary-color);
--modal-bg: rgba(255, 255, 255, 0.98);
}
.dark-mode {
--bg-gradient: linear-gradient(135deg, #4A0040 0%, #CC65B5 100%);
--text-color: #ffffff;
--cell-bg-off: #2a2a2a;
--cell-bg-on: #FFA500;
--glow-color: rgba(255, 165, 0, 0.8);
--cell-border: #444444;
--game-bg: rgba(45, 45, 45, 0.95);
--shadow-color: rgba(0, 0, 0, 0.3);
--footer-color: rgba(0, 0, 0, 0.8);
--button-bg: #555555;
--button-hover: #666666;
--modal-bg: rgba(45, 45, 45, 0.98);
}
/* Body & Layout */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-image: var(--bg-gradient);
background-attachment: fixed;
color: var(--text-color);
min-height: 100vh;
padding: 20px 20px 80px;
text-align: center;
transition: background-image 0.3s ease;
}
/* Theme Toggle Button */
.theme-toggle {
position: fixed;
top: 20px;
right: 20px;
background: var(--button-bg);
color: var(--text-color);
border: none;
border-radius: 50%;
width: 50px;
height: 50px;
font-size: 1.5em;
cursor: pointer;
box-shadow: 0 2px 10px var(--shadow-color);
transition: all 0.3s ease;
z-index: 100;
}
.theme-toggle:hover {
transform: scale(1.1) rotate(15deg);
box-shadow: 0 4px 15px var(--shadow-color);
}
/* Titel */
h1 {
margin: 20px 0 30px;
font-size: 2.5em;
text-shadow: 2px 2px 4px var(--shadow-color);
}
/* Game Container */
.game-container {
display: flex;
flex-direction: column;
align-items: center;
margin: 0 auto;
max-width: 800px;
background: var(--game-bg);
padding: 30px;
border-radius: 20px;
box-shadow: 0 8px 20px var(--shadow-color);
backdrop-filter: blur(10px);
}
/* Schwierigkeitsauswahl */
.difficulty {
display: flex;
gap: 10px;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 20px;
}
.difficulty button {
padding: 10px 15px;
background: var(--button-bg);
color: var(--text-color);
border: 2px solid transparent;
border-radius: 8px;
cursor: pointer;
font-size: 0.9em;
font-weight: bold;
transition: all 0.3s ease;
}
.difficulty button:hover {
background: var(--button-hover);
transform: translateY(-2px);
box-shadow: 0 4px 8px var(--shadow-color);
}
.difficulty button.active {
background: var(--button-active);
color: white;
border-color: var(--secondary-color);
}
/* Game Header */
.game-header {
display: flex;
gap: 15px;
flex-wrap: wrap;
justify-content: center;
align-items: center;
margin-bottom: 20px;
width: 100%;
}
.moves-counter {
font-size: 1.3em;
font-weight: bold;
padding: 10px 20px;
background: var(--button-bg);
border-radius: 8px;
}
.btn {
padding: 10px 20px;
background: var(--button-bg);
color: var(--text-color);
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1em;
font-weight: bold;
transition: all 0.3s ease;
}
.btn:hover {
background: var(--button-hover);
transform: translateY(-2px);
box-shadow: 0 4px 8px var(--shadow-color);
}
.btn:active {
transform: translateY(0);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
/* Grid */
.grid {
display: grid;
gap: 4px;
background: var(--cell-border);
border: 3px solid var(--cell-border);
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
margin: 20px auto;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
/* Cells */
.cell {
width: var(--cell-size);
height: var(--cell-size);
background: var(--cell-bg-off);
border: 2px solid var(--cell-border);
cursor: pointer;
transition: all 0.3s ease;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.cell.on {
background: var(--cell-bg-on);
box-shadow:
0 0 20px var(--glow-color),
0 0 40px var(--glow-color) inset,
0 0 10px rgba(255, 255, 255, 0.5) inset;
}
.cell:hover:not(.animating) {
transform: scale(1.05);
border-color: var(--secondary-color);
z-index: 10;
}
.cell.animating {
animation: toggle-animation 0.3s ease;
}
@keyframes toggle-animation {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.15) rotate(5deg); }
}
/* Modal System */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.75);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
.modal-overlay.active {
opacity: 1;
pointer-events: all;
}
.modal {
background: var(--modal-bg);
padding: 30px;
border-radius: 15px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
max-width: 500px;
width: 90%;
position: relative;
transform: translateY(-30px);
transition: transform 0.3s ease;
}
.modal-overlay.active .modal {
transform: translateY(0);
}
.modal h2 {
margin-bottom: 15px;
font-size: 2em;
}
.modal p {
margin: 10px 0;
line-height: 1.6;
}
.modal ul {
text-align: left;
margin: 15px 0;
padding-left: 25px;
}
.modal li {
margin: 8px 0;
}
.modal .btn {
margin: 10px 5px 0;
}
.close-modal {
position: absolute;
top: 15px;
right: 15px;
background: none;
border: none;
font-size: 2em;
cursor: pointer;
color: var(--text-color);
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s ease;
}
.close-modal:hover {
background: var(--button-hover);
transform: rotate(90deg);
}
/* Footer */
footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: var(--footer-color);
color: var(--text-color);
padding: 15px;
text-align: center;
backdrop-filter: blur(10px);
}
footer a {
color: var(--text-color);
text-decoration: none;
font-weight: bold;
transition: color 0.3s ease;
}
footer a:hover {
color: var(--primary-color);
}
/* Responsive Design */
@media (max-width: 600px) {
:root {
--cell-size: 50px;
}
h1 {
font-size: 2em;
}
.game-container {
padding: 20px 15px;
}
.difficulty button {
padding: 8px 12px;
font-size: 0.85em;
}
.moves-counter {
font-size: 1.1em;
padding: 8px 15px;
}
.btn {
padding: 8px 15px;
font-size: 0.9em;
}
.modal {
padding: 20px;
}
.modal h2 {
font-size: 1.5em;
}
}
@media (max-width: 400px) {
:root {
--cell-size: 40px;
}
h1 {
font-size: 1.5em;
margin: 10px 0 20px;
}
.difficulty {
gap: 5px;
}
.difficulty button {
padding: 6px 10px;
font-size: 0.8em;
}
}
</style>
</head>
<body>
<!-- Theme Toggle Button -->
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
<!-- Titel -->
<h1>💡 Lights Out 💡</h1>
<!-- Game Container -->
<div class="game-container">
<!-- Schwierigkeitsauswahl -->
<div class="difficulty">
<button data-size="3" onclick="changeGridSize(3)">3×3</button>
<button data-size="4" onclick="changeGridSize(4)">4×4</button>
<button data-size="5" class="active" onclick="changeGridSize(5)">5×5 (Standard)</button>
<button data-size="6" onclick="changeGridSize(6)">6×6</button>
<button data-size="7" onclick="changeGridSize(7)">7×7</button>
</div>
<!-- Game Header -->
<div class="game-header">
<div class="moves-counter">
Züge: <span id="moves">0</span>
</div>
<button class="btn" onclick="neuesSpiel()">🔄 Neues Spiel</button>
<button class="btn" id="undoBtn" onclick="undo()">↶ Undo</button>
<button class="btn" onclick="zeigeAnleitung()">❓ Anleitung</button>
</div>
<!-- Grid (dynamisch generiert) -->
<div class="grid" id="grid"></div>
</div>
<!-- Anleitung Modal -->
<div id="instructionModal" class="modal-overlay">
<div class="modal">
<button class="close-modal" onclick="schliesseModal('instructionModal')">&times;</button>
<h2>📖 Spielanleitung</h2>
<p><strong>Ziel:</strong> Schalte alle Lichter aus!</p>
<p><strong>Regeln:</strong></p>
<ul>
<li>Klicke auf eine Zelle, um sie und ihre 4 orthogonal angrenzenden Nachbarn umzuschalten (oben, unten, links, rechts)</li>
<li>Eingeschaltet → Ausgeschaltet, Ausgeschaltet → Eingeschaltet</li>
<li>Versuche, alle Lichter mit möglichst wenigen Zügen auszuschalten</li>
<li>Du kannst Züge mit dem Undo-Button rückgängig machen</li>
</ul>
<p><strong>Tipp:</strong> Die Reihenfolge der Klicks ist egal! Jede Zelle muss maximal einmal geklickt werden.</p>
<button class="btn" onclick="schliesseModal('instructionModal')">Verstanden!</button>
</div>
</div>
<!-- Gewinn-Modal -->
<div id="winModal" class="modal-overlay">
<div class="modal">
<h2>🎉 Gewonnen! 🎉</h2>
<p>Alle Lichter sind aus!</p>
<p style="font-size: 1.3em; font-weight: bold;">Züge: <span id="finalMoves">0</span></p>
<p id="bestScore" style="font-size: 1.1em; color: var(--primary-color);">
Bester Score (<span id="gridSizeLabel">5×5</span>): <span id="bestMoves">--</span> Züge
</p>
<p id="newRecord" style="display: none; font-weight: bold; color: #4CAF50; font-size: 1.2em;">
🏆 Neuer Rekord! 🏆
</p>
<button class="btn" onclick="neuesSpiel()">🔄 Neues Spiel</button>
<button class="btn" onclick="schliesseModal('winModal')">Schließen</button>
</div>
</div>
<!-- Footer -->
<footer>
<a href="https://tools.ponywave.de/">← Zurück zur Startseite</a> |
© <span id="currentYear"></span> <a href="https://akamaru.de" target="_blank">Akamaru</a>
</footer>
<script>
// ===== STATE MANAGEMENT =====
let gridSize = 5; // Aktuelle Grid-Größe (3-7)
let moves = 0; // Aktuelle Züge
let grid = []; // 2D-Array: grid[row][col] = true/false (on/off)
let gameWon = false; // Gewinn-Flag
let bestScores = {}; // localStorage: { '3': 15, '5': 22, ... }
let moveHistory = []; // Stack für Undo: [[row, col], ...]
// ===== INITIALISIERUNG =====
function init() {
initTheme();
loadBestScores();
neuesSpiel();
document.getElementById('currentYear').textContent = new Date().getFullYear();
}
// ===== GRID-FUNKTIONEN =====
function initGrid() {
// Starte mit leerem Grid (alle Lichter aus)
grid = Array(gridSize).fill(null).map(() => Array(gridSize).fill(false));
// Rückwärts-Simulation: Zufällige Klicks für lösbare Konfiguration
const numRandomClicks = Math.floor(gridSize * gridSize * 0.6);
const clickedCells = new Set();
for (let i = 0; i < numRandomClicks; i++) {
const row = Math.floor(Math.random() * gridSize);
const col = Math.floor(Math.random() * gridSize);
const key = `${row},${col}`;
// Vermeide doppelte Klicks (da sie sich aufheben würden)
if (!clickedCells.has(key)) {
clickedCells.add(key);
toggleCellAndNeighbors(row, col, true); // silent=true
}
}
}
function toggleCellAndNeighbors(row, col, silent = false) {
// Hauptzelle togglen
grid[row][col] = !grid[row][col];
// 4 orthogonale Nachbarn (oben, unten, links, rechts)
const neighbors = [
[row - 1, col], // Oben
[row + 1, col], // Unten
[row, col - 1], // Links
[row, col + 1] // Rechts
];
neighbors.forEach(([r, c]) => {
// Nur wenn innerhalb des Grids
if (r >= 0 && r < gridSize && c >= 0 && c < gridSize) {
grid[r][c] = !grid[r][c];
}
});
if (!silent) {
moves++;
moveHistory.push([row, col]);
updateMovesDisplay();
updateUndoButton();
renderGrid();
checkWin();
}
}
function renderGrid() {
const gridElement = document.getElementById('grid');
gridElement.innerHTML = '';
gridElement.style.gridTemplateColumns = `repeat(${gridSize}, var(--cell-size))`;
for (let row = 0; row < gridSize; row++) {
for (let col = 0; col < gridSize; col++) {
const cell = document.createElement('div');
cell.className = 'cell';
cell.dataset.row = row;
cell.dataset.col = col;
if (grid[row][col]) {
cell.classList.add('on');
}
// Event Listeners
cell.addEventListener('click', () => handleCellClick(row, col));
cell.addEventListener('touchstart', (e) => {
e.preventDefault();
handleCellClick(row, col);
}, { passive: false });
gridElement.appendChild(cell);
}
}
}
// ===== EVENT HANDLING =====
function handleCellClick(row, col) {
if (gameWon) return;
// Animation
const cellElement = document.querySelector(`.cell[data-row="${row}"][data-col="${col}"]`);
if (cellElement) {
cellElement.classList.add('animating');
setTimeout(() => cellElement.classList.remove('animating'), 300);
}
toggleCellAndNeighbors(row, col);
}
function undo() {
if (moveHistory.length === 0 || gameWon) return;
const [row, col] = moveHistory.pop();
// Toggle ohne Züge zu erhöhen (nur Grid-State zurücksetzen)
grid[row][col] = !grid[row][col];
const neighbors = [
[row - 1, col],
[row + 1, col],
[row, col - 1],
[row, col + 1]
];
neighbors.forEach(([r, c]) => {
if (r >= 0 && r < gridSize && c >= 0 && c < gridSize) {
grid[r][c] = !grid[r][c];
}
});
moves--;
updateMovesDisplay();
updateUndoButton();
renderGrid();
}
function changeGridSize(size) {
gridSize = size;
// Active-Button aktualisieren
document.querySelectorAll('.difficulty button').forEach(btn => {
btn.classList.toggle('active', parseInt(btn.dataset.size) === size);
});
// Zellengröße anpassen für große Grids
if (size >= 6) {
document.documentElement.style.setProperty('--cell-size', '50px');
} else if (size >= 5) {
document.documentElement.style.setProperty('--cell-size', '60px');
} else {
document.documentElement.style.setProperty('--cell-size', '70px');
}
neuesSpiel();
}
function neuesSpiel() {
moves = 0;
gameWon = false;
moveHistory = [];
schliesseModal('winModal');
updateMovesDisplay();
updateUndoButton();
initGrid();
renderGrid();
}
// ===== GEWINN-ERKENNUNG =====
function checkWin() {
const allOff = grid.every(row => row.every(cell => !cell));
if (allOff && !gameWon) {
gameWon = true;
// Bestscores aktualisieren
const isNewRecord = !bestScores[gridSize] || moves < bestScores[gridSize];
if (isNewRecord) {
bestScores[gridSize] = moves;
saveBestScores();
}
setTimeout(() => showWinModal(isNewRecord), 500);
}
}
function showWinModal(isNewRecord) {
document.getElementById('finalMoves').textContent = moves;
document.getElementById('gridSizeLabel').textContent = `${gridSize}×${gridSize}`;
const bestMoves = bestScores[gridSize];
document.getElementById('bestMoves').textContent = bestMoves || '--';
const newRecordElement = document.getElementById('newRecord');
newRecordElement.style.display = isNewRecord ? 'block' : 'none';
const modal = document.getElementById('winModal');
modal.classList.add('active');
}
// ===== UI-UPDATES =====
function updateMovesDisplay() {
document.getElementById('moves').textContent = moves;
}
function updateUndoButton() {
const undoBtn = document.getElementById('undoBtn');
undoBtn.disabled = moveHistory.length === 0 || gameWon;
}
// ===== MODAL-FUNKTIONEN =====
function zeigeAnleitung() {
document.getElementById('instructionModal').classList.add('active');
}
function schliesseModal(modalId) {
document.getElementById(modalId).classList.remove('active');
}
// Modal schließen bei Klick auf Overlay
document.querySelectorAll('.modal-overlay').forEach(overlay => {
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
overlay.classList.remove('active');
}
});
});
// ===== LOCALSTORAGE =====
function loadBestScores() {
const saved = localStorage.getItem('lightsOutBestScores');
bestScores = saved ? JSON.parse(saved) : {};
}
function saveBestScores() {
localStorage.setItem('lightsOutBestScores', JSON.stringify(bestScores));
}
// ===== THEME TOGGLE =====
function toggleTheme() {
document.body.classList.toggle('dark-mode');
const btn = document.querySelector('.theme-toggle');
btn.textContent = document.body.classList.contains('dark-mode') ? '☀️' : '🌙';
localStorage.setItem('theme', document.body.classList.contains('dark-mode') ? 'dark' : 'light');
}
function initTheme() {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark' || (!savedTheme && prefersDark)) {
document.body.classList.add('dark-mode');
document.querySelector('.theme-toggle').textContent = '☀️';
}
}
// ===== START =====
window.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>