@entropy-tamer/reynard-algorithms
Version:
Algorithm primitives and data structures for Reynard applications
449 lines (374 loc) • 13.9 kB
text/typescript
/**
* Game Engine Integration Example
*
* This example shows how to integrate reynard-algorithms into a game engine
* with real-time collision detection, spatial optimization, and performance monitoring.
*/
import {
detectCollisions,
createAABB,
AABB,
SpatialHash,
createSpatialHash,
measurePerformance,
createOptimizedCollisionAdapter,
WorkloadAnalyzer,
Point,
Circle,
Rectangle,
} from "../src/index.js";
// ============================================================================
// Game Object Types
// ============================================================================
interface GameObject {
id: string;
type: "player" | "enemy" | "bullet" | "powerup" | "obstacle";
position: Point;
velocity: Point;
bounds: AABB;
radius?: number; // for circular objects
health?: number;
damage?: number;
speed?: number;
}
interface GameState {
objects: Map<string, GameObject>;
spatialHash: SpatialHash;
collisionAdapter: ReturnType<typeof createOptimizedCollisionAdapter>;
workloadAnalyzer: WorkloadAnalyzer;
performance: {
frameTime: number;
collisionTime: number;
spatialUpdateTime: number;
objectCount: number;
};
}
// ============================================================================
// Game Engine Class
// ============================================================================
export class GameEngine {
private state: GameState;
private worldWidth: number;
private worldHeight: number;
private cellSize: number;
private frameCount: number = 0;
constructor(worldWidth: number = 1000, worldHeight: number = 1000, cellSize: number = 50) {
this.worldWidth = worldWidth;
this.worldHeight = worldHeight;
this.cellSize = cellSize;
this.state = {
objects: new Map(),
spatialHash: createSpatialHash(worldWidth, worldHeight, cellSize),
collisionAdapter: createOptimizedCollisionAdapter(),
workloadAnalyzer: new WorkloadAnalyzer(),
performance: {
frameTime: 0,
collisionTime: 0,
spatialUpdateTime: 0,
objectCount: 0,
},
};
}
// ============================================================================
// Object Management
// ============================================================================
addObject(obj: GameObject): void {
this.state.objects.set(obj.id, obj);
this.updateSpatialHash(obj);
}
removeObject(id: string): void {
const obj = this.state.objects.get(id);
if (obj) {
this.state.spatialHash.remove(id);
this.state.objects.delete(id);
}
}
getObject(id: string): GameObject | undefined {
return this.state.objects.get(id);
}
getAllObjects(): GameObject[] {
return Array.from(this.state.objects.values());
}
// ============================================================================
// Spatial Hash Management
// ============================================================================
private updateSpatialHash(obj: GameObject): void {
this.state.spatialHash.remove(obj.id);
this.state.spatialHash.insert(obj.id, obj.position.x, obj.position.y, obj.bounds.width, obj.bounds.height);
}
private updateAllSpatialHashes(): void {
const startTime = performance.now();
// Clear spatial hash
this.state.spatialHash.clear();
// Re-insert all objects
for (const obj of this.state.objects.values()) {
this.state.spatialHash.insert(obj.id, obj.position.x, obj.position.y, obj.bounds.width, obj.bounds.height);
}
this.state.performance.spatialUpdateTime = performance.now() - startTime;
}
// ============================================================================
// Collision Detection
// ============================================================================
private detectCollisions(): Array<{ a: GameObject; b: GameObject }> {
const startTime = performance.now();
// Get all objects as array for collision detection
const objects = this.getAllObjects();
// Use PAW optimization for algorithm selection
const workload = {
objectCount: objects.length,
spatialDensity: this.calculateSpatialDensity(),
overlapRatio: this.calculateOverlapRatio(),
};
const analysis = this.state.workloadAnalyzer.analyzeWorkload(workload);
const recommendation = this.state.collisionAdapter.getAlgorithmRecommendation(analysis);
// Perform collision detection
const collisions = detectCollisions(
objects.map(obj => ({
id: obj.id,
bounds: obj.bounds,
}))
);
// Convert back to GameObject collisions
const gameObjectCollisions = collisions.map(collision => ({
a: this.state.objects.get(collision.a.id)!,
b: this.state.objects.get(collision.b.id)!,
}));
this.state.performance.collisionTime = performance.now() - startTime;
return gameObjectCollisions;
}
// ============================================================================
// Physics and Movement
// ============================================================================
updatePhysics(deltaTime: number): void {
for (const obj of this.state.objects.values()) {
// Update position based on velocity
obj.position.x += obj.velocity.x * deltaTime;
obj.position.y += obj.velocity.y * deltaTime;
// Update bounds
obj.bounds.x = obj.position.x;
obj.bounds.y = obj.position.y;
// Keep objects within world bounds
this.constrainToWorld(obj);
}
// Update spatial hash after all movements
this.updateAllSpatialHashes();
}
private constrainToWorld(obj: GameObject): void {
// Constrain to world bounds
if (obj.position.x < 0) {
obj.position.x = 0;
obj.velocity.x = Math.abs(obj.velocity.x); // Bounce
}
if (obj.position.x + obj.bounds.width > this.worldWidth) {
obj.position.x = this.worldWidth - obj.bounds.width;
obj.velocity.x = -Math.abs(obj.velocity.x); // Bounce
}
if (obj.position.y < 0) {
obj.position.y = 0;
obj.velocity.y = Math.abs(obj.velocity.y); // Bounce
}
if (obj.position.y + obj.bounds.height > this.worldHeight) {
obj.position.y = this.worldHeight - obj.bounds.height;
obj.velocity.y = -Math.abs(obj.velocity.y); // Bounce
}
}
// ============================================================================
// Game Logic
// ============================================================================
handleCollisions(collisions: Array<{ a: GameObject; b: GameObject }>): void {
for (const collision of collisions) {
const { a, b } = collision;
// Handle different collision types
if (a.type === "player" && b.type === "enemy") {
this.handlePlayerEnemyCollision(a, b);
} else if (a.type === "player" && b.type === "powerup") {
this.handlePlayerPowerupCollision(a, b);
} else if (a.type === "bullet" && b.type === "enemy") {
this.handleBulletEnemyCollision(a, b);
} else if (a.type === "enemy" && b.type === "bullet") {
this.handleBulletEnemyCollision(b, a);
}
}
}
private handlePlayerEnemyCollision(player: GameObject, enemy: GameObject): void {
// Player takes damage
if (player.health !== undefined) {
player.health -= enemy.damage || 10;
if (player.health <= 0) {
this.removeObject(player.id);
console.log("Player destroyed!");
}
}
}
private handlePlayerPowerupCollision(player: GameObject, powerup: GameObject): void {
// Player gets powerup
if (player.health !== undefined) {
player.health += 20; // Heal
}
this.removeObject(powerup.id);
console.log("Powerup collected!");
}
private handleBulletEnemyCollision(bullet: GameObject, enemy: GameObject): void {
// Enemy takes damage
if (enemy.health !== undefined) {
enemy.health -= bullet.damage || 25;
if (enemy.health <= 0) {
this.removeObject(enemy.id);
console.log("Enemy destroyed!");
}
}
this.removeObject(bullet.id);
}
// ============================================================================
// Performance Monitoring
// ============================================================================
private calculateSpatialDensity(): number {
const totalArea = this.worldWidth * this.worldHeight;
const objectArea = Array.from(this.state.objects.values()).reduce(
(sum, obj) => sum + obj.bounds.width * obj.bounds.height,
0
);
return objectArea / totalArea;
}
private calculateOverlapRatio(): number {
// Simplified overlap calculation
const objects = this.getAllObjects();
let overlaps = 0;
let totalComparisons = 0;
for (let i = 0; i < objects.length; i++) {
for (let j = i + 1; j < objects.length; j++) {
totalComparisons++;
if (objects[i].bounds.intersects(objects[j].bounds)) {
overlaps++;
}
}
}
return totalComparisons > 0 ? overlaps / totalComparisons : 0;
}
getPerformanceStats() {
return {
...this.state.performance,
objectCount: this.state.objects.size,
spatialDensity: this.calculateSpatialDensity(),
overlapRatio: this.calculateOverlapRatio(),
frameCount: this.frameCount,
};
}
// ============================================================================
// Main Game Loop
// ============================================================================
update(deltaTime: number): void {
const frameStartTime = performance.now();
// Update physics
this.updatePhysics(deltaTime);
// Detect collisions
const collisions = this.detectCollisions();
// Handle collisions
this.handleCollisions(collisions);
// Update performance stats
this.state.performance.frameTime = performance.now() - frameStartTime;
this.state.performance.objectCount = this.state.objects.size;
this.frameCount++;
}
// ============================================================================
// Utility Methods
// ============================================================================
queryObjects(x: number, y: number, width: number, height: number): GameObject[] {
const objectIds = this.state.spatialHash.query(x, y, width, height);
return objectIds.map(id => this.state.objects.get(id)!).filter(Boolean);
}
getObjectsNear(point: Point, radius: number): GameObject[] {
return this.queryObjects(point.x - radius, point.y - radius, radius * 2, radius * 2).filter(obj => {
const distance = point.distanceTo(obj.position);
return distance <= radius;
});
}
}
// ============================================================================
// Example Usage
// ============================================================================
export function createSampleGame(): GameEngine {
const game = new GameEngine(800, 600, 40);
// Create player
const player = {
id: "player",
type: "player" as const,
position: new Point(400, 500),
velocity: new Point(0, 0),
bounds: createAABB(400, 500, 20, 20),
health: 100,
speed: 200,
};
// Create enemies
for (let i = 0; i < 10; i++) {
const enemy = {
id: `enemy${i}`,
type: "enemy" as const,
position: new Point(Math.random() * 800, Math.random() * 300),
velocity: new Point((Math.random() - 0.5) * 100, (Math.random() - 0.5) * 100),
bounds: createAABB(0, 0, 15, 15),
health: 50,
damage: 10,
};
enemy.bounds.x = enemy.position.x;
enemy.bounds.y = enemy.position.y;
game.addObject(enemy);
}
// Create powerups
for (let i = 0; i < 3; i++) {
const powerup = {
id: `powerup${i}`,
type: "powerup" as const,
position: new Point(Math.random() * 800, Math.random() * 600),
velocity: new Point(0, 0),
bounds: createAABB(0, 0, 10, 10),
};
powerup.bounds.x = powerup.position.x;
powerup.bounds.y = powerup.position.y;
game.addObject(powerup);
}
game.addObject(player);
return game;
}
export async function runGameSimulation(): Promise<void> {
console.log("🎮 Game Engine Integration Example");
const game = createSampleGame();
const targetFPS = 60;
const frameTime = 1000 / targetFPS;
console.log("Starting game simulation...");
console.log("Initial objects:", game.getAllObjects().length);
// Run simulation for 5 seconds
const simulationTime = 5000;
const startTime = Date.now();
while (Date.now() - startTime < simulationTime) {
const frameStart = performance.now();
// Update game
game.update(frameTime / 1000);
// Log performance every 60 frames (1 second at 60 FPS)
if (game["frameCount"] % 60 === 0) {
const stats = game.getPerformanceStats();
console.log(`Frame ${game["frameCount"]}:`, {
objects: stats.objectCount,
frameTime: `${stats.frameTime.toFixed(2)}ms`,
collisionTime: `${stats.collisionTime.toFixed(2)}ms`,
spatialUpdateTime: `${stats.spatialUpdateTime.toFixed(2)}ms`,
spatialDensity: stats.spatialDensity.toFixed(3),
overlapRatio: stats.overlapRatio.toFixed(3),
});
}
// Maintain target frame rate
const frameDuration = performance.now() - frameStart;
const sleepTime = Math.max(0, frameTime - frameDuration);
if (sleepTime > 0) {
await new Promise(resolve => setTimeout(resolve, sleepTime));
}
}
console.log("Simulation completed!");
console.log("Final objects:", game.getAllObjects().length);
const finalStats = game.getPerformanceStats();
console.log("Final performance stats:", finalStats);
}
// Run simulation if this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
runGameSimulation().catch(console.error);
}