diff options
Diffstat (limited to 'src/20/main.zig')
| -rw-r--r-- | src/20/main.zig | 470 |
1 files changed, 470 insertions, 0 deletions
diff --git a/src/20/main.zig b/src/20/main.zig new file mode 100644 index 0000000..badad27 --- /dev/null +++ b/src/20/main.zig @@ -0,0 +1,470 @@ +const std = @import("std"); +const aoc = @import("aoc"); + +const tile_width: u32 = 10; +const Tile = struct { + id: u32, + data: [tile_width * tile_width]u1, + sides: [4][tile_width]u1, // NOTE: update these values when placed accordingly + adj: [4]?usize, + pos: aoc.Pos, +}; +const Flip = enum { NONE, HOR, VERT }; +const flips = [_]Flip{ Flip.NONE, Flip.HOR, Flip.VERT }; + +const seamonster = [_][]const u8{ + " # ", + "# ## ## ###", + " # # # # # # ", +}; + +const dirs = [4]aoc.Pos{ + aoc.Pos{ .x = 0, .y = -1 }, + aoc.Pos{ .x = 1, .y = 0 }, + aoc.Pos{ .x = 0, .y = 1 }, + aoc.Pos{ .x = -1, .y = 0 }, +}; + +fn printGrid(tiles: []Tile, grid: []?usize, grid_width: u32) void { + var y: u32 = 0; + while (y < grid_width * tile_width) : (y += 1) { + var x: u32 = 0; + if (y > 0 and y % tile_width == 0) aoc.debugfmt("\n", .{}); + while (x < grid_width * tile_width) : (x += 1) { + if (x > 0 and x % tile_width == 0) aoc.debugfmt(" ", .{}); + var tile_index: ?usize = grid[@divFloor(y, tile_width) * grid_width + @divFloor(x, tile_width)]; + if (tile_index != null) { + var tile = tiles[tile_index.?]; + var c: u8 = if (tile.data[(y % tile_width) * tile_width + (x % tile_width)] == 1) '#' else '.'; + aoc.debugfmt("{c}", .{c}); + } else { + aoc.debugfmt("?", .{}); + } + } + aoc.debugfmt("\n", .{}); + } + aoc.debugfmt("\n\n", .{}); +} + +fn printTile(data: []u1, width: u32) void { + var y: u32 = 0; + while (y < width) : (y += 1) { + printTileSlice(data[y * width .. (y + 1) * width]); + aoc.debugfmt("\n", .{}); + } +} + +fn printTileSlice(slice: []const u1) void { + var i: u32 = 0; + while (i < slice.len) : (i += 1) { + var c: u8 = if (slice[i] == 1) '#' else '.'; + aoc.debugfmt("{c}", .{c}); + } +} + +fn couldMatch(side1: [tile_width]u1, side2: [tile_width]u1) bool { + var side1_cpy = side1; + if (std.mem.eql(u1, side1_cpy[0..], side2[0..])) return true; + std.mem.reverse(u1, side1_cpy[0..]); + if (std.mem.eql(u1, side1_cpy[0..], side2[0..])) return true; + return false; +} + +fn parseTiles(tiles: *std.ArrayList(Tile), input: []u8) !void { + var tileit = std.mem.split(u8, input, "\n\n"); + while (tileit.next()) |tilestr| { + if (tilestr.len <= 1) continue; + var tile: Tile = undefined; + var lineit = std.mem.tokenize(u8, tilestr, "\n"); + + // read tile id + var numstr = (try aoc.unwrap(lineit.next()))[5..9]; + tile.id = try std.fmt.parseInt(u32, numstr, 10); + + // read tile data + var i: u32 = 0; + var line: []const u8 = undefined; + while (i < tile_width * tile_width) : (i += 1) { + if (i % tile_width == 0) line = try aoc.unwrap(lineit.next()); + tile.data[i] = @boolToInt(line[i % tile_width] == '#'); + } + + // read side bits + i = 0; + while (i < 4) : (i += 1) { + var sidebits: [tile_width]u1 = undefined; + for (sidebits) |_, k| { + sidebits[k] = switch (i) { + @enumToInt(aoc.Dir.Name.NORTH) => tile.data[k], + @enumToInt(aoc.Dir.Name.WEST) => tile.data[(tile_width - 1 - k) * tile_width], + @enumToInt(aoc.Dir.Name.EAST) => tile.data[tile_width - 1 + k * tile_width], + @enumToInt(aoc.Dir.Name.SOUTH) => tile.data[tile_width * tile_width - 1 - k], + else => unreachable, + }; + } + tile.sides[i] = sidebits; + } + + // init misc data + tile.adj = [_]?usize{ null, null, null, null }; + + try tiles.append(tile); + } +} + +fn matchTiles(tiles: *std.ArrayList(Tile)) void { + // for each items's side, look for another item with a matching side + for (tiles.items) |*tile, tile_index| { + sides_loop: for (tile.sides) |_, i| { + for (tiles.items) |*otile, otile_index| { + if (tile_index == otile_index) continue; + + for (otile.sides) |_, oi| { + if (tile.adj[i] == null and otile.adj[oi] == null and couldMatch(tile.sides[i], otile.sides[oi])) { + tile.adj[i] = otile_index; + otile.adj[oi] = tile_index; + continue :sides_loop; + } + } + } + } + + if (aoc.debug) { + aoc.debugfmt("{}:\nSIDES ", .{tile.id}); + for (tile.sides) |side| { + for (side) |b| { + var c: u8 = if (b == 1) '#' else '.'; + aoc.debugfmt("{c}", .{c}); + } + aoc.debugfmt(" ", .{}); + } + aoc.debugfmt("\n", .{}); + for (tile.adj) |adj, i| { + if (tile.adj[i] == null) { + aoc.debugfmt("null ", .{}); + } else { + var adjtile = tiles.items[adj.?]; + aoc.debugfmt("{} ", .{adjtile.id}); + } + } + aoc.debugfmt("\n\n", .{}); + } + } +} + +fn checkSides(tiles: []Tile, grid: []?usize, grid_width: u32, p: aoc.Pos, adj: [4]?usize, sides: [4][10]u1) bool { + var rot: u32 = 0; + while (rot < 4) : (rot += 1) { + const np = p.add(dirs[rot]); + aoc.debugfmt("{}\n", .{@intToEnum(aoc.Dir.Name, @intCast(u2, rot))}); + aoc.debugfmt("{} {}\n", .{ np.x, np.y }); + + if (np.x < 0 or np.x >= grid_width or np.y < 0 or np.y >= grid_width) { + aoc.debugfmt("Side is null\n", .{}); + if (adj[rot] == null) { + continue; + } else { + aoc.debugfmt("Side {} should be NULL!\n", .{@intToEnum(aoc.Dir.Name, @intCast(u2, rot))}); + return false; + } + } + + var ti: ?usize = grid[@intCast(u32, np.y * @intCast(i64, grid_width) + np.x)]; + if (ti == null) continue; + + var otherside = tiles[ti.?].sides[aoc.Dir.nextCW(rot, 2)]; + std.mem.reverse(u1, otherside[0..]); + printTileSlice(otherside[0..]); + aoc.debugfmt(" vs ", .{}); + printTileSlice(sides[rot][0..]); + aoc.debugfmt("\n", .{}); + + if (!std.mem.eql(u1, otherside[0..], sides[rot][0..])) return false; + } + return true; +} + +fn rotateData(newdata: *[]u1, olddata: []u1, width: u32, rot: usize) void { + for (olddata) |v, di| { + const dy = @divFloor(di, width); + const dx = di % width; + var nx = dx; + var ny = dy; + switch (rot) { + @enumToInt(aoc.Dir.Name.NORTH) => {}, + @enumToInt(aoc.Dir.Name.EAST) => { + nx = width - 1 - dy; + ny = dx; + }, + @enumToInt(aoc.Dir.Name.SOUTH) => { + nx = width - 1 - dx; + ny = width - 1 - dy; + }, + @enumToInt(aoc.Dir.Name.WEST) => { + nx = dy; + ny = width - 1 - dx; + }, + else => {}, + } + newdata.*[ny * width + nx] = v; + } +} + +fn flipData(data: []u1, width: u32, flip: Flip) void { + switch (flip) { + Flip.HOR => { + var y: u32 = 0; + while (y < width) : (y += 1) { + std.mem.reverse(u1, data[y * width .. (y + 1) * width]); + } + }, + Flip.VERT => { + var x: u32 = 0; + while (x < width) : (x += 1) { + var y: u32 = 0; + while (y < width / 2) : (y += 1) { + std.mem.swap(u1, &data[x + y * width], &data[x + (width - 1 - y) * width]); + } + } + }, + else => {}, + } +} + +fn manipulateAndPlace(allocator: std.mem.Allocator, tiles: []Tile, grid: []?usize, grid_width: u32, p: aoc.Pos, tile_index: usize) !void { + var tile = &tiles[tile_index]; + + for (dirs) |_, rot| { + for (flips) |flip| { + // apply manipulation to only sides first for checking + var sides = tile.sides; + var tmp_sides = tile.sides; + var adj = tile.adj; + var tmp_adj = tile.adj; + switch (flip) { + Flip.HOR => { + std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.NORTH)][0..]); + std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.SOUTH)][0..]); + tmp_sides[@enumToInt(aoc.Dir.Name.WEST)] = sides[@enumToInt(aoc.Dir.Name.EAST)]; + tmp_sides[@enumToInt(aoc.Dir.Name.EAST)] = sides[@enumToInt(aoc.Dir.Name.WEST)]; + std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.EAST)][0..]); + std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.WEST)][0..]); + std.mem.swap(?usize, &adj[@enumToInt(aoc.Dir.Name.EAST)], &adj[@enumToInt(aoc.Dir.Name.WEST)]); + }, + Flip.VERT => { + std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.WEST)][0..]); + std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.EAST)][0..]); + tmp_sides[@enumToInt(aoc.Dir.Name.NORTH)] = sides[@enumToInt(aoc.Dir.Name.SOUTH)]; + tmp_sides[@enumToInt(aoc.Dir.Name.SOUTH)] = sides[@enumToInt(aoc.Dir.Name.NORTH)]; + std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.NORTH)][0..]); + std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.SOUTH)][0..]); + std.mem.swap(?usize, &adj[@enumToInt(aoc.Dir.Name.NORTH)], &adj[@enumToInt(aoc.Dir.Name.SOUTH)]); + }, + else => {}, + } + sides = tmp_sides; + + for (dirs) |_, i| { + tmp_sides[i] = sides[aoc.Dir.nextCCW(i, rot)]; + tmp_adj[i] = adj[aoc.Dir.nextCCW(i, rot)]; + } + + aoc.debugfmt("\nROT: {}\n", .{rot}); + aoc.debugfmt("FLIP: {}\n", .{flip}); + aoc.debugfmt("SIDES: \n", .{}); + for (tmp_sides) |side| { + printTileSlice(side[0..]); + aoc.debugfmt("\n", .{}); + } + const valid = checkSides(tiles, grid, grid_width, p, tmp_adj, tmp_sides); + + // now apply manipulations to image data + if (valid) { + // for (tile.sides) |side| { + // printTileSlice(side[0..]); + // aoc.debugfmt("\n", .{}); + // } + aoc.debugfmt("{} {} {}\n", .{ tile.id, flip, rot }); + printTile(tile.data[0..], tile_width); + aoc.debugfmt("\n", .{}); + tile.sides = tmp_sides; + tile.pos = p; + tile.adj = tmp_adj; + grid[@intCast(u32, p.y * @intCast(i64, grid_width) + p.x)] = tile_index; + + flipData(tile.data[0..], tile_width, flip); + + var newdata = try allocator.alloc(u1, tile_width * tile_width); + defer allocator.free(newdata); + + rotateData(&newdata, tile.data[0..], tile_width, rot); + std.mem.copy(u1, tile.data[0..], newdata); + return; + } + } + } + return aoc.Error.InvalidInput; +} + +fn part1(allocator: std.mem.Allocator, input: []u8, args: [][]u8) !?[]u8 { + _ = args; + + var tiles = std.ArrayList(Tile).init(allocator); + defer tiles.deinit(); + + try parseTiles(&tiles, input); + + matchTiles(&tiles); + + var answer: u64 = 1; + for (tiles.items) |tile| { + var unknown: u32 = 0; + for (tile.adj) |v| { + unknown += @boolToInt(v == null); + } + if (unknown == 2) answer *= tile.id; + } + + return try std.fmt.allocPrint(allocator, "{}", .{answer}); +} + +fn part2(allocator: std.mem.Allocator, input: []u8, args: [][]u8) !?[]u8 { + _ = args; + + var tiles = std.ArrayList(Tile).init(allocator); + defer tiles.deinit(); + + try parseTiles(&tiles, input); + + matchTiles(&tiles); + + var grid = try allocator.alloc(?usize, tiles.items.len); + std.mem.set(?usize, grid, null); + defer allocator.free(grid); + var grid_width = @intCast(u32, std.math.sqrt(tiles.items.len)); + + var left = std.ArrayList(usize).init(allocator); + defer left.deinit(); + + for (tiles.items) |_, tile_index| { + try left.append(tile_index); + } + + // add a corner as first item to add onto + for (tiles.items) |tile, tile_index| { + var unknown: u32 = 0; + for (tile.adj) |v| { + unknown += @boolToInt(v == null); + } + if (unknown == 2) { + try manipulateAndPlace(allocator, tiles.items, grid, grid_width, aoc.Pos{ .x = 0, .y = 0 }, tile_index); + _ = left.swapRemove(tile_index); + break; + } + } + + // find adjacents and manipulate until they fit + next_tile: while (left.items.len > 0) { + if (aoc.debug) printGrid(tiles.items, grid, grid_width); + + for (grid) |_, gi| { + aoc.debugfmt("POS {}\n", .{gi}); + const tile_index: ?usize = grid[gi] orelse continue; + aoc.debugfmt("POS {} YES\n", .{gi}); + + var placed = &tiles.items[tile_index.?]; + for (placed.adj) |want, dir| { + if (want == null) continue; + // aoc.debugfmt("LOOKING FOR {}\n", .{want.?}); + // for (left.items) |item| { + // aoc.debugfmt("> {}\n", .{item}); + // } + if (std.mem.indexOfScalar(usize, left.items, want.?)) |left_index| { + const new_index = left.items[left_index]; + const new_tile = tiles.items[new_index]; + var npos = placed.pos.add(dirs[dir]); + aoc.debugfmt("TRYING TO PLACE {} NEW AT: {} {}\n", .{ new_tile.id, npos.x, npos.y }); + + try manipulateAndPlace(allocator, tiles.items, grid, grid_width, npos, new_index); + + _ = left.swapRemove(left_index); + continue :next_tile; + } + } + } + return aoc.Error.InvalidInput; + } + + if (aoc.debug) printGrid(tiles.items, grid, grid_width); + + // convert grid to image + var image = try allocator.alloc(u1, grid.len * (tile_width - 2) * (tile_width - 2)); + defer allocator.free(image); + + const image_width = grid_width * (tile_width - 2); + var y: u32 = 0; + while (y < grid_width) : (y += 1) { + var x: u32 = 0; + while (x < grid_width) : (x += 1) { + var tile = tiles.items[grid[y * grid_width + x].?]; + var doff = (y * grid_width * (tile_width - 2) + x) * (tile_width - 2); + var dy: u32 = 1; + while (dy < tile_width - 1) : (dy += 1) { + var dx: u32 = 1; + while (dx < tile_width - 1) : (dx += 1) { + image[doff + (dy - 1) * image_width + (dx - 1)] = tile.data[dy * tile_width + dx]; + } + } + } + } + + y = 0; + while (y < grid_width * (tile_width - 2)) : (y += 1) { + printTileSlice(image[y * grid_width * (tile_width - 2) .. (y + 1) * grid_width * (tile_width - 2)]); + aoc.debugfmt("\n", .{}); + } + + // rotate image till we find a sea monster + var image_copy = try allocator.dupe(u1, image); + defer allocator.free(image_copy); + + var rot: usize = 0; + while (rot < 4) : (rot += 1) { + for (flips) |flip| { + flipData(image, image_width, flip); + rotateData(&image_copy, image, image_width, rot); + + y = 0; + var count: u32 = 0; + while (y < image_width - seamonster.len) : (y += 1) { + var x: u32 = 0; + check_next: while (x < image_width - seamonster[0].len) : (x += 1) { + var sy: u32 = 0; + while (sy < seamonster.len) : (sy += 1) { + var sx: u32 = 0; + while (sx < seamonster[0].len) : (sx += 1) { + if (seamonster[sy][sx] == '#' and image_copy[(y + sy) * image_width + (x + sx)] != 1) + continue :check_next; + } + } + count += 1; + } + } + + if (count > 0) { + var image_count = std.mem.count(u1, image_copy, ([_]u1{1})[0..]); + var seamonster_count: usize = 0; + var sy: u32 = 0; + while (sy < seamonster.len) : (sy += 1) { + seamonster_count += std.mem.count(u8, seamonster[sy], "#"); + } + const answer = image_count - count * seamonster_count; + return try std.fmt.allocPrint(allocator, "{}", .{answer}); + } + } + } + + return null; +} + +pub const main = aoc.main(part1, part2, .{ "54755174472007", "1692" }); |
