Přidána typescriptová implementace

This commit is contained in:
Jakub Pelc 2025-03-25 22:16:43 +01:00
parent 4fc86cf354
commit fc2c8ac1aa
11 changed files with 773 additions and 0 deletions

1
asteracer-typescript/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules

View file

@ -0,0 +1,5 @@
Použití:
- Instalujte si Node >= 22 (`nvm use 22` pokud máte nvm) (je potřeba pro přímé spouštění typescriptu bez kompilátoru)
- `npm install`
- `npm run app [název souboru mapy] [název souboru s instrukcemi]`

View file

@ -0,0 +1,34 @@
import { readFileSync } from "fs";
import { checkInstructions, simulate } from "./src/asteracer/simulation";
import { parseInstructions, parseMap } from "./src/asteracer/parse_map";
const worldFileName = process.argv[2];
const instructionsFileName = process.argv[3];
if (!worldFileName || !instructionsFileName) {
console.log("Použití: npm run app [název souboru mapy] [název souboru s instrukcemi]");
process.exit();
}
const worldFileText = readFileSync(worldFileName, { encoding: 'utf8', flag: 'r' });
const instructionsFileText = readFileSync(instructionsFileName, { encoding: 'utf8', flag: 'r' });
const world = parseMap(worldFileText);
const instructions = parseInstructions(instructionsFileText);
try {
checkInstructions(instructions);
} catch (e) {
console.error(e.message);
process.exit();
}
const simulationResult = await simulate(world, instructions);
if (simulationResult.messages) {
for (const msg of simulationResult.messages) {
console.log(msg);
}
} else {
console.log("Závod úspěšně dokončen!");
}

236
asteracer-typescript/package-lock.json generated Normal file
View file

@ -0,0 +1,236 @@
{
"name": "asteracer-simulation",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "asteracer-simulation",
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"@types/node": "^22.13.13",
"ts-node": "^10.9.2",
"typescript": "^5.7.3"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@tsconfig/node10": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.13.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz",
"integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/acorn": {
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-walk": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.11.0"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true,
"license": "MIT"
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true,
"license": "ISC"
},
"node_modules/ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-esm": "dist/bin-esm.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/typescript": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"license": "MIT"
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true,
"license": "MIT"
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
}
}
}

View file

@ -0,0 +1,17 @@
{
"name": "asteracer-simulation",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"app": "node --experimental-strip-types --experimental-specifier-resolution=node --loader ts-node/esm ./index.ts"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^22.13.13",
"ts-node": "^10.9.2",
"typescript": "^5.7.3"
}
}

View file

@ -0,0 +1,72 @@
import { Aabb, CircleId, Point } from "./types";
export interface IGrid {
//queryAlongLine(a: Point, b: Point, radius: number): Circle[];
queryCircle(p: Point, radius: number): CircleId[];
}
/**
* Stupid "acceleration" structure that just iterates over everything.
*/
export class Grid implements IGrid {
private _circles: CircleId[];
constructor(items: CircleId[]) {
this._circles = items;
}
public queryAlongLine(a: Point, b: Point, radius: number): CircleId[] {
const intersections: CircleId[] = [];
for (const c of this._circles) {
const dist = pointLineSegmentDistance(a, b, c);
if (dist < (c.radius + radius) * 1.5) {
intersections.push(c);
}
}
return intersections;
}
public queryCircle(p: Point, radius: number): CircleId[] {
const intersections: CircleId[] = [];
for (let i = 0; i < this._circles.length; i++) {
const c = this._circles[i];
const dx = (c.x - p.x) | 0;
const dy = (c.y - p.y) | 0;
const distSq = dx * dx + dy * dy;
const threshold = (radius + c.radius + 100) | 0;
if (distSq <= threshold * threshold) {
intersections.push(c);
}
}
return intersections;
}
}
function distanceF64(a: Point, b: Point): number {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
}
function pointLineSegmentDistance(lineA: Point, lineB: Point, point: Point): number {
const dirX = lineB.x - lineA.x;
const dirY = lineB.y - lineA.y;
const relativePointX = point.x - lineA.x;
const relativePointY = point.y - lineA.y;
const lineLength = Math.sqrt(dirX * dirX + dirY * dirY);
const distAlongLineClosestPoint = (dirX * relativePointX + dirY * relativePointY) / lineLength;
const clampedDistAlongLineClosestPoint = Math.min(Math.max(distAlongLineClosestPoint, 0), lineLength);
const closest = {
x: lineA.x + dirX * clampedDistAlongLineClosestPoint,
y: lineA.y + dirY * clampedDistAlongLineClosestPoint,
};
const distClosest = distanceF64(closest, point);
return distClosest;
}

