Compare commits
	
		
			2 commits
		
	
	
		
			cd93ccb063
			...
			fc2c8ac1aa
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fc2c8ac1aa | |||
| 4fc86cf354 | 
					 12 changed files with 774 additions and 1 deletions
				
			
		
							
								
								
									
										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"] | ||||||
|  | } | ||||||
|  | @ -121,7 +121,7 @@ Iterujeme přes všechny cíle `goal` a označíme je jako dosažené, pokud `eu | ||||||
| Všimněte si, že simulace má několik neintuitivních zjednodušení a vlastností: | Všimněte si, že simulace má několik neintuitivních zjednodušení a vlastností: | ||||||
| 
 | 
 | ||||||
| - Při úvodním posunu lodi o její rychlost **nekontrolujeme** kolize. | - Při úvodním posunu lodi o její rychlost **nekontrolujeme** kolize. | ||||||
| - Kolize kontrolujeme až jako průsečík finální pozice lodi s asteroidy či cíle. | - Kolize kontrolujeme až jako průsečík finální pozice lodi s asteroidy či cíly. | ||||||
| - Při kolizi s asteroidem se loď **neodrazí**, tedy nezmění se směr její rychlosti. | - Při kolizi s asteroidem se loď **neodrazí**, tedy nezmění se směr její rychlosti. | ||||||
| - Při řešení kolizí loď pouze posouváme ven z asteroidů. | - Při řešení kolizí loď pouze posouváme ven z asteroidů. | ||||||
| - Při kolizi nezáleží na tom, v jaké fázi řešení podkroků vydělíme rychlost lodi dvěma. | - Při kolizi nezáleží na tom, v jaké fázi řešení podkroků vydělíme rychlost lodi dvěma. | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue