A fully-featured 11v11 football (soccer) simulation game built with TypeScript, React, and Canvas. Initially generated by Claude, and then tweaked to work well.
Football Simulator is a modern web-based football game that demonstrates:
- Pure TypeScript Game Engine - A completely decoupled game engine with zero React dependencies
- Intelligent Decisions - Intelligent player behavior including possession, passing, and tactical positioning
- Realistic Physics - Ball friction, collision detection, and impulse-based responses
- Clean Architecture - Modular design following SOLID principles and separation of concerns
The game features two 11-player teams (home in red, away in blue) with a 4-3-3 formation, automatic goal detection, and real-time score tracking. Simply hit play to watch AI-controlled players battle it out!
- Node.js 16+
- pnpm (or npm/yarn)
# Install dependencies
pnpm install
# Start development server
pnpm dev
# Opens at http://localhost:5173/
# Build for production
pnpm build
# Run linter
pnpm lint- 11 vs 11 Players - Full football teams with realistic formations
- 4-3-3 Formation - Goalkeeper, 4 defenders, 3 midfielders, 3 forwards per team
- Role-Based AI - Each player has a specific role affecting their behavior:
- Goalkeeper: Tracks ball vertically, makes long passes
- Defender: Intercepts passes, positions defensively
- Midfielder: Balances defense and attack, supports play
- Forward: Aggressive positioning, finishing opportunities
- Ball Possession - Realistic possession system based on proximity
- Intelligent Passing - AI players make forward passes to teammates
- Dribbling - Players can dribble forward when in possession
- Collision Physics - Physical interactions between players
- Goal Detection - Automatic scoring when ball crosses the goal line
- Score Tracking - Real-time score display for both teams
- Play/Pause - Toggle game simulation
- Reset Game - Reset to initial state
- Score Display - Real-time match score
src/
โโโ App.tsx # React UI component
โโโ engine/ # Pure TypeScript Game Engine
โโโ GameEngine.ts # Main orchestrator (60 FPS game loop)
โโโ index.ts # Public API exports
โโโ entities/
โ โโโ Ball.ts # Ball physics and behavior
โ โโโ Player.ts # Player AI and physics
โโโ math/
โ โโโ Vector.ts # 2D vector math utilities
โโโ renderer/
โ โโโ Renderer.ts # Canvas drawing operations
โโโ physics/
โโโ Physics.ts # Collision detection & response
The game is built with a strict separation between the game engine (pure TypeScript) and the UI layer (React).
Game Engine (Pure TypeScript) - No React Dependencies
// src/engine/GameEngine.ts
export class GameEngine {
// Game state
getState(): GameState
// Lifecycle
start(): void
stop(): void
togglePause(): void
resetGame(): void
// Callbacks
onStateChange(callback: (state: GameState) => void): void
}React Integration - UI Only
// src/App.tsx
function App() {
const [gameState, setGameState] = useState<GameState>({
score: { home: 0, away: 0 },
paused: false,
});
useEffect(() => {
const engine = new GameEngine(canvas);
engine.onStateChange(setGameState);
engine.start();
return () => engine.destroy();
}, []);
// Render UI and canvas
}| Module | Responsibility |
|---|---|
| GameEngine.ts | Game state, loop coordination, system integration |
| Ball.ts | Ball physics, friction, collision, goal detection |
| Player.ts | Player AI, movement, possession, passing logic |
| Vector.ts | 2D vector math operations and utilities |
| Renderer.ts | Canvas rendering: pitch, players, ball |
| Physics.ts | Player collision detection and impulse response |
App.tsx
โโ GameEngine
โโ Ball
โ โโ Vector
โโ Player[]
โ โโ Vector
โ โโ Ball
โโ Renderer
โ โโ Ball
โ โโ Player
โโ Physics
โโ Player
โ Zero circular dependencies โ No React in game engine โ 100% TypeScript typed
The AI system uses role-specific logic to determine player behavior:
Possession Logic
- Players with the ball (within 20px radius) gain possession
- Possession holders either pass or dribble based on timers
- Goalkeepers hold longer than outfield players
Passing
- Players prefer forward passes to teammates
- Falls back to any available teammate if no forward options
- Pass power calculated based on distance
Off-Ball Positioning
- Goalkeepers track the ball vertically at their goal
- Outfield players chase the ball if closest and it's slow
- Players return to formation when ball is far away
- Players provide support play near ball position
| Role | Possession | Speed | Acceleration | Positioning |
|---|---|---|---|---|
| Goalkeeper | Hold 20 frames | 1.5 | 0.25 | Track ball vertically |
| Defender | Dribble 40 frames | 2.0 | 0.35 | Defensive shape |
| Midfielder | Dribble 40 frames | 2.3 | 0.40 | Balance defense/attack |
| Forward | Dribble 40 frames | 2.5 | 0.45 | Aggressive positioning |
- Velocity & Friction: 0.97x friction per frame (3% energy loss)
- Bounce Damping: 0.7x energy on wall collisions
- Stop Threshold: Velocity < 0.05 treated as zero
- Size: 6px radius
- Collision Damping: 0.3x energy in player collisions
- Friction: 0.88x per frame (12% deceleration)
- Size: 15px radius
- Mass: 2 units (vs ball's 1)
- Impulse-based collision response
- Elastic collisions between players
- Automatic boundary constraints
- Distance-based pass detection
- โ TypeScript Compilation: PASS
- โ Vite Build: SUCCESS
- โ Compiler Errors: 0
- โ Compiler Warnings: 0
- โ 7 modular TypeScript files (~785 lines total)
- โ 100% type coverage
- โ No circular dependencies
- โ No unused imports or variables
- โ SOLID principles followed
| Aspect | Before | After |
|---|---|---|
| Files | 1 monolithic | 7 focused |
| Lines | 400+ mixed | 785 organized |
| React coupling | Heavy | None in engine |
| Type safety | Partial | 100% |
| Reusability | Limited | Full |
| Testability | Difficult | Easy |
import { GameEngine } from './engine/GameEngine';
import type { GameState } from './engine/GameEngine';
const canvasEl = document.getElementById('game') as HTMLCanvasElement;
const engine = new GameEngine(canvasEl);
// Listen to state changes
engine.onStateChange((newState: GameState) => {
console.log(`Score: ${newState.score.home} - ${newState.score.away}`);
console.log(`Paused: ${newState.paused}`);
});
// Control the game
engine.start();
engine.togglePause();
engine.resetGame();
engine.stop();Each frame:
- Update Ball - Apply velocity, friction, detect goals
- Update Players - AI decisions, movement, possession
- Collision Detection - Resolve player-player collisions
- Render - Draw pitch, players, and ball
Home Team (Red) - Attacks Right Away Team (Blue) - Attacks Left
GK GK
LB LCB RCB RB LB LCB RCB RB
LM CM RM LM CM RM
LW ST RW LW ST RW
To add new features:
- Ball Behavior: Modify
src/engine/entities/Ball.ts - Player AI: Modify
src/engine/entities/Player.ts - Rendering: Modify
src/engine/renderer/Renderer.ts - Physics: Modify
src/engine/physics/Physics.ts - Coordination: Modify
src/engine/GameEngine.ts
Example: Adding stamina system
// In Player.ts
export class Player {
stamina: number = 100;
update(ball: Ball, players: Player[]) {
// ... existing code ...
// Recovery when not in possession
if (!this.hasPossession) {
this.stamina = Math.min(100, this.stamina + 0.5);
} else {
this.stamina = Math.max(0, this.stamina - 1);
}
// Reduce speed when tired
const speedMultiplier = this.stamina < 20 ? 0.8 : 1.0;
this.maxSpeed *= speedMultiplier;
}
}The game uses a classic 4-3-3 formation with 11 players per team:
- 1 Goalkeeper - Maintains defensive position
- 4 Defenders - Left back, 2 center backs, right back
- 3 Midfielders - Left, center, right
- 3 Forwards - Left wing, striker, right wing
Goals are automatically detected when:
- Ball crosses goal line (X < 30 or X > 870)
- Ball is within goal area (Y between 140-460)
- Ball has been hit by a player
A player has possession when:
- Ball is within 20px radius of player
- No opponent is closer to the ball
- Ball is traveling slow enough
- Frame Rate: 60 FPS target
- Entities: 22 players + 1 ball = 23 total
- Collisions: Nยฒ detection (optimizable for larger games)
- Memory: ~5-10MB runtime
- Canvas Size: 900x600px
The game uses 2D vector math for all physics:
const velocity = new Vector(5, 10);
const friction = velocity.mult(0.97);
const normalized = velocity.normalize();
const magnitude = velocity.mag();Collisions use impulse-based responses:
- Calculate relative velocity
- Apply counter-impulse to both bodies
- Dampen energy loss (0.3x for players)
Each player evaluates:
- Do I have possession?
- Is the ball moving fast? (Chase if < 3 m/s)
- Am I in formation? (Return if far away)
- Can I support play? (Position near ball)
The clean architecture makes it easy to add:
- Advanced AI: Tactics system, pressing strategies, formations
- Player Stats: Individual attributes (pace, passing, shooting)
- Animations: Smooth transitions, celebration animations
- Match Events: Yellow/red cards, fouls, injuries
- Replay System: Record and replay match highlights
- Multiplayer: Network play or local hot-seat mode
- UI Dashboard: Team selection, player management, statistics
- Sound Effects: Goal sounds, crowd noise, commentary
- Different Game Modes: Tournament, career, custom matches
- REFACTORING_SUMMARY.md - Detailed breakdown of all changes
- REFACTORING_COMPLETE.md - Complete refactoring checklist
- DEVELOPER_GUIDE.md - Comprehensive development guide
- COMPLETION_CHECKLIST.md - All requirements verified
See LICENSE file for details.
The modular architecture makes it easy to contribute:
- Pick a module to enhance
- Review its type definitions
- Implement your feature
- Test in the game
- Ensure no circular dependencies
Happy coding! โฝ