Skip to content

Commit 7891b33

Browse files
committedAug 22, 2016
Pathfinding library working
1 parent 024ab4f commit 7891b33

10 files changed

+356
-86
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules
22
dist
3+
.idea

‎src/constants.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const ROWS = 10;
2+
export const COLUMNS = ROWS;
3+
export const CELL_SIZE = 50;
4+
export const WORLD_WIDTH = COLUMNS * CELL_SIZE;
5+
export const WORLD_HEIGHT = ROWS * CELL_SIZE;

‎src/index.ts

+8-34
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,19 @@
11
/// <reference path="../node_modules/phaser/typescript/phaser.comments.d.ts" />
22
import { Game, AUTO } from 'phaser';
3-
import BootState from './states/boot-state';
4-
import PreloadState from './states/preload-state';
5-
import GameState from './states/game-state';
3+
import {
4+
WORLD_HEIGHT,
5+
WORLD_WIDTH,
6+
CELL_SIZE,
7+
} from './constants';
8+
import GameState from "./states/game-state";
69

710

811
window.addEventListener('load', () => gameStart());
912

1013

1114
function gameStart() {
12-
const size = getLandsacapeDimensions(700, 350);
13-
const game = new Game(size.width, size.height, AUTO);
14-
game.state.add('Boot', new BootState());
15-
game.state.add('Preload', new PreloadState());
15+
const game = new Game(WORLD_HEIGHT, WORLD_WIDTH, AUTO);
1616
game.state.add('Game', new GameState());
17-
game.state.start('Boot');
17+
game.state.start('Game');
1818
(<any>window).game = game;
1919
}
20-
21-
22-
function getLandsacapeDimensions(maxWidth: number, maxHeight: number): ISize {
23-
const x = window.innerWidth * window.devicePixelRatio;
24-
const y = window.innerHeight * window.devicePixelRatio;
25-
let width = Math.max(x, y);
26-
let height = Math.min(x, y);
27-
let ratio = 1;
28-
29-
if (width > maxWidth)
30-
ratio = maxWidth / width;
31-
32-
if (height > maxHeight)
33-
ratio = maxHeight / height;
34-
35-
width *= ratio;
36-
height *= ratio;
37-
38-
return { width, height };
39-
}
40-
41-
42-
interface ISize {
43-
width: number;
44-
height: number;
45-
}

‎src/prefabs/.gitkeep

Whitespace-only changes.

‎src/prefabs/cell.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import Sprite = Phaser.Sprite;
2+
import BitmapData = Phaser.BitmapData;
3+
import Point = Phaser.Point;
4+
import { INode } from "./grid";
5+
import Game = Phaser.Game;
6+
import Text = Phaser.Text;
7+
8+
9+
export default class Cell extends Sprite implements INode {
10+
gridPosition: Point;
11+
parentNode: Cell;
12+
gCost: number;
13+
hCost: number;
14+
private text: Text;
15+
16+
get fCost() {
17+
return this.gCost + this.hCost * 2;
18+
}
19+
20+
get isObstacle() {
21+
return this.weight === 0;
22+
}
23+
24+
constructor(
25+
game: Game,
26+
x: number,
27+
y: number,
28+
bitmap: BitmapData,
29+
gridX: number,
30+
gridY: number,
31+
public weight: number
32+
) {
33+
super(game, x, y, bitmap);
34+
this.gCost = 0;
35+
this.hCost = 0;
36+
this.gridPosition = new Point(gridX, gridY);
37+
38+
const styles = { font: '14px Arial', fill: '#00F' };
39+
this.text = this.game.add.text(this.x + this.width / 2, this.y + this.height / 2, '', styles);
40+
this.text.anchor.setTo(0.5);
41+
}
42+
43+
update() {
44+
super.update();
45+
this.text.text = `${this.fCost}\n${this.gCost}+${this.hCost}`;
46+
}
47+
48+
toString() {
49+
return `x:${this.gridPosition.x},y:${this.gridPosition.y}`;
50+
}
51+
}