View file

@ -0,0 +1,103 @@
import { Point, World } from "./types";
class Parser {
private _split: string[];
private _index: number = 0;
constructor(file: string) {
this._split = file.split(/\s+/);
}
public readInt(): number {
const int = parseInt(this._split[this._index]) | 0;
this._index++;
return int;
}
public end(): boolean {
return this._index === this._split.length - 1;
}
}
export function parseMap(file: string): World {
const input = new Parser(file);
const world: World = {
shipStartX: input.readInt(),
shipStartY: input.readInt(),
shipRadius: input.readInt(),
minX: input.readInt(),
minY: input.readInt(),
maxX: input.readInt(),
maxY: input.readInt(),
asteroids: [],
goals: [],
};
const asteroidCount = input.readInt();
for (let i = 0; i < asteroidCount; i++) {
world.asteroids.push({
x: input.readInt(),
y: input.readInt(),
radius: input.readInt(),
id: i,
type: "asteroid",
});
}
const goalCount = input.readInt();
for (let i = 0; i < goalCount; i++) {
world.goals.push({
x: input.readInt(),
y: input.readInt(),
radius: input.readInt(),
id: i,
type: "goal",
});
}
return world;
}
export function parseInstructions(file: string): Point[] {
const input = new Parser(file);
const instructions: Point[] = [];
const instructionCount = input.readInt();
for (let i = 0; i < instructionCount; i++) {
if (input.end()) {
throw new Error("V souboru je méně instrukcí, než kolik udává jeho první řádek!");
}
instructions.push({
x: input.readInt(),
y: input.readInt(),
});
}
return instructions;
}
export function parseLog(file: string): [Point[], Point[]] {
const expectedPositions: Point[] = [];
const expectedVelocity: Point[] = [];
const input = new Parser(file);
while(!input.end()) {
expectedPositions.push({
x: input.readInt(),
y: input.readInt(),
});
expectedVelocity.push({
x: input.readInt(),
y: input.readInt(),
});
input.readInt(); // ignore goals
}
return [expectedPositions, expectedVelocity];
}

View file

