aoc-2020-zig

Advent of Code 2020 Solutions in Zig
git clone https://git.sinitax.com/sinitax/aoc-2020-zig
Log | Files | Refs | README | sfeed.txt

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" });