Restructure player AI from individual to

This commit is contained in:
Pierre Wessman 2025-09-18 20:27:23 +02:00
parent af1cbf8110
commit f7cec84973

View File

@ -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