Compare commits

...

10 Commits

Author SHA1 Message Date
Louis Pearson 8fe3e42752 Update flake.nix 2022-08-26 10:17:13 -06:00
Louis Pearson e45f233fa9 Merge branch 'wapm' 2022-08-17 19:42:49 -06:00
Louis Pearson 8348ff7c4c Get wapm working 2022-08-17 19:40:50 -06:00
Louis Pearson e2fb6a88ae Add wapm files 2022-08-17 19:23:19 -06:00
Louis Pearson 8a18d321b3 Add direction enum, improve wire loading/saving 2022-08-11 00:42:48 -06:00
Louis Pearson 05328945e6 Fix map extraction 2022-08-10 21:12:17 -06:00
Louis Pearson aa539339b9 Switch to a multi stage circuit build 2022-08-10 18:25:57 -06:00
Louis Pearson 4ccc7e9a2e Begin adding directed conduit pieces 2022-08-10 17:17:25 -06:00
Louis Pearson e36cffb8c6 Update LDtk 2022-08-10 15:45:12 -06:00
Louis Pearson df2a3a7db5 Playing around with the map 2022-08-09 23:06:09 -06:00
12 changed files with 1783 additions and 674 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@
/zig-out
/src/zig-cache/
/bundle/
wapm_packages

25
README.md Normal file
View File

@ -0,0 +1,25 @@
# Wired
A puzzle platformer with wires.
## Controls
- Left/Right: Move left and right
- Up/Down: Look at items above and below
- X: Jump
- Z: Select
## Dependencies
- `zig` to compile the code
- `wasm-opt` to optimize the generated wasm file for release. It is a part of `binaryen`
- `wasm4` to run the generated cart
## Building
``` shellsession
git clone --recursive
zig build # makes a debug build
w4 run zig-out/lib/cart.wasm
zig build opt -Drelease-small # optimize cart size for release
```

File diff suppressed because it is too large Load Diff

2
deps/zig-ldtk vendored

@ -1 +1 @@
Subproject commit e93618602d79d1e998a10c0ce1b4473459b19ff4
Subproject commit d1ace5b48eaf1caa3bce44add0d14e4fb2f10061

View File

