2024-01-20 21:10:12 +08:00

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;
}
};