function comments

This commit is contained in:
Pierre Wessman 2025-09-16 13:58:13 +02:00
parent 4236d0098d
commit c09bc6edd5

View File

@ -1,4 +1,13 @@
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) {
this.id = id;
this.name = name;
@ -43,6 +52,13 @@ class Player {
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) {
this.updateEnergy(deltaTime);
this.updateMovement(deltaTime);
@ -55,6 +71,11 @@ 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);
@ -68,6 +89,11 @@ 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) {
const direction = this.targetPosition.subtract(this.position).normalize();
const distance = this.position.distance(this.targetPosition);
@ -91,6 +117,10 @@ class Player {
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) {
let angleDiff = this.targetAngle - this.angle;
angleDiff = Physics.wrapAngle(angleDiff);
@ -102,6 +132,13 @@ 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) {
const currentTime = Date.now();
if (currentTime - this.aiState.lastAction < this.aiState.reactionTime) {
@ -128,6 +165,14 @@ 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) {
const enemyGoal = this.team === 'home' ?
new Vector2(gameState.rink.width - 50, gameState.rink.centerY) :
@ -158,6 +203,15 @@ class Player {
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) {
const puckOwner = opponents.find(p => p.state.hasPuck) || teammates.find(p => p.state.hasPuck);
const isClosestToPuck = this.isClosestPlayerToPuck(puck, teammates);
@ -177,6 +231,13 @@ 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) {
const goal = this.team === 'home' ?
new Vector2(10, gameState.rink.centerY) :
@ -202,11 +263,22 @@ class Player {
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) {
this.moveToPosition(puck.position);
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) {
const direction = target.subtract(puck.position).normalize();
const power = this.attributes.shooting / 100 * 800;
@ -221,6 +293,13 @@ class Player {
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) {
const direction = target.position.subtract(puck.position).normalize();
const distance = puck.position.distance(target.position);
@ -232,6 +311,12 @@ class Player {
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) {
if (this.position.distance(target.position) < 30) {
target.velocity = target.velocity.add(
@ -244,11 +329,21 @@ class Player {
return false;
}
/**
* Sets the player's target position and facing angle
* @param {Vector2} target - Target position to move toward
*/
moveToPosition(target) {
this.targetPosition = target.copy();
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) {
const ownGoal = this.team === 'home' ?
new Vector2(50, gameState.rink.centerY) :
@ -259,11 +354,24 @@ class Player {
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) {
this.moveToPosition(this.getFormationPosition(gameState, puck, players));
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) {
const side = this.team === 'home' ? -1 : 1;
const rink = gameState.rink;
@ -276,6 +384,14 @@ class Player {
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) {
const homeAttackingZone = rink.width * 0.67; // Right side for home team
const awayAttackingZone = rink.width * 0.33; // Left side for away team
@ -298,6 +414,15 @@ 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) {
const centerY = rink.centerY;
const puckInfluenceX = (puck.position.x - rink.centerX) * 0.3; // Follow puck horizontally
@ -374,6 +499,11 @@ class Player {
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) {
let nearest = null;
let minDistance = Infinity;
@ -389,6 +519,12 @@ class Player {
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) {
let bestTarget = null;
let bestScore = -1;
@ -420,6 +556,13 @@ class Player {
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) {
// Check if this player (excluding goalies) is closest to the puck on their team
if (this.role === 'G' || this.state.hasPuck) return false;
@ -444,6 +587,13 @@ class 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) {
// Check if there's a clear line to goal (simplified check)
const directionToGoal = goalPosition.subtract(this.position).normalize();
@ -468,6 +618,13 @@ class Player {
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) {
// Create an intelligent path toward the goal
let targetPosition = goalPosition.copy();
@ -493,6 +650,14 @@ class Player {
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) {
const currentPos = this.position;
const adjustment = new Vector2(0, 0);
@ -531,11 +696,19 @@ class Player {
return adjustment;
}
/**
* Constrains player position to stay within rink boundaries
* Uses hardcoded rink dimensions of 1000x600
*/
keepInBounds() {
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));
}
/**
* Renders the player on the canvas with team colors, puck indicator, and role text
* @param {CanvasRenderingContext2D} ctx - 2D rendering context for drawing
*/
render(ctx) {
ctx.save();
ctx.translate(this.position.x, this.position.y);
@ -565,6 +738,12 @@ class Player {
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) {
const faceoffPos = this.getFaceoffPosition(gameState, players);
this.moveToPosition(faceoffPos);
@ -579,6 +758,13 @@ 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) {
const faceoffLocation = gameState.faceoff.location;
const side = this.team === 'home' ? -1 : 1;