Browse Source

a bunch of work

main
Martin Dørum 2 years ago
parent
commit
3ee6cc94da
13 changed files with 932 additions and 1109 deletions
  1. 1
    0
      .gitignore
  2. 10
    0
      ideas.txt
  3. 55
    1063
      package-lock.json
  4. BIN
      public/ambient.mp3
  5. 1
    1
      public/index.html
  6. BIN
      soundtrack/ambient.mmpz
  7. BIN
      soundtrack/ambient.mmpz.bak
  8. 748
    0
      src/CircuitSim.svelte
  9. 24
    0
      src/MainMenu.svelte
  10. 31
    0
      src/Router.svelte
  11. 49
    38
      src/Scene.svelte
  12. 11
    0
      src/SceneSelector.svelte
  13. 2
    7
      src/main.js

+ 1
- 0
.gitignore View File

@@ -1,2 +1,3 @@
/node_modules/
/public/build/
*.mmpz.bak

+ 10
- 0
ideas.txt View File

@@ -0,0 +1,10 @@
# Puzzle Series Ideas

* Morse code decoder
* Decode morse code into ASCII
* Write ASCII to printer
* CPU
* ALU
* Registers
* Control logic
* ???

+ 55
- 1063
package-lock.json
File diff suppressed because it is too large
View File


BIN
public/ambient.mp3 View File


+ 1
- 1
public/index.html View File

@@ -14,6 +14,6 @@
</head>

<body>
<audio src="ambient.mp3" autoplay loop></audio>
<audio id="soundtrack" src="ambient.mp3" loop></audio>
</body>
</html>

BIN
soundtrack/ambient.mmpz View File


BIN
soundtrack/ambient.mmpz.bak View File


+ 748
- 0
src/CircuitSim.svelte View File

@@ -0,0 +1,748 @@
<canvas bind:this={canvas}></canvas>

<style>
canvas {
width: 100vw;
height: 100vh;
position: absolute;
top: 0px;
left: 0px;
}
</style>

<script>
import {onMount} from 'svelte';

class Link {
constructor(from, index) {
this.from = from;
this.index = index;
this.current = false;
this.next = false;

this.connections = [];
}

connect(node, index) {
node.inputs[index].links.push(this);
this.connections.push({node, index});
}

disconnect(node, index) {
for (let i = this.connections.length - 1; i >= 0; --i) {
let conn = this.connections[i];
if (conn.node == node && conn.index == index) {
this.connections.splice(i, 1);
}
}
}

destroy() {
for (let conn of this.connections) {
for (let i = conn.node.inputs[conn.index].links.length - 1; i >= 0; --i) {
if (conn.node.inputs[conn.index].links[i] == this) {
conn.node.inputs[conn.index].links.splice(i, 1);
}
}
}
}

commit() {
this.current = this.next;
}
}

class Input {
constructor(x, y) {
this.name = "IN";
this.x = x;
this.y = y;
this.inputs = [];
this.outputs = [{name: "Out", link: new Link(this, 0)}];

this.width = 3;
this.height = 1;

this.lit = false;
}

activate() {
this.lit = !this.lit;
}

tick() {
if (this.lit) {
this.outputs[0].link.next = true;
} else {
this.outputs[0].link.next = false;
}
}

commit() {
this.outputs[0].link.commit();
}
}

class NotGate {
constructor(x, y) {
this.name = "NOT";
this.x = x;
this.y = y;
this.inputs = [{name: "In", links: []}];
this.outputs = [{name: "Out", link: new Link(this, 0)}];

this.width = 4;
this.height = 1;

this.lit = false;
}

tick() {
this.outputs[0].link.next = true;
for (let link of this.inputs[0].links) {
if (link.current) {
this.outputs[0].link.next = false;
break;
}
}
}

commit() {
this.outputs[0].link.commit();
this.lit = this.outputs[0].link.current;
}
}

