This commit is contained in:
Pierre Wessman 2025-09-16 12:54:41 +02:00
parent 7cc0950a84
commit 961dcc3a25
2 changed files with 273 additions and 31 deletions

View File

@ -141,25 +141,36 @@ class Player {
const nearestOpponent = this.findNearestPlayer(opponents); const nearestOpponent = this.findNearestPlayer(opponents);
const distanceToGoal = this.position.distance(enemyGoal); const distanceToGoal = this.position.distance(enemyGoal);
const distanceToNearestOpponent = nearestOpponent ? this.position.distance(nearestOpponent.position) : Infinity;
if (distanceToGoal < 200 && Math.random() < 0.3) { // More aggressive shooting when in scoring position
this.shoot(puck, enemyGoal); if (distanceToGoal < 250 && this.hasGoodShootingAngle(enemyGoal, opponents)) {
} else if (nearestOpponent && this.position.distance(nearestOpponent.position) < 80) { if (distanceToGoal < 150 || Math.random() < 0.5) {
const bestTeammate = this.findBestPassTarget(teammates, opponents); this.shoot(puck, enemyGoal);
if (bestTeammate && Math.random() < 0.7) { return;
this.pass(puck, bestTeammate);
} else {
this.moveToPosition(enemyGoal);
} }
} else {
this.moveToPosition(enemyGoal);
} }
// If under heavy pressure, look for a pass first
if (distanceToNearestOpponent < 60) {
const bestTeammate = this.findBestPassTarget(teammates, opponents);
if (bestTeammate && Math.random() < 0.8) {
this.pass(puck, bestTeammate);
return;
}
}
// Default behavior: advance aggressively toward goal
this.advanceTowardGoal(enemyGoal, opponents, gameState.rink);
} }
behaviorWithoutPuck(gameState, puck, teammates, opponents, distanceToPuck) { behaviorWithoutPuck(gameState, puck, teammates, opponents, distanceToPuck) {
const puckOwner = opponents.find(p => p.state.hasPuck) || teammates.find(p => p.state.hasPuck); 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];
if (!puckOwner && distanceToPuck < 200) { if (!puckOwner && isClosestToPuck && distanceToPuck < 200) {
// Only chase if this player is closest to the puck on their team
this.chasePuck(puck); this.chasePuck(puck);
} else if (puckOwner && puckOwner.team !== this.team) { } else if (puckOwner && puckOwner.team !== this.team) {
if (distanceToPuck < 150 && Math.random() < 0.2) { if (distanceToPuck < 150 && Math.random() < 0.2) {
@ -168,7 +179,7 @@ class Player {
this.defendPosition(gameState, puckOwner); this.defendPosition(gameState, puckOwner);
} }
} else { } else {
this.moveToFormationPosition(gameState); this.moveToFormationPosition(gameState, puck, allPlayers);
} }
} }
@ -254,30 +265,119 @@ class Player {
this.aiState.behavior = 'defending'; this.aiState.behavior = 'defending';
} }
moveToFormationPosition(gameState) { moveToFormationPosition(gameState, puck, players) {
this.moveToPosition(this.getFormationPosition(gameState)); this.moveToPosition(this.getFormationPosition(gameState, puck, players));
this.aiState.behavior = 'formation'; this.aiState.behavior = 'formation';
} }
getFormationPosition(gameState) { getFormationPosition(gameState, puck, players) {
const side = this.team === 'home' ? -1 : 1; const side = this.team === 'home' ? -1 : 1;
const centerX = gameState.rink.centerX; const rink = gameState.rink;
const centerY = gameState.rink.centerY;
switch (this.role) { // Determine if team is attacking or defending based on puck position and possession
case 'C': const puckOwner = players.find(p => p.state.hasPuck);
return new Vector2(centerX + side * 100, centerY); const isAttacking = this.determineTeamState(puck, puckOwner, rink);
case 'LW':
return new Vector2(centerX + side * 150, centerY - 100); // Get base formation position based on attacking/defending state
case 'RW': return this.getContextualPosition(rink, side, isAttacking, puck);
return new Vector2(centerX + side * 150, centerY + 100); }
case 'LD':
return new Vector2(centerX + side * 200, centerY - 80); determineTeamState(puck, puckOwner, rink) {
case 'RD': const homeAttackingZone = rink.width * 0.67; // Right side for home team
return new Vector2(centerX + side * 200, centerY + 80); const awayAttackingZone = rink.width * 0.33; // Left side for away team
default:
return this.homePosition; // If teammate has puck, team is likely attacking
if (puckOwner && puckOwner.team === this.team) {
return true;
} }
// If opponent has puck, team is defending
if (puckOwner && puckOwner.team !== this.team) {
return false;
}
// No possession - determine by puck location
if (this.team === 'home') {
return puck.position.x > homeAttackingZone;
} else {
return puck.position.x < awayAttackingZone;
}
}
getContextualPosition(rink, side, isAttacking, puck) {
const centerY = rink.centerY;
const puckInfluenceX = (puck.position.x - rink.centerX) * 0.3; // Follow puck horizontally
const puckInfluenceY = (puck.position.y - centerY) * 0.2; // Follow puck vertically (less influence)
let baseX, baseY;
if (isAttacking) {
// Attacking formation - push forward toward opponent's goal
const attackZone = this.team === 'home' ? rink.width * 0.75 : rink.width * 0.25;
switch (this.role) {
case 'C':
baseX = attackZone;
baseY = centerY;
break;
case 'LW':
baseX = attackZone - 50;
baseY = centerY - 120;
break;
case 'RW':
baseX = attackZone - 50;
baseY = centerY + 120;
break;
case 'LD':
baseX = attackZone - 150;
baseY = centerY - 80;
break;
case 'RD':
baseX = attackZone - 150;
baseY = centerY + 80;
break;
default:
return this.homePosition;
}
} else {
// Defensive formation - fall back toward own goal
const defenseZone = this.team === 'home' ? rink.width * 0.25 : rink.width * 0.75;
switch (this.role) {
case 'C':
baseX = defenseZone;
baseY = centerY;
break;
case 'LW':
baseX = defenseZone + side * 50;
baseY = centerY - 100;
break;
case 'RW':
baseX = defenseZone + side * 50;
baseY = centerY + 100;
break;
case 'LD':
baseX = defenseZone + side * 100;
baseY = centerY - 60;
break;
case 'RD':
baseX = defenseZone + side * 100;
baseY = centerY + 60;
break;
default:
return this.homePosition;
}
}
// Apply puck influence for more dynamic positioning
baseX += puckInfluenceX;
baseY += puckInfluenceY;
// 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);
} }
findNearestPlayer(players) { findNearestPlayer(players) {
@ -326,6 +426,117 @@ class Player {
return bestTarget; return bestTarget;
} }
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;
const myDistance = this.position.distance(puck.position);
// Include self in the list to compare against
const allTeamPlayers = [this, ...teammates.filter(t => t.role !== 'G' && !t.state.hasPuck)];
// Find the closest player
let closestDistance = Infinity;
let closestPlayer = null;
allTeamPlayers.forEach(player => {
const distance = player.position.distance(puck.position);
if (distance < closestDistance) {
closestDistance = distance;
closestPlayer = player;
}
});
return closestPlayer === this;
}
hasGoodShootingAngle(goalPosition, opponents) {
// Check if there's a clear line to goal (simplified check)
const directionToGoal = goalPosition.subtract(this.position).normalize();
// Check if opponents are blocking the shot
for (let opponent of opponents) {
const directionToOpponent = opponent.position.subtract(this.position);
const distanceToOpponent = directionToOpponent.magnitude();
// Skip distant opponents
if (distanceToOpponent > 150) continue;
const directionToOpponentNorm = directionToOpponent.normalize();
const dot = directionToGoal.dot(directionToOpponentNorm);
// If opponent is roughly in line with goal and close enough to block
if (dot > 0.8 && distanceToOpponent < 80) {
return false;
}
}
return true;
}
advanceTowardGoal(goalPosition, opponents, rink) {
// Create an intelligent path toward the goal
let targetPosition = goalPosition.copy();
// Adjust approach based on position and opponents
const directionToGoal = goalPosition.subtract(this.position).normalize();
const distanceToGoal = this.position.distance(goalPosition);
// If close to goal, move more directly
if (distanceToGoal < 200) {
this.moveToPosition(goalPosition);
return;
}
// Look for the best path around opponents
const pathAdjustment = this.findBestPathToGoal(goalPosition, opponents, rink);
targetPosition = targetPosition.add(pathAdjustment);
// Keep target in bounds
targetPosition.x = Math.max(50, Math.min(rink.width - 50, targetPosition.x));
targetPosition.y = Math.max(50, Math.min(rink.height - 50, targetPosition.y));
this.moveToPosition(targetPosition);
}
findBestPathToGoal(goalPosition, opponents, rink) {
const currentPos = this.position;
const adjustment = new Vector2(0, 0);
// Check for opponents blocking direct path
const directPath = goalPosition.subtract(currentPos).normalize();
opponents.forEach(opponent => {
const toOpponent = opponent.position.subtract(currentPos);
const distanceToOpponent = toOpponent.magnitude();
// Only consider opponents that might interfere
if (distanceToOpponent > 120 || distanceToOpponent < 30) return;
const toOpponentNorm = toOpponent.normalize();
const dot = directPath.dot(toOpponentNorm);
// If opponent is somewhat in the path
if (dot > 0.5) {
// Calculate avoidance vector (perpendicular to opponent direction)
const avoidVector = new Vector2(-toOpponentNorm.y, toOpponentNorm.x);
const influence = (120 - distanceToOpponent) / 120; // Stronger influence when closer
// Choose direction based on field position to avoid going out of bounds
if (currentPos.y < rink.height / 2) {
adjustment.y += Math.abs(avoidVector.y) * influence * 30;
} else {
adjustment.y -= Math.abs(avoidVector.y) * influence * 30;
}
// Slight lateral adjustment
adjustment.x += avoidVector.x * influence * 15;
}
});
return adjustment;
}
keepInBounds() { keepInBounds() {
this.position.x = Math.max(this.radius, Math.min(1000 - this.radius, this.position.x)); 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)); this.position.y = Math.max(this.radius, Math.min(600 - this.radius, this.position.y));

