diff --git a/examples/aoc2024/day13/part1.jou b/examples/aoc2024/day13/part1.jou new file mode 100644 index 00000000..2f0c7806 --- /dev/null +++ b/examples/aoc2024/day13/part1.jou @@ -0,0 +1,63 @@ +import "stdlib/io.jou" + + +def cost(a: int, b: int) -> int: + return 3*a + b + + +class ClawMachine: + a_vector: int[2] + b_vector: int[2] + prize: int[2] + + def is_solution(self, a: int, b: int) -> bool: + return ( + a * self->a_vector[0] + b * self->b_vector[0] == self->prize[0] + and a * self->a_vector[1] + b * self->b_vector[1] == self->prize[1] + ) + + # Returns smallest possible cost to win, or -1 if cannot win + def optimize(self) -> int: + best_cost = -1 + + for a = 0; a <= 100; a++: + for b = 0; b <= 100; b++: + if self->is_solution(a, b) and (best_cost == -1 or cost(a, b) < best_cost): + best_cost = cost(a, b) + + return best_cost + + +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + + total_price = 0 + + ax: int + ay: int + bx: int + by: int + px: int + py: int + + while True: + ret = fscanf(f, "Button A: X+%d, Y+%d\n", &ax, &ay) + if ret != 2: + # End of file reached + break + + ret = fscanf(f, "Button B: X+%d, Y+%d\n", &bx, &by) + assert ret == 2 + ret = fscanf(f, "Prize: X=%d, Y=%d\n", &px, &py) + assert ret == 2 + + m = ClawMachine{a_vector = [ax, ay], b_vector = [bx, by], prize = [px, py]} + solution = m.optimize() + if solution != -1: + total_price += solution + + printf("%d\n", total_price) # Output: 480 + + fclose(f) + return 0 diff --git a/examples/aoc2024/day13/part2.jou b/examples/aoc2024/day13/part2.jou new file mode 100644 index 00000000..3c226524 --- /dev/null +++ b/examples/aoc2024/day13/part2.jou @@ -0,0 +1,214 @@ +import "stdlib/io.jou" +import "stdlib/math.jou" +import "stdlib/mem.jou" + + +def gcd(a: long, b: long) -> long: + assert a > 0 and b > 0 + + while a > 0 and b > 0: + # Euclidean algorithm: Reduce the bigger number modulo smaller number. + if a > b: + a %= b + else: + b %= a + + # Return whichever number isn't zero. + return llmax(a, b) + + +# Returns the shortest vector in the same direction as the given vector. +def shortest_vector(v: long[2]) -> long[2]: + unwanted_scaling = gcd(llabs(v[0]), llabs(v[1])) + return [v[0] / unwanted_scaling, v[1] / unwanted_scaling] + + +# Determines if two vectors go in the same direction. +# +# This would be easy with determinant, but I don't want to use them because +# calculating determinant might overflow 64-bit long data type. I don't think +# that would happen, but it could be really hard to debug. +def same_direction(v1: long[2], v2: long[2]) -> bool: + s1 = shortest_vector(v1) + s2 = shortest_vector(v2) + return s1[0] == s2[0] and s1[1] == s2[1] + + +# Solves the following linear equations for x and y: +# +# ax + by = c +# dx + ey = f +# +# Assumes that there is a unique solution (may be negative or a fraction). +# According to linear algebra, this is same as saying that (a,d) and (b,e) +# vectors go in different directions. +# +# Return value is [x, y]. If solution is not an integer, returns [-1,-1]. +def solve_linear_unique_2x2_system(a: long, b: long, c: long, d: long, e: long, f: long) -> long[2]: + if a < 0: + a *= -1 + b *= -1 + c *= -1 + if d < 0: + d *= -1 + e *= -1 + f *= -1 + + # Gaussian elimination + Euclidean algorithm + while a != 0 and d != 0: + if a > d: + # Subtract second equation from first n times + n = a/d + a -= n*d + b -= n*e + c -= n*f + else: + # Subtract first equation from second n times + n = d/a + d -= n*a + e -= n*b + f -= n*c + + if a != 0: + memswap(&a, &d, sizeof(a)) + memswap(&b, &e, sizeof(b)) + memswap(&c, &f, sizeof(c)) + + # Due to uniqueness assumption, equations must now look like this: + # + # by = c (b != 0) + # dx + ey = f (d != 0) + assert a == 0 + assert b != 0 + assert d != 0 + + y = c / b + x = (f - e*y) / d + + if a*x + b*y == c and d*x + e*y == f: + # Solution happens to consist of integers + return [x, y] + else: + return [-1L, -1L] + + +def cost(a: long, b: long) -> long: + return 3*a + b + + +# Solves a 1-dimensional variant of the problem. Specifically: +# +# a*(button A presses) + b*(button B presses) = prize +# +# Returns [button A presses, button B presses]. +def optimize_1d(a: long, b: long, prize: long) -> long[2]: + assert a > 0 + assert b > 0 + assert prize > 0 + + if prize % gcd(a, b) != 0: + # The prize is not reachable. Any combination of a and b + # either doesn't reach the prize or jumps beyond the prize. + return [-1L, -1L] + + # Figure out which button press moves the claw the most per token. + # Three B button presses cost as many tokens as one A button press. + # A is better if it moves the claw more for the same amount of tokens. + a_is_better = a > 3*b + + # Start by pressing the better button as much as we can without going + # beyond the prize. Use the other button to move the rest of the way. + if a_is_better: + a_presses = prize / a + b_presses = (prize - a*a_presses) / b + else: + b_presses = prize / b + a_presses = (prize - b*b_presses) / a + + while a_presses >= 0 and b_presses >= 0: + if a*a_presses + b*b_presses == prize: + # Done! Solution found!!! + return [a_presses, b_presses] + + # Decrease the amount of better button presses. This makes the + # solution worse but may be necessary to land exactly at the claw. + # + # This seems to never happen with the inputs given in AoC. + if a_is_better: + a_presses-- + b_presses = (prize - a*a_presses) / b + else: + b_presses-- + a_presses = (prize - b*b_presses) / a + + # No solution + return [-1L, -1L] + + +class ClawMachine: + a_vector: long[2] + b_vector: long[2] + prize: long[2] + + # Returns cheapest button presses to win, or [-1, -1] if cannot win. + def optimize(self) -> long[2]: + if same_direction(self->a_vector, self->b_vector): + # The machine moves along a line. + if not same_direction(self->a_vector, self->prize): + # The prize is not on the line. + return [-1L, -1L] + # Consider only the x coordinates. + return optimize_1d(self->a_vector[0], self->b_vector[0], self->prize[0]) + else: + # According to linear algebra, there is a unique way to reach the + # prize. However, it may involve negative or non-integer amounts of + # button presses :) + solution = solve_linear_unique_2x2_system( + self->a_vector[0], self->b_vector[0], self->prize[0], + self->a_vector[1], self->b_vector[1], self->prize[1], + ) + a_presses = solution[0] + b_presses = solution[1] + if a_presses >= 0 and b_presses >= 0: + return [a_presses, b_presses] + else: + return [-1L, -1L] + + + +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + + total_price = 0L + + ax: long + ay: long + bx: long + by: long + px: long + py: long + + while True: + ret = fscanf(f, "Button A: X+%lld, Y+%lld\n", &ax, &ay) + if ret != 2: + # End of file reached + break + + ret = fscanf(f, "Button B: X+%lld, Y+%lld\n", &bx, &by) + assert ret == 2 + ret = fscanf(f, "Prize: X=%lld, Y=%lld\n", &px, &py) + assert ret == 2 + + px += 10000000000000L + py += 10000000000000L + + m = ClawMachine{a_vector = [ax, ay], b_vector = [bx, by], prize = [px, py]} + solution = m.optimize() + if solution[0] >= 0 and solution[1] >= 0: + total_price += cost(solution[0], solution[1]) + + printf("%lld\n", total_price) # Output: 875318608908 + + fclose(f) + return 0 diff --git a/examples/aoc2024/day13/sampleinput.txt b/examples/aoc2024/day13/sampleinput.txt new file mode 100644 index 00000000..912f4820 --- /dev/null +++ b/examples/aoc2024/day13/sampleinput.txt @@ -0,0 +1,15 @@ +Button A: X+94, Y+34 +Button B: X+22, Y+67 +Prize: X=8400, Y=5400 + +Button A: X+26, Y+66 +Button B: X+67, Y+21 +Prize: X=12748, Y=12176 + +Button A: X+17, Y+86 +Button B: X+84, Y+37 +Prize: X=7870, Y=6450 + +Button A: X+69, Y+23 +Button B: X+27, Y+71 +Prize: X=18641, Y=10279 diff --git a/examples/aoc2024/day14/part1.jou b/examples/aoc2024/day14/part1.jou new file mode 100644 index 00000000..3371edea --- /dev/null +++ b/examples/aoc2024/day14/part1.jou @@ -0,0 +1,69 @@ +import "stdlib/io.jou" + + +enum Quadrant: + TopLeft + TopRight + BottomLeft + BottomRight + SomeKindOfMiddle + + +global width: int +global height: int + + +def determine_quadrant(x: int, y: int) -> Quadrant: + assert 0 <= x and x < width + assert 0 <= y and y < height + assert width % 2 == 1 + assert height % 2 == 1 + + mid_x = (width - 1) / 2 + mid_y = (height - 1) / 2 + + if x < mid_x: + if y < mid_y: + return Quadrant::TopLeft + if y > mid_y: + return Quadrant::BottomLeft + + if x > mid_x: + if y < mid_y: + return Quadrant::TopRight + if y > mid_y: + return Quadrant::BottomRight + + assert x == mid_x or y == mid_y + return Quadrant::SomeKindOfMiddle + + +def main() -> int: + # sampleinput size + width = 11 + height = 7 + + # actual input size + #width = 101 + #height = 103 + + f = fopen("sampleinput.txt", "r") + assert f != NULL + + qcounts: int[5] = [0, 0, 0, 0, 0] + + px: int + py: int + vx: int + vy: int + while fscanf(f, "p=%d,%d v=%d,%d\n", &px, &py, &vx, &vy) == 4: + x = (px + 100*vx) % width + y = (py + 100*vy) % height + qcounts[determine_quadrant(x, y) as int]++ + + fclose(f) + + # Output: 12 + printf("%d\n", qcounts[0] * qcounts[1] * qcounts[2] * qcounts[3]) + + return 0 diff --git a/examples/aoc2024/day14/part2.jou b/examples/aoc2024/day14/part2.jou new file mode 100644 index 00000000..787bbc28 --- /dev/null +++ b/examples/aoc2024/day14/part2.jou @@ -0,0 +1,95 @@ +import "stdlib/io.jou" +import "stdlib/mem.jou" +import "stdlib/str.jou" + + +global width: int +global height: int + + +class Robot: + position: int[2] + velocity: int[2] + + def move(self) -> None: + self->position[0] += self->velocity[0] + self->position[1] += self->velocity[1] + + self->position[0] %= width + self->position[1] %= height + + +def main() -> int: +# # actual input params +# width = 101 +# height = 103 +# image_identifier = "xxxxxxxxxxxx" # Picture of christmas tree contains this +# filename = "input" + + # sample input params + width = 11 + height = 7 + image_identifier = "xxxx" + filename = "sampleinput.txt" + + f = fopen(filename, "r") + assert f != NULL + + max_robots = 1000 + robots: Robot* = malloc(sizeof(robots[0]) * max_robots) + assert robots != NULL + nrobots = 0 + + px: int + py: int + vx: int + vy: int + while fscanf(f, "p=%d,%d v=%d,%d\n", &px, &py, &vx, &vy) == 4: + assert nrobots < max_robots + robots[nrobots++] = Robot{ + position = [px, py], + velocity = [vx, vy], + } + + fclose(f) + + assert width < 150 + assert height < 150 + grid: byte[150][150] + + move_counter = 0 + found_signs_of_image = False + + while not found_signs_of_image: + for i = 0; i < nrobots; i++: + robots[i].move() + move_counter++ + + for y = 0; y < height; y++: + memset(&grid[y], ' ', width) + grid[y][width] = '\0' + for i = 0; i < nrobots; i++: + grid[robots[i].position[1]][robots[i].position[0]] = 'x' + + # Use the fact that picture of tree contains many consecutive x's + for y = 0; y < height; y++: + if strstr(grid[y], image_identifier) != NULL: + found_signs_of_image = True + + # With sample input, this doesn't really look like a christmas tree at + # all. With actual input it's very obviously a christmas tree :) + # + # Output: *** Picture of christmas tree found after 6 moves *** + # Output: | x x x| + # Output: | xxxx | + # Output: | xx x | + # Output: | | + # Output: | x | + # Output: | | + # Output: |x | + printf("*** Picture of christmas tree found after %d moves ***\n", move_counter) + for y = 0; y < height; y++: + printf("|%s|\n", grid[y]) + + free(robots) + return 0 diff --git a/examples/aoc2024/day14/sampleinput.txt b/examples/aoc2024/day14/sampleinput.txt new file mode 100644 index 00000000..2455da4c --- /dev/null +++ b/examples/aoc2024/day14/sampleinput.txt @@ -0,0 +1,12 @@ +p=0,4 v=3,-3 +p=6,3 v=-1,-3 +p=10,3 v=-1,2 +p=2,0 v=2,-1 +p=0,0 v=1,3 +p=3,0 v=-2,-2 +p=7,6 v=-1,-3 +p=3,0 v=-1,-2 +p=9,3 v=2,3 +p=7,3 v=-1,2 +p=2,4 v=2,-3 +p=9,5 v=-3,-3 diff --git a/examples/aoc2024/day15/part1.jou b/examples/aoc2024/day15/part1.jou new file mode 100644 index 00000000..7af0f769 --- /dev/null +++ b/examples/aoc2024/day15/part1.jou @@ -0,0 +1,81 @@ +import "stdlib/io.jou" +import "stdlib/mem.jou" +import "stdlib/str.jou" +import "../../aoc2023/grid.jou" + + +def move_right(start: byte*) -> None: + assert start != NULL + assert *start == '@' + + relevant_length = strspn(start, "@.O") + + first_blank = -1 + for i = 0; i < relevant_length; i++: + if start[i] == '.': + first_blank = i + break + + if first_blank == -1: + # no room to move, do nothing + return + + # shift boxes to the right, so effectively blank moves to left + memswap(&start[1], &start[first_blank], 1) + memswap(&start[0], &start[1], 1) + assert starts_with(start, ".@") + + +def horizontal_mirror(grid: Grid*) -> None: + for y = 0; y < grid->height; y++: + first = &grid->data[(grid->width + 1) * y] + last = &first[grid->width - 1] + while first < last: + memswap(first++, last--, 1) + + +def move(grid: Grid*, how: byte) -> None: + if how == '>': + move_right(strstr(grid->data, "@")) + elif how == 'v': + grid->transpose() + move_right(strstr(grid->data, "@")) + grid->transpose() + elif how == '<': + horizontal_mirror(grid) + move_right(strstr(grid->data, "@")) + horizontal_mirror(grid) + elif how == '^': + grid->transpose() + horizontal_mirror(grid) + move_right(strstr(grid->data, "@")) + horizontal_mirror(grid) + grid->transpose() + else: + assert False + + +def main() -> int: + f = fopen("sampleinput1.txt", "r") + assert f != NULL + grid = read_grid_from_file(f) + + while True: + c = fgetc(f) + if c == -1: # TODO: EOF constant + break + if c == '\n': + continue + move(&grid, c as byte) + + fclose(f) + + result = 0 + for x = 0; x < grid.width; x++: + for y = 0; y < grid.height; y++: + if grid.get([x, y]) == 'O': + result += 100*y + x + printf("%d\n", result) # Output: 2028 + + free(grid.data) + return 0 diff --git a/examples/aoc2024/day15/part2.jou b/examples/aoc2024/day15/part2.jou new file mode 100644 index 00000000..882f3d3b --- /dev/null +++ b/examples/aoc2024/day15/part2.jou @@ -0,0 +1,190 @@ +import "stdlib/io.jou" +import "stdlib/mem.jou" +import "stdlib/str.jou" +import "../../aoc2023/grid.jou" + + +def move_right(start: byte*) -> None: + assert start != NULL + assert *start == '@' + + relevant_length = strspn(start, "@.[]") + + first_blank = -1 + for i = 0; i < relevant_length; i++: + if start[i] == '.': + first_blank = i + break + + if first_blank == -1: + # no room to move, do nothing + return + + # shift boxes to the right + memmove(&start[2], &start[1], first_blank - 1) + start[0] = '.' + start[1] = '@' + + +# x_left and y specify the location of "[" part of box +def can_move_box_up(grid: Grid*, x: int, y: int) -> bool: + if grid->get([x, y]) == '[': + x_left = x + x_right = x+1 + elif grid->get([x, y]) == ']': + x_left = x-1 + x_right = x + else: + assert False + + if grid->get([x_left, y-1]) == '#' or grid->get([x_right, y-1]) == '#': + # bumping into wall + return False + + for x = x_left; x <= x_right; x++: + if strchr("[]", grid->get([x, y-1])) != NULL and not can_move_box_up(grid, x, y-1): + # Some kind of box (aligned or not) above, and it won't move. + return False + + return True + + +def move_box_up(grid: Grid*, x: int, y: int) -> None: + if grid->get([x, y]) == '[': + x_left = x + x_right = x+1 + elif grid->get([x, y]) == ']': + x_left = x-1 + x_right = x + else: + assert False + + for x = x_left; x <= x_right; x++: + if strchr("[]", grid->get([x, y-1])) != NULL: + move_box_up(grid, x, y-1) + + assert grid->get([x_left, y-1]) == '.' + assert grid->get([x_right, y-1]) == '.' + assert grid->get([x_left, y]) == '[' + assert grid->get([x_right, y]) == ']' + + grid->set([x_left, y-1], '[') + grid->set([x_right, y-1], ']') + grid->set([x_left, y], '.') + grid->set([x_right, y], '.') + + +def can_move_up(grid: Grid*) -> bool: + pos: int[2] = grid->find_first('@') + above = [pos[0], pos[1] - 1] + + return ( + grid->get(above) == '.' + or ( + strchr("[]", grid->get(above)) != NULL + and can_move_box_up(grid, above[0], above[1]) + ) + ) + + +def move_up(grid: Grid*) -> None: + pos: int[2] = grid->find_first('@') + above = [pos[0], pos[1] - 1] + + if strchr("[]", grid->get(above)) != NULL: + move_box_up(grid, above[0], above[1]) + + assert grid->get(above) == '.' + assert grid->get(pos) == '@' + + grid->set(above, '@') + grid->set(pos, '.') + + +def horizontal_mirror(grid: Grid*) -> None: + bytes_per_row = grid->width + 1 + for y = 0; y < grid->height; y++: + first = &grid->data[bytes_per_row * y] + last = &first[grid->width - 1] + while first < last: + memswap(first++, last--, 1) + + +def vertical_mirror(grid: Grid*) -> None: + bytes_per_row = grid->width + 1 + first = grid->data + last = &first[bytes_per_row*(grid->height - 1)] + + while first < last: + memswap(first, last, grid->width) + first = &first[bytes_per_row] + last = &last[-bytes_per_row] + + +def move(grid: Grid*, how: byte) -> None: + if how == '>': + move_right(strstr(grid->data, "@")) + elif how == '<': + horizontal_mirror(grid) + move_right(strstr(grid->data, "@")) + horizontal_mirror(grid) + elif how == '^': + if can_move_up(grid): + move_up(grid) + elif how == 'v': + vertical_mirror(grid) + if can_move_up(grid): + move_up(grid) + vertical_mirror(grid) + else: + assert False + + +def double_width(grid: Grid*) -> None: + new_grid = Grid{ + width = 2 * grid->width, + height = grid->height, + data = malloc((2*grid->width + 1)*grid->height + 1), + } + new_grid.data[0] = '\0' + for y = 0; y < grid->height; y++: + for x = 0; x < grid->width; x++: + if grid->get([x, y]) == 'O': + strcat(new_grid.data, "[]") + elif grid->get([x, y]) == '@': + strcat(new_grid.data, "@.") + else: + s = [grid->get([x, y]), grid->get([x, y]), '\0'] + strcat(new_grid.data, s) + strcat(new_grid.data, "\n") + + free(grid->data) + *grid = new_grid + + +def main() -> int: + f = fopen("sampleinput2.txt", "r") + assert f != NULL + grid = read_grid_from_file(f) + double_width(&grid) + + while True: + #getchar() + c = fgetc(f) + if c == -1: # TODO: EOF constant + break + if c == '\n': + continue + move(&grid, c as byte) + + fclose(f) + + result = 0 + for x = 0; x < grid.width; x++: + for y = 0; y < grid.height; y++: + if grid.get([x, y]) == '[': + result += 100*y + x + printf("%d\n", result) # Output: 9021 + + free(grid.data) + return 0 diff --git a/examples/aoc2024/day15/sampleinput1.txt b/examples/aoc2024/day15/sampleinput1.txt new file mode 100644 index 00000000..8163605d --- /dev/null +++ b/examples/aoc2024/day15/sampleinput1.txt @@ -0,0 +1,10 @@ +######## +#..O.O.# +##@.O..# +#...O..# +#.#.O..# +#...O..# +#......# +######## + +<^^>>>vv>v<< diff --git a/examples/aoc2024/day15/sampleinput2-simple.txt b/examples/aoc2024/day15/sampleinput2-simple.txt new file mode 100644 index 00000000..6ee6098f --- /dev/null +++ b/examples/aoc2024/day15/sampleinput2-simple.txt @@ -0,0 +1,9 @@ +####### +#...#.# +#.....# +#..OO@# +#..O..# +#.....# +####### + +^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ +vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< +<>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ +^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^>< +^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ +<><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> +^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<>< +v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^ diff --git a/examples/aoc2024/day16/part1.jou b/examples/aoc2024/day16/part1.jou new file mode 100644 index 00000000..f6078155 --- /dev/null +++ b/examples/aoc2024/day16/part1.jou @@ -0,0 +1,163 @@ +# This was really an exercise in learning Dijkstra's algorithm. +# I finally understood why it works!!! :) + +import "stdlib/io.jou" +import "stdlib/str.jou" +import "stdlib/math.jou" +import "stdlib/mem.jou" +import "../../aoc2023/grid.jou" + + +global visual_mode: bool + + +class State: + place: int[2] + direction: int[2] + score: int + + def next_states(self) -> State[3]: + x = self->place[0] + y = self->place[1] + dx = self->direction[0] + dy = self->direction[1] + + if visual_mode: + turn_cost = 1 + else: + turn_cost = 1000 + + return [ + # Go forward + State{place=[x+dx, y+dy], direction=[dx, dy], score=self->score + 1}, + # Turn both ways + State{place=[x, y], direction=[-dy, dx], score=self->score + turn_cost}, + State{place=[x, y], direction=[dy, -dx], score=self->score + turn_cost}, + ] + + +def direction_to_0123(dir: int[2]) -> int: + if dir[0] == 1 and dir[1] == 0: + return 0 # right + if dir[0] == 0 and dir[1] == 1: + return 1 # down + if dir[0] == -1 and dir[1] == 0: + return 2 # left + if dir[0] == 0 and dir[1] == -1: + return 3 # up + assert False + + +def min4(a: int, b: int, c: int, d: int) -> int: + return min(min(a, b), min(c, d)) + + +def visualize(grid: Grid*, smallest_scores: int[4][150][150]*, todo: State*, todo_len: int) -> None: + assert visual_mode + + for y = 0; y < grid->height; y++: + for x = 0; x < grid->width; x++: + in_todo = False + for i = 0; i < todo_len; i++: + if todo[i].place[0] == x and todo[i].place[1] == y: + in_todo = True + if in_todo: + printf("\x1b[1;44m") # ANSI code for yellow + + if grid->get([x, y]) == '#': + putchar('#') + else: + best = min4( + (*smallest_scores)[x][y][0], + (*smallest_scores)[x][y][1], + (*smallest_scores)[x][y][2], + (*smallest_scores)[x][y][3], + ) + s = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + if best >= strlen(s): + putchar('.') + else: + putchar(s[best]) + + if in_todo: + printf("\x1b[1;0m") # ANSI code reset + putchar('\n') + + printf("\n\n\n") + fflush(stdout) + + +if WINDOWS: + # dummy function, hopefully nobody wants to use visual mode on windows + def usleep(x: int) -> None: + pass +else: + declare usleep(x: int) -> int + + +def main() -> int: + # This file is how I finally learned Dijkstra's algorithm :) + # Set to true to see what I saw. (Something else than windows required.) + visual_mode = False + + f = fopen("sampleinput.txt", "r") + assert f != NULL + grid = read_grid_from_file(f) + fclose(f) + + int_max = 0x7fffffff # TODO: belongs to stdlib + + assert grid.width <= 150 + assert grid.height <= 150 + smallest_scores: int[4][150][150]* = malloc(sizeof(*smallest_scores)) + assert smallest_scores != NULL + + # (*smallest_scores)[x][y][direction as 0123] = shortest path to x,y counting turns + for x = 0; x < grid.width; x++: + for y = 0; y < grid.height; y++: + (*smallest_scores)[x][y] = [int_max, int_max, int_max, int_max] + + todo: State[2000] + todo[0] = State{place = grid.find_first('S'), direction = [1, 0]} + todo_len = 1 + while todo_len > 0: + # Pop from front (fast enough because todo list is short) + # I tried popping from end, that causes lots of unnecessary recalculating. + state = todo[0] + todo_len-- + memmove(&todo[0], &todo[1], todo_len * sizeof(todo[0])) + + scoreptr = &(*smallest_scores)[state.place[0]][state.place[1]][direction_to_0123(state.direction)] + + # Ignore states that have ran into walls. + if grid.get(state.place) == '#': + continue + + # Ignore this state if some other todo list item has already reached + # the same place with a better score. That other todo list item will do + # a better job when discovering other paths. + # + # This is the thing I didn't realize earlier :) + if state.score < *scoreptr: + *scoreptr = state.score + next_states: State[3] = state.next_states() + assert todo_len + 3 <= sizeof(todo)/sizeof(todo[0]) + memcpy(&todo[todo_len], &next_states, sizeof(next_states)) + todo_len += 3 + + if visual_mode: + visualize(&grid, smallest_scores, todo, todo_len) + usleep(100000) + + pos = grid.find_first('E') + best = min4( + (*smallest_scores)[pos[0]][pos[1]][0], + (*smallest_scores)[pos[0]][pos[1]][1], + (*smallest_scores)[pos[0]][pos[1]][2], + (*smallest_scores)[pos[0]][pos[1]][3], + ) + printf("%d\n", best) # Output: 7036 + + free(grid.data) + free(smallest_scores) + return 0 diff --git a/examples/aoc2024/day16/part2.jou b/examples/aoc2024/day16/part2.jou new file mode 100644 index 00000000..32bbe329 --- /dev/null +++ b/examples/aoc2024/day16/part2.jou @@ -0,0 +1,137 @@ +import "stdlib/io.jou" +import "stdlib/math.jou" +import "stdlib/mem.jou" +import "../../aoc2023/grid.jou" + + +class State: + place: int[2] + direction: int[2] + score: int + source: StateStats* # where we came from + + def next_states(self, stats_of_this_state: StateStats*) -> State[3]: + x = self->place[0] + y = self->place[1] + dx = self->direction[0] + dy = self->direction[1] + + assert stats_of_this_state->x == x + assert stats_of_this_state->y == y + + return [ + # Go forward + State{place=[x+dx, y+dy], direction=[dx, dy], score=self->score + 1, source=stats_of_this_state}, + # Turn both ways + State{place=[x, y], direction=[-dy, dx], score=self->score + 1000, source=stats_of_this_state}, + State{place=[x, y], direction=[dy, -dx], score=self->score + 1000, source=stats_of_this_state}, + ] + + +def direction_to_0123(dir: int[2]) -> int: + if dir[0] == 1 and dir[1] == 0: + return 0 # right + if dir[0] == 0 and dir[1] == 1: + return 1 # down + if dir[0] == -1 and dir[1] == 0: + return 2 # left + if dir[0] == 0 and dir[1] == -1: + return 3 # up + assert False + + +class StateStats: + best_score: int + sources: void*[10] # TODO: https://github.com/Akuli/jou/issues/473 + sources_len: int + x: int + y: int + + def append_source(self, new_source: StateStats*) -> None: + if new_source != NULL: + assert self->sources_len < sizeof(self->sources)/sizeof(self->sources[0]) + self->sources[self->sources_len++] = new_source + + +def min4(a: int, b: int, c: int, d: int) -> int: + return min(min(a, b), min(c, d)) + + +# Traverse backwards to find all ways to reach best score, mark them on grid +def mark_best_paths(grid: Grid*, dest_stats: StateStats*) -> None: + assert dest_stats != NULL + grid->set([dest_stats->x, dest_stats->y], 'O') + for i = 0; i < dest_stats->sources_len; i++: + mark_best_paths(grid, dest_stats->sources[i]) + + +def main() -> int: + f = fopen("sampleinput.txt", "r") + assert f != NULL + grid = read_grid_from_file(f) + fclose(f) + + int_max = 0x7fffffff # TODO: belongs to stdlib + + assert grid.width <= 150 + assert grid.height <= 150 + all_stats: StateStats[4][150][150]* = malloc(sizeof(*all_stats)) + assert all_stats != NULL + + # (*all_stats)[x][y][direction as 0123] = shortest path to x,y counting turns + for x = 0; x < grid.width; x++: + for y = 0; y < grid.height; y++: + (*all_stats)[x][y] = [ + StateStats{x=x, y=y, best_score=int_max}, + StateStats{x=x, y=y, best_score=int_max}, + StateStats{x=x, y=y, best_score=int_max}, + StateStats{x=x, y=y, best_score=int_max}, + ] + + todo: State[2000] + todo[0] = State{place = grid.find_first('S'), direction = [1, 0], source = NULL} + todo_len = 1 + + while todo_len > 0: + # Pop from front (fast enough because todo list is short) + state = todo[0] + todo_len-- + memmove(&todo[0], &todo[1], todo_len * sizeof(todo[0])) + + stats = &(*all_stats)[state.place[0]][state.place[1]][direction_to_0123(state.direction)] + + # Ignore states that have ran into walls. + if grid.get(state.place) == '#': + continue + + if state.score == stats->best_score: + stats->append_source(state.source) + elif state.score < stats->best_score: + stats->best_score = state.score + stats->sources_len = 0 + stats->append_source(state.source) + + next_states: State[3] = state.next_states(stats) + assert todo_len + 3 <= sizeof(todo)/sizeof(todo[0]) + memcpy(&todo[todo_len], &next_states, sizeof(next_states)) + todo_len += 3 + + pos = grid.find_first('E') + + # Make sure part 1 wasn't broken + best = min4( + (*all_stats)[pos[0]][pos[1]][0].best_score, + (*all_stats)[pos[0]][pos[1]][1].best_score, + (*all_stats)[pos[0]][pos[1]][2].best_score, + (*all_stats)[pos[0]][pos[1]][3].best_score, + ) + printf("%d\n", best) # Output: 7036 + + for s = &(*all_stats)[pos[0]][pos[1]][0]; s < &(*all_stats)[pos[0]][pos[1]][4]; s++: + if s->best_score == best: + mark_best_paths(&grid, s) + printf("%d\n", grid.count('O')) # Output: 45 + + free(grid.data) + free(all_stats) + return 0 diff --git a/examples/aoc2024/day16/sampleinput.txt b/examples/aoc2024/day16/sampleinput.txt new file mode 100644 index 00000000..2c21676c --- /dev/null +++ b/examples/aoc2024/day16/sampleinput.txt @@ -0,0 +1,15 @@ +############### +#.......#....E# +#.#.###.#.###.# +#.....#.#...#.# +#.###.#####.#.# +#.#.#.......#.# +#.#.#####.###.# +#...........#.# +###.#.#####.#.# +#...#.....#.#.# +#.#.#.###.#.#.# +#.....#...#.#.# +#.###.#.#.#.#.# +#S..#.....#...# +###############