const std = @import("std");
const PATH = "input/day06.txt";
const Str = []const u8;
const CoordType = u10;
const Coords = [2]CoordType;
const Actions = enum {
on,
off,
toggle,
};
pub fn first(allocator: std.mem.Allocator) !usize {
_ = allocator;
return switchLights(@embedFile(PATH));
}
pub fn second(allocator: std.mem.Allocator) !usize {
_ = allocator;
return changeLights(@embedFile(PATH));
}
fn switchLights(commands: Str) !usize {
const Lights = [1000]std.StaticBitSet(1000);
var lights: Lights = undefined;
for (lights) |*row| {
row.* = (std.StaticBitSet(1000)).initEmpty();
}
var lines = std.mem.tokenize(u8, commands, "\n");
while (lines.next()) |line| {
var words = std.mem.tokenize(u8, line, " ");
// parse action
var action: Actions = undefined;
// turn or toggle
if (words.next().?[1] == 'u') {
// on or off
if (words.next().?[1] == 'n') action = .on else action = .off;
} else {
action = .toggle;
}
// parse coordinates
const top = try parseCoords(words.next().?);
_ = words.next(); // drop through
const bottom = try parseCoords(words.next().?);
const range = std.bit_set.Range{
.start = top[0],
.end = bottom[0] + 1,
};
// do the action
switch (action) {
.on => {
var row: usize = top[1];
while (row <= bottom[1]) : (row += 1) {
lights[row].setRangeValue(range, true);
}
},
.off => {
var row: usize = top[1];
while (row <= bottom[1]) : (row += 1) {
lights[row].setRangeValue(range, false);
}
},
.toggle => {
var ts = std.StaticBitSet(1000).initEmpty();
ts.setRangeValue(range, true);
var row: usize = top[1];
while (row <= bottom[1]) : (row += 1) {
lights[row].toggleSet(ts);
}
},
}
}
var sum: usize = 0;
for (lights) |row| {
sum += row.count();
}
return sum;
}
fn changeLights(commands: Str) !usize {
const LightType = u8;
var lights = [_][1000]LightType{[_]LightType{0} ** 1000} ** 1000;
var lines = std.mem.tokenize(u8, commands, "\n");
while (lines.next()) |line| {
var words = std.mem.tokenize(u8, line, " ");
// parse action
var action: Actions = undefined;
// turn or toggle
if (words.next().?[1] == 'u') {
// on or off
if (words.next().?[1] == 'n') action = .on else action = .off;
} else {
action = .toggle;
}
// parse coordinates
const top = try parseCoords(words.next().?);
_ = words.next(); // drop through
const bottom = try parseCoords(words.next().?);
// do the action
switch (action) {
.on => {
var x: usize = top[0];
while (x <= bottom[0]) : (x += 1) {
var y: usize = top[1];
while (y <= bottom[1]) : (y += 1) {
lights[x][y] += 1;
}
}
},
.toggle => {
var x: usize = top[0];
while (x <= bottom[0]) : (x += 1) {
var y: usize = top[1];
while (y <= bottom[1]) : (y += 1) {
lights[x][y] += 2;
}
}
},
.off => {
var x: usize = top[0];
while (x <= bottom[0]) : (x += 1) {
var y: usize = top[1];
while (y <= bottom[1]) : (y += 1) {
lights[x][y] -|= 1;
}
}
},
}
}
var sum: usize = 0;
for (lights) |row| {
for (row) |item| {
sum += item;
}
}
return sum;
}
fn parseCoords(a: Str) !Coords {
var crd: Coords = undefined;
var parts = std.mem.tokenize(u8, a, ",");
crd[0] = try std.fmt.parseUnsigned(CoordType, parts.next().?, 10);
crd[1] = try std.fmt.parseUnsigned(CoordType, parts.next().?, 10);
return crd;
}
test "switchLights" {
try std.testing.expectEqual(@as(usize, 1000 * 1000), try switchLights("turn on 0,0 through 999,999"));
try std.testing.expectEqual(@as(usize, 1000), try switchLights("toggle 0,0 through 999,0"));
}
test "changeLights" {
try std.testing.expectEqual(@as(usize, 1), try changeLights("turn on 0,0 through 0,0"));
try std.testing.expectEqual(@as(usize, 2 * 1000 * 1000), try changeLights("toggle 0,0 through 999,999"));
}
test "day06a" {
try std.testing.expectEqual(@as(usize, 569999), try first(std.testing.allocator));
}
test "day06b" {
try std.testing.expectEqual(@as(usize, 17836115), try second(std.testing.allocator));
}