View File

@ -37,6 +37,10 @@ class AISystem {
const puckOwner = this.findPuckOwner(players); const puckOwner = this.findPuckOwner(players);
const gameContext = this.analyzeGameContext(players, puck, gameState, puckOwner); const gameContext = this.analyzeGameContext(players, puck, gameState, puckOwner);
// Find closest player to puck for each team (excluding goalies)
const closestPlayers = this.findClosestPlayersByTeam(players, puck);
gameContext.closestPlayers = closestPlayers;
players.forEach(player => { players.forEach(player => {
if (player.role !== 'G') { if (player.role !== 'G') {
this.updatePlayerAI(player, gameContext); this.updatePlayerAI(player, gameContext);
@ -48,6 +52,31 @@ class AISystem {
return players.find(player => player.state.hasPuck) || null; return players.find(player => player.state.hasPuck) || null;
} }
findClosestPlayersByTeam(players, puck) {
const homeClosest = { player: null, distance: Infinity };
const awayClosest = { player: null, distance: Infinity };
players.forEach(player => {
// Skip goalies and players with the puck
if (player.role === 'G' || player.state.hasPuck) return;
const distance = player.position.distance(puck.position);
if (player.team === 'home' && distance < homeClosest.distance) {
homeClosest.player = player;
homeClosest.distance = distance;
} else if (player.team === 'away' && distance < awayClosest.distance) {
awayClosest.player = player;
awayClosest.distance = distance;
}
});
return {
home: homeClosest.player,
away: awayClosest.player
};
}
analyzeGameContext(players, puck, gameState, puckOwner) { analyzeGameContext(players, puck, gameState, puckOwner) {
const context = { const context = {
puckOwner, puckOwner,
@ -151,6 +180,7 @@ class AISystem {
updatePlayerBehavior(player, context) { updatePlayerBehavior(player, context) {
const distanceToPuck = player.position.distance(context.puckPosition); const distanceToPuck = player.position.distance(context.puckPosition);
const isNearPuck = distanceToPuck < 100; const isNearPuck = distanceToPuck < 100;
const isClosestToPuck = context.closestPlayers[player.team] === player;
if (player.state.hasPuck) { if (player.state.hasPuck) {
player.aiState.behavior = 'puck_carrier'; player.aiState.behavior = 'puck_carrier';
@ -161,7 +191,8 @@ class AISystem {
} else if (context.puckOwner && context.puckOwner.team !== player.team) { } else if (context.puckOwner && context.puckOwner.team !== player.team) {
player.aiState.behavior = 'pressure'; player.aiState.behavior = 'pressure';
this.executePressureBehavior(player, context); this.executePressureBehavior(player, context);
} else if (isNearPuck) { } else if (!context.puckOwner && isClosestToPuck && isNearPuck) {
// Only allow the closest player to chase loose pucks
player.aiState.behavior = 'chase'; player.aiState.behavior = 'chase';
this.executeChaseBehavior(player, context); this.executeChaseBehavior(player, context);
} else { } else {