diff --git a/src/entities/player.js b/src/entities/player.js index c7f167c..d33766b 100644 --- a/src/entities/player.js +++ b/src/entities/player.js @@ -134,7 +134,7 @@ class Player { /** * Main AI decision making system - handles reaction timing, faceoffs, and behavioral switching - * Delegates to specific behavior methods based on puck possession + * Delegates to specific behavior methods based on team 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 @@ -157,22 +157,93 @@ class Player { const teammates = players.filter(p => p.team === this.team && p.id !== this.id); const opponents = players.filter(p => p.team !== this.team); - if (this.state.hasPuck) { - this.behaviorWithPuck(gameState, puck, teammates, opponents); + // Determine team possession + const teammatePuckCarrier = teammates.find(p => p.state.hasPuck); + const opponentPuckCarrier = opponents.find(p => p.state.hasPuck); + const myTeamHasPuck = this.state.hasPuck || teammatePuckCarrier; + + if (myTeamHasPuck) { + this.behaviorWhenTeamHasPuck(gameState, puck, teammates, opponents, distanceToPuck); + } else if (opponentPuckCarrier) { + this.behaviorWhenOpponentHasPuck(gameState, puck, teammates, opponents, opponentPuckCarrier); } else { - this.behaviorWithoutPuck(gameState, puck, teammates, opponents, distanceToPuck); + this.behaviorWhenPuckIsLoose(gameState, puck, teammates, opponents, distanceToPuck); } } /** - * Offensive AI behavior when player has possession of the puck + * AI behavior when this player's team has possession of the puck + * Handles both when this player has the puck and when a teammate has it + * @param {Object} gameState - Current game state with rink dimensions + * @param {Object} puck - Puck object with position and velocity + * @param {Array} teammates - Array of teammate player objects + * @param {Array} opponents - Array of opposing player objects + * @param {number} distanceToPuck - Pre-calculated distance to puck + */ + behaviorWhenTeamHasPuck(gameState, puck, teammates, opponents, distanceToPuck) { + if (this.state.hasPuck) { + // This player has the puck - offensive behavior + this.offensiveBehaviorWithPuck(gameState, puck, teammates, opponents); + } else { + // Teammate has the puck - support behavior + this.supportOffensiveBehavior(gameState, puck, teammates, opponents); + } + } + + /** + * AI behavior when the opponent team has possession of the puck + * All players focus on defensive positioning and pressure + * @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 {Object} opponentPuckCarrier - The opponent player who has the puck + */ + behaviorWhenOpponentHasPuck(gameState, puck, teammates, opponents, opponentPuckCarrier) { + // Check if this player is the closest defender to the puck carrier + const isClosestDefender = this.isClosestDefenderToPuckCarrier(opponentPuckCarrier, teammates); + + if (isClosestDefender) { + // Closest defender aggressively targets the puck carrier + this.moveToPosition(opponentPuckCarrier.position); + this.aiState.behavior = 'aggressive_pressure'; + } else { + // Other players position defensively + this.defendPosition(gameState, opponentPuckCarrier); + } + } + + /** + * AI behavior when the puck is loose (no one has possession) + * Players compete to gain control while maintaining team structure + * @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 + */ + behaviorWhenPuckIsLoose(gameState, puck, teammates, opponents, distanceToPuck) { + const isClosestToPuck = this.isClosestPlayerToPuck(puck, teammates); + const allPlayers = [...teammates, ...opponents, this]; + + if (isClosestToPuck && distanceToPuck < 200) { + // Only chase if this player is closest to the puck on their team + this.chasePuck(puck); + } else { + // Maintain formation position while puck is contested + this.moveToFormationPosition(gameState, puck, allPlayers); + } + } + + /** + * Offensive behavior when this specific player has 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) { + offensiveBehaviorWithPuck(gameState, puck, teammates, opponents) { const enemyGoal = this.team === 'home' ? new Vector2(gameState.rink.width - 50, gameState.rink.centerY) : new Vector2(50, gameState.rink.centerY); @@ -203,38 +274,21 @@ class Player { } /** - * Defensive AI behavior when player doesn't have puck possession - * Chooses between chasing loose puck, defending against opponents, or maintaining formation + * Support behavior when a teammate has the puck + * Positions for passes, creates scoring opportunities, and maintains offensive 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); + supportOffensiveBehavior(gameState, puck, teammates, opponents) { const allPlayers = [...teammates, ...opponents, this]; + const puckCarrier = teammates.find(p => p.state.hasPuck); - if (!puckOwner && isClosestToPuck && distanceToPuck < 200) { - // Only chase if this player is closest to the puck on their team - this.chasePuck(puck); - } else if (puckOwner && puckOwner.team !== this.team) { - // Check if this player is the closest defender to the puck carrier - 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); - } else { - this.defendPosition(gameState, puckOwner); - } - } else { - this.moveToFormationPosition(gameState, puck, allPlayers); - } + // Move to an offensive formation position that supports the puck carrier + const supportPosition = this.getOffensiveSupportPosition(gameState, puck, puckCarrier, opponents); + this.moveToPosition(supportPosition); + this.aiState.behavior = 'offensive_support'; } /** @@ -392,6 +446,75 @@ class Player { return this.getContextualPosition(rink, side, isAttacking, puck); } + /** + * Calculates offensive support position when a teammate has the puck + * Positions player to receive passes, create scoring chances, and maintain offensive pressure + * @param {Object} gameState - Current game state with rink dimensions + * @param {Object} puck - Puck object with position + * @param {Object} puckCarrier - Teammate who has the puck + * @param {Array} opponents - Array of opposing player objects + * @returns {Vector2} Calculated offensive support position + */ + getOffensiveSupportPosition(gameState, puck, puckCarrier, opponents) { + const rink = gameState.rink; + const enemyGoal = this.team === 'home' ? + new Vector2(rink.width - 50, rink.centerY) : + new Vector2(50, rink.centerY); + + // Base position is an aggressive offensive formation + const side = this.team === 'home' ? 1 : -1; + const attackZone = this.team === 'home' ? rink.width * 0.75 : rink.width * 0.25; + + let baseX, baseY; + + switch (this.role) { + case 'C': + // Center positions for rebounds and passes + baseX = attackZone; + baseY = rink.centerY + (puck.position.y > rink.centerY ? -60 : 60); + break; + case 'LW': + // Left wing positions for cross-ice passes and wraparounds + baseX = attackZone - 30; + baseY = rink.centerY - 140; + break; + case 'RW': + // Right wing positions for cross-ice passes and wraparounds + baseX = attackZone - 30; + baseY = rink.centerY + 140; + break; + case 'LD': + // Left defense supports from the point + baseX = attackZone - 120; + baseY = rink.centerY - 100; + break; + case 'RD': + // Right defense supports from the point + baseX = attackZone - 120; + baseY = rink.centerY + 100; + break; + default: + return this.getFormationPosition(gameState, puck, [puckCarrier, ...opponents, this]); + } + + // Adjust position to avoid clustering with puck carrier + if (puckCarrier) { + const distanceToPuckCarrier = new Vector2(baseX, baseY).distance(puckCarrier.position); + if (distanceToPuckCarrier < 80) { + // Spread out from puck carrier + const awayFromCarrier = new Vector2(baseX, baseY).subtract(puckCarrier.position).normalize(); + baseX += awayFromCarrier.x * 50; + baseY += awayFromCarrier.y * 50; + } + } + + // Keep positions within rink bounds + baseX = Math.max(50, Math.min(rink.width - 50, baseX)); + baseY = Math.max(50, Math.min(rink.height - 50, baseY)); + + return new Vector2(baseX, baseY); + } + /** * Determines if this player's team is in attacking or defending mode * Based on puck possession and puck location on the rink