Compare commits
10 Commits
9ef283d3ac
...
8fe3e42752
Author | SHA1 | Date |
---|---|---|
Louis Pearson | 8fe3e42752 | |
Louis Pearson | e45f233fa9 | |
Louis Pearson | 8348ff7c4c | |
Louis Pearson | e2fb6a88ae | |
Louis Pearson | 8a18d321b3 | |
Louis Pearson | 05328945e6 | |
Louis Pearson | aa539339b9 | |
Louis Pearson | 4ccc7e9a2e | |
Louis Pearson | e36cffb8c6 | |
Louis Pearson | df2a3a7db5 |
|
@ -2,3 +2,5 @@
|
|||
/zig-out
|
||||
/src/zig-cache/
|
||||
/bundle/
|
||||
|
||||
wapm_packages
|
|
@ -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
|
@ -1 +1 @@
|
|||
Subproject commit e93618602d79d1e998a10c0ce1b4473459b19ff4
|
||||
Subproject commit d1ace5b48eaf1caa3bce44add0d14e4fb2f10061
|
22
flake.lock
22
flake.lock
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
95
src/game.zig
95
src/game.zig
|
@ -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
|
||||
|
|
139
src/world.zig
139
src/world.zig
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"
|
Binary file not shown.
Loading…
Reference in New Issue