208 lines
3.8 KiB
JavaScript
208 lines
3.8 KiB
JavaScript
import { Vec2 } from './vec2.js';
|
|
import { Queue } from './queue.js';
|
|
|
|
export const N = 20;
|
|
export const BSIZE = new Vec2(N, N);
|
|
|
|
export const SQR_TYPE = {
|
|
WALL: 0,
|
|
SPACE: 1,
|
|
EXCEED: 2,
|
|
};
|
|
|
|
export class State {
|
|
constructor(player, box) {
|
|
this.player = player ?? new Vec2(0, 0);
|
|
this.box = box ?? new Vec2(1, 1);
|
|
}
|
|
|
|
asHash() {
|
|
return this.player.x + this.player.y * N
|
|
+ this.box.x * N * N + this.box.y * N * N * N;
|
|
}
|
|
|
|
isValid() {
|
|
return !this.player.equals(this.box) && this.player.isInside(Vec2.zero, BSIZE)
|
|
&& this.box.isInside(Vec2.zero, BSIZE);
|
|
}
|
|
|
|
isValidInMaze(maze) {
|
|
return this.isValid() && maze.getV(this.player) && maze.getV(this.box);
|
|
}
|
|
|
|
clone() {
|
|
return new State(this.player.clone(), this.box.clone());
|
|
}
|
|
|
|
toString() {
|
|
return `[State: player ${this.player.toString()}, box ${this.box.toString()}]`;
|
|
}
|
|
|
|
static fromRaw(o) {
|
|
return new State(Vec2.fromRaw(o.player), Vec2.fromRaw(o.box));
|
|
}
|
|
}
|
|
|
|
export class Maze {
|
|
constructor() {
|
|
this.mp = new Array(N * N).fill(SQR_TYPE.SPACE);
|
|
this.target = new Vec2(0, 0);
|
|
this.init = new State();
|
|
}
|
|
|
|
static fromRaw(o) {
|
|
let res = new Maze();
|
|
res.mp = o.mp;
|
|
res.target = Vec2.fromRaw(o.target);
|
|
res.init = State.fromRaw(o.init);
|
|
return res;
|
|
}
|
|
|
|
get(x, y) {
|
|
return this.mp[x * N + y];
|
|
}
|
|
|
|
getV(v) {
|
|
return this.get(v.x, v.y);
|
|
}
|
|
|
|
set(x, y, v) {
|
|
this.mp[x * N + y] = v;
|
|
return this;
|
|
}
|
|
|
|
setV(p, v) {
|
|
return this.set(p.x, p.y, v);
|
|
}
|
|
|
|
flip(x, y) {
|
|
this.mp[x * N + y] ^= 1;
|
|
return this;
|
|
}
|
|
|
|
flipV(p) {
|
|
return this.flip(p.x, p.y);
|
|
}
|
|
|
|
exportCharMap(s) {
|
|
let state = s ?? this.init;
|
|
let res = new Array(N);
|
|
for (let i = 0; i < N; ++i) {
|
|
let line = [];
|
|
for (let j = 0; j < N; ++j) {
|
|
let c = '.';
|
|
if (this.get(i, j) == SQR_TYPE.WALL) {
|
|
c = '#';
|
|
}
|
|
|
|
if (this.target.match(i, j)) {
|
|
c = 'O';
|
|
} else if (state.player.match(i, j)) {
|
|
c = 'P';
|
|
} else if (state.target.match(i, j)) {
|
|
c = '*';
|
|
}
|
|
line.push(c);
|
|
}
|
|
res[i] = line.join('');
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static fromCharMap(s) {
|
|
let res = new Maze();
|
|
for (let i = 0; i < N; ++i) {
|
|
for (let j = 0; j < N; ++j) {
|
|
if (s[i][j] == '#') {
|
|
res.set(i, j, SQR_TYPE.WALL);
|
|
} else {
|
|
res.set(i, j, SQR_TYPE.SPACE);
|
|
}
|
|
|
|
if (s[i][j] == 'O') {
|
|
res.target = new Vec2(i, j);
|
|
} else if (s[i][j] == 'P') {
|
|
res.init.player = new Vec2(i, j);
|
|
} else if (s[i][j] == '*') {
|
|
res.init.box = new Vec2(i, j);
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
};
|
|
|
|
export class AnalyzeContext {
|
|
constructor(maze) {
|
|
this.maze = maze;
|
|
this.dis = new Array(N * N * N * N).fill(-1);
|
|
this.source = new Array(N * N * N * N).fill(null);
|
|
this.dis[maze.init.asHash()] = 0;
|
|
|
|
this.is_bfs_done = false;
|
|
this.step = -1;
|
|
this.path = null;
|
|
}
|
|
|
|
asResult() {
|
|
return {
|
|
is_bfs_done: this.is_bfs_done,
|
|
dis: this.dis,
|
|
source: this.source,
|
|
step: this.step,
|
|
path: this.path,
|
|
};
|
|
}
|
|
|
|
bfs() {
|
|
const dt = [new Vec2(0, 1), new Vec2(0, -1), new Vec2(1, 0), new Vec2(-1, 0)];
|
|
|
|
if (this.is_bfs_done) {
|
|
return this.step;
|
|
}
|
|
|
|
let end_state = null;
|
|
let q = new Queue();
|
|
q.push(this.maze.init.clone());
|
|
while (q.size()) {
|
|
let x = q.front();
|
|
q.pop();
|
|
|
|
let dis = this.dis[x.asHash()];
|
|
if (x.box.equals(this.maze.target) && this.step == -1) {
|
|
this.step = dis;
|
|
end_state = x;
|
|
}
|
|
|
|
for (let d of dt) {
|
|
let y = x.clone();
|
|
y.player.addTo(d);
|
|
if (y.player.equals(y.box)) {
|
|
y.box.addTo(d);
|
|
}
|
|
|
|
let yh = y.asHash();
|
|
if (y.isValidInMaze(this.maze) && this.dis[yh] == -1) {
|
|
this.dis[yh] = dis + 1;
|
|
this.source[yh] = x.clone();
|
|
q.push(y);
|
|
}
|
|
}
|
|
}
|
|
this.is_bfs_done = true;
|
|
|
|
if (this.step != -1) {
|
|
this.path = [];
|
|
let p = end_state;
|
|
while (p != null) {
|
|
this.path.push(p);
|
|
p = this.source[p.asHash()];
|
|
}
|
|
this.path.reverse();
|
|
}
|
|
|
|
return this.step;
|
|
}
|
|
};
|
|
|