Solutions of most (39 out of 50 so far) puzzles in Zig (system language, alternative for C). My first experience with it.
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

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