From aa539339b9a5933046009e37edc3730177136d30 Mon Sep 17 00:00:00 2001 From: Louis Pearson Date: Wed, 10 Aug 2022 18:25:57 -0600 Subject: [PATCH] Switch to a multi stage circuit build --- tools/LDtkImport.zig | 489 +++++++++++++++---------------------------- 1 file changed, 166 insertions(+), 323 deletions(-) diff --git a/tools/LDtkImport.zig b/tools/LDtkImport.zig index 43e8430..2fd2392 100644 --- a/tools/LDtkImport.zig +++ b/tools/LDtkImport.zig @@ -358,8 +358,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{}; @@ -382,69 +380,21 @@ pub fn buildCircuit(alloc: std.mem.Allocator, levels: []world.Level) !std.ArrayL var next_node = last_node; const tile = level.getTile(coord) orelse continue; - std.log.warn("[buildCircuit] {} [{}] {}", .{ coord, node.data.last_node, tile }); + // std.log.warn("[buildCircuit] {} [{}] {}", .{ coord, node.data.last_node, tile }); if (tile != .flags) continue; const flags = tile.flags; 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 => { - // Skip vertical inputs - const last_coord = node.data.last_coord.?; - const input_dir: Dir = getInputDirection(coord, last_coord); - if (input_dir == .North or input_dir == .South) { - _ = visited.remove(coord); - continue; - } - const left = try alloc.create(Node); - left.* = Node{ .data = .{ - .last_node = last_node, - .coord = coord.add(.{ -1, 0 }), - .last_coord = coord, - } }; - bfs_queue.append(left); - - const right = try alloc.create(Node); - right.* = Node{ .data = .{ - .last_node = last_node, - .coord = coord.add(.{ 1, 0 }), - .last_coord = coord, - } }; - bfs_queue.append(right); - continue; - }, - .Conduit_Vertical => { - // Skip horizontal inputs - const last_coord = node.data.last_coord.?; - const input_dir: Dir = getInputDirection(coord, last_coord); - if (input_dir == .West or input_dir == .East) { - _ = visited.remove(coord); - continue; - } - const up = try alloc.create(Node); - up.* = Node{ .data = .{ - .last_node = last_node, - .coord = coord.add(.{ 0, -1 }), - .last_coord = coord, - } }; - bfs_queue.append(up); - - const down = try alloc.create(Node); - down.* = Node{ .data = .{ - .last_node = last_node, - .coord = coord.add(.{ 0, 1 }), - .last_coord = coord, - } }; - bfs_queue.append(down); - continue; - }, + .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(.{ @@ -468,159 +418,35 @@ pub fn buildCircuit(alloc: std.mem.Allocator, levels: []world.Level) !std.ArrayL .coord = coord, }); }, - .Switch_Off, .Switch_On => { - // Identify input side - const last_coord = node.data.last_coord.?; - const input_dir: Dir = getInputDirection(coord, last_coord); - // Find outlets - const ncoord = coord.add(.{ 0, -1 }); - const wcoord = coord.add(.{ -1, 0 }); - const ecoord = coord.add(.{ 1, 0 }); - const scoord = coord.add(.{ 0, 1 }); - - const north_opt = if (level.getTile(ncoord)) |t| t.getCircuit() else @panic("AAAAA"); - const west_opt = if (level.getTile(wcoord)) |t| t.getCircuit() else @panic("AAAAA"); - const east_opt = if (level.getTile(ecoord)) |t| t.getCircuit() else @panic("AAAAA"); - const south_opt = if (level.getTile(scoord)) |t| t.getCircuit() else @panic("AAAAA"); - - const north_tile = north_opt orelse world.CircuitType.None; - const west_tile = west_opt orelse world.CircuitType.None; - const east_tile = east_opt orelse world.CircuitType.None; - const south_tile = south_opt orelse world.CircuitType.None; - - const north = north_tile != .None and north_tile != .Conduit_Horizontal; - const west = west_tile != .None and west_tile != .Conduit_Vertical; - const east = east_tile != .None and east_tile != .Conduit_Vertical; - const south = south_tile != .None and south_tile != .Conduit_Horizontal; - - std.log.warn("[buildCircuit] {}: {} {},\n\t{} {},\n\t{} {},\n\t{} {}", .{ - coord, - north_tile, - ncoord, - west_tile, - wcoord, - east_tile, - ecoord, - south_tile, - scoord, - }); - - // 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, + }); + }, + .Switch_On => { + // Add switch + next_node = @intCast(world.NodeID, nodes.items.len); + try nodes.append(.{ + .kind = .{ .Switch = .{ + .source = last_node, + .state = 1, } }, .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); - } - - 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); - } - - 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); - } - - 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; }, .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, @@ -628,139 +454,18 @@ pub fn buildCircuit(alloc: std.mem.Allocator, levels: []world.Level) !std.ArrayL } }, .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 - next_node = @intCast(world.NodeID, nodes.items.len); - try nodes.append(.{ - .kind = .{ .And = .{ last_node, std.math.maxInt(world.NodeID) } }, - .coord = coord, - }); - std.log.warn("{}", .{nodes.items[last_node]}); - switch (nodes.items[last_node].kind) { - .And => |_and| { - if (_and[0] == std.math.maxInt(world.NodeID)) { - nodes.items[last_node].kind.And[0] = next_node; - } else if (_and[1] == std.math.maxInt(world.NodeID)) { - nodes.items[last_node].kind.And[1] = next_node; - } else { - return error.AndGateFilled; - } - }, - .SwitchOutlet => |_switch| { - _ = _switch; - std.log.warn("{}", .{nodes.items[last_node].coord}); - }, - .Socket => |socket| { - _ = socket; - }, - else => return error.Unimplemented, - } - } 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, + }); }, .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, + }); }, .Diode => { // TODO @@ -802,9 +507,140 @@ pub fn buildCircuit(alloc: std.mem.Allocator, levels: []world.Level) !std.ArrayL } } + 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 => {}, + } + } + 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) { + 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; +} + const Dir = enum { North, West, East, South }; fn getInputDirection(coord: world.Coordinate, last_coord: world.Coordinate) Dir { @@ -825,3 +661,10 @@ fn getLevel(levels: []world.Level, x: i8, y: i8) ?world.Level { } return null; } + +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; +}