class Player { constructor(id, name, team, position, x, y) { this.id = id; this.name = name; this.team = team; // 'home' or 'away' this.position = new Vector2(x, y); this.velocity = new Vector2(0, 0); this.targetPosition = new Vector2(x, y); this.role = position; // 'LW', 'C', 'RW', 'LD', 'RD', 'G' this.radius = position === 'G' ? 20 : 12; this.mass = position === 'G' ? 2 : 1; this.maxSpeed = position === 'G' ? 150 : 200; this.acceleration = 800; this.restitution = 0.8; this.attributes = { speed: Math.random() * 20 + 70, shooting: Math.random() * 20 + 70, passing: Math.random() * 20 + 70, defense: Math.random() * 20 + 70, checking: Math.random() * 20 + 70, puckHandling: Math.random() * 20 + 70, awareness: Math.random() * 20 + 70 }; this.state = { hasPuck: false, energy: 100, checking: false, penaltyTime: 0, injured: false }; this.aiState = { target: null, behavior: 'defensive', lastAction: 0, reactionTime: 50 + Math.random() * 100 }; this.homePosition = new Vector2(x, y); this.angle = 0; this.targetAngle = 0; } update(deltaTime, gameState, puck, players) { if (this.state.penaltyTime > 0) { this.state.penaltyTime -= deltaTime; return; } this.updateEnergy(deltaTime); this.updateMovement(deltaTime); this.updateAngle(deltaTime); if (this.role !== 'G') { this.updateAI(gameState, puck, players); } else { this.updateGoalie(gameState, puck, players); } } updateEnergy(deltaTime) { const energyDrain = this.velocity.magnitude() / this.maxSpeed * 10 * deltaTime; this.state.energy = Math.max(0, this.state.energy - energyDrain); if (this.state.energy < 20) { this.maxSpeed *= 0.7; } if (this.velocity.magnitude() < 50) { this.state.energy = Math.min(100, this.state.energy + 15 * deltaTime); } } updateMovement(deltaTime) { const direction = this.targetPosition.subtract(this.position).normalize(); const distance = this.position.distance(this.targetPosition); if (distance > 10) { const force = direction.multiply(this.acceleration * deltaTime); this.velocity = this.velocity.add(force); } else { // Slow down when close to target this.velocity = this.velocity.multiply(0.8); } const speedMultiplier = Math.min(1, this.state.energy / 100); this.velocity = this.velocity.limit(this.maxSpeed * speedMultiplier); // Reduced friction for more responsive movement this.velocity = Physics.applyFriction(this.velocity, 2, deltaTime); this.position = this.position.add(this.velocity.multiply(deltaTime)); this.keepInBounds(); } updateAngle(deltaTime) { let angleDiff = this.targetAngle - this.angle; angleDiff = Physics.wrapAngle(angleDiff); const turnRate = 10; if (Math.abs(angleDiff) > 0.1) { this.angle += Math.sign(angleDiff) * turnRate * deltaTime; this.angle = Physics.wrapAngle(this.angle); } } updateAI(gameState, puck, players) { const currentTime = Date.now(); if (currentTime - this.aiState.lastAction < this.aiState.reactionTime) { return; } this.aiState.lastAction = currentTime; const distanceToPuck = this.position.distance(puck.position); 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); } else { this.behaviorWithoutPuck(gameState, puck, teammates, opponents, distanceToPuck); } } behaviorWithPuck(gameState, puck, teammates, opponents) { const enemyGoal = this.team === 'home' ? new Vector2(gameState.rink.width - 50, gameState.rink.centerY) : new Vector2(50, gameState.rink.centerY); const nearestOpponent = this.findNearestPlayer(opponents); const distanceToGoal = this.position.distance(enemyGoal); if (distanceToGoal < 200 && Math.random() < 0.3) { this.shoot(puck, enemyGoal); } else if (nearestOpponent && this.position.distance(nearestOpponent.position) < 80) { const bestTeammate = this.findBestPassTarget(teammates, opponents); if (bestTeammate && Math.random() < 0.7) { this.pass(puck, bestTeammate); } else { this.moveToPosition(enemyGoal); } } else { this.moveToPosition(enemyGoal); } } behaviorWithoutPuck(gameState, puck, teammates, opponents, distanceToPuck) { const puckOwner = opponents.find(p => p.state.hasPuck) || teammates.find(p => p.state.hasPuck); if (!puckOwner && distanceToPuck < 200) { this.chasePuck(puck); } else if (puckOwner && puckOwner.team !== this.team) { if (distanceToPuck < 150 && Math.random() < 0.2) { this.checkPlayer(puckOwner); } else { this.defendPosition(gameState, puckOwner); } } else { this.moveToFormationPosition(gameState); } } updateGoalie(gameState, puck, players) { const goal = this.team === 'home' ? new Vector2(10, gameState.rink.centerY) : new Vector2(gameState.rink.width - 10, gameState.rink.centerY); const crease = { x: goal.x - 30, y: goal.y - 60, width: 60, height: 120 }; if (this.position.distance(puck.position) < 80) { this.targetPosition = puck.position.lerp(goal, 0.3); } else { this.targetPosition = goal.add(new Vector2( this.team === 'home' ? 20 : -20, (puck.position.y - goal.y) * 0.3 )); } this.targetPosition.x = Math.max(crease.x, Math.min(crease.x + crease.width, this.targetPosition.x)); this.targetPosition.y = Math.max(crease.y, Math.min(crease.y + crease.height, this.targetPosition.y)); } chasePuck(puck) { this.moveToPosition(puck.position); this.aiState.behavior = 'chasing'; } shoot(puck, target) { const direction = target.subtract(puck.position).normalize(); const power = this.attributes.shooting / 100 * 800; const accuracy = this.attributes.shooting / 100; const spread = (1 - accuracy) * 0.5; const angle = direction.angle() + (Math.random() - 0.5) * spread; puck.velocity = Vector2.fromAngle(angle, power); this.state.hasPuck = false; return true; } pass(puck, target) { const direction = target.position.subtract(puck.position).normalize(); const distance = puck.position.distance(target.position); const power = Math.min(600, distance * 2); puck.velocity = direction.multiply(power); this.state.hasPuck = false; return true; } checkPlayer(target) { if (this.position.distance(target.position) < 30) { target.velocity = target.velocity.add( this.position.subtract(target.position).normalize().multiply(-200) ); this.aiState.behavior = 'checking'; return true; } this.moveToPosition(target.position); return false; } moveToPosition(target) { this.targetPosition = target.copy(); this.targetAngle = target.subtract(this.position).angle(); } defendPosition(gameState, opponent) { const ownGoal = this.team === 'home' ? new Vector2(50, gameState.rink.centerY) : new Vector2(gameState.rink.width - 50, gameState.rink.centerY); const defendPoint = opponent.position.lerp(ownGoal, 0.6); this.moveToPosition(defendPoint); this.aiState.behavior = 'defending'; } moveToFormationPosition(gameState) { this.moveToPosition(this.getFormationPosition(gameState)); this.aiState.behavior = 'formation'; } getFormationPosition(gameState) { const side = this.team === 'home' ? -1 : 1; const centerX = gameState.rink.centerX; const centerY = gameState.rink.centerY; switch (this.role) { case 'C': return new Vector2(centerX + side * 100, centerY); case 'LW': return new Vector2(centerX + side * 150, centerY - 100); case 'RW': return new Vector2(centerX + side * 150, centerY + 100); case 'LD': return new Vector2(centerX + side * 200, centerY - 80); case 'RD': return new Vector2(centerX + side * 200, centerY + 80); default: return this.homePosition; } } findNearestPlayer(players) { let nearest = null; let minDistance = Infinity; players.forEach(player => { const distance = this.position.distance(player.position); if (distance < minDistance) { minDistance = distance; nearest = player; } }); return nearest; } findBestPassTarget(teammates, opponents) { let bestTarget = null; let bestScore = -1; teammates.forEach(teammate => { const distance = this.position.distance(teammate.position); if (distance < 50 || distance > 300) return; let blocked = false; opponents.forEach(opponent => { const lineToTeammate = teammate.position.subtract(this.position); const lineToOpponent = opponent.position.subtract(this.position); const angle = Math.abs(lineToTeammate.angle() - lineToOpponent.angle()); if (angle < 0.3 && this.position.distance(opponent.position) < distance) { blocked = true; } }); if (!blocked) { const score = teammate.attributes.puckHandling / distance; if (score > bestScore) { bestScore = score; bestTarget = teammate; } } }); return bestTarget; } 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)); } render(ctx) { ctx.save(); ctx.translate(this.position.x, this.position.y); ctx.rotate(this.angle); ctx.fillStyle = this.team === 'home' ? '#4a90e2' : '#e24a4a'; ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(0, 0, this.radius, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); if (this.state.hasPuck) { ctx.fillStyle = '#ffff00'; ctx.beginPath(); ctx.arc(0, -this.radius - 5, 3, 0, Math.PI * 2); ctx.fill(); } ctx.fillStyle = '#fff'; ctx.font = '10px Arial'; ctx.textAlign = 'center'; ctx.fillText(this.role, 0, 3); ctx.restore(); } }