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 * 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} gameState - Current game state including faceoff 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
@ -157,22 +157,93 @@ class Player {
const teammates = players.filter(p => p.team === this.team && p.id !== this.id); const teammates = players.filter(p => p.team === this.team && p.id !== this.id);
const opponents = players.filter(p => p.team !== this.team); const opponents = players.filter(p => p.team !== this.team);
if (this.state.hasPuck) { // Determine team possession
this.behaviorWithPuck(gameState, puck, teammates, opponents); 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 { } 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 * Prioritizes shooting, then passing under pressure, then advancing toward goal
* @param {Object} gameState - Current game state with rink dimensions * @param {Object} gameState - Current game state with rink dimensions
* @param {Object} puck - Puck object to shoot or pass * @param {Object} puck - Puck object to shoot or pass
* @param {Array} teammates - Array of teammate player objects * @param {Array} teammates - Array of teammate player objects
* @param {Array} opponents - Array of opposing 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' ? const enemyGoal = this.team === 'home' ?
new Vector2(gameState.rink.width - 50, gameState.rink.centerY) : new Vector2(gameState.rink.width - 50, gameState.rink.centerY) :
new Vector2(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 * Support behavior when a teammate has the puck
* Chooses between chasing loose puck, defending against opponents, or maintaining formation * Positions for passes, creates scoring opportunities, and maintains offensive formation
* @param {Object} gameState - Current game state * @param {Object} gameState - Current game state
* @param {Object} puck - Puck object with position * @param {Object} puck - Puck object with position
* @param {Array} teammates - Array of teammate player objects * @param {Array} teammates - Array of teammate player objects
* @param {Array} opponents - Array of opposing player objects * @param {Array} opponents - Array of opposing player objects
* @param {number} distanceToPuck - Pre-calculated distance to puck
*/ */
behaviorWithoutPuck(gameState, puck, teammates, opponents, distanceToPuck) { supportOffensiveBehavior(gameState, puck, teammates, opponents) {
const puckOwner = opponents.find(p => p.state.hasPuck) || teammates.find(p => p.state.hasPuck);
const isClosestToPuck = this.isClosestPlayerToPuck(puck, teammates);
const allPlayers = [...teammates, ...opponents, this]; const allPlayers = [...teammates, ...opponents, this];
const puckCarrier = teammates.find(p => p.state.hasPuck);
if (!puckOwner && isClosestToPuck && distanceToPuck < 200) { // Move to an offensive formation position that supports the puck carrier
// Only chase if this player is closest to the puck on their team const supportPosition = this.getOffensiveSupportPosition(gameState, puck, puckCarrier, opponents);
this.chasePuck(puck); this.moveToPosition(supportPosition);
} else if (puckOwner && puckOwner.team !== this.team) { this.aiState.behavior = 'offensive_support';
// 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);
}
} }
/** /**
@ -392,6 +446,75 @@ class Player {
return this.getContextualPosition(rink, side, isAttacking, puck); 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 * Determines if this player's team is in attacking or defending mode
* Based on puck possession and puck location on the rink * Based on puck possession and puck location on the rink