@ -0,0 +1,241 @@
import { timeout } from "../util";
import { Grid, IGrid } from "./grid";
import { Aabb, CircleId, Point, SimulationResult, TickResult, World } from "./types";
const dragSlowdownFraction = [9, 10];
const collisionSlowdownFraction = [1, 2];
const maxCollisionSubticks = 5;
function tick(gridAsteroids: IGrid, gridGoals: IGrid, worldAabb: Aabb, shipRadius: number, shipPositionStart: Point, shipVelocityStart: Point, instruction: Point): TickResult {
let velocityX = shipVelocityStart.x | 0;
let velocityY = shipVelocityStart.y | 0;
const r = shipRadius | 0;
// Zpomalení
velocityX = ((velocityX * (dragSlowdownFraction[0] | 0)) / (dragSlowdownFraction[1] | 0)) | 0;
velocityY = ((velocityY * (dragSlowdownFraction[0] | 0)) / (dragSlowdownFraction[1] | 0)) | 0;
// Instrukce
velocityX += instruction.x | 0;
velocityY += instruction.y | 0;
// Posun
let positionX = (shipPositionStart.x | 0) + velocityX;
let positionY = (shipPositionStart.y | 0) + velocityY;
// Kolize
let hadAnyCollision = false;
for (let subtick = 0; subtick < maxCollisionSubticks; subtick++) {
let collidedThisSubtick = false;
const asteroidCandidates = gridAsteroids.queryCircle({
x: positionX,
y: positionY,
}, r);
for (let i = 0; i < asteroidCandidates.length; i++) {
const c = asteroidCandidates[i];
const dx = (positionX - c.x) | 0; // od středu asteroidu ke středu lodi
const dy = (positionY - c.y) | 0;
const distSq = dx * dx + dy * dy;
const dist = Math.sqrt(distSq) | 0;
const threshold = (r + c.radius) | 0;
if (dist > threshold) {
// Kolize nenastala
continue;
}
// Kolidujeme
const pushBy = (c.radius + r) - dist;
positionX += (((pushBy * dx) | 0) / dist) | 0;
positionY += (((pushBy * dy) | 0) / dist) | 0;
collidedThisSubtick = true;
break;
}
// Kolize s okraji
if (positionX - r < worldAabb.minX) {
positionX = (worldAabb.minX | 0) + r;
collidedThisSubtick = true;
}
if (positionY - r < worldAabb.minY) {
positionY = (worldAabb.minY | 0) + r;
collidedThisSubtick = true;
}
if (positionX + r > worldAabb.maxX) {
positionX = (worldAabb.maxX | 0) - r;
collidedThisSubtick = true;
}
if (positionY + r > worldAabb.maxY) {
positionY = (worldAabb.maxY | 0) - r;
collidedThisSubtick = true;
}
if (collidedThisSubtick) {
hadAnyCollision = true;
} else {
break;
}
}
// Zpomalení v důsledku kolize
if (hadAnyCollision) {
velocityX = ((velocityX * (collisionSlowdownFraction[0] | 0)) / (collisionSlowdownFraction[1] | 0)) | 0;
velocityY = ((velocityY * (collisionSlowdownFraction[0] | 0)) / (collisionSlowdownFraction[1] | 0)) | 0;
}
// Kolize s cíli
let goalsIntersected: CircleId[] = [];
const goalCandidates = gridGoals.queryCircle({
x: positionX,
y: positionY,
}, r);
for (let i = 0; i < goalCandidates.length; i++) {
const c = goalCandidates[i];
const dx = (positionX - c.x) | 0; // od středu cíle ke středu lodi
const dy = (positionY - c.y) | 0;
const distSq = dx * dx + dy * dy;
const dist = Math.sqrt(distSq) | 0;
const threshold = (r + c.radius) | 0;
if (dist > threshold) {
// Kolize nenastala
continue;
}
// Kolidujeme
goalsIntersected.push(c);
}
return {
shipPositionStart: {
x: shipPositionStart.x,
y: shipPositionStart.y,
},
shipPositionEnd: {
x: positionX,
y: positionY,
},
shipVelocityStart: {
x: shipVelocityStart.x,
y: shipVelocityStart.y,
},
shipVelocityEnd: {
x: velocityX,
y: velocityY,
},
goalsIntersected,
};
}
/**
* Throws if any instruction is invalid (too large acceleration).
*/
export function checkInstructions(instructions: Point[]): void {
for (const i of instructions) {
const x = i.x | 0;
const y = i.y | 0;
const lenSq = x * x + y * y;
if (lenSq > 127 * 127) {
throw new Error(`Instrukce x=${i.x} y=${i.y} přesáhla povolené zrychlení! (${Math.sqrt(lenSq)} > 127)`);
}
}
}
export async function simulate(world: World, instructions: Point[], periodicYield?: boolean, onYield?: (processedInstructions: number) => void): Promise<SimulationResult> {
const gridAsteroids = new Grid(world.asteroids);
const gridGoals = new Grid(world.goals);
const ticks: TickResult[] = [];
let latestPosition: Point = {
x: world.shipStartX,
y: world.shipStartY,
};
let latestVelocity: Point = {
x: 0,
y: 0,
};
let tickCount = 0;
let goalsReached = 0;
let reachedGoalIds: Set<number> = new Set();
let allGoalsReachedTick: number = undefined;
for (let index = 0; index < instructions.length; index++) {
const instruction = instructions[index];
const result = tick(gridAsteroids, gridGoals, world, world.shipRadius, latestPosition, latestVelocity, instruction);
latestPosition.x = result.shipPositionEnd.x;
latestPosition.y = result.shipPositionEnd.y;
latestVelocity.x = result.shipVelocityEnd.x;
latestVelocity.y = result.shipVelocityEnd.y;
tickCount++;
ticks.push(result);
for (const goal of result.goalsIntersected) {
if (!reachedGoalIds.has(goal.id)) {
reachedGoalIds.add(goal.id)
goalsReached++;
}
}
if (!allGoalsReachedTick && goalsReached >= world.goals.length) {
allGoalsReachedTick = index;
}
if (periodicYield && index > 0 && index % 5000 === 0) {
if (onYield) {
onYield(index);
}
await timeout(0);
}
}
const messages: string[] = [];
if (!allGoalsReachedTick && world.goals.length > 0) {
messages.push("Nebyly dosaženy všechny cíle!");
}
if (allGoalsReachedTick && allGoalsReachedTick < instructions.length - 1) {
messages.push("Vaše instrukce pokračují i po dosažení posledního cíle! Instrukce po dosažení posledního cíle můžete bezpečně smazat a tím si vylepšit čas.");
}
return {
goalsReached: reachedGoalIds,
ticksExecuted: tickCount,
tickResults: ticks,
messages
};
}
export function generateLog(world: World, result: SimulationResult): string {
const chunks: string[] = [];
let goalStates = [];
for (let i = 0; i < world.goals.length; i++) {
goalStates.push(0);
}
for (const tick of result.tickResults) {
for (const goal of tick.goalsIntersected) {
goalStates[goal.id] = 1;
}
chunks.push(tick.shipPositionEnd.x.toString());
chunks.push(" ");
chunks.push(tick.shipPositionEnd.y.toString());
chunks.push(" ");
chunks.push(tick.shipVelocityEnd.x.toString());
chunks.push(" ");
chunks.push(tick.shipVelocityEnd.y.toString());
chunks.push(" ");
for (let i = 0; i < goalStates.length; i++) {
chunks.push(goalStates[i].toString());
}
chunks.push("\n");
}
return chunks.join("");
}

View file

@ -0,0 +1,45 @@
export type Aabb = {
minX: number;
minY: number;
maxX: number;
maxY: number;
};
export type Circle = {
x: number;
y: number;
radius: number;
};
export type CircleId = Circle &{
id: number;
type: 'asteroid' | 'goal';
}
export type World = Aabb & {
shipStartX: number;
shipStartY: number;
shipRadius: number;
asteroids: CircleId[];
goals: CircleId[];
};
export type Point = {
x: number;
y: number;
};
export type TickResult = {
shipPositionStart: Point;
shipVelocityStart: Point;
shipPositionEnd: Point;
shipVelocityEnd: Point;
goalsIntersected: CircleId[];
};
export type SimulationResult = {
ticksExecuted: number;
goalsReached: Set<number>;
tickResults: TickResult[];
messages: string[];
};

View file

@ -0,0 +1,3 @@
export function timeout(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}

View file

@ -0,0 +1,16 @@
{
"compilerOptions": {
"noImplicitAny": true,
"lib": [ "dom", "esnext" ],
"module": "esnext",
"target": "ES2017",
"strict": false,
"moduleDetection": "force",
"moduleResolution": "node",
},
"exclude": [
"node_modules",
"static"
],
"include": ["src/**/*.ts"]
}