class RulesSystem { constructor(gameState) { this.gameState = gameState; this.lastOffsideCheck = 0; this.lastIcingCheck = 0; this.penaltyQueue = []; } update(players, puck, deltaTime) { this.checkOffside(players, puck); this.checkIcing(players, puck); this.checkGoaltenderInterference(players, puck); this.checkHighSticking(players, puck); this.processPenaltyQueue(); } checkOffside(players, puck) { const currentTime = Date.now(); if (currentTime - this.lastOffsideCheck < 500) return; const puckOwner = players.find(p => p.state.hasPuck); if (!puckOwner) return; const centerLine = this.gameState.rink.centerX; const offensiveZoneLine = puckOwner.team === 'home' ? this.gameState.rink.width * 0.67 : this.gameState.rink.width * 0.33; const puckInOffensiveZone = puckOwner.team === 'home' ? puck.position.x > offensiveZoneLine : puck.position.x < offensiveZoneLine; if (puckInOffensiveZone) { const teammates = players.filter(p => p.team === puckOwner.team && p.id !== puckOwner.id && p.role !== 'G' ); const offsidePlayers = teammates.filter(teammate => { const isAhead = puckOwner.team === 'home' ? teammate.position.x > puck.position.x : teammate.position.x < puck.position.x; const inOffensiveZone = puckOwner.team === 'home' ? teammate.position.x > offensiveZoneLine : teammate.position.x < offensiveZoneLine; return isAhead && inOffensiveZone; }); if (offsidePlayers.length > 0) { this.callOffside(puckOwner.team, offsidePlayers); this.lastOffsideCheck = currentTime; } } } callOffside(team, players) { this.gameState.addEvent(`OFFSIDE - ${team.toUpperCase()}`); const faceoffPosition = this.gameState.rink.faceoffDots[ team === 'home' ? 1 : 3 ]; this.gameState.emit('offside', { team, players: players.map(p => p.name), faceoffPosition }); this.scheduleFaceoff(faceoffPosition); } checkIcing(players, puck) { const currentTime = Date.now(); if (currentTime - this.lastIcingCheck < 1000) return; if (!puck.lastTeamTouch) return; const shootingTeam = puck.lastTeamTouch; const shootingZoneLine = shootingTeam === 'home' ? this.gameState.rink.width * 0.33 : this.gameState.rink.width * 0.67; const puckCrossedOwnZone = shootingTeam === 'home' ? puck.position.x < shootingZoneLine : puck.position.x > shootingZoneLine; if (!puckCrossedOwnZone) return; const goalLine = shootingTeam === 'home' ? this.gameState.rink.width - 50 : 50; const puckPastGoalLine = shootingTeam === 'home' ? puck.position.x > goalLine : puck.position.x < goalLine; if (puckPastGoalLine) { const shootingPlayers = players.filter(p => p.team === shootingTeam); const opposingPlayers = players.filter(p => p.team !== shootingTeam); const nearestShootingPlayer = this.findNearestPlayer(puck.position, shootingPlayers); const nearestOpposingPlayer = this.findNearestPlayer(puck.position, opposingPlayers); if (nearestOpposingPlayer && nearestOpposingPlayer.position.distance(puck.position) < nearestShootingPlayer.position.distance(puck.position)) { this.callIcing(shootingTeam); this.lastIcingCheck = currentTime; } } } callIcing(team) { this.gameState.addEvent(`ICING - ${team.toUpperCase()}`); const faceoffPosition = this.gameState.rink.faceoffDots[ team === 'home' ? 0 : 4 ]; this.gameState.emit('icing', { team, faceoffPosition }); this.scheduleFaceoff(faceoffPosition); } checkGoaltenderInterference(players, puck) { const goalies = players.filter(p => p.role === 'G'); goalies.forEach(goalie => { const crease = this.getCreaseArea(goalie.team); const opponents = players.filter(p => p.team !== goalie.team && this.isPlayerInCrease(p, crease) ); opponents.forEach(opponent => { if (opponent.velocity.magnitude() > 100) { this.callGoaltenderInterference(opponent); } }); }); } checkHighSticking(players, puck) { if (puck.position.y < 200) { const lastTouch = puck.lastPlayerTouch; if (lastTouch && Math.random() < 0.1) { this.callHighSticking(lastTouch); } } } isPlayerInCrease(player, crease) { return player.position.x >= crease.x && player.position.x <= crease.x + crease.width && player.position.y >= crease.y && player.position.y <= crease.y + crease.height; } getCreaseArea(team) { const goalY = this.gameState.rink.centerY; const goalX = team === 'home' ? 50 : this.gameState.rink.width - 50; return { x: goalX - 30, y: goalY - 60, width: 60, height: 120 }; } callGoaltenderInterference(player) { this.queuePenalty(player.team, player.name, 'Goaltender Interference', 120); } callHighSticking(player) { this.queuePenalty(player.team, player.name, 'High Sticking', 120); } queuePenalty(team, player, type, duration) { this.penaltyQueue.push({ team, player, type, duration, timestamp: Date.now() }); } processPenaltyQueue() { if (this.penaltyQueue.length === 0) return; const penalty = this.penaltyQueue.shift(); this.gameState.addPenalty( penalty.team, penalty.player, penalty.type, penalty.duration ); } scheduleFaceoff(position) { setTimeout(() => { this.gameState.emit('faceoff', { position }); }, 2000); } findNearestPlayer(position, players) { let nearest = null; let minDistance = Infinity; players.forEach(player => { const distance = player.position.distance(position); if (distance < minDistance) { minDistance = distance; nearest = player; } }); return nearest; } checkFighting(players) { for (let i = 0; i < players.length; i++) { for (let j = i + 1; j < players.length; j++) { const player1 = players[i]; const player2 = players[j]; if (player1.team !== player2.team && player1.position.distance(player2.position) < 20 && player1.velocity.magnitude() > 150 && player2.velocity.magnitude() > 150 && Math.random() < 0.01) { this.callFighting(player1, player2); } } } } callFighting(player1, player2) { this.queuePenalty(player1.team, player1.name, 'Fighting', 300); this.queuePenalty(player2.team, player2.name, 'Fighting', 300); this.gameState.addEvent(`FIGHT - ${player1.name} vs ${player2.name}`); } reset() { this.lastOffsideCheck = 0; this.lastIcingCheck = 0; this.penaltyQueue = []; } }