AI
This commit is contained in:
parent
7cc0950a84
commit
961dcc3a25
@ -141,25 +141,36 @@ class Player {
|
||||
|
||||
const nearestOpponent = this.findNearestPlayer(opponents);
|
||||
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
|
||||
if (distanceToGoal < 250 && this.hasGoodShootingAngle(enemyGoal, opponents)) {
|
||||
if (distanceToGoal < 150 || Math.random() < 0.5) {
|
||||
this.shoot(puck, enemyGoal);
|
||||
} else if (nearestOpponent && this.position.distance(nearestOpponent.position) < 80) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If under heavy pressure, look for a pass first
|
||||
if (distanceToNearestOpponent < 60) {
|
||||
const bestTeammate = this.findBestPassTarget(teammates, opponents);
|
||||
if (bestTeammate && Math.random() < 0.7) {
|
||||
if (bestTeammate && Math.random() < 0.8) {
|
||||
this.pass(puck, bestTeammate);
|
||||
} else {
|
||||
this.moveToPosition(enemyGoal);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.moveToPosition(enemyGoal);
|
||||
}
|
||||
|
||||
// Default behavior: advance aggressively toward goal
|
||||
this.advanceTowardGoal(enemyGoal, opponents, gameState.rink);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
} else if (puckOwner && puckOwner.team !== this.team) {
|
||||
if (distanceToPuck < 150 && Math.random() < 0.2) {
|
||||
@ -168,7 +179,7 @@ class Player {
|
||||
this.defendPosition(gameState, puckOwner);
|
||||
}
|
||||
} else {
|
||||
this.moveToFormationPosition(gameState);
|
||||
this.moveToFormationPosition(gameState, puck, allPlayers);
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,30 +265,119 @@ class Player {
|
||||
this.aiState.behavior = 'defending';
|
||||
}
|
||||
|
||||
moveToFormationPosition(gameState) {
|
||||
this.moveToPosition(this.getFormationPosition(gameState));
|
||||
moveToFormationPosition(gameState, puck, players) {
|
||||
this.moveToPosition(this.getFormationPosition(gameState, puck, players));
|
||||
this.aiState.behavior = 'formation';
|
||||
}
|
||||
|
||||
getFormationPosition(gameState) {
|
||||
getFormationPosition(gameState, puck, players) {
|
||||
const side = this.team === 'home' ? -1 : 1;
|
||||
const centerX = gameState.rink.centerX;
|
||||
const centerY = gameState.rink.centerY;
|
||||
const rink = gameState.rink;
|
||||
|
||||
// Determine if team is attacking or defending based on puck position and possession
|
||||
const puckOwner = players.find(p => p.state.hasPuck);
|
||||
const isAttacking = this.determineTeamState(puck, puckOwner, rink);
|
||||
|
||||
// Get base formation position based on attacking/defending state
|
||||
return this.getContextualPosition(rink, side, isAttacking, puck);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// 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':
|
||||
return new Vector2(centerX + side * 100, centerY);
|
||||
baseX = attackZone;
|
||||
baseY = centerY;
|
||||
break;
|
||||
case 'LW':
|
||||
return new Vector2(centerX + side * 150, centerY - 100);
|
||||
baseX = attackZone - 50;
|
||||
baseY = centerY - 120;
|
||||
break;
|
||||
case 'RW':
|
||||
return new Vector2(centerX + side * 150, centerY + 100);
|
||||
baseX = attackZone - 50;
|
||||
baseY = centerY + 120;
|
||||
break;
|
||||
case 'LD':
|
||||
return new Vector2(centerX + side * 200, centerY - 80);
|
||||
baseX = attackZone - 150;
|
||||
baseY = centerY - 80;
|
||||
break;
|
||||
case 'RD':
|
||||
return new Vector2(centerX + side * 200, centerY + 80);
|
||||
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) {
|
||||
@ -326,6 +426,117 @@ class Player {
|
||||
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() {
|
||||
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));
|
||||
|
||||
@ -37,6 +37,10 @@ class AISystem {
|
||||
const puckOwner = this.findPuckOwner(players);
|
||||
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 => {
|
||||
if (player.role !== 'G') {
|
||||
this.updatePlayerAI(player, gameContext);
|
||||
@ -48,6 +52,31 @@ class AISystem {
|
||||
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) {
|
||||
const context = {
|
||||
puckOwner,
|
||||
@ -151,6 +180,7 @@ class AISystem {
|
||||
updatePlayerBehavior(player, context) {
|
||||
const distanceToPuck = player.position.distance(context.puckPosition);
|
||||
const isNearPuck = distanceToPuck < 100;
|
||||
const isClosestToPuck = context.closestPlayers[player.team] === player;
|
||||
|
||||
if (player.state.hasPuck) {
|
||||
player.aiState.behavior = 'puck_carrier';
|
||||
@ -161,7 +191,8 @@ class AISystem {
|
||||
} else if (context.puckOwner && context.puckOwner.team !== player.team) {
|
||||
player.aiState.behavior = 'pressure';
|
||||
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';
|
||||
this.executeChaseBehavior(player, context);
|
||||
} else {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user