2025-01-13 19:05:21 -08:00

216 lines
8.8 KiB
Zig

const std = @import("std");
const print = std.debug.print;
const util = @import("util.zig");
const expect = std.testing.expect;
pub fn main() !void {
// const response = try part_one(false);
// print("{}\n", .{response});
try part_two();
}
const Point = struct { x: u32, y: u32 };
const Velocity = struct { x: i32, y: i32 };
const Robot = struct {
position: Point,
velocity: Velocity,
pub fn move(self: *Robot, width: usize, height: usize) void {
// I wish it were possible to put this on multiple lines, but it appears to be a syntax error
const new_x: u32 = @intCast(if (self.velocity.x >= 0) (self.position.x + util.magnitude(self.velocity.x)) % width else (self.position.x + (width - util.magnitude(self.velocity.x))) % width);
const new_y: u32 = @intCast(if (self.velocity.y >= 0) (self.position.y + util.magnitude(self.velocity.y)) % height else (self.position.y + (height - util.magnitude(self.velocity.y))) % height);
self.position = Point{ .x = new_x, .y = new_y };
}
pub fn move_iterated(self: *Robot, width: usize, height: usize, move_count: u32) void {
for (0..move_count) |_| {
// Technically inefficient in that it does the modulo operation on every movement, but the alternative would
// be having to deal with Zig's bullshit integer limits, so... :shrug:
self.move(width, height);
}
}
pub fn from_line(line: []const u8) !Robot {
// print("DEBUG - parsing line {s}\n", .{line});
const start_pos_x: usize = 2;
const end_pos_x: usize = std.mem.indexOf(u8, line, ",").?;
// print("DEBUG - end_pos_x is {}\n", .{end_pos_x});
const start_pos_y: usize = end_pos_x + 1;
const end_pos_y: usize = std.mem.indexOf(u8, line, " ").?;
// print("DEBUG - end_pos_y is {}\n", .{end_pos_y});
const start_vel_x: usize = std.mem.indexOf(u8, line, "v").? + 2;
// print("DEBUG - start_vel_x is {}\n", .{start_vel_x});
const end_vel_x: usize = std.mem.indexOf(u8, line[end_pos_y..], ",").? + end_pos_y;
// print("DEBUG - end_vel_x is {}\n", .{end_vel_x});
const start_vel_y: usize = end_vel_x + 1;
const end_vel_y: usize = line.len;
const x = try std.fmt.parseInt(u32, line[start_pos_x..end_pos_x], 10);
const y = try std.fmt.parseInt(u32, line[start_pos_y..end_pos_y], 10);
const vel_x = try std.fmt.parseInt(i32, line[start_vel_x..end_vel_x], 10);
const vel_y = try std.fmt.parseInt(i32, line[start_vel_y..end_vel_y], 10);
print("DEBUG - x, y, vel_x, vel_y are {}, {}, {}, {}\n", .{ x, y, vel_x, vel_y });
return Robot{ .position = Point{ .x = x, .y = y }, .velocity = Velocity{ .x = vel_x, .y = vel_y } };
}
};
fn part_one(is_test_case: bool) anyerror!u32 {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const input_file = try util.getInputFile("14", is_test_case);
const data = try util.readAllInputWithAllocator(input_file, allocator);
defer allocator.free(data);
var it = std.mem.splitScalar(u8, data, '\n');
var robots = std.ArrayList(Robot).init(allocator);
defer robots.deinit();
const height: u32 = if (is_test_case) 7 else 103;
const width: u32 = if (is_test_case) 11 else 101;
while (it.next()) |line| {
try robots.append(try Robot.from_line(line));
}
var first_quadrant_count: u32 = 0;
var second_quadrant_count: u32 = 0;
var third_quadrant_count: u32 = 0;
var fourth_quadrant_count: u32 = 0;
// I don't know why this has to be `*robot` - see question [here](https://ziggit.dev/t/how-to-get-a-non-const-pointer-to-a-structs-field-from-within-a-function-of-the-struct/7639/13?u=scubbo)
for (robots.items) |*robot| {
robot.move_iterated(width, height, 100);
print("DEBUG - robot in position {}/{}\n", .{ robot.position.x, robot.position.y });
// Can't use a `switch`, here, as `width/2` and `height/2` are not comptime expressions
if (robot.position.x < width / 2 and robot.position.y < height / 2) {
first_quadrant_count += 1;
}
if (robot.position.x > width / 2 and robot.position.y < height / 2) {
second_quadrant_count += 1;
}
if (robot.position.x < width / 2 and robot.position.y > height / 2) {
third_quadrant_count += 1;
}
if (robot.position.x > width / 2 and robot.position.y > height / 2) {
fourth_quadrant_count += 1;
}
}
print("DEBUG - quadrant counts are {}, {}, {}, {}\n", .{ first_quadrant_count, second_quadrant_count, third_quadrant_count, fourth_quadrant_count });
return first_quadrant_count * second_quadrant_count * third_quadrant_count * fourth_quadrant_count;
}
// ...wtf!? How on earth are you meant to do this without visual inspection?
// ...ok, giving up on this. I stepped through the output (run `zig run solutions 14.zig > /tmp/output 2>&1`, then open in vim and do:
// `qt/Iteration<enter>ztqq` (to record a macro on key `t` which will bring the next iteration to the top of the screen), then repeatedly do
// `@t`) but can't see anything resembling a christmas tree.
// Iterations 12 and 35 both show some structure, but nothing like a tree (and those are both rejected as answers)
fn part_two() anyerror!void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const input_file = try util.getInputFile("14", false);
const data = try util.readAllInputWithAllocator(input_file, allocator);
defer allocator.free(data);
var it = std.mem.splitScalar(u8, data, '\n');
var robots = std.ArrayList(Robot).init(allocator);
defer robots.deinit();
const height: u32 = if (false) 7 else 103;
const width: u32 = if (false) 11 else 101;
while (it.next()) |line| {
try robots.append(try Robot.from_line(line));
}
for (0..100) |iteration| {
for (robots.items) |*robot| {
robot.move(width, height);
}
var lines = std.ArrayList([]u32).init(allocator);
for (0..height) |_| {
var line = std.ArrayList(u32).init(allocator);
for (0..width) |_| {
try line.append(0);
}
try lines.append(try line.toOwnedSlice());
}
const lines_owned = try lines.toOwnedSlice();
for (robots.items) |*robot| {
lines_owned[robot.position.y][robot.position.x] += 1;
}
// Print out display
print("Iteration count: {}\n", .{iteration + 1});
for (lines_owned) |line| {
for (line) |val| {
if (val > 0) {
print("X", .{});
} else {
print(" ", .{});
}
}
print("|\n", .{}); // I'm cheating by putting this trailing pipe to stop Vim from giving ugly red highlights on trailing whitespace
}
print("\n\n", .{});
for (lines_owned) |line| {
allocator.free(line);
}
allocator.free(lines_owned);
std.time.sleep(1000000);
}
}
test "robot movement" {
var robot = Robot{ .position = Point{ .x = 2, .y = 4 }, .velocity = Velocity{ .x = 2, .y = -3 } };
robot.move(11, 7);
print("DEBUG - robot position after one iteration is {}/{}\n", .{ robot.position.x, robot.position.y });
try expect(robot.position.x == 4);
try expect(robot.position.y == 1);
robot.move(11, 7);
print("DEBUG - robot position after two iterations is {}/{}\n", .{ robot.position.x, robot.position.y });
try expect(robot.position.x == 6);
try expect(robot.position.y == 5);
robot.move(11, 7);
print("DEBUG - robot position after three iterations is {}/{}\n", .{ robot.position.x, robot.position.y });
try expect(robot.position.x == 8);
try expect(robot.position.y == 2);
robot.move(11, 7);
print("DEBUG - robot position after four iterations is {}/{}\n", .{ robot.position.x, robot.position.y });
try expect(robot.position.x == 10);
try expect(robot.position.y == 6);
robot.move(11, 7);
print("DEBUG - robot position after five iterations is {}/{}\n", .{ robot.position.x, robot.position.y });
try expect(robot.position.x == 1);
try expect(robot.position.y == 3);
}
test "iterated movement" {
var robot = Robot{ .position = Point{ .x = 2, .y = 4 }, .velocity = Velocity{ .x = 2, .y = -3 } };
robot.move_iterated(11, 7, 3);
print("DEBUG - robot position after three iterations (in one go) is {}/{}\n", .{ robot.position.x, robot.position.y });
try expect(robot.position.x == 8);
try expect(robot.position.y == 2);
}
test "part_one" {
const part_one_response = try part_one(true);
print("DEBUG - part_one_response is {}\n", .{part_one_response});
try expect(part_one_response == 12);
}