From ac5b83afeeff8dad3c84836fe057471a2c6fe11e Mon Sep 17 00:00:00 2001 From: Pierre Wessman <4029607+pierrewessman@users.noreply.github.com> Date: Tue, 16 Sep 2025 14:24:09 +0200 Subject: [PATCH] debug system --- CLAUDE.md | 42 +++- README.md | 43 +++- css/styles.css | 166 +++++++++++++ index.html | 34 +++ src/engine/game-engine.js | 8 +- src/systems/debug-system.js | 464 ++++++++++++++++++++++++++++++++++++ src/systems/renderer.js | 227 +++++++++++++++++- 7 files changed, 975 insertions(+), 9 deletions(-) create mode 100644 src/systems/debug-system.js diff --git a/CLAUDE.md b/CLAUDE.md index 2a4d4c2..56296ee 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,7 +25,7 @@ The application loads scripts in a specific order due to class dependencies: 1. **Utilities**: `vector.js`, `physics.js` (foundational math/physics) 2. **Entities**: `player.js`, `puck.js` (game objects) -3. **Systems**: `renderer.js`, `physics-system.js`, `ai-system.js`, `rules-system.js` +3. **Systems**: `renderer.js`, `physics-system.js`, `ai-system.js`, `rules-system.js`, `debug-system.js` 4. **Engine**: `game-state.js`, `game-engine.js`, `main.js` (orchestration) ### Key Classes and Responsibilities @@ -35,9 +35,10 @@ The application loads scripts in a specific order due to class dependencies: - **GameState** (`game-state.js`): Score tracking, time management, penalty system - **Player** (`player.js`): Hockey player with AI, physics, and role-based behavior - **Puck** (`puck.js`): Physics-based puck with collision detection -- **Renderer** (`renderer.js`): 2D canvas rendering with camera system +- **Renderer** (`renderer.js`): 2D canvas rendering with camera system and debug visualizations - **AISystem** (`ai-system.js`): Team formations and strategic AI behaviors - **RulesSystem** (`rules-system.js`): Hockey rule enforcement (offside, icing, penalties) +- **DebugSystem** (`debug-system.js`): Comprehensive debugging interface with real-time inspection ### Game Loop Architecture The engine runs at 60 FPS using `requestAnimationFrame` with these phases: @@ -55,10 +56,45 @@ The engine runs at 60 FPS using `requestAnimationFrame` with these phases: ## Controls Reference - `SPACE` - Pause/Resume game -- `D` - Toggle debug information +- `D` - Toggle debug information and visual overlays - `R` - Reset game - `F11` - Toggle fullscreen - `Mouse Wheel` - Camera zoom +- **Debug Mode Button** - Open/close comprehensive debug panel + +## Debug System Features + +### Enhanced Debug Panel +When debug mode is active, you can pause the game and inspect all variables: + +- **Real-time Debug Panel**: Right-side panel with live game state information +- **Player State Inspection**: Click any player to see detailed attributes, AI state, and physics data +- **Game State Monitoring**: Period, time, score, pause status, faceoff information +- **Puck Tracking**: Position, velocity, ownership, and physics data + +### Interactive Debug Features +- **Player Selection**: Click players in debug panel or directly on the canvas +- **Target Visualization**: Selected players show bright cyan lines to their target positions +- **Visual Feedback**: Hover effects, selection highlighting, and smooth animations + +### Console Debug Functions +Access via `debugHelpers` global object: +- `debugHelpers.getPlayers()` - Get all player objects +- `debugHelpers.getHomeTeam()` / `debugHelpers.getAwayTeam()` - Get team players +- `debugHelpers.getPlayer(id)` - Get specific player by ID +- `debugHelpers.getPuck()` - Get puck object with full state +- `debugHelpers.getGameState()` - Get complete game state +- `debugHelpers.getPuckCarrier()` - Get player currently carrying the puck +- `debugHelpers.exportGameState()` - Export full game state for analysis + +### Debug Visualizations +When visual debug mode is active (`D` key): +- **Velocity Vectors**: Red arrows showing player movement direction +- **Target Lines**: Green dashed lines to player destinations +- **AI Targets**: Yellow dotted lines to AI decision targets +- **Energy Bars**: Visual energy indicators above players +- **Puck Carrier Highlight**: Yellow dashed circle around puck carrier +- **Selected Player**: Cyan line to target with crosshair marker ## File Structure Notes - All code is vanilla JavaScript (ES6 classes) diff --git a/README.md b/README.md index 7a0718a..8612eec 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,8 @@ hockey-manager/ │ │ ├── renderer.js # 2D rendering system │ │ ├── physics-system.js # Physics calculations │ │ ├── ai-system.js # AI formation and strategy -│ │ └── rules-system.js # Hockey rules enforcement +│ │ ├── rules-system.js # Hockey rules enforcement +│ │ └── debug-system.js # Comprehensive debugging interface │ └── utils/ │ ├── vector.js # 2D vector mathematics │ └── physics.js # Physics utility functions @@ -63,8 +64,9 @@ hockey-manager/ 2. **Start playing**: The game will automatically initialize and start 3. **Use controls**: - **Space**: Pause/Resume - - **D**: Toggle debug mode + - **D**: Toggle debug mode with visual overlays - **R**: Reset game + - **Debug Mode Button**: Open comprehensive debug panel - **Mouse wheel**: Zoom in/out - **F11**: Toggle fullscreen @@ -84,6 +86,43 @@ hockey-manager/ - **Play/Pause** - Start/stop game simulation - **Speed Control** - Adjust game speed (0.5x, 1x, 2x, 4x) - **Reset Game** - Return to initial game state +- **Debug Mode** - Open comprehensive debug panel for game inspection + +## Debug System + +The hockey engine includes a comprehensive debug system for inspecting game state and player behavior: + +### Debug Panel Features +- **Real-time Game State**: Live monitoring of period, time, score, and game status +- **Player State Inspection**: Click any player to view detailed attributes, AI decisions, and physics data +- **Puck Tracking**: Complete puck state including position, velocity, and ownership +- **Interactive Selection**: Click players in the panel or directly on the canvas for detailed inspection + +### Visual Debug Overlays +When debug mode is active (`D` key): +- **Velocity Vectors**: Red arrows showing player movement direction and speed +- **Target Lines**: Green dashed lines indicating where players are trying to move +- **AI Targets**: Yellow dotted lines showing AI decision targets +- **Energy Bars**: Visual indicators of player energy levels +- **Selected Player Visualization**: Bright cyan lines showing target destination with crosshair markers + +### Console Debug Functions +Access programmatic debugging via the global `debugHelpers` object: +```javascript +debugHelpers.getPlayers() // All player objects +debugHelpers.getHomeTeam() // Home team players only +debugHelpers.getAwayTeam() // Away team players only +debugHelpers.getPlayer(id) // Specific player by ID +debugHelpers.getPuck() // Puck object with full state +debugHelpers.getPuckCarrier() // Player currently with puck +debugHelpers.exportGameState() // Complete game state export +``` + +This debug system is essential for: +- **AI Development**: Understanding player decision-making and behavior patterns +- **Performance Analysis**: Monitoring physics calculations and render performance +- **Game Balance**: Analyzing player attributes and gameplay mechanics +- **Bug Investigation**: Detailed state inspection for troubleshooting issues ## Game Mechanics diff --git a/css/styles.css b/css/styles.css index 247b78a..5436f13 100644 --- a/css/styles.css +++ b/css/styles.css @@ -129,4 +129,170 @@ button:active { border-radius: 3px; margin: 2px; font-size: 12px; +} + +/* Debug Panel Styles */ +.debug-panel { + position: absolute; + top: 20px; + right: 20px; + width: 400px; + max-height: 80vh; + background: rgba(0, 0, 0, 0.95); + border: 2px solid #4a90e2; + border-radius: 10px; + overflow: hidden; + z-index: 20; + pointer-events: auto; + font-size: 12px; +} + +.debug-panel.hidden { + display: none; +} + +.debug-header { + background: #4a90e2; + padding: 10px 15px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #333; +} + +.debug-header h3 { + margin: 0; + font-size: 16px; + color: white; +} + +#debug-close { + background: transparent; + border: none; + color: white; + font-size: 20px; + padding: 0; + cursor: pointer; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; +} + +#debug-close:hover { + background: rgba(255, 255, 255, 0.2); + border-radius: 50%; +} + +.debug-content { + max-height: calc(80vh - 60px); + overflow-y: auto; + padding: 15px; +} + +.debug-section { + margin-bottom: 20px; + border-bottom: 1px solid #333; + padding-bottom: 15px; +} + +.debug-section:last-child { + border-bottom: none; + margin-bottom: 0; +} + +.debug-section h4 { + margin: 0 0 10px 0; + color: #4a90e2; + font-size: 14px; +} + +.debug-section h5 { + margin: 10px 0 5px 0; + color: #ccc; + font-size: 13px; +} + +.debug-team { + margin-bottom: 15px; +} + +.debug-player { + background: rgba(255, 255, 255, 0.05); + padding: 15px; + margin: 10px 0; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s ease; + user-select: none; + border: 2px solid transparent; + position: relative; + min-height: 80px; + display: flex; + flex-direction: column; + justify-content: center; +} + +.debug-player:hover { + background: rgba(255, 255, 255, 0.15); + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); +} + +.debug-player:active { + transform: scale(0.98) translateY(0px); +} + +.debug-player.selected { + background: rgba(74, 144, 226, 0.4); + border: 2px solid #4a90e2; + box-shadow: 0 4px 12px rgba(74, 144, 226, 0.3); +} + +.debug-player.selected:hover { + background: rgba(74, 144, 226, 0.5); +} + +.debug-player-header { + font-weight: bold; + color: #fff; + margin-bottom: 5px; +} + +.debug-player-info { + color: #ccc; + line-height: 1.4; +} + +.debug-value { + color: #4a90e2; + font-weight: bold; +} + +.debug-coords { + font-family: 'Courier New', monospace; + font-size: 11px; +} + +#debug-selected-player { + background: rgba(255, 255, 255, 0.05); + padding: 15px; + border-radius: 5px; + min-height: 100px; +} + +.debug-attribute { + display: flex; + justify-content: space-between; + margin: 3px 0; +} + +.debug-attribute-name { + color: #ccc; +} + +.debug-attribute-value { + color: #4a90e2; + font-weight: bold; } \ No newline at end of file diff --git a/index.html b/index.html index 46f49dd..44d262d 100644 --- a/index.html +++ b/index.html @@ -32,6 +32,39 @@ + + + + @@ -43,6 +76,7 @@ + diff --git a/src/engine/game-engine.js b/src/engine/game-engine.js index 63a9b97..596895d 100644 --- a/src/engine/game-engine.js +++ b/src/engine/game-engine.js @@ -19,6 +19,10 @@ class GameEngine { this.setupPlayers(); this.setupEventListeners(); this.setupControls(); + + // Initialize debug system after all other systems are set up + window.debugMode = false; // Initialize debug mode + this.debugSystem = new DebugSystem(this); } setupPlayers() { @@ -231,7 +235,9 @@ class GameEngine { this.renderEffects(); this.renderer.drawUI(this.gameState); - this.renderer.drawDebugInfo(this.gameState, this.players, this.puck); + + const selectedPlayer = this.debugSystem ? this.debugSystem.selectedPlayer : null; + this.renderer.drawDebugInfo(this.gameState, this.players, this.puck, selectedPlayer); } renderEffects() { diff --git a/src/systems/debug-system.js b/src/systems/debug-system.js new file mode 100644 index 0000000..d494d65 --- /dev/null +++ b/src/systems/debug-system.js @@ -0,0 +1,464 @@ +class DebugSystem { + constructor(gameEngine) { + this.gameEngine = gameEngine; + this.isVisible = false; + this.selectedPlayer = null; + this.updateInterval = null; + + this.setupEventListeners(); + this.setupGlobalDebugFunctions(); + } + + setupEventListeners() { + // Debug toggle button + document.getElementById('debug-toggle').addEventListener('click', () => { + this.toggleDebugPanel(); + }); + + // Close debug panel + document.getElementById('debug-close').addEventListener('click', () => { + this.hideDebugPanel(); + }); + + // Canvas click for player selection + this.gameEngine.canvas.addEventListener('click', (e) => { + if (this.isVisible) { + this.handleCanvasClick(e); + } + }); + + // Monitor debug mode changes (triggered by existing D key handler) + let lastDebugMode = window.debugMode; + setInterval(() => { + if (window.debugMode !== lastDebugMode) { + if (window.debugMode && !this.isVisible) { + this.showDebugPanel(); + } else if (!window.debugMode && this.isVisible) { + this.hideDebugPanel(); + } + lastDebugMode = window.debugMode; + } + }, 100); + } + + toggleDebugPanel() { + if (this.isVisible) { + this.hideDebugPanel(); + } else { + this.showDebugPanel(); + } + } + + showDebugPanel() { + this.isVisible = true; + document.getElementById('debug-panel').classList.remove('hidden'); + + // Start real-time updates + this.updateInterval = setInterval(() => { + this.updateDebugInfo(); + }, 100); // Update 10 times per second + + // Initial update + this.updateDebugInfo(); + } + + hideDebugPanel() { + this.isVisible = false; + document.getElementById('debug-panel').classList.add('hidden'); + + // Stop real-time updates + if (this.updateInterval) { + clearInterval(this.updateInterval); + this.updateInterval = null; + } + } + + handleCanvasClick(e) { + const rect = this.gameEngine.canvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + // Find closest player to click position + let closestPlayer = null; + let closestDistance = Infinity; + + this.gameEngine.players.forEach(player => { + const distance = Math.sqrt( + Math.pow(player.position.x - x, 2) + + Math.pow(player.position.y - y, 2) + ); + + if (distance < player.radius + 10 && distance < closestDistance) { + closestPlayer = player; + closestDistance = distance; + } + }); + + if (closestPlayer) { + this.selectPlayer(closestPlayer); + } + } + + selectPlayer(player) { + // Remove previous selection styling + document.querySelectorAll('.debug-player').forEach(el => { + el.classList.remove('selected'); + }); + + this.selectedPlayer = player; + + // Add selection styling to the clicked player + const playerElement = document.querySelector(`[data-player-id="${player.id}"]`); + if (playerElement) { + playerElement.classList.add('selected'); + } + + this.updateSelectedPlayerInfo(); + } + + updateDebugInfo() { + if (!this.isVisible) return; + + this.updateGameStateInfo(); + this.updatePuckInfo(); + this.updatePlayersInfo(); + if (this.selectedPlayer) { + this.updateSelectedPlayerInfo(); + } + } + + updateGameStateInfo() { + const gameState = this.gameEngine.gameState; + const info = ` +
+ Period: + ${gameState.getPeriodName()} +
+
+ Time: + ${gameState.formatTime(gameState.timeRemaining)} +
+
+ Score: + ${gameState.homeScore} - ${gameState.awayScore} +
+
+ Paused: + ${gameState.isPaused} +
+
+ Speed: + ${gameState.gameSpeed}x +
+
+ Faceoff Active: + ${gameState.faceoff.isActive} +
+ `; + document.getElementById('debug-game-state').innerHTML = info; + } + + updatePuckInfo() { + const puck = this.gameEngine.puck; + const info = ` +
+ Position: + (${puck.position.x.toFixed(1)}, ${puck.position.y.toFixed(1)}) +
+
+ Velocity: + (${puck.velocity.x.toFixed(1)}, ${puck.velocity.y.toFixed(1)}) +
+
+ Speed: + ${puck.getSpeed().toFixed(1)} +
+
+ Active: + ${this.gameEngine.puckActive} +
+
+ Owner: + ${puck.lastTouchedBy || 'None'} +
+ `; + document.getElementById('debug-puck').innerHTML = info; + } + + updatePlayersInfo() { + const homePlayers = this.gameEngine.players.filter(p => p.team === 'home'); + const awayPlayers = this.gameEngine.players.filter(p => p.team === 'away'); + + this.renderPlayersList('debug-home-players', homePlayers); + this.renderPlayersList('debug-away-players', awayPlayers); + } + + renderPlayersList(containerId, players) { + const container = document.getElementById(containerId); + + // Only create elements if they don't exist yet (prevent flickering) + if (container.children.length === 0) { + this.createPlayerElements(container, players); + } + + // Update existing elements instead of recreating them + players.forEach((player, index) => { + const playerDiv = container.children[index]; + if (!playerDiv) return; + + // Update selection state + if (this.selectedPlayer && this.selectedPlayer.id === player.id) { + playerDiv.classList.add('selected'); + } else { + playerDiv.classList.remove('selected'); + } + + // Update dynamic content only + const coordsElement = playerDiv.querySelector('.debug-coords'); + const speedElement = playerDiv.querySelector('.debug-speed'); + const energyElement = playerDiv.querySelector('.debug-energy'); + const puckElement = playerDiv.querySelector('.debug-puck'); + const behaviorElement = playerDiv.querySelector('.debug-behavior'); + + if (coordsElement) coordsElement.textContent = `Pos: (${player.position.x.toFixed(0)}, ${player.position.y.toFixed(0)})`; + if (speedElement) speedElement.textContent = player.velocity.magnitude().toFixed(1); + if (energyElement) energyElement.textContent = `${player.state.energy.toFixed(0)}%`; + if (puckElement) puckElement.textContent = player.state.hasPuck; + if (behaviorElement) behaviorElement.textContent = player.aiState.behavior; + }); + } + + createPlayerElements(container, players) { + players.forEach(player => { + const playerDiv = document.createElement('div'); + playerDiv.className = 'debug-player'; + playerDiv.setAttribute('data-player-id', player.id); + + playerDiv.innerHTML = ` +
${player.role} - ${player.name}
+
+
Pos: (${player.position.x.toFixed(0)}, ${player.position.y.toFixed(0)})
+
Speed: ${player.velocity.magnitude().toFixed(1)}
+
Energy: ${player.state.energy.toFixed(0)}%
+
Has Puck: ${player.state.hasPuck}
+
Behavior: ${player.aiState.behavior}
+
+ `; + + // Make the entire div clickable with better event handling + playerDiv.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + this.selectPlayer(player); + }); + + // Add mouse events for better feedback + playerDiv.addEventListener('mouseenter', (e) => { + if (!playerDiv.classList.contains('selected')) { + playerDiv.style.background = 'rgba(255, 255, 255, 0.15)'; + } + }); + + playerDiv.addEventListener('mouseleave', (e) => { + if (!playerDiv.classList.contains('selected')) { + playerDiv.style.background = ''; + } + }); + + playerDiv.addEventListener('mousedown', (e) => { + e.preventDefault(); + playerDiv.style.transform = 'scale(0.98)'; + }); + + playerDiv.addEventListener('mouseup', (e) => { + e.preventDefault(); + playerDiv.style.transform = ''; + }); + + container.appendChild(playerDiv); + }); + } + + updateSelectedPlayerInfo() { + if (!this.selectedPlayer) return; + + const player = this.selectedPlayer; + const info = ` +
+

