ported simulation

This commit is contained in:
2025-05-17 15:55:26 +02:00
parent 23e5707e98
commit 6ffa7e9c68
33 changed files with 1265 additions and 233 deletions

View File

@@ -1,9 +1,8 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { OptionsComponent } from '../options/options.component';
import { GameState } from './gameState';
import { SimulationEngine } from './simulationEngine';
import { GameRules } from './gameRules';
import { Command } from './command';
import { SimMain } from './sim/SimMain';
import { GdRoot } from './data/GdRoot';
import { SimCommand } from './sim/commands/SimCommand';
@Component({
selector: 'app-game',
@@ -12,10 +11,11 @@ import { Command } from './command';
imports: [OptionsComponent],
})
export class GameComponent implements OnInit {
engine!: SimulationEngine;
simMain!: SimMain;
private optionsOpen = false;
private isPaused = false;
private lastFrameTime = 0;
private tileSize = 10; // Size of each tile in pixels
@ViewChild('gameCanvas') canvasRef!: ElementRef<HTMLCanvasElement>;
@ViewChild('canvasWrapper') wrapperRef!: ElementRef<HTMLDivElement>;
@@ -26,93 +26,78 @@ export class GameComponent implements OnInit {
pixelScale: number = 1;
async ngOnInit() {
const initialState: GameState = {
tick: 0,
units: [{ id: 'u1', x: 0, y: 0 }],
commandHistory: [],
};
const json = await fetch('/assets/gameData.json').then((r) => r.json());
this.engine = new SimulationEngine(initialState, GameRules, json);
const gdRoot = await this.loadGdRoot();
this.simMain = new SimMain(gdRoot);
requestAnimationFrame(this.gameLoop.bind(this));
}
start() {
this.engine.start();
this.simMain.start(performance.now());
}
stop() {
this.engine.stop();
this.simMain.stop();
}
step() {
this.engine.step();
this.simMain.step();
}
rewind() {
this.engine.rewindToZero();
//this.simMain.rewindToZero();
}
fastForward() {
this.engine.fastForwardToEnd();
this.simMain.fastForwardToEnd();
}
async loadGdRoot(): Promise<GdRoot> {
const data = await fetch('/assets/data/gdRoot.json').then((r) => r.json());
// if (!isGdRoot(data)) {
// throw new Error("Invalid GdRoot!");
// }
const gdRoot: GdRoot = data;
return gdRoot;
}
async reloadGameData() {
const json = await fetch('/assets/gameData.json').then((r) => r.json());
this.engine.reinitializeWithNewData(json);
}
issueMove() {
const cmd: Command = {
step: this.engine.currentStep + 1,
type: 'move',
payload: { id: 'u1', dx: 1, dy: 0 },
};
this.engine.issueCommand(cmd);
this.simMain.setGdRoot(await this.loadGdRoot());
}
ngAfterViewInit(): void {
this.resizeCanvas();
requestAnimationFrame(this.render);
requestAnimationFrame(this.update);
window.addEventListener('resize', this.resizeCanvas.bind(this));
}
render = (now: number) => {
if (!this.engine) return;
update = (now: number) => {
if (!this.simMain) {
return;
}
const [prevState, currState] = this.engine.getRenderStates();
this.simMain.update(now)
// Compute interpolation factor (0..1)
const lastStepTime = this.engine.isRunning ? this.engine.lastStepTime : now;
const t = this.engine.isRunning
? Math.max(Math.min((now - lastStepTime) / this.engine.interval, 1), 0)
: 1; // fully render the last known state
const lastStepTime = this.simMain.isRunning
? this.simMain.lastStepTime
: now;
const t = this.simMain.isRunning
? Math.max(
Math.min((now - lastStepTime) / this.simMain.interval, 1),
0
)
: 1;
this.renderFrame(prevState, currState, t);
this.renderFrame(t);
requestAnimationFrame(this.render);
requestAnimationFrame(this.update);
};
renderFrame(prev: GameState, curr: GameState, t: number) {
renderFrame(t: number) {
const ctx = this.canvasRef.nativeElement.getContext('2d');
if (!ctx) return;
const canvas = this.canvasRef.nativeElement;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < curr.units.length; i++) {
const currUnit = curr.units[i];
const prevUnit =
prev.units.find((u) => u.id === currUnit.id) ?? currUnit;
const x = prevUnit.x + (currUnit.x - prevUnit.x) * t;
const y = prevUnit.y + (currUnit.y - prevUnit.y) * t;
ctx.fillStyle = 'cyan';
ctx.beginPath();
ctx.arc(x * 10, y * 10, 5, 0, Math.PI * 2);
ctx.fill();
}
}
resizeCanvas(): void {
@@ -149,6 +134,18 @@ export class GameComponent implements OnInit {
return [px / this.pixelScale, py / this.pixelScale];
}
onCanvasClick(event: MouseEvent) {
const canvas = this.canvasRef.nativeElement;
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const px = (event.clientX - rect.left) * scaleX;
const py = (event.clientY - rect.top) * scaleY;
const [gx, gy] = this.fromScreen(px, py);
}
private gameLoop(currentTime: number): void {
if (this.lastFrameTime === 0) {
this.lastFrameTime = currentTime;