puck 1
This commit is contained in:
parent
92b2d30d2b
commit
1990d98aa2
110
CLAUDE.md
Normal file
110
CLAUDE.md
Normal file
@ -0,0 +1,110 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
A hockey match engine simulation built with PhaserJS and TypeScript. The engine uses continuous positioning (exact X/Y coordinates on a 2D rink) combined with behavior trees for AI decision-making to create a realistic hockey match simulation.
|
||||
|
||||
**Core Concept**: Players have exact positions on a 60x30m international hockey rink, with AI running behavior trees at 60 FPS to make tactical decisions (passing, shooting, positioning, etc.).
|
||||
|
||||
## Development Commands
|
||||
|
||||
```bash
|
||||
# Start development server (runs on port 3000)
|
||||
npm run dev
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
|
||||
# Preview production build
|
||||
npm run preview
|
||||
```
|
||||
|
||||
## Coordinate System
|
||||
|
||||
**Critical**: The rink uses a centered coordinate system:
|
||||
- Origin (0, 0) = center of rink
|
||||
- Rink dimensions: 60m length × 30m width
|
||||
- Left goal: x = -26m, Right goal: x = +26m
|
||||
- All positions use **meters as floats** (not integers)
|
||||
- Screen rendering: 1 meter = 20 pixels (SCALE constant)
|
||||
|
||||
## Architecture
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
config/
|
||||
constants.ts # All game constants (rink dimensions, colors, speeds)
|
||||
game/
|
||||
main.ts # Phaser game initialization and config
|
||||
GameScene.ts # Main game scene (rendering, game loop)
|
||||
Goal.ts # Goal structure with physics bodies
|
||||
entities/ # Player and Puck classes (planned)
|
||||
systems/ # BehaviorTree, PositioningSystem, PuckSystem (planned)
|
||||
```
|
||||
|
||||
### Implementation Phases (from PLAN.md)
|
||||
|
||||
The project follows a phased approach:
|
||||
1. **Phase 1** (✓): Environment setup, rink rendering
|
||||
2. **Phase 2** (Next): Player entities (12 players: 10 skaters + 2 goalies) with smooth movement
|
||||
3. **Phase 3**: Puck entity and possession mechanics
|
||||
4. **Phase 4**: Behavior tree for puck carrier decisions (shoot/pass/carry)
|
||||
5. **Phase 5**: Team offensive positioning system
|
||||
6. **Phase 6**: Defensive behaviors and pressure mechanics
|
||||
7. **Phase 7**: Goalie AI
|
||||
8. **Phase 8**: Game flow (goals, faceoffs, scoring)
|
||||
9. **Phase 9**: Polish and tuning
|
||||
|
||||
### Key Systems (Planned)
|
||||
|
||||
**Behavior Trees**: Decision-making engine that runs every tick
|
||||
- Evaluates game state (possession, positions, threats)
|
||||
- Weights actions by player attributes (Hockey IQ, Skill, Speed)
|
||||
- Outputs: move, pass, shoot, or defensive actions
|
||||
|
||||
**Tactical Positioning**: Heat map-based positioning
|
||||
- Different formations based on game situation (offense/defense)
|
||||
- Players move toward ideal positions modified by:
|
||||
- Puck location
|
||||
- Teammate spacing
|
||||
- Opponent positions
|
||||
- Player stamina/speed
|
||||
|
||||
**Puck Movement**:
|
||||
- Pass success = f(passer skill, distance, pressure, receiver skill)
|
||||
- Shots use trajectory calculation with goalie save probability
|
||||
- Smooth interpolation for visual feedback
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Framework**: Phaser 3.90.0 (with Arcade Physics)
|
||||
- **TypeScript**: Strict mode enabled
|
||||
- **Build Tool**: Vite 5.4
|
||||
- **Target FPS**: 60 (constant in constants.ts)
|
||||
- **Physics**: Arcade physics with zero gravity (top-down view)
|
||||
|
||||
## Configuration
|
||||
|
||||
All magic numbers and game constants are centralized in `src/config/constants.ts`:
|
||||
- Rink dimensions and zone lines
|
||||
- Goal dimensions
|
||||
- Colors (ice, boards, lines)
|
||||
- Scale factor (meters to pixels)
|
||||
- Game settings (FPS)
|
||||
|
||||
Future constants will include:
|
||||
- Player speeds, shot speeds, pass speeds
|
||||
- AI decision probabilities
|
||||
- Attribute ranges
|
||||
|
||||
## Development Notes
|
||||
|
||||
- **Incremental testing**: Test each phase thoroughly before proceeding
|
||||
- **Debug visualization**: Add toggleable overlays for targets, zones, etc.
|
||||
- **Tuning over precision**: Numbers are starting points; tune based on feel
|
||||
- Refer to PLAN.md for detailed phase requirements and validation criteria
|
||||
- Refer to NOTES.md for architectural decisions and design notes
|
||||
112
src/entities/Puck.ts
Normal file
112
src/entities/Puck.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import Phaser from 'phaser';
|
||||
import { SCALE } from '../config/constants';
|
||||
|
||||
export type PuckState = 'loose' | 'carried' | 'passing' | 'shot';
|
||||
|
||||
export class Puck extends Phaser.GameObjects.Container {
|
||||
private sprite!: Phaser.GameObjects.Graphics;
|
||||
private body!: Phaser.Physics.Arcade.Body;
|
||||
|
||||
// Position in game coordinates (meters)
|
||||
public gameX: number;
|
||||
public gameY: number;
|
||||
|
||||
// Puck state
|
||||
public state: PuckState;
|
||||
public possession: 'home' | 'away' | null;
|
||||
public carrier: string | null; // PlayerID
|
||||
|
||||
constructor(scene: Phaser.Scene, gameX: number = 0, gameY: number = 0) {
|
||||
// Convert game coordinates to screen coordinates
|
||||
const screenX = (scene.game.config.width as number) / 2 + gameX * SCALE;
|
||||
const screenY = (scene.game.config.height as number) / 2 - gameY * SCALE;
|
||||
|
||||
super(scene, screenX, screenY);
|
||||
|
||||
this.gameX = gameX;
|
||||
this.gameY = gameY;
|
||||
this.state = 'loose';
|
||||
this.possession = null;
|
||||
this.carrier = null;
|
||||
|
||||
// Add to scene
|
||||
scene.add.existing(this);
|
||||
scene.physics.add.existing(this);
|
||||
|
||||
this.body = this.body as Phaser.Physics.Arcade.Body;
|
||||
|
||||
// Set physics body size to match the puck visual (10px diameter)
|
||||
this.body.setSize(10, 10);
|
||||
this.body.setOffset(-5, -5); // Center the body on the container
|
||||
|
||||
// Set initial velocity (5 m/s in a random direction for testing)
|
||||
const angle = 3.1415; //Math.random() * Math.PI * 2;
|
||||
const speed = 30 * SCALE; // 5 m/s converted to pixels/s
|
||||
this.body.setVelocity(
|
||||
Math.cos(angle) * speed,
|
||||
Math.sin(angle) * speed
|
||||
);
|
||||
|
||||
// Add bounce to keep it moving
|
||||
this.body.setBounce(1, 1);
|
||||
this.body.setCollideWorldBounds(true);
|
||||
|
||||
this.createSprite();
|
||||
}
|
||||
|
||||
private createSprite() {
|
||||
const graphics = this.scene.add.graphics();
|
||||
|
||||
// Draw puck as black circle (5px radius)
|
||||
graphics.fillStyle(0x000000, 1);
|
||||
graphics.fillCircle(0, 0, 5);
|
||||
|
||||
// Add white highlight for visibility on ice
|
||||
graphics.fillStyle(0xffffff, 0.3);
|
||||
graphics.fillCircle(-1, -1, 2);
|
||||
|
||||
this.add(graphics);
|
||||
this.sprite = graphics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update puck position in game coordinates (meters)
|
||||
*/
|
||||
public setGamePosition(gameX: number, gameY: number) {
|
||||
this.gameX = gameX;
|
||||
this.gameY = gameY;
|
||||
|
||||
// Convert to screen coordinates
|
||||
const centerX = (this.scene.game.config.width as number) / 2;
|
||||
const centerY = (this.scene.game.config.height as number) / 2;
|
||||
|
||||
this.setPosition(
|
||||
centerX + gameX * SCALE,
|
||||
centerY - gameY * SCALE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set puck to be carried by a player
|
||||
*/
|
||||
public setCarrier(playerId: string, team: 'home' | 'away') {
|
||||
this.carrier = playerId;
|
||||
this.possession = team;
|
||||
this.state = 'carried';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set puck to loose state
|
||||
*/
|
||||
public setLoose() {
|
||||
this.carrier = null;
|
||||
this.state = 'loose';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get puck position in game coordinates
|
||||
*/
|
||||
public getGamePosition(): { x: number; y: number } {
|
||||
return { x: this.gameX, y: this.gameY };
|
||||
}
|
||||
}
|
||||
@ -15,10 +15,12 @@ import {
|
||||
COLOR_GOAL_CREASE
|
||||
} from '../config/constants';
|
||||
import { Goal } from './Goal';
|
||||
import { Puck } from '../entities/Puck';
|
||||
|
||||
export class GameScene extends Phaser.Scene {
|
||||
private leftGoal!: Goal;
|
||||
private rightGoal!: Goal;
|
||||
private puck!: Puck;
|
||||
|
||||
constructor() {
|
||||
super({ key: 'GameScene' });
|
||||
@ -27,6 +29,20 @@ export class GameScene extends Phaser.Scene {
|
||||
create() {
|
||||
this.drawRink();
|
||||
this.createGoals();
|
||||
this.createPuck();
|
||||
}
|
||||
|
||||
private createPuck() {
|
||||
// Initialize puck at center ice (0, 0 in game coordinates)
|
||||
this.puck = new Puck(this, 0, 0);
|
||||
|
||||
// Add collisions between puck and goal posts
|
||||
this.physics.add.collider(this.puck, this.leftGoal.getLeftPost());
|
||||
this.physics.add.collider(this.puck, this.leftGoal.getRightPost());
|
||||
this.physics.add.collider(this.puck, this.leftGoal.getBackBar());
|
||||
this.physics.add.collider(this.puck, this.rightGoal.getLeftPost());
|
||||
this.physics.add.collider(this.puck, this.rightGoal.getRightPost());
|
||||
this.physics.add.collider(this.puck, this.rightGoal.getBackBar());
|
||||
}
|
||||
|
||||
private createGoals() {
|
||||
|
||||
@ -12,7 +12,7 @@ const config: Phaser.Types.Core.GameConfig = {
|
||||
default: 'arcade',
|
||||
arcade: {
|
||||
gravity: { x: 0, y: 0 },
|
||||
debug: false
|
||||
debug: true
|
||||
}
|
||||
},
|
||||
fps: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user