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