325 lines
9.2 KiB
JavaScript
325 lines
9.2 KiB
JavaScript
class GameEngine {
|
|
constructor(canvas) {
|
|
this.canvas = canvas;
|
|
this.gameState = new GameState();
|
|
this.renderer = new Renderer(canvas);
|
|
|
|
this.players = [];
|
|
this.puck = new Puck(500, 300);
|
|
this.puckActive = false; // Puck starts inactive for faceoff
|
|
|
|
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()
|
|
});
|
|
|
|
|
|
setTimeout(() => {
|
|
this.startFaceoff();
|
|
}, 2000);
|
|
});
|
|
|
|
this.gameState.on('save', (data) => {
|
|
this.addEffect({
|
|
type: 'save',
|
|
position: this.puck.position.copy(),
|
|
duration: 1000,
|
|
startTime: Date.now()
|
|
});
|
|
|
|
});
|
|
|
|
|
|
this.gameState.on('period-end', () => {
|
|
});
|
|
|
|
this.gameState.on('faceoff-start', (data) => {
|
|
this.puckActive = false;
|
|
});
|
|
|
|
this.gameState.on('faceoff-drop', () => {
|
|
this.puckActive = true;
|
|
});
|
|
|
|
this.gameState.on('faceoff-complete', (data) => {
|
|
this.puck.faceoffDrop(data.winner, data.location, this.gameState.faceoff.participants);
|
|
});
|
|
}
|
|
|
|
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('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.gameState.updateFaceoff(deltaTime);
|
|
|
|
this.players.forEach(player => {
|
|
player.update(deltaTime, this.gameState, this.puck, this.players);
|
|
});
|
|
|
|
if (this.puckActive) {
|
|
this.puck.update(deltaTime, this.gameState, this.players);
|
|
this.updateCollisions();
|
|
this.renderer.updateCamera(this.puck.position);
|
|
}
|
|
|
|
this.updateEffects(deltaTime);
|
|
}
|
|
|
|
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()
|
|
});
|
|
}
|
|
}
|
|
|
|
render() {
|
|
this.renderer.clear();
|
|
this.renderer.drawRink(this.gameState);
|
|
this.renderer.drawPlayers(this.players);
|
|
|
|
if (this.puckActive) {
|
|
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(location = null) {
|
|
if (!location) {
|
|
location = { x: this.gameState.rink.centerX, y: this.gameState.rink.centerY };
|
|
}
|
|
|
|
console.log('GameEngine.startFaceoff called with location:', location);
|
|
|
|
// Reset puck position but keep it inactive
|
|
this.puck.reset(location.x, location.y);
|
|
this.puckActive = false;
|
|
|
|
// Start the faceoff sequence
|
|
this.gameState.startFaceoff(location);
|
|
}
|
|
|
|
resetGame() {
|
|
this.gameState.reset();
|
|
this.effects = [];
|
|
this.puckActive = false;
|
|
|
|
// 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.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
|
|
}
|
|
} |