‎src/prefabs/grid.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import Point = Phaser.Point;
2+
import { CELL_SIZE } from "../constants";
3+
4+
5+
export default class Grid<T extends INode> {
6+
private data: T[][];
7+
8+
constructor(public cols: number, public rows: number) {}
9+
10+
fill(creator: (row: number, col: number) => T) {
11+
this.data = [];
12+
13+
for (let i = 0; i < this.rows; i++) {
14+
this.data[ i ] = [];
15+
for (let j = 0; j < this.cols; j++)
16+
this.data[ i ][ j ] = creator(i, j);
17+
}
18+
}
19+
20+
getCell(x: number, y: number): T {
21+
const row = this.data[x];
22+
return row ? (row[y] || null) : null;
23+
}
24+
25+
forEach(iterator: (cell: T, x: number, y: number, grid: Grid<T>) => void) {
26+
for (let i = 0; i < this.rows; i++)
27+
for (let j = 0; j < this.cols; j++)
28+
iterator(this.data[i][j], i, j, this);
29+
}
30+
31+
getNodeFromPoint(point: Point) {
32+
const coords = point.clone().divide(CELL_SIZE, CELL_SIZE).floor();
33+
return this.getCell(coords.x, coords.y);
34+
}
35+
36+
getNeighbours(node: T): T[] {
37+
const x = node.gridPosition.x;
38+
const y = node.gridPosition.y;
39+
const neighbours = <T[]>[];
40+
41+
for (let i = -1; i <= 1; i++)
42+
for (let j = -1; j <= 1; j++)
43+
if ((i || j) && this.getCell(x + i, y + j))
44+
neighbours.push(this.getCell(x + i, j + y));
45+
46+
return neighbours;
47+
}
48+
}
49+
50+
51+
export interface INode {
52+
gridPosition: Point;
53+
isObstacle: boolean,
54+
weight: number,
55+
parentNode: INode,
56+
fCost: number,
57+
gCost: number,
58+
hCost: number,
59+
}

