Compare commits
No commits in common. "cad05ad8950e1bc829570bbf7bc9b305dd0759cf" and "961dcc3a252ebe807df13c9642ac7cf2dd461aa9" have entirely different histories.
cad05ad895
...
961dcc3a25
42
CLAUDE.md
42
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)
|
1. **Utilities**: `vector.js`, `physics.js` (foundational math/physics)
|
||||||
2. **Entities**: `player.js`, `puck.js` (game objects)
|
2. **Entities**: `player.js`, `puck.js` (game objects)
|
||||||
3. **Systems**: `renderer.js`, `physics-system.js`, `ai-system.js`, `rules-system.js`, `debug-system.js`
|
3. **Systems**: `renderer.js`, `physics-system.js`, `ai-system.js`, `rules-system.js`
|
||||||
4. **Engine**: `game-state.js`, `game-engine.js`, `main.js` (orchestration)
|
4. **Engine**: `game-state.js`, `game-engine.js`, `main.js` (orchestration)
|
||||||
|
|
||||||
### Key Classes and Responsibilities
|
### Key Classes and Responsibilities
|
||||||
@ -35,10 +35,9 @@ The application loads scripts in a specific order due to class dependencies:
|
|||||||
- **GameState** (`game-state.js`): Score tracking, time management, penalty system
|
- **GameState** (`game-state.js`): Score tracking, time management, penalty system
|
||||||
- **Player** (`player.js`): Hockey player with AI, physics, and role-based behavior
|
- **Player** (`player.js`): Hockey player with AI, physics, and role-based behavior
|
||||||
- **Puck** (`puck.js`): Physics-based puck with collision detection
|
- **Puck** (`puck.js`): Physics-based puck with collision detection
|
||||||
- **Renderer** (`renderer.js`): 2D canvas rendering with camera system and debug visualizations
|
- **Renderer** (`renderer.js`): 2D canvas rendering with camera system
|
||||||
- **AISystem** (`ai-system.js`): Team formations and strategic AI behaviors
|
- **AISystem** (`ai-system.js`): Team formations and strategic AI behaviors
|
||||||
- **RulesSystem** (`rules-system.js`): Hockey rule enforcement (offside, icing, penalties)
|
- **RulesSystem** (`rules-system.js`): Hockey rule enforcement (offside, icing, penalties)
|
||||||
- **DebugSystem** (`debug-system.js`): Comprehensive debugging interface with real-time inspection
|
|
||||||
|
|
||||||
### Game Loop Architecture
|
### Game Loop Architecture
|
||||||
The engine runs at 60 FPS using `requestAnimationFrame` with these phases:
|
The engine runs at 60 FPS using `requestAnimationFrame` with these phases:
|
||||||
@ -56,45 +55,10 @@ The engine runs at 60 FPS using `requestAnimationFrame` with these phases:
|
|||||||
|
|
||||||
## Controls Reference
|
## Controls Reference
|
||||||
- `SPACE` - Pause/Resume game
|
- `SPACE` - Pause/Resume game
|
||||||
- `D` - Toggle debug information and visual overlays
|
- `D` - Toggle debug information
|
||||||
- `R` - Reset game
|
- `R` - Reset game
|
||||||
- `F11` - Toggle fullscreen
|
- `F11` - Toggle fullscreen
|
||||||
- `Mouse Wheel` - Camera zoom
|
- `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
|
## File Structure Notes
|
||||||
- All code is vanilla JavaScript (ES6 classes)
|
- All code is vanilla JavaScript (ES6 classes)
|
||||||
|
|||||||
103
PLAYER.md
103
PLAYER.md
@ -1,103 +0,0 @@
|
|||||||
# Player Logic Documentation
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
The `Player` class represents hockey players with AI-driven behavior, physics simulation, and role-based positioning. Each player has attributes, states, and intelligent decision-making capabilities.
|
|
||||||
|
|
||||||
## Player Structure
|
|
||||||
|
|
||||||
### Core Properties
|
|
||||||
- **Identity**: `id`, `name`, `team` ('home' or 'away'), `role` (C, LW, RW, LD, RD, G)
|
|
||||||
- **Physics**: `position`, `velocity`, `targetPosition`, `mass`, `radius`, `maxSpeed`
|
|
||||||
- **Attributes**: Speed, shooting, passing, defense, checking, puck handling, awareness (70-90 range)
|
|
||||||
- **State**: Energy, puck possession, checking status, injury status
|
|
||||||
- **AI State**: Current target, behavior mode, reaction timing
|
|
||||||
|
|
||||||
### Player Roles
|
|
||||||
- **C (Center)**: Primary playmaker, takes faceoffs
|
|
||||||
- **LW/RW (Wings)**: Offensive forwards, positioned on left/right sides
|
|
||||||
- **LD/RD (Defense)**: Defensive players, protect own zone
|
|
||||||
- **G (Goalie)**: Larger, slower, stays in crease area
|
|
||||||
|
|
||||||
## AI Behavior System
|
|
||||||
|
|
||||||
### Main Update Loop
|
|
||||||
1. **Energy Management**: Drains based on movement speed, regenerates when stationary
|
|
||||||
2. **Movement Physics**: Accelerates toward target position with friction
|
|
||||||
3. **Angle Updates**: Smoothly rotates toward target angle
|
|
||||||
4. **Behavior Selection**: Chooses actions based on game situation
|
|
||||||
|
|
||||||
### Faceoff Handling
|
|
||||||
When `gameState.faceoff.isActive` is true:
|
|
||||||
- Players move to specific faceoff positions based on their role
|
|
||||||
- Centers line up at the dot, wings/defense stay outside the circle
|
|
||||||
- Goalies remain in their nets
|
|
||||||
|
|
||||||
### Behavior States
|
|
||||||
|
|
||||||
#### With Puck
|
|
||||||
- **Shooting**: Attempts shots when in good scoring position (<250 units from goal)
|
|
||||||
- **Passing**: Finds open teammates when under pressure (<60 units from opponent)
|
|
||||||
- **Advancing**: Moves toward enemy goal while avoiding opponents
|
|
||||||
|
|
||||||
#### Without Puck
|
|
||||||
- **Chasing**: Closest non-goalie teammate pursues loose puck
|
|
||||||
- **Checking**: Applies body checks to opponents with puck
|
|
||||||
- **Defending**: Positions between puck carrier and own goal
|
|
||||||
- **Formation**: Moves to tactical position based on game situation
|
|
||||||
|
|
||||||
### Formation System
|
|
||||||
Players dynamically switch between attacking and defensive formations:
|
|
||||||
|
|
||||||
**Attacking Formation** (when team has puck or puck in offensive zone):
|
|
||||||
- Forwards push toward opponent's goal
|
|
||||||
- Defense provides support from behind
|
|
||||||
- Creates offensive pressure
|
|
||||||
|
|
||||||
**Defensive Formation** (when opponent has puck):
|
|
||||||
- Players fall back toward own goal
|
|
||||||
- Tight defensive positioning
|
|
||||||
- Focus on puck recovery
|
|
||||||
|
|
||||||
## Key AI Methods
|
|
||||||
|
|
||||||
### Decision Making
|
|
||||||
- `updateAI()`: Main AI decision loop with reaction timing
|
|
||||||
- `behaviorWithPuck()`: Offensive actions (shoot, pass, advance)
|
|
||||||
- `behaviorWithoutPuck()`: Defensive/support actions
|
|
||||||
- `determineTeamState()`: Analyzes if team is attacking or defending
|
|
||||||
|
|
||||||
### Movement & Positioning
|
|
||||||
- `getFormationPosition()`: Calculates tactical position based on game state
|
|
||||||
- `moveToPosition()`: Sets movement target and facing direction
|
|
||||||
- `findBestPathToGoal()`: Intelligent pathfinding around opponents
|
|
||||||
|
|
||||||
### Interactions
|
|
||||||
- `shoot()`: Fires puck toward target with accuracy based on attributes
|
|
||||||
- `pass()`: Passes to teammate with appropriate power
|
|
||||||
- `checkPlayer()`: Physical body check that affects opponent's velocity
|
|
||||||
|
|
||||||
### Utility Functions
|
|
||||||
- `isClosestPlayerToPuck()`: Determines if this player should chase loose puck
|
|
||||||
- `hasGoodShootingAngle()`: Checks for clear shot opportunity
|
|
||||||
- `findBestPassTarget()`: Locates optimal passing target
|
|
||||||
|
|
||||||
## Goalie Behavior
|
|
||||||
Goalies have specialized behavior:
|
|
||||||
- Stay within crease boundaries
|
|
||||||
- Position based on puck location
|
|
||||||
- Move to intercept shots
|
|
||||||
- Don't participate in offensive plays
|
|
||||||
|
|
||||||
## Physics Integration
|
|
||||||
- Collision detection with radius-based boundaries
|
|
||||||
- Realistic acceleration and deceleration
|
|
||||||
- Energy affects maximum speed (tired players move slower)
|
|
||||||
- Friction applied for realistic movement
|
|
||||||
|
|
||||||
## Rendering
|
|
||||||
- Team-colored circles with role labels
|
|
||||||
- Yellow indicator when player has puck
|
|
||||||
- Rotation shows facing direction
|
|
||||||
- Debug information available
|
|
||||||
|
|
||||||
The player system creates realistic hockey gameplay through intelligent AI, formation tactics, and physics-based movement.
|
|
||||||
43
README.md
43
README.md
@ -51,8 +51,7 @@ hockey-manager/
|
|||||||
│ │ ├── renderer.js # 2D rendering system
|
│ │ ├── renderer.js # 2D rendering system
|
||||||
│ │ ├── physics-system.js # Physics calculations
|
│ │ ├── physics-system.js # Physics calculations
|
||||||
│ │ ├── ai-system.js # AI formation and strategy
|
│ │ ├── 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/
|
│ └── utils/
|
||||||
│ ├── vector.js # 2D vector mathematics
|
│ ├── vector.js # 2D vector mathematics
|
||||||
│ └── physics.js # Physics utility functions
|
│ └── physics.js # Physics utility functions
|
||||||
@ -64,9 +63,8 @@ hockey-manager/
|
|||||||
2. **Start playing**: The game will automatically initialize and start
|
2. **Start playing**: The game will automatically initialize and start
|
||||||
3. **Use controls**:
|
3. **Use controls**:
|
||||||
- **Space**: Pause/Resume
|
- **Space**: Pause/Resume
|
||||||
- **D**: Toggle debug mode with visual overlays
|
- **D**: Toggle debug mode
|
||||||
- **R**: Reset game
|
- **R**: Reset game
|
||||||
- **Debug Mode Button**: Open comprehensive debug panel
|
|
||||||
- **Mouse wheel**: Zoom in/out
|
- **Mouse wheel**: Zoom in/out
|
||||||
- **F11**: Toggle fullscreen
|
- **F11**: Toggle fullscreen
|
||||||
|
|
||||||
@ -86,43 +84,6 @@ hockey-manager/
|
|||||||
- **Play/Pause** - Start/stop game simulation
|
- **Play/Pause** - Start/stop game simulation
|
||||||
- **Speed Control** - Adjust game speed (0.5x, 1x, 2x, 4x)
|
- **Speed Control** - Adjust game speed (0.5x, 1x, 2x, 4x)
|
||||||
- **Reset Game** - Return to initial game state
|
- **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
|
## Game Mechanics
|
||||||
|
|
||||||
|
|||||||
166
css/styles.css
166
css/styles.css
@ -129,170 +129,4 @@ button:active {
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
font-size: 12px;
|
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;
|
|
||||||
}
|
}
|
||||||
38
index.html
38
index.html
@ -26,45 +26,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="game-stats">
|
<div id="game-stats">
|
||||||
<div id="shots">Shots: <span id="home-shots">0</span> - <span id="away-shots">0</span></div>
|
<div id="shots">Shots: <span id="home-shots">0</span> - <span id="away-shots">0</span></div>
|
||||||
|
<div id="penalties">
|
||||||
|
<div id="home-penalties"></div>
|
||||||
|
<div id="away-penalties"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="controls">
|
<div id="controls">
|
||||||
<button id="play-pause">Play/Pause</button>
|
<button id="play-pause">Play/Pause</button>
|
||||||
<button id="speed-control">Speed: 1x</button>
|
<button id="speed-control">Speed: 1x</button>
|
||||||
<button id="reset-game">Reset</button>
|
<button id="reset-game">Reset</button>
|
||||||
<button id="debug-toggle">Debug Mode</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="debug-panel" class="debug-panel hidden">
|
|
||||||
<div class="debug-header">
|
|
||||||
<h3>Debug Panel</h3>
|
|
||||||
<button id="debug-close">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="debug-content">
|
|
||||||
<div class="debug-section">
|
|
||||||
<h4>Game State</h4>
|
|
||||||
<div id="debug-game-state"></div>
|
|
||||||
</div>
|
|
||||||
<div class="debug-section">
|
|
||||||
<h4>Puck</h4>
|
|
||||||
<div id="debug-puck"></div>
|
|
||||||
</div>
|
|
||||||
<div class="debug-section">
|
|
||||||
<h4>Players</h4>
|
|
||||||
<div class="debug-team">
|
|
||||||
<h5>Home Team</h5>
|
|
||||||
<div id="debug-home-players"></div>
|
|
||||||
</div>
|
|
||||||
<div class="debug-team">
|
|
||||||
<h5>Away Team</h5>
|
|
||||||
<div id="debug-away-players"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="debug-section">
|
|
||||||
<h4>Selected Player</h4>
|
|
||||||
<div id="debug-selected-player">Click a player to see detailed info</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -76,7 +47,6 @@
|
|||||||
<script src="src/systems/physics-system.js"></script>
|
<script src="src/systems/physics-system.js"></script>
|
||||||
<script src="src/systems/ai-system.js"></script>
|
<script src="src/systems/ai-system.js"></script>
|
||||||
<script src="src/systems/rules-system.js"></script>
|
<script src="src/systems/rules-system.js"></script>
|
||||||
<script src="src/systems/debug-system.js"></script>
|
|
||||||
<script src="src/engine/game-state.js"></script>
|
<script src="src/engine/game-state.js"></script>
|
||||||
<script src="src/engine/game-engine.js"></script>
|
<script src="src/engine/game-engine.js"></script>
|
||||||
<script src="src/engine/main.js"></script>
|
<script src="src/engine/main.js"></script>
|
||||||
|
|||||||
@ -19,10 +19,6 @@ class GameEngine {
|
|||||||
this.setupPlayers();
|
this.setupPlayers();
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
this.setupControls();
|
this.setupControls();
|
||||||
|
|
||||||
// Initialize debug system after all other systems are set up
|
|
||||||
window.debugMode = false; // Initialize debug mode
|
|
||||||
this.debugSystem = new DebugSystem(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPlayers() {
|
setupPlayers() {
|
||||||
@ -94,6 +90,13 @@ class GameEngine {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.gameState.on('penalty', (data) => {
|
||||||
|
const penalizedPlayer = this.players.find(p => p.name === data.player);
|
||||||
|
if (penalizedPlayer) {
|
||||||
|
penalizedPlayer.state.penaltyTime = data.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
this.gameState.on('period-end', () => {
|
this.gameState.on('period-end', () => {
|
||||||
});
|
});
|
||||||
@ -144,7 +147,11 @@ class GameEngine {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Zoom controls disabled for fixed camera view
|
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() {
|
start() {
|
||||||
@ -184,6 +191,7 @@ class GameEngine {
|
|||||||
if (this.puckActive) {
|
if (this.puckActive) {
|
||||||
this.puck.update(deltaTime, this.gameState, this.players);
|
this.puck.update(deltaTime, this.gameState, this.players);
|
||||||
this.updateCollisions();
|
this.updateCollisions();
|
||||||
|
this.renderer.updateCamera(this.puck.position);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateEffects(deltaTime);
|
this.updateEffects(deltaTime);
|
||||||
@ -221,6 +229,16 @@ class GameEngine {
|
|||||||
duration: 500,
|
duration: 500,
|
||||||
startTime: Date.now()
|
startTime: Date.now()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (Math.random() < 0.1) {
|
||||||
|
const penalizedPlayer = speed1 > speed2 ? player1 : player2;
|
||||||
|
this.gameState.addPenalty(
|
||||||
|
penalizedPlayer.team,
|
||||||
|
penalizedPlayer.name,
|
||||||
|
'Checking',
|
||||||
|
120
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,9 +253,7 @@ class GameEngine {
|
|||||||
|
|
||||||
this.renderEffects();
|
this.renderEffects();
|
||||||
this.renderer.drawUI(this.gameState);
|
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() {
|
renderEffects() {
|
||||||
@ -288,6 +304,7 @@ class GameEngine {
|
|||||||
player.velocity = new Vector2(0, 0);
|
player.velocity = new Vector2(0, 0);
|
||||||
player.state.hasPuck = false;
|
player.state.hasPuck = false;
|
||||||
player.state.energy = 100;
|
player.state.energy = 100;
|
||||||
|
player.state.penaltyTime = 0;
|
||||||
player.aiState.lastAction = 0;
|
player.aiState.lastAction = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -18,12 +18,14 @@ class GameState {
|
|||||||
shots: 0,
|
shots: 0,
|
||||||
saves: 0,
|
saves: 0,
|
||||||
hits: 0,
|
hits: 0,
|
||||||
|
penalties: [],
|
||||||
faceoffWins: 0
|
faceoffWins: 0
|
||||||
},
|
},
|
||||||
away: {
|
away: {
|
||||||
shots: 0,
|
shots: 0,
|
||||||
saves: 0,
|
saves: 0,
|
||||||
hits: 0,
|
hits: 0,
|
||||||
|
penalties: [],
|
||||||
faceoffWins: 0
|
faceoffWins: 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -50,6 +52,10 @@ class GameState {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.powerPlay = {
|
||||||
|
home: null,
|
||||||
|
away: null
|
||||||
|
};
|
||||||
|
|
||||||
this.faceoff = {
|
this.faceoff = {
|
||||||
isActive: false,
|
isActive: false,
|
||||||
@ -85,6 +91,7 @@ class GameState {
|
|||||||
this.endPeriod();
|
this.endPeriod();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updatePenalties(deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
endPeriod() {
|
endPeriod() {
|
||||||
@ -121,7 +128,41 @@ class GameState {
|
|||||||
this.emit('save', { team, saves: this.stats[team].saves });
|
this.emit('save', { team, saves: this.stats[team].saves });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addPenalty(team, player, type, duration = 120) {
|
||||||
|
const penalty = {
|
||||||
|
player,
|
||||||
|
type,
|
||||||
|
duration,
|
||||||
|
timeRemaining: duration
|
||||||
|
};
|
||||||
|
|
||||||
|
this.stats[team].penalties.push(penalty);
|
||||||
|
this.addEvent(`PENALTY - ${team.toUpperCase()} ${player}: ${type}`);
|
||||||
|
|
||||||
|
const oppositeTeam = team === 'home' ? 'away' : 'home';
|
||||||
|
this.powerPlay[oppositeTeam] = Date.now() + (duration * 1000);
|
||||||
|
|
||||||
|
this.emit('penalty', { team, player, type, duration });
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePenalties(deltaTime) {
|
||||||
|
['home', 'away'].forEach(team => {
|
||||||
|
this.stats[team].penalties = this.stats[team].penalties.filter(penalty => {
|
||||||
|
penalty.timeRemaining -= deltaTime * this.gameSpeed;
|
||||||
|
if (penalty.timeRemaining <= 0) {
|
||||||
|
this.emit('penalty-expired', { team, penalty });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
['home', 'away'].forEach(team => {
|
||||||
|
if (this.powerPlay[team] && Date.now() > this.powerPlay[team]) {
|
||||||
|
this.powerPlay[team] = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
addEvent(description) {
|
addEvent(description) {
|
||||||
const event = {
|
const event = {
|
||||||
@ -230,6 +271,7 @@ class GameState {
|
|||||||
isPaused: this.isPaused,
|
isPaused: this.isPaused,
|
||||||
gameSpeed: this.gameSpeed,
|
gameSpeed: this.gameSpeed,
|
||||||
gameOver: this.gameOver,
|
gameOver: this.gameOver,
|
||||||
|
powerPlay: { ...this.powerPlay },
|
||||||
faceoff: { ...this.faceoff }
|
faceoff: { ...this.faceoff }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,4 @@
|
|||||||
class Player {
|
class Player {
|
||||||
/**
|
|
||||||
* Creates a hockey player with physics properties, AI behavior, and game attributes
|
|
||||||
* @param {string} id - Unique identifier for the player
|
|
||||||
* @param {string} name - Player's display name
|
|
||||||
* @param {string} team - Team affiliation ('home' or 'away')
|
|
||||||
* @param {string} position - Hockey position ('LW', 'C', 'RW', 'LD', 'RD', 'G')
|
|
||||||
* @param {number} x - Initial x coordinate
|
|
||||||
* @param {number} y - Initial y coordinate
|
|
||||||
*/
|
|
||||||
constructor(id, name, team, position, x, y) {
|
constructor(id, name, team, position, x, y) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@ -19,7 +10,7 @@ class Player {
|
|||||||
this.role = position; // 'LW', 'C', 'RW', 'LD', 'RD', 'G'
|
this.role = position; // 'LW', 'C', 'RW', 'LD', 'RD', 'G'
|
||||||
this.radius = position === 'G' ? 20 : 12;
|
this.radius = position === 'G' ? 20 : 12;
|
||||||
this.mass = position === 'G' ? 2 : 1;
|
this.mass = position === 'G' ? 2 : 1;
|
||||||
this.maxSpeed = position === 'G' ? 200 : 280;
|
this.maxSpeed = position === 'G' ? 150 : 200;
|
||||||
this.acceleration = 800;
|
this.acceleration = 800;
|
||||||
this.restitution = 0.8;
|
this.restitution = 0.8;
|
||||||
|
|
||||||
@ -37,6 +28,7 @@ class Player {
|
|||||||
hasPuck: false,
|
hasPuck: false,
|
||||||
energy: 100,
|
energy: 100,
|
||||||
checking: false,
|
checking: false,
|
||||||
|
penaltyTime: 0,
|
||||||
injured: false
|
injured: false
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -52,14 +44,12 @@ class Player {
|
|||||||
this.targetAngle = 0;
|
this.targetAngle = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Main update loop for the player - handles energy, movement, rotation, and AI behavior
|
|
||||||
* @param {number} deltaTime - Time elapsed since last frame in seconds
|
|
||||||
* @param {Object} gameState - Current game state including rink dimensions and game status
|
|
||||||
* @param {Object} puck - Puck object with position and velocity
|
|
||||||
* @param {Array} players - Array of all players on the ice
|
|
||||||
*/
|
|
||||||
update(deltaTime, gameState, puck, players) {
|
update(deltaTime, gameState, puck, players) {
|
||||||
|
if (this.state.penaltyTime > 0) {
|
||||||
|
this.state.penaltyTime -= deltaTime;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.updateEnergy(deltaTime);
|
this.updateEnergy(deltaTime);
|
||||||
this.updateMovement(deltaTime);
|
this.updateMovement(deltaTime);
|
||||||
this.updateAngle(deltaTime);
|
this.updateAngle(deltaTime);
|
||||||
@ -71,11 +61,6 @@ class Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates player energy/stamina based on movement and provides recovery when stationary
|
|
||||||
* Energy affects max speed - tired players move slower
|
|
||||||
* @param {number} deltaTime - Time elapsed since last frame in seconds
|
|
||||||
*/
|
|
||||||
updateEnergy(deltaTime) {
|
updateEnergy(deltaTime) {
|
||||||
const energyDrain = this.velocity.magnitude() / this.maxSpeed * 10 * deltaTime;
|
const energyDrain = this.velocity.magnitude() / this.maxSpeed * 10 * deltaTime;
|
||||||
this.state.energy = Math.max(0, this.state.energy - energyDrain);
|
this.state.energy = Math.max(0, this.state.energy - energyDrain);
|
||||||
@ -89,11 +74,6 @@ class Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates player physics including movement toward target, velocity limits, and collision bounds
|
|
||||||
* Applies acceleration toward target position with deceleration when close
|
|
||||||
* @param {number} deltaTime - Time elapsed since last frame in seconds
|
|
||||||
*/
|
|
||||||
updateMovement(deltaTime) {
|
updateMovement(deltaTime) {
|
||||||
const direction = this.targetPosition.subtract(this.position).normalize();
|
const direction = this.targetPosition.subtract(this.position).normalize();
|
||||||
const distance = this.position.distance(this.targetPosition);
|
const distance = this.position.distance(this.targetPosition);
|
||||||
@ -117,10 +97,6 @@ class Player {
|
|||||||
this.keepInBounds();
|
this.keepInBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates player rotation to face target angle with smooth turning animation
|
|
||||||
* @param {number} deltaTime - Time elapsed since last frame in seconds
|
|
||||||
*/
|
|
||||||
updateAngle(deltaTime) {
|
updateAngle(deltaTime) {
|
||||||
let angleDiff = this.targetAngle - this.angle;
|
let angleDiff = this.targetAngle - this.angle;
|
||||||
angleDiff = Physics.wrapAngle(angleDiff);
|
angleDiff = Physics.wrapAngle(angleDiff);
|
||||||
@ -132,13 +108,6 @@ class Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Main AI decision making system - handles reaction timing, faceoffs, and behavioral switching
|
|
||||||
* Delegates to specific behavior methods based on puck possession
|
|
||||||
* @param {Object} gameState - Current game state including faceoff status
|
|
||||||
* @param {Object} puck - Puck object with position and velocity
|
|
||||||
* @param {Array} players - Array of all players on the ice
|
|
||||||
*/
|
|
||||||
updateAI(gameState, puck, players) {
|
updateAI(gameState, puck, players) {
|
||||||
const currentTime = Date.now();
|
const currentTime = Date.now();
|
||||||
if (currentTime - this.aiState.lastAction < this.aiState.reactionTime) {
|
if (currentTime - this.aiState.lastAction < this.aiState.reactionTime) {
|
||||||
@ -165,14 +134,6 @@ class Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Offensive AI behavior when player has possession of the puck
|
|
||||||
* Prioritizes shooting, then passing under pressure, then advancing toward goal
|
|
||||||
* @param {Object} gameState - Current game state with rink dimensions
|
|
||||||
* @param {Object} puck - Puck object to shoot or pass
|
|
||||||
* @param {Array} teammates - Array of teammate player objects
|
|
||||||
* @param {Array} opponents - Array of opposing player objects
|
|
||||||
*/
|
|
||||||
behaviorWithPuck(gameState, puck, teammates, opponents) {
|
behaviorWithPuck(gameState, puck, teammates, opponents) {
|
||||||
const enemyGoal = this.team === 'home' ?
|
const enemyGoal = this.team === 'home' ?
|
||||||
new Vector2(gameState.rink.width - 50, gameState.rink.centerY) :
|
new Vector2(gameState.rink.width - 50, gameState.rink.centerY) :
|
||||||
@ -203,15 +164,6 @@ class Player {
|
|||||||
this.advanceTowardGoal(enemyGoal, opponents, gameState.rink);
|
this.advanceTowardGoal(enemyGoal, opponents, gameState.rink);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Defensive AI behavior when player doesn't have puck possession
|
|
||||||
* Chooses between chasing loose puck, defending against opponents, or maintaining formation
|
|
||||||
* @param {Object} gameState - Current game state
|
|
||||||
* @param {Object} puck - Puck object with position
|
|
||||||
* @param {Array} teammates - Array of teammate player objects
|
|
||||||
* @param {Array} opponents - Array of opposing player objects
|
|
||||||
* @param {number} distanceToPuck - Pre-calculated distance to puck
|
|
||||||
*/
|
|
||||||
behaviorWithoutPuck(gameState, puck, teammates, opponents, distanceToPuck) {
|
behaviorWithoutPuck(gameState, puck, teammates, opponents, distanceToPuck) {
|
||||||
const puckOwner = opponents.find(p => p.state.hasPuck) || teammates.find(p => p.state.hasPuck);
|
const puckOwner = opponents.find(p => p.state.hasPuck) || teammates.find(p => p.state.hasPuck);
|
||||||
const isClosestToPuck = this.isClosestPlayerToPuck(puck, teammates);
|
const isClosestToPuck = this.isClosestPlayerToPuck(puck, teammates);
|
||||||
@ -221,14 +173,7 @@ class Player {
|
|||||||
// Only chase if this player is closest to the puck on their team
|
// Only chase if this player is closest to the puck on their team
|
||||||
this.chasePuck(puck);
|
this.chasePuck(puck);
|
||||||
} else if (puckOwner && puckOwner.team !== this.team) {
|
} else if (puckOwner && puckOwner.team !== this.team) {
|
||||||
// Check if this player is the closest defender to the puck carrier
|
if (distanceToPuck < 150 && Math.random() < 0.2) {
|
||||||
const isClosestDefender = this.isClosestDefenderToPuckCarrier(puckOwner, teammates);
|
|
||||||
|
|
||||||
if (isClosestDefender) {
|
|
||||||
// Closest defender aggressively targets the puck carrier
|
|
||||||
this.moveToPosition(puckOwner.position);
|
|
||||||
this.aiState.behavior = 'aggressive_pressure';
|
|
||||||
} else if (distanceToPuck < 150 && Math.random() < 0.2) {
|
|
||||||
this.checkPlayer(puckOwner);
|
this.checkPlayer(puckOwner);
|
||||||
} else {
|
} else {
|
||||||
this.defendPosition(gameState, puckOwner);
|
this.defendPosition(gameState, puckOwner);
|
||||||
@ -238,13 +183,6 @@ class Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Goalie-specific AI behavior - stays in crease and tracks puck movement
|
|
||||||
* Positions between puck and goal, with more aggressive positioning when puck is close
|
|
||||||
* @param {Object} gameState - Current game state with rink dimensions
|
|
||||||
* @param {Object} puck - Puck object with position
|
|
||||||
* @param {Array} players - Array of all players (unused but maintained for consistency)
|
|
||||||
*/
|
|
||||||
updateGoalie(gameState, puck, players) {
|
updateGoalie(gameState, puck, players) {
|
||||||
const goal = this.team === 'home' ?
|
const goal = this.team === 'home' ?
|
||||||
new Vector2(10, gameState.rink.centerY) :
|
new Vector2(10, gameState.rink.centerY) :
|
||||||
@ -270,22 +208,11 @@ class Player {
|
|||||||
this.targetPosition.y = Math.max(crease.y, Math.min(crease.y + crease.height, this.targetPosition.y));
|
this.targetPosition.y = Math.max(crease.y, Math.min(crease.y + crease.height, this.targetPosition.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets player target to chase after a loose puck
|
|
||||||
* @param {Object} puck - Puck object with position to chase
|
|
||||||
*/
|
|
||||||
chasePuck(puck) {
|
chasePuck(puck) {
|
||||||
this.moveToPosition(puck.position);
|
this.moveToPosition(puck.position);
|
||||||
this.aiState.behavior = 'chasing';
|
this.aiState.behavior = 'chasing';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shoots the puck toward a target with power and accuracy based on player attributes
|
|
||||||
* Applies random spread based on shooting accuracy - better shooters are more precise
|
|
||||||
* @param {Object} puck - Puck object to shoot
|
|
||||||
* @param {Vector2} target - Target position to aim for
|
|
||||||
* @returns {boolean} True if shot was taken
|
|
||||||
*/
|
|
||||||
shoot(puck, target) {
|
shoot(puck, target) {
|
||||||
const direction = target.subtract(puck.position).normalize();
|
const direction = target.subtract(puck.position).normalize();
|
||||||
const power = this.attributes.shooting / 100 * 800;
|
const power = this.attributes.shooting / 100 * 800;
|
||||||
@ -300,17 +227,10 @@ class Player {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Passes the puck to a teammate with power scaled by distance
|
|
||||||
* Closer passes are softer, longer passes are harder
|
|
||||||
* @param {Object} puck - Puck object to pass
|
|
||||||
* @param {Object} target - Target player object to pass to
|
|
||||||
* @returns {boolean} True if pass was made
|
|
||||||
*/
|
|
||||||
pass(puck, target) {
|
pass(puck, target) {
|
||||||
const direction = target.position.subtract(puck.position).normalize();
|
const direction = target.position.subtract(puck.position).normalize();
|
||||||
const distance = puck.position.distance(target.position);
|
const distance = puck.position.distance(target.position);
|
||||||
const power = Math.min(800, distance * 2.5);
|
const power = Math.min(600, distance * 2);
|
||||||
|
|
||||||
puck.velocity = direction.multiply(power);
|
puck.velocity = direction.multiply(power);
|
||||||
this.state.hasPuck = false;
|
this.state.hasPuck = false;
|
||||||
@ -318,12 +238,6 @@ class Player {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to body check an opponent player
|
|
||||||
* If close enough, applies knockback force; otherwise moves toward target
|
|
||||||
* @param {Object} target - Target player to check
|
|
||||||
* @returns {boolean} True if check was successful (contact made)
|
|
||||||
*/
|
|
||||||
checkPlayer(target) {
|
checkPlayer(target) {
|
||||||
if (this.position.distance(target.position) < 30) {
|
if (this.position.distance(target.position) < 30) {
|
||||||
target.velocity = target.velocity.add(
|
target.velocity = target.velocity.add(
|
||||||
@ -336,21 +250,11 @@ class Player {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the player's target position and facing angle
|
|
||||||
* @param {Vector2} target - Target position to move toward
|
|
||||||
*/
|
|
||||||
moveToPosition(target) {
|
moveToPosition(target) {
|
||||||
this.targetPosition = target.copy();
|
this.targetPosition = target.copy();
|
||||||
this.targetAngle = target.subtract(this.position).angle();
|
this.targetAngle = target.subtract(this.position).angle();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Positions player defensively between opponent and own goal
|
|
||||||
* Uses interpolation to stay closer to opponent than goal
|
|
||||||
* @param {Object} gameState - Current game state with rink dimensions
|
|
||||||
* @param {Object} opponent - Opponent player to defend against
|
|
||||||
*/
|
|
||||||
defendPosition(gameState, opponent) {
|
defendPosition(gameState, opponent) {
|
||||||
const ownGoal = this.team === 'home' ?
|
const ownGoal = this.team === 'home' ?
|
||||||
new Vector2(50, gameState.rink.centerY) :
|
new Vector2(50, gameState.rink.centerY) :
|
||||||
@ -361,24 +265,11 @@ class Player {
|
|||||||
this.aiState.behavior = 'defending';
|
this.aiState.behavior = 'defending';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves player to their calculated formation position based on game context
|
|
||||||
* @param {Object} gameState - Current game state
|
|
||||||
* @param {Object} puck - Puck object with position
|
|
||||||
* @param {Array} players - Array of all players for formation calculation
|
|
||||||
*/
|
|
||||||
moveToFormationPosition(gameState, puck, players) {
|
moveToFormationPosition(gameState, puck, players) {
|
||||||
this.moveToPosition(this.getFormationPosition(gameState, puck, players));
|
this.moveToPosition(this.getFormationPosition(gameState, puck, players));
|
||||||
this.aiState.behavior = 'formation';
|
this.aiState.behavior = 'formation';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates ideal formation position for this player based on team state and puck location
|
|
||||||
* @param {Object} gameState - Current game state with rink dimensions
|
|
||||||
* @param {Object} puck - Puck object with position
|
|
||||||
* @param {Array} players - Array of all players to determine puck ownership
|
|
||||||
* @returns {Vector2} Calculated formation position
|
|
||||||
*/
|
|
||||||
getFormationPosition(gameState, puck, players) {
|
getFormationPosition(gameState, puck, players) {
|
||||||
const side = this.team === 'home' ? -1 : 1;
|
const side = this.team === 'home' ? -1 : 1;
|
||||||
const rink = gameState.rink;
|
const rink = gameState.rink;
|
||||||
@ -391,14 +282,6 @@ class Player {
|
|||||||
return this.getContextualPosition(rink, side, isAttacking, puck);
|
return this.getContextualPosition(rink, side, isAttacking, puck);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if this player's team is in attacking or defending mode
|
|
||||||
* Based on puck possession and puck location on the rink
|
|
||||||
* @param {Object} puck - Puck object with position
|
|
||||||
* @param {Object} puckOwner - Player object who has puck possession (null if loose)
|
|
||||||
* @param {Object} rink - Rink object with dimensions
|
|
||||||
* @returns {boolean} True if team is attacking, false if defending
|
|
||||||
*/
|
|
||||||
determineTeamState(puck, puckOwner, rink) {
|
determineTeamState(puck, puckOwner, rink) {
|
||||||
const homeAttackingZone = rink.width * 0.67; // Right side for home team
|
const homeAttackingZone = rink.width * 0.67; // Right side for home team
|
||||||
const awayAttackingZone = rink.width * 0.33; // Left side for away team
|
const awayAttackingZone = rink.width * 0.33; // Left side for away team
|
||||||
@ -421,15 +304,6 @@ class Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates specific position for player based on role, team state, and puck influence
|
|
||||||
* Different formations for attacking vs defending, with puck tracking adjustments
|
|
||||||
* @param {Object} rink - Rink object with dimensions and center points
|
|
||||||
* @param {number} side - Team side multiplier (-1 for home, 1 for away)
|
|
||||||
* @param {boolean} isAttacking - Whether team is in attacking formation
|
|
||||||
* @param {Object} puck - Puck object for positional influence
|
|
||||||
* @returns {Vector2} Calculated contextual position
|
|
||||||
*/
|
|
||||||
getContextualPosition(rink, side, isAttacking, puck) {
|
getContextualPosition(rink, side, isAttacking, puck) {
|
||||||
const centerY = rink.centerY;
|
const centerY = rink.centerY;
|
||||||
const puckInfluenceX = (puck.position.x - rink.centerX) * 0.3; // Follow puck horizontally
|
const puckInfluenceX = (puck.position.x - rink.centerX) * 0.3; // Follow puck horizontally
|
||||||
@ -506,11 +380,6 @@ class Player {
|
|||||||
return new Vector2(baseX, baseY);
|
return new Vector2(baseX, baseY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the closest player from a given array of players
|
|
||||||
* @param {Array} players - Array of player objects to search through
|
|
||||||
* @returns {Object|null} Nearest player object, or null if no players provided
|
|
||||||
*/
|
|
||||||
findNearestPlayer(players) {
|
findNearestPlayer(players) {
|
||||||
let nearest = null;
|
let nearest = null;
|
||||||
let minDistance = Infinity;
|
let minDistance = Infinity;
|
||||||
@ -526,12 +395,6 @@ class Player {
|
|||||||
return nearest;
|
return nearest;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Evaluates teammates to find the best pass target based on distance, skill, and opponent blocking
|
|
||||||
* @param {Array} teammates - Array of teammate player objects
|
|
||||||
* @param {Array} opponents - Array of opponent players that might block the pass
|
|
||||||
* @returns {Object|null} Best teammate to pass to, or null if no good options
|
|
||||||
*/
|
|
||||||
findBestPassTarget(teammates, opponents) {
|
findBestPassTarget(teammates, opponents) {
|
||||||
let bestTarget = null;
|
let bestTarget = null;
|
||||||
let bestScore = -1;
|
let bestScore = -1;
|
||||||
@ -563,13 +426,6 @@ class Player {
|
|||||||
return bestTarget;
|
return bestTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if this player is the closest non-goalie teammate to the puck
|
|
||||||
* Used to decide who should chase loose pucks
|
|
||||||
* @param {Object} puck - Puck object with position
|
|
||||||
* @param {Array} teammates - Array of teammate player objects
|
|
||||||
* @returns {boolean} True if this player is closest to puck on their team
|
|
||||||
*/
|
|
||||||
isClosestPlayerToPuck(puck, teammates) {
|
isClosestPlayerToPuck(puck, teammates) {
|
||||||
// Check if this player (excluding goalies) is closest to the puck on their team
|
// Check if this player (excluding goalies) is closest to the puck on their team
|
||||||
if (this.role === 'G' || this.state.hasPuck) return false;
|
if (this.role === 'G' || this.state.hasPuck) return false;
|
||||||
@ -594,43 +450,6 @@ class Player {
|
|||||||
return closestPlayer === this;
|
return closestPlayer === this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this player is the closest defender to the puck carrier
|
|
||||||
* @param {Object} puckCarrier - The player who has the puck
|
|
||||||
* @param {Array} teammates - Array of teammate player objects
|
|
||||||
* @returns {boolean} True if this player is closest to puck carrier on their team
|
|
||||||
*/
|
|
||||||
isClosestDefenderToPuckCarrier(puckCarrier, teammates) {
|
|
||||||
// Skip goalies
|
|
||||||
if (this.role === 'G') return false;
|
|
||||||
|
|
||||||
const myDistance = this.position.distance(puckCarrier.position);
|
|
||||||
|
|
||||||
// Include self in the list to compare against (excluding goalies)
|
|
||||||
const allTeamPlayers = [this, ...teammates.filter(t => t.role !== 'G')];
|
|
||||||
|
|
||||||
// Find the closest player to the puck carrier
|
|
||||||
let closestDistance = Infinity;
|
|
||||||
let closestPlayer = null;
|
|
||||||
|
|
||||||
allTeamPlayers.forEach(player => {
|
|
||||||
const distance = player.position.distance(puckCarrier.position);
|
|
||||||
if (distance < closestDistance) {
|
|
||||||
closestDistance = distance;
|
|
||||||
closestPlayer = player;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return closestPlayer === this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Evaluates whether player has a clear shooting angle to goal
|
|
||||||
* Checks if opponents are blocking the direct path to goal
|
|
||||||
* @param {Vector2} goalPosition - Target goal position
|
|
||||||
* @param {Array} opponents - Array of opponent players that might block shot
|
|
||||||
* @returns {boolean} True if shooting angle is clear
|
|
||||||
*/
|
|
||||||
hasGoodShootingAngle(goalPosition, opponents) {
|
hasGoodShootingAngle(goalPosition, opponents) {
|
||||||
// Check if there's a clear line to goal (simplified check)
|
// Check if there's a clear line to goal (simplified check)
|
||||||
const directionToGoal = goalPosition.subtract(this.position).normalize();
|
const directionToGoal = goalPosition.subtract(this.position).normalize();
|
||||||
@ -655,13 +474,6 @@ class Player {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Intelligently advances player with puck toward the opponent's goal
|
|
||||||
* Uses pathfinding to avoid opponents and direct approach when close
|
|
||||||
* @param {Vector2} goalPosition - Target goal position to advance toward
|
|
||||||
* @param {Array} opponents - Array of opponent players to avoid
|
|
||||||
* @param {Object} rink - Rink object with boundary dimensions
|
|
||||||
*/
|
|
||||||
advanceTowardGoal(goalPosition, opponents, rink) {
|
advanceTowardGoal(goalPosition, opponents, rink) {
|
||||||
// Create an intelligent path toward the goal
|
// Create an intelligent path toward the goal
|
||||||
let targetPosition = goalPosition.copy();
|
let targetPosition = goalPosition.copy();
|
||||||
@ -687,14 +499,6 @@ class Player {
|
|||||||
this.moveToPosition(targetPosition);
|
this.moveToPosition(targetPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates path adjustments to avoid opponents while advancing toward goal
|
|
||||||
* Creates lateral movement to navigate around blocking opponents
|
|
||||||
* @param {Vector2} goalPosition - Target goal position
|
|
||||||
* @param {Array} opponents - Array of opponent players to avoid
|
|
||||||
* @param {Object} rink - Rink object for boundary awareness
|
|
||||||
* @returns {Vector2} Position adjustment vector to avoid opponents
|
|
||||||
*/
|
|
||||||
findBestPathToGoal(goalPosition, opponents, rink) {
|
findBestPathToGoal(goalPosition, opponents, rink) {
|
||||||
const currentPos = this.position;
|
const currentPos = this.position;
|
||||||
const adjustment = new Vector2(0, 0);
|
const adjustment = new Vector2(0, 0);
|
||||||
@ -733,27 +537,19 @@ class Player {
|
|||||||
return adjustment;
|
return adjustment;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Constrains player position to stay within rink boundaries
|
|
||||||
* Uses hardcoded rink dimensions of 1000x600
|
|
||||||
*/
|
|
||||||
keepInBounds() {
|
keepInBounds() {
|
||||||
this.position.x = Math.max(this.radius, Math.min(1000 - this.radius, this.position.x));
|
this.position.x = Math.max(this.radius, Math.min(1000 - this.radius, this.position.x));
|
||||||
this.position.y = Math.max(this.radius, Math.min(600 - this.radius, this.position.y));
|
this.position.y = Math.max(this.radius, Math.min(600 - this.radius, this.position.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the player on the canvas with team colors, puck indicator, and role text
|
|
||||||
* @param {CanvasRenderingContext2D} ctx - 2D rendering context for drawing
|
|
||||||
*/
|
|
||||||
render(ctx) {
|
render(ctx) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.translate(this.position.x, this.position.y);
|
ctx.translate(this.position.x, this.position.y);
|
||||||
ctx.rotate(this.angle);
|
ctx.rotate(this.angle);
|
||||||
|
|
||||||
ctx.fillStyle = this.team === 'home' ? '#4a90e2' : '#e24a4a';
|
ctx.fillStyle = this.team === 'home' ? '#4a90e2' : '#e24a4a';
|
||||||
ctx.strokeStyle = this.state.hasPuck ? '#000' : '#fff';
|
ctx.strokeStyle = '#fff';
|
||||||
ctx.lineWidth = this.state.hasPuck ? 3 : 2;
|
ctx.lineWidth = 2;
|
||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
|
ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
|
||||||
@ -775,12 +571,6 @@ class Player {
|
|||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles player positioning during faceoff situations
|
|
||||||
* Centers participate directly, other positions maintain legal distance from faceoff circle
|
|
||||||
* @param {Object} gameState - Current game state with faceoff information
|
|
||||||
* @param {Array} players - Array of all players for positioning context
|
|
||||||
*/
|
|
||||||
handleFaceoffPositioning(gameState, players) {
|
handleFaceoffPositioning(gameState, players) {
|
||||||
const faceoffPos = this.getFaceoffPosition(gameState, players);
|
const faceoffPos = this.getFaceoffPosition(gameState, players);
|
||||||
this.moveToPosition(faceoffPos);
|
this.moveToPosition(faceoffPos);
|
||||||
@ -795,13 +585,6 @@ class Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates legal faceoff positioning for each player role
|
|
||||||
* Centers face off directly, other positions must stay outside faceoff circle per hockey rules
|
|
||||||
* @param {Object} gameState - Current game state with faceoff location and rink info
|
|
||||||
* @param {Array} players - Array of all players (unused but maintained for consistency)
|
|
||||||
* @returns {Vector2} Legal faceoff position for this player's role
|
|
||||||
*/
|
|
||||||
getFaceoffPosition(gameState, players) {
|
getFaceoffPosition(gameState, players) {
|
||||||
const faceoffLocation = gameState.faceoff.location;
|
const faceoffLocation = gameState.faceoff.location;
|
||||||
const side = this.team === 'home' ? -1 : 1;
|
const side = this.team === 'home' ? -1 : 1;
|
||||||
|
|||||||
@ -18,7 +18,6 @@ class Puck {
|
|||||||
this.updatePosition(deltaTime);
|
this.updatePosition(deltaTime);
|
||||||
this.checkBoardCollisions(gameState);
|
this.checkBoardCollisions(gameState);
|
||||||
this.checkPlayerCollisions(players, gameState);
|
this.checkPlayerCollisions(players, gameState);
|
||||||
this.checkPuckPossession(players);
|
|
||||||
this.updateTrail();
|
this.updateTrail();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,48 +26,6 @@ class Puck {
|
|||||||
this.position = this.position.add(this.velocity.multiply(deltaTime));
|
this.position = this.position.add(this.velocity.multiply(deltaTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
checkPuckPossession(players) {
|
|
||||||
const puckCarrier = players.find(player => player.state.hasPuck);
|
|
||||||
if (!puckCarrier) return;
|
|
||||||
|
|
||||||
// Check if any opponent is trying to steal the puck
|
|
||||||
const opponents = players.filter(p => p.team !== puckCarrier.team && p.role !== 'G');
|
|
||||||
const nearbyOpponent = opponents.find(opponent => {
|
|
||||||
const distanceToCarrier = opponent.position.distance(puckCarrier.position);
|
|
||||||
const distanceToPuck = opponent.position.distance(this.position);
|
|
||||||
return distanceToCarrier < 25 && distanceToPuck < 20;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (nearbyOpponent) {
|
|
||||||
// Opponent is close enough to potentially steal the puck
|
|
||||||
const stealChance = 0.005; // 0.5% chance per frame (roughly 30% per second at 60fps)
|
|
||||||
if (Math.random() < stealChance) {
|
|
||||||
puckCarrier.state.hasPuck = false;
|
|
||||||
nearbyOpponent.state.hasPuck = true;
|
|
||||||
this.lastPlayerTouch = nearbyOpponent;
|
|
||||||
this.lastTeamTouch = nearbyOpponent.team;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sticky puck logic - keep puck attached to carrier
|
|
||||||
const stickDistance = 15; // Distance puck stays from player
|
|
||||||
const directionToPlayer = puckCarrier.position.subtract(this.position).normalize();
|
|
||||||
|
|
||||||
// Position puck slightly in front of player based on their movement direction
|
|
||||||
let puckOffset;
|
|
||||||
if (puckCarrier.velocity.magnitude() > 10) {
|
|
||||||
// When moving, position puck in front of player
|
|
||||||
puckOffset = puckCarrier.velocity.normalize().multiply(stickDistance);
|
|
||||||
} else {
|
|
||||||
// When stationary, keep puck close
|
|
||||||
puckOffset = directionToPlayer.multiply(-stickDistance);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.position = puckCarrier.position.add(puckOffset);
|
|
||||||
this.velocity = puckCarrier.velocity.multiply(1.0); // Match player velocity
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTrail() {
|
updateTrail() {
|
||||||
if (this.velocity.magnitude() > 50) {
|
if (this.velocity.magnitude() > 50) {
|
||||||
this.trail.unshift({
|
this.trail.unshift({
|
||||||
@ -142,6 +99,8 @@ class Puck {
|
|||||||
let closestDistance = Infinity;
|
let closestDistance = Infinity;
|
||||||
|
|
||||||
players.forEach(player => {
|
players.forEach(player => {
|
||||||
|
if (player.state.penaltyTime > 0) return;
|
||||||
|
|
||||||
const distance = this.position.distance(player.position);
|
const distance = this.position.distance(player.position);
|
||||||
const collisionDistance = this.radius + player.radius;
|
const collisionDistance = this.radius + player.radius;
|
||||||
|
|
||||||
@ -154,11 +113,11 @@ class Puck {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (closestPlayer) {
|
if (closestPlayer) {
|
||||||
this.handlePlayerCollision(closestPlayer, gameState, players);
|
this.handlePlayerCollision(closestPlayer, gameState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePlayerCollision(player, gameState, players) {
|
handlePlayerCollision(player, gameState) {
|
||||||
const distance = this.position.distance(player.position);
|
const distance = this.position.distance(player.position);
|
||||||
const minDistance = this.radius + player.radius;
|
const minDistance = this.radius + player.radius;
|
||||||
|
|
||||||
@ -178,15 +137,15 @@ class Puck {
|
|||||||
|
|
||||||
this.velocity = this.velocity.subtract(direction.multiply(impulse * player.mass * this.restitution));
|
this.velocity = this.velocity.subtract(direction.multiply(impulse * player.mass * this.restitution));
|
||||||
|
|
||||||
if (player.role !== 'G') {
|
if (this.velocity.magnitude() < 100 && player.role !== 'G') {
|
||||||
this.pickupPuck(player, gameState, players);
|
this.pickupPuck(player, gameState);
|
||||||
} else if (player.role === 'G' && this.velocity.magnitude() > 50) {
|
} else if (player.role === 'G' && this.velocity.magnitude() > 50) {
|
||||||
this.handleGoalieSave(player, gameState);
|
this.handleGoalieSave(player, gameState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pickupPuck(player, gameState, players) {
|
pickupPuck(player, gameState) {
|
||||||
if (player.state.hasPuck) return;
|
if (player.state.hasPuck) return;
|
||||||
|
|
||||||
players.forEach(p => p.state.hasPuck = false);
|
players.forEach(p => p.state.hasPuck = false);
|
||||||
@ -226,7 +185,7 @@ class Puck {
|
|||||||
this.trail = [];
|
this.trail = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
pass(target, power = 500) {
|
pass(target, power = 300) {
|
||||||
const direction = target.subtract(this.position).normalize();
|
const direction = target.subtract(this.position).normalize();
|
||||||
this.velocity = direction.multiply(power);
|
this.velocity = direction.multiply(power);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,6 +84,7 @@ class AISystem {
|
|||||||
gameTime: gameState.timeRemaining,
|
gameTime: gameState.timeRemaining,
|
||||||
period: gameState.period,
|
period: gameState.period,
|
||||||
scoreGap: gameState.homeScore - gameState.awayScore,
|
scoreGap: gameState.homeScore - gameState.awayScore,
|
||||||
|
powerPlay: gameState.powerPlay,
|
||||||
zone: this.determinePuckZone(puck, gameState)
|
zone: this.determinePuckZone(puck, gameState)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -103,6 +104,9 @@ class AISystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectFormation(context) {
|
selectFormation(context) {
|
||||||
|
if (context.powerPlay.home || context.powerPlay.away) {
|
||||||
|
return context.powerPlay.home ? 'offensive' : 'defensive';
|
||||||
|
}
|
||||||
|
|
||||||
switch (context.zone) {
|
switch (context.zone) {
|
||||||
case 'offensive':
|
case 'offensive':
|
||||||
@ -120,6 +124,7 @@ class AISystem {
|
|||||||
if (context.gameTime < 300) urgency += 0.3;
|
if (context.gameTime < 300) urgency += 0.3;
|
||||||
if (context.gameTime < 120) urgency += 0.3;
|
if (context.gameTime < 120) urgency += 0.3;
|
||||||
if (Math.abs(context.scoreGap) > 1) urgency += 0.2;
|
if (Math.abs(context.scoreGap) > 1) urgency += 0.2;
|
||||||
|
if (context.powerPlay.home || context.powerPlay.away) urgency += 0.4;
|
||||||
|
|
||||||
return Math.min(1, urgency);
|
return Math.min(1, urgency);
|
||||||
}
|
}
|
||||||
@ -176,9 +181,6 @@ class AISystem {
|
|||||||
const distanceToPuck = player.position.distance(context.puckPosition);
|
const distanceToPuck = player.position.distance(context.puckPosition);
|
||||||
const isNearPuck = distanceToPuck < 100;
|
const isNearPuck = distanceToPuck < 100;
|
||||||
const isClosestToPuck = context.closestPlayers[player.team] === player;
|
const isClosestToPuck = context.closestPlayers[player.team] === player;
|
||||||
const isClosestDefender = context.puckOwner &&
|
|
||||||
context.puckOwner.team !== player.team &&
|
|
||||||
context.closestPlayers[player.team] === player;
|
|
||||||
|
|
||||||
if (player.state.hasPuck) {
|
if (player.state.hasPuck) {
|
||||||
player.aiState.behavior = 'puck_carrier';
|
player.aiState.behavior = 'puck_carrier';
|
||||||
@ -186,10 +188,6 @@ class AISystem {
|
|||||||
} else if (context.puckOwner && context.puckOwner.team === player.team) {
|
} else if (context.puckOwner && context.puckOwner.team === player.team) {
|
||||||
player.aiState.behavior = 'support';
|
player.aiState.behavior = 'support';
|
||||||
this.executeSupportBehavior(player, context);
|
this.executeSupportBehavior(player, context);
|
||||||
} else if (isClosestDefender) {
|
|
||||||
// Closest defender directly targets the puck carrier
|
|
||||||
player.aiState.behavior = 'aggressive_pressure';
|
|
||||||
this.executeAggressivePressureBehavior(player, context);
|
|
||||||
} else if (context.puckOwner && context.puckOwner.team !== player.team) {
|
} else if (context.puckOwner && context.puckOwner.team !== player.team) {
|
||||||
player.aiState.behavior = 'pressure';
|
player.aiState.behavior = 'pressure';
|
||||||
this.executePressureBehavior(player, context);
|
this.executePressureBehavior(player, context);
|
||||||
@ -228,14 +226,6 @@ class AISystem {
|
|||||||
player.aiState.action = 'support';
|
player.aiState.action = 'support';
|
||||||
}
|
}
|
||||||
|
|
||||||
executeAggressivePressureBehavior(player, context) {
|
|
||||||
const opponent = context.puckOwner;
|
|
||||||
|
|
||||||
// Closest defender directly targets the puck carrier's position
|
|
||||||
player.targetPosition = opponent.position.copy();
|
|
||||||
player.aiState.action = 'aggressive_pressure';
|
|
||||||
}
|
|
||||||
|
|
||||||
executePressureBehavior(player, context) {
|
executePressureBehavior(player, context) {
|
||||||
const opponent = context.puckOwner;
|
const opponent = context.puckOwner;
|
||||||
const pressurePosition = this.calculatePressurePosition(player, opponent, context);
|
const pressurePosition = this.calculatePressurePosition(player, opponent, context);
|
||||||
|
|||||||
@ -1,464 +0,0 @@
|
|||||||
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 = `
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Period:</span>
|
|
||||||
<span class="debug-attribute-value">${gameState.getPeriodName()}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Time:</span>
|
|
||||||
<span class="debug-attribute-value">${gameState.formatTime(gameState.timeRemaining)}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Score:</span>
|
|
||||||
<span class="debug-attribute-value">${gameState.homeScore} - ${gameState.awayScore}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Paused:</span>
|
|
||||||
<span class="debug-attribute-value">${gameState.isPaused}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Speed:</span>
|
|
||||||
<span class="debug-attribute-value">${gameState.gameSpeed}x</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Faceoff Active:</span>
|
|
||||||
<span class="debug-attribute-value">${gameState.faceoff.isActive}</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
document.getElementById('debug-game-state').innerHTML = info;
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePuckInfo() {
|
|
||||||
const puck = this.gameEngine.puck;
|
|
||||||
const info = `
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Position:</span>
|
|
||||||
<span class="debug-attribute-value debug-coords">(${puck.position.x.toFixed(1)}, ${puck.position.y.toFixed(1)})</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Velocity:</span>
|
|
||||||
<span class="debug-attribute-value debug-coords">(${puck.velocity.x.toFixed(1)}, ${puck.velocity.y.toFixed(1)})</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Speed:</span>
|
|
||||||
<span class="debug-attribute-value">${puck.getSpeed().toFixed(1)}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Active:</span>
|
|
||||||
<span class="debug-attribute-value">${this.gameEngine.puckActive}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Owner:</span>
|
|
||||||
<span class="debug-attribute-value">${puck.lastTouchedBy || 'None'}</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
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 = `
|
|
||||||
<div class="debug-player-header">${player.role} - ${player.name}</div>
|
|
||||||
<div class="debug-player-info">
|
|
||||||
<div class="debug-coords">Pos: (${player.position.x.toFixed(0)}, ${player.position.y.toFixed(0)})</div>
|
|
||||||
<div>Speed: <span class="debug-value debug-speed">${player.velocity.magnitude().toFixed(1)}</span></div>
|
|
||||||
<div>Energy: <span class="debug-value debug-energy">${player.state.energy.toFixed(0)}%</span></div>
|
|
||||||
<div>Has Puck: <span class="debug-value debug-puck">${player.state.hasPuck}</span></div>
|
|
||||||
<div>Behavior: <span class="debug-value debug-behavior">${player.aiState.behavior}</span></div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 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 = `
|
|
||||||
<div style="border-bottom: 1px solid #333; padding-bottom: 10px; margin-bottom: 15px;">
|
|
||||||
<h4 style="margin: 0 0 5px 0; color: #4a90e2;">${player.name} (${player.role})</h4>
|
|
||||||
<div style="color: #ccc; font-size: 11px;">Team: ${player.team.toUpperCase()}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-bottom: 15px;">
|
|
||||||
<h5 style="margin: 0 0 8px 0; color: #ccc;">Position & Physics</h5>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Position:</span>
|
|
||||||
<span class="debug-attribute-value debug-coords">(${player.position.x.toFixed(1)}, ${player.position.y.toFixed(1)})</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Velocity:</span>
|
|
||||||
<span class="debug-attribute-value debug-coords">(${player.velocity.x.toFixed(1)}, ${player.velocity.y.toFixed(1)})</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Speed:</span>
|
|
||||||
<span class="debug-attribute-value">${player.velocity.magnitude().toFixed(1)}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Target:</span>
|
|
||||||
<span class="debug-attribute-value debug-coords">(${player.targetPosition.x.toFixed(1)}, ${player.targetPosition.y.toFixed(1)})</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-bottom: 15px;">
|
|
||||||
<h5 style="margin: 0 0 8px 0; color: #ccc;">Game State</h5>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Has Puck:</span>
|
|
||||||
<span class="debug-attribute-value">${player.state.hasPuck}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Energy:</span>
|
|
||||||
<span class="debug-attribute-value">${player.state.energy.toFixed(1)}%</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Checking:</span>
|
|
||||||
<span class="debug-attribute-value">${player.state.checking}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Injured:</span>
|
|
||||||
<span class="debug-attribute-value">${player.state.injured}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-bottom: 15px;">
|
|
||||||
<h5 style="margin: 0 0 8px 0; color: #ccc;">AI State</h5>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Behavior:</span>
|
|
||||||
<span class="debug-attribute-value">${player.aiState.behavior}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Target:</span>
|
|
||||||
<span class="debug-attribute-value">${player.aiState.target ? player.aiState.target.constructor.name : 'None'}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Last Action:</span>
|
|
||||||
<span class="debug-attribute-value">${player.aiState.lastAction.toFixed(0)}ms ago</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Reaction Time:</span>
|
|
||||||
<span class="debug-attribute-value">${player.aiState.reactionTime.toFixed(0)}ms</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h5 style="margin: 0 0 8px 0; color: #ccc;">Attributes</h5>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Speed:</span>
|
|
||||||
<span class="debug-attribute-value">${player.attributes.speed.toFixed(0)}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Shooting:</span>
|
|
||||||
<span class="debug-attribute-value">${player.attributes.shooting.toFixed(0)}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Passing:</span>
|
|
||||||
<span class="debug-attribute-value">${player.attributes.passing.toFixed(0)}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Defense:</span>
|
|
||||||
<span class="debug-attribute-value">${player.attributes.defense.toFixed(0)}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Checking:</span>
|
|
||||||
<span class="debug-attribute-value">${player.attributes.checking.toFixed(0)}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Puck Handling:</span>
|
|
||||||
<span class="debug-attribute-value">${player.attributes.puckHandling.toFixed(0)}</span>
|
|
||||||
</div>
|
|
||||||
<div class="debug-attribute">
|
|
||||||
<span class="debug-attribute-name">Awareness:</span>
|
|
||||||
<span class="debug-attribute-value">${player.attributes.awareness.toFixed(0)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,9 +2,15 @@ class Renderer {
|
|||||||
constructor(canvas) {
|
constructor(canvas) {
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
this.ctx = canvas.getContext('2d');
|
this.ctx = canvas.getContext('2d');
|
||||||
|
this.camera = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
zoom: 1,
|
||||||
|
target: null,
|
||||||
|
smoothing: 0.1
|
||||||
|
};
|
||||||
|
|
||||||
this.setupCanvas();
|
this.setupCanvas();
|
||||||
this.setupFixedCamera();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupCanvas() {
|
setupCanvas() {
|
||||||
@ -12,27 +18,6 @@ class Renderer {
|
|||||||
this.ctx.imageSmoothingEnabled = false;
|
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() {
|
clear() {
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||||
}
|
}
|
||||||
@ -157,7 +142,9 @@ class Renderer {
|
|||||||
this.applyCamera();
|
this.applyCamera();
|
||||||
|
|
||||||
players.forEach(player => {
|
players.forEach(player => {
|
||||||
player.render(this.ctx);
|
if (player.state.penaltyTime <= 0) {
|
||||||
|
player.render(this.ctx);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ctx.restore();
|
this.ctx.restore();
|
||||||
@ -173,6 +160,7 @@ class Renderer {
|
|||||||
drawUI(gameState) {
|
drawUI(gameState) {
|
||||||
this.updateScoreBoard(gameState);
|
this.updateScoreBoard(gameState);
|
||||||
this.updateGameStats(gameState);
|
this.updateGameStats(gameState);
|
||||||
|
this.updatePenalties(gameState);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateScoreBoard(gameState) {
|
updateScoreBoard(gameState) {
|
||||||
@ -187,6 +175,27 @@ class Renderer {
|
|||||||
document.getElementById('away-shots').textContent = gameState.stats.away.shots;
|
document.getElementById('away-shots').textContent = gameState.stats.away.shots;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePenalties(gameState) {
|
||||||
|
const homePenalties = document.getElementById('home-penalties');
|
||||||
|
const awayPenalties = document.getElementById('away-penalties');
|
||||||
|
|
||||||
|
homePenalties.innerHTML = '';
|
||||||
|
awayPenalties.innerHTML = '';
|
||||||
|
|
||||||
|
gameState.stats.home.penalties.forEach(penalty => {
|
||||||
|
const penaltyDiv = document.createElement('div');
|
||||||
|
penaltyDiv.className = 'penalty-box';
|
||||||
|
penaltyDiv.textContent = `${penalty.player}: ${penalty.type} (${Math.ceil(penalty.timeRemaining)}s)`;
|
||||||
|
homePenalties.appendChild(penaltyDiv);
|
||||||
|
});
|
||||||
|
|
||||||
|
gameState.stats.away.penalties.forEach(penalty => {
|
||||||
|
const penaltyDiv = document.createElement('div');
|
||||||
|
penaltyDiv.className = 'penalty-box';
|
||||||
|
penaltyDiv.textContent = `${penalty.player}: ${penalty.type} (${Math.ceil(penalty.timeRemaining)}s)`;
|
||||||
|
awayPenalties.appendChild(penaltyDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
drawParticleEffect(position, type, color = '#ffff00') {
|
drawParticleEffect(position, type, color = '#ffff00') {
|
||||||
this.ctx.save();
|
this.ctx.save();
|
||||||
@ -257,7 +266,17 @@ class Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateCamera(target) {
|
updateCamera(target) {
|
||||||
// Camera is now fixed - no updates needed
|
if (target) {
|
||||||
|
this.camera.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.camera.target) {
|
||||||
|
const targetX = this.canvas.width / 2 - this.camera.target.x * this.camera.zoom;
|
||||||
|
const targetY = this.canvas.height / 2 - this.camera.target.y * this.camera.zoom;
|
||||||
|
|
||||||
|
this.camera.x += (targetX - this.camera.x) * this.camera.smoothing;
|
||||||
|
this.camera.y += (targetY - this.camera.y) * this.camera.smoothing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyCamera() {
|
applyCamera() {
|
||||||
@ -283,22 +302,12 @@ class Renderer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawDebugInfo(gameState, players, puck, selectedPlayer = null) {
|
drawDebugInfo(gameState, players, puck) {
|
||||||
// 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;
|
if (!window.debugMode) return;
|
||||||
|
|
||||||
// Draw basic debug info overlay
|
|
||||||
this.ctx.save();
|
this.ctx.save();
|
||||||
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
|
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
|
||||||
this.ctx.fillRect(10, 10, 250, 180);
|
this.ctx.fillRect(10, 10, 200, 150);
|
||||||
|
|
||||||
this.ctx.fillStyle = '#fff';
|
this.ctx.fillStyle = '#fff';
|
||||||
this.ctx.font = '12px monospace';
|
this.ctx.font = '12px monospace';
|
||||||
@ -308,219 +317,8 @@ class Renderer {
|
|||||||
this.ctx.fillText(`Game Time: ${gameState.formatTime(gameState.timeRemaining)}`, 20, 90);
|
this.ctx.fillText(`Game Time: ${gameState.formatTime(gameState.timeRemaining)}`, 20, 90);
|
||||||
this.ctx.fillText(`Period: ${gameState.period}`, 20, 110);
|
this.ctx.fillText(`Period: ${gameState.period}`, 20, 110);
|
||||||
this.ctx.fillText(`Paused: ${gameState.isPaused}`, 20, 130);
|
this.ctx.fillText(`Paused: ${gameState.isPaused}`, 20, 130);
|
||||||
this.ctx.fillText(`Faceoff: ${gameState.faceoff.isActive}`, 20, 150);
|
this.ctx.fillText(`Speed: ${gameState.gameSpeed}x`, 20, 150);
|
||||||
this.ctx.fillText(`Puck Active: ${gameState.puckActive !== undefined ? gameState.puckActive : 'N/A'}`, 20, 170);
|
|
||||||
|
|
||||||
this.ctx.restore();
|
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3,11 +3,15 @@ class RulesSystem {
|
|||||||
this.gameState = gameState;
|
this.gameState = gameState;
|
||||||
this.lastOffsideCheck = 0;
|
this.lastOffsideCheck = 0;
|
||||||
this.lastIcingCheck = 0;
|
this.lastIcingCheck = 0;
|
||||||
|
this.penaltyQueue = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
update(players, puck, deltaTime) {
|
update(players, puck, deltaTime) {
|
||||||
this.checkOffside(players, puck);
|
this.checkOffside(players, puck);
|
||||||
this.checkIcing(players, puck);
|
this.checkIcing(players, puck);
|
||||||
|
this.checkGoaltenderInterference(players, puck);
|
||||||
|
this.checkHighSticking(players, puck);
|
||||||
|
this.processPenaltyQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
checkOffside(players, puck) {
|
checkOffside(players, puck) {
|
||||||
@ -122,6 +126,81 @@ class RulesSystem {
|
|||||||
this.scheduleFaceoff(faceoffPosition);
|
this.scheduleFaceoff(faceoffPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkGoaltenderInterference(players, puck) {
|
||||||
|
const goalies = players.filter(p => p.role === 'G');
|
||||||
|
|
||||||
|
goalies.forEach(goalie => {
|
||||||
|
const crease = this.getCreaseArea(goalie.team);
|
||||||
|
const opponents = players.filter(p =>
|
||||||
|
p.team !== goalie.team &&
|
||||||
|
this.isPlayerInCrease(p, crease)
|
||||||
|
);
|
||||||
|
|
||||||
|
opponents.forEach(opponent => {
|
||||||
|
if (opponent.velocity.magnitude() > 100) {
|
||||||
|
this.callGoaltenderInterference(opponent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
checkHighSticking(players, puck) {
|
||||||
|
if (puck.position.y < 200) {
|
||||||
|
const lastTouch = puck.lastPlayerTouch;
|
||||||
|
if (lastTouch && Math.random() < 0.1) {
|
||||||
|
this.callHighSticking(lastTouch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isPlayerInCrease(player, crease) {
|
||||||
|
return player.position.x >= crease.x &&
|
||||||
|
player.position.x <= crease.x + crease.width &&
|
||||||
|
player.position.y >= crease.y &&
|
||||||
|
player.position.y <= crease.y + crease.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCreaseArea(team) {
|
||||||
|
const goalY = this.gameState.rink.centerY;
|
||||||
|
const goalX = team === 'home' ? 50 : this.gameState.rink.width - 50;
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: goalX - 30,
|
||||||
|
y: goalY - 60,
|
||||||
|
width: 60,
|
||||||
|
height: 120
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
callGoaltenderInterference(player) {
|
||||||
|
this.queuePenalty(player.team, player.name, 'Goaltender Interference', 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
callHighSticking(player) {
|
||||||
|
this.queuePenalty(player.team, player.name, 'High Sticking', 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
queuePenalty(team, player, type, duration) {
|
||||||
|
this.penaltyQueue.push({
|
||||||
|
team,
|
||||||
|
player,
|
||||||
|
type,
|
||||||
|
duration,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
processPenaltyQueue() {
|
||||||
|
if (this.penaltyQueue.length === 0) return;
|
||||||
|
|
||||||
|
const penalty = this.penaltyQueue.shift();
|
||||||
|
this.gameState.addPenalty(
|
||||||
|
penalty.team,
|
||||||
|
penalty.player,
|
||||||
|
penalty.type,
|
||||||
|
penalty.duration
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
scheduleFaceoff(position) {
|
scheduleFaceoff(position) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -144,8 +223,34 @@ class RulesSystem {
|
|||||||
return nearest;
|
return nearest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkFighting(players) {
|
||||||
|
for (let i = 0; i < players.length; i++) {
|
||||||
|
for (let j = i + 1; j < players.length; j++) {
|
||||||
|
const player1 = players[i];
|
||||||
|
const player2 = players[j];
|
||||||
|
|
||||||
|
if (player1.team !== player2.team &&
|
||||||
|
player1.position.distance(player2.position) < 20 &&
|
||||||
|
player1.velocity.magnitude() > 150 &&
|
||||||
|
player2.velocity.magnitude() > 150 &&
|
||||||
|
Math.random() < 0.01) {
|
||||||
|
|
||||||
|
this.callFighting(player1, player2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callFighting(player1, player2) {
|
||||||
|
this.queuePenalty(player1.team, player1.name, 'Fighting', 300);
|
||||||
|
this.queuePenalty(player2.team, player2.name, 'Fighting', 300);
|
||||||
|
|
||||||
|
this.gameState.addEvent(`FIGHT - ${player1.name} vs ${player2.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.lastOffsideCheck = 0;
|
this.lastOffsideCheck = 0;
|
||||||
this.lastIcingCheck = 0;
|
this.lastIcingCheck = 0;
|
||||||
|
this.penaltyQueue = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user