class Renderer { constructor(canvas) { this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.setupCanvas(); this.setupFixedCamera(); } setupCanvas() { this.canvas.style.imageRendering = 'pixelated'; this.ctx.imageSmoothingEnabled = false; } setupFixedCamera() { const rinkWidth = 1000; const rinkHeight = 600; const padding = 50; const scaleX = this.canvas.width / (rinkWidth + padding * 2); const scaleY = this.canvas.height / (rinkHeight + padding * 2); const zoom = Math.min(scaleX, scaleY); const x = (this.canvas.width - rinkWidth * zoom) / 2; const y = (this.canvas.height - rinkHeight * zoom) / 2; this.camera = { x: x, y: y, zoom: zoom, target: null, smoothing: 0.1 }; } clear() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); } drawRink(gameState) { const rink = gameState.rink; this.ctx.save(); this.applyCamera(); this.ctx.fillStyle = '#f8f8f8'; this.ctx.fillRect(0, 0, rink.width, rink.height); this.drawRinkLines(rink); this.drawGoals(rink); this.drawFaceoffDots(rink); this.drawCreases(rink); this.ctx.restore(); } drawRinkLines(rink) { this.ctx.strokeStyle = '#d32f2f'; this.ctx.lineWidth = 3; this.ctx.beginPath(); this.ctx.rect(0, 0, rink.width, rink.height); this.ctx.stroke(); this.ctx.strokeStyle = '#2196f3'; this.ctx.lineWidth = 2; this.ctx.beginPath(); this.ctx.moveTo(rink.centerX, 0); this.ctx.lineTo(rink.centerX, rink.height); this.ctx.stroke(); const zoneWidth = rink.width / 3; this.ctx.strokeStyle = '#2196f3'; this.ctx.setLineDash([10, 5]); this.ctx.beginPath(); this.ctx.moveTo(zoneWidth, 0); this.ctx.lineTo(zoneWidth, rink.height); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.moveTo(rink.width - zoneWidth, 0); this.ctx.lineTo(rink.width - zoneWidth, rink.height); this.ctx.stroke(); this.ctx.setLineDash([]); this.ctx.beginPath(); this.ctx.arc(rink.centerX, rink.centerY, 100, 0, Math.PI * 2); this.ctx.stroke(); } drawGoals(rink) { this.ctx.strokeStyle = '#d32f2f'; this.ctx.lineWidth = 4; const goalY = rink.centerY; const goalHeight = rink.goalHeight; this.ctx.beginPath(); this.ctx.moveTo(0, goalY - goalHeight); this.ctx.lineTo(-10, goalY - goalHeight); this.ctx.lineTo(-10, goalY + goalHeight); this.ctx.lineTo(0, goalY + goalHeight); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.moveTo(rink.width, goalY - goalHeight); this.ctx.lineTo(rink.width + 10, goalY - goalHeight); this.ctx.lineTo(rink.width + 10, goalY + goalHeight); this.ctx.lineTo(rink.width, goalY + goalHeight); this.ctx.stroke(); this.ctx.fillStyle = 'rgba(211, 47, 47, 0.1)'; this.ctx.fillRect(-10, goalY - goalHeight, 10, goalHeight * 2); this.ctx.fillRect(rink.width, goalY - goalHeight, 10, goalHeight * 2); } drawCreases(rink) { this.ctx.strokeStyle = '#4fc3f7'; this.ctx.lineWidth = 2; this.ctx.fillStyle = 'rgba(79, 195, 247, 0.1)'; const creaseRadius = 60; const goalY = rink.centerY; this.ctx.beginPath(); this.ctx.arc(0, goalY, creaseRadius, -Math.PI/2, Math.PI/2); this.ctx.fill(); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.arc(rink.width, goalY, creaseRadius, Math.PI/2, -Math.PI/2); this.ctx.fill(); this.ctx.stroke(); } drawFaceoffDots(rink) { this.ctx.fillStyle = '#d32f2f'; rink.faceoffDots.forEach(dot => { this.ctx.beginPath(); this.ctx.arc(dot.x, dot.y, 8, 0, Math.PI * 2); this.ctx.fill(); this.ctx.strokeStyle = '#d32f2f'; this.ctx.lineWidth = 2; this.ctx.beginPath(); this.ctx.arc(dot.x, dot.y, 30, 0, Math.PI * 2); this.ctx.stroke(); }); } drawPlayers(players) { this.ctx.save(); this.applyCamera(); players.forEach(player => { player.render(this.ctx); }); this.ctx.restore(); } drawPuck(puck) { this.ctx.save(); this.applyCamera(); puck.render(this.ctx); this.ctx.restore(); } drawUI(gameState) { this.updateScoreBoard(gameState); this.updateGameStats(gameState); } updateScoreBoard(gameState) { document.querySelector('.team.home .score').textContent = gameState.homeScore; document.querySelector('.team.away .score').textContent = gameState.awayScore; document.getElementById('period').textContent = gameState.getPeriodName(); document.getElementById('clock').textContent = gameState.formatTime(gameState.timeRemaining); } updateGameStats(gameState) { document.getElementById('home-shots').textContent = gameState.stats.home.shots; document.getElementById('away-shots').textContent = gameState.stats.away.shots; } drawParticleEffect(position, type, color = '#ffff00') { this.ctx.save(); this.applyCamera(); switch (type) { case 'goal': this.drawGoalEffect(position); break; case 'hit': this.drawHitEffect(position, color); break; case 'save': this.drawSaveEffect(position); break; } this.ctx.restore(); } drawGoalEffect(position) { this.ctx.fillStyle = '#ffff00'; this.ctx.strokeStyle = '#ff8800'; this.ctx.lineWidth = 3; for (let i = 0; i < 8; i++) { const angle = (i / 8) * Math.PI * 2; const x = position.x + Math.cos(angle) * 30; const y = position.y + Math.sin(angle) * 30; this.ctx.beginPath(); this.ctx.arc(x, y, 5, 0, Math.PI * 2); this.ctx.fill(); this.ctx.stroke(); } } drawHitEffect(position, color) { this.ctx.strokeStyle = color; this.ctx.lineWidth = 4; for (let i = 0; i < 6; i++) { const angle = (i / 6) * Math.PI * 2; const startX = position.x + Math.cos(angle) * 10; const startY = position.y + Math.sin(angle) * 10; const endX = position.x + Math.cos(angle) * 25; const endY = position.y + Math.sin(angle) * 25; this.ctx.beginPath(); this.ctx.moveTo(startX, startY); this.ctx.lineTo(endX, endY); this.ctx.stroke(); } } drawSaveEffect(position) { this.ctx.fillStyle = '#4fc3f7'; this.ctx.strokeStyle = '#0288d1'; this.ctx.lineWidth = 2; this.ctx.beginPath(); this.ctx.arc(position.x, position.y, 20, 0, Math.PI * 2); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.arc(position.x, position.y, 15, 0, Math.PI * 2); this.ctx.stroke(); } updateCamera(target) { // Camera is now fixed - no updates needed } applyCamera() { this.ctx.translate(this.camera.x, this.camera.y); this.ctx.scale(this.camera.zoom, this.camera.zoom); } setZoom(zoom) { this.camera.zoom = Math.max(0.5, Math.min(2.0, zoom)); } screenToWorld(screenPos) { return new Vector2( (screenPos.x - this.camera.x) / this.camera.zoom, (screenPos.y - this.camera.y) / this.camera.zoom ); } worldToScreen(worldPos) { return new Vector2( worldPos.x * this.camera.zoom + this.camera.x, worldPos.y * this.camera.zoom + this.camera.y ); } drawDebugInfo(gameState, players, puck) { if (!window.debugMode) return; this.ctx.save(); this.ctx.fillStyle = 'rgba(0, 0, 0, 0.8)'; this.ctx.fillRect(10, 10, 200, 150); this.ctx.fillStyle = '#fff'; this.ctx.font = '12px monospace'; this.ctx.fillText(`FPS: ${Math.round(1000 / 16)}`, 20, 30); this.ctx.fillText(`Players: ${players.length}`, 20, 50); this.ctx.fillText(`Puck Speed: ${Math.round(puck.velocity.magnitude())}`, 20, 70); this.ctx.fillText(`Game Time: ${gameState.formatTime(gameState.timeRemaining)}`, 20, 90); this.ctx.fillText(`Period: ${gameState.period}`, 20, 110); this.ctx.fillText(`Paused: ${gameState.isPaused}`, 20, 130); this.ctx.fillText(`Speed: ${gameState.gameSpeed}x`, 20, 150); this.ctx.restore(); } }