const std = @import("std"); pub const title = "Day 09: Movie Theater"; pub fn run(allocator: std.mem.Allocator) !void { const input = @embedFile("./input/day09.txt"); //const input = // \\7,1 // \\11,1 // \\11,7 // \\9,7 // \\9,5 // \\2,5 // \\2,3 // \\7,3 // ; var lines = std.mem.tokenizeScalar(u8, input, '\n'); var tiles: std.ArrayList(Tile) = .empty; defer tiles.deinit(allocator); std.debug.print("parsing tiles...\n", .{}); var index: usize = 0; while (lines.next()) |line| { defer index += 1; var coordinates = std.mem.tokenizeScalar(u8, line, ','); const x = blk: { const s = coordinates.next() orelse continue; break :blk try std.fmt.parseUnsigned(u64, s, 10); }; const y = blk: { const s = coordinates.next() orelse continue; break :blk try std.fmt.parseUnsigned(u64, s, 10); }; try tiles.append(allocator, .{ .index = index, .pos = .{ x, y } }); } std.debug.print("calculating edges...\n", .{}); var edges: std.ArrayList(Edge) = .empty; defer edges.deinit(allocator); for (tiles.items, 0..) |tile, i| { const a = tile; const b = if (i == tiles.items.len - 1) tiles.items[0] else tiles.items[i + 1]; try edges.append(allocator, Edge.fromTiles(a, b)); } std.debug.print("finding largest rectangle...\n", .{}); var largest_area: usize = 0; var largest_rectangle: Rectangle = undefined; for (tiles.items, 0..) |first_tile, i| { inner: for (tiles.items[i + 1..]) |second_tile| { const rectangle = first_tile.rectangle(second_tile); std.debug.print("checking rectangle {f}...\n", .{rectangle}); const has_inside = rectangle.width() >= 3 and rectangle.height() >= 3; if (has_inside) { const one: @Vector(2, u64) = @splat(1); const top_left = rectangle.topLeft() + one; const bottom_right = rectangle.bottomRight() - one; edge_loop: for (edges.items) |edge| { if (edge.direction == .horizontal) { if (edge.a.pos[1] < top_left[1] or edge.a.pos[1] > bottom_right[1]) { continue :edge_loop; } const start, const end = if (edge.a.pos[0] < edge.b.pos[0] + 1) .{ edge.a.pos[0], edge.b.pos[0] } else .{ edge.b.pos[0], edge.a.pos[0] + 1 }; for (start..end) |ex| { const edge_tile: @Vector(2, u64) = .{ ex, edge.a.pos[1] }; if (top_left[0] <= edge_tile[0] and edge_tile[0] <= bottom_right[0]) { std.debug.print("rectangle {f} hits edge {f}\n", .{rectangle, edge}); continue :inner; } } } else { if (edge.a.pos[0] < top_left[0] or edge.a.pos[0] > bottom_right[0]) { continue :edge_loop; } const start, const end = if (edge.a.pos[1] < edge.b.pos[1] + 1) .{ edge.a.pos[1], edge.b.pos[1] } else .{ edge.b.pos[1], edge.a.pos[1] + 1 }; for (start..end) |ey| { const edge_tile: @Vector(2, u64) = .{ edge.a.pos[0], ey }; if (top_left[1] <= edge_tile[1] and edge_tile[1] <= bottom_right[1]) { std.debug.print("rectangle {f} hits edge {f}\n", .{rectangle, edge}); continue :inner; } } } } } const area = rectangle.area(); if (area > largest_area) { largest_area = area; largest_rectangle = rectangle; } } } std.debug.print("rectangle = {f}\n", .{largest_rectangle}); std.debug.print("top_left = {any}, bottom_right = {any}\n", .{largest_rectangle.topLeft(), largest_rectangle.bottomRight()}); var buffer: [64]u8 = undefined; var stdout_writer = std.fs.File.stdout().writer(&buffer); const stdout = &stdout_writer.interface; try stdout.print("{d}\n", .{largest_area}); try stdout.flush(); } const Tile = struct { index: usize, pos: @Vector(2, u64), const Self = @This(); pub fn format(self: Self, w: *std.io.Writer) std.io.Writer.Error!void { try w.print("{d},{d}", .{self.pos[0], self.pos[1]}); } pub fn rectangle(self: Self, other_corner: Self) Rectangle { return Rectangle.fromTiles(self, other_corner); } }; const Edge = struct { a: Tile, b: Tile, direction: Direction, const Self = @This(); const Direction = enum { horizontal, vertical, }; pub fn fromTiles(a: Tile, b: Tile) Self { const direction: Direction = if (a.pos[0] == b.pos[0]) .vertical else if (a.pos[1] == b.pos[1]) .horizontal else unreachable; return .{ .a = a, .b = b, .direction = direction, }; } pub fn format(self: Self, w: *std.io.Writer) std.io.Writer.Error!void { try w.print("{f} - {f}", .{self.a, self.b}); } }; /// 0,0 1,0 /// a ---- c /// | | /// | | /// d ---- b /// 0,1 1,1 const Rectangle = struct { a: Tile, b: Tile, c: @Vector(2, u64), d: @Vector(2, u64), const Self = @This(); pub fn fromTiles(a: Tile, b: Tile) Self { const cx = if (a.pos[0] <= b.pos[0]) b.pos[0] else a.pos[0]; const cy = if (a.pos[1] >= b.pos[1]) a.pos[1] else b.pos[1]; const dx = if (a.pos[0] <= b.pos[0]) a.pos[0] else b.pos[0]; const dy = if (a.pos[1] >= b.pos[1]) b.pos[1] else a.pos[1]; return .{ .a = a, .b = b, .c = .{ cx, cy }, .d = .{ dx, dy }, }; } pub fn format(self: Self, w: *std.io.Writer) std.io.Writer.Error!void { try w.print("{d} to {d} ({f} - {d},{d} - {f} - {d},{d})", .{self.a.index, self.b.index, self.a, self.c[0], self.c[1], self.b, self.d[0], self.d[1]}); } pub fn topLeft(self: Self) @Vector(2, u64) { const verts = [_]@Vector(2, u64){ self.c, self.b.pos, self.d }; var top_left: @Vector(2, u64) = self.a.pos; for (verts) |vert| { const cond = vert <= top_left; if (@reduce(.And, cond)) { top_left = vert; } } return top_left; } pub fn bottomRight(self: Self) @Vector(2, u64) { const verts = [_]@Vector(2, u64){ self.d, self.a.pos, self.c }; var bottom_right: @Vector(2, u64) = self.b.pos; for (verts) |vert| { const cond = vert >= bottom_right; if (@reduce(.And, cond)) { bottom_right = vert; } } return bottom_right; } pub fn width(self: Self) u64 { const cond = self.a.pos > self.b.pos; const high = @select(u64, cond, self.a.pos, self.b.pos); const low = @select(u64, !cond, self.a.pos, self.b.pos); const diff = high - low; return diff[0] + 1; } pub fn height(self: Self) u64 { const cond = self.a.pos > self.b.pos; const high = @select(u64, cond, self.a.pos, self.b.pos); const low = @select(u64, !cond, self.a.pos, self.b.pos); const diff = high - low; return diff[1] + 1; } pub fn area(self: Self) u64 { const cond = self.a.pos > self.b.pos; const high = @select(u64, cond, self.a.pos, self.b.pos); const low = @select(u64, !cond, self.a.pos, self.b.pos); const diff = high - low; const one: @Vector(2, u64) = @splat(1); return @reduce(.Mul, diff + one); } };