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 @@
+
+
+
+
+
+
+
+
+
+
+
Selected Player
+
Click a player to see detailed info
+
+
@@ -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 = `
+
+
+
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