diff options
-rw-r--r-- | index.html | 10 | ||||
-rw-r--r-- | level.js | 107 | ||||
-rw-r--r-- | levelSelect.js | 40 | ||||
-rw-r--r-- | main.js | 43 | ||||
-rw-r--r-- | render.js | 2 | ||||
-rw-r--r-- | style.css | 64 |
6 files changed, 200 insertions, 66 deletions
@@ -2,11 +2,16 @@ <html> <head> <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> <title>robotic</title> <script type="module" src="main.js"></script> <script src="mathjs/mathjs-expression-parser.js"></script> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Jura:wght@300..700&display=swap" rel="stylesheet"> + <link href="style.css" rel="stylesheet"> </head> - <body> + <body style="font-family: Jura;"> <audio id="music-starboard" src="sounds/music/starboard.mp3"></audio> <audio id="music-minute" src="sounds/music/minute.mp3"></audio> <audio id="music-cribwhistling" src="sounds/music/cribwhistling.mp3"></audio> @@ -17,8 +22,9 @@ <audio id="sfx-wrong" src="sounds/sfx/gui-beep/33789__jobro__5-beep-c.wav"></audio> <audio id="sfx-buttonenter" src="sounds/sfx/gui-beep/33777__jobro__1-beep-c.wav"></audio> - <audio id="sfx-buttonclick" src="sounds/sfx/gui-beep/33778__jobro__2-beep-a.wav"></audio> + <audio id="sfx-buttonclick" src="sounds/sfx/gui-beep/33780__jobro__2-beep-c.wav"></audio> <div id="root"></div> + <div id="overlay"></div> </body> </html> @@ -20,7 +20,9 @@ export function setupLevel(resources, home) { completed: false, index: 0, equations: { + xStr: X_START, x: math.compile(X_START), + yStr: Y_START, y: math.compile(Y_START), }, }; @@ -29,45 +31,31 @@ export function setupLevel(resources, home) { } -export function setupLevelUi(level, root, audio) { - const ui = { audio }; - - const buttonEnterAudio = document.getElementById('sfx-buttonenter'); - const buttonClickAudio = document.getElementById('sfx-buttonclick'); - audio.createMediaElementSource(buttonEnterAudio).connect(audio.destination); - audio.createMediaElementSource(buttonClickAudio).connect(audio.destination); - - ui.resourceAudio = document.getElementById('sfx-resource'); - ui.resourceSource = audio.createMediaElementSource(ui.resourceAudio); - ui.resourceSource.connect(audio.destination); - - ui.doneAudio = document.getElementById('sfx-done'); - ui.doneSource = audio.createMediaElementSource(ui.doneAudio); - ui.doneSource.connect(audio.destination); - - ui.wrongAudio = document.getElementById('sfx-wrong'); - ui.wrongSource = audio.createMediaElementSource(ui.wrongAudio); - ui.wrongSource.connect(audio.destination); +export function setupLevelUi(level, root, sfx) { + const ui = { sfx }; + root.classList.add('center'); ui.launchButton = document.createElement('input'); ui.launchButton.type = 'button'; ui.launchButton.value = 'Launch'; ui.launchButton.onmouseenter = () => { - buttonEnterAudio.load(); - buttonEnterAudio.play(); + sfx.buttonEnterAudio.currentTime = 0; + sfx.buttonEnterAudio.play(); } ui.launchButton.onclick = () => { - buttonClickAudio.load(); - buttonClickAudio.play(); + 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.value = X_START; + 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; @@ -76,11 +64,15 @@ export function setupLevelUi(level, root, audio) { }; 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 = Y_START; + 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; @@ -89,40 +81,54 @@ export function setupLevelUi(level, root, audio) { }; 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"; - root.appendChild(ui.launchButton); + root.appendChild(ui.xeqLabel); root.appendChild(ui.xeq); + root.appendChild(document.createElement('br')); + root.appendChild(ui.yeqLabel); root.appendChild(ui.yeq); + root.appendChild(document.createElement('br')); + root.appendChild(ui.launchButton); root.appendChild(ui.message); ui.canvas = document.createElement('canvas'); - ui.canvas.width = 600; - ui.canvas.height = 600; + const setCanvasSize = () => { + const size = Math.min(0.7*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; root.appendChild(ui.canvas); + root.appendChild(document.createElement('br')); ui.returnBtn = document.createElement('input'); ui.returnBtn.type = 'button'; - ui.returnBtn.value = 'Return'; + ui.returnBtn.value = '↲ Return'; ui.returnBtn.onmouseenter = () => { - buttonEnterAudio.load(); - buttonEnterAudio.play(); + sfx.buttonEnterAudio.currentTime = 0; + sfx.buttonEnterAudio.play(); } ui.returnBtn.onclick = () => { - buttonClickAudio.load(); - buttonClickAudio.play(); + sfx.buttonClickAudio.currentTime = 0; + sfx.buttonClickAudio.play(); root.innerText = ''; - setupLevelSelectUi(root, audio); + root.classList.remove('center'); + window.onresize = () => {}; + setupLevelSelectUi(root, sfx); } root.appendChild(ui.returnBtn); - 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); return ui; } @@ -137,8 +143,8 @@ function stepLevel(ui, level, index) { .map(([x, i]) => [ distance(pos, x.position), i ]) .forEach(([d, i]) => { if (d < 0.4) { - ui.resourceAudio.load(); - ui.resourceAudio.play(); + ui.sfx.resourceAudio.currentTime = 0; + ui.sfx.resourceAudio.play(); level.resources[i].collected = true; } }); @@ -147,11 +153,12 @@ function stepLevel(ui, level, index) { if (distance(pos, level.home) < 0.4) { level.running = false; - finishLevel(ui, level); + finishLevel(ui, level, false); render(ui.ctx, level, 0); } else if (level.running && index < level.path.length-1) { setTimeout(() => stepLevel(ui, level, index+1), 1); } else { + finishLevel(ui, level, true); level.running = false; render(ui.ctx, level, 0); } @@ -175,15 +182,19 @@ function distance(p1, p2) { } -function finishLevel(ui, level) { +function finishLevel(ui, level, fail) { const uncollected = level.resources.reduce((acc, { collected }) => acc + (collected ? 0 : 1), 0); - if (uncollected > 0) { - ui.wrongAudio.load(); - ui.wrongAudio.play(); + if (fail) { + ui.sfx.wrongAudio.currentTime = 0; + ui.sfx.wrongAudio.play(); + ui.message.innerText = 'did not arrive at destination'; + } else if (uncollected > 0) { + ui.sfx.wrongAudio.currentTime = 0; + ui.sfx.wrongAudio.play(); ui.message.innerText = 'resources remaining!'; } else { - ui.doneAudio.load(); - ui.doneAudio.play(); + ui.sfx.doneAudio.currentTime = 0; + ui.sfx.doneAudio.play(); ui.message.innerText = 'mission complete!'; level.completed = true; } diff --git a/levelSelect.js b/levelSelect.js index b1f6073..0fbdbf0 100644 --- a/levelSelect.js +++ b/levelSelect.js @@ -3,38 +3,54 @@ import{ setupLevel, setupLevelUi } from './level.js'; const levels = { 'Initial Jump': [ [], setupLevel([], [8, 0]) ], 'Monopole Mining': [ ['Initial Jump'], setupLevel([[3, 3]], [8,8]) ], + 'Parabolic': [ ['Initial Jump', 'Monopole Mining'], setupLevel([[-2, 4], [0,0], [2, 4]], [3,9]) ], + 'Sinusoidal': [ + ['Initial Jump', 'Monopole Mining'], + setupLevel([ + [-4, 4*Math.sin(0)], + [0.5*Math.PI - 4, 4*Math.sin(0.5*Math.PI)], + [1.0*Math.PI - 4, 4*Math.sin(1.0*Math.PI)], + [1.5*Math.PI - 4, 4*Math.sin(1.5*Math.PI)], + ], [2.0*Math.PI - 4, 4*Math.sin(2.0*Math.PI)]), + ], }; -export function setupLevelSelectUi(root, audio) { - const buttonEnterAudio = document.getElementById('sfx-buttonenter'); - const buttonClickAudio = document.getElementById('sfx-buttonclick'); - audio.createMediaElementSource(buttonEnterAudio).connect(audio.destination); - audio.createMediaElementSource(buttonClickAudio).connect(audio.destination); - +export function setupLevelSelectUi(root, sfx) { const levelList = document.createElement('ol'); const levelPicker = name => { const [ dependencies, level ] = levels[name]; const allDependenciesSatisfied = dependencies.map(x => levels[x][1].completed).reduce((acc, x) => acc && x, true); - if (allDependenciesSatisfied) { + // if (allDependenciesSatisfied) { + if (true) { const button = document.createElement('input'); button.type = 'button'; button.value = name; button.onmouseenter = () => { - buttonEnterAudio.load(); - buttonEnterAudio.play(); + sfx.buttonEnterAudio.currentTime = 0; + sfx.buttonEnterAudio.play(); } button.onclick = () => { - buttonClickAudio.load(); - buttonClickAudio.play(); + sfx.buttonClickAudio.currentTime = 0; + sfx.buttonClickAudio.play(); root.innerText = ''; - setupLevelUi(level, root, audio); + setupLevelUi(level, root, sfx); } const li = document.createElement('li'); li.appendChild(button); + if (level.completed) { + li.appendChild(document.createTextNode('[complete]')); + } levelList.appendChild(li); } } [...Object.keys(levels)].forEach(levelPicker); + const header = document.createElement('h1'); + header.classList.add('center'); + header.innerText = 'AVAILABLE CONTRACTS'; + root.appendChild(header); + const p = document.createElement('p'); + p.innerText = 'Please select a General Products AMH hyperspace navigation contract from the list below.'; + root.appendChild(p); root.appendChild(levelList); } @@ -4,13 +4,32 @@ import { setupLevel, setupLevelUi } from './level.js'; window.onload = () => { const root = document.getElementById('root'); + root.classList.add('center'); + + const title = document.createElement('h1'); + title.innerText = 'GENERAL PRODUCTS'; + root.appendChild(title); + + const logo = document.createElement('h1'); + logo.classList.add('logo'); + logo.innerText = '※'; + root.appendChild(logo); + + const title2 = document.createElement('h2'); + title2.innerText = 'AUTOMATED MONOPOLE HARVESTER (AMH) HYPERSPACE NAVIGATIONAL SERVICES'; + root.appendChild(title2); + + const paragraph = document.createElement('p'); + paragraph.innerText = 'Greetings, navigator! Thank you for helping us to program the hyperspace engines of our Automated Monopole Harvester (AMH) platforms. By doing so, you help ensure stable antimatter production throughout the Nine Worlds.'; + root.appendChild(paragraph); const start = document.createElement('input'); start.type = 'button'; - start.value = 'Start'; + start.value = 'Connect to AMH'; start.onclick = () => { + root.classList.remove('center'); + root.innerText = ''; const audio = new AudioContext(); - root.removeChild(start); const musicGain = audio.createGain(); const addMusic = (music, id) => { @@ -35,9 +54,27 @@ window.onload = () => { ) musicList[0].element.play(); + const sfx = {}; + sfx.buttonEnterAudio = document.getElementById('sfx-buttonenter'); + sfx.buttonClickAudio = document.getElementById('sfx-buttonclick'); + audio.createMediaElementSource(sfx.buttonEnterAudio).connect(audio.destination); + audio.createMediaElementSource(sfx.buttonClickAudio).connect(audio.destination); + + sfx.resourceAudio = document.getElementById('sfx-resource'); + sfx.resourceSource = audio.createMediaElementSource(sfx.resourceAudio); + sfx.resourceSource.connect(audio.destination); + + sfx.doneAudio = document.getElementById('sfx-done'); + sfx.doneSource = audio.createMediaElementSource(sfx.doneAudio); + sfx.doneSource.connect(audio.destination); + + sfx.wrongAudio = document.getElementById('sfx-wrong'); + sfx.wrongSource = audio.createMediaElementSource(sfx.wrongAudio); + sfx.wrongSource.connect(audio.destination); + // const level = setupLevel([[2, 2], [3,3]], [7, 7]); // const ui = setupLevelUi(level, root, audio); - setupLevelSelectUi(root, audio); + setupLevelSelectUi(root, sfx); }; root.appendChild(start); } @@ -84,7 +84,7 @@ export function drawGrid(ctx) { export function drawPath(ctx, path) { const [ start, ...line ] = path; ctx.strokeStyle = 'white'; - ctx.lineWidth = 0.02; + ctx.lineWidth = 0.04; ctx.beginPath(); ctx.moveTo(start[0], start[1]); line.forEach(p => ctx.lineTo(p[0], p[1])); diff --git a/style.css b/style.css new file mode 100644 index 0000000..e0c968d --- /dev/null +++ b/style.css @@ -0,0 +1,64 @@ +body { + color: white; + background-color: black; + font-family: "Jura", monospace; + font-variant-caps: small-caps; + position: relative; + overflow-x: hidden; +} + +.logo { + margin-top: 0em; + margin-bottom: 0em; + font-size: 10em; +} + +#overlay { + position: absolute; + top: 0; + right: 0; + left: 0; + height: 100%; + background: orange; + mix-blend-mode: multiply; + pointer-events: none; + overflow: none; +} + +#root { + width: min(90vw, 50em); + margin: auto; + border-left: 1px solid white; + border-right: 1px solid white; + padding: 0.5em; + min-height: 95vh; +} + +.center { + text-align: center; + margin: auto; +} + +input { + font-family: "Jura", monospace; + font-variant-caps: small-caps; + color: white; + background-color: black; + font-size: 1em; + padding: 0.5em; + margin: 0.5em; + font-weight: bold; + border: 2px solid; +} +input[type=button] { + padding: 0.6em; + font-size: 1.3em; +} +input[type=button]:hover { + color: black; + background-color: white; +} +input[type=button]:active { + color: black; + background-color: grey; +} |