main.zig (16941B)
1const std = @import("std"); 2const aoc = @import("aoc"); 3 4const tile_width: u32 = 10; 5const Tile = struct { 6 id: u32, 7 data: [tile_width * tile_width]u1, 8 sides: [4][tile_width]u1, // NOTE: update these values when placed accordingly 9 adj: [4]?usize, 10 pos: aoc.Pos, 11}; 12const Flip = enum { NONE, HOR, VERT }; 13const flips = [_]Flip{ Flip.NONE, Flip.HOR, Flip.VERT }; 14 15const seamonster = [_][]const u8{ 16 " # ", 17 "# ## ## ###", 18 " # # # # # # ", 19}; 20 21const dirs = [4]aoc.Pos{ 22 aoc.Pos{ .x = 0, .y = -1 }, 23 aoc.Pos{ .x = 1, .y = 0 }, 24 aoc.Pos{ .x = 0, .y = 1 }, 25 aoc.Pos{ .x = -1, .y = 0 }, 26}; 27 28fn printGrid(tiles: []Tile, grid: []?usize, grid_width: u32) void { 29 var y: u32 = 0; 30 while (y < grid_width * tile_width) : (y += 1) { 31 var x: u32 = 0; 32 if (y > 0 and y % tile_width == 0) aoc.debugfmt("\n", .{}); 33 while (x < grid_width * tile_width) : (x += 1) { 34 if (x > 0 and x % tile_width == 0) aoc.debugfmt(" ", .{}); 35 var tile_index: ?usize = grid[@divFloor(y, tile_width) * grid_width + @divFloor(x, tile_width)]; 36 if (tile_index != null) { 37 var tile = tiles[tile_index.?]; 38 var c: u8 = if (tile.data[(y % tile_width) * tile_width + (x % tile_width)] == 1) '#' else '.'; 39 aoc.debugfmt("{c}", .{c}); 40 } else { 41 aoc.debugfmt("?", .{}); 42 } 43 } 44 aoc.debugfmt("\n", .{}); 45 } 46 aoc.debugfmt("\n\n", .{}); 47} 48 49fn printTile(data: []u1, width: u32) void { 50 var y: u32 = 0; 51 while (y < width) : (y += 1) { 52 printTileSlice(data[y * width .. (y + 1) * width]); 53 aoc.debugfmt("\n", .{}); 54 } 55} 56 57fn printTileSlice(slice: []const u1) void { 58 var i: u32 = 0; 59 while (i < slice.len) : (i += 1) { 60 var c: u8 = if (slice[i] == 1) '#' else '.'; 61 aoc.debugfmt("{c}", .{c}); 62 } 63} 64 65fn couldMatch(side1: [tile_width]u1, side2: [tile_width]u1) bool { 66 var side1_cpy = side1; 67 if (std.mem.eql(u1, side1_cpy[0..], side2[0..])) return true; 68 std.mem.reverse(u1, side1_cpy[0..]); 69 if (std.mem.eql(u1, side1_cpy[0..], side2[0..])) return true; 70 return false; 71} 72 73fn parseTiles(tiles: *std.ArrayList(Tile), input: []u8) !void { 74 var tileit = std.mem.split(u8, input, "\n\n"); 75 while (tileit.next()) |tilestr| { 76 if (tilestr.len <= 1) continue; 77 var tile: Tile = undefined; 78 var lineit = std.mem.tokenize(u8, tilestr, "\n"); 79 80 // read tile id 81 var numstr = (try aoc.unwrap(lineit.next()))[5..9]; 82 tile.id = try std.fmt.parseInt(u32, numstr, 10); 83 84 // read tile data 85 var i: u32 = 0; 86 var line: []const u8 = undefined; 87 while (i < tile_width * tile_width) : (i += 1) { 88 if (i % tile_width == 0) line = try aoc.unwrap(lineit.next()); 89 tile.data[i] = @boolToInt(line[i % tile_width] == '#'); 90 } 91 92 // read side bits 93 i = 0; 94 while (i < 4) : (i += 1) { 95 var sidebits: [tile_width]u1 = undefined; 96 for (sidebits) |_, k| { 97 sidebits[k] = switch (i) { 98 @enumToInt(aoc.Dir.Name.NORTH) => tile.data[k], 99 @enumToInt(aoc.Dir.Name.WEST) => tile.data[(tile_width - 1 - k) * tile_width], 100 @enumToInt(aoc.Dir.Name.EAST) => tile.data[tile_width - 1 + k * tile_width], 101 @enumToInt(aoc.Dir.Name.SOUTH) => tile.data[tile_width * tile_width - 1 - k], 102 else => unreachable, 103 }; 104 } 105 tile.sides[i] = sidebits; 106 } 107 108 // init misc data 109 tile.adj = [_]?usize{ null, null, null, null }; 110 111 try tiles.append(tile); 112 } 113} 114 115fn matchTiles(tiles: *std.ArrayList(Tile)) void { 116 // for each items's side, look for another item with a matching side 117 for (tiles.items) |*tile, tile_index| { 118 sides_loop: for (tile.sides) |_, i| { 119 for (tiles.items) |*otile, otile_index| { 120 if (tile_index == otile_index) continue; 121 122 for (otile.sides) |_, oi| { 123 if (tile.adj[i] == null and otile.adj[oi] == null and couldMatch(tile.sides[i], otile.sides[oi])) { 124 tile.adj[i] = otile_index; 125 otile.adj[oi] = tile_index; 126 continue :sides_loop; 127 } 128 } 129 } 130 } 131 132 if (aoc.debug) { 133 aoc.debugfmt("{}:\nSIDES ", .{tile.id}); 134 for (tile.sides) |side| { 135 for (side) |b| { 136 var c: u8 = if (b == 1) '#' else '.'; 137 aoc.debugfmt("{c}", .{c}); 138 } 139 aoc.debugfmt(" ", .{}); 140 } 141 aoc.debugfmt("\n", .{}); 142 for (tile.adj) |adj, i| { 143 if (tile.adj[i] == null) { 144 aoc.debugfmt("null ", .{}); 145 } else { 146 var adjtile = tiles.items[adj.?]; 147 aoc.debugfmt("{} ", .{adjtile.id}); 148 } 149 } 150 aoc.debugfmt("\n\n", .{}); 151 } 152 } 153} 154 155fn checkSides(tiles: []Tile, grid: []?usize, grid_width: u32, p: aoc.Pos, adj: [4]?usize, sides: [4][10]u1) bool { 156 var rot: u32 = 0; 157 while (rot < 4) : (rot += 1) { 158 const np = p.add(dirs[rot]); 159 aoc.debugfmt("{}\n", .{@intToEnum(aoc.Dir.Name, @intCast(u2, rot))}); 160 aoc.debugfmt("{} {}\n", .{ np.x, np.y }); 161 162 if (np.x < 0 or np.x >= grid_width or np.y < 0 or np.y >= grid_width) { 163 aoc.debugfmt("Side is null\n", .{}); 164 if (adj[rot] == null) { 165 continue; 166 } else { 167 aoc.debugfmt("Side {} should be NULL!\n", .{@intToEnum(aoc.Dir.Name, @intCast(u2, rot))}); 168 return false; 169 } 170 } 171 172 var ti: ?usize = grid[@intCast(u32, np.y * @intCast(i64, grid_width) + np.x)]; 173 if (ti == null) continue; 174 175 var otherside = tiles[ti.?].sides[aoc.Dir.nextCW(rot, 2)]; 176 std.mem.reverse(u1, otherside[0..]); 177 printTileSlice(otherside[0..]); 178 aoc.debugfmt(" vs ", .{}); 179 printTileSlice(sides[rot][0..]); 180 aoc.debugfmt("\n", .{}); 181 182 if (!std.mem.eql(u1, otherside[0..], sides[rot][0..])) return false; 183 } 184 return true; 185} 186 187fn rotateData(newdata: *[]u1, olddata: []u1, width: u32, rot: usize) void { 188 for (olddata) |v, di| { 189 const dy = @divFloor(di, width); 190 const dx = di % width; 191 var nx = dx; 192 var ny = dy; 193 switch (rot) { 194 @enumToInt(aoc.Dir.Name.NORTH) => {}, 195 @enumToInt(aoc.Dir.Name.EAST) => { 196 nx = width - 1 - dy; 197 ny = dx; 198 }, 199 @enumToInt(aoc.Dir.Name.SOUTH) => { 200 nx = width - 1 - dx; 201 ny = width - 1 - dy; 202 }, 203 @enumToInt(aoc.Dir.Name.WEST) => { 204 nx = dy; 205 ny = width - 1 - dx; 206 }, 207 else => {}, 208 } 209 newdata.*[ny * width + nx] = v; 210 } 211} 212 213fn flipData(data: []u1, width: u32, flip: Flip) void { 214 switch (flip) { 215 Flip.HOR => { 216 var y: u32 = 0; 217 while (y < width) : (y += 1) { 218 std.mem.reverse(u1, data[y * width .. (y + 1) * width]); 219 } 220 }, 221 Flip.VERT => { 222 var x: u32 = 0; 223 while (x < width) : (x += 1) { 224 var y: u32 = 0; 225 while (y < width / 2) : (y += 1) { 226 std.mem.swap(u1, &data[x + y * width], &data[x + (width - 1 - y) * width]); 227 } 228 } 229 }, 230 else => {}, 231 } 232} 233 234fn manipulateAndPlace(allocator: std.mem.Allocator, tiles: []Tile, grid: []?usize, grid_width: u32, p: aoc.Pos, tile_index: usize) !void { 235 var tile = &tiles[tile_index]; 236 237 for (dirs) |_, rot| { 238 for (flips) |flip| { 239 // apply manipulation to only sides first for checking 240 var sides = tile.sides; 241 var tmp_sides = tile.sides; 242 var adj = tile.adj; 243 var tmp_adj = tile.adj; 244 switch (flip) { 245 Flip.HOR => { 246 std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.NORTH)][0..]); 247 std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.SOUTH)][0..]); 248 tmp_sides[@enumToInt(aoc.Dir.Name.WEST)] = sides[@enumToInt(aoc.Dir.Name.EAST)]; 249 tmp_sides[@enumToInt(aoc.Dir.Name.EAST)] = sides[@enumToInt(aoc.Dir.Name.WEST)]; 250 std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.EAST)][0..]); 251 std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.WEST)][0..]); 252 std.mem.swap(?usize, &adj[@enumToInt(aoc.Dir.Name.EAST)], &adj[@enumToInt(aoc.Dir.Name.WEST)]); 253 }, 254 Flip.VERT => { 255 std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.WEST)][0..]); 256 std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.EAST)][0..]); 257 tmp_sides[@enumToInt(aoc.Dir.Name.NORTH)] = sides[@enumToInt(aoc.Dir.Name.SOUTH)]; 258 tmp_sides[@enumToInt(aoc.Dir.Name.SOUTH)] = sides[@enumToInt(aoc.Dir.Name.NORTH)]; 259 std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.NORTH)][0..]); 260 std.mem.reverse(u1, tmp_sides[@enumToInt(aoc.Dir.Name.SOUTH)][0..]); 261 std.mem.swap(?usize, &adj[@enumToInt(aoc.Dir.Name.NORTH)], &adj[@enumToInt(aoc.Dir.Name.SOUTH)]); 262 }, 263 else => {}, 264 } 265 sides = tmp_sides; 266 267 for (dirs) |_, i| { 268 tmp_sides[i] = sides[aoc.Dir.nextCCW(i, rot)]; 269 tmp_adj[i] = adj[aoc.Dir.nextCCW(i, rot)]; 270 } 271 272 aoc.debugfmt("\nROT: {}\n", .{rot}); 273 aoc.debugfmt("FLIP: {}\n", .{flip}); 274 aoc.debugfmt("SIDES: \n", .{}); 275 for (tmp_sides) |side| { 276 printTileSlice(side[0..]); 277 aoc.debugfmt("\n", .{}); 278 } 279 const valid = checkSides(tiles, grid, grid_width, p, tmp_adj, tmp_sides); 280 281 // now apply manipulations to image data 282 if (valid) { 283 // for (tile.sides) |side| { 284 // printTileSlice(side[0..]); 285 // aoc.debugfmt("\n", .{}); 286 // } 287 aoc.debugfmt("{} {} {}\n", .{ tile.id, flip, rot }); 288 printTile(tile.data[0..], tile_width); 289 aoc.debugfmt("\n", .{}); 290 tile.sides = tmp_sides; 291 tile.pos = p; 292 tile.adj = tmp_adj; 293 grid[@intCast(u32, p.y * @intCast(i64, grid_width) + p.x)] = tile_index; 294 295 flipData(tile.data[0..], tile_width, flip); 296 297 var newdata = try allocator.alloc(u1, tile_width * tile_width); 298 defer allocator.free(newdata); 299 300 rotateData(&newdata, tile.data[0..], tile_width, rot); 301 std.mem.copy(u1, tile.data[0..], newdata); 302 return; 303 } 304 } 305 } 306 return aoc.Error.InvalidInput; 307} 308 309fn part1(allocator: std.mem.Allocator, input: []u8, args: [][]u8) !?[]u8 { 310 _ = args; 311 312 var tiles = std.ArrayList(Tile).init(allocator); 313 defer tiles.deinit(); 314 315 try parseTiles(&tiles, input); 316 317 matchTiles(&tiles); 318 319 var answer: u64 = 1; 320 for (tiles.items) |tile| { 321 var unknown: u32 = 0; 322 for (tile.adj) |v| { 323 unknown += @boolToInt(v == null); 324 } 325 if (unknown == 2) answer *= tile.id; 326 } 327 328 return try std.fmt.allocPrint(allocator, "{}", .{answer}); 329} 330 331fn part2(allocator: std.mem.Allocator, input: []u8, args: [][]u8) !?[]u8 { 332 _ = args; 333 334 var tiles = std.ArrayList(Tile).init(allocator); 335 defer tiles.deinit(); 336 337 try parseTiles(&tiles, input); 338 339 matchTiles(&tiles); 340 341 var grid = try allocator.alloc(?usize, tiles.items.len); 342 std.mem.set(?usize, grid, null); 343 defer allocator.free(grid); 344 var grid_width = @intCast(u32, std.math.sqrt(tiles.items.len)); 345 346 var left = std.ArrayList(usize).init(allocator); 347 defer left.deinit(); 348 349 for (tiles.items) |_, tile_index| { 350 try left.append(tile_index); 351 } 352 353 // add a corner as first item to add onto 354 for (tiles.items) |tile, tile_index| { 355 var unknown: u32 = 0; 356 for (tile.adj) |v| { 357 unknown += @boolToInt(v == null); 358 } 359 if (unknown == 2) { 360 try manipulateAndPlace(allocator, tiles.items, grid, grid_width, aoc.Pos{ .x = 0, .y = 0 }, tile_index); 361 _ = left.swapRemove(tile_index); 362 break; 363 } 364 } 365 366 // find adjacents and manipulate until they fit 367 next_tile: while (left.items.len > 0) { 368 if (aoc.debug) printGrid(tiles.items, grid, grid_width); 369 370 for (grid) |_, gi| { 371 aoc.debugfmt("POS {}\n", .{gi}); 372 const tile_index: ?usize = grid[gi] orelse continue; 373 aoc.debugfmt("POS {} YES\n", .{gi}); 374 375 var placed = &tiles.items[tile_index.?]; 376 for (placed.adj) |want, dir| { 377 if (want == null) continue; 378 // aoc.debugfmt("LOOKING FOR {}\n", .{want.?}); 379 // for (left.items) |item| { 380 // aoc.debugfmt("> {}\n", .{item}); 381 // } 382 if (std.mem.indexOfScalar(usize, left.items, want.?)) |left_index| { 383 const new_index = left.items[left_index]; 384 const new_tile = tiles.items[new_index]; 385 var npos = placed.pos.add(dirs[dir]); 386 aoc.debugfmt("TRYING TO PLACE {} NEW AT: {} {}\n", .{ new_tile.id, npos.x, npos.y }); 387 388 try manipulateAndPlace(allocator, tiles.items, grid, grid_width, npos, new_index); 389 390 _ = left.swapRemove(left_index); 391 continue :next_tile; 392 } 393 } 394 } 395 return aoc.Error.InvalidInput; 396 } 397 398 if (aoc.debug) printGrid(tiles.items, grid, grid_width); 399 400 // convert grid to image 401 var image = try allocator.alloc(u1, grid.len * (tile_width - 2) * (tile_width - 2)); 402 defer allocator.free(image); 403 404 const image_width = grid_width * (tile_width - 2); 405 var y: u32 = 0; 406 while (y < grid_width) : (y += 1) { 407 var x: u32 = 0; 408 while (x < grid_width) : (x += 1) { 409 var tile = tiles.items[grid[y * grid_width + x].?]; 410 var doff = (y * grid_width * (tile_width - 2) + x) * (tile_width - 2); 411 var dy: u32 = 1; 412 while (dy < tile_width - 1) : (dy += 1) { 413 var dx: u32 = 1; 414 while (dx < tile_width - 1) : (dx += 1) { 415 image[doff + (dy - 1) * image_width + (dx - 1)] = tile.data[dy * tile_width + dx]; 416 } 417 } 418 } 419 } 420 421 y = 0; 422 while (y < grid_width * (tile_width - 2)) : (y += 1) { 423 printTileSlice(image[y * grid_width * (tile_width - 2) .. (y + 1) * grid_width * (tile_width - 2)]); 424 aoc.debugfmt("\n", .{}); 425 } 426 427 // rotate image till we find a sea monster 428 var image_copy = try allocator.dupe(u1, image); 429 defer allocator.free(image_copy); 430 431 var rot: usize = 0; 432 while (rot < 4) : (rot += 1) { 433 for (flips) |flip| { 434 flipData(image, image_width, flip); 435 rotateData(&image_copy, image, image_width, rot); 436 437 y = 0; 438 var count: u32 = 0; 439 while (y < image_width - seamonster.len) : (y += 1) { 440 var x: u32 = 0; 441 check_next: while (x < image_width - seamonster[0].len) : (x += 1) { 442 var sy: u32 = 0; 443 while (sy < seamonster.len) : (sy += 1) { 444 var sx: u32 = 0; 445 while (sx < seamonster[0].len) : (sx += 1) { 446 if (seamonster[sy][sx] == '#' and image_copy[(y + sy) * image_width + (x + sx)] != 1) 447 continue :check_next; 448 } 449 } 450 count += 1; 451 } 452 } 453 454 if (count > 0) { 455 var image_count = std.mem.count(u1, image_copy, ([_]u1{1})[0..]); 456 var seamonster_count: usize = 0; 457 var sy: u32 = 0; 458 while (sy < seamonster.len) : (sy += 1) { 459 seamonster_count += std.mem.count(u8, seamonster[sy], "#"); 460 } 461 const answer = image_count - count * seamonster_count; 462 return try std.fmt.allocPrint(allocator, "{}", .{answer}); 463 } 464 } 465 } 466 467 return null; 468} 469 470pub const main = aoc.main(part1, part2, .{ "54755174472007", "1692" });