Circuit simulation online

master
Louis Pearson 2022-01-19 22:05:30 -07:00
parent 878665056c
commit dbf10e0aaa
2 changed files with 122 additions and 25 deletions

View File

@ -1,5 +1,4 @@
const std = @import("std"); const std = @import("std");
const assets = @import("assets");
fn is_circuit(tile: u8) bool { fn is_circuit(tile: u8) bool {
return is_plug(tile) or is_conduit(tile) or is_switch(tile); return is_plug(tile) or is_conduit(tile) or is_switch(tile);
@ -16,10 +15,14 @@ fn is_conduit(tile: u8) bool {
(tile >= 176 and tile < 180); (tile >= 176 and tile < 180);
} }
fn is_switch(tile: u8) bool { pub fn is_switch(tile: u8) bool {
return tile >= 134 and tile < 136; return tile >= 134 and tile < 136;
} }
fn toggle_switch(tile: u8) u8 {
return if (tile == 134) 135 else 134;
}
const Side = enum(u2) { up, right, down, left }; const Side = enum(u2) { up, right, down, left };
fn side(s: Side) u2 { fn side(s: Side) u2 {
return @enumToInt(s); return @enumToInt(s);
@ -51,10 +54,10 @@ fn get_inputs(tile: u8) Current {
178 => .{ true, false, true, true }, 178 => .{ true, false, true, true },
179 => .{ true, true, true, false }, 179 => .{ true, true, true, false },
// Plugs // Plugs
149 => .{ false, false, true, false }, 150 => .{ false, false, true, false },
150 => .{ true, false, false, false }, 151 => .{ true, false, false, false },
151 => .{ false, false, false, true }, 152 => .{ false, false, false, true },
152 => .{ false, true, false, false }, 153 => .{ false, true, false, false },
// Closed switch // Closed switch
134 => .{ true, false, true, false }, 134 => .{ true, false, true, false },
else => .{ false, false, false, false }, else => .{ false, false, false, false },
@ -66,10 +69,10 @@ const Plugs = [4]bool;
fn get_plugs(tile: u8) Plugs { fn get_plugs(tile: u8) Plugs {
return switch (tile) { return switch (tile) {
// Plugs // Plugs
149 => .{ true, false, false, false }, 150 => .{ true, false, false, false },
150 => .{ false, false, true, false }, 151 => .{ false, false, true, false },
151 => .{ false, true, false, false }, 152 => .{ false, true, false, false },
152 => .{ false, false, false, true }, 153 => .{ false, false, false, true },
// Cross // Cross
146 => .{ true, true, true, true }, 146 => .{ true, true, true, true },
else => .{ false, false, false, false }, else => .{ false, false, false, false },
@ -103,11 +106,16 @@ fn dir(s: Side) Cell {
}; };
} }
pub fn get_cell(c: Cell) ?u8 { pub fn get_cell(this: @This(), c: Cell) ?u8 {
if (c[0] < 0 or c[0] > 19 or c[1] > 19 or c[1] < 0) return null; if (c[0] < 0 or c[0] > 19 or c[1] > 19 or c[1] < 0) return null;
const i = @intCast(usize, @mod(c[0], 20) + (c[1] * 20)); const i = @intCast(usize, @mod(c[0], 20) + (c[1] * 20));
return if (assets.conduit[i] != 0) assets.conduit[i] - 1 else null; return if (this.cells[i].tile != 0) this.cells[i].tile - 1 else null;
// return assets.conduit[i]; }
pub fn set_cell(this: *@This(), c: Cell, tile: u8) void {
if (c[0] < 0 or c[0] > 19 or c[1] > 19 or c[1] < 0) return;
const i = @intCast(usize, @mod(c[0], 20) + (c[1] * 20));
this.cells[i].tile = tile + 1;
} }
fn index2cell(i: usize) Cell { fn index2cell(i: usize) Cell {
@ -119,18 +127,26 @@ fn cell2index(c: Cell) ?usize {
return @intCast(usize, @mod(c[0], 20) + (c[1] * 20)); return @intCast(usize, @mod(c[0], 20) + (c[1] * 20));
} }
const CellState = bool; const CellState = struct { enabled: bool = false, tile: u8 };
const MAXCELLS = 400; const MAXCELLS = 400;
const CellMap = [MAXCELLS]CellState; // std.AutoHashMap(Cell, CellState); const CellMap = [MAXCELLS]CellState; // std.AutoHashMap(Cell, CellState);
offset: Cell, offset: Cell,
cells: CellMap, cells: CellMap,
bridges: std.BoundedArray([2]Cell, 10),
pub fn init() @This() { pub fn init(offset: Cell, map: []const u8) @This() {
return @This(){ var this = @This(){
.offset = Cell{ 0, 0 }, .offset = offset,
.cells = [1]CellState{false} ** 400, .cells = undefined,
.bridges = std.BoundedArray([2]Cell, 10).init(0) catch unreachable,
}; };
// TODO: copy only part of a map
for (map) |tile, i| {
this.cells[i].enabled = false;
this.cells[i].tile = tile;
}
return this;
} }
pub fn indexOf(this: @This(), cell: Cell) ?usize { pub fn indexOf(this: @This(), cell: Cell) ?usize {
@ -139,7 +155,30 @@ pub fn indexOf(this: @This(), cell: Cell) ?usize {
pub fn enable(this: *@This(), cell: Cell) void { pub fn enable(this: *@This(), cell: Cell) void {
if (this.indexOf(cell)) |c| { if (this.indexOf(cell)) |c| {
this.cells[c] = true; this.cells[c].enabled = true;
}
}
pub fn bridge(this: *@This(), cells: [2]Cell) void {
if (this.indexOf(cells[0])) |_| {
if (this.indexOf(cells[1])) |_| {
this.bridges.append(cells) catch unreachable;
}
}
}
pub fn enabled(this: @This(), cell: Cell) bool {
if (this.indexOf(cell)) |c| {
return this.cells[c].enabled;
}
return false;
}
pub fn toggle(this: *@This(), cell: Cell) void {
if (this.get_cell(cell)) |tile| {
if (is_switch(tile)) {
this.set_cell(cell, toggle_switch(tile));
}
} }
} }
@ -165,7 +204,7 @@ pub fn fill(this: *@This(), root: Cell) void {
q.insert(root); q.insert(root);
while (q.remove()) |cell| { while (q.remove()) |cell| {
const index = this.indexOf(cell) orelse continue; const index = this.indexOf(cell) orelse continue;
const tile = get_cell(cell) orelse continue; const tile = this.get_cell(cell) orelse continue;
if (visited.isSet(index)) continue; if (visited.isSet(index)) continue;
visited.set(index); visited.set(index);
this.enable(cell); this.enable(cell);
@ -173,8 +212,23 @@ pub fn fill(this: *@This(), root: Cell) void {
if (!conductor) continue; if (!conductor) continue;
const s = @intToEnum(Side, i); const s = @intToEnum(Side, i);
const delta = dir(s); const delta = dir(s);
// w4.trace("side {} ({}), delta {}", .{ s, i, delta });
q.insert(cell + delta); q.insert(cell + delta);
} }
if (is_plug(tile)) {
for (this.bridges.constSlice()) |b| {
if (@reduce(.And, b[0] == cell)) {
q.insert(b[1]);
} else if (@reduce(.And, b[1] == cell)) {
q.insert(b[0]);
}
}
}
} }
} }
pub fn clear(this: *@This()) void {
for (this.cells) |*cell| {
cell.enabled = false;
}
this.bridges.resize(0) catch unreachable;
}

View File

@ -115,7 +115,7 @@ const KB = 1024;
var heap: [8 * KB]u8 = undefined; var heap: [8 * KB]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&heap); var fba = std.heap.FixedBufferAllocator.init(&heap);
var world: World = World.init(fba.allocator()); var world: World = World.init(fba.allocator());
var circuit = Circuit.init(); var circuit = Circuit.init(Vec2{ 0, 0 }, &assets.conduit);
const anim_store = struct { const anim_store = struct {
const stand = Anim.frame(0); const stand = Anim.frame(0);
@ -191,6 +191,7 @@ export fn update() void {
world.process(1, &.{.pos}, velocityProcess); world.process(1, &.{.pos}, velocityProcess);
world.process(1, &.{ .pos, .physics }, physicsProcess); world.process(1, &.{ .pos, .physics }, physicsProcess);
world.processWithID(1, &.{ .pos, .control }, wireManipulationProcess); world.processWithID(1, &.{ .pos, .control }, wireManipulationProcess);
world.processWithID(1, &.{ .pos, .control }, circuitManipulationProcess);
world.process(1, &.{.wire}, wirePhysicsProcess); world.process(1, &.{.wire}, wirePhysicsProcess);
world.process(1, &.{ .pos, .control, .physics, .kinematic }, controlProcess); world.process(1, &.{ .pos, .control, .physics, .kinematic }, controlProcess);
world.process(1, &.{ .pos, .kinematic }, kinematicProcess); world.process(1, &.{ .pos, .kinematic }, kinematicProcess);
@ -198,6 +199,21 @@ export fn update() void {
world.process(1, &.{ .sprite, .controlAnim, .control }, controlAnimProcess); world.process(1, &.{ .sprite, .controlAnim, .control }, controlAnimProcess);
world.process(1, &.{ .pos, .sprite }, drawProcess); world.process(1, &.{ .pos, .sprite }, drawProcess);
circuit.clear();
const q = World.Query.require(&.{.wire});
var wireIter = world.iter(q);
while (wireIter.next()) |wireID| {
const e = world.get(wireID);
const nodes = e.wire.?.nodes.constSlice();
const cellBegin = world2cell(nodes[0].pos);
const cellEnd = world2cell(nodes[nodes.len - 1].pos);
circuit.bridge(.{ cellBegin, cellEnd });
}
for (assets.sources) |source| {
circuit.fill(source);
}
w4.DRAW_COLORS.* = 0x0210; w4.DRAW_COLORS.* = 0x0210;
for (assets.solid) |tilePlus, i| { for (assets.solid) |tilePlus, i| {
const tile = tilePlus - 1; const tile = tilePlus - 1;
@ -206,9 +222,10 @@ export fn update() void {
w4.blitSub(&assets.tiles, pos, .{ 8, 8 }, t, 128, .{ .bpp = .b2 }); w4.blitSub(&assets.tiles, pos, .{ 8, 8 }, t, 128, .{ .bpp = .b2 });
} }
for (assets.conduit) |tilePlus, i| { for (circuit.cells) |cell, i| {
const tilePlus = cell.tile;
if (tilePlus == 0) continue; if (tilePlus == 0) continue;
if (circuit.cells[i]) w4.DRAW_COLORS.* = 0x0210 else w4.DRAW_COLORS.* = 0x0310; if (circuit.cells[i].enabled) w4.DRAW_COLORS.* = 0x0210 else w4.DRAW_COLORS.* = 0x0310;
const tile = tilePlus - 1; const tile = tilePlus - 1;
const t = w4.Vec2{ @intCast(i32, (tile % 16) * 8), @intCast(i32, (tile / 16) * 8) }; const t = w4.Vec2{ @intCast(i32, (tile % 16) * 8), @intCast(i32, (tile / 16) * 8) };
const pos = w4.Vec2{ @intCast(i32, (i % 20) * 8), @intCast(i32, (i / 20) * 8) }; const pos = w4.Vec2{ @intCast(i32, (i % 20) * 8), @intCast(i32, (i / 20) * 8) };
@ -241,6 +258,10 @@ export fn update() void {
time += 1; time += 1;
} }
fn world2cell(vec: Vec2f) Vec2 {
return vec2ftovec2(vec / @splat(2, @as(f32, 8)));
}
/// pos should be in tile coordinates, not world coordinates /// pos should be in tile coordinates, not world coordinates
fn get_conduit(vec: Vec2) ?u8 { fn get_conduit(vec: Vec2) ?u8 {
const x = vec[0]; const x = vec[0];
@ -250,6 +271,28 @@ fn get_conduit(vec: Vec2) ?u8 {
return assets.conduit[@intCast(u32, i)]; return assets.conduit[@intCast(u32, i)];
} }
fn circuitManipulationProcess(_: f32, id: usize, pos: *Pos, control: *Control) void {
_ = id;
var offset = switch (control.facing) {
.left => Vec2f{ -6, -4 },
.right => Vec2f{ 6, -4 },
.up => Vec2f{ 0, -12 },
.down => Vec2f{ 0, 4 },
};
if (control.grabbing == null) {
const mapPos = vec2ftovec2(pos.pos + offset);
const cell = @divTrunc(mapPos, @splat(2, @as(i32, 8)));
if (circuit.get_cell(cell)) |tile| {
if (Circuit.is_switch(tile)) {
indicator = .{ .t = .plug, .pos = cell * @splat(2, @as(i32, 8)) + Vec2{ 4, 4 } };
if (input.btnp(.one, .two)) {
circuit.toggle(cell);
}
}
}
}
}
fn wireManipulationProcess(_: f32, id: usize, pos: *Pos, control: *Control) void { fn wireManipulationProcess(_: f32, id: usize, pos: *Pos, control: *Control) void {
var offset = switch (control.facing) { var offset = switch (control.facing) {
.left => Vec2f{ -6, -4 }, .left => Vec2f{ -6, -4 },
@ -277,7 +320,7 @@ fn wireManipulationProcess(_: f32, id: usize, pos: *Pos, control: *Control) void
} }
var mapPos = vec2ftovec2((pos.pos + offset) / @splat(2, @as(f32, 8))); var mapPos = vec2ftovec2((pos.pos + offset) / @splat(2, @as(f32, 8)));
if (Circuit.is_plug(Circuit.get_cell(mapPos) orelse 0)) { if (Circuit.is_plug(circuit.get_cell(mapPos) orelse 0)) {
indicator = .{ .t = .plug, .pos = mapPos * @splat(2, @as(i32, 8)) + Vec2{ 4, 4 } }; indicator = .{ .t = .plug, .pos = mapPos * @splat(2, @as(i32, 8)) + Vec2{ 4, 4 } };
if (input.btnp(.one, .two)) { if (input.btnp(.one, .two)) {
e.wire.?.nodes.slice()[details.which].pinned = true; e.wire.?.nodes.slice()[details.which].pinned = true;