12 KiB
Hockey Match Engine MVP - Implementation Plan
Overview
A minimal hockey match engine using PhaserJS with continuous positioning and behavior trees. The goal is to validate the architecture with the simplest working implementation.
Technical Stack
- Framework: PhaserJS 3
- Language: TypeScript
- Coordinate System: International rink (60x30m), origin at center (0,0)
- Update Rate: 60 FPS (Phaser's default game loop)
Core Architecture
1. Game State
GameState {
puck: { x, y, possession: PlayerID|null, carrier: PlayerID|null }
teams: [homeTeam, awayTeam]
score: { home: 0, away: 0 }
gameTime: elapsed seconds
}
2. Player Structure
Player {
id: string
team: 'home'|'away'
position: 'LW'|'C'|'RW'|'LD'|'RD'|'G'
x, y: float (current position)
targetX, targetY: float (desired position)
attributes: {
speed: 0-100 (movement speed)
skill: 0-100 (pass/shot accuracy, decision quality)
}
state: 'offensive'|'defensive'
}
3. Behavior Tree (Simplified)
Root
├─ Is Player Goalie?
│ └─ Goalie Behavior (stay near net, track puck)
│
├─ Does Player Have Puck?
│ ├─ Can Shoot? → Shoot
│ ├─ Can Pass? → Pass to best teammate
│ └─ Else → Skate toward opponent net
│
├─ Is Puck Loose?
│ └─ Move toward puck (chase)
│
└─ Team Has Puck? (offensive state)
├─ Move to offensive support position
└─ Else → Move to defensive coverage position
Implementation Phases
Phase 1: Environment Setup
Goal: Get PhaserJS running with a hockey rink displayed
Tasks:
- Initialize npm project with package.json
- Install dependencies:
phaser(and optionallytypescript,vitefor dev server) - Create basic project structure:
/src /game main.js (Phaser game configuration) GameScene.js (main game scene) /entities Player.js Puck.js /systems BehaviorTree.js PositioningSystem.js PuckSystem.js /config constants.js (rink dimensions, game constants) index.html - Draw the rink:
- Rectangle 60x30m (scale for display, e.g., 1m = 10px → 600x300px)
- Center red line
- Blue lines at ±18.3m
- Goal creases (simple rectangles at x=±26, y=0)
- Optional: boards/corners (rounded rectangle)
Validation: Browser displays a hockey rink with proper markings
Phase 2: Player Entities & Basic Movement
Goal: Render 12 players (10 skaters + 2 goalies) and move them to target positions
Tasks:
-
Create Player class:
- Visual: colored circle (10px radius)
- Home team: blue circles
- Away team: red circles
- Goalies: larger circles or different shade
- Render at x,y coordinates
- Add text label with position (LW, C, RW, LD, RD, G)
- Visual: colored circle (10px radius)
-
Create initial formations (5v5 setup):
- Home team (left side, attacking right):
- G: (-26, 0)
- LD: (-15, -5), RD: (-15, 5)
- LW: (-10, -8), C: (-10, 0), RW: (-10, 8)
- Away team (right side, attacking left):
- G: (26, 0)
- LD: (15, 5), RD: (15, -5)
- LW: (10, 8), C: (10, 0), RW: (10, -8)
- Home team (left side, attacking right):
-
Implement interpolated movement:
- Each player has
targetX, targetY - Each frame: move toward target using easing (lerp or Phaser tweens)
- Movement speed based on
speedattribute (e.g., speed=80 → 8 m/s)
- Each player has
-
Test: Set random target positions and watch players smoothly move
Validation: 12 colored circles on ice, moving smoothly when targets change
Phase 3: Puck & Basic Possession
Goal: Add puck entity and implement possession mechanics
Tasks:
-
Create Puck class:
- Visual: small black circle (5px radius)
- Position: x, y
- State:
loose,carried,passing,shot possession: which team has controlcarrier: specific PlayerID holding puck
-
Implement puck carrying:
- When player has puck: puck.x = player.x, puck.y = player.y
- Puck "sticks" to carrier position each frame
-
Implement loose puck pickup:
- Calculate distance from each player to puck
- If player within pickup radius (1.5m) and puck is loose:
- Pickup chance = f(player.skill, player.speed)
- Assign carrier, set possession
-
Faceoff system:
- Place puck at (0, 0)
- Set state =
loose - Both centers skate to center, first to arrive picks up
Validation: Puck spawns at center, players skate to it, one picks it up
Phase 4: Basic Behavior Tree - Puck Carrier Actions
Goal: Player with puck makes decisions (shoot, pass, carry)
Tasks:
-
Create BehaviorTree evaluator:
- Input: player, gameState
- Output: action {type: 'move'|'pass'|'shoot', target: ...}
-
Implement carrier behaviors:
-
Shoot decision:
- If within shooting range (x > 15m from opponent net) and clear lane:
- Shoot chance = f(skill, distance to net)
- Action:
{type: 'shoot', targetX: opponentNet.x, targetY: random(-2, 2)}
-
Pass decision:
- Find all teammates
- Filter: not covered by opponent (simple distance check)
- Rate by: distance to net, openness
- If good option exists:
{type: 'pass', target: teammateID}
-
Carry decision (default):
- Move toward opponent net
- Target: (opponentNet.x - 5, y + random(-3, 3))
-
-
Action execution:
-
Shoot:
- Travel time = distance / shotSpeed (e.g., 30 m/s)
- Goalie save chance = f(goalie.skill, shot distance, shot accuracy)
- Outcome: goal (puck to net, score++, faceoff) or save (puck loose near net)
-
Pass:
- Travel time = distance / passSpeed (15 m/s)
- Success chance = f(passer.skill, distance, pressure)
- If success: receiver becomes carrier
- If fail: puck becomes loose at midpoint
-
Carry: player skates with puck
-
Validation: Player with puck skates toward net, occasionally passes or shoots
Phase 5: Team Positioning System (Offensive)
Goal: Players without puck move to support positions
Tasks:
-
Create simple heat map / positioning zones:
- Offensive formation (team has puck):
- Wingers: spread wide (y = ±10)
- Center: support puck carrier (x = carrier.x - 5, y = 0)
- Defense: hold blue line (x = offensiveZone - 15)
- Offensive formation (team has puck):
-
Implement positioning logic:
-
Each non-carrier calculates ideal position based on:
- Puck location
- Their position role (LW, C, RW, LD, RD)
- Teammate spacing (avoid clustering)
-
Set targetX, targetY to ideal position
-
Player moves there using Phase 2 movement system
-
-
Dynamic adjustment:
- If puck changes possession, recalculate all positions
Validation: When one team has puck, their players spread into offensive formation
Phase 6: Defensive Behaviors
Goal: Defending team pressures puck carrier and covers passing lanes
Tasks:
-
Add defensive state detection:
- If opponent has puck: all players on team → state = 'defensive'
-
Implement defensive positioning:
-
Forecheck (1 nearest forward):
- Chase puck carrier (targetX = carrier.x, targetY = carrier.y)
- Apply "pressure" when within 2m
-
Coverage (other forwards):
- Cover closest opponent forward
- Stay between opponent and own net
-
Defensive zone (defense):
- Protect net-front (x = -20 to -25, y = ±4)
- Box out opponent forwards
-
-
Pressure mechanic:
- When defender within 2m of carrier:
- Reduce carrier's pass/shot success chance
- Increase turnover chance (puck becomes loose)
- When defender within 2m of carrier:
Validation: Defending team chases puck carrier and covers opponents
Phase 7: Goalie Behavior
Goal: Goalies track puck and make saves
Tasks:
-
Goalie positioning:
- Stay in crease: x = ±27 (near net)
- Track puck Y position: targetY = clamp(puck.y, -3, 3)
- Interpolate to target (slower than skaters)
-
Save mechanics:
- On shot toward net:
- Calculate if shot trajectory intersects goalie position (±1m tolerance)
- Save chance = f(goalie.skill, shot speed, shot accuracy)
- Save: puck becomes loose in crease
- Goal: puck "enters net", score increments
- On shot toward net:
-
Visual feedback:
- Flash goalie when making save
- Puck bounce animation on save
Validation: Goalies move laterally to track puck, stop some shots
Phase 8: Game Flow & Reset
Goal: Handle goals and faceoffs, basic game loop
Tasks:
-
Goal detection:
- Check if puck crosses goal line (x > 29.5 or x < -29.5, y within net width)
- Increment score
- Trigger celebration pause (1 second)
- Reset to faceoff
-
Faceoff logic:
- Reset all players to formation positions (Phase 2)
- Place puck at (0, 0)
- Set puck.state = 'loose'
- Resume game
-
Game clock (optional for MVP):
- Display elapsed time
- No periods/end game for now (continuous)
-
Simple HUD:
- Score display: "Home 2 - 1 Away"
- Optional: possession indicator
Validation: Full game loop - faceoff → play → goal → faceoff → repeat
Phase 9: Polish & Tuning
Goal: Make it feel like hockey
Tasks:
-
Tune movement speeds:
- Skater speed: ~8-10 m/s
- Puck pass speed: 15 m/s
- Puck shot speed: 30 m/s
-
Tune AI probabilities:
- Shot frequency: should see ~5-15 shots per team per minute
- Pass success: ~70-80% for uncontested passes
- Turnover frequency: balance offense/defense
-
Visual polish:
- Puck trail (line showing recent path)
- Player orientation (rotate sprite toward movement direction)
- Smooth animations for passes/shots
-
Balancing:
- Adjust skill/speed ranges so games are competitive
- Test with different attribute values
Validation: Game looks and feels reasonably like hockey
Success Criteria for MVP
- ✅ 12 players (10 skaters + 2 goalies) on ice
- ✅ Players move smoothly in formations
- ✅ Puck possession and carrying works
- ✅ Players pass, shoot, and score goals
- ✅ Basic offensive positioning (spread out, support)
- ✅ Basic defensive pressure (chase carrier)
- ✅ Goalies make saves
- ✅ Goals trigger faceoffs and score updates
- ✅ Game runs continuously without crashes
Future Iterations (Post-MVP)
Phase 10+: Additional features to consider after MVP validation:
- Offsides/icing rules
- Line changes and stamina system
- More strategies (1-2-2 forecheck, trap, etc.)
- Penalties and power plays
- More player attributes (checking, passing accuracy, shot power)
- Better AI decision trees (risk assessment, situational awareness)
- Replay system
- Match statistics tracking
- 3D visualization or better sprites
Development Tips
- Test incrementally: After each phase, test thoroughly before moving on
- Use constants file: Store all magic numbers (rink size, speeds, probabilities) in one place
- Debug visualization: Add toggleable overlays (target positions, pressure zones, etc.)
- Start simple: Don't add complexity until basic mechanics work
- Iterate on feel: The numbers in this plan are starting points - tune them based on what looks/feels right
Estimated Timeline (Solo Developer)
- Phase 1: 2-4 hours (setup + rink rendering)
- Phase 2: 3-4 hours (player entities + movement)
- Phase 3: 2-3 hours (puck + possession)
- Phase 4: 4-6 hours (behavior tree + carrier actions)
- Phase 5: 3-4 hours (offensive positioning)
- Phase 6: 4-5 hours (defensive behaviors)
- Phase 7: 2-3 hours (goalie AI)
- Phase 8: 2-3 hours (game flow)
- Phase 9: 4-6 hours (tuning + polish)
Total: ~26-38 hours for a working MVP
Next Steps
- Set up development environment (Phase 1)
- Get rink rendering working
- Proceed through phases sequentially
- Test and validate each phase before moving forward
- Document any architectural changes or insights in NOTES.md
Good luck! 🏒