remove energy mechanism

This commit is contained in:
Pierre Wessman 2025-09-19 10:48:24 +02:00
parent 89b473aecf
commit 4bf182c2a9
6 changed files with 6 additions and 363 deletions

View File

@ -2,7 +2,9 @@
"permissions": { "permissions": {
"allow": [ "allow": [
"Bash(find:*)", "Bash(find:*)",
"Bash(node:*)" "Bash(node:*)",
"Bash(git add:*)",
"Bash(git commit:*)"
], ],
"deny": [], "deny": [],
"ask": [] "ask": []

View File

@ -1,319 +0,0 @@
# Player AI System Documentation
This document provides a comprehensive overview of the AI decision-making system for hockey players in the game, including detailed flowcharts showing how player states and behaviors are determined.
## Overview
The Player AI system uses a sophisticated Entity-Component-System (ECS) pattern with context-aware decision making. Each player has multiple layers of state management:
- **Physical State**: Position, velocity, energy, puck possession
- **AI State**: Current behavior, target, reaction timing
- **Game Context**: Team possession, puck location, game situation
## Core AI Decision Flow
```mermaid
flowchart TD
A[Player Update Called] --> B{Is Goalie?}
B -->|Yes| C[Update Goalie AI]
B -->|No| D[Check Reaction Time]
D --> E{Reaction Time Met?}
E -->|No| F[Skip AI Update]
E -->|Yes| G{Is Faceoff Active?}
G -->|Yes| H[Handle Faceoff Positioning]
G -->|No| I[Determine Puck Possession]
I --> J{Who Has Puck?}
J -->|My Team| K[Team Has Puck Behavior]
J -->|Opponent Team| L[Opponent Has Puck Behavior]
J -->|Loose Puck| M[Loose Puck Behavior]
C --> N[Position in Crease]
H --> O[Move to Faceoff Position]
K --> P[Offensive Strategy]
L --> Q[Defensive Strategy]
M --> R[Puck Competition]
F --> S[End Update]
N --> S
O --> S
P --> S
Q --> S
R --> S
```
## Team Possession States
### When Team Has Puck
```mermaid
flowchart TD
A[Team Has Puck] --> B{Do I Have Puck?}
B -->|Yes| C[Offensive Behavior With Puck]
B -->|No| D[Support Offensive Behavior]
C --> E{Distance to Goal < 250?}
E -->|Yes| F{Good Shooting Angle?}
F -->|Yes| G{Distance < 150 OR Random < 0.5?}
G -->|Yes| H[SHOOT]
G -->|No| I[Consider Pass]
F -->|No| I
E -->|No| I
I --> J{Teammate Closer to Goal?}
J -->|Yes| K{Random < 0.7?}
K -->|Yes| L[PASS to Closer Teammate]
K -->|No| M[Check Pressure]
J -->|No| M
M --> N{Opponent Distance < 100?}
N -->|Yes| O{Find Good Pass Target?}
O -->|Yes| P{Random < 0.8?}
P -->|Yes| Q[PASS to Best Target]
P -->|No| R[ADVANCE to Goal]
O -->|No| R
N -->|No| R
D --> S[Calculate Support Position]
S --> T[Move to Support Position]
H --> U[End Turn]
L --> U
Q --> U
R --> U
T --> U
```
### When Opponent Has Puck
```mermaid
flowchart TD
A[Opponent Has Puck] --> B{Am I Closest Defender?}
B -->|Yes| C[Aggressive Pressure]
B -->|No| D[Defensive Positioning]
C --> E[Target Puck Carrier]
C --> F[Set Behavior: aggressive_pressure]
D --> G[Calculate Defend Position]
D --> H[Set Behavior: defending]
E --> I[End Turn]
F --> I
G --> I
H --> I
```
### When Puck is Loose
```mermaid
flowchart TD
A[Puck is Loose] --> B{Am I Closest to Puck?}
B -->|Yes| C{Distance < 200?}
C -->|Yes| D[Chase Puck]
C -->|No| E[Formation Position]
B -->|No| E
D --> F[Set Behavior: chasing]
E --> G[Calculate Formation Position]
E --> H[Set Behavior: formation]
F --> I[End Turn]
G --> I
H --> I
```
## Formation System
### Team State Determination
```mermaid
flowchart TD
A[Determine Team State] --> B{Does Teammate Have Puck?}
B -->|Yes| C[ATTACKING]
B -->|No| D{Does Opponent Have Puck?}
D -->|Yes| E[DEFENDING]
D -->|No| F[Check Puck Location]
F --> G{Team Side}
G -->|Home| H{Puck X > 67% Width?}
G -->|Away| I{Puck X < 33% Width?}
H -->|Yes| C
H -->|No| E
I -->|Yes| C
I -->|No| E
```
### Position Calculation by Role
```mermaid
flowchart TD
A[Calculate Position] --> B{Team State}
B -->|Attacking| C[Attacking Formation]
B -->|Defending| D[Defending Formation]
C --> E{Player Role}
E -->|Center| F[Attack Zone, Center Y]
E -->|Left Wing| G[Attack Zone - 50, Center Y - 120]
E -->|Right Wing| H[Attack Zone - 50, Center Y + 120]
E -->|Left Defense| I[Attack Zone - 150, Center Y - 80]
E -->|Right Defense| J[Attack Zone - 150, Center Y + 80]
D --> K{Player Role}
K -->|Center| L[Defense Zone, Center Y]
K -->|Left Wing| M[Defense Zone + Side*50, Center Y - 100]
K -->|Right Wing| N[Defense Zone + Side*50, Center Y + 100]
K -->|Left Defense| O[Defense Zone + Side*100, Center Y - 60]
K -->|Right Defense| P[Defense Zone + Side*100, Center Y + 60]
F --> Q[Apply Puck Influence]
G --> Q
H --> Q
I --> Q
J --> Q
L --> Q
M --> Q
N --> Q
O --> Q
P --> Q
Q --> R[Clamp to Rink Bounds]
R --> S[Final Position]
```
## Decision Making Algorithms
### Pass Target Selection
```mermaid
flowchart TD
A[Find Best Pass Target] --> B[Loop Through Teammates]
B --> C{Distance 50-300?}
C -->|No| D[Skip Teammate]
C -->|Yes| E[Check Pass Blocking]
E --> F[Loop Through Opponents]
F --> G{Opponent Blocks Pass Line?}
G -->|Yes| H[Mark as Blocked]
G -->|No| I[Continue Check]
I --> J{More Opponents?}
J -->|Yes| F
J -->|No| K{Pass Blocked?}
K -->|Yes| D
K -->|No| L[Calculate Score]
L --> M[Score = Skill / Distance]
M --> N{Best Score So Far?}
N -->|Yes| O[Update Best Target]
N -->|No| P[Continue]
D --> Q{More Teammates?}
P --> Q
O --> Q
Q -->|Yes| B
Q -->|No| R[Return Best Target]
H --> D
```
### Shooting Decision
```mermaid
flowchart TD
A[Check Shooting Angle] --> B[Calculate Direction to Goal]
B --> C[Loop Through Opponents]
C --> D{Opponent Distance < 150?}
D -->|No| E[Skip Opponent]
D -->|Yes| F[Calculate Direction to Opponent]
F --> G[Calculate Dot Product]
G --> H{Dot > 0.8 AND Distance < 80?}
H -->|Yes| I[Shot Blocked]
H -->|No| J{More Opponents?}
E --> J
J -->|Yes| C
J -->|No| K[Clear Shot]
I --> L[Return False]
K --> M[Return True]
```
## Goalie AI System
```mermaid
flowchart TD
A[Goalie Update] --> B[Calculate Goal Position]
B --> C[Define Crease Area]
C --> D{Puck Distance < 80?}
D -->|Yes| E[Aggressive Position]
D -->|No| F[Standard Position]
E --> G[Lerp Between Puck and Goal (0.3)]
F --> H[Goal + Offset Based on Puck Y]
G --> I[Clamp to Crease Bounds]
H --> I
I --> J[Set Target Position]
```
## Faceoff Positioning
```mermaid
flowchart TD
A[Faceoff Active] --> B{Player Role}
B -->|Center| C[Position at Faceoff Dot]
B -->|Left Wing| D[Outside Circle + Buffer, Left Side]
B -->|Right Wing| E[Outside Circle + Buffer, Right Side]
B -->|Left Defense| F[Well Outside, Left Side]
B -->|Right Defense| G[Well Outside, Right Side]
B -->|Goalie| H[Stay in Net]
C --> I[Set as Faceoff Participant]
D --> J[Legal Position Outside Circle]
E --> J
F --> J
G --> J
H --> J
I --> K[Set Behavior: faceoff]
J --> K
```
## AI Behavior States
The AI system uses several behavior states to track what each player is currently doing:
- **`aggressive_pressure`**: Actively pursuing the puck carrier
- **`defending`**: Positioning defensively between opponent and goal
- **`chasing`**: Moving directly toward loose puck
- **`formation`**: Moving to calculated formation position
- **`offensive_support`**: Supporting teammate with puck
- **`checking`**: Performing body check on opponent
- **`faceoff`**: Positioned for faceoff situation
## Key AI Features
### Reaction Time System
- Each player has a unique reaction time (50-150ms)
- Prevents all players from reacting simultaneously
- Creates more realistic, staggered decision making
### Energy Management
- Energy affects maximum speed
- Players with low energy (< 20) move 30% slower
- Energy recovers when moving slowly
- Creates realistic fatigue simulation
### Contextual Decision Making
- Decisions based on multiple factors: distance, player attributes, game situation
- Probabilistic choices add unpredictability
- Different strategies for different scenarios
### Formation Intelligence
- Dynamic formations based on puck possession
- Role-specific positioning (forwards vs defensemen)
- Puck influence affects positioning slightly
- Maintains team structure while allowing individual decisions
This AI system creates realistic hockey gameplay with players that react intelligently to game situations while maintaining team strategy and individual characteristics.

View File

@ -289,7 +289,6 @@ class GameEngine {
player.position = player.homePosition.copy(); player.position = player.homePosition.copy();
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.aiState.lastAction = 0; player.aiState.lastAction = 0;
}); });

