diff --git a/examples/aoc2023/day15/part1.jou b/examples/aoc2023/day15/part1.jou new file mode 100644 index 00000000..6b545e56 --- /dev/null +++ b/examples/aoc2023/day15/part1.jou @@ -0,0 +1,31 @@ +import "stdlib/io.jou" +import "stdlib/mem.jou" + + +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + + huge = 100000 + s: byte* = malloc(huge) + assert s != NULL + assert fgets(s, huge, f) != NULL + fclose(f) + + h = 0 as byte + result = 0 + + for p = s; *p != '\0' and *p != '\n'; p++: + if *p == ',': + result += h + h = 0 as byte + else: + h += *p + h *= 17 as byte + + result += h + printf("%d\n", result) # Output: 1320 + + free(s) + + return 0 diff --git a/examples/aoc2023/day15/part2.jou b/examples/aoc2023/day15/part2.jou new file mode 100644 index 00000000..95878648 --- /dev/null +++ b/examples/aoc2023/day15/part2.jou @@ -0,0 +1,88 @@ +import "stdlib/ascii.jou" +import "stdlib/io.jou" +import "stdlib/mem.jou" +import "stdlib/str.jou" + + +class Lens: + label: byte[50] + value: int + + +class Box: + lenses: Lens[100] + nlenses: int + + def set(self, label: byte*, value: int) -> None: + for i = 0; i < self->nlenses; i++: + if strcmp(self->lenses[i].label, label) == 0: + self->lenses[i].value = value + return + + lens = Lens{value=value} + assert strlen(label) < sizeof(lens.label) + strcpy(lens.label, label) + + assert self->nlenses < sizeof(self->lenses)/sizeof(self->lenses[0]) + self->lenses[self->nlenses++] = lens + + def remove_by_label(self, label: byte*) -> None: + for i = 0; i < self->nlenses; i++: + if strcmp(self->lenses[i].label, label) == 0: + memmove(&self->lenses[i], &self->lenses[i+1], (--self->nlenses - i) * sizeof(self->lenses[0])) + break + + +def hash(s: byte*) -> byte: + result = 0 as byte + for p = s; *p != '\0'; p++: + result += *p + result *= 17 as byte + return result + + +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + + huge = 100000 + s: byte* = malloc(huge) + assert s != NULL + assert fgets(s, huge, f) != NULL + fclose(f) + + for p = s; *p != '\0'; p++: + if *p == ',': + *p = ' ' + commands = split_by_ascii_whitespace(s) + + boxes: Box[256]* = malloc(sizeof(*boxes)) + assert boxes != NULL + memset(boxes, 0, sizeof(*boxes)) + + for commandptr = commands; *commandptr != NULL; commandptr++: + command = *commandptr + + if ends_with(command, "-"): + command[strlen(command)-1] = '\0' + box = &(*boxes)[hash(command)] + box->remove_by_label(command) + else: + eq = strchr(command, '=') + assert eq != NULL + *eq = '\0' + box = &(*boxes)[hash(command)] + box->set(command, atoi(&eq[1])) + + free(commands) + free(s) + + result = 0 + for box_num = 0; box_num < 256; box_num++: + box = &(*boxes)[box_num] + for slot_num = 0; slot_num < box->nlenses; slot_num++: + result += (box_num + 1) * (slot_num + 1) * box->lenses[slot_num].value + + printf("%d\n", result) # Output: 145 + free(boxes) + return 0 diff --git a/examples/aoc2023/day15/sampleinput.txt b/examples/aoc2023/day15/sampleinput.txt new file mode 100644 index 00000000..4f58f740 --- /dev/null +++ b/examples/aoc2023/day15/sampleinput.txt @@ -0,0 +1 @@ +rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7 diff --git a/examples/aoc2023/day16/part1.jou b/examples/aoc2023/day16/part1.jou new file mode 100644 index 00000000..ce598ef2 --- /dev/null +++ b/examples/aoc2023/day16/part1.jou @@ -0,0 +1,111 @@ +import "stdlib/math.jou" +import "stdlib/io.jou" +import "stdlib/mem.jou" +import "../grid.jou" + + +# Returns same direction twice, if there is only one direction where to go +def get_new_directions(old_dir: int[2], character: byte) -> int[2][2]: + dx = old_dir[0] + dy = old_dir[1] + + assert (abs(dx) == 1 and dy == 0) or (abs(dy) == 1 and dx == 0) + + if character == '.' or (character == '|' and dx == 0) or (character == '-' and dy == 0): + return [old_dir, old_dir] + + # Reflect across line y=x: from high school math, we know this swaps x and y coordinates + if character == '\\': + return [[dy, dx], [dy, dx]] + + # Reflect across line y=-x: can swap x and y, and then rotate 180deg. + # This is because rotating the mirror 90deg rotates light 180deg. + if character == '/': + return [[-dy, -dx], [-dy, -dx]] + + # split + if character == '|' and dy == 0: + return [[0, -1], [0, 1]] + if character == '-' and dx == 0: + return [[-1, 0], [1, 0]] + + assert False + + +class LightBeam: + location: int[2] + direction: int[2] + + +def direction_to_int(d: int[2]) -> int: + if d[0] == 0 and d[1] == -1: + return 0 + if d[0] == 0 and d[1] == 1: + return 1 + if d[0] == -1 and d[1] == 0: + return 2 + if d[0] == 1 and d[1] == 0: + return 3 + assert False + + +# How return value works: +# (*returnvalue)[x][y][direction_to_int(dir)] = True, if there was beam at (x,y) going in direction. +def run_beam(grid: Grid*, start_beam: LightBeam) -> bool[4][200][200]*: + assert grid->width < 200 + assert grid->height < 200 + result: bool[4][200][200]* = calloc(1, sizeof(*result)) + assert result != NULL + + todo: LightBeam* = malloc(sizeof(todo[0])) + todo[0] = start_beam + todo_len = 1 + todo_alloc = 1 + + while todo_len > 0: + # Make sure there is room for another beam + if todo_alloc == todo_len: + todo_alloc *= 2 + todo = realloc(todo, sizeof(todo[0]) * todo_alloc) + assert todo != NULL + + beam = todo[--todo_len] + x = beam.location[0] + y = beam.location[1] + ptr = &(*result)[x][y][direction_to_int(beam.direction)] + if *ptr: + # we have already handled this beam --> prevent getting stuck in loop + continue + *ptr = True + + next_dirs = get_new_directions(beam.direction, grid->get([x, y])) + for i = 0; i < 2; i++: + dir = next_dirs[i] + location = [beam.location[0] + dir[0], beam.location[1] + dir[1]] + if grid->is_in_bounds(location): + todo[todo_len++] = LightBeam{location = location, direction = dir} + + free(todo) + return result + + +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + grid = read_grid_from_file(f) + fclose(f) + + visited = run_beam(&grid, LightBeam{location = [0,0], direction = [1,0]}) + + n = 0 + for x = 0; x < 200; x++: + for y = 0; y < 200; y++: + for i = 0; i < 4; i++: + if (*visited)[x][y][i]: + n++ + break + printf("%d\n", n) # Output: 46 + + free(grid.data) + free(visited) + return 0 diff --git a/examples/aoc2023/day16/part2.jou b/examples/aoc2023/day16/part2.jou new file mode 100644 index 00000000..847fcb29 --- /dev/null +++ b/examples/aoc2023/day16/part2.jou @@ -0,0 +1,129 @@ +import "stdlib/math.jou" +import "stdlib/io.jou" +import "stdlib/mem.jou" +import "../grid.jou" + + +# Returns same direction twice, if there is only one direction where to go +def get_new_directions(old_dir: int[2], character: byte) -> int[2][2]: + dx = old_dir[0] + dy = old_dir[1] + + assert (abs(dx) == 1 and dy == 0) or (abs(dy) == 1 and dx == 0) + + if character == '.' or (character == '|' and dx == 0) or (character == '-' and dy == 0): + return [old_dir, old_dir] + + # Reflect across line y=x: from high school math, we know this swaps x and y coordinates + if character == '\\': + return [[dy, dx], [dy, dx]] + + # Reflect across line y=-x: can swap x and y, and then rotate 180deg. + # This is because rotating the mirror 90deg rotates light 180deg. + if character == '/': + return [[-dy, -dx], [-dy, -dx]] + + # split + if character == '|' and dy == 0: + return [[0, -1], [0, 1]] + if character == '-' and dx == 0: + return [[-1, 0], [1, 0]] + + assert False + + +class LightBeam: + location: int[2] + direction: int[2] + + +def direction_to_int(d: int[2]) -> int: + if d[0] == 0 and d[1] == -1: + return 0 + if d[0] == 0 and d[1] == 1: + return 1 + if d[0] == -1 and d[1] == 0: + return 2 + if d[0] == 1 and d[1] == 0: + return 3 + assert False + + +def run_beam(grid: Grid*, start_beam: LightBeam) -> int: + assert grid->width < 200 + assert grid->height < 200 + + # How visited array works: + # (*visited)[x][y][direction_to_int(dir)] = True, if there was beam at (x,y) going in direction. + visited: bool[4][200][200]* = calloc(1, sizeof(*visited)) + assert visited != NULL + + todo: LightBeam* = malloc(sizeof(todo[0])) + todo[0] = start_beam + todo_len = 1 + todo_alloc = 1 + + while todo_len > 0: + # Make sure there is room for another beam + if todo_alloc == todo_len: + todo_alloc *= 2 + todo = realloc(todo, sizeof(todo[0]) * todo_alloc) + assert todo != NULL + + beam = todo[--todo_len] + x = beam.location[0] + y = beam.location[1] + ptr = &(*visited)[x][y][direction_to_int(beam.direction)] + if *ptr: + # we have already handled this beam --> prevent getting stuck in loop + continue + *ptr = True + + next_dirs = get_new_directions(beam.direction, grid->get([x, y])) + for i = 0; i < 2; i++: + dir = next_dirs[i] + location = [beam.location[0] + dir[0], beam.location[1] + dir[1]] + if grid->is_in_bounds(location): + todo[todo_len++] = LightBeam{location = location, direction = dir} + + free(todo) + + n = 0 + for x = 0; x < 200; x++: + for y = 0; y < 200; y++: + for i = 0; i < 4; i++: + if (*visited)[x][y][i]: + n++ + break + + free(visited) + return n + + +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + grid = read_grid_from_file(f) + fclose(f) + + best = 0 + + for x = 0; x < grid.width; x++: + n = run_beam(&grid, LightBeam{location = [x, 0], direction = [0, 1]}) + if n > best: + best = n + n = run_beam(&grid, LightBeam{location = [x, grid.height - 1], direction = [0, -1]}) + if n > best: + best = n + + for y = 0; y < grid.height; y++: + n = run_beam(&grid, LightBeam{location = [0, y], direction = [1, 0]}) + if n > best: + best = n + n = run_beam(&grid, LightBeam{location = [grid.width - 1, y], direction = [-1, 0]}) + if n > best: + best = n + + printf("%d\n", best) # Output: 51 + free(grid.data) + return 0 diff --git a/examples/aoc2023/day16/sampleinput.txt b/examples/aoc2023/day16/sampleinput.txt new file mode 100644 index 00000000..d6805ce6 --- /dev/null +++ b/examples/aoc2023/day16/sampleinput.txt @@ -0,0 +1,10 @@ +.|...\.... +|.-.\..... +.....|-... +........|. +.......... +.........\ +..../.\\.. +.-.-/..|.. +.|....-|.\ +..//.|.... diff --git a/examples/aoc2023/day17/part1.jou b/examples/aoc2023/day17/part1.jou new file mode 100644 index 00000000..613d824b --- /dev/null +++ b/examples/aoc2023/day17/part1.jou @@ -0,0 +1,132 @@ +import "stdlib/io.jou" +import "stdlib/mem.jou" +import "../grid.jou" + + +def direction_to_int(d: int[2]) -> int: + if d[0] == 0 and d[1] == -1: + return 0 + if d[0] == 0 and d[1] == 1: + return 1 + if d[0] == -1 and d[1] == 0: + return 2 + if d[0] == 1 and d[1] == 0: + return 3 + assert False + + +def point_to_int(p: int[2]) -> int: + x = p[0] + y = p[1] + assert 0 <= x and x < 150 and 0 <= y and y < 150 + return 150*x + y + + +# Node.to_int() return values are smaller than this +# TODO: global constants so this doesn't need to be a function +def max_num_nodes() -> int: + return 4*150*150 + + +class Node: + location: int[2] + last_direction: int[2] + + def to_int(self) -> int: + return 4*point_to_int(self->location) + direction_to_int(self->last_direction) + + def get_neighbors_in_graph(self, grid: Grid*, nneighbors: int*, neighbors: Node[6]*, weights: int[6]*) -> None: + *nneighbors = 0 + + for sign = -1; sign <= 1; sign += 2: + # Rotate direction +-90deg + dx = sign*self->last_direction[1] + dy = -sign*self->last_direction[0] + + x = self->location[0] + y = self->location[1] + weight = 0 + + for nsteps = 1; nsteps <= 3; nsteps++: + x += dx + y += dy + if not grid->is_in_bounds([x, y]): + break + weight += grid->get([x, y]) - '0' + + assert *nneighbors < 6 + (*neighbors)[*nneighbors] = Node{location = [x,y], last_direction = [dx,dy]} + (*weights)[*nneighbors] = weight + ++*nneighbors + + +def min(x: int, y: int) -> int: + if x < y: + return x + return y + + +def dijkstra_algorithm(grid: Grid*, start1: Node, start2: Node, goal1: Node, goal2: Node) -> int: + assert grid->is_in_bounds(goal1.location) + assert grid->is_in_bounds(goal2.location) + + distances: int* = calloc(sizeof(distances[0]), max_num_nodes()) + nodes_with_distance_set: Node* = calloc(sizeof(nodes_with_distance_set[0]), max_num_nodes()) + known_shortest: bool* = calloc(sizeof(known_shortest[0]), max_num_nodes()) + + for i = 0; i < max_num_nodes(); i++: + distances[i] = -1 + distances[start1.to_int()] = 0 + distances[start2.to_int()] = 0 + + nodes_with_distance_set[0] = start1 + nodes_with_distance_set[1] = start2 + n_nodes_with_distance_set = 2 + + while not (known_shortest[goal1.to_int()] and known_shortest[goal2.to_int()]): + # pick the node with unknown-yet-to-be-smallest distance, whose distance is smallest + current: Node* = NULL + for i = 0; i < n_nodes_with_distance_set; i++: + n = &nodes_with_distance_set[i] + if (not known_shortest[n->to_int()]) and (current == NULL or distances[n->to_int()] < distances[current->to_int()]): + current = n + assert current != NULL + + # for some reason that i don't understand, the distance of the node we picked is known to be smallest + known_shortest[current->to_int()] = True + + # update neighbor distances, if visiting through current makes them shorter + neighbors: Node[6] + nneighbors: int + edge_weights: int[6] + current->get_neighbors_in_graph(grid, &nneighbors, &neighbors, &edge_weights) + for i = 0; i < nneighbors; i++: + neighbor = neighbors[i] + d = distances[current->to_int()] + edge_weights[i] + if distances[neighbor.to_int()] == -1: + distances[neighbor.to_int()] = d + nodes_with_distance_set[n_nodes_with_distance_set++] = neighbor + elif distances[neighbor.to_int()] > d: + distances[neighbor.to_int()] = d + + result = min(distances[goal1.to_int()], distances[goal2.to_int()]) + free(distances) + free(nodes_with_distance_set) + free(known_shortest) + return result + + +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + grid = read_grid_from_file(f) + fclose(f) + + start1 = Node{location = [0,0], last_direction = [-1,0]} + start2 = Node{location = [0,0], last_direction = [0,-1]} + goal1 = Node{location = [grid.width - 1, grid.height - 1], last_direction = [1,0]} + goal2 = Node{location = [grid.width - 1, grid.height - 1], last_direction = [0,1]} + printf("%d\n", dijkstra_algorithm(&grid, start1, start2, goal1, goal2)) # Output: 102 + + free(grid.data) + return 0 diff --git a/examples/aoc2023/day17/part2.jou b/examples/aoc2023/day17/part2.jou new file mode 100644 index 00000000..ae537329 --- /dev/null +++ b/examples/aoc2023/day17/part2.jou @@ -0,0 +1,133 @@ +import "stdlib/io.jou" +import "stdlib/mem.jou" +import "../grid.jou" + + +def direction_to_int(d: int[2]) -> int: + if d[0] == 0 and d[1] == -1: + return 0 + if d[0] == 0 and d[1] == 1: + return 1 + if d[0] == -1 and d[1] == 0: + return 2 + if d[0] == 1 and d[1] == 0: + return 3 + assert False + + +def point_to_int(p: int[2]) -> int: + x = p[0] + y = p[1] + assert 0 <= x and x < 150 and 0 <= y and y < 150 + return 150*x + y + + +# Node.to_int() return values are smaller than this +# TODO: global constants so this doesn't need to be a function +def max_num_nodes() -> int: + return 4*150*150 + + +class Node: + location: int[2] + last_direction: int[2] + + def to_int(self) -> int: + return 4*point_to_int(self->location) + direction_to_int(self->last_direction) + + def get_neighbors_in_graph(self, grid: Grid*, nneighbors: int*, neighbors: Node[14]*, weights: int[14]*) -> None: + *nneighbors = 0 + + for sign = -1; sign <= 1; sign += 2: + # Rotate direction +-90deg + dx = sign*self->last_direction[1] + dy = -sign*self->last_direction[0] + + x = self->location[0] + y = self->location[1] + weight = 0 + + for nsteps = 1; nsteps <= 10; nsteps++: + x += dx + y += dy + if not grid->is_in_bounds([x, y]): + break + weight += grid->get([x, y]) - '0' + + if nsteps >= 4: + assert *nneighbors < 14 + (*neighbors)[*nneighbors] = Node{location = [x,y], last_direction = [dx,dy]} + (*weights)[*nneighbors] = weight + ++*nneighbors + + +def min(x: int, y: int) -> int: + if x < y: + return x + return y + + +def dijkstra_algorithm(grid: Grid*, start1: Node, start2: Node, goal1: Node, goal2: Node) -> int: + assert grid->is_in_bounds(goal1.location) + assert grid->is_in_bounds(goal2.location) + + distances: int* = calloc(sizeof(distances[0]), max_num_nodes()) + nodes_with_distance_set: Node* = calloc(sizeof(nodes_with_distance_set[0]), max_num_nodes()) + known_shortest: bool* = calloc(sizeof(known_shortest[0]), max_num_nodes()) + + for i = 0; i < max_num_nodes(); i++: + distances[i] = -1 + distances[start1.to_int()] = 0 + distances[start2.to_int()] = 0 + + nodes_with_distance_set[0] = start1 + nodes_with_distance_set[1] = start2 + n_nodes_with_distance_set = 2 + + while not (known_shortest[goal1.to_int()] and known_shortest[goal2.to_int()]): + # pick the node with unknown-yet-to-be-smallest distance, whose distance is smallest + current: Node* = NULL + for i = 0; i < n_nodes_with_distance_set; i++: + n = &nodes_with_distance_set[i] + if (not known_shortest[n->to_int()]) and (current == NULL or distances[n->to_int()] < distances[current->to_int()]): + current = n + assert current != NULL + + # for some reason that i don't understand, the distance of the node we picked is known to be smallest + known_shortest[current->to_int()] = True + + # update neighbor distances, if visiting through current makes them shorter + neighbors: Node[14] + nneighbors: int + edge_weights: int[14] + current->get_neighbors_in_graph(grid, &nneighbors, &neighbors, &edge_weights) + for i = 0; i < nneighbors; i++: + neighbor = neighbors[i] + d = distances[current->to_int()] + edge_weights[i] + if distances[neighbor.to_int()] == -1: + distances[neighbor.to_int()] = d + nodes_with_distance_set[n_nodes_with_distance_set++] = neighbor + elif distances[neighbor.to_int()] > d: + distances[neighbor.to_int()] = d + + result = min(distances[goal1.to_int()], distances[goal2.to_int()]) + free(distances) + free(nodes_with_distance_set) + free(known_shortest) + return result + + +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + grid = read_grid_from_file(f) + fclose(f) + + start1 = Node{location = [0,0], last_direction = [-1,0]} + start2 = Node{location = [0,0], last_direction = [0,-1]} + goal1 = Node{location = [grid.width - 1, grid.height - 1], last_direction = [1,0]} + goal2 = Node{location = [grid.width - 1, grid.height - 1], last_direction = [0,1]} + printf("%d\n", dijkstra_algorithm(&grid, start1, start2, goal1, goal2)) # Output: 94 + + free(grid.data) + return 0 diff --git a/examples/aoc2023/day17/sampleinput.txt b/examples/aoc2023/day17/sampleinput.txt new file mode 100644 index 00000000..f400d6e0 --- /dev/null +++ b/examples/aoc2023/day17/sampleinput.txt @@ -0,0 +1,13 @@ +2413432311323 +3215453535623 +3255245654254 +3446585845452 +4546657867536 +1438598798454 +4457876987766 +3637877979653 +4654967986887 +4564679986453 +1224686865563 +2546548887735 +4322674655533