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