class GameEngine { constructor(canvas) { this.canvas = canvas; this.gameState = new GameState(); this.renderer = new Renderer(canvas); this.audioSystem = new AudioSystem(); this.players = []; this.puck = new Puck(500, 300); this.lastTime = 0; this.deltaTime = 0; this.targetFPS = 60; this.frameTime = 1000 / this.targetFPS; this.isRunning = false; this.effects = []; this.setupPlayers(); this.setupEventListeners(); this.setupControls(); } setupPlayers() { const homeTeamPositions = [ { role: 'G', x: 80, y: 300 }, { role: 'LD', x: 200, y: 220 }, { role: 'RD', x: 200, y: 380 }, { role: 'LW', x: 350, y: 200 }, { role: 'C', x: 400, y: 300 }, { role: 'RW', x: 350, y: 400 } ]; const awayTeamPositions = [ { role: 'G', x: 920, y: 300 }, { role: 'LD', x: 800, y: 220 }, { role: 'RD', x: 800, y: 380 }, { role: 'LW', x: 650, y: 200 }, { role: 'C', x: 600, y: 300 }, { role: 'RW', x: 650, y: 400 } ]; homeTeamPositions.forEach((pos, index) => { const player = new Player( `home_${index}`, `Player ${index + 1}`, 'home', pos.role, pos.x, pos.y ); this.players.push(player); }); awayTeamPositions.forEach((pos, index) => { const player = new Player( `away_${index}`, `Player ${index + 7}`, 'away', pos.role, pos.x, pos.y ); this.players.push(player); }); } setupEventListeners() { this.gameState.on('goal', (data) => { this.addEffect({ type: 'goal', position: this.puck.position.copy(), duration: 2000, startTime: Date.now() }); this.audioSystem.onGoal(data.team); setTimeout(() => { this.startFaceoff(); }, 2000); }); this.gameState.on('save', (data) => { this.addEffect({ type: 'save', position: this.puck.position.copy(), duration: 1000, startTime: Date.now() }); this.audioSystem.onSave(); }); this.gameState.on('penalty', (data) => { const penalizedPlayer = this.players.find(p => p.name === data.player); if (penalizedPlayer) { penalizedPlayer.state.penaltyTime = data.duration; } this.audioSystem.onPenalty(); }); this.gameState.on('period-end', () => { this.audioSystem.onPeriodEnd(); }); } setupControls() { document.getElementById('play-pause').addEventListener('click', () => { this.gameState.togglePause(); }); document.getElementById('speed-control').addEventListener('click', (e) => { const speeds = [0.5, 1, 2, 4]; const currentIndex = speeds.indexOf(this.gameState.gameSpeed); const nextIndex = (currentIndex + 1) % speeds.length; this.gameState.setSpeed(speeds[nextIndex]); e.target.textContent = `Speed: ${speeds[nextIndex]}x`; }); document.getElementById('sound-toggle').addEventListener('click', (e) => { const enabled = this.audioSystem.toggle(); e.target.textContent = `Sound: ${enabled ? 'ON' : 'OFF'}`; }); document.getElementById('reset-game').addEventListener('click', () => { this.resetGame(); }); window.addEventListener('keydown', (e) => { switch (e.key) { case ' ': e.preventDefault(); this.gameState.togglePause(); break; case 'd': window.debugMode = !window.debugMode; break; case 'r': this.resetGame(); break; } }); this.canvas.addEventListener('wheel', (e) => { e.preventDefault(); const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1; this.renderer.setZoom(this.renderer.camera.zoom * zoomFactor); }); } start() { this.isRunning = true; this.lastTime = performance.now(); this.gameLoop(); } stop() { this.isRunning = false; } gameLoop(currentTime = performance.now()) { if (!this.isRunning) return; this.deltaTime = (currentTime - this.lastTime) / 1000; this.lastTime = currentTime; this.deltaTime = Math.min(this.deltaTime, 1/30); this.update(this.deltaTime); this.render(); requestAnimationFrame((time) => this.gameLoop(time)); } update(deltaTime) { if (this.gameState.isPaused) return; this.gameState.updateTime(deltaTime); this.players.forEach(player => { player.update(deltaTime, this.gameState, this.puck, this.players); }); this.puck.update(deltaTime, this.gameState, this.players); this.updateCollisions(); this.updateEffects(deltaTime); this.renderer.updateCamera(this.puck.position); } updateCollisions() { for (let i = 0; i < this.players.length; i++) { for (let j = i + 1; j < this.players.length; j++) { const player1 = this.players[i]; const player2 = this.players[j]; if (Physics.checkCircleCollision( player1.position, player1.radius, player2.position, player2.radius )) { Physics.resolveCircleCollision(player1, player2); if (player1.team !== player2.team && (player1.velocity.magnitude() > 100 || player2.velocity.magnitude() > 100)) { this.handlePlayerCollision(player1, player2); } } } } } handlePlayerCollision(player1, player2) { const speed1 = player1.velocity.magnitude(); const speed2 = player2.velocity.magnitude(); if (speed1 > 150 || speed2 > 150) { this.addEffect({ type: 'hit', position: player1.position.lerp(player2.position, 0.5), duration: 500, startTime: Date.now() }); if (Math.random() < 0.1) { const penalizedPlayer = speed1 > speed2 ? player1 : player2; this.gameState.addPenalty( penalizedPlayer.team, penalizedPlayer.name, 'Checking', 120 ); } } } render() { this.renderer.clear(); this.renderer.drawRink(this.gameState); this.renderer.drawPlayers(this.players); this.renderer.drawPuck(this.puck); this.renderEffects(); this.renderer.drawUI(this.gameState); this.renderer.drawDebugInfo(this.gameState, this.players, this.puck); } renderEffects() { this.effects.forEach(effect => { const elapsed = Date.now() - effect.startTime; const progress = elapsed / effect.duration; if (progress < 1) { this.renderer.drawParticleEffect(effect.position, effect.type); } }); } updateEffects(deltaTime) { this.effects = this.effects.filter(effect => { const elapsed = Date.now() - effect.startTime; return elapsed < effect.duration; }); } addEffect(effect) { this.effects.push(effect); } startFaceoff(position = null) { if (!position) { position = new Vector2(this.gameState.rink.centerX, this.gameState.rink.centerY); } this.puck.reset(position.x, position.y); const nearbyPlayers = this.players.filter(player => player.position.distance(position) < 100 ).sort((a, b) => a.position.distance(position) - b.position.distance(position) ); if (nearbyPlayers.length >= 2) { const player1 = nearbyPlayers[0]; const player2 = nearbyPlayers[1]; // Immediately start the faceoff this.puck.faceoff(player1, player2); } else { // If no players nearby, give puck to closest player const allPlayers = this.players.filter(p => p.role !== 'G'); if (allPlayers.length > 0) { const closest = allPlayers.reduce((closest, player) => player.position.distance(position) < closest.position.distance(position) ? player : closest ); closest.state.hasPuck = true; this.puck.lastPlayerTouch = closest; this.puck.lastTeamTouch = closest.team; } } } resetGame() { this.gameState.reset(); this.effects = []; // Clear all player states first this.players.forEach(player => { player.position = player.homePosition.copy(); player.velocity = new Vector2(0, 0); player.state.hasPuck = false; player.state.energy = 100; player.state.penaltyTime = 0; player.aiState.lastAction = 0; }); // Reset puck after players this.puck.reset(); // Start faceoff after a short delay setTimeout(() => { this.startFaceoff(); }, 1000); } getGameData() { return { gameState: this.gameState.getGameState(), players: this.players.map(p => ({ id: p.id, name: p.name, team: p.team, position: p.position, role: p.role, state: p.state })), puck: { position: this.puck.position, velocity: this.puck.velocity, speed: this.puck.getSpeed() } }; } loadGameData(data) { // Implementation for loading saved game state // This would restore the game from a saved state } }