Skip to content

Commit d576473

Browse files
committedJul 20, 2022
Last working version
1 parent 089fc08 commit d576473

File tree

7 files changed

+7428
-137
lines changed

7 files changed

+7428
-137
lines changed
 

‎index.html

+7-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@
22
<html>
33
<head>
44
<meta charste="utf-8" />
5-
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" />
5+
<meta
6+
name="viewport"
7+
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"
8+
/>
69
<title>Test</title>
710
<style>
8-
html, body {
11+
html,
12+
body {
913
height: 100%;
1014
padding: 0;
1115
margin: 0;
16+
background: black;
1217
}
1318
</style>
1419
</head>

‎package-lock.json

+7,291
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+5-4
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
"description": "",
55
"main": "index.html",
66
"dependencies": {
7-
"pixi.js": "^4.3.2"
7+
"pixi.js": "4.3.2"
88
},
99
"devDependencies": {
10-
"@types/pixi.js": "^4.3.2",
11-
"ts-loader": "^2.0.1",
12-
"typescript": "^2.2.1"
10+
"@types/pixi.js": "4.3.2",
11+
"ts-loader": "2.0.1",
12+
"typescript": "2.2.1",
13+
"webpack": "2.4.1"
1314
},
1415
"scripts": {
1516
"test": "echo \"Error: no test specified\" && exit 1"

‎src/config.ts

+9-13
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,29 @@
11
export class Color {
2-
public static readonly TILE = new Color(0x00FF00);
2+
public static readonly TILE = new Color(0x101010);
33
public static readonly CLUSTER = [
4-
new Color(0xFF0000),
5-
new Color(0x0000FF),
6-
new Color(0xFFFF00),
7-
new Color(0xFF00FF),
8-
new Color(0x00FFFF),
4+
new Color(0x0000ff),
5+
new Color(0x0000ff),
6+
new Color(0xffff00),
7+
new Color(0xff00ff),
8+
new Color(0x00ffff),
99
];
1010

11-
1211
private constructor(public value: number) {}
1312

14-
1513
toString(): string {
1614
let value = this.value.toString(16);
17-
18-
while (value.length < 6)
19-
value = '0' + value;
15+
16+
while (value.length < 6) value = '0' + value;
2017

2118
return `#${value}`;
2219
}
2320
}
2421

25-
2622
export enum Side {
2723
NORTH,
2824
SOUTH,
2925
EAST,
3026
WEST,
3127
// UP,
3228
// DOWN,
33-
}
29+
}

‎src/constants.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ export const PERF_ITERATIONS = 0;
88
export const RANDOM_PATH_INTERVAL = 500;
99
export const DRAW_GRID = true;
1010
export const DRAW_CLUSTERS = true;
11-
export const START_COLOR = 'purple';
12-
export const END_COLOR = 'purple';
13-
export const PATH_COLOR = 'green';
11+
export const START_COLOR = 'white';
12+
export const END_COLOR = 'white';
13+
export const PATH_COLOR = 'yellow';
1414
export const GRID_MULTIPLIER = 1;
15-
export const LOG_AVERAGE = false;
15+
export const LOG_AVERAGE = false;

‎src/index.ts

+79-84
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
import { drawSquare } from "./utils";
2-
import { Color } from "./config";
3-
import MAP_DATA from "./map-data";
4-
import AStar from "./pathfinding/a-star";
5-
import { World } from "./world/world";
6-
import Tile from "./world/tile";
7-
import { Pathfinding } from "./pathfinding/pathfinding";
8-
import * as constants from "./constants";
9-
import Vector from "./core/vector";
10-
1+
import { Color } from './config';
2+
import * as constants from './constants';
3+
import Vector from './core/vector';
4+
import MAP_DATA from './map-data';
5+
import AStar from './pathfinding/a-star';
6+
import { Pathfinding } from './pathfinding/pathfinding';
7+
import { drawSquare } from './utils';
8+
import Tile from './world/tile';
9+
import { World } from './world/world';
1110

1211
console.log(JSON.stringify(constants, null, ' '));
1312

14-
1513
let world: World;
1614
let aStar: AStar<Tile>;
1715
let pathfinding: Pathfinding;
@@ -20,33 +18,36 @@ let ctx: CanvasRenderingContext2D;
2018
let prevPath: IPrintablePath = null;
2119
const timeCost = [] as number[];
2220

23-
2421
init();
25-
const failure = [ world.get(25, 0), world.get(56, 25) ];
26-
const successful = [ world.get(25, 2), world.get(33, 30) ];
22+
const failure = [world.get(25, 0), world.get(56, 25)];
23+
const successful = [world.get(25, 2), world.get(33, 30)];
2724

28-
29-
if (constants.PERF_ITERATIONS)
30-
performanceTest(constants.PERF_ITERATIONS);
25+
if (constants.PERF_ITERATIONS) performanceTest(constants.PERF_ITERATIONS);
3126

3227
frame();
3328

3429
if (constants.RANDOM_PATH_INTERVAL)
35-
setInterval(randomPath, constants.RANDOM_PATH_INTERVAL)
36-
30+
setInterval(randomPath, constants.RANDOM_PATH_INTERVAL);
3731

3832
function init() {
39-
measure(() => {
40-
const data = constants.GRID_MULTIPLIER ?
41-
multiplyGrid(MAP_DATA, constants.GRID_MULTIPLIER) :
42-
MAP_DATA;
43-
44-
world = new World(data, constants.TILE_SIZE, constants.DIAGONAL_MOVEMENT_COST);
45-
aStar = new AStar<Tile>(constants.CLOSER_MODIFIER);
46-
pathfinding = new Pathfinding(world, aStar, constants.CLUSTER_SIZE);
47-
}, {
48-
message: 'INIT',
49-
});
33+
measure(
34+
() => {
35+
const data = constants.GRID_MULTIPLIER
36+
? multiplyGrid(MAP_DATA, constants.GRID_MULTIPLIER)
37+
: MAP_DATA;
38+
39+
world = new World(
40+
data,
41+
constants.TILE_SIZE,
42+
constants.DIAGONAL_MOVEMENT_COST
43+
);
44+
aStar = new AStar<Tile>(constants.CLOSER_MODIFIER);
45+
pathfinding = new Pathfinding(world, aStar, constants.CLUSTER_SIZE);
46+
},
47+
{
48+
message: 'INIT',
49+
}
50+
);
5051

5152
canvas = document.querySelector('canvas');
5253
ctx = canvas.getContext('2d');
@@ -55,22 +56,20 @@ function init() {
5556
ctx.translate(constants.TILE_SIZE, constants.TILE_SIZE);
5657
}
5758

58-
5959
function frame() {
6060
ctx.clearRect(0, 0, canvas.width, canvas.height);
61-
world.forEach(tile => tile.print(ctx, constants.DRAW_GRID));
6261

63-
if (constants.DRAW_CLUSTERS)
64-
drawClusters();
65-
62+
if (constants.DRAW_CLUSTERS) drawClusters();
63+
64+
world.forEach((tile) => tile.print(ctx, constants.DRAW_GRID));
65+
6666
requestAnimationFrame(frame);
6767
}
6868

69-
7069
function drawClusters() {
7170
pathfinding.forEach((cluster, pathfinding) => {
7271
const depth = 0;
73-
const size = (constants.CLUSTER_SIZE ** (depth + 1)) * constants.TILE_SIZE;
72+
const size = constants.CLUSTER_SIZE ** (depth + 1) * constants.TILE_SIZE;
7473
const coords = cluster.location.multiply(size);
7574

7675
drawSquare(ctx, coords.x, coords.y, size, {
@@ -80,56 +79,58 @@ function drawClusters() {
8079
});
8180
}
8281

83-
8482
function randomPath() {
85-
if (prevPath)
86-
prevPath.remove();
87-
88-
const start = new Vector(random(world.size.x), random(world.size.y));
89-
const end = new Vector(random(world.size.x), random(world.size.y));
90-
console.log(`drawPath(world.get(${start.x}, ${start.y}), world.get(${end.x}, ${end.y}))`);
91-
92-
prevPath = drawPath(
93-
world.get(start.x, start.y),
94-
world.get(end.x, end.y),
83+
if (prevPath) prevPath.remove();
84+
85+
let start, end;
86+
87+
do {
88+
start = new Vector(random(world.size.x), random(world.size.y));
89+
} while (world.get(start.x, start.y).isObstacle);
90+
91+
do {
92+
end = new Vector(random(world.size.x), random(world.size.y));
93+
} while (world.get(end.x, end.y).isObstacle);
94+
95+
console.log(
96+
`drawPath(world.get(${start.x}, ${start.y}), world.get(${end.x}, ${end.y}))`
9597
);
96-
}
9798

99+
prevPath = drawPath(world.get(start.x, start.y), world.get(end.x, end.y));
100+
}
98101

99102
function drawPath(from: Tile, to: Tile): IPrintablePath {
100-
const {
101-
duration,
102-
result,
103-
} = measure(() => pathfinding.resolve(from, to) || { tiles: [] as Tile[] }, {
104-
log: false,
105-
});
103+
const { duration, result } = measure(
104+
() => pathfinding.resolve(from, to) || { tiles: [] as Tile[] },
105+
{
106+
log: false,
107+
}
108+
);
106109

107-
for (const tile of result.tiles)
108-
tile.color = constants.PATH_COLOR;
110+
for (const tile of result.tiles) tile.color = constants.PATH_COLOR;
109111

110112
from.color = constants.START_COLOR;
111113
to.color = constants.END_COLOR;
112-
114+
113115
(result as any).remove = () => {
114116
from.color = null;
115117
to.color = null;
116118

117-
for (const tile of result.tiles)
118-
tile.color = null;
119-
}
119+
for (const tile of result.tiles) tile.color = null;
120+
};
120121

121122
if (constants.LOG_AVERAGE) {
122123
timeCost.push(duration);
123-
const average = timeCost.reduce((prev, current) => prev + current) / timeCost.length;
124+
const average =
125+
timeCost.reduce((prev, current) => prev + current) / timeCost.length;
124126
console.log('AVERAGE A*', average, result.tiles.length);
125127
}
126-
128+
127129
return result as IPrintablePath;
128130
}
129131

130-
131132
function performanceTest(repetitions: number) {
132-
const cases: { [key: string]: () => void } = {
133+
const cases: { [key: string]: () => void } = {
133134
'successful non-hierarchical': () => {
134135
for (let i = 0; i < repetitions; i++)
135136
aStar.getPath(successful[0], successful[1]);
@@ -148,31 +149,26 @@ function performanceTest(repetitions: number) {
148149
},
149150
};
150151

151-
for (const key of Object.keys(cases))
152-
measure(cases[key], { message: key });
152+
for (const key of Object.keys(cases)) measure(cases[key], { message: key });
153153
}
154154

155-
156155
function random(max: number = 1, min: number = 0) {
157-
return Math.floor(Math.random() * (max - min)) + min
156+
return Math.floor(Math.random() * (max - min)) + min;
158157
}
159158

160-
161-
function measure(operation: Function, {
162-
message = 'Operation',
163-
log = true,
164-
} = {}) {
159+
function measure(
160+
operation: Function,
161+
{ message = 'Operation', log = true } = {}
162+
) {
165163
const before = performance.now();
166164
const result = operation();
167165
const after = performance.now();
168166
const duration = after - before;
169167

170-
if (log)
171-
console.log(`${message} = ${duration}ms`);
172-
173-
return { duration, result };
174-
}
168+
if (log) console.log(`${message} = ${duration}ms`);
175169

170+
return { duration, result };
171+
}
176172

177173
function multiplyGrid(grid: number[][], multiplier: number) {
178174
if (multiplier === 0) return [];
@@ -182,20 +178,19 @@ function multiplyGrid(grid: number[][], multiplier: number) {
182178

183179
grid.forEach((row, y) => {
184180
for (let i = 0; i < multiplier; i++)
185-
result[y * multiplier + i] = [] as number[];
186-
181+
result[y * multiplier + i] = [] as number[];
182+
187183
row.forEach((value, x) => {
188184
for (let i = 0; i < multiplier; i++)
189185
for (let j = 0; j < multiplier; j++)
190-
result[y * multiplier + i][x * multiplier + j] = value;
186+
result[y * multiplier + i][x * multiplier + j] = value;
191187
});
192188
});
193189

194190
return result;
195191
}
196192

197-
198193
interface IPrintablePath {
199194
tiles: Tile[];
200195
remove(): void;
201-
}
196+
}

‎src/world/tile.ts

+33-30
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
1-
import { IPrintable } from "../interfaces";
2-
import { drawSquare, fillSquare, round } from "../utils";
3-
import { Color } from "../config";
4-
import { INode, INodeRelation } from "../pathfinding/i-node";
5-
import { World } from "./world";
6-
import { SubclassExpectedError } from "../pathfinding/errors";
7-
import { IArea } from "../pathfinding/i-area";
8-
import Vector from "../core/vector";
9-
1+
import { Color } from '../config';
2+
import Vector from '../core/vector';
3+
import { IPrintable } from '../interfaces';
4+
import { SubclassExpectedError } from '../pathfinding/errors';
5+
import { IArea } from '../pathfinding/i-area';
6+
import { INode, INodeRelation } from '../pathfinding/i-node';
7+
import { drawSquare, round } from '../utils';
8+
import { World } from './world';
109

1110
export default class Tile implements IPrintable, INode {
1211
private world: World;
1312
id: string;
1413
color: string;
1514
content: string;
1615

17-
1816
constructor(
1917
public readonly location: Vector,
2018
public readonly size: number,
2119
private _travelCost: number,
22-
private readonly diagonalMovementCost: number,
20+
private readonly diagonalMovementCost: number
2321
) {}
2422

25-
2623
get isObstacle(): boolean {
2724
return this.travelCost === 1;
2825
}
@@ -31,7 +28,6 @@ export default class Tile implements IPrintable, INode {
3128
return this._travelCost;
3229
}
3330

34-
3531
setWorld(world: World) {
3632
this.world = world;
3733
}
@@ -42,40 +38,42 @@ export default class Tile implements IPrintable, INode {
4238

4339
for (const neighbor of neighbors)
4440
if (!neighbor.isObstacle || neighbor === this)
45-
result.set(neighbor, { cost: this.isAdjacent(neighbor) ? 1 : this.diagonalMovementCost });
41+
result.set(neighbor, {
42+
cost: this.isAdjacent(neighbor) ? 1 : this.diagonalMovementCost,
43+
});
4644

4745
return result;
4846
}
4947

5048
getCostTo(neighbor: INode): number {
5149
const tile = neighbor as Tile;
5250

53-
if (this === neighbor)
54-
return 0;
51+
if (this === neighbor) return 0;
52+
53+
if (this.isAdjacent(tile)) return 1;
54+
55+
if (this.isDiagonal(tile)) return this.diagonalMovementCost;
5556

56-
if (this.isAdjacent(tile))
57-
return 1;
58-
59-
if (this.isDiagonal(tile))
60-
return this.diagonalMovementCost;
61-
6257
debugger;
6358
throw new Error('Argument should be a neighbor');
6459
}
6560

6661
estimateDistanceTo(tile: Tile): number {
6762
const diff = Vector.diff(this.location, tile.location).abs();
6863

69-
const layerMovement = diff.x > diff.y ?
70-
this.diagonalMovementCost * 10 * diff.y + 10 * (diff.x - diff.y) :
71-
this.diagonalMovementCost * 10 * diff.x + 10 * (diff.y - diff.x);
64+
const layerMovement =
65+
diff.x > diff.y
66+
? this.diagonalMovementCost * 10 * diff.y + 10 * (diff.x - diff.y)
67+
: this.diagonalMovementCost * 10 * diff.x + 10 * (diff.y - diff.x);
7268

7369
return round(layerMovement); // + z * LAYER_CHANGE_COST;
7470
}
7571

7672
isNeighbor(other: INode): boolean {
7773
if (!(other instanceof Tile))
78-
throw new SubclassExpectedError(`Expected Tile but ${other.constructor.name} found`);
74+
throw new SubclassExpectedError(
75+
`Expected Tile but ${other.constructor.name} found`
76+
);
7977

8078
return this.isAdjacent(other) || this.isDiagonal(other);
8179
}
@@ -85,29 +83,34 @@ export default class Tile implements IPrintable, INode {
8583
}
8684

8785
isDiagonal(other: Tile) {
88-
return Vector.diff(this.location, other.location).magnitude === Vector.round(Math.SQRT2);
86+
return (
87+
Vector.diff(this.location, other.location).magnitude ===
88+
Vector.round(Math.SQRT2)
89+
);
8990
}
9091

9192
print(ctx: CanvasRenderingContext2D, drawGrid?: boolean): void {
9293
const x = this.location.x * this.size;
9394
const y = this.location.y * this.size;
9495

96+
const obstacleColor = 'purple';
97+
9598
if (drawGrid) {
9699
drawSquare(ctx, x, y, this.size, {
97-
color: Color.TILE.toString(),
100+
color: this.isObstacle ? obstacleColor : Color.TILE.toString(),
98101
});
99102
}
100103

101104
if (this.isObstacle) {
102-
ctx.fillStyle = 'black';
105+
ctx.fillStyle = obstacleColor;
103106
ctx.fillRect(x, y, this.size, this.size);
104107
}
105108

106109
if (this.color) {
107110
ctx.fillStyle = this.color;
108111
ctx.fillRect(x, y, this.size, this.size);
109112
}
110-
113+
111114
if (this.content) {
112115
ctx.font = '14px Arial';
113116
ctx.textAlign = 'center';

0 commit comments

Comments
 (0)
Please sign in to comment.