View File

@ -35,7 +35,6 @@ class Player {
this.state = { this.state = {
hasPuck: false, hasPuck: false,
energy: 100,
checking: false, checking: false,
injured: false injured: false
}; };
@ -53,14 +52,13 @@ class Player {
} }
/** /**
* Main update loop for the player - handles energy, movement, rotation, and AI behavior * Main update loop for the player - handles movement, rotation, and AI behavior
* @param {number} deltaTime - Time elapsed since last frame in seconds * @param {number} deltaTime - Time elapsed since last frame in seconds
* @param {Object} gameState - Current game state including rink dimensions and game status * @param {Object} gameState - Current game state including rink dimensions and game status
* @param {Object} puck - Puck object with position and velocity * @param {Object} puck - Puck object with position and velocity
* @param {Array} players - Array of all players on the ice * @param {Array} players - Array of all players on the ice
*/ */
update(deltaTime, gameState, puck, players) { update(deltaTime, gameState, puck, players) {
this.updateEnergy(deltaTime);
this.updateMovement(deltaTime); this.updateMovement(deltaTime);
this.updateAngle(deltaTime); this.updateAngle(deltaTime);
@ -71,24 +69,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) {
const energyDrain = this.velocity.magnitude() / this.maxSpeed * 10 * deltaTime;
this.state.energy = Math.max(0, this.state.energy - energyDrain);
if (this.state.energy < 20) {
this.maxSpeed *= 0.7;
}
if (this.velocity.magnitude() < 50) {
this.state.energy = Math.min(100, this.state.energy + 15 * deltaTime);
}
}
/** /**
* Updates player physics including movement toward target, velocity limits, and collision bounds * Updates player physics including movement toward target, velocity limits, and collision bounds
* Applies acceleration toward target position with deceleration when close * Applies acceleration toward target position with deceleration when close
@ -106,8 +86,7 @@ class Player {
this.velocity = this.velocity.multiply(0.8); this.velocity = this.velocity.multiply(0.8);
} }
const speedMultiplier = Math.min(1, this.state.energy / 100); this.velocity = this.velocity.limit(this.maxSpeed);
this.velocity = this.velocity.limit(this.maxSpeed * speedMultiplier);
// Reduced friction for more responsive movement // Reduced friction for more responsive movement
this.velocity = Physics.applyFriction(this.velocity, 2, deltaTime); this.velocity = Physics.applyFriction(this.velocity, 2, deltaTime);

View File

@ -216,13 +216,11 @@ class DebugSystem {
// Update dynamic content only // Update dynamic content only
const coordsElement = playerDiv.querySelector('.debug-coords'); const coordsElement = playerDiv.querySelector('.debug-coords');
const speedElement = playerDiv.querySelector('.debug-speed'); const speedElement = playerDiv.querySelector('.debug-speed');
const energyElement = playerDiv.querySelector('.debug-energy');
const puckElement = playerDiv.querySelector('.debug-puck'); const puckElement = playerDiv.querySelector('.debug-puck');
const behaviorElement = playerDiv.querySelector('.debug-behavior'); const behaviorElement = playerDiv.querySelector('.debug-behavior');
if (coordsElement) coordsElement.textContent = `Pos: (${player.position.x.toFixed(0)}, ${player.position.y.toFixed(0)})`; 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 (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 (puckElement) puckElement.textContent = player.state.hasPuck;
if (behaviorElement) behaviorElement.textContent = player.aiState.behavior; if (behaviorElement) behaviorElement.textContent = player.aiState.behavior;
}); });
@ -239,7 +237,6 @@ class DebugSystem {
<div class="debug-player-info"> <div class="debug-player-info">
<div class="debug-coords">Pos: (${player.position.x.toFixed(0)}, ${player.position.y.toFixed(0)})</div> <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>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>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>Behavior: <span class="debug-value debug-behavior">${player.aiState.behavior}</span></div>
</div> </div>
@ -315,10 +312,6 @@ class DebugSystem {
<span class="debug-attribute-name">Has Puck:</span> <span class="debug-attribute-name">Has Puck:</span>
<span class="debug-attribute-value">${player.state.hasPuck}</span> <span class="debug-attribute-value">${player.state.hasPuck}</span>
</div> </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"> <div class="debug-attribute">
<span class="debug-attribute-name">Checking:</span> <span class="debug-attribute-name">Checking:</span>
<span class="debug-attribute-value">${player.state.checking}</span> <span class="debug-attribute-value">${player.state.checking}</span>

View File

@ -372,21 +372,10 @@ class Renderer {
const x = player.position.x; const x = player.position.x;
const y = player.position.y - player.radius - 5; const y = player.position.y - player.radius - 5;
// Draw player ID and energy // Draw player ID
this.ctx.fillStyle = player.team === 'home' ? '#ff4444' : '#4444ff'; this.ctx.fillStyle = player.team === 'home' ? '#ff4444' : '#4444ff';
this.ctx.fillText(`${player.role}`, x - 10, y); 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 // Highlight puck carrier
if (player.state.hasPuck) { if (player.state.hasPuck) {
this.ctx.strokeStyle = '#ffff00'; this.ctx.strokeStyle = '#ffff00';