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.
328 lines
8.4 KiB
328 lines
8.4 KiB
const std = @import("std");
|
|
|
|
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: *Self, needle: T) bool {
|
|
for (0..self.length) |i| {
|
|
if (self.mem[i] == needle) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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 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, 1024, 1024, CellStatus);
|
|
|
|
const TerrainCoordinates = Coordinates(TerrainCoordinate);
|
|
|
|
const Direction = enum(u8) {
|
|
Right = 0,
|
|
Down = 1,
|
|
Left = 2,
|
|
Up = 3,
|
|
};
|
|
|
|
const Command = packed struct(u32) {
|
|
direction: Direction,
|
|
_: u8 = 0,
|
|
length: u16,
|
|
};
|
|
|
|
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 solveForTerrain(terrain: *Terrain) usize {
|
|
floodTerrain(terrain);
|
|
|
|
const cells = terrain.*.cells;
|
|
var result: usize = 0;
|
|
|
|
for (0..cells.len) |x| {
|
|
for (0..cells.len) |y| {
|
|
if (cells[x][y] != .NotDug) {
|
|
result += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
fn getTargetCoordinates(coordinates: TerrainCoordinates, command: Command) TerrainCoordinates {
|
|
return .{
|
|
.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 dig(terrain: *Terrain, command: Command) void {
|
|
const target = getTargetCoordinates(terrain.*.coordinates, command);
|
|
|
|
while (!terrain.*.coordinates.isSameX(target)) {
|
|
terrain.*.coordinates.approachX(target);
|
|
terrain.*.setCurrentCell(.Dug);
|
|
}
|
|
|
|
while (!terrain.*.coordinates.isSameY(target)) {
|
|
terrain.*.coordinates.approachY(target);
|
|
terrain.*.setCurrentCell(.Dug);
|
|
}
|
|
}
|
|
|
|
fn parseCommand(command: []const u8) Command {
|
|
var index: usize = 2;
|
|
const length = readNumber(u16, command, &index);
|
|
const direction: Direction = switch (command[0]) {
|
|
'R' => .Right,
|
|
'L' => .Left,
|
|
'U' => .Up,
|
|
'D' => .Down,
|
|
else => unreachable,
|
|
};
|
|
|
|
return .{
|
|
.length = length,
|
|
.direction = direction,
|
|
};
|
|
}
|
|
|
|
fn solveForCommands(commands: []const Command) usize {
|
|
var terrain = Terrain.initZeroes();
|
|
|
|
for (commands) |command| {
|
|
dig(&terrain, command);
|
|
}
|
|
|
|
return solveForTerrain(&terrain);
|
|
}
|
|
|
|
fn solveAll(reader: anytype) !usize {
|
|
var result: usize = 0;
|
|
while (true) {
|
|
var commands = StackList(Command, 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});
|
|
}
|
|
|