Browse Source

add controls and a sandbox mode

main
Martin Dørum 2 years ago
parent
commit
f8d1e8d249
8 changed files with 254 additions and 166 deletions
  1. 60
    162
      src/CircuitSim.svelte
  2. 5
    2
      src/MainMenu.svelte
  3. 2
    0
      src/Router.svelte
  4. 15
    0
      src/Sandbox.svelte
  5. 9
    1
      src/Scene.svelte
  6. 0
    1
      src/SceneSelector.svelte
  7. 159
    0
      src/circuit-components.js
  8. 4
    0
      src/levels/01-intro.js

+ 60
- 162
src/CircuitSim.svelte View File

@@ -1,177 +1,39 @@
<canvas bind:this={canvas}></canvas>
<main>
<canvas bind:this={canvas}></canvas>
<div class="controls">
{#each components as comp}
<button on:click={sim.addNodeAtCursor(new comp.ctor())}>{comp.name}</button>
{/each}
</div>
</main>

<style>
canvas {
main, 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;
}
.controls {
position: absolute;
bottom: 0px;
left: 0px;
width: 100%;
height: 40px;
}

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;
}
.controls > * {
box-sizing: border-box;
height: 100%;
}
</style>

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;
}
}
}
<script>
import {onMount, onDestroy} from 'svelte';

commit() {
this.lit = this.nextLit;
}
}
export let components;

class LogicSim {
constructor(can) {
@@ -184,12 +46,15 @@
this.currentTouch = null;
this.x = 0;
this.y = 0;
this.cursorX = 0;
this.cursorY = 0;

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

this.requestFrame();

@@ -379,6 +244,9 @@

onMouseMove(offsetX, offsetY, movementX, movementY, buttons) {
let [x, y] = this.coordsFromScreenPos(offsetX, offsetY);
this.cursorX = x;
this.cursorY = y;

let dx = 0, dy = 0;
if (this.mouseMoveStart) {
dx = x - this.mouseMoveStart.x;
@@ -442,6 +310,17 @@
p.y = y;
this.requestFrame();
}

if (this.cursorAttachedNode != null) {
let node = this.cursorAttachedNode;
let newX = Math.round(x - node.width / 2);
let newY = Math.round(y - node.height / 2);
if (newX != node.x || newY != node.y) {
node.x = newX;
node.y = newY;
this.requestFrame();
}
}
}

onClick(offsetX, offsetY) {
@@ -450,6 +329,15 @@
}

let [x, y] = this.coordsFromScreenPos(offsetX, offsetY);

if (this.cursorAttachedNode != null) {
let node = this.cursorAttachedNode;
node.x = Math.round(x - node.width / 2);
node.y = Math.round(y - node.height / 2);
this.cursorAttachedNode = null;
return;
}

let node = this.getNodeAt(x, y);
let io = null;
if (node != null) {
@@ -562,9 +450,10 @@
}

addNodeAtCursor(node) {
node.x = Math.round(this.x);
node.y = Math.round(this.y);
node.x = Math.round(this.cursorX - node.width / 2);
node.y = Math.round(this.cursorY - node.height / 2);
this.nodes.push(node);
this.cursorAttachedNode = node;
this.requestFrame();
}

@@ -741,8 +630,17 @@

let canvas;
let sim;
let interval = null;

onMount(() => {
sim = new LogicSim(canvas);
interval = setInterval(sim.update.bind(sim), 100);
});

onDestroy(() => {
if (interval != null) {
clearInterval(interval);
interval = null;
}
});
</script>

+ 5
- 2
src/MainMenu.svelte View File

@@ -1,7 +1,10 @@
<main>
<h1>Game</h1>

<a on:click={play} href="#/play">Start</a>
<ul>
<li><a on:click={play} href="#/play">Start</a></li>
<li><a href="#/sandbox">Sandbox Mode</a></li>
</ul>
</main>

<style>
@@ -11,7 +14,7 @@
}

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


+ 2
- 0
src/Router.svelte View File

@@ -5,10 +5,12 @@
<script>
import MainMenu from "./MainMenu.svelte";
import SceneSelector from "./SceneSelector.svelte";
import Sandbox from "./Sandbox.svelte";

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

let element = null;

+ 15
- 0
src/Sandbox.svelte View File

@@ -0,0 +1,15 @@
<main>
<CircuitSim components={components} />
</main>

<script>
import CircuitSim from './CircuitSim.svelte';
import * as comps from './circuit-components.js';

let components = [
{name: "Input", ctor: comps.Input},
{name: "NotGate", ctor: comps.NotGate},
{name: "Diode", ctor: comps.Diode},
{name: "Lamp", ctor: comps.Lamp},
];
</script>

+ 9
- 1
src/Scene.svelte View File

@@ -19,7 +19,7 @@
<button on:click={begin}>Skip</button>
</div>
{:else}
<CircuitSim />
<CircuitSim components={components} />
{/if}
</main>

@@ -47,6 +47,14 @@
export let level;

import CircuitSim from './CircuitSim.svelte';
import * as availableComponents from './circuit-components.js';

let components = level.components.map(name => {
if (availableComponents[name] == null) {
throw new Error(name, "is not a valid component name");
}
return {name, ctor: availableComponents[name]};
});

let page = 0;
let showIntro = true;

+ 0
- 1
src/SceneSelector.svelte View File

@@ -4,7 +4,6 @@

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

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

let level = level01;

+ 159
- 0
src/circuit-components.js View File

@@ -0,0 +1,159 @@
export 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;
}
}

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

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

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

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

+ 4
- 0
src/levels/01-intro.js View File

@@ -6,6 +6,10 @@ export default {
outputs: [
"LED",
],
components: [
"Input",
"NotGate",
],

tests: [
[false], [false],

Loading…
Cancel
Save