import { setupLevelSelectUi } from './levelSelect.js'; import { render } from './render.js'; const X_START = 't'; const Y_START = 't'; const PATH_LEN = 20; const PATH_STEP = PATH_LEN/1000; const SLOW_RAMP = 1; const FAST_RAMP = 0.01; export function setupLevel(resources, home, intro) { const level = { resources: resources.map(position => ({ position, collected: false })), home, intro: intro || null, running: false, completed: false, index: 0, equations: { xStr: X_START, x: math.compile(X_START), yStr: Y_START, y: math.compile(Y_START), }, }; level.path = computePath(level.equations); return level; } export function setupLevelUi(level, root, sfx) { const ui = { root, sfx }; root.classList.add('center'); ui.launchButton = document.createElement('input'); ui.launchButton.type = 'button'; ui.launchButton.value = 'launch'; ui.launchButton.onmouseenter = () => { sfx.buttonEnterAudio.currentTime = 0; sfx.buttonEnterAudio.play(); } ui.launchButton.onclick = () => { sfx.buttonClickAudio.currentTime = 0; sfx.buttonClickAudio.play(); ui.message.innerText = ''; level.running = false; setTimeout(() => { level.running=true; stepLevel(ui, level, 0); }, 20); } ui.xeq = document.createElement('input'); ui.xeq.id = 'xeq'; ui.xeq.value = level.equations.xStr; ui.xeq.onchange = (e) => { try { level.equations.xStr = e.target.value; level.equations.x = math.compile(e.target.value); } catch(err) { } level.running = false; level.path = computePath(level.equations); level.resources.forEach(x => x.collected = false); render(ui.ctx, level, 0); }; ui.xeq.onkeydown = ui.xeq.onchange; ui.xeq.onkeyup = ui.xeq.onchange; ui.xeqLabel = document.createElement('label'); ui.xeqLabel.htmlFor = 'xeq'; ui.xeqLabel.innerText = 'x(t) = '; ui.yeq = document.createElement('input'); ui.yeq.value = level.equations.yStr; ui.yeq.onchange = (e) => { try { level.equations.yStr = e.target.value; level.equations.y = math.compile(e.target.value); } catch(err) { } level.running = false; level.path = computePath(level.equations); level.resources.forEach(x => x.collected = false); render(ui.ctx, level, 0); }; ui.yeq.onkeydown = ui.yeq.onchange; ui.yeq.onkeyup = ui.yeq.onchange; ui.yeqLabel = document.createElement('label'); ui.yeqLabel.htmlFor = 'yeq'; ui.yeqLabel.innerText = 'y(t) = '; ui.message = document.createElement('div'); ui.id = "level-message"; const appearSound = () => { sfx.listAppearAudio.currentTime = 0; sfx.listAppearAudio.play(); }; setTimeout(() => { root.appendChild(ui.xeqLabel); root.appendChild(ui.xeq); root.appendChild(document.createElement('br')); appearSound(); }, 100); setTimeout(() => { root.appendChild(ui.yeqLabel); root.appendChild(ui.yeq); root.appendChild(document.createElement('br')); appearSound(); }, 200); setTimeout(() => { root.appendChild(ui.launchButton); root.appendChild(ui.message); appearSound(); }, 300); ui.canvas = document.createElement('canvas'); ui.canvas.classList.add('bordered'); const setCanvasSize = () => { const size = Math.min(0.8*window.innerHeight, 0.9*root.offsetWidth); ui.canvas.width = size; ui.canvas.height = size; ui.ctx = ui.canvas.getContext('2d'); ui.ctx.translate(ui.canvas.width/2, ui.canvas.height/2) ui.ctx.scale(ui.canvas.width/20, -ui.canvas.height/20); render(ui.ctx, level, 0); } setCanvasSize(); window.onresize = setCanvasSize; setTimeout(() => { root.appendChild(ui.canvas); root.appendChild(document.createElement('br')); appearSound(); }, 400); ui.returnBtn = document.createElement('input'); ui.returnBtn.type = 'button'; ui.returnBtn.value = '↲ return'; ui.returnBtn.onmouseenter = () => { sfx.buttonEnterAudio.currentTime = 0; sfx.buttonEnterAudio.play(); } ui.returnBtn.onclick = () => { sfx.buttonClickAudio.currentTime = 0; sfx.buttonClickAudio.play(); root.innerText = ''; root.classList.remove('center'); window.onresize = () => {}; setupLevelSelectUi(root, sfx); } setTimeout(() => { root.appendChild(ui.returnBtn); appearSound(); }, 500); if (level.intro) { setTimeout(() => showAlert(ui, level.intro), 600); } return ui; } function stepLevel(ui, level, index) { render(ui.ctx, level, index); const pos = level.path[index]; const distances = level.resources .map((x, i) => [x, i]) .filter(([x, _]) => x.collected === false) .map(([x, i]) => [ distance(pos, x.position), i ]) .forEach(([d, i]) => { if (d < 0.4) { ui.sfx.resourceAudio.currentTime = 0; ui.sfx.resourceAudio.play(); level.resources[i].collected = true; } }); if (distance(pos, level.home) < 0.4) { level.running = false; finishLevel(ui, level, false); render(ui.ctx, level, 0); } else if (level.running && index < level.path.length-1) { const distNext = distance(pos, level.path[index+1] || pos); setTimeout(() => stepLevel(ui, level, index+1), 50*distNext); } else { if (index >= level.path.length-1) { finishLevel(ui, level, true); } level.running = false; render(ui.ctx, level, 0); } } function computePath(equations, start, end, step) { try { const ts = [...Array(Math.floor((PATH_LEN)/PATH_STEP)).keys()].map(k => PATH_STEP * k); return ts.map(t => [ equations.x.eval({t}), equations.y.eval({t}) ]); } catch (err) { return [ [0, 0] ]; } } function distance(p1, p2) { const [x1, y1] = p1; const [x2, y2] = p2; return Math.sqrt((x2-x1)**2 + (y2-y1)**2); } function finishLevel(ui, level, fail) { const uncollected = level.resources.reduce((acc, { collected }) => acc + (collected ? 0 : 1), 0); if (fail) { ui.sfx.wrongAudio.currentTime = 0; ui.sfx.wrongAudio.play(); showAlert(ui, 'trajectory does not reach AMH processor'); // ui.message.innerText = 'did not arrive at destination'; } else if (uncollected > 0) { ui.sfx.wrongAudio.currentTime = 0; ui.sfx.wrongAudio.play(); showAlert(ui, 'monopole harvesting failure'); // ui.message.innerText = 'resources remaining!'; } else { ui.sfx.doneAudio.currentTime = 0; ui.sfx.doneAudio.play(); showAlert(ui, 'navigation successful: contract complete', true); // ui.message.innerText = 'mission complete!'; level.completed = true; } level.resources.forEach(x => x.collected = false); } function showAlert(ui, text, ret) { const div = document.createElement('div'); div.classList.add('center-screen-container'); const div2 = document.createElement('div'); div2.classList.add('center-screen'); div.appendChild(div2); const p = document.createElement('p'); p.classList.add('alert-box'); p.innerText = text; div2.appendChild(p); ui.root.appendChild(div); const button = document.createElement('input'); button.type = 'button'; button.value = 'OK'; button.onmouseenter = () => { ui.sfx.buttonEnterAudio.currentTime = 0; ui.sfx.buttonEnterAudio.play(); } button.onclick = () => { ui.sfx.buttonClickAudio.currentTime = 0; ui.sfx.buttonClickAudio.play(); ui.root.removeChild(div); } p.appendChild(document.createElement('br')); p.appendChild(button); if (ret) { const returnBtn = document.createElement('input'); returnBtn.type = 'button'; returnBtn.value = '↲ return'; returnBtn.onmouseenter = () => { ui.sfx.buttonEnterAudio.currentTime = 0; ui.sfx.buttonEnterAudio.play(); } returnBtn.onclick = () => { ui.sfx.buttonClickAudio.currentTime = 0; ui.sfx.buttonClickAudio.play(); ui.root.innerText = ''; ui.root.classList.remove('center'); window.onresize = () => {}; setupLevelSelectUi(ui.root, ui.sfx); } p.appendChild(returnBtn); } }