From c09bc6edd5d29173b8e6bb14a8f86adcc477a82f Mon Sep 17 00:00:00 2001 From: Pierre Wessman <4029607+pierrewessman@users.noreply.github.com> Date: Tue, 16 Sep 2025 13:58:13 +0200 Subject: [PATCH] function comments --- src/entities/player.js | 186 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/src/entities/player.js b/src/entities/player.js index 2787745..7953ab2 100644 --- a/src/entities/player.js +++ b/src/entities/player.js @@ -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;