Přidána typescriptová implementace
This commit is contained in:
parent
4fc86cf354
commit
fc2c8ac1aa
11 changed files with 773 additions and 0 deletions
asteracer-typescript
1
asteracer-typescript/.gitignore
vendored
Normal file
1
asteracer-typescript/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
node_modules
|
5
asteracer-typescript/README.md
Normal file
5
asteracer-typescript/README.md
Normal 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]`
|
34
asteracer-typescript/index.ts
Normal file
34
asteracer-typescript/index.ts
Normal 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
236
asteracer-typescript/package-lock.json
generated
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
asteracer-typescript/package.json
Normal file
17
asteracer-typescript/package.json
Normal 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"
|
||||
}
|
||||
}
|
72
asteracer-typescript/src/asteracer/grid.ts
Normal file
72
asteracer-typescript/src/asteracer/grid.ts
Normal 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;
|
||||
}
|
103
asteracer-typescript/src/asteracer/parse_map.ts
Normal file
103
asteracer-typescript/src/asteracer/parse_map.ts
Normal 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];
|
||||
}
|
241
asteracer-typescript/src/asteracer/simulation.ts
Normal file
241
asteracer-typescript/src/asteracer/simulation.ts
Normal 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("");
|
||||
}
|
45
asteracer-typescript/src/asteracer/types.ts
Normal file
45
asteracer-typescript/src/asteracer/types.ts
Normal 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[];
|
||||
};
|
3
asteracer-typescript/src/util.ts
Normal file
3
asteracer-typescript/src/util.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export function timeout(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
16
asteracer-typescript/tsconfig.json
Normal file
16
asteracer-typescript/tsconfig.json
Normal 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"]
|
||||
}
|
Loading…
Reference in a new issue