@ -32,11 +32,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1660017629,
"narHash": "sha256-Koz6/k7c6hx4qVz/bboxdR2QsBdkxjRWpNmsOWJtXZE=",
"lastModified": 1660137652,
"narHash": "sha256-L92gcG6Ya4bqjJmStl/HTENhc0PR9lmVgTmeVpk13Os=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9f15d6c3a74d2778c6e1af67947c95f100dc6fd2",
"rev": "a7f89ddd6313ef88fc9c6534ac2511ba9da85fdb",
"type": "github"
},
"original": {
@ -70,11 +70,11 @@
},
"unstable": {
"locked": {
"lastModified": 1659981942,
"narHash": "sha256-uCFiP/B/NXOWzhN6TKfMbSxtVMk1bVnCrnJRjCF6RmU=",
"lastModified": 1660071133,
"narHash": "sha256-XX6T9wcvEZIVWY4TO5O1d2MgFyFrF2v4TpCFs7fjdn8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "39d7f929fbcb1446ad7aa7441b04fb30625a4190",
"rev": "36cc29d837e7232e3176e4651e8e117a6f231793",
"type": "github"
},
"original": {
@ -89,15 +89,15 @@
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1659919434,
"narHash": "sha256-U6QsM5FbFpEkqPfXwe/QvNdCWwBIXW9A3fhXo3hFps8=",
"owner": "arqv",
"lastModified": 1661317012,
"narHash": "sha256-3Lm//qoKwWj9p/gdCaLSASB9kvBw1vfC9BBYUvhVbWU=",
"owner": "mitchellh",
"repo": "zig-overlay",
"rev": "db9604e7fff0a41f06302d61dd4f320c4e23b81e",
"rev": "252c13ba498106f37054ad2c4db8e261f569a81e",
"type": "github"
},
"original": {
"owner": "arqv",
"owner": "mitchellh",
"repo": "zig-overlay",
"type": "github"
}

View File

@ -3,7 +3,7 @@
inputs = {
flake-utils.url = "github:numtide/flake-utils";
zig.url = "github:arqv/zig-overlay";
zig.url = "github:mitchellh/zig-overlay";
unstable.url = "nixpkgs/nixos-unstable";
};
@ -18,7 +18,7 @@
# nix develop
devShells.default = pkgs.mkShell {
buildInputs = [
zig.packages.${system}.master.latest
zig.packages.${system}.master
pkgs.butler
pkgs.binaryen
pkgs.tiled

View File

@ -21,10 +21,6 @@ pub const Options = struct {
db: world.Database,
};
fn is_solid(tile: u7) bool {
return tile != 0 and tile != 1;
}
/// Extracts a compressed level into the map and circuit buffers
pub fn extractLevel(opt: Options) !void {
w4.tracef("extract begin");
@ -46,7 +42,7 @@ pub fn extractLevel(opt: Options) !void {
circuit.map_size = .{ level.width, height };
w4.tracef("%d", @src().line);
var auto_map = try alloc.alloc(bool, size);
var auto_map = try alloc.alloc(world.SolidType, size);
defer alloc.free(auto_map);
var circuit_map = try alloc.alloc(CircuitType, size);
@ -56,7 +52,14 @@ pub fn extractLevel(opt: Options) !void {
for (tiles) |data, i| {
switch (data) {
.tile => |tile| {
auto_map[i] = false;
w4.tracef("[extract tile] [%d] %d", i, tile);
const is_solid = world.Tiles.is_solid(tile);
const is_oneway = world.Tiles.is_solid(tile);
auto_map[i] = solid_type: {
if (is_solid) break :solid_type .Solid;
if (is_oneway) break :solid_type .Oneway;
break :solid_type .Empty;
};
map.tiles[i] = tile;
circuit_map[i] = .None;
},
@ -79,7 +82,8 @@ pub fn extractLevel(opt: Options) !void {
const y = @divTrunc(i, width);
const stride = width;
if (!auto_map[i]) {
w4.tracef("[extract] %d (%d, %d)", @enumToInt(auto_map[i]), x, y);
if (auto_map[i] == .Empty) {
autotiles[i] = null;
continue;
}
@ -93,25 +97,25 @@ pub fn extractLevel(opt: Options) !void {
// Check horizontal neighbors
if (x == 0) {
west = out_of_bounds;
east = auto_map[i + 1];
east = auto_map[i + 1] == .Solid;
} else if (x == width - 1) {
west = auto_map[i - 1];
west = auto_map[i - 1] == .Solid;
east = out_of_bounds;
} else {
west = auto_map[i - 1];
east = auto_map[i + 1];
west = auto_map[i - 1] == .Solid;
east = auto_map[i + 1] == .Solid;
}
// Check vertical neighbours
if (y == 0) {
north = out_of_bounds;
south = auto_map[i + stride];
south = auto_map[i + stride] == .Solid;
} else if (y == height - 1) {
north = auto_map[i - stride];
north = auto_map[i - stride] == .Solid;
south = out_of_bounds;
} else {
north = auto_map[i - stride];
south = auto_map[i + stride];
north = auto_map[i - stride] == .Solid;
south = auto_map[i + stride] == .Solid;
}
autotiles[i] = AutoTile{
@ -125,14 +129,20 @@ pub fn extractLevel(opt: Options) !void {
for (autotiles) |autotile_opt, i| {
if (autotile_opt) |autotile| {
const tile = tileset.find(autotile);
const tile = switch (auto_map[i]) {
.Solid => tileset.find(autotile),
.Oneway => world.Tiles.OneWayMiddle,
.Empty => 0,
};
map.tiles[i] = tile;
}
}
var autocircuit = try alloc.alloc(?AutoTile, size);
defer alloc.free(autocircuit);
w4.tracef("autotile circuit");
// Auto generate circuit
// Re-use autotiles to save memory
{
var i: usize = 0;
while (i < size) : (i += 1) {
@ -154,7 +164,7 @@ pub fn extractLevel(opt: Options) !void {
}
if (circuit_map[i] == .None) {
autotiles[i] = null;
autocircuit[i] = null;
continue;
}
@ -170,28 +180,28 @@ pub fn extractLevel(opt: Options) !void {
// Check horizontal neighbors
if (x == 0) {
west = out_of_bounds;
east = circuit_map[i + 1] != .None;
east = circuit_map[i + 1] != .None and circuit_map[i + 1] != .Conduit_Vertical;
} else if (x == width - 1) {
west = circuit_map[i - 1] != .None;
west = circuit_map[i - 1] != .None and circuit_map[i - 1] != .Conduit_Vertical;
east = out_of_bounds;
} else {
west = circuit_map[i - 1] != .None;
east = circuit_map[i + 1] != .None;
west = circuit_map[i - 1] != .None and circuit_map[i - 1] != .Conduit_Vertical;
east = circuit_map[i + 1] != .None and circuit_map[i + 1] != .Conduit_Vertical;
}
// Check vertical neighbours
if (y == 0) {
north = out_of_bounds;
south = circuit_map[i + stride] != .None;
south = circuit_map[i + stride] != .None and circuit_map[i + stride] != .Conduit_Horizontal;
} else if (y == height - 1) {
north = circuit_map[i - stride] != .None;
north = circuit_map[i - stride] != .None and circuit_map[i - stride] != .Conduit_Horizontal;
south = out_of_bounds;
} else {
north = circuit_map[i - stride] != .None;
south = circuit_map[i + stride] != .None;
north = circuit_map[i - stride] != .None and circuit_map[i - stride] != .Conduit_Horizontal;
south = circuit_map[i + stride] != .None and circuit_map[i + stride] != .Conduit_Horizontal;
}
autotiles[i] = AutoTile{
autocircuit[i] = AutoTile{
.North = north,
.South = south,
.West = west,
@ -200,15 +210,21 @@ pub fn extractLevel(opt: Options) !void {
}
}
for (autotiles) |autotile_opt, i| {
for (autocircuit) |autotile_opt, i| {
if (autotile_opt) |autotile| {
const tile = switch (circuit_map[i]) {
.Conduit, .Source, .Join => opt.conduit.find(autotile),
.Conduit,
.Source,
.Join,
=> opt.conduit.find(autotile),
.Conduit_Vertical => opt.conduit.find(.{ .North = true, .South = true, .West = false, .East = false }),
.Conduit_Horizontal => opt.conduit.find(.{ .North = false, .South = false, .West = true, .East = true }),
.Switch_On => opt.switch_on.find(autotile),
.Switch_Off => opt.switch_off.find(autotile),
.Plug, .Socket => opt.plug.find(autotile),
.And => world.Tiles.LogicAnd,
.Xor => world.Tiles.LogicXor,
.Diode => world.Tiles.LogicDiode,
.None, .Outlet => 0,
};
circuit.map[i] = tile;

View File

@ -50,6 +50,19 @@ const Wire = struct {
node.pos = b + @splat(2, @intToFloat(f32, i)) * size / @splat(2, @intToFloat(f32, this.nodes.len));
}
}
pub fn addInline(this: *@This(), div: usize, point: Vec2f) !void {
const divf = @splat(2, @intToFloat(f32, div));
var last = this.end().pos;
const dist = point - last;
const chunk = dist / divf;
var i: usize = 0;
while (i < div) : (i += 1) {
const next = last + chunk;
last = next;
try this.nodes.append(Pos.init(next));
}
}
};
const Player = struct {
@ -202,6 +215,11 @@ const playerAnim = pac: {
break :pac animArr.slice();
};
fn posFromWorld(coord: world.Coordinate) Vec2f {
const tile_size = Vec2{ 8, 8 };
return util.vec2ToVec2f(coord.toVec2() * tile_size);
}
fn loadLevel(lvl: usize) !void {
fba.reset();
map.clear();
@ -236,24 +254,39 @@ fn loadLevel(lvl: usize) !void {
const coord0 = wire[0].coord.subC(levelc);
const coord1 = wire[1].coord.subC(levelc);
w4.tracef("---- Wire [%d, %d] (%d, %d), (%d, %d)", wireArr[0], wireArr[1], coord0.val[0], coord0.val[1], coord1.val[0], coord1.val[1]);
const p1 = util.vec2ToVec2f(coord0.toVec2() * tile_size + Vec2{ 4, 4 });
const p2 = util.vec2ToVec2f(coord1.toVec2() * tile_size + Vec2{ 4, 4 });
var w = try wires.addOne();
_ = try w.nodes.resize(0);
// const divisions = wire.divisions;
const divisions = 10;
var i: usize = 0;
while (i <= divisions) : (i += 1) {
try w.nodes.append(Pos.init(p1));
const divisions = 7;
var last_coord: world.Coordinate = undefined;
for (wireSlice) |world_wire| {
switch (world_wire) {
.Begin => |coord| {
last_coord = coord.subC(levelc);
w4.tracef("\t start (%d, %d)", last_coord.val[0], last_coord.val[1]);
try w.nodes.append(Pos.init(posFromWorld(last_coord) + Vec2f{ 4, 4 }));
},
.BeginPinned => |coord| {
last_coord = coord.subC(levelc);
w4.tracef("\t start [a] (%d, %d)", last_coord.val[0], last_coord.val[1]);
try w.nodes.append(Pos.init(posFromWorld(last_coord) + Vec2f{ 4, 4 }));
},
.Point => |offset| {
last_coord = last_coord.addOffset(offset);
w4.tracef("\t point (%d, %d) = last + (%d, %d)", last_coord.val[0], last_coord.val[1], offset[0], offset[1]);
try w.addInline(divisions, posFromWorld(last_coord) + Vec2f{ 4, 4 });
},
.PointPinned => |offset| {
last_coord = last_coord.addOffset(offset);
w4.tracef("\t point (%d, %d) = last + (%d, %d)", last_coord.val[0], last_coord.val[1], offset[0], offset[1]);
try w.addInline(divisions, posFromWorld(last_coord) + Vec2f{ 4, 4 });
},
.End => break,
}
}
w.begin().pos = p1;
w.end().pos = p2;
w.begin().pinned = wire[0].anchored;
w.end().pinned = wire[1].anchored;
w.straighten();
}
}
@ -313,27 +346,37 @@ fn moveLevel(direction: enum { L, R, U, D }) !void {
// Save wires back into database
const levelc = world.Coordinate.fromWorld(level.world_x, level.world_y);
while (wires.popOrNull()) |*w| {
var wire: [10]world.Wire = undefined;
// Are the ends anchored?
const aStart = w.begin().pinned;
const aEnd = w.begin().pinned;
const divby = @splat(2, @as(f32, 8));
const wstart = world.Coordinate.fromVec2f(w.begin().pos / divby).addC(levelc);
const offset = w.end().pos - w.begin().pos;
const end = world.Coordinate.fromVec2f(offset / divby).toOffset();
var wire: [3]world.Wire = undefined;
if (aStart) {
wire[0] = .{ .BeginPinned = wstart };
} else {
wire[0] = .{ .Begin = wstart };
w4.tracef("[moveLevel] new wire (%d,%d)", wstart.val[0], wstart.val[1]);
wire[0] = if (aStart) .{ .BeginPinned = wstart } else .{ .Begin = wstart };
var idx: usize = 1;
var last_pos = w.begin().pos;
for (w.nodes.constSlice()) |point, i| {
if (i == 0) continue;
const length = util.lengthf(point.pos - last_pos) / 8;
if (i % 8 == 0 or length > 6 or i == w.nodes.constSlice().len - 1) {
const diff = point.pos - last_pos;
const offset = world.Coordinate.fromVec2f(diff / divby).toOffset();
wire[idx] = if (point.pinned) .{ .PointPinned = offset } else .{ .Point = offset };
idx += 1;
last_pos = point.pos;
w4.tracef("\t offset (%d,%d)", offset[0], offset[1]);
}
}
if (aEnd) {
wire[1] = .{ .PointPinned = end };
} else {
wire[1] = .{ .Point = end };
}
wire[idx] = .End;
idx += 1;
wire[2] = .End;
db.addWire(&wire);
w4.tracef("\t finished, length %d", idx);
db.addWire(wire[0..idx]);
}
// TODO: Figure out the more principled way for checking boundaries

View File

@ -2,22 +2,6 @@
const std = @import("std");
/// The CircuitType of a tile modifies how the tile responds to
/// electricity
pub const CircuitType = enum(u4) {
None = 0,
Conduit = 1,
Plug = 2,
Switch_Off = 3,
Switch_On = 4,
Join = 5,
And = 6,
Xor = 7,
Outlet = 8,
Source = 9,
Socket = 10,
};
/// This lists the most important tiles so I don't have to keep rewriting things
pub const Tiles = struct {
// Switches
@ -50,9 +34,10 @@ pub const Tiles = struct {
pub const LogicAnd = 20;
pub const LogicNot = 21;
pub const LogicXor = 22;
pub const LogicDiode = 23;
pub fn is_logic(tile: u8) bool {
return tile >= 21 and tile <= 24;
return tile >= LogicAnd and tile <= LogicDiode;
}
pub const ConduitCross = 96;
@ -109,43 +94,80 @@ pub const Tiles = struct {
}, 2);
};
pub const SolidType = enum(u2) {
Empty = 0,
Solid = 1,
Oneway = 2,
};
/// The CircuitType of a tile modifies how the tile responds to
/// electricity
pub const CircuitType = enum(u5) {
None = 0,
Conduit = 1,
Plug = 2,
Switch_Off = 3,
Switch_On = 4,
Join = 5,
And = 6,
Xor = 7,
Outlet = 8,
Source = 9,
Socket = 10,
Diode = 11,
Conduit_Vertical = 12,
Conduit_Horizontal = 13,
pub fn canConnect(circuit: CircuitType, side: Direction) bool {
return switch (circuit) {
.None => false,
.Conduit_Vertical => (side != .East and side != .West),
.Conduit_Horizontal => (side != .North and side != .South),
else => true,
};
}
};
pub const TileData = union(enum) {
tile: u7,
flags: struct {
solid: bool,
solid: SolidType,
circuit: CircuitType,
},
pub fn getCircuit(data: TileData) ?CircuitType {
switch (data) {
.tile => |_| return null,
.flags => |flags| {
if (flags.circuit == .None) return null;
return flags.circuit;
},
if (data == .flags) {
return data.flags.circuit;
}
return null;
}
const isTileBitMask = 0b1000_0000;
const tileBitMask = 0b0111_1111;
const solidBitMask = 0b0000_0011;
const circuitBitMask = 0b0111_1100;
pub fn toByte(data: TileData) u8 {
switch (data) {
.tile => |int| return 0b1000_0000 | @intCast(u8, int),
.flags => |flags| {
const solid = @enumToInt(flags.solid);
const circuit = @enumToInt(flags.circuit);
return (@intCast(u7, @boolToInt(flags.solid))) | (@intCast(u7, circuit) << 1);
return ((@intCast(u8, solid) & solidBitMask) | ((@intCast(u8, circuit) << 2) & circuitBitMask));
},
}
}
pub fn fromByte(byte: u8) TileData {
const is_tile = (0b1000_0000 & byte) > 0;
const is_tile = (isTileBitMask & byte) > 0;
if (is_tile) {
const tile = @intCast(u7, (0b0111_1111 & byte));
const tile = @intCast(u7, (tileBitMask & byte));
return TileData{ .tile = tile };
} else {
const is_solid = (0b0000_0001 & byte) > 0;
const circuit = @intCast(u4, (0b0001_1110 & byte) >> 1);
const solid = @intCast(u2, (solidBitMask & byte));
const circuit = @intCast(u5, (circuitBitMask & byte) >> 2);
return TileData{ .flags = .{
.solid = is_solid,
.solid = @intToEnum(SolidType, solid),
.circuit = @intToEnum(CircuitType, circuit),
} };
}
@ -250,6 +272,33 @@ pub const Coordinate = struct {
}
};
pub const Direction = enum {
North,
West,
East,
South,
pub const each = [_]Direction{ .North, .West, .East, .South };
pub fn toOffset(dir: Direction) [2]i16 {
return switch (dir) {
.North => .{ 0, -1 },
.West => .{ -1, 0 },
.East => .{ 1, 0 },
.South => .{ 0, 1 },
};
}
pub fn getOpposite(dir: Direction) Direction {
return switch (dir) {
.North => .South,
.West => .East,
.East => .West,
.South => .North,
};
}
};
pub const Level = struct {
world_x: i8,
world_y: i8,
@ -257,12 +306,12 @@ pub const Level = struct {
size: u16,
tiles: ?[]TileData,
pub fn init(x: u8, y: u8, width: u16, buf: []TileData) Level {
pub fn init(x: i8, y: i8, width: u16, buf: []TileData) Level {
return Level{
.world_x = x,
.world_y = y,
.width = width,
.size = buf.len,
.size = @intCast(u16, buf.len),
.tiles = buf,
};
}
@ -310,13 +359,20 @@ pub const Level = struct {
pub fn getTile(level: Level, globalc: Coord) ?TileData {
const tiles = level.tiles orelse return null;
const worldc = globalc.toLevelTopLeft();
const se = worldc.add(.{ 20, 20 });
if (!globalc.within(worldc, se)) return null;
const x = globalc.val[0] - worldc.val[0];
const y = globalc.val[1] - worldc.val[1];
const w = @intCast(i16, level.width);
const w = @intCast(i32, level.width);
const i = @intCast(usize, x + y * w);
return tiles[i];
}
pub fn getCircuit(level: Level, globalc: Coord) ?CircuitType {
const tile = level.getTile(globalc) orelse return null;
return tile.getCircuit();
}
pub fn getJoin(level: Level, which: usize) ?Coordinate {
const tiles = level.tiles orelse return null;
var joinCount: usize = 0;
@ -548,11 +604,11 @@ pub const Wire = union(enum) {
try coord.write(writer);
},
.Point => |point| {
const byte = @bitCast(u8, @intCast(i8, point[0])) | @bitCast(u8, @intCast(i8, point[1])) << 4;
const byte = (@bitCast(u8, @intCast(i8, point[0])) & 0b0000_1111) | (@bitCast(u8, @intCast(i8, point[1])) & 0b1111_0000) << 4;
try writer.writeByte(byte);
},
.PointPinned => |point| {
const byte = @bitCast(u8, @intCast(i8, point[0])) | @bitCast(u8, @intCast(i8, point[1])) << 4;
const byte = (@bitCast(u8, @intCast(i8, point[0])) & 0b0000_1111) | (@bitCast(u8, @intCast(i8, point[1])) & 0b1111_0000) << 4;
try writer.writeByte(byte);
},
.End => {},
@ -817,25 +873,30 @@ pub const Database = struct {
if (visited[i]) return db.circuit_info[i].energized;
visited[i] = true;
const node = db.circuit_info[i];
const w4 = @import("wasm4.zig");
switch (node.kind) {
.And => |And| {
w4.tracef("[updateCircuitFragment] %d And %d %d", i, And[0], And[1]);
const input1 = db.updateCircuitFragment(And[0], visited);
const input2 = db.updateCircuitFragment(And[1], visited);
db.circuit_info[i].energized = (input1 and input2);
},
.Xor => |Xor| {
w4.tracef("[updateCircuitFragment] %d Xor", i);
const input1 = db.updateCircuitFragment(Xor[0], visited);
const input2 = db.updateCircuitFragment(Xor[1], visited);
db.circuit_info[i].energized = (input1 and !input2) or (input2 and !input1);
},
.Source => db.circuit_info[i].energized = true,
.Conduit => |Conduit| {
w4.tracef("[updateCircuitFragment] %d Conduit", i);
const input1 = db.updateCircuitFragment(Conduit[0], visited);
const input2 = db.updateCircuitFragment(Conduit[1], visited);
db.circuit_info[i].energized = (input1 or input2);
},
// TODO: Sockets may come before the plug they are connected to
.Socket => |socket_opt| {
w4.tracef("[updateCircuitFragment] %d Socket", i);
if (socket_opt) |input| {
db.circuit_info[i].energized = db.updateCircuitFragment(input, visited);
} else {
@ -843,12 +904,15 @@ pub const Database = struct {
}
},
.Plug => |Plug| {
w4.tracef("[updateCircuitFragment] %d Plug", i);
db.circuit_info[i].energized = db.updateCircuitFragment(Plug, visited);
},
.Switch => |_Switch| {
w4.tracef("[updateCircuitFragment] %d Switch %d", i, _Switch.source);
db.circuit_info[i].energized = db.updateCircuitFragment(_Switch.source, visited);
},
.SwitchOutlet => |_Switch| {
w4.tracef("[updateCircuitFragment] %d Switch Outlet", i);
const is_energized = db.updateCircuitFragment(_Switch.source, visited);
const _switch = db.circuit_info[_Switch.source].kind.Switch;
const _outlet = db.circuit_info[i].kind.SwitchOutlet;
@ -858,12 +922,15 @@ pub const Database = struct {
db.circuit_info[i].energized = _outlet.which == _switch.state;
},
.Join => |Join| {
w4.tracef("[updateCircuitFragment] %d Join", i);
db.circuit_info[i].energized = db.updateCircuitFragment(Join, visited);
},
.Outlet => |Outlet| {
w4.tracef("[updateCircuitFragment] %d Outlet", i);
db.circuit_info[i].energized = db.updateCircuitFragment(Outlet, visited);
},
}
w4.tracef("[updateCircuitFragment] %d end", i);
return db.circuit_info[i].energized;
}
@ -872,6 +939,8 @@ pub const Database = struct {
defer alloc.free(visited);
std.mem.set(bool, visited, false);
var i: usize = db.circuit_info.len - 1;
const w4 = @import("wasm4.zig");
w4.tracef("[updateCircuit] circuit info len %d", db.circuit_info.len);
while (i > 0) : (i -|= 1) {
_ = db.updateCircuitFragment(i, visited);
if (i == 0) break;

View File

@ -3,6 +3,9 @@ const std = @import("std");
const LDtk = @import("../deps/zig-ldtk/src/LDtk.zig");
const world = @import("../src/world.zig");
const Coord = world.Coordinate;
const Dir = world.Direction;
const KB = 1024;
const MB = 1024 * KB;
@ -71,6 +74,16 @@ fn make(step: *std.build.Step) !void {
.wires = &wires,
});
// for (parsed_level.tiles.?) |tile, i| {
// if (tile == .tile) {
// std.log.warn("{:0>2}: {}", .{ i, tile.tile });
// } else if (tile == .flags) {
// std.log.warn("{:0>2}: {s} {s}", .{ i, @tagName(tile.flags.solid), @tagName(tile.flags.circuit) });
// } else {
// std.log.warn("{:0>2}: {}", .{ i, tile });
// }
// }
try levels.append(parsed_level);
}
defer for (levels.items) |level| {
@ -152,8 +165,8 @@ fn parseLevel(opt: struct {
const layers = level.layerInstances orelse return error.NoLayers;
const world_x: i8 = @intCast(i8, @divExact(level.worldX, (ldtk.worldGridWidth orelse 160)));
const world_y: i8 = @intCast(i8, @divExact(level.worldY, (ldtk.worldGridHeight orelse 160)));
const world_x: i8 = @intCast(i8, @divFloor(level.worldX, (ldtk.worldGridWidth orelse 160)));
const world_y: i8 = @intCast(i8, @divFloor(level.worldY, (ldtk.worldGridHeight orelse 160)));
var circuit_layer: ?LDtk.LayerInstance = null;
var collision_layer: ?LDtk.LayerInstance = null;
@ -178,11 +191,11 @@ fn parseLevel(opt: struct {
kind_opt = .Trapdoor;
}
const levelc = world.Coordinate.fromWorld(world_x, world_y);
const levelc = Coord.fromWorld(world_x, world_y);
// Parsing code for wire entities. They're a little more complex
// than the rest
if (kind_opt) |kind| {
const entc = world.Coordinate.init(.{
const entc = Coord.init(.{
@intCast(i16, entity.__grid[0]),
@intCast(i16, entity.__grid[1]),
});
@ -192,28 +205,28 @@ fn parseLevel(opt: struct {
if (is_wire) {
var anchor1 = false;
var anchor2 = false;
const p1_c = world.Coordinate.init(.{
@intCast(i16, entity.__grid[0]),
@intCast(i16, entity.__grid[1]),
});
var p2_c = world.Coordinate.init(.{
const p1_c = Coord.init(.{
@intCast(i16, entity.__grid[0]),
@intCast(i16, entity.__grid[1]),
});
std.log.warn("[parseLevel:wire] {}", .{ p1_c });
var points: []Coord = undefined;
for (entity.fieldInstances) |field| {
if (std.mem.eql(u8, field.__identifier, "Anchor")) {
const anchors = field.__value.Array.items;
anchor1 = anchors[0].Bool;
anchor2 = anchors[1].Bool;
} else if (std.mem.eql(u8, field.__identifier, "Point")) {
const end = field.__value.Array.items.len - 1;
const endpoint = field.__value.Array.items[end];
const x = endpoint.Object.get("cx").?;
const y = endpoint.Object.get("cy").?;
p2_c.val = .{
@intCast(i16, x.Integer),
@intCast(i16, y.Integer),
};
points = try allocator.alloc(Coord, field.__value.Array.items.len);
for (field.__value.Array.items) |point, i| {
const x = point.Object.get("cx").?;
const y = point.Object.get("cy").?;
std.log.warn("\t{} {}", .{ x.Integer, y.Integer });
points[i] = Coord.init(.{
@intCast(i16, x.Integer),
@intCast(i16, y.Integer),
});
}
}
}
@ -223,11 +236,21 @@ fn parseLevel(opt: struct {
try wires.append(.{ .Begin = p1_c.addC(levelc) });
}
if (anchor2) {
try wires.append(.{ .PointPinned = p2_c.subC(p1_c).toOffset() });
} else {
try wires.append(.{ .Point = p2_c.subC(p1_c).toOffset() });
std.log.warn("\tConverting to wire nodes", .{});
var last_point = p1_c;
for (points) |point, i| {
const offset = point.subC(last_point).toOffset();
std.log.warn("\toffset: {} {}", .{ offset[0], offset[1] });
last_point = point;
if (i == points.len - 1) {
if (anchor2) {
try wires.append(.{ .PointPinned = offset });
continue;
}
}
try wires.append(.{ .Point = offset });
}
try wires.append(.End);
}
}
@ -272,63 +295,77 @@ fn parseLevel(opt: struct {
const tiles = parsed_level.tiles.?;
for (tiles) |_, i| {
tiles[i] = world.TileData{ .tile = 0 };
}
// Add unchanged tile data
for (collision.autoLayerTiles) |autotile| {
const x = @divExact(autotile.px[0], collision.__gridSize);
const y = @divExact(autotile.px[1], collision.__gridSize);
const i = @intCast(usize, x + y * width);
const sx = @divExact(autotile.src[0], collision.__gridSize);
const sy = @divExact(autotile.src[1], collision.__gridSize);
const t = sx + sy * 16;
const t = autotile.t;
tiles[i] = world.TileData{ .tile = @intCast(u7, t) };
}
// Add circuit tiles
for (circuit.intGridCsv) |cir64, i| {
const cir = @intCast(u4, cir64);
const cir = @intToEnum(world.CircuitType, @intCast(u5, cir64));
const col = collision.intGridCsv[i];
if (col == 0 or col == 1) {
tiles[i] = world.TileData{ .flags = .{
.solid = col == 1,
.circuit = @intToEnum(world.CircuitType, cir),
} };
}
if (cir != .None and col == 2) return error.DebrisAndCircuitOverlapped;
if (cir == .None) continue;
const solid: world.SolidType = switch (col) {
0 => .Empty,
1 => .Solid,
3 => .Oneway,
else => continue,
};
tiles[i] = world.TileData{ .flags = .{
.solid = solid,
.circuit = cir,
} };
}
return parsed_level;
}
pub fn buildCircuit(alloc: std.mem.Allocator, levels: []world.Level) !std.ArrayList(world.CircuitNode) {
const Coord = world.Coordinate;
const SearchItem = struct {
coord: Coord,
last_coord: ?Coord = null,
last_node: world.NodeID,
fn next(current: @This(), current_node: world.NodeID, offset: [2]i16) @This() {
return @This(){
.coord = current.coord.add(offset),
.last_coord = current.coord,
.last_node = current_node,
};
}
};
const Queue = std.TailQueue(SearchItem);
const Node = Queue.Node;
var nodes = std.ArrayList(world.CircuitNode).init(alloc);
var node_input_dir = std.ArrayList(Dir).init(alloc);
defer node_input_dir.deinit();
var source_node = std.ArrayList(world.NodeID).init(alloc);
defer source_node.deinit();
var sources = Queue{};
var sockets = Queue{};
var level_hashmap = std.AutoHashMap(u16, world.Level).init(alloc);
defer level_hashmap.deinit();
for (levels) |level| {
const id: u16 = @bitCast(u8, level.world_x) | @intCast(u16, @bitCast(u8, level.world_y)) << 8;
// So we can quickly find levels
try level_hashmap.put(id, level);
// Use a global coordinate system for our algorithm
const global_x = @intCast(i16, level.world_x) * 20;
const global_y = @intCast(i16, level.world_y) * 20;
for (level.tiles orelse continue) |tileData, i| {
const x = global_x + @intCast(i16, @mod(i, level.width));
const y = global_y + @intCast(i16, @divTrunc(i, level.width));
const coordinate = try alloc.create(Node);
coordinate.* = .{ .data = .{
const search_item = try alloc.create(Node);
search_item.* = .{ .data = .{
.last_node = @intCast(world.NodeID, nodes.items.len),
.coord = Coord.init(.{ x, y }),
} };
@ -340,12 +377,11 @@ pub fn buildCircuit(alloc: std.mem.Allocator, levels: []world.Level) !std.ArrayL
switch (flags.circuit) {
.Source => {
try nodes.append(.{ .kind = .Source, .coord = Coord.init(.{ x, y }) });
sources.append(coordinate);
sources.append(search_item);
},
.Socket => {
// try nodes.append(.{ .kind = .{ .Plug = null } });
coordinate.data.last_node = std.math.maxInt(world.NodeID);
sockets.append(coordinate);
search_item.data.last_node = std.math.maxInt(world.NodeID);
sockets.append(search_item);
},
else => {
// Do nothing
@ -358,8 +394,6 @@ pub fn buildCircuit(alloc: std.mem.Allocator, levels: []world.Level) !std.ArrayL
var visited = std.AutoHashMap(Coord, void).init(alloc);
defer visited.deinit();
var multi_input = std.AutoHashMap(Coord, usize).init(alloc);
defer multi_input.deinit();
var bfs_queue = Queue{};
@ -367,42 +401,48 @@ pub fn buildCircuit(alloc: std.mem.Allocator, levels: []world.Level) !std.ArrayL
while (run < 2) : (run += 1) {
if (run == 0) bfs_queue.concatByMoving(&sources);
if (run == 1) bfs_queue.concatByMoving(&sockets);
// bfs_queue.concatByMoving(&outlets);
while (bfs_queue.popFirst()) |node| {
// Make sure we clean up the node's memory
defer alloc.destroy(node);
const coord = node.data.coord;
if (visited.contains(coord)) continue;
try visited.put(coord, .{});
// TODO remove magic numbers
try visited.put(coord, {});
const worldc = coord.toWorld();
const id: u16 = @bitCast(u8, worldc[0]) | @intCast(u16, @bitCast(u8, worldc[1])) << 8;
// const level_opt: ?world.Level = level_hashmap.get(.{ world_x, world_y });
if (level_hashmap.getPtr(id) != null) {
const level = level_hashmap.getPtr(id);
// const level = getLevel(levels, worldc[0], worldc[1]);
if (getLevel(levels, worldc[0], worldc[1])) |level| {
const last_node = node.data.last_node;
var next_node = last_node;
const tile = level.?.getTile(coord).?;
const tile = level.getTile(coord) orelse continue;
if (tile != .flags) continue;
const flags = tile.flags;
const dir = if (last_node != std.math.maxInt(world.NodeID))
getInputDirection(coord, nodes.items[last_node].coord)
else
.South;
switch (flags.circuit) {
.Source => {}, // Do nothing, but add everything around the source
.Conduit => {
// Collects from two other nodes. Intersections will need to be stored so when
// we find out we have to outputs, we can add the conduit and possible rewrite
// previous nodes to point to the conduit
// TODO
},
.Conduit_Horizontal => {},
.Conduit_Vertical => {},
.Source => {}, // Do nothing, but add everything around the source
.Socket => {
next_node = @intCast(world.NodeID, nodes.items.len);
try nodes.append(.{
.kind = .{ .Socket = null },
.coord = coord,
});
try node_input_dir.append(dir);
try source_node.append(last_node);
},
.Plug => {
// Plugs by their nature end a conduit path, so don't add
@ -411,6 +451,8 @@ pub fn buildCircuit(alloc: std.mem.Allocator, levels: []world.Level) !std.ArrayL
.kind = .{ .Plug = last_node },
.coord = coord,
});
try node_input_dir.append(dir);
try source_node.append(last_node);
continue;
},
.Outlet => {
@ -419,265 +461,95 @@ pub fn buildCircuit(alloc: std.mem.Allocator, levels: []world.Level) !std.ArrayL
.kind = .{ .Outlet = last_node },
.coord = coord,
});
try node_input_dir.append(dir);
try source_node.append(last_node);
},
.Switch_Off, .Switch_On => {
// Identify input side
const last_coord = node.data.last_coord.?;
const Dir = enum { North, West, East, South };
const input_dir: Dir = dir: {
if (last_coord.eq(coord.add(.{ 0, -1 }))) {
break :dir .North;
} else if (last_coord.eq(coord.add(.{ -1, 0 }))) {
break :dir .West;
} else if (last_coord.eq(coord.add(.{ 1, 0 }))) {
break :dir .East;
} else {
break :dir .South;
}
};
// Find outlets
const north_opt = level.?.getTile(coord.add(.{ 0, -1 })).?.getCircuit();
const west_opt = level.?.getTile(coord.add(.{ -1, 0 })).?.getCircuit();
const east_opt = level.?.getTile(coord.add(.{ 1, 0 })).?.getCircuit();
const south_opt = level.?.getTile(coord.add(.{ 0, 1 })).?.getCircuit();
const north = (north_opt orelse world.CircuitType.None) != .None;
const west = (west_opt orelse world.CircuitType.None) != .None;
const east = (east_opt orelse world.CircuitType.None) != .None;
const south = (south_opt orelse world.CircuitType.None) != .None;
// We don't have four way switches, don't allow them
std.debug.assert(west != true or east != true);
// We only have vertically oriented switches ATM
std.debug.assert(north == true and south == true);
// Determine initial state of switch
const state: u8 = state: {
// Vertical switch
if (!west and !east) {
if (flags.circuit == .Switch_Off) break :state 0;
break :state 1;
}
if (east and !west) {
if (flags.circuit == .Switch_Off) break :state 0;
break :state 1;
}
if (west and !east) {
if (flags.circuit == .Switch_Off) break :state 0;
break :state 1;
}
return error.ImpossibleSwitchState;
};
.Switch_Off => {
// Add switch
next_node = @intCast(world.NodeID, nodes.items.len);
try nodes.append(.{
.kind = .{ .Switch = .{
.source = last_node,
.state = state,
.state = 0,
} },
.coord = coord,
});
// Add switch outlets
if (input_dir != .West and west) {
const out_node = @intCast(world.NodeID, nodes.items.len);
const new_coord = coord.add(.{-1, 0});
try nodes.append(.{
.kind = .{ .SwitchOutlet = .{
.source = next_node,
.which = 0,
} },
.coord = new_coord,
});
const right = try alloc.create(Node);
right.* = Node{ .data = .{
.last_node = out_node,
.coord = new_coord,
.last_coord = coord,
} };
bfs_queue.append(right);
}
try node_input_dir.append(dir);
try source_node.append(last_node);
if (input_dir != .East and east) {
const out_node = @intCast(world.NodeID, nodes.items.len);
const new_coord = coord.add(.{1, 0});
try nodes.append(.{
.kind = .{ .SwitchOutlet = .{
.source = next_node,
.which = 0,
} },
.coord = new_coord,
});
const left = try alloc.create(Node);
left.* = Node{ .data = .{
.last_node = out_node,
.coord = new_coord,
.last_coord = coord,
} };
bfs_queue.append(left);
}
// Loop over sides, check if they are connected, and add a
// switch outlet if so
for (Dir.each) |side| {
const next_coord = coord.add(side.toOffset());
if (level.getCircuit(next_coord)) |circuit| {
if (circuit.canConnect(side.getOpposite()) and side != dir) {
const outlet = @intCast(world.NodeID, nodes.items.len);
const which = if (side == .North or side == .South) @as(u8, 1) else @as(u8, 0);
try nodes.append(.{
.kind = .{ .SwitchOutlet = .{
.source = next_node,
.which = which,
} },
.coord = next_coord,
});
try node_input_dir.append(side);
try source_node.append(next_node);
if (input_dir != .South and south) {
const out_node = @intCast(world.NodeID, nodes.items.len);
const new_coord = coord.add(.{0, 1});
try nodes.append(.{
.kind = .{ .SwitchOutlet = .{
.source = next_node,
.which = 1,
} },
.coord = new_coord,
});
const down = try alloc.create(Node);
down.* = Node{ .data = .{
.last_node = out_node,
.coord = new_coord,
.last_coord = coord,
} };
bfs_queue.append(down);
const outlet_search = try alloc.create(Node);
outlet_search.* = .{ .data = node.data.next(outlet, side.toOffset()) };
bfs_queue.append(outlet_search);
}
}
}
if (input_dir != .North and north) {
const out_node = @intCast(world.NodeID, nodes.items.len);
const new_coord = coord.add(.{0, -1});
try nodes.append(.{
.kind = .{ .SwitchOutlet = .{
.source = next_node,
.which = 1,
} },
.coord = new_coord,
});
const up = try alloc.create(Node);
up.* = Node{ .data = .{
.last_node = out_node,
.coord = new_coord,
.last_coord = coord,
} };
bfs_queue.append(up);
}
continue;
},
.Switch_On => {
// Add switch
next_node = @intCast(world.NodeID, nodes.items.len);
try nodes.append(.{
.kind = .{ .Switch = .{
.source = last_node,
.state = 1,
} },
.coord = coord,
});
try node_input_dir.append(dir);
try source_node.append(last_node);
},
.Join => {
const last_coord = node.data.last_coord.?;
if (last_coord.toLevelTopLeft().eq(coord.toLevelTopLeft())) {
std.log.warn("Join first side", .{});
} else {
std.log.warn("Join second side", .{});
next_node = @intCast(world.NodeID, nodes.items.len);
std.log.warn("Join second side", .{});
try nodes.append(.{
.kind = .{ .Join = last_node },
.coord = coord,
});
try node_input_dir.append(dir);
try source_node.append(last_node);
}
},
.And => {
// TODO: verify And gate is properly connected. A source node
// should never feed directly into an And gate output. Inputs
// should be to the left and right.
const last_coord = node.data.last_coord.?;
const Side = enum { O, L, R };
const side: Side =
if (last_coord.val[0] == coord.val[0] - 1)
Side.L
else if (last_coord.val[0] == coord.val[0] + 1)
Side.R
else
Side.O;
// std.log.warn("{any}: {}", .{ coord, side });
if (multi_input.get(coord)) |a| {
switch (side) {
.L => {
// std.log.warn("Filling left", .{});
nodes.items[a].kind.And[0] = last_node;
},
.R => {
// std.log.warn("Filling right", .{});
nodes.items[a].kind.And[1] = last_node;
},
else => {}, // reverse connection
}
} else {
_ = visited.remove(coord);
if (side == .O) {
// TODO: reverse the path, since the search path
// may have come from a plug
return error.OutputToSource;
} else if (side == .L) {
next_node = @intCast(world.NodeID, nodes.items.len);
try nodes.append(.{
.kind = .{ .And = .{ last_node, std.math.maxInt(world.NodeID) } },
.coord = coord,
});
} else if (side == .R) {
next_node = @intCast(world.NodeID, nodes.items.len);
try nodes.append(.{
.kind = .{ .And = .{ std.math.maxInt(world.NodeID), last_node } },
.coord = coord,
});
}
try multi_input.put(coord, next_node);
const up = try alloc.create(Node);
up.* = Node{ .data = .{
.last_node = next_node,
.coord = coord.add(.{ 0, -1 }),
.last_coord = coord,
} };
bfs_queue.append(up);
continue;
}
next_node = @intCast(world.NodeID, nodes.items.len);
try nodes.append(.{
.kind = .{ .And = .{ std.math.maxInt(world.NodeID), std.math.maxInt(world.NodeID) } },
.coord = coord,
});
try node_input_dir.append(dir);
try source_node.append(last_node);
},
.Xor => {
std.log.warn("XOR XOR XOR",.{});
// TODO: verify Xor gate is properly connected
const last_coord = node.data.last_coord.?;
const Side = enum { O, L, R };
const side: Side =
if (last_coord.val[0] == coord.val[0] - 1)
Side.L
else if (last_coord.val[0] == coord.val[0] + 1)
Side.R
else
Side.O;
// std.log.warn("{any}: {}", .{ coord, side });
if (multi_input.get(coord)) |a| {
switch (side) {
.L => {
// std.log.warn("Filling left", .{});
nodes.items[a].kind.Xor[0] = last_node;
},
.R => {
// std.log.warn("Filling right", .{});
nodes.items[a].kind.Xor[1] = last_node;
},
else => {}, // reverse connection
}
} else {
_ = visited.remove(coord);
if (side == .O) {
// TODO: reverse the path, since the search path
// may have come from a plug
return error.OutputToSource;
} else if (side == .L) {
next_node = @intCast(world.NodeID, nodes.items.len);
try nodes.append(.{
.kind = .{ .Xor = .{ last_node, std.math.maxInt(world.NodeID) } },
.coord = coord,
});
} else if (side == .R) {
next_node = @intCast(world.NodeID, nodes.items.len);
try nodes.append(.{
.kind = .{ .Xor = .{ std.math.maxInt(world.NodeID), last_node } },
.coord = coord,
});
}
try multi_input.put(coord, next_node);
const up = try alloc.create(Node);
up.* = Node{ .data = .{
.last_node = next_node,
.coord = coord.add(.{ 0, -1 }),
.last_coord = coord,
} };
bfs_queue.append(up);
continue;
}
next_node = @intCast(world.NodeID, nodes.items.len);
try nodes.append(.{
.kind = .{ .Xor = .{ std.math.maxInt(world.NodeID), std.math.maxInt(world.NodeID) } },
.coord = coord,
});
try node_input_dir.append(dir);
try source_node.append(last_node);
},
.Diode => {
// TODO
},
.None => continue,
}
@ -712,10 +584,166 @@ pub fn buildCircuit(alloc: std.mem.Allocator, levels: []world.Level) !std.ArrayL
bfs_queue.append(left);
bfs_queue.append(down);
bfs_queue.append(up);
}
}
}
var i: usize = 0;
while (i < nodes.items.len) : (i += 1) {
switch (nodes.items[i].kind) {
.Source => {},
.And => {
const neighbors = try findNeighbors(alloc, levels, nodes.items, i);
defer neighbors.deinit();
std.log.warn("[{}]: Found {} neighbors", .{ i, neighbors.items.len });
for (neighbors.items) |neighbor, a| {
std.log.warn("\tNeighbor {}: [{}] {}", .{ a, neighbor.id, neighbor.side });
if (neighbor.side == .West) nodes.items[i].kind.And[0] = neighbor.id;
if (neighbor.side == .East) nodes.items[i].kind.And[1] = neighbor.id;
}
},
.Xor => {},
.Conduit => {},
.Plug => {},
.Socket => {},
.Switch => {},
.SwitchOutlet => {},
.Join => {},
.Outlet => {},
}
}
return nodes;
}
const Neighbor = struct {
side: Dir,
id: world.NodeID,
};
fn findNeighbors(
alloc: std.mem.Allocator,
levels: []world.Level,
nodes: []world.CircuitNode,
index: usize,
) !std.ArrayList(Neighbor) {
var visited = std.AutoHashMap(Coord, void).init(alloc);
defer visited.deinit();
const SearchItem = struct {
side: Dir,
coord: Coord,
fn init(side: Dir, coord: Coord) @This() {
const init_item = @This(){ .side = side, .coord = coord };
const item = switch (side) {
.North => init_item.add(.{ 0, -1 }),
.West => init_item.add(.{ -1, 0 }),
.East => init_item.add(.{ 1, 0 }),
.South => init_item.add(.{ 0, 1 }),
};
return item;
}
fn add(item: @This(), val: [2]i16) @This() {
var new_item = @This(){
.side = item.side,
.coord = item.coord.add(val),
};
return new_item;
}
};
const Queue = std.TailQueue(SearchItem);
const Node = Queue.Node;
var bfs_queue = Queue{};
var neighbors = std.ArrayList(Neighbor).init(alloc);
{
const coord = nodes[index].coord;
try visited.put(coord, {});
const north = try alloc.create(Node);
const west = try alloc.create(Node);
const east = try alloc.create(Node);
const south = try alloc.create(Node);
north.* = Node{ .data = SearchItem.init(.South, coord) };
west.* = Node{ .data = SearchItem.init(.West, coord) };
east.* = Node{ .data = SearchItem.init(.East, coord) };
south.* = Node{ .data = SearchItem.init(.North, coord) };
bfs_queue.append(north);
bfs_queue.append(west);
bfs_queue.append(east);
bfs_queue.append(south);
}
while (bfs_queue.popFirst()) |node| {
// Make sure we clean up the node's memory
defer alloc.destroy(node);
const coord = node.data.coord;
const item = node.data;
if (visited.contains(coord)) continue;
try visited.put(coord, {});
const worldc = coord.toWorld();
const level = getLevel(levels, worldc[0], worldc[1]) orelse continue;
const tile = level.getTile(coord) orelse continue;
_ = tile.getCircuit() orelse continue;
if (getNode(nodes, coord)) |i| {
try neighbors.append(.{
.id = i,
.side = item.side,
});
// Stop processing at circuit nodes
continue;
}
const right = try alloc.create(Node);
const left = try alloc.create(Node);
const down = try alloc.create(Node);
const up = try alloc.create(Node);
right.* = Node{ .data = item.add(.{ 1, 0 }) };
left.* = Node{ .data = item.add(.{ -1, 0 }) };
down.* = Node{ .data = item.add(.{ 0, 1 }) };
up.* = Node{ .data = item.add(.{ 0, -1 }) };
bfs_queue.append(right);
bfs_queue.append(left);
bfs_queue.append(down);
bfs_queue.append(up);
}
return neighbors;
}
fn getInputDirection(coord: Coord, last_coord: Coord) Dir {
if (last_coord.eq(coord.add(.{ 0, -1 }))) {
return .North;
} else if (last_coord.eq(coord.add(.{ -1, 0 }))) {
return .West;
} else if (last_coord.eq(coord.add(.{ 1, 0 }))) {
return .East;
} else {
return .South;
}
}
fn getLevel(levels: []world.Level, x: i8, y: i8) ?world.Level {
for (levels) |level| {
if (level.world_x == x and level.world_y == y) return level;
}
return null;
}
fn getNode(nodes: []world.CircuitNode, coord: Coord) ?world.NodeID {
for (nodes) |node, i| {
if (node.coord.eq(coord)) return @intCast(world.NodeID, i);
}
return null;
}

18
wapm.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "desttinghim/wired"
version = "0.2.0"
description = "A puzzle platformer game with wire physics."
readme = "README.md"
repository = "https://github.com/desttinghim/wired"
license = "ISC"
[[module]]
name = "wired"
source = "wired.wasm"
abi = "wasm4"
interfaces = { wasm4 = "0.0.1" }
[[command]]
runner = "wasm4@0.0.1"
name = "play"
module = "wired"

BIN
wired.wasm Normal file

Binary file not shown.