2022-08-03 16:23:59 -06:00
|
|
|
//! Uses zig-ldtk to convert a ldtk file into a binary format for wired
|
|
|
|
const std = @import("std");
|
|
|
|
const LDtk = @import("../deps/zig-ldtk/src/LDtk.zig");
|
2022-08-03 22:06:17 -06:00
|
|
|
const world = @import("../src/world.zig");
|
2022-08-03 16:23:59 -06:00
|
|
|
|
|
|
|
const KB = 1024;
|
|
|
|
const MB = 1024 * KB;
|
|
|
|
|
|
|
|
const LDtkImport = @This();
|
|
|
|
|
|
|
|
step: std.build.Step,
|
|
|
|
builder: *std.build.Builder,
|
|
|
|
source_path: std.build.FileSource,
|
|
|
|
output_name: []const u8,
|
2022-08-04 01:59:32 -06:00
|
|
|
world_data: std.build.GeneratedFile,
|
2022-08-03 16:23:59 -06:00
|
|
|
|
|
|
|
pub fn create(b: *std.build.Builder, opt: struct {
|
|
|
|
source_path: std.build.FileSource,
|
|
|
|
output_name: []const u8,
|
|
|
|
}) *@This() {
|
|
|
|
var result = b.allocator.create(LDtkImport) catch @panic("memory");
|
|
|
|
result.* = LDtkImport{
|
|
|
|
.step = std.build.Step.init(.custom, "convert and embed a ldtk map file", b.allocator, make),
|
|
|
|
.builder = b,
|
|
|
|
.source_path = opt.source_path,
|
|
|
|
.output_name = opt.output_name,
|
2022-08-04 01:59:32 -06:00
|
|
|
.world_data = undefined,
|
2022-08-03 16:23:59 -06:00
|
|
|
};
|
2022-08-04 01:59:32 -06:00
|
|
|
result.*.world_data = std.build.GeneratedFile{ .step = &result.*.step };
|
2022-08-03 16:23:59 -06:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn make(step: *std.build.Step) !void {
|
|
|
|
const this = @fieldParentPtr(LDtkImport, "step", step);
|
|
|
|
|
|
|
|
const allocator = this.builder.allocator;
|
|
|
|
const cwd = std.fs.cwd();
|
|
|
|
|
|
|
|
// Get path to source and output
|
|
|
|
const source_src = this.source_path.getPath(this.builder);
|
|
|
|
const output = this.builder.getInstallPath(.lib, this.output_name);
|
|
|
|
|
|
|
|
// Open ldtk file and read all of it into `source`
|
|
|
|
const source_file = try cwd.openFile(source_src, .{});
|
|
|
|
defer source_file.close();
|
|
|
|
const source = try source_file.readToEndAlloc(allocator, 10 * MB);
|
|
|
|
defer allocator.free(source);
|
|
|
|
|
2022-08-04 17:18:34 -06:00
|
|
|
var ldtk_parser = try LDtk.parse(allocator, source);
|
|
|
|
defer ldtk_parser.deinit();
|
|
|
|
|
|
|
|
const ldtk = ldtk_parser.root;
|
2022-08-03 16:23:59 -06:00
|
|
|
|
2022-08-06 01:15:38 -06:00
|
|
|
// Store levels
|
|
|
|
var levels = std.ArrayList(world.Level).init(allocator);
|
|
|
|
defer levels.deinit();
|
2022-08-04 17:18:34 -06:00
|
|
|
|
2022-08-08 18:00:25 -06:00
|
|
|
var entity_array = std.ArrayList(world.Entity).init(allocator);
|
|
|
|
defer entity_array.deinit();
|
2022-08-03 23:50:50 -06:00
|
|
|
|
2022-08-08 21:56:26 -06:00
|
|
|
var wires = std.ArrayList(world.Wire).init(allocator);
|
|
|
|
defer wires.deinit();
|
|
|
|
|
2022-08-08 18:00:25 -06:00
|
|
|
for (ldtk.levels) |level| {
|
|
|
|
std.log.warn("Level: {}", .{levels.items.len});
|
2022-08-05 21:55:07 -06:00
|
|
|
const parsed_level = try parseLevel(.{
|
|
|
|
.allocator = allocator,
|
|
|
|
.ldtk = ldtk,
|
|
|
|
.level = level,
|
|
|
|
.entity_array = &entity_array,
|
2022-08-08 21:56:26 -06:00
|
|
|
.wires = &wires,
|
2022-08-05 21:55:07 -06:00
|
|
|
});
|
2022-08-03 23:50:50 -06:00
|
|
|
|
2022-08-06 01:15:38 -06:00
|
|
|
try levels.append(parsed_level);
|
|
|
|
}
|
|
|
|
defer for (levels.items) |level| {
|
|
|
|
allocator.free(level.tiles.?);
|
|
|
|
};
|
|
|
|
|
2022-08-07 00:16:00 -06:00
|
|
|
var circuit = try buildCircuit(allocator, levels.items);
|
|
|
|
defer circuit.deinit();
|
|
|
|
// TODO
|
|
|
|
for (circuit.items) |node, i| {
|
2022-08-07 14:40:08 -06:00
|
|
|
std.log.warn("{:0>2}: {}", .{ i, node });
|
2022-08-07 00:16:00 -06:00
|
|
|
}
|
|
|
|
|
2022-08-08 21:56:26 -06:00
|
|
|
for (wires.items) |node, i| {
|
|
|
|
std.log.warn("Wire {:0>2}: {any}", .{ i, node });
|
|
|
|
}
|
|
|
|
|
2022-08-06 01:15:38 -06:00
|
|
|
// Calculate the offset of each level and store it in the headers.
|
|
|
|
// Offset is relative to the beginning of level.data
|
|
|
|
var level_headers = std.ArrayList(world.LevelHeader).init(allocator);
|
|
|
|
defer level_headers.deinit();
|
|
|
|
|
|
|
|
for (levels.items) |level, i| {
|
|
|
|
if (level_headers.items.len == 0) {
|
|
|
|
try level_headers.append(.{
|
|
|
|
.x = level.world_x,
|
|
|
|
.y = level.world_y,
|
|
|
|
.offset = 0,
|
|
|
|
});
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const last_offset = level_headers.items[i - 1].offset;
|
|
|
|
const last_size = try levels.items[i - 1].calculateSize();
|
|
|
|
const offset = @intCast(u16, last_offset + last_size);
|
|
|
|
try level_headers.append(.{
|
|
|
|
.x = level.world_x,
|
|
|
|
.y = level.world_y,
|
|
|
|
.offset = offset,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create array to write data to
|
|
|
|
var data = std.ArrayList(u8).init(allocator);
|
|
|
|
defer data.deinit();
|
|
|
|
const writer = data.writer();
|
|
|
|
|
2022-08-08 21:56:26 -06:00
|
|
|
try world.write(
|
|
|
|
writer,
|
|
|
|
level_headers.items,
|
|
|
|
entity_array.items,
|
|
|
|
wires.items,
|
|
|
|
circuit.items,
|
|
|
|
levels.items,
|
|
|
|
);
|
2022-08-06 01:15:38 -06:00
|
|
|
|
|
|
|
// Open output file and write data into it
|
2022-08-03 16:23:59 -06:00
|
|
|
cwd.makePath(this.builder.getInstallPath(.lib, "")) catch |e| switch (e) {
|
|
|
|
error.PathAlreadyExists => {},
|
|
|
|
else => return e,
|
|
|
|
};
|
|
|
|
try cwd.writeFile(output, data.items);
|
2022-08-04 01:59:32 -06:00
|
|
|
|
|
|
|
this.world_data.path = output;
|
2022-08-03 16:23:59 -06:00
|
|
|
}
|
2022-08-05 21:55:07 -06:00
|
|
|
|
2022-08-06 01:15:38 -06:00
|
|
|
/// Returns parsed level. User owns level.tiles
|
2022-08-05 21:55:07 -06:00
|
|
|
fn parseLevel(opt: struct {
|
|
|
|
allocator: std.mem.Allocator,
|
|
|
|
ldtk: LDtk.Root,
|
|
|
|
level: LDtk.Level,
|
|
|
|
entity_array: *std.ArrayList(world.Entity),
|
2022-08-08 21:56:26 -06:00
|
|
|
wires: *std.ArrayList(world.Wire),
|
2022-08-05 21:55:07 -06:00
|
|
|
}) !world.Level {
|
|
|
|
const ldtk = opt.ldtk;
|
|
|
|
const level = opt.level;
|
|
|
|
const entity_array = opt.entity_array;
|
|
|
|
const allocator = opt.allocator;
|
2022-08-08 21:56:26 -06:00
|
|
|
const wires = opt.wires;
|
2022-08-05 21:55:07 -06:00
|
|
|
|
|
|
|
const layers = level.layerInstances orelse return error.NoLayers;
|
|
|
|
|
2022-08-10 17:17:25 -06:00
|
|
|
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)));
|
2022-08-05 21:55:07 -06:00
|
|
|
|
|
|
|
var circuit_layer: ?LDtk.LayerInstance = null;
|
|
|
|
var collision_layer: ?LDtk.LayerInstance = null;
|
|
|
|
|
|
|
|
for (layers) |layer| {
|
|
|
|
if (std.mem.eql(u8, layer.__identifier, "Entities")) {
|
|
|
|
// Entities
|
|
|
|
std.debug.assert(layer.__type == .Entities);
|
|
|
|
|
|
|
|
for (layer.entityInstances) |entity| {
|
2022-08-08 21:56:26 -06:00
|
|
|
var is_wire = false;
|
2022-08-05 21:55:07 -06:00
|
|
|
var kind_opt: ?world.EntityKind = null;
|
|
|
|
if (std.mem.eql(u8, entity.__identifier, "Player")) {
|
|
|
|
kind_opt = .Player;
|
|
|
|
} else if (std.mem.eql(u8, entity.__identifier, "Wire")) {
|
2022-08-08 21:56:26 -06:00
|
|
|
is_wire = true;
|
2022-08-05 21:55:07 -06:00
|
|
|
} else if (std.mem.eql(u8, entity.__identifier, "Coin")) {
|
|
|
|
kind_opt = .Coin;
|
|
|
|
} else if (std.mem.eql(u8, entity.__identifier, "Door")) {
|
|
|
|
kind_opt = .Door;
|
|
|
|
} else if (std.mem.eql(u8, entity.__identifier, "Trapdoor")) {
|
|
|
|
kind_opt = .Trapdoor;
|
|
|
|
}
|
|
|
|
|
2022-08-08 21:56:26 -06:00
|
|
|
const levelc = world.Coordinate.fromWorld(world_x, world_y);
|
2022-08-07 00:16:00 -06:00
|
|
|
// Parsing code for wire entities. They're a little more complex
|
|
|
|
// than the rest
|
2022-08-05 21:55:07 -06:00
|
|
|
if (kind_opt) |kind| {
|
2022-08-08 21:56:26 -06:00
|
|
|
const entc = world.Coordinate.init(.{
|
|
|
|
@intCast(i16, entity.__grid[0]),
|
|
|
|
@intCast(i16, entity.__grid[1]),
|
|
|
|
});
|
|
|
|
const world_entity = world.Entity{ .kind = kind, .coord = levelc.addC(entc) };
|
|
|
|
try entity_array.append(world_entity);
|
|
|
|
}
|
|
|
|
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(.{
|
|
|
|
@intCast(i16, entity.__grid[0]),
|
|
|
|
@intCast(i16, entity.__grid[1]),
|
|
|
|
});
|
|
|
|
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),
|
|
|
|
};
|
2022-08-05 21:55:07 -06:00
|
|
|
}
|
|
|
|
}
|
2022-08-08 21:56:26 -06:00
|
|
|
|
|
|
|
if (anchor1) {
|
|
|
|
try wires.append(.{ .BeginPinned = p1_c.addC(levelc) });
|
|
|
|
} else {
|
|
|
|
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() });
|
|
|
|
}
|
|
|
|
try wires.append(.End);
|
2022-08-05 21:55:07 -06:00
|
|
|
}
|
|
|
|
}
|
2022-08-08 18:00:25 -06:00
|
|
|
|
|
|
|
std.log.warn("Entities: {}", .{entity_array.items.len});
|
2022-08-05 21:55:07 -06:00
|
|
|
} else if (std.mem.eql(u8, layer.__identifier, "Circuit")) {
|
|
|
|
// Circuit
|
|
|
|
std.debug.assert(layer.__type == .IntGrid);
|
|
|
|
|
|
|
|
circuit_layer = layer;
|
|
|
|
} else if (std.mem.eql(u8, layer.__identifier, "Collision")) {
|
|
|
|
// Collision
|
|
|
|
std.debug.assert(layer.__type == .IntGrid);
|
|
|
|
|
|
|
|
collision_layer = layer;
|
|
|
|
} else {
|
|
|
|
// Unknown
|
|
|
|
std.log.warn("{s}: {}", .{ layer.__identifier, layer.__type });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (circuit_layer == null) return error.MissingCircuitLayer;
|
|
|
|
if (collision_layer == null) return error.MissingCollisionLayer;
|
|
|
|
|
|
|
|
const circuit = circuit_layer.?;
|
|
|
|
const collision = collision_layer.?;
|
|
|
|
|
|
|
|
std.debug.assert(circuit.__cWid == collision.__cWid);
|
|
|
|
std.debug.assert(circuit.__cHei == collision.__cHei);
|
|
|
|
|
|
|
|
const width = @intCast(u16, circuit.__cWid);
|
|
|
|
const size = @intCast(u16, width * circuit.__cHei);
|
|
|
|
|
2022-08-08 18:00:25 -06:00
|
|
|
// Entities go into global scope now
|
2022-08-05 21:55:07 -06:00
|
|
|
var parsed_level = world.Level{
|
|
|
|
.world_x = world_x,
|
|
|
|
.world_y = world_y,
|
|
|
|
.width = @intCast(u16, width),
|
|
|
|
.size = @intCast(u16, size),
|
2022-08-06 01:15:38 -06:00
|
|
|
.tiles = try allocator.alloc(world.TileData, size),
|
2022-08-05 21:55:07 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
const tiles = parsed_level.tiles.?;
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
tiles[i] = world.TileData{ .tile = @intCast(u7, t) };
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add circuit tiles
|
|
|
|
for (circuit.intGridCsv) |cir64, i| {
|
2022-08-10 17:17:25 -06:00
|
|
|
const cir = @intToEnum(world.CircuitType, @intCast(u5, cir64));
|
2022-08-05 21:55:07 -06:00
|
|
|
const col = collision.intGridCsv[i];
|
2022-08-10 17:17:25 -06:00
|
|
|
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 => return error.DebrisAndCircuitOverlapped,
|
|
|
|
};
|
|
|
|
if (cir == .Socket)
|
|
|
|
std.log.warn("[parseLevel] {}: {}", .{ i, cir });
|
|
|
|
tiles[i] = world.TileData{ .flags = .{
|
|
|
|
.solid = solid,
|
|
|
|
.circuit = cir,
|
|
|
|
} };
|
2022-08-05 21:55:07 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return parsed_level;
|
|
|
|
}
|
2022-08-07 00:16:00 -06:00
|
|
|
|
|
|
|
pub fn buildCircuit(alloc: std.mem.Allocator, levels: []world.Level) !std.ArrayList(world.CircuitNode) {
|
2022-08-07 14:40:08 -06:00
|
|
|
const Coord = world.Coordinate;
|
2022-08-07 00:16:00 -06:00
|
|
|
const SearchItem = struct {
|
2022-08-07 14:40:08 -06:00
|
|
|
coord: Coord,
|
|
|
|
last_coord: ?Coord = null,
|
2022-08-07 15:56:26 -06:00
|
|
|
last_node: world.NodeID,
|
2022-08-07 00:16:00 -06:00
|
|
|
};
|
|
|
|
const Queue = std.TailQueue(SearchItem);
|
|
|
|
const Node = Queue.Node;
|
|
|
|
|
|
|
|
var nodes = std.ArrayList(world.CircuitNode).init(alloc);
|
|
|
|
|
|
|
|
var sources = Queue{};
|
2022-08-08 12:45:34 -06:00
|
|
|
var sockets = Queue{};
|
2022-08-07 00:16:00 -06:00
|
|
|
|
|
|
|
for (levels) |level| {
|
|
|
|
// Use a global coordinate system for our algorithm
|
2022-08-07 14:40:08 -06:00
|
|
|
const global_x = @intCast(i16, level.world_x) * 20;
|
|
|
|
const global_y = @intCast(i16, level.world_y) * 20;
|
2022-08-07 00:16:00 -06:00
|
|
|
for (level.tiles orelse continue) |tileData, i| {
|
2022-08-07 14:40:08 -06:00
|
|
|
const x = global_x + @intCast(i16, @mod(i, level.width));
|
|
|
|
const y = global_y + @intCast(i16, @divTrunc(i, level.width));
|
2022-08-10 17:17:25 -06:00
|
|
|
const search_item = try alloc.create(Node);
|
|
|
|
search_item.* = .{ .data = .{
|
2022-08-07 15:56:26 -06:00
|
|
|
.last_node = @intCast(world.NodeID, nodes.items.len),
|
2022-08-07 14:40:08 -06:00
|
|
|
.coord = Coord.init(.{ x, y }),
|
2022-08-07 00:51:08 -06:00
|
|
|
} };
|
2022-08-07 00:16:00 -06:00
|
|
|
switch (tileData) {
|
|
|
|
.tile => |_| {
|
|
|
|
// Do nothing
|
|
|
|
},
|
|
|
|
.flags => |flags| {
|
|
|
|
switch (flags.circuit) {
|
|
|
|
.Source => {
|
2022-08-07 14:40:08 -06:00
|
|
|
try nodes.append(.{ .kind = .Source, .coord = Coord.init(.{ x, y }) });
|
2022-08-10 17:17:25 -06:00
|
|
|
sources.append(search_item);
|
2022-08-07 00:16:00 -06:00
|
|
|
},
|
2022-08-08 12:45:34 -06:00
|
|
|
.Socket => {
|
2022-08-10 17:17:25 -06:00
|
|
|
search_item.data.last_node = std.math.maxInt(world.NodeID);
|
|
|
|
sockets.append(search_item);
|
2022-08-07 00:51:08 -06:00
|
|
|
},
|
2022-08-07 00:16:00 -06:00
|
|
|
else => {
|
|
|
|
// Do nothing
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-07 14:40:08 -06:00
|
|
|
var visited = std.AutoHashMap(Coord, void).init(alloc);
|
2022-08-07 12:47:44 -06:00
|
|
|
defer visited.deinit();
|
2022-08-07 00:16:00 -06:00
|
|
|
|
|
|
|
var bfs_queue = Queue{};
|
|
|
|
|
|
|
|
var run: usize = 0;
|
|
|
|
while (run < 2) : (run += 1) {
|
|
|
|
if (run == 0) bfs_queue.concatByMoving(&sources);
|
2022-08-08 12:45:34 -06:00
|
|
|
if (run == 1) bfs_queue.concatByMoving(&sockets);
|
2022-08-07 00:16:00 -06:00
|
|
|
|
|
|
|
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;
|
2022-08-10 17:17:25 -06:00
|
|
|
try visited.put(coord, {});
|
|
|
|
|
2022-08-07 14:40:08 -06:00
|
|
|
const worldc = coord.toWorld();
|
2022-08-10 17:17:25 -06:00
|
|
|
// const level = getLevel(levels, worldc[0], worldc[1]);
|
|
|
|
if (getLevel(levels, worldc[0], worldc[1])) |level| {
|
2022-08-07 00:16:00 -06:00
|
|
|
const last_node = node.data.last_node;
|
|
|
|
var next_node = last_node;
|
|
|
|
|
2022-08-10 17:17:25 -06:00
|
|
|
const tile = level.getTile(coord) orelse continue;
|
2022-08-10 18:25:57 -06:00
|
|
|
// std.log.warn("[buildCircuit] {} [{}] {}", .{ coord, node.data.last_node, tile });
|
2022-08-07 00:16:00 -06:00
|
|
|
|
|
|
|
if (tile != .flags) continue;
|
|
|
|
const flags = tile.flags;
|
|
|
|
|
|
|
|
switch (flags.circuit) {
|
2022-08-07 15:56:26 -06:00
|
|
|
.Conduit => {
|
2022-08-08 14:39:33 -06:00
|
|
|
// 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
|
2022-08-07 00:16:00 -06:00
|
|
|
// TODO
|
|
|
|
},
|
2022-08-10 18:25:57 -06:00
|
|
|
.Conduit_Horizontal => {},
|
|
|
|
.Conduit_Vertical => {},
|
|
|
|
.Source => {}, // Do nothing, but add everything around the source
|
2022-08-08 12:45:34 -06:00
|
|
|
.Socket => {
|
|
|
|
next_node = @intCast(world.NodeID, nodes.items.len);
|
|
|
|
try nodes.append(.{
|
|
|
|
.kind = .{ .Socket = null },
|
|
|
|
.coord = coord,
|
|
|
|
});
|
|
|
|
},
|
2022-08-07 14:40:08 -06:00
|
|
|
.Plug => {
|
2022-08-08 12:45:34 -06:00
|
|
|
// Plugs by their nature end a conduit path, so don't add
|
|
|
|
// surrounding tiles.
|
|
|
|
try nodes.append(.{
|
|
|
|
.kind = .{ .Plug = last_node },
|
|
|
|
.coord = coord,
|
|
|
|
});
|
|
|
|
continue;
|
2022-08-07 00:16:00 -06:00
|
|
|
},
|
|
|
|
.Outlet => {
|
2022-08-07 15:56:26 -06:00
|
|
|
next_node = @intCast(world.NodeID, nodes.items.len);
|
2022-08-07 00:51:08 -06:00
|
|
|
try nodes.append(.{
|
|
|
|
.kind = .{ .Outlet = last_node },
|
|
|
|
.coord = coord,
|
|
|
|
});
|
2022-08-07 00:16:00 -06:00
|
|
|
},
|
2022-08-10 18:25:57 -06:00
|
|
|
.Switch_Off => {
|
|
|
|
// Add switch
|
|
|
|
next_node = @intCast(world.NodeID, nodes.items.len);
|
|
|
|
try nodes.append(.{
|
|
|
|
.kind = .{ .Switch = .{
|
|
|
|
.source = last_node,
|
|
|
|
.state = 0,
|
|
|
|
} },
|
|
|
|
.coord = coord,
|
2022-08-10 17:17:25 -06:00
|
|
|
});
|
2022-08-10 18:25:57 -06:00
|
|
|
},
|
|
|
|
.Switch_On => {
|
2022-08-08 14:39:33 -06:00
|
|
|
// Add switch
|
2022-08-07 15:56:26 -06:00
|
|
|
next_node = @intCast(world.NodeID, nodes.items.len);
|
2022-08-07 00:51:08 -06:00
|
|
|
try nodes.append(.{
|
2022-08-08 14:39:33 -06:00
|
|
|
.kind = .{ .Switch = .{
|
|
|
|
.source = last_node,
|
2022-08-10 18:25:57 -06:00
|
|
|
.state = 1,
|
2022-08-08 14:39:33 -06:00
|
|
|
} },
|
2022-08-07 00:51:08 -06:00
|
|
|
.coord = coord,
|
|
|
|
});
|
2022-08-07 00:16:00 -06:00
|
|
|
},
|
|
|
|
.Join => {
|
2022-08-07 14:40:08 -06:00
|
|
|
const last_coord = node.data.last_coord.?;
|
|
|
|
if (last_coord.toLevelTopLeft().eq(coord.toLevelTopLeft())) {
|
|
|
|
std.log.warn("Join first side", .{});
|
|
|
|
} else {
|
2022-08-07 15:56:26 -06:00
|
|
|
next_node = @intCast(world.NodeID, nodes.items.len);
|
2022-08-10 18:25:57 -06:00
|
|
|
std.log.warn("Join second side", .{});
|
2022-08-07 14:40:08 -06:00
|
|
|
try nodes.append(.{
|
|
|
|
.kind = .{ .Join = last_node },
|
|
|
|
.coord = coord,
|
|
|
|
});
|
|
|
|
}
|
2022-08-07 00:16:00 -06:00
|
|
|
},
|
|
|
|
.And => {
|
2022-08-10 18:25:57 -06:00
|
|
|
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,
|
|
|
|
});
|
2022-08-07 00:16:00 -06:00
|
|
|
},
|
|
|
|
.Xor => {
|
2022-08-10 18:25:57 -06:00
|
|
|
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,
|
|
|
|
});
|
2022-08-07 00:16:00 -06:00
|
|
|
},
|
2022-08-10 17:17:25 -06:00
|
|
|
.Diode => {
|
|
|
|
// TODO
|
|
|
|
},
|
2022-08-08 12:45:34 -06:00
|
|
|
.None => continue,
|
2022-08-07 00:16:00 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
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 = .{
|
|
|
|
.last_node = next_node,
|
2022-08-07 14:40:08 -06:00
|
|
|
.coord = coord.add(.{ 1, 0 }),
|
2022-08-07 12:47:44 -06:00
|
|
|
.last_coord = coord,
|
2022-08-07 00:16:00 -06:00
|
|
|
} };
|
|
|
|
left.* = Node{ .data = .{
|
|
|
|
.last_node = next_node,
|
2022-08-07 14:40:08 -06:00
|
|
|
.coord = coord.add(.{ -1, 0 }),
|
2022-08-07 12:47:44 -06:00
|
|
|
.last_coord = coord,
|
2022-08-07 00:16:00 -06:00
|
|
|
} };
|
|
|
|
down.* = Node{ .data = .{
|
|
|
|
.last_node = next_node,
|
2022-08-07 14:40:08 -06:00
|
|
|
.coord = coord.add(.{ 0, 1 }),
|
2022-08-07 12:47:44 -06:00
|
|
|
.last_coord = coord,
|
2022-08-07 00:16:00 -06:00
|
|
|
} };
|
|
|
|
up.* = Node{ .data = .{
|
|
|
|
.last_node = next_node,
|
2022-08-07 14:40:08 -06:00
|
|
|
.coord = coord.add(.{ 0, -1 }),
|
2022-08-07 12:47:44 -06:00
|
|
|
.last_coord = coord,
|
2022-08-07 00:16:00 -06:00
|
|
|
} };
|
|
|
|
|
|
|
|
bfs_queue.append(right);
|
|
|
|
bfs_queue.append(left);
|
|
|
|
bfs_queue.append(down);
|
|
|
|
bfs_queue.append(up);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-10 18:25:57 -06:00
|
|
|
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);
|
|
|
|
std.log.warn("[{}]: Found {} neighbors", .{ i, neighbors.items.len });
|
|
|
|
for (neighbors.items) |node, a| {
|
|
|
|
std.log.warn("\tNeighbor {}: [{}] {}", .{ a, node.id, node.side });
|
|
|
|
// if (node.side == .North) linkNeighbor(nodes.items[node]);
|
|
|
|
if (node.side == .West) nodes.items[i].kind.And[0] = node.id;
|
|
|
|
if (node.side == .East) nodes.items[i].kind.And[1] = node.id;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
.Xor => {},
|
|
|
|
.Conduit => {},
|
|
|
|
.Plug => {},
|
|
|
|
.Socket => {},
|
|
|
|
.Switch => {},
|
|
|
|
.SwitchOutlet => {},
|
|
|
|
.Join => {},
|
|
|
|
.Outlet => {},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-07 00:16:00 -06:00
|
|
|
return nodes;
|
|
|
|
}
|
2022-08-10 17:17:25 -06:00
|
|
|
|
2022-08-10 18:25:57 -06:00
|
|
|
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) {
|
|
|
|
const Coord = world.Coordinate;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-08-10 17:17:25 -06:00
|
|
|
const Dir = enum { North, West, East, South };
|
|
|
|
|
|
|
|
fn getInputDirection(coord: world.Coordinate, last_coord: world.Coordinate) 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;
|
|
|
|
}
|
2022-08-10 18:25:57 -06:00
|
|
|
|
|
|
|
fn getNode(nodes: []world.CircuitNode, coord: world.Coordinate) ?world.NodeID {
|
|
|
|
for (nodes) |node, i| {
|
|
|
|
if (node.coord.eq(coord)) return @intCast(world.NodeID, i);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|