You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
417 lines
12 KiB
417 lines
12 KiB
const std = @import("std");
|
|
|
|
fn Order(comptime T: type) type {
|
|
return struct {
|
|
fn order(context: void, lhs: T, rhs: T) std.math.Order {
|
|
_ = context;
|
|
return std.math.order(lhs, rhs);
|
|
}
|
|
};
|
|
}
|
|
|
|
fn StackList(comptime T: type, comptime capacity_type: type, comptime capacity: capacity_type) type {
|
|
return struct {
|
|
const Self = @This();
|
|
mem: [capacity]T,
|
|
length: capacity_type,
|
|
|
|
fn add(self: *Self, value: T) void {
|
|
self.mem[self.length] = value;
|
|
self.length += 1;
|
|
}
|
|
|
|
fn addIfNotNull(self: *Self, nullable_value: ?T) void {
|
|
if (nullable_value) |value| {
|
|
self.add(value);
|
|
}
|
|
}
|
|
|
|
fn has(self: *const Self, needle: T) bool {
|
|
for (0..self.length) |i| {
|
|
if (self.mem[i] == needle) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
fn addUnique(self: *Self, value: T) void {
|
|
if (!self.has(value)) {
|
|
self.add(value);
|
|
}
|
|
}
|
|
|
|
fn getMutableSlice(self: *Self) []T {
|
|
return (&self.mem)[0..self.length];
|
|
}
|
|
|
|
fn getSlice(self: *const Self) []const T {
|
|
return self.mem[0..self.length];
|
|
}
|
|
|
|
fn init() Self {
|
|
return Self{
|
|
.mem = undefined,
|
|
.length = 0,
|
|
};
|
|
}
|
|
};
|
|
}
|
|
|
|
fn RingQueueWrapper(comptime T: type, comptime size: usize) type {
|
|
return struct {
|
|
const Self = @This();
|
|
mem: *[size]T,
|
|
first: usize,
|
|
next: usize,
|
|
|
|
fn add(self: *Self, value: T) void {
|
|
std.debug.assert(self.next - self.first < size);
|
|
self.mem.*[self.next % size] = value;
|
|
self.next += 1;
|
|
}
|
|
|
|
fn take(self: *Self) T {
|
|
const result = self.mem.*[self.first % size];
|
|
self.first += 1;
|
|
return result;
|
|
}
|
|
|
|
fn isEmpty(self: *const Self) bool {
|
|
return self.next == self.first;
|
|
}
|
|
|
|
fn init(mem: *[size]T) Self {
|
|
return Self{
|
|
.mem = mem,
|
|
.first = 0,
|
|
.next = 0,
|
|
};
|
|
}
|
|
};
|
|
}
|
|
|
|
fn readNumber(comptime T: type, line: []const u8, index: *usize) T {
|
|
var result: T = 0;
|
|
while (index.* < line.len and line[index.*] == ' ') : (index.* += 1) {}
|
|
|
|
var is_negative = false;
|
|
if (index.* < line.len and line[index.*] == '-') {
|
|
is_negative = true;
|
|
index.* += 1;
|
|
}
|
|
|
|
std.debug.assert(index.* < line.len);
|
|
|
|
while (index.* < line.len) : (index.* += 1) {
|
|
const char = line[index.*];
|
|
switch (char) {
|
|
'0'...'9' => {
|
|
result = result * 10 + (char - '0');
|
|
},
|
|
else => {
|
|
break;
|
|
},
|
|
}
|
|
}
|
|
|
|
return if (is_negative) (0 - result) else result;
|
|
}
|
|
|
|
fn readHexNumber(comptime T: type, line: []const u8, index: *usize) T {
|
|
var result: T = 0;
|
|
while (index.* < line.len and line[index.*] == ' ') : (index.* += 1) {}
|
|
|
|
var is_negative = false;
|
|
if (index.* < line.len and line[index.*] == '-') {
|
|
is_negative = true;
|
|
index.* += 1;
|
|
}
|
|
|
|
std.debug.assert(index.* < line.len);
|
|
|
|
while (index.* < line.len) : (index.* += 1) {
|
|
const char = line[index.*];
|
|
switch (char) {
|
|
'0'...'9' => {
|
|
result = result * 16 + (char - '0');
|
|
},
|
|
'a'...'f' => {
|
|
result = result * 16 + 10 + (char - 'a');
|
|
},
|
|
'A'...'F' => {
|
|
result = result * 16 + 10 + (char - 'A');
|
|
},
|
|
else => {
|
|
break;
|
|
},
|
|
}
|
|
}
|
|
|
|
return if (is_negative) (0 - result) else result;
|
|
}
|
|
|
|
fn Coordinates(comptime CoordinateType: type) type {
|
|
return struct {
|
|
const Self = @This();
|
|
x: CoordinateType,
|
|
y: CoordinateType,
|
|
|
|
fn isSameX(self: *const Self, other: Self) bool {
|
|
return self.x == other.x;
|
|
}
|
|
|
|
fn approachX(self: *Self, other: Self) void {
|
|
if (self.x < other.x) {
|
|
self.x += 1;
|
|
} else {
|
|
self.x -= 1;
|
|
}
|
|
}
|
|
|
|
fn isSameY(self: *const Self, other: Self) bool {
|
|
return self.y == other.y;
|
|
}
|
|
|
|
fn approachY(self: *Self, other: Self) void {
|
|
if (self.y < other.y) {
|
|
self.y += 1;
|
|
} else {
|
|
self.y -= 1;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
fn GenericTerrain(comptime CoordinateType: type, comptime height: CoordinateType, comptime width: CoordinateType, comptime CellType: type) type {
|
|
return struct {
|
|
const Self = @This();
|
|
coordinates: Coordinates(CoordinateType),
|
|
cells: [height][width]CellType,
|
|
fn initZeroes() Self {
|
|
return .{
|
|
.coordinates = .{
|
|
.x = height / 2,
|
|
.y = width / 2,
|
|
},
|
|
.cells = std.mem.zeroes([height][width]CellType),
|
|
};
|
|
}
|
|
|
|
fn getCurrentCell(self: *const Self) CellType {
|
|
return self.cells[self.coordinates.x][self.coordinates.y];
|
|
}
|
|
|
|
fn setCurrentCell(self: *Self, value: CellType) void {
|
|
self.cells[self.coordinates.x][self.coordinates.y] = value;
|
|
}
|
|
};
|
|
}
|
|
|
|
const CellStatus = enum(u2) {
|
|
Default = 0,
|
|
NotDug = 1,
|
|
Dug = 2,
|
|
};
|
|
|
|
const TerrainCoordinate = u16;
|
|
|
|
const Terrain = GenericTerrain(TerrainCoordinate, 2048, 2048, CellStatus);
|
|
|
|
const TerrainCoordinates = Coordinates(TerrainCoordinate);
|
|
|
|
const Direction = enum(u8) {
|
|
Right = 0,
|
|
Down = 1,
|
|
Left = 2,
|
|
Up = 3,
|
|
};
|
|
|
|
const RawCoordinate = u32;
|
|
|
|
const RawCommand = packed struct(u32) {
|
|
direction: Direction,
|
|
length: u24,
|
|
};
|
|
|
|
const RawCoordinates = Coordinates(RawCoordinate);
|
|
|
|
fn floodTerrain(terrain: *Terrain) void {
|
|
var queue_mem: [65_536]TerrainCoordinates = undefined;
|
|
var tasks = RingQueueWrapper(TerrainCoordinates, queue_mem.len).init(&queue_mem);
|
|
|
|
tasks.add(.{ .x = 0, .y = 0 });
|
|
|
|
while (!tasks.isEmpty()) {
|
|
const task = tasks.take();
|
|
terrain.coordinates = task;
|
|
//std.debug.print("Processing task for {d},{d} (current status: {any})\n", .{ x, y, terrain.*.cells[x][y] });
|
|
if (terrain.getCurrentCell() == .Default) {
|
|
terrain.setCurrentCell(.NotDug);
|
|
//std.debug.print("Marked {d},{d} as NotDug\n", .{ x, y });
|
|
|
|
const x = task.x;
|
|
const y = task.y;
|
|
if (x > 0) {
|
|
tasks.add(.{ .x = x - 1, .y = y });
|
|
}
|
|
|
|
if (x + 1 < terrain.*.cells.len) {
|
|
tasks.add(.{ .x = x + 1, .y = y });
|
|
}
|
|
|
|
if (y > 0) {
|
|
tasks.add(.{ .x = x, .y = y - 1 });
|
|
}
|
|
|
|
if (y + 1 < terrain.*.cells[x].len) {
|
|
tasks.add(.{ .x = x, .y = y + 1 });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn getTargetRawCoordinates(coordinates: RawCoordinates, command: RawCommand) RawCoordinates {
|
|
return RawCoordinates{
|
|
.x = switch (command.direction) {
|
|
.Right => coordinates.x + command.length,
|
|
.Left => coordinates.x - command.length,
|
|
.Up, .Down => coordinates.x,
|
|
},
|
|
.y = switch (command.direction) {
|
|
.Down => coordinates.y + command.length,
|
|
.Up => coordinates.y - command.length,
|
|
.Left, .Right => coordinates.y,
|
|
},
|
|
};
|
|
}
|
|
|
|
fn getCoordinate(known_values: []const RawCoordinate, raw_coordinate: RawCoordinate) TerrainCoordinate {
|
|
const maybe_index = std.sort.binarySearch(RawCoordinate, raw_coordinate, known_values, {}, Order(RawCoordinate).order);
|
|
return @as(TerrainCoordinate, @intCast(maybe_index.?));
|
|
}
|
|
|
|
fn solveForCommands(raw_commands: []const RawCommand) usize {
|
|
const start_value = 1 << 23;
|
|
var raw_coordinates = RawCoordinates{
|
|
.x = start_value,
|
|
.y = start_value,
|
|
};
|
|
var known_values = StackList(RawCoordinate, u16, 2048).init();
|
|
known_values.addUnique(0);
|
|
known_values.addUnique(start_value - 1);
|
|
known_values.addUnique(start_value);
|
|
known_values.addUnique(start_value + 1);
|
|
known_values.addUnique(std.math.maxInt(RawCoordinate));
|
|
for (raw_commands) |raw_command| {
|
|
raw_coordinates = getTargetRawCoordinates(raw_coordinates, raw_command);
|
|
known_values.addUnique(raw_coordinates.x - 1);
|
|
known_values.addUnique(raw_coordinates.x);
|
|
known_values.addUnique(raw_coordinates.x + 1);
|
|
known_values.addUnique(raw_coordinates.y - 1);
|
|
known_values.addUnique(raw_coordinates.y);
|
|
known_values.addUnique(raw_coordinates.y + 1);
|
|
}
|
|
|
|
std.sort.heap(RawCoordinate, known_values.getMutableSlice(), {}, std.sort.asc(RawCoordinate));
|
|
|
|
var known_values_slice = known_values.getSlice();
|
|
|
|
raw_coordinates = RawCoordinates{
|
|
.x = start_value,
|
|
.y = start_value,
|
|
};
|
|
|
|
var terrain = Terrain.initZeroes();
|
|
terrain.coordinates = .{
|
|
.x = getCoordinate(known_values_slice, start_value),
|
|
.y = getCoordinate(known_values_slice, start_value),
|
|
};
|
|
|
|
for (raw_commands) |raw_command| {
|
|
raw_coordinates = getTargetRawCoordinates(raw_coordinates, raw_command);
|
|
const target = TerrainCoordinates{
|
|
.x = getCoordinate(known_values_slice, raw_coordinates.x),
|
|
.y = getCoordinate(known_values_slice, raw_coordinates.y),
|
|
};
|
|
|
|
while (!terrain.coordinates.isSameX(target)) {
|
|
terrain.coordinates.approachX(target);
|
|
terrain.setCurrentCell(.Dug);
|
|
}
|
|
|
|
while (!terrain.coordinates.isSameY(target)) {
|
|
terrain.coordinates.approachY(target);
|
|
terrain.setCurrentCell(.Dug);
|
|
}
|
|
}
|
|
|
|
floodTerrain(&terrain);
|
|
|
|
const cells = terrain.cells;
|
|
var result: u64 = 0;
|
|
|
|
for (0..cells.len) |x| {
|
|
for (0..cells[x].len) |y| {
|
|
if (cells[x][y] != .NotDug) {
|
|
// Doesn't really matter whether we take x..x+1 or x-1..x here, because boundary blocks will always have size 1
|
|
result += @as(u64, known_values_slice[x + 1] - known_values_slice[x]) * @as(u64, known_values_slice[y + 1] - known_values_slice[y]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
fn parseCommand(command: []const u8) RawCommand {
|
|
var index: usize = 2;
|
|
_ = readNumber(u8, command, &index);
|
|
index += 3;
|
|
const value = readHexNumber(u24, command, &index);
|
|
const direction: Direction = switch (value % 16) {
|
|
0 => .Right,
|
|
1 => .Down,
|
|
2 => .Left,
|
|
3 => .Up,
|
|
else => unreachable,
|
|
};
|
|
|
|
return .{
|
|
.length = value / 16,
|
|
.direction = direction,
|
|
};
|
|
}
|
|
|
|
fn solveAll(reader: anytype) !usize {
|
|
var result: usize = 0;
|
|
while (true) {
|
|
var commands = StackList(RawCommand, usize, 1024).init();
|
|
|
|
var empty_line_reached = false;
|
|
var line_buffer: [1000]u8 = undefined;
|
|
while (try reader.readUntilDelimiterOrEof(&line_buffer, '\n')) |line| {
|
|
if (line.len == 0) {
|
|
empty_line_reached = true;
|
|
break;
|
|
}
|
|
commands.add(parseCommand(line));
|
|
}
|
|
|
|
result += solveForCommands(commands.getSlice());
|
|
|
|
if (!empty_line_reached) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn main() !void {
|
|
const stdout = std.io.getStdOut().writer();
|
|
|
|
const raw_in = std.io.getStdIn();
|
|
var buffered_reader = std.io.bufferedReader(raw_in.reader());
|
|
var reader = buffered_reader.reader();
|
|
const result = try solveAll(&reader);
|
|
try stdout.print("{d}\n", .{result});
|
|
}
|
|
|