${player.name} (${player.role})

+
Team: ${player.team.toUpperCase()}
+
+ +
+
Position & Physics
+
+ Position: + (${player.position.x.toFixed(1)}, ${player.position.y.toFixed(1)}) +
+
+ Velocity: + (${player.velocity.x.toFixed(1)}, ${player.velocity.y.toFixed(1)}) +
+
+ Speed: + ${player.velocity.magnitude().toFixed(1)} +
+
+ Target: + (${player.targetPosition.x.toFixed(1)}, ${player.targetPosition.y.toFixed(1)}) +
+
+ +
+
Game State
+
+ Has Puck: + ${player.state.hasPuck} +
+
+ Energy: + ${player.state.energy.toFixed(1)}% +
+
+ Checking: + ${player.state.checking} +
+
+ Injured: + ${player.state.injured} +
+
+ +
+
AI State
+
+ Behavior: + ${player.aiState.behavior} +
+
+ Target: + ${player.aiState.target ? player.aiState.target.constructor.name : 'None'} +
+
+ Last Action: + ${player.aiState.lastAction.toFixed(0)}ms ago +
+
+ Reaction Time: + ${player.aiState.reactionTime.toFixed(0)}ms +
+
+ +
+
Attributes
+
+ Speed: + ${player.attributes.speed.toFixed(0)} +
+
+ Shooting: + ${player.attributes.shooting.toFixed(0)} +
+
+ Passing: + ${player.attributes.passing.toFixed(0)} +
+
+ Defense: + ${player.attributes.defense.toFixed(0)} +
+
+ Checking: + ${player.attributes.checking.toFixed(0)} +
+
+ Puck Handling: + ${player.attributes.puckHandling.toFixed(0)} +
+
+ Awareness: + ${player.attributes.awareness.toFixed(0)} +
+
+ `; + + document.getElementById('debug-selected-player').innerHTML = info; + } + + setupGlobalDebugFunctions() { + // Add global debug helper functions to window object + window.debugHelpers = { + // Get all players + getPlayers: () => this.gameEngine.players, + + // Get players by team + getHomeTeam: () => this.gameEngine.players.filter(p => p.team === 'home'), + getAwayTeam: () => this.gameEngine.players.filter(p => p.team === 'away'), + + // Get players by position + getPlayersByPosition: (position) => this.gameEngine.players.filter(p => p.role === position), + getGoalies: () => this.gameEngine.players.filter(p => p.role === 'G'), + getDefense: () => this.gameEngine.players.filter(p => p.role === 'LD' || p.role === 'RD'), + getForwards: () => this.gameEngine.players.filter(p => ['LW', 'C', 'RW'].includes(p.role)), + + // Get specific player + getPlayer: (id) => this.gameEngine.players.find(p => p.id === id), + + // Get puck + getPuck: () => this.gameEngine.puck, + + // Get game state + getGameState: () => this.gameEngine.gameState, + + // Get player with puck + getPuckCarrier: () => this.gameEngine.players.find(p => p.state.hasPuck), + + // Export game state for debugging + exportGameState: () => { + const state = { + gameState: this.gameEngine.gameState.getGameState(), + players: this.gameEngine.players.map(p => ({ + id: p.id, + name: p.name, + team: p.team, + role: p.role, + position: { x: p.position.x, y: p.position.y }, + velocity: { x: p.velocity.x, y: p.velocity.y }, + targetPosition: { x: p.targetPosition.x, y: p.targetPosition.y }, + state: { ...p.state }, + aiState: { ...p.aiState }, + attributes: { ...p.attributes } + })), + puck: { + position: { x: this.gameEngine.puck.position.x, y: this.gameEngine.puck.position.y }, + velocity: { x: this.gameEngine.puck.velocity.x, y: this.gameEngine.puck.velocity.y }, + speed: this.gameEngine.puck.getSpeed(), + lastTouchedBy: this.gameEngine.puck.lastTouchedBy + } + }; + console.log('Game State Export:', state); + return state; + }, + + // Show debug panel + showDebug: () => this.showDebugPanel(), + + // Hide debug panel + hideDebug: () => this.hideDebugPanel() + }; + + // Log available debug functions + console.log('Debug Helper Functions Available:'); + console.log('- debugHelpers.getPlayers() - Get all players'); + console.log('- debugHelpers.getHomeTeam() - Get home team players'); + console.log('- debugHelpers.getAwayTeam() - Get away team players'); + console.log('- debugHelpers.getPlayersByPosition(pos) - Get players by position'); + console.log('- debugHelpers.getPlayer(id) - Get specific player by ID'); + console.log('- debugHelpers.getPuck() - Get puck object'); + console.log('- debugHelpers.getGameState() - Get game state'); + console.log('- debugHelpers.getPuckCarrier() - Get player with puck'); + console.log('- debugHelpers.exportGameState() - Export full game state'); + console.log('- debugHelpers.showDebug() - Show debug panel'); + console.log('- debugHelpers.hideDebug() - Hide debug panel'); + } +} \ No newline at end of file diff --git a/src/systems/renderer.js b/src/systems/renderer.js index 96d1a65..9f99bac 100644 --- a/src/systems/renderer.js +++ b/src/systems/renderer.js @@ -283,12 +283,22 @@ class Renderer { ); } - drawDebugInfo(gameState, players, puck) { + drawDebugInfo(gameState, players, puck, selectedPlayer = null) { + // Always draw selected player target line if there is one, even when debug mode is off + if (selectedPlayer) { + this.ctx.save(); + this.applyCamera(); + this.drawSelectedPlayerTarget(selectedPlayer); + this.ctx.restore(); + } + + // Only draw the rest of debug info if debug mode is on if (!window.debugMode) return; + // Draw basic debug info overlay this.ctx.save(); this.ctx.fillStyle = 'rgba(0, 0, 0, 0.8)'; - this.ctx.fillRect(10, 10, 200, 150); + this.ctx.fillRect(10, 10, 250, 180); this.ctx.fillStyle = '#fff'; this.ctx.font = '12px monospace'; @@ -298,8 +308,219 @@ class Renderer { 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.fillText(`Faceoff: ${gameState.faceoff.isActive}`, 20, 150); + this.ctx.fillText(`Puck Active: ${gameState.puckActive !== undefined ? gameState.puckActive : 'N/A'}`, 20, 170); + + this.ctx.restore(); + + // Draw enhanced debug visualizations on the rink + this.ctx.save(); + this.applyCamera(); + + this.drawDebugVectors(players, puck); + this.drawDebugPlayerInfo(players); + this.drawDebugPuckInfo(puck); this.ctx.restore(); } + + drawDebugVectors(players, puck) { + // Draw velocity vectors for players + players.forEach(player => { + if (player.velocity.magnitude() > 5) { + this.drawVector(player.position, player.velocity, '#ff4444', 0.5); + } + + // Draw target position + if (player.targetPosition) { + this.drawLine(player.position, player.targetPosition, '#44ff44', 1, [5, 5]); + } + + // Draw AI target if exists + if (player.aiState.target && player.aiState.target.position) { + this.drawLine(player.position, player.aiState.target.position, '#ffff44', 1, [2, 2]); + } + }); + + // Draw puck velocity vector + if (puck.velocity.magnitude() > 10) { + this.drawVector(puck.position, puck.velocity, '#4444ff', 0.3); + } + } + + drawDebugPlayerInfo(players) { + this.ctx.font = '10px Arial'; + + players.forEach(player => { + const x = player.position.x; + const y = player.position.y - player.radius - 5; + + // Draw player ID and energy + this.ctx.fillStyle = player.team === 'home' ? '#ff4444' : '#4444ff'; + this.ctx.fillText(`${player.role}`, x - 10, y); + + // Draw energy bar + const barWidth = 20; + const barHeight = 3; + const energyPercent = player.state.energy / 100; + + this.ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'; + this.ctx.fillRect(x - barWidth/2, y - 15, barWidth, barHeight); + + this.ctx.fillStyle = energyPercent > 0.5 ? '#44ff44' : energyPercent > 0.25 ? '#ffff44' : '#ff4444'; + this.ctx.fillRect(x - barWidth/2, y - 15, barWidth * energyPercent, barHeight); + + // Highlight puck carrier + if (player.state.hasPuck) { + this.ctx.strokeStyle = '#ffff00'; + this.ctx.lineWidth = 3; + this.ctx.setLineDash([3, 3]); + this.ctx.beginPath(); + this.ctx.arc(x, player.position.y, player.radius + 5, 0, Math.PI * 2); + this.ctx.stroke(); + this.ctx.setLineDash([]); + } + }); + } + + drawDebugPuckInfo(puck) { + const x = puck.position.x; + const y = puck.position.y - 20; + + this.ctx.font = '8px Arial'; + this.ctx.fillStyle = '#ffffff'; + this.ctx.fillText(`Speed: ${Math.round(puck.velocity.magnitude())}`, x - 20, y); + + if (puck.lastTouchedBy) { + this.ctx.fillText(`Last: ${puck.lastTouchedBy}`, x - 20, y + 10); + } + } + + drawVector(position, vector, color = '#ffffff', scale = 1) { + const endX = position.x + vector.x * scale; + const endY = position.y + vector.y * scale; + + this.ctx.strokeStyle = color; + this.ctx.lineWidth = 2; + this.ctx.setLineDash([]); + + // Draw vector line + this.ctx.beginPath(); + this.ctx.moveTo(position.x, position.y); + this.ctx.lineTo(endX, endY); + this.ctx.stroke(); + + // Draw arrow head + const angle = Math.atan2(vector.y, vector.x); + const arrowSize = 8; + + this.ctx.beginPath(); + this.ctx.moveTo(endX, endY); + this.ctx.lineTo( + endX - arrowSize * Math.cos(angle - Math.PI / 6), + endY - arrowSize * Math.sin(angle - Math.PI / 6) + ); + this.ctx.moveTo(endX, endY); + this.ctx.lineTo( + endX - arrowSize * Math.cos(angle + Math.PI / 6), + endY - arrowSize * Math.sin(angle + Math.PI / 6) + ); + this.ctx.stroke(); + } + + drawLine(start, end, color = '#ffffff', width = 1, dash = []) { + this.ctx.strokeStyle = color; + this.ctx.lineWidth = width; + this.ctx.setLineDash(dash); + + this.ctx.beginPath(); + this.ctx.moveTo(start.x, start.y); + this.ctx.lineTo(end.x, end.y); + this.ctx.stroke(); + + this.ctx.setLineDash([]); + } + + drawSelectedPlayerTarget(selectedPlayer) { + if (!selectedPlayer) { + return; + } + + if (!selectedPlayer.targetPosition) { + console.log('Selected player has no target position:', selectedPlayer.name); + return; + } + + // Check if target is different from current position + const distance = Math.sqrt( + Math.pow(selectedPlayer.targetPosition.x - selectedPlayer.position.x, 2) + + Math.pow(selectedPlayer.targetPosition.y - selectedPlayer.position.y, 2) + ); + + if (distance < 5) { + // Target too close to player, not worth drawing line + return; + } + + // Save current context state + this.ctx.save(); + + // Draw a bright, prominent line from selected player to their target + this.ctx.strokeStyle = '#00ffff'; // Cyan color for high visibility + this.ctx.lineWidth = 6; + this.ctx.setLineDash([12, 6]); // Larger dashed line pattern + this.ctx.lineCap = 'round'; + + this.ctx.beginPath(); + this.ctx.moveTo(selectedPlayer.position.x, selectedPlayer.position.y); + this.ctx.lineTo(selectedPlayer.targetPosition.x, selectedPlayer.targetPosition.y); + this.ctx.stroke(); + + // Also draw a solid white line underneath for extra visibility + this.ctx.strokeStyle = '#ffffff'; + this.ctx.lineWidth = 2; + this.ctx.setLineDash([]); + + this.ctx.beginPath(); + this.ctx.moveTo(selectedPlayer.position.x, selectedPlayer.position.y); + this.ctx.lineTo(selectedPlayer.targetPosition.x, selectedPlayer.targetPosition.y); + this.ctx.stroke(); + + // Draw target position marker (circle) + this.ctx.setLineDash([]); + this.ctx.fillStyle = '#00ffff'; + this.ctx.strokeStyle = '#ffffff'; + this.ctx.lineWidth = 3; + + this.ctx.beginPath(); + this.ctx.arc(selectedPlayer.targetPosition.x, selectedPlayer.targetPosition.y, 10, 0, Math.PI * 2); + this.ctx.fill(); + this.ctx.stroke(); + + // Draw crosshair in the target circle + this.ctx.strokeStyle = '#ffffff'; + this.ctx.lineWidth = 2; + + const targetX = selectedPlayer.targetPosition.x; + const targetY = selectedPlayer.targetPosition.y; + + this.ctx.beginPath(); + this.ctx.moveTo(targetX - 6, targetY); + this.ctx.lineTo(targetX + 6, targetY); + this.ctx.moveTo(targetX, targetY - 6); + this.ctx.lineTo(targetX, targetY + 6); + this.ctx.stroke(); + + // Highlight the selected player with a special border + this.ctx.strokeStyle = '#00ffff'; + this.ctx.lineWidth = 4; + this.ctx.setLineDash([8, 4]); + + this.ctx.beginPath(); + this.ctx.arc(selectedPlayer.position.x, selectedPlayer.position.y, selectedPlayer.radius + 10, 0, Math.PI * 2); + this.ctx.stroke(); + + // Restore context state + this.ctx.restore(); + } } \ No newline at end of file