Compare commits
3 Commits
ddf734aed6
...
94cd28d403
Author | SHA1 | Date | |
---|---|---|---|
94cd28d403 | |||
475208def0 | |||
893ebef103 |
9
Makefile
Normal file
9
Makefile
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
CC=g++
|
||||||
|
INCLUDE_DIR=include
|
||||||
|
CFLAGS=-std=c++17 -fopenmp -Wall -O3 -march=native -I $(INCLUDE_DIR)
|
||||||
|
|
||||||
|
box-eater: optimizer/box-eater.cpp
|
||||||
|
$(CC) $(CFLAGS) -o $@ $^
|
||||||
|
|
||||||
|
box-breaker: optimizer/box-breaker.cpp
|
||||||
|
$(CC) $(CFLAGS) -o $@ $^
|
200
analyzer/index.html
Normal file
200
analyzer/index.html
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Visual Pushbox 2024</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div id="board">
|
||||||
|
<div id="maze"></div>
|
||||||
|
<div id="target" class="maze-sqr sqr-target"></div>
|
||||||
|
<div id="player" class="maze-sqr maze-mv sqr-player"></div>
|
||||||
|
<div id="box" class="maze-sqr maze-mv sqr-box"></div>
|
||||||
|
</div>
|
||||||
|
<div id="control-panel">
|
||||||
|
<div class="ctrl-title">Analyzer</div>
|
||||||
|
<table class="full-width info-table"><tbody>
|
||||||
|
<tr><td>Analyze Status:</td><td id="analyze-status" class="text-right">Not Analyzed</td></tr>
|
||||||
|
<tr><td>Total Steps:</td><td id="analyze-steps" class="text-right">N/A</td></tr>
|
||||||
|
</tbody></table>
|
||||||
|
<div class="btn-group full-width top-margin">
|
||||||
|
<button id="analyze" class="btn float-left full-width">Analyze</button>
|
||||||
|
</div>
|
||||||
|
<div class="ctrl-title top-margin">Playback</div>
|
||||||
|
<div id="progress-indicator">
|
||||||
|
<div id="progress-value" class="float-left">415 / 1618</div>
|
||||||
|
<div id="progress-left" class="float-right">-1203</div>
|
||||||
|
</div>
|
||||||
|
<div id="progress-bar" class="full-width top-margin">
|
||||||
|
<div id="progress-bar-fg" class="full-height"></div>
|
||||||
|
</div>
|
||||||
|
<div class="ctrl-title top-margin">Import / Export</div>
|
||||||
|
<textarea autocomplete="off" spellcheck="false" id="maze_charmap"></textarea>
|
||||||
|
<div id="charmap_controls" class="btn-group full-width top-margin">
|
||||||
|
<button id="charmap_import" class="btn float-left">Import</button>
|
||||||
|
<button id="charmap_export" class="btn float-right">Export</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script type="module" src="index.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
#progress-bar {
|
||||||
|
background-color: lightgrey;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-bar-fg {
|
||||||
|
width: 30%;
|
||||||
|
background-color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-height {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-table, .info-table tr, .info-table td {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
outline: none;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
margin: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 0px;
|
||||||
|
border: none;
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: grey;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[disabled] {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: none;
|
||||||
|
outline: none;
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctrl-title {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
font-size: x-large;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#control-panel {
|
||||||
|
margin-left: 40px;
|
||||||
|
display: inline-block;
|
||||||
|
width: 300px;
|
||||||
|
height: calc(80vh - 20px);
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-margin {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
height: 30px;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-left {
|
||||||
|
display: inline-block;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-right {
|
||||||
|
display: inline-block;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#maze_charmap {
|
||||||
|
width: calc(100% - 2px);
|
||||||
|
height: 298px;
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 13.33px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
#board {
|
||||||
|
width: 80vh;
|
||||||
|
height: 80vh;
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-block;
|
||||||
|
float: left;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maze-sqr {
|
||||||
|
position: absolute;
|
||||||
|
width: 4vh;
|
||||||
|
height: 4vh;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#maze {
|
||||||
|
position: relative;
|
||||||
|
z-index: 5;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maze-mv {
|
||||||
|
z-index: 10;
|
||||||
|
transition: top 0.2s, left 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sqr-wall {
|
||||||
|
background-color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sqr-space {
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sqr-target {
|
||||||
|
background-color: lime;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sqr-player {
|
||||||
|
background-color: lightblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sqr-box {
|
||||||
|
background-color: orange;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</html>
|
138
analyzer/index.js
Normal file
138
analyzer/index.js
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import { Vec2 } from './vec2.js';
|
||||||
|
import { N, SQR_TYPE, Maze, AnalyzeContext, State } from './pushbox.js';
|
||||||
|
|
||||||
|
class BoardSquare {
|
||||||
|
constructor(attach_element) {
|
||||||
|
this.e = attach_element ?? document.createElement('div');
|
||||||
|
this.e.classList.add('fixtl', 'maze-sqr');
|
||||||
|
this.e.style.top = '0';
|
||||||
|
this.e.style.left = '0';
|
||||||
|
this.type = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
moveTo(x, y) {
|
||||||
|
this.e.style.top = `${4 * x}vh`;
|
||||||
|
this.e.style.left = `${4 * y}vh`;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
moveToV(v) {
|
||||||
|
return this.moveTo(v.x, v.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
setType(type) {
|
||||||
|
if (this.type) {
|
||||||
|
this.e.classList.remove(`sqr-${this.type}`);
|
||||||
|
}
|
||||||
|
this.type = type;
|
||||||
|
this.e.classList.add(`sqr-${this.type}`);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class BoardUI {
|
||||||
|
constructor() {
|
||||||
|
this.maze = document.getElementById('maze');
|
||||||
|
this.target = new BoardSquare(document.getElementById('target'));
|
||||||
|
this.box = new BoardSquare(document.getElementById('box'));
|
||||||
|
this.player = new BoardSquare(document.getElementById('player'));
|
||||||
|
this.squares = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
drawMaze(maze) {
|
||||||
|
while (this.maze.hasChildNodes()) {
|
||||||
|
this.maze.removeChild(this.maze.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.squares = [];
|
||||||
|
for (let i = 0; i < N; ++i) {
|
||||||
|
for (let j = 0; j < N; ++j) {
|
||||||
|
let sqr = new BoardSquare();
|
||||||
|
sqr.moveTo(i, j);
|
||||||
|
if (maze.get(i, j) == SQR_TYPE.WALL) {
|
||||||
|
sqr.setType('wall');
|
||||||
|
} else {
|
||||||
|
sqr.setType('space');
|
||||||
|
}
|
||||||
|
this.maze.appendChild(sqr.e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.target.moveToV(maze.target);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateState(state) {
|
||||||
|
this.player.moveToV(state.player);
|
||||||
|
this.box.moveToV(state.box);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const charmap_input = document.getElementById('maze_charmap');
|
||||||
|
const analyze_button = document.getElementById('analyze');
|
||||||
|
const analyze_status_element = document.getElementById('analyze-status');
|
||||||
|
let board = new BoardUI();
|
||||||
|
let maze = null, analyze_res = null;
|
||||||
|
let current_worker = null;
|
||||||
|
|
||||||
|
function resetAnalyze() {
|
||||||
|
document.getElementById('analyze-status').innerHTML = 'Not Analyzed';
|
||||||
|
document.getElementById('analyze-steps').innerHTML = 'N/A';
|
||||||
|
analyze_res = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function importMaze(charmap_val) {
|
||||||
|
let charmp = charmap_val.split('\n').map(x => x.trimEnd());
|
||||||
|
maze = Maze.fromCharMap(charmp);
|
||||||
|
board.drawMaze(maze);
|
||||||
|
board.updateState(maze.init);
|
||||||
|
analyze_button.disabled = false;
|
||||||
|
resetAnalyze();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localStorage.getItem('charmap-cache') != null) {
|
||||||
|
charmap_input.value = localStorage.getItem('charmap-cache');
|
||||||
|
importMaze(charmap_input.value);
|
||||||
|
} else {
|
||||||
|
analyze_button.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('charmap_import').addEventListener('click', function() {
|
||||||
|
localStorage.setItem('charmap-cache', charmap_input.value);
|
||||||
|
importMaze(charmap_input.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
analyze_button.addEventListener('click', function() {
|
||||||
|
resetAnalyze();
|
||||||
|
if (current_worker != null) {
|
||||||
|
current_worker.terminate();
|
||||||
|
current_worker = null;
|
||||||
|
analyze_button.innerHTML = 'Analyze';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maze == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_worker = new Worker('solver.js?v=7', { type: 'module' });
|
||||||
|
analyze_status_element.innerHTML = 'Dispatching';
|
||||||
|
analyze_button.innerHTML = 'Stop';
|
||||||
|
current_worker.onmessage = (msg_r) => {
|
||||||
|
let msg = msg_r.data;
|
||||||
|
if (msg.what === 'started') {
|
||||||
|
analyze_status_element.innerHTML = 'Running';
|
||||||
|
} else if (msg.what == 'done') {
|
||||||
|
analyze_res = msg.value;
|
||||||
|
console.log(analyze_res);
|
||||||
|
analyze_status_element.innerHTML = 'Done';
|
||||||
|
document.getElementById('analyze-steps').innerHTML = analyze_res.step.toString();
|
||||||
|
current_worker.terminate();
|
||||||
|
current_worker = null;
|
||||||
|
analyze_button.innerHTML = 'Analyze';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
current_worker.postMessage(maze);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
209
analyzer/pushbox.js
Normal file
209
analyzer/pushbox.js
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
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();
|
||||||
|
console.log(x.toString());
|
||||||
|
|
||||||
|
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) {
|
||||||
|
console.log(y.toString());
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
43
analyzer/queue.js
Normal file
43
analyzer/queue.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
export class Queue {
|
||||||
|
constructor() {
|
||||||
|
this.val = [];
|
||||||
|
this.ptr = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size() {
|
||||||
|
return this.val.length - this.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
get length() {
|
||||||
|
return this.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
empty() {
|
||||||
|
return this.size() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
front() {
|
||||||
|
return this.val[this.ptr];
|
||||||
|
}
|
||||||
|
|
||||||
|
_rebuild() {
|
||||||
|
if (this.ptr > 0) {
|
||||||
|
this.val = this.val.slice(this.ptr);
|
||||||
|
this.ptr = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pop() {
|
||||||
|
this.ptr += 1;
|
||||||
|
if (this.ptr >= 16 && this.ptr >= this.val.length / 2) {
|
||||||
|
this._rebuild();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
push(x) {
|
||||||
|
this.val.push(x);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
9
analyzer/solver.js
Normal file
9
analyzer/solver.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Maze, AnalyzeContext } from './pushbox.js';
|
||||||
|
|
||||||
|
onmessage = (msg) => {
|
||||||
|
let ac = new AnalyzeContext(Maze.fromRaw(msg.data));
|
||||||
|
postMessage({ what: 'started' });
|
||||||
|
ac.bfs();
|
||||||
|
postMessage({ what: 'done', value: ac.asResult() });
|
||||||
|
};
|
||||||
|
|
38
analyzer/vec2.js
Normal file
38
analyzer/vec2.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
export class Vec2 {
|
||||||
|
constructor(x, y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
addTo(o) {
|
||||||
|
this.x += o.x;
|
||||||
|
this.y += o.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
isInside(lt, rb) {
|
||||||
|
return this.x >= lt.x && this.x < rb.x && this.y >= lt.y && this.y < rb.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
return new Vec2(this.x, this.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
equals(o) {
|
||||||
|
return this.x == o.x && this.y == o.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
match(x, y) {
|
||||||
|
return this.x == x && this.y == y;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `(${this.x}, ${this.y})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromRaw(o) {
|
||||||
|
return new Vec2(o.x, o.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
static zero = new Vec2(0, 0);
|
||||||
|
};
|
||||||
|
|
@ -1,167 +1,19 @@
|
|||||||
#include <bits/stdc++.h>
|
#include <bits/stdc++.h>
|
||||||
|
#include "pushbox.h"
|
||||||
|
#include "box-solver.h"
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
const int N = 20;
|
|
||||||
|
|
||||||
mt19937 rng(random_device{}());
|
mt19937 rng(random_device{}());
|
||||||
uniform_real_distribution<float> d01(0, 1);
|
uniform_real_distribution<float> d01(0, 1);
|
||||||
uniform_int_distribution<int> rng10(0, 9);
|
uniform_int_distribution<int> rng10(0, 9);
|
||||||
uniform_int_distribution<int> rngN(0, 19);
|
uniform_int_distribution<int> rngN(0, 19);
|
||||||
|
|
||||||
enum {
|
|
||||||
POI_PERSON,
|
|
||||||
POI_BOX,
|
|
||||||
POI_TARGET,
|
|
||||||
POI_EXCEED,
|
|
||||||
};
|
|
||||||
|
|
||||||
#define compiler_assume(condition) \
|
#define compiler_assume(condition) \
|
||||||
do { \
|
do { \
|
||||||
if (!(condition)) \
|
if (!(condition)) \
|
||||||
__builtin_unreachable(); \
|
__builtin_unreachable(); \
|
||||||
} while (false); \
|
} while (false); \
|
||||||
|
|
||||||
struct Maze {
|
|
||||||
bitset<N> M[N];
|
|
||||||
int poi[3][2];
|
|
||||||
|
|
||||||
int at(int x, int y) const {
|
|
||||||
return M[x][y];
|
|
||||||
}
|
|
||||||
|
|
||||||
void set(int x, int y, int v) {
|
|
||||||
M[x][y] = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
void flip(int x, int y) {
|
|
||||||
M[x].flip(y);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isValid() const {
|
|
||||||
for (int i = 0; i < POI_EXCEED; ++i) {
|
|
||||||
for (int j : {0, 1}) {
|
|
||||||
if (poi[i][j] < 0 || poi[i][j] >= N)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (at(poi[i][0], poi[i][1]) == 0)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
inline Maze loadMaze(std::FILE *f) {
|
|
||||||
char *s = new char[N + 5];
|
|
||||||
Maze res;
|
|
||||||
for (int i = 0; i < N; ++i) {
|
|
||||||
std::fscanf(f, "%s", s);
|
|
||||||
for (int j = 0; j < N; ++j) {
|
|
||||||
res.set(i, j, s[j] != '#');
|
|
||||||
switch (s[j]) {
|
|
||||||
case 'P':
|
|
||||||
res.poi[POI_PERSON][0] = i;
|
|
||||||
res.poi[POI_PERSON][1] = j;
|
|
||||||
break;
|
|
||||||
case '*':
|
|
||||||
res.poi[POI_BOX][0] = i;
|
|
||||||
res.poi[POI_BOX][1] = j;
|
|
||||||
break;
|
|
||||||
case 'O':
|
|
||||||
res.poi[POI_TARGET][0] = i;
|
|
||||||
res.poi[POI_TARGET][1] = j;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete[] s;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void writeMaze(const Maze &m, std::FILE *f) {
|
|
||||||
for (int i = 0; i < N; ++i) {
|
|
||||||
for (int j = 0; j < N; ++j) {
|
|
||||||
if (i == m.poi[POI_PERSON][0] && j == m.poi[POI_PERSON][1])
|
|
||||||
std::fputc('P', f);
|
|
||||||
else if (i == m.poi[POI_BOX][0] && j == m.poi[POI_BOX][1])
|
|
||||||
std::fputc('*', f);
|
|
||||||
else if (i == m.poi[POI_TARGET][0] && j == m.poi[POI_TARGET][1])
|
|
||||||
std::fputc('O', f);
|
|
||||||
else if (m.at(i, j) == 0)
|
|
||||||
std::fputc('#', f);
|
|
||||||
else
|
|
||||||
std::fputc('.', f);
|
|
||||||
}
|
|
||||||
std::fputc('\n', f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Solve {
|
|
||||||
bitset<N> mp[N];
|
|
||||||
int dis[N][N][N][N];
|
|
||||||
queue<tuple<int, int, int, int>> qu;
|
|
||||||
|
|
||||||
inline int bfs(int px, int py, int bx, int by, int tx, int ty) {
|
|
||||||
memset(dis, 127, sizeof(dis));
|
|
||||||
while (!qu.empty())
|
|
||||||
qu.pop();
|
|
||||||
|
|
||||||
auto expand = [&](int npx, int npy, int nbx, int nby, int d) {
|
|
||||||
if (npx < 0 || npx >= N)
|
|
||||||
return;
|
|
||||||
if (npy < 0 || npy >= N)
|
|
||||||
return;
|
|
||||||
if (nbx < 0 || nbx >= N)
|
|
||||||
return;
|
|
||||||
if (nby < 0 || nby >= N)
|
|
||||||
return;
|
|
||||||
if (mp[npx][npy] == 0)
|
|
||||||
return;
|
|
||||||
if (mp[nbx][nby] == 0)
|
|
||||||
return;
|
|
||||||
if (npx == nbx && npy == nby)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (dis[npx][npy][nbx][nby] <= d)
|
|
||||||
return;
|
|
||||||
dis[npx][npy][nbx][nby] = d;
|
|
||||||
qu.emplace(npx, npy, nbx, nby);
|
|
||||||
};
|
|
||||||
|
|
||||||
expand(px, py, bx, by, 0);
|
|
||||||
|
|
||||||
while (!qu.empty()) {
|
|
||||||
auto [npx, npy, nbx, nby] = qu.front();
|
|
||||||
int d = dis[npx][npy][nbx][nby];
|
|
||||||
qu.pop();
|
|
||||||
|
|
||||||
if (nbx == tx && nby == ty)
|
|
||||||
return d;
|
|
||||||
|
|
||||||
expand(npx + 1, npy, nbx, nby, d + 1);
|
|
||||||
expand(npx - 1, npy, nbx, nby, d + 1);
|
|
||||||
expand(npx, npy + 1, nbx, nby, d + 1);
|
|
||||||
expand(npx, npy - 1, nbx, nby, d + 1);
|
|
||||||
if (npx + 1 == nbx && npy == nby)
|
|
||||||
expand(npx + 1, npy, nbx + 1, nby, d + 1);
|
|
||||||
if (npx - 1 == nbx && npy == nby)
|
|
||||||
expand(npx - 1, npy, nbx - 1, nby, d + 1);
|
|
||||||
if (npx == nbx && npy + 1 == nby)
|
|
||||||
expand(npx, npy + 1, nbx, nby + 1, d + 1);
|
|
||||||
if (npx == nbx && npy - 1 == nby)
|
|
||||||
expand(npx, npy - 1, nbx, nby - 1, d + 1);
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int solve(Maze q) {
|
|
||||||
for (int i = 0; i < 20; ++i)
|
|
||||||
mp[i] = q.M[i];
|
|
||||||
return bfs(q.poi[POI_PERSON][0], q.poi[POI_PERSON][1]
|
|
||||||
, q.poi[POI_BOX][0], q.poi[POI_BOX][1], q.poi[POI_TARGET][0], q.poi[POI_TARGET][1]);
|
|
||||||
}
|
|
||||||
}; // namespace Solve
|
|
||||||
|
|
||||||
const float initial_temperature = 10;
|
const float initial_temperature = 10;
|
||||||
const float initial_temperature_pv = 60;
|
const float initial_temperature_pv = 60;
|
||||||
const float termperature_delta = .97;
|
const float termperature_delta = .97;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user