hockey-manager/src/engine/game-engine.js
2025-09-16 11:18:20 +02:00

350 lines
10 KiB
JavaScript

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: 10, 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: 970, 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
}
}