class Diode {
constructor(x, y) {
this.name = "DIODE";
this.x = x;
this.y = y;
this.inputs = [{name: "In", links: []}];
this.outputs = [{name: "Out", link: new Link(this, 0)}];

this.width = 4;
this.height = 1;

this.lit = false;
}

tick() {
this.outputs[0].link.next = false;
for (let link of this.inputs[0].links) {
if (link.current) {
this.outputs[0].link.next = true;
break;
}
}
}

commit() {
this.outputs[0].link.commit();
this.lit = this.outputs[0].link.current;
}
}

class Lamp {
constructor(x, y) {
this.name = "LAMP";
this.x = x;
this.y = y;
this.inputs = [{name: "In", links: []}];
this.outputs = [];

this.width = 4;
this.height = 1;

this.lit = false;
this.nextLit = false;
}

tick() {
this.nextLit = false;
for (let link of this.inputs[0].links) {
if (link.current) {
this.nextLit = true;
break;
}
}
}

commit() {
this.lit = this.nextLit;
}
}

class LogicSim {
constructor(can) {
this.scale = 40;
this.can = can;
this.ctx = can.getContext("2d");
this.nodes = [];
this.frameRequested = false;
this.clickCancelled = false;
this.currentTouch = null;
this.x = 0;
this.y = 0;

this.tooltip = null;
this.selectedNodes = [];
this.mouseMoveStart = null;
this.currentLink = null;
this.selection = null;

this.requestFrame();

window.addEventListener("resize", () => {
this.requestFrame();
});

window.addEventListener("keydown", evt => {
this.onKeyDown(evt.key);
});

can.addEventListener("wheel", evt => {
this.onScroll(evt.deltaY / 10);
});

can.addEventListener("mousedown", evt => {
this.onMouseDown(evt.offsetX, evt.offsetY, evt.buttons, evt);
});

can.addEventListener("mouseup", evt => {
this.onMouseUp();
});

can.addEventListener("mousemove", evt => {
this.onMouseMove(evt.offsetX, evt.offsetY, evt.movementX, evt.movementY, evt.buttons);
});

can.addEventListener("click", evt => {
this.onClick(evt.offsetX, evt.offsetY);
});

can.addEventListener("touchstart", evt => {
if (this.currentTouch != null || evt.changedTouches.length == 0) {
return;
}

let relevantTouch = evt.changedTouches[0];
this.currentTouch = relevantTouch;
this.onMouseDown(relevantTouch.clientX, relevantTouch.clientY, 1, evt);
});

can.addEventListener("touchend", evt => {
if (this.currentTouch == null) {
return;
}

let relevantTouch = null;
for (let touch of evt.changedTouches) {
if (touch.identifier == this.currentTouch.identifier) {
relevantTouch = touch;
break;
}
}

if (relevantTouch == null) {
return;
}

this.currentTouch = null;
tis.onMouseUp();
});

can.addEventListener("touchmove", evt => {
if (this.currentTouch == null) {
return;
}

let relevantTouch = null;
for (let touch of evt.changedTouches) {
if (touch.identifier == this.currentTouch.identifier) {
relevantTouch = touch;
break;
}
}

if (relevantTouch == null) {
return;
}

let deltaX = this.currentTouch.clientX - relevantTouch.clientX;
let deltaY = this.currentTouch.clientY - relevantTouch.clientY;
this.currentTouch = relevantTouch;

this.onMouseMove(relevantTouch.clientX, relevantTouch.clientY, -deltaX, -deltaY, 1);
});
}

onKeyDown(key) {
let responded = true;
if (key == "Escape" || key == "q") {
this.tooltip = null;
this.selectedNodes = [];
this.mouseMoveStart = null;
this.currentLink = null;
this.selection = null;
this.requestFrame();
} else if (key == "Delete") {
for (let node of this.selectedNodes) {
this.deleteNode(node);
}
this.selectedNodes = [];
} else if (key == "Enter") {
for (let node of this.selectedNodes) {
if (node.activate) {
node.activate();
this.requestFrame();
}
}
} else {
responded = false;
}

if (responded) {
evt.preventDefault();
}
}

onScroll(delta) {
this.scale -= delta * (this.scale / 40);
if (this.scale > 200) {
this.scale = 200;
} else if (this.scale < 10) {
this.scale = 10;
}

this.requestFrame();
}

onMouseDown(offsetX, offsetY, buttons, keys) {
this.clickCancelled = false;
let [x, y] = this.coordsFromScreenPos(offsetX, offsetY);
let node = this.getNodeAt(x, y);
this.mouseMoveStart = {x, y, node};

if (keys.shiftKey && buttons == 1) {
this.selectedNodes = [];
this.selection = {fromX: x, fromY: y, toX: x, toY: y};
this.tooltip = null;
this.requestFrame();
} else if (node != null && buttons == 1) {
if (this.selectedNodes.indexOf(node) == -1) {
this.selectedNodes = [node];
this.requestFrame();
}
} else if (buttons == 1) {
this.selectedNodes = [];
this.requestFrame();
}
}

onMouseUp() {
this.mouseMoveStart = null;

if (this.selection) {
this.selectedNodes = [];
let [x1, y1] = [this.selection.fromX, this.selection.fromY];
let [x2, y2] = [this.selection.toX, this.selection.toY];

// Make sure we actually have a rect from top-left to bottom-right
let tmp;
if (x1 > x2) {
tmp = x1;
x1 = x2;
x2 = tmp;
}
if (y1 > y2) {
tmp = y1;
y1 = y2;
y2 = tmp;
}

for (let node of this.nodes) {
let nodeX1 = node.x;
let nodeY1 = node.y - 0.5;
let nodeX2 = nodeX1 + node.width;
let nodeY2 = nodeY1 + node.height;

if (x1 < nodeX2 && x2 > nodeX1 && y1 < nodeY2 && y2 > nodeY1) {
this.selectedNodes.push(node);
}
}

this.selection = null;
this.requestFrame();
}
}

onMouseMove(offsetX, offsetY, movementX, movementY, buttons) {
let [x, y] = this.coordsFromScreenPos(offsetX, offsetY);
let dx = 0, dy = 0;
if (this.mouseMoveStart) {
dx = x - this.mouseMoveStart.x;
dy = y - this.mouseMoveStart.y;
}

if (
buttons == 1 && this.selection == null &&
this.mouseMoveStart != null && this.currentLink == null) {
if (this.mouseMoveStart.node == null) {
this.x -= movementX / this.scale;
this.y -= movementY / this.scale;
this.requestFrame();
} else {
let nodeDX = Math.round(dx);
let nodeDY = Math.round(dy);

if (nodeDX != 0 || nodeDY != 0) {
this.clickCancelled = true;
this.mouseMoveStart.x += nodeDX;
this.mouseMoveStart.y += nodeDY;

for (let node of this.selectedNodes) {
node.x += nodeDX;
node.y += nodeDY;
}

this.requestFrame();
}
}
}

let node = this.getNodeAt(x, y);
let io = null;
if (node != null) {
io = this.getNodeIOAt(node, x, y);
}

if (io == null && this.tooltip == null) {
// Do nothing
} else if (io == null && this.tooltip != null) {
this.tooltip = null;
this.requestFrame();
} else {
this.tooltip = {
text: io.io.name,
x, y,
};
this.requestFrame();
}

if (this.selection != null) {
this.selection.toX = x;
this.selection.toY = y;
this.requestFrame();
}

if (this.currentLink != null) {
let p = this.currentLink.path[this.currentLink.path.length - 1];
p.x = x;
p.y = y;
this.requestFrame();
}
}

onClick(offsetX, offsetY) {
if (this.clickCancelled) {
return;
}

let [x, y] = this.coordsFromScreenPos(offsetX, offsetY);
let node = this.getNodeAt(x, y);
let io = null;
if (node != null) {
io = this.getNodeIOAt(node, x, y);
}

if (this.currentLink != null) {
if (io) {
let fromNode = this.currentLink.from.node;
let fromIO = this.currentLink.from.io;
if (fromIO.type == "input" && io.type == "output") {
io.io.link.connect(fromNode, fromIO.index);
} else if (fromIO.type == "output" && io.type == "input") {
fromIO.io.link.connect(node, io.index);
}

if (fromIO.type != io.type) {
this.currentLink = null;
this.requestFrame();
}
} else {
let path = this.currentLink.path;
path[path.length - 1].x = Math.round(path[path.length - 1].x);
path[path.length - 1].y = Math.round(path[path.length - 1].y);
path.push({x, y});
this.requestFrame();
}
} else if (io != null) {
this.currentLink = {from: {node, io}, path: [{x, y}]};
} else {
if (node && node.activate) {
node.activate();
this.requestFrame();
}
}
}

deleteNode(node) {
for (let i = 0; i < node.inputs.length; ++i) {
let input = node.inputs[i];
for (let link of input.links) {
link.disconnect(node, i);
}
}

for (let out of node.outputs) {
out.link.destroy();
}

this.nodes.splice(this.nodes.indexOf(node), 1);
}

coordsFromScreenPos(screenX, screenY) {
return [
(screenX - this.can.offsetWidth / 2) / this.scale + this.x,
(screenY - this.can.offsetHeight / 2) / this.scale + this.y,
];
}

getNodeAt(x, y) {
for (let i = this.nodes.length - 1; i >= 0; --i) {
let node = this.nodes[i];
let nodeX1 = node.x - 0.5;
let nodeY1 = node.y - 0.5;
let nodeX2 = node.x + node.width + 0.5;
let nodeY2 = node.y - 0.5 + node.height;

if (
x >= nodeX1 && x <= nodeX2 &&
y >= nodeY1 && y <= nodeY2) {
return node;
}
}

return null;
}

getNodeIOAt(node, x, y) {
for (let n = 0; n < node.inputs.length; ++n) {
let dx = node.x - x;
let dy = node.y + n - y;
let dist = Math.abs(Math.sqrt(dx * dx + dy * dy));
if (dist < 0.5) {
return {type: "input", io: node.inputs[n], index: n};
}
}

for (let n = 0; n < node.outputs.length; ++n) {
let dx = node.x + node.width - x;
let dy = node.y + n - y;
let dist = Math.abs(Math.sqrt(dx * dx + dy * dy));
if (dist < 0.5) {
return {type: "output", io: node.outputs[n], index: n};
}
}

return null;
}

requestFrame() {
if (this.frameRequested) {
return;
}

this.frameRequested = true;
window.requestAnimationFrame(() => {
this.frameRequested = false;
this.draw();
});
}

addNodeAtCursor(node) {
node.x = Math.round(this.x);
node.y = Math.round(this.y);
this.nodes.push(node);
this.requestFrame();
}

draw() {
let pixelRatio = 1;
if (window.devicePixelRatio != null) {
pixelRatio = window.devicePixelRatio;
}

this.can.width = this.can.offsetWidth * pixelRatio;
this.can.height = this.can.offsetHeight * pixelRatio;
let canWidth = this.can.width / this.scale;
let canHeight = this.can.height / this.scale;
this.ctx.save();

this.ctx.translate(this.can.width / 2, this.can.height / 2);
this.ctx.scale(this.scale * pixelRatio, this.scale * pixelRatio);
this.ctx.translate(-this.x, -this.y);

this.ctx.strokeStyle = "rgba(0, 0, 0, 0.2)";
this.ctx.lineWidth = 1 / this.scale;

let gridStartX = Math.floor(this.x - canWidth / 2);
let gridStartY = Math.floor(this.y - canHeight / 2);

for (let x = gridStartX; x < canWidth + gridStartX + 1; x += 1) {
this.ctx.beginPath();
this.ctx.moveTo(x, gridStartY - canHeight - 1);
this.ctx.lineTo(x, gridStartY + canHeight + 1);
this.ctx.stroke();
}

for (let y = gridStartY; y < canHeight + gridStartY + 1; y += 1) {
this.ctx.beginPath();
this.ctx.moveTo(gridStartX - canWidth - 1, y);
this.ctx.lineTo(gridStartX + canWidth + 1, y);
this.ctx.stroke();
}

this.ctx.font = "0.8px Arial";
this.ctx.lineWidth = 0.1;

for (let node of this.nodes) {
for (let output of node.outputs) {
let link = output.link;

if (link.current) {
this.ctx.strokeStyle = "red";
} else {
this.ctx.strokeStyle = "brown";
}

for (let conn of link.connections) {
this.ctx.beginPath();
this.ctx.moveTo(link.from.x + link.from.width, link.from.y + link.index);
this.ctx.lineTo(conn.node.x, conn.node.y + conn.index);
this.ctx.stroke();
}
}
}

this.ctx.strokeStyle = "black";
this.ctx.lineWidth = 0.1;
this.ctx.setLineDash([0.2, 0.1]);
for (let node of this.selectedNodes) {
this.ctx.beginPath();
this.ctx.strokeRect(node.x - 0.1, node.y - 0.5 - 0.1, node.width + 0.2, node.height + 0.2);
}
this.ctx.lineWidth = 0.1;
this.ctx.setLineDash([]);

for (let node of this.nodes) {
if (node.lit) {
this.ctx.fillStyle = "red";
} else {
this.ctx.fillStyle = "brown";
}

this.ctx.strokeStyle = "black";

let metrics = this.ctx.measureText(node.name);
let textWidth = metrics.width;
this.ctx.fillRect(node.x, node.y - 0.5, node.width, node.height);

this.ctx.fillStyle = "white";
this.ctx.fillText(
node.name,
node.x + node.width / 2 - textWidth / 2,
node.y - 0.5 + 0.8);

this.ctx.fillStyle = "black";
this.ctx.strokeStyle = "white";

for (let y = 0; y < node.inputs.length; ++y) {
this.ctx.beginPath();
this.ctx.arc(node.x, node.y + y, 0.45, 0, 2 * Math.PI);
this.ctx.fill();
this.ctx.stroke();
}

for (let y = 0; y < node.outputs.length; ++y) {
this.ctx.beginPath();
this.ctx.arc(node.x + node.width, node.y + y, 0.45, 0, 2 * Math.PI);
this.ctx.fill();
this.ctx.stroke();
}
}

if (this.currentLink != null) {
this.ctx.strokeStyle = "black";

let from = this.currentLink.from;
let fromX = from.node.x;
let fromY = from.node.y + from.io.index;
if (from.io.type == "output") {
fromX += from.node.width;
}

this.ctx.beginPath();
this.ctx.moveTo(fromX, fromY);
for (let el of this.currentLink.path) {
this.ctx.lineTo(el.x, el.y);
}

this.ctx.stroke();
}

if (this.tooltip != null) {
this.ctx.fillStyle = "black";
this.ctx.strokeStyle = "grey";

let metrics = this.ctx.measureText(this.tooltip.text);
let textWidth = metrics.width;
let textAscent = metrics.actualBoundingBoxAscent;
let x = this.tooltip.x - (textWidth / 4);
let y = this.tooltip.y - 1.2;
this.ctx.beginPath();
this.ctx.rect(
x - 0.17, y - 0.2, textWidth + 0.4, 1.1);
this.ctx.fill();
this.ctx.stroke();

this.ctx.fillStyle = "white";
this.ctx.fillText(this.tooltip.text, x, y + 0.6);
}

if (this.selection != null) {
this.ctx.fillStyle = "rgba(100, 100, 250, 0.2)";
this.ctx.strokeStyle = "black";
this.ctx.beginPath();
this.ctx.rect(
this.selection.fromX, this.selection.fromY,
this.selection.toX - this.selection.fromX,
this.selection.toY - this.selection.fromY);
this.ctx.fill();
this.ctx.stroke();
}

this.ctx.restore();
}

update() {
for (let node of this.nodes) {
node.tick();
}

for (let node of this.nodes) {
node.commit();
}

this.requestFrame();
}
}

