Initial commit: kids games
Contains game-jumpnrun and game-labyrinth projects. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
237
game-labyrinth/js/main.js
Normal file
237
game-labyrinth/js/main.js
Normal file
@@ -0,0 +1,237 @@
|
||||
// Game Loop + State Management
|
||||
|
||||
const Game = {
|
||||
state: 'menu', // menu, playing, levelComplete, gameOver, win
|
||||
currentLevel: 0,
|
||||
elapsedTime: 0,
|
||||
totalTime: 0,
|
||||
lastTime: 0,
|
||||
grid: null,
|
||||
|
||||
isMobile: false,
|
||||
|
||||
init() {
|
||||
this.isMobile = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
|
||||
Renderer.init();
|
||||
this.setupInput();
|
||||
this.fitCanvas();
|
||||
window.addEventListener('resize', () => this.fitCanvas());
|
||||
this.state = 'menu';
|
||||
this.lastTime = performance.now() / 1000;
|
||||
this.loop();
|
||||
},
|
||||
|
||||
fitCanvas() {
|
||||
const canvas = Renderer.canvas;
|
||||
const dpadHeight = this.isMobile ? 190 : 0;
|
||||
const maxW = window.innerWidth - 8;
|
||||
const maxH = window.innerHeight - 8 - dpadHeight;
|
||||
const scaleX = maxW / canvas.width;
|
||||
const scaleY = maxH / canvas.height;
|
||||
const scale = Math.min(scaleX, scaleY, 3); // max 3x um nicht zu groß auf Desktop
|
||||
canvas.style.width = Math.floor(canvas.width * scale) + 'px';
|
||||
canvas.style.height = Math.floor(canvas.height * scale) + 'px';
|
||||
},
|
||||
|
||||
setupInput() {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
this.handleKey(e.key);
|
||||
});
|
||||
|
||||
// Touch-Steuerung (Swipe auf Canvas)
|
||||
let touchStartX = 0, touchStartY = 0;
|
||||
const canvas = Renderer.canvas;
|
||||
canvas.addEventListener('touchstart', (e) => {
|
||||
e.preventDefault();
|
||||
touchStartX = e.touches[0].clientX;
|
||||
touchStartY = e.touches[0].clientY;
|
||||
});
|
||||
canvas.addEventListener('touchend', (e) => {
|
||||
e.preventDefault();
|
||||
const dx = e.changedTouches[0].clientX - touchStartX;
|
||||
const dy = e.changedTouches[0].clientY - touchStartY;
|
||||
const absDx = Math.abs(dx);
|
||||
const absDy = Math.abs(dy);
|
||||
if (Math.max(absDx, absDy) < 20) {
|
||||
// Tap = Enter
|
||||
this.handleKey('Enter');
|
||||
return;
|
||||
}
|
||||
if (absDx > absDy) {
|
||||
this.handleKey(dx > 0 ? 'ArrowRight' : 'ArrowLeft');
|
||||
} else {
|
||||
this.handleKey(dy > 0 ? 'ArrowDown' : 'ArrowUp');
|
||||
}
|
||||
});
|
||||
|
||||
// D-Pad Buttons
|
||||
const dpadMap = {
|
||||
'dpad-up': 'ArrowUp',
|
||||
'dpad-down': 'ArrowDown',
|
||||
'dpad-left': 'ArrowLeft',
|
||||
'dpad-right': 'ArrowRight'
|
||||
};
|
||||
for (const [id, key] of Object.entries(dpadMap)) {
|
||||
const btn = document.getElementById(id);
|
||||
if (!btn) continue;
|
||||
btn.addEventListener('touchstart', (e) => {
|
||||
e.preventDefault();
|
||||
this.handleKey(key);
|
||||
});
|
||||
// Auch wiederholtes Drücken ermöglichen
|
||||
let interval = null;
|
||||
btn.addEventListener('touchstart', () => {
|
||||
interval = setInterval(() => this.handleKey(key), 150);
|
||||
});
|
||||
btn.addEventListener('touchend', () => clearInterval(interval));
|
||||
btn.addEventListener('touchcancel', () => clearInterval(interval));
|
||||
}
|
||||
},
|
||||
|
||||
handleKey(key) {
|
||||
switch (this.state) {
|
||||
case 'menu':
|
||||
if (key === 'Enter' || key === ' ') this.startGame();
|
||||
break;
|
||||
case 'playing':
|
||||
Player.handleInput(key);
|
||||
break;
|
||||
case 'gameOver':
|
||||
if (key === 'Enter' || key === ' ') this.startGame();
|
||||
break;
|
||||
case 'levelComplete':
|
||||
if (key === 'Enter' || key === ' ') this.nextLevel();
|
||||
break;
|
||||
case 'win':
|
||||
if (key === 'Enter' || key === ' ') {
|
||||
this.currentLevel = 0;
|
||||
this.totalTime = 0;
|
||||
this.startGame();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
startGame() {
|
||||
this.currentLevel = 0;
|
||||
this.totalTime = 0;
|
||||
this.loadLevel(0);
|
||||
this.state = 'playing';
|
||||
this.fitCanvas();
|
||||
},
|
||||
|
||||
loadLevel(index) {
|
||||
const level = LEVELS[index];
|
||||
// Deep copy des Grids
|
||||
this.grid = level.grid.map(row => [...row]);
|
||||
|
||||
const cols = this.grid[0].length;
|
||||
const rows = this.grid.length;
|
||||
Renderer.resize(cols, rows);
|
||||
this.fitCanvas();
|
||||
|
||||
// Spieler-Start finden
|
||||
const playerStart = Utils.findTile(this.grid, 2);
|
||||
Player.init(playerStart.col, playerStart.row);
|
||||
|
||||
// Monster-Start finden
|
||||
const monsterStart = Utils.findTile(this.grid, 4);
|
||||
Monster.init(monsterStart.col, monsterStart.row, level.monsterSpeed, level.speedIncrease, level.maxMonsterSpeed);
|
||||
|
||||
// Start-Tiles zu Wegen machen (damit man drüber laufen kann)
|
||||
this.grid[playerStart.row][playerStart.col] = 0;
|
||||
this.grid[monsterStart.row][monsterStart.col] = 0;
|
||||
|
||||
this.elapsedTime = 0;
|
||||
},
|
||||
|
||||
nextLevel() {
|
||||
this.currentLevel++;
|
||||
if (this.currentLevel >= LEVELS.length) {
|
||||
this.state = 'win';
|
||||
} else {
|
||||
this.loadLevel(this.currentLevel);
|
||||
this.state = 'playing';
|
||||
}
|
||||
},
|
||||
|
||||
checkWin() {
|
||||
// Spieler hat den Ausgang erreicht
|
||||
const exit = Utils.findTile(this.grid, 3);
|
||||
if (exit && Player.col === exit.col && Player.row === exit.row) {
|
||||
this.totalTime += this.elapsedTime;
|
||||
this.state = 'levelComplete';
|
||||
}
|
||||
},
|
||||
|
||||
checkGameOver() {
|
||||
if (Monster.checkCollision(Player.visualX, Player.visualY)) {
|
||||
this.state = 'gameOver';
|
||||
}
|
||||
},
|
||||
|
||||
update(dt) {
|
||||
if (this.state !== 'playing') return;
|
||||
|
||||
this.elapsedTime += dt;
|
||||
Player.update(dt, this.grid);
|
||||
Monster.update(dt, this.grid, Player.col, Player.row, this.elapsedTime);
|
||||
|
||||
this.checkWin();
|
||||
this.checkGameOver();
|
||||
},
|
||||
|
||||
draw(time) {
|
||||
switch (this.state) {
|
||||
case 'menu':
|
||||
Renderer.drawMenuScreen();
|
||||
break;
|
||||
|
||||
case 'playing':
|
||||
Renderer.clear();
|
||||
Renderer.drawGrid(this.grid, time);
|
||||
Player.draw(time);
|
||||
Monster.draw(time);
|
||||
Renderer.drawHUD(this.currentLevel + 1, this.elapsedTime, Monster.speed);
|
||||
break;
|
||||
|
||||
case 'gameOver':
|
||||
Renderer.clear();
|
||||
Renderer.drawGrid(this.grid, time);
|
||||
Player.draw(time);
|
||||
Monster.draw(time);
|
||||
Renderer.drawHUD(this.currentLevel + 1, this.elapsedTime, Monster.speed);
|
||||
Renderer.drawGameOverScreen(this.currentLevel + 1, this.elapsedTime);
|
||||
break;
|
||||
|
||||
case 'levelComplete':
|
||||
Renderer.clear();
|
||||
Renderer.drawGrid(this.grid, time);
|
||||
Player.draw(time);
|
||||
Renderer.drawHUD(this.currentLevel + 1, this.elapsedTime, Monster.speed);
|
||||
Renderer.drawLevelCompleteScreen(this.currentLevel + 1);
|
||||
break;
|
||||
|
||||
case 'win':
|
||||
Renderer.drawWinScreen(this.totalTime);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
loop() {
|
||||
const now = performance.now() / 1000;
|
||||
const dt = Math.min(now - this.lastTime, 0.05); // Cap delta time
|
||||
this.lastTime = now;
|
||||
|
||||
this.update(dt);
|
||||
this.draw(now);
|
||||
|
||||
requestAnimationFrame(() => this.loop());
|
||||
}
|
||||
};
|
||||
|
||||
// Start!
|
||||
Game.init();
|
||||
Reference in New Issue
Block a user