234 lines
6.0 KiB
JavaScript

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);
}
};
function fillTemplate(id, val) {
document.getElementById(id).innerHTML = val.toString();
}
function updateProgressBar(p) {
document.getElementById('progress-bar-fg').style.width = `${p}%`;
}
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, current_progress = 0;
let autoplay_timer = null;
let current_worker = null;
function stopAutplay() {
if (autoplay_timer != null) {
clearInterval(autoplay_timer);
autoplay_timer = null;
fillTemplate('progressctl-auto', 'Play');
}
}
function resetAnalyze() {
stopAutplay();
analyze_status_element.innerHTML = 'Not Analyzed';
fillTemplate('analyze-steps', 'N/A');
fillTemplate('current-progress', '0');
fillTemplate('full-progress', '0');
fillTemplate('progress-remain', '-0');
updateProgressBar(0);
current_progress = 0;
analyze_res = null;
}
function moveProgress(d) {
if (analyze_res == null || analyze_res.step == -1) {
return;
}
current_progress += d;
if (current_progress < 0) {
current_progress = 0;
}
if (current_progress > analyze_res) {
current_progress = analyze_res.step;
}
let st = analyze_res.path[current_progress];
board.updateState(st);
fillTemplate('current-progress', current_progress.toString());
fillTemplate('progress-remain', `-${analyze_res.step - current_progress}`);
updateProgressBar(current_progress * 100 / analyze_res.step);
}
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);
});
document.getElementById('progressctl-next').addEventListener('click', function() {
moveProgress(1);
});
document.getElementById('progressctl-prev').addEventListener('click', function() {
moveProgress(-1);
});
document.addEventListener('keydown', function(event) {
if (event.isComposing || event.keyCode === 229) {
return;
}
if (event.key === 'ArrowLeft') {
moveProgress(-1);
} else if (event.key === 'ArrowRight') {
moveProgress(1);
}
});
document.getElementById('progressctl-auto').addEventListener('click', function() {
if (autoplay_timer == null) {
autoplay_timer = setInterval(() => {
if (current_progress == analyze_res.step) {
stopAutplay();
return;
}
moveProgress(1);
}, 500);
fillTemplate('progressctl-auto', 'Pause');
} else {
stopAutplay();
}
});
document.getElementById('progress-bar').addEventListener('click', function(e) {
stopAutplay();
const bar = document.getElementById('progress-bar');
let p = (e.clientX - bar.getBoundingClientRect().x) / bar.clientWidth;
if (analyze_res != null && analyze_res.step != -1) {
current_progress = Math.round(analyze_res.step * p);
moveProgress(0);
}
});
document.getElementById('progressctl-reset').addEventListener('click', function() {
stopAutplay();
moveProgress(-current_progress);
});
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 = 'Analyzing';
} else if (msg.what == 'done') {
analyze_res = msg.value;
current_worker.terminate();
current_worker = null;
analyze_button.innerHTML = 'Analyze';
analyze_status_element.innerHTML = 'Analyze Done';
fillTemplate('full-progress', analyze_res.step);
fillTemplate('analyze-steps', analyze_res.step);
fillTemplate('progress-remain', `-${analyze_res.step}`);
}
};
current_worker.postMessage(maze);
});
});