let canvas;
let sim;

onMount(() => {
sim = new LogicSim(canvas);
});
</script>

+ 24
- 0
src/MainMenu.svelte View File

@@ -0,0 +1,24 @@
<main>
<h1>Game</h1>

<a on:click={play} href="#/play">Start</a>
</main>

<style>
main {
max-width: 1000px;
margin: auto;
}

a {
color: #aaa;
}
</style>

<script>
function play() {
let el = document.getElementById("soundtrack");
console.log(el);
el.play();
}
</script>

+ 31
- 0
src/Router.svelte View File

@@ -0,0 +1,31 @@
<main>
<svelte:component this={element} />
</main>

<script>
import MainMenu from "./MainMenu.svelte";
import SceneSelector from "./SceneSelector.svelte";

let routes = {
"/": MainMenu,
"/play": SceneSelector,
};

let element = null;

function route() {
let path = location.hash.substr(1);
if (path == "") {
path = "/";
}

if (routes[path] != null) {
element = routes[path];
} else {
element = routes["/"];
}
}

route();
window.addEventListener("hashchange", route);
</script>

+ 49
- 38
src/Scene.svelte View File

@@ -1,7 +1,55 @@
<main>
{#if showIntro}
<h1>{level.name}</h1>

<div class="story">
<p></p>
{@html level.pages[page]}
<p></p>
</div>

<div class="controls">
{page + 1}/{level.pages.length}
<button on:click={previous} disabled={page == 0}>Back</button>
{#if page < level.pages.length - 1}
<button on:click={next}>Next</button>
{:else}
<button on:click={begin}>Begin</button>
{/if}
<button on:click={begin}>Skip</button>
</div>
{:else}
<CircuitSim />
{/if}
</main>

<style>
h1 {
margin-bottom: 0px;
text-align: center;
}

.story {
max-width: 800px;
margin: auto;
}

.story p:first-child {
margin: 0px;
}

.controls {
text-align: center;
}
</style>

<script>
export let level;

import CircuitSim from './CircuitSim.svelte';

let page = 0;
let showIntro = true;

function previous() {
if (page > 0) {
@@ -16,43 +64,6 @@
}

function begin() {
console.log("ok");
showIntro = false;
}
</script>

<main>
<h1>{level.name}</h1>

<div id="story">
<p></p>
{@html level.pages[page]}
<p></p>
</div>

<div id="controls">
{page + 1}/{level.pages.length}
<button on:click={previous} disabled={page == 0}>Back</button>
{#if page < level.pages.length - 1}
<button on:click={next}>Next</button>
{:else}
<button on:click={begin}>Begin</button>
{/if}
</div>
</main>

<style>
h1 {
margin-bottom: 0px;
text-align: center;
}
#story {
max-width: 800px;
margin: auto;
}
#story p:first-child {
margin: 0px;
}
#controls {
text-align: center;
}
</style>

+ 11
- 0
src/SceneSelector.svelte View File

@@ -0,0 +1,11 @@
<main>
<Scene level={level} />
</main>

<script>
import Scene from './Scene.svelte';

import level01 from './levels/01-intro.js';

let level = level01;
</script>

+ 2
- 7
src/main.js View File

@@ -1,10 +1,5 @@
import Scene from './Scene.svelte';
import Router from './Router.svelte';

import level01 from './levels/01-intro.js';

export default new Scene({
export default new Router({
target: document.body,
props: {
level: level01,
},
});

Loading…
Cancel
Save