‎src/prefabs/pathfinding.ts

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import Grid, { INode } from "./grid";
2+
import Point = Phaser.Point;
3+
import Sprite = Phaser.Sprite;
4+
5+
6+
const DIAGONAL_COST = 1.4;
7+
8+
9+
export default class Pathfinding<T extends INode> {
10+
private start: T;
11+
private end: T;
12+
open: T[];
13+
closed: T[];
14+
private current: T;
15+
private neighbours: T[];
16+
private neighbourIndex: number;
17+
18+
constructor(
19+
private grid: Grid<T>,
20+
startPoint: Point,
21+
endPoint: Point
22+
) {
23+
this.start = grid.getNodeFromPoint(startPoint);
24+
this.end = grid.getNodeFromPoint(endPoint);
25+
this.open = <T[]>[];
26+
this.closed = <T[]>[];
27+
this.setOpen(this.start);
28+
}
29+
30+
next() {
31+
return this.neighbours ?
32+
this.processNeighbour() :
33+
this.processOpen();
34+
}
35+
36+
private processOpen() {
37+
let best = 0;
38+
39+
for (let i = 1; i < this.open.length; i++) {
40+
const bestNode = this.open[best];
41+
const entry = this.open[i];
42+
43+
if (entry.fCost < bestNode.fCost || (entry.fCost === bestNode.fCost && entry.hCost < bestNode.hCost))
44+
best = i;
45+
}
46+
47+
if (this.current)
48+
(<Sprite><any>this.current).tint = 0x888888;
49+
50+
this.current = this.open[best];
51+
this.setClosed(this.current, best);
52+
(<Sprite><any>this.current).tint = 0x00FFFF;
53+
54+
if (this.current === this.end)
55+
return Pathfinding.retrace(this.start, this.end);
56+
57+
this.neighbours = this.grid.getNeighbours(this.current);
58+
// console.log(`current x:${this.current.gridPosition.x} y:${this.current.gridPosition.y} neighbors:${this.neighbours.length}`);
59+
this.neighbourIndex = 0;
60+
61+
while (this.neighbours)
62+
this.processNeighbour();
63+
}
64+
65+
private processNeighbour() {
66+
const neighbour = this.neighbours[ this.neighbourIndex++ ];
67+
// console.log(`processsing x:${neighbour.gridPosition.x} y:${neighbour.gridPosition.y}`);
68+
69+
if (this.neighbourIndex >= this.neighbours.length)
70+
this.neighbours = null;
71+
72+
if (neighbour.isObstacle || this.closed.indexOf(neighbour) !== -1)
73+
return;
74+
75+
const movement = this.current.gCost + Pathfinding.getDistance(this.current, neighbour);
76+
if (movement < neighbour.gCost || this.open.indexOf(neighbour) === -1) {
77+
neighbour.gCost = movement;
78+
neighbour.hCost = Pathfinding.getDistance(neighbour, this.end);
79+
neighbour.parentNode = this.current;
80+
81+
if (this.open.indexOf(neighbour) === -1)
82+
this.setOpen(neighbour);
83+
}
84+
}
85+
86+
private setClosed(node: T, openIndex: number) {
87+
this.open.splice(openIndex, 1);
88+
this.closed.push(node);
89+
}
90+
91+
private setOpen(node: T) {
92+
this.open.push(node);
93+
(<Sprite><any>node).tint = 0x00FF00;
94+
}
95+
96+
97+
static findPath<U extends INode>(grid: Grid<U>, startPoint: Point, endPoint: Point): U[] {
98+
const start = grid.getNodeFromPoint(startPoint);
99+
const end = grid.getNodeFromPoint(endPoint);
100+
const open = [ start ];
101+
const closed: INode[] = [];
102+
103+
while (open.length) {
104+
let best = 0;
105+
106+
for (let i = 1; i < open.length; i++) {
107+
if (open[i].fCost < open[best].fCost || (open[i].fCost === open[best].fCost && open[i].hCost < open[best].hCost))
108+
best = i;
109+
}
110+
111+
const current = open.splice(best, 1)[0];
112+
closed.push(current);
113+
114+
if (current === end)
115+
return Pathfinding.retrace(start, end);
116+
117+
const neighbours = grid.getNeighbours(current);
118+
119+
for (let i = 0; i < neighbours.length; i++) {
120+
const neighbour = neighbours[i];
121+
if (neighbour.isObstacle || closed.indexOf(neighbour) !== -1)
122+
continue;
123+
124+
const movement = current.gCost + this.getDistance(current, neighbour);
125+
if (movement < neighbour.gCost || open.indexOf(neighbour) === -1) {
126+
neighbour.gCost = movement;
127+
neighbour.hCost = Pathfinding.getDistance(neighbour, end);
128+
neighbour.parentNode = current;
129+
130+
if (open.indexOf(neighbour) === -1)
131+
open.push(neighbour);
132+
}
133+
}
134+
}
135+
}
136+
137+
private static getDistance<U extends INode>(nodeA: U, nodeB: U) {
138+
const x = Math.abs(nodeA.gridPosition.x - nodeB.gridPosition.x);
139+
const y = Math.abs(nodeA.gridPosition.y - nodeB.gridPosition.y);
140+
141+
return x > y ?
142+
DIAGONAL_COST * 10 * y + 10 * (x - y) :
143+
DIAGONAL_COST * 10 * x + 10 * (y - x);
144+
}
145+
146+
private static retrace<U extends INode>(start: U, end: U) {
147+
const path: U[] = [];
148+
let current = end;
149+
150+
while (current !== start) {
151+
path.push(current);
152+
current = <U>current.parentNode;
153+
}
154+
155+
return path.reverse();
156+
}
157+
}

‎src/states/boot-state.ts

-23
This file was deleted.

0 commit comments

Comments
 (0)