Compare commits
	
		
			55 Commits
		
	
	
		
			7bcab0b085
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 93e365d10b | |||
| 9300d786f8 | |||
| ec75800078 | |||
| 16a0d9b3f8 | |||
| d830919adc | |||
| 056e29b96c | |||
| 47fbeb1ce9 | |||
| 4826c0d21e | |||
| 0950e76d8d | |||
| 5921a90c73 | |||
| 4a429bd62b | |||
| 953716b95a | |||
| 43c6d9e325 | |||
| 927feb071e | |||
| 5107cf7f82 | |||
| 9a7eef5186 | |||
| dd1e8a81fd | |||
| 05f1f99ac1 | |||
| befa6403ba | |||
| ab60a501f5 | |||
| 6ab8ef6d26 | |||
| ad4adc2372 | |||
| 6caa3516e6 | |||
| c23ff2ea4b | |||
| 60a9ee3d2d | |||
| a6edde9139 | |||
| 2d0001ec12 | |||
| 32fc0862a7 | |||
| 8dddcd6224 | |||
| 242eaeb634 | |||
| 2cbd13d224 | |||
| a35be7ec92 | |||
| cee23e5034 | |||
| 679e03217b | |||
| 402372f802 | |||
| c525fdc3d7 | |||
| da0f00375b | |||
| 1ed3f6d981 | |||
| 5e304ef1e5 | |||
| 1f0389071c | |||
| 6374452e24 | |||
| da56d30760 | |||
| 0d89355f86 | |||
| 9a1b3bedc2 | |||
| 03063514a5 | |||
| 52a253c1ea | |||
| c97fd6e8af | |||
| acdb212ddd | |||
| 6071366ed6 | |||
| eeebb6e721 | |||
| 54d93d6a1b | |||
| 4fb226448f | |||
| 590ba743a1 | |||
| 11277f0677 | |||
| b45cc17e0d | 
							
								
								
									
										75
									
								
								day14.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								day14.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | |||||||
|  | import re | ||||||
|  | import pygame | ||||||
|  |  | ||||||
|  | with open("res/inputs/day14.txt", "r") as f: | ||||||
|  |     lines = f.read().split("\n") | ||||||
|  |  | ||||||
|  | bots = [] | ||||||
|  |  | ||||||
|  | for line in lines: | ||||||
|  |     m = re.match("^p=(.*?),(.*?) v=(.*?),(.*?)$", line) | ||||||
|  |     bot = { | ||||||
|  |         "pos": { | ||||||
|  |             "x": int(m.group(1)), | ||||||
|  |             "y": int(m.group(2)), | ||||||
|  |         }, | ||||||
|  |         "vel": { | ||||||
|  |             "x": int(m.group(3)), | ||||||
|  |             "y": int(m.group(4)), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     bots.append(bot) | ||||||
|  |  | ||||||
|  | W = 101 | ||||||
|  | H = 103 | ||||||
|  | pygame.init() | ||||||
|  | win = pygame.display.set_mode([W, H]) | ||||||
|  |  | ||||||
|  | def display(): | ||||||
|  |     win.fill(0) | ||||||
|  |     for bot in bots: | ||||||
|  |         win.set_at((bot["pos"]["x"], bot["pos"]["y"]), (255, 255, 255)) | ||||||
|  |      | ||||||
|  |     pygame.display.flip() | ||||||
|  |  | ||||||
|  | def move(f): | ||||||
|  |     for bot in bots: | ||||||
|  |         bot["pos"]["x"] = (bot["pos"]["x"] + bot["vel"]["x"] * f) % W | ||||||
|  |         bot["pos"]["y"] = (bot["pos"]["y"] + bot["vel"]["y"] * f) % H | ||||||
|  |  | ||||||
|  | clock = pygame.time.Clock() | ||||||
|  | running = True | ||||||
|  | step = 0 | ||||||
|  | update = True | ||||||
|  | auto = True | ||||||
|  |  | ||||||
|  | while running: | ||||||
|  |     for event in pygame.event.get(): | ||||||
|  |         if event.type == pygame.QUIT: | ||||||
|  |             running = False | ||||||
|  |         elif event.type == pygame.KEYDOWN: | ||||||
|  |             if event.key == pygame.K_ESCAPE: | ||||||
|  |                 running = False | ||||||
|  |             elif event.key == pygame.K_RIGHT: | ||||||
|  |                 step += 1 | ||||||
|  |                 move(1) | ||||||
|  |                 update = True | ||||||
|  |             elif event.key == pygame.K_LEFT: | ||||||
|  |                 step -= 1 | ||||||
|  |                 move(-1) | ||||||
|  |                 update = True | ||||||
|  |             elif event.key == pygame.K_SPACE: | ||||||
|  |                 auto = not auto | ||||||
|  |      | ||||||
|  |     if auto: | ||||||
|  |         move(1) | ||||||
|  |         step += 1 | ||||||
|  |         update = True | ||||||
|  |      | ||||||
|  |     if update: | ||||||
|  |         print(step) | ||||||
|  |         pygame.display.set_caption(f"Day 14 - step {step} - {clock.get_fps():.2f}fps") | ||||||
|  |         display() | ||||||
|  |         update = False | ||||||
|  |      | ||||||
|  |     clock.tick(20) | ||||||
							
								
								
									
										26
									
								
								day22.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								day22.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | with open("res/inputs/day22.txt", "r") as f: | ||||||
|  |     values = list(map(int, f.read().split("\n"))) | ||||||
|  |  | ||||||
|  | calls = 0 | ||||||
|  |  | ||||||
|  | def step(a, shift): | ||||||
|  |     global calls | ||||||
|  |     calls += 1 | ||||||
|  |     s = (a >> -shift) if shift < 0 else (a << shift) | ||||||
|  |     return (s ^ a) & 0xffffff | ||||||
|  |  | ||||||
|  | def rand(value): | ||||||
|  |     b = step(value, 6) | ||||||
|  |     c = step(b, -5) | ||||||
|  |     d = step(c, 11) | ||||||
|  |     return d | ||||||
|  |  | ||||||
|  |  | ||||||
|  | total = 0 | ||||||
|  | for v in values: | ||||||
|  |     for _ in range(2000): | ||||||
|  |         v = rand(v) | ||||||
|  |     total += v | ||||||
|  |  | ||||||
|  | print(total) | ||||||
|  | print(calls) | ||||||
							
								
								
									
										
											BIN
										
									
								
								progress.png
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								progress.png
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 135 KiB | 
| @@ -6,3 +6,45 @@ | |||||||
|   stars: 2 |   stars: 2 | ||||||
| 4: | 4: | ||||||
|   stars: 2 |   stars: 2 | ||||||
|  | 5: | ||||||
|  |   stars: 2 | ||||||
|  | 6: | ||||||
|  |   stars: 2 | ||||||
|  | 7: | ||||||
|  |   stars: 2 | ||||||
|  | 8: | ||||||
|  |   stars: 2 | ||||||
|  | 9: | ||||||
|  |   stars: 2 | ||||||
|  | 10: | ||||||
|  |   stars: 2 | ||||||
|  | 11: | ||||||
|  |   stars: 2 | ||||||
|  | 12: | ||||||
|  |   stars: 2 | ||||||
|  | 13: | ||||||
|  |   stars: 2 | ||||||
|  | 14: | ||||||
|  |   stars: 2 | ||||||
|  | 15: | ||||||
|  |   stars: 2 | ||||||
|  | 16: | ||||||
|  |   stars: 0 | ||||||
|  | 17: | ||||||
|  |   stars: 1 | ||||||
|  | 18: | ||||||
|  |   stars: 2 | ||||||
|  | 19: | ||||||
|  |   stars: 2 | ||||||
|  | 20: | ||||||
|  |   stars: 1 | ||||||
|  | 21: | ||||||
|  |   stars: 0 | ||||||
|  | 22: | ||||||
|  |   stars: 1 | ||||||
|  | 23: | ||||||
|  |   stars: 2 | ||||||
|  | 24: | ||||||
|  |   stars: 1 | ||||||
|  | 25: | ||||||
|  |   stars: 1 | ||||||
							
								
								
									
										8
									
								
								res/examples/day10.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								res/examples/day10.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | 89010123 | ||||||
|  | 78121874 | ||||||
|  | 87430965 | ||||||
|  | 96549874 | ||||||
|  | 45678903 | ||||||
|  | 32019012 | ||||||
|  | 01329801 | ||||||
|  | 10456732 | ||||||
							
								
								
									
										1
									
								
								res/examples/day11.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								res/examples/day11.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | 125 17 | ||||||
							
								
								
									
										4
									
								
								res/examples/day12_1.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								res/examples/day12_1.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | AAAA | ||||||
|  | BBCD | ||||||
|  | BBCC | ||||||
|  | EEEC | ||||||
							
								
								
									
										5
									
								
								res/examples/day12_2.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								res/examples/day12_2.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | OOOOO | ||||||
|  | OXOXO | ||||||
|  | OOOOO | ||||||
|  | OXOXO | ||||||
|  | OOOOO | ||||||
							
								
								
									
										10
									
								
								res/examples/day12_3.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								res/examples/day12_3.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | RRRRIICCFF | ||||||
|  | RRRRIICCCF | ||||||
|  | VVRRRCCFFF | ||||||
|  | VVRCCCJFFF | ||||||
|  | VVVVCJJCFE | ||||||
|  | VVIVCCJJEE | ||||||
|  | VVIIICJJEE | ||||||
|  | MIIIIIJJEE | ||||||
|  | MIIISIJEEE | ||||||
|  | MMMISSJEEE | ||||||
							
								
								
									
										5
									
								
								res/examples/day12_4.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								res/examples/day12_4.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | EEEEE | ||||||
|  | EXXXX | ||||||
|  | EEEEE | ||||||
|  | EXXXX | ||||||
|  | EEEEE | ||||||
							
								
								
									
										6
									
								
								res/examples/day12_5.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								res/examples/day12_5.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | AAAAAA | ||||||
|  | AAABBA | ||||||
|  | AAABBA | ||||||
|  | ABBAAA | ||||||
|  | ABBAAA | ||||||
|  | AAAAAA | ||||||
							
								
								
									
										15
									
								
								res/examples/day13.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								res/examples/day13.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										12
									
								
								res/examples/day14.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								res/examples/day14.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										10
									
								
								res/examples/day15_1.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								res/examples/day15_1.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | ######## | ||||||
|  | #..O.O.# | ||||||
|  | ##@.O..# | ||||||
|  | #...O..# | ||||||
|  | #.#.O..# | ||||||
|  | #...O..# | ||||||
|  | #......# | ||||||
|  | ######## | ||||||
|  |  | ||||||
|  | <^^>>>vv<v>>v<< | ||||||
							
								
								
									
										21
									
								
								res/examples/day15_2.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								res/examples/day15_2.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | ########## | ||||||
|  | #..O..O.O# | ||||||
|  | #......O.# | ||||||
|  | #.OO..O.O# | ||||||
|  | #..O@..O.# | ||||||
|  | #O#..O...# | ||||||
|  | #O..O..O.# | ||||||
|  | #.OO.O.OO# | ||||||
|  | #....O...# | ||||||
|  | ########## | ||||||
|  |  | ||||||
|  | <vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^ | ||||||
|  | vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v | ||||||
|  | ><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv< | ||||||
|  | <<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ | ||||||
|  | ^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^>< | ||||||
|  | ^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^ | ||||||
|  | >^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ | ||||||
|  | <><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> | ||||||
|  | ^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v> | ||||||
|  | v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^ | ||||||
							
								
								
									
										15
									
								
								res/examples/day16_1.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								res/examples/day16_1.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | ############### | ||||||
|  | #.......#....E# | ||||||
|  | #.#.###.#.###.# | ||||||
|  | #.....#.#...#.# | ||||||
|  | #.###.#####.#.# | ||||||
|  | #.#.#.......#.# | ||||||
|  | #.#.#####.###.# | ||||||
|  | #...........#.# | ||||||
|  | ###.#.#####.#.# | ||||||
|  | #...#.....#.#.# | ||||||
|  | #.#.#.###.#.#.# | ||||||
|  | #.....#...#.#.# | ||||||
|  | #.###.#.#.#.#.# | ||||||
|  | #S..#.....#...# | ||||||
|  | ############### | ||||||
							
								
								
									
										17
									
								
								res/examples/day16_2.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								res/examples/day16_2.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | ################# | ||||||
|  | #...#...#...#..E# | ||||||
|  | #.#.#.#.#.#.#.#.# | ||||||
|  | #.#.#.#...#...#.# | ||||||
|  | #.#.#.#.###.#.#.# | ||||||
|  | #...#.#.#.....#.# | ||||||
|  | #.#.#.#.#.#####.# | ||||||
|  | #.#...#.#.#.....# | ||||||
|  | #.#.#####.#.###.# | ||||||
|  | #.#.#.......#...# | ||||||
|  | #.#.###.#####.### | ||||||
|  | #.#.#...#.....#.# | ||||||
|  | #.#.#.#####.###.# | ||||||
|  | #.#.#.........#.# | ||||||
|  | #.#.#.#########.# | ||||||
|  | #S#.............# | ||||||
|  | ################# | ||||||
							
								
								
									
										5
									
								
								res/examples/day17_1.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								res/examples/day17_1.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | Register A: 729 | ||||||
|  | Register B: 0 | ||||||
|  | Register C: 0 | ||||||
|  |  | ||||||
|  | Program: 0,1,5,4,3,0 | ||||||
							
								
								
									
										5
									
								
								res/examples/day17_2.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								res/examples/day17_2.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | Register A: 2024 | ||||||
|  | Register B: 0 | ||||||
|  | Register C: 0 | ||||||
|  |  | ||||||
|  | Program: 0,3,5,4,3,0 | ||||||
							
								
								
									
										25
									
								
								res/examples/day18.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								res/examples/day18.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | 5,4 | ||||||
|  | 4,2 | ||||||
|  | 4,5 | ||||||
|  | 3,0 | ||||||
|  | 2,1 | ||||||
|  | 6,3 | ||||||
|  | 2,4 | ||||||
|  | 1,5 | ||||||
|  | 0,6 | ||||||
|  | 3,3 | ||||||
|  | 2,6 | ||||||
|  | 5,1 | ||||||
|  | 1,2 | ||||||
|  | 5,5 | ||||||
|  | 2,5 | ||||||
|  | 6,5 | ||||||
|  | 1,4 | ||||||
|  | 0,4 | ||||||
|  | 6,4 | ||||||
|  | 1,1 | ||||||
|  | 6,1 | ||||||
|  | 1,0 | ||||||
|  | 0,5 | ||||||
|  | 1,6 | ||||||
|  | 2,0 | ||||||
							
								
								
									
										10
									
								
								res/examples/day19.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								res/examples/day19.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | r, wr, b, g, bwu, rb, gb, br | ||||||
|  |  | ||||||
|  | brwrr | ||||||
|  | bggr | ||||||
|  | gbbr | ||||||
|  | rrbgbr | ||||||
|  | ubwu | ||||||
|  | bwurrg | ||||||
|  | brgr | ||||||
|  | bbrgwb | ||||||
							
								
								
									
										15
									
								
								res/examples/day20.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								res/examples/day20.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | ############### | ||||||
|  | #...#...#.....# | ||||||
|  | #.#.#.#.#.###.# | ||||||
|  | #S#...#.#.#...# | ||||||
|  | #######.#.#.### | ||||||
|  | #######.#.#...# | ||||||
|  | #######.#.###.# | ||||||
|  | ###..E#...#...# | ||||||
|  | ###.#######.### | ||||||
|  | #...###...#...# | ||||||
|  | #.#####.#.###.# | ||||||
|  | #.#...#.#.#...# | ||||||
|  | #.#.#.#.#.#.### | ||||||
|  | #...#...#...### | ||||||
|  | ############### | ||||||
							
								
								
									
										5
									
								
								res/examples/day21.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								res/examples/day21.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | 029A | ||||||
|  | 980A | ||||||
|  | 179A | ||||||
|  | 456A | ||||||
|  | 379A | ||||||
							
								
								
									
										1
									
								
								res/examples/day22.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								res/examples/day22.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | 123 | ||||||
							
								
								
									
										32
									
								
								res/examples/day23.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								res/examples/day23.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | kh-tc | ||||||
|  | qp-kh | ||||||
|  | de-cg | ||||||
|  | ka-co | ||||||
|  | yn-aq | ||||||
|  | qp-ub | ||||||
|  | cg-tb | ||||||
|  | vc-aq | ||||||
|  | tb-ka | ||||||
|  | wh-tc | ||||||
|  | yn-cg | ||||||
|  | kh-ub | ||||||
|  | ta-co | ||||||
|  | de-co | ||||||
|  | tc-td | ||||||
|  | tb-wq | ||||||
|  | wh-td | ||||||
|  | ta-ka | ||||||
|  | td-qp | ||||||
|  | aq-cg | ||||||
|  | wq-ub | ||||||
|  | ub-vc | ||||||
|  | de-ta | ||||||
|  | wq-aq | ||||||
|  | wq-vc | ||||||
|  | wh-yn | ||||||
|  | ka-de | ||||||
|  | kh-ta | ||||||
|  | co-tc | ||||||
|  | wh-qp | ||||||
|  | tb-vc | ||||||
|  | td-yn | ||||||
							
								
								
									
										10
									
								
								res/examples/day24_1.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								res/examples/day24_1.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | x00: 1 | ||||||
|  | x01: 1 | ||||||
|  | x02: 1 | ||||||
|  | y00: 0 | ||||||
|  | y01: 1 | ||||||
|  | y02: 0 | ||||||
|  |  | ||||||
|  | x00 AND y00 -> z00 | ||||||
|  | x01 XOR y01 -> z01 | ||||||
|  | x02 OR y02 -> z02 | ||||||
							
								
								
									
										47
									
								
								res/examples/day24_2.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								res/examples/day24_2.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | x00: 1 | ||||||
|  | x01: 0 | ||||||
|  | x02: 1 | ||||||
|  | x03: 1 | ||||||
|  | x04: 0 | ||||||
|  | y00: 1 | ||||||
|  | y01: 1 | ||||||
|  | y02: 1 | ||||||
|  | y03: 1 | ||||||
|  | y04: 1 | ||||||
|  |  | ||||||
|  | ntg XOR fgs -> mjb | ||||||
|  | y02 OR x01 -> tnw | ||||||
|  | kwq OR kpj -> z05 | ||||||
|  | x00 OR x03 -> fst | ||||||
|  | tgd XOR rvg -> z01 | ||||||
|  | vdt OR tnw -> bfw | ||||||
|  | bfw AND frj -> z10 | ||||||
|  | ffh OR nrd -> bqk | ||||||
|  | y00 AND y03 -> djm | ||||||
|  | y03 OR y00 -> psh | ||||||
|  | bqk OR frj -> z08 | ||||||
|  | tnw OR fst -> frj | ||||||
|  | gnj AND tgd -> z11 | ||||||
|  | bfw XOR mjb -> z00 | ||||||
|  | x03 OR x00 -> vdt | ||||||
|  | gnj AND wpb -> z02 | ||||||
|  | x04 AND y00 -> kjc | ||||||
|  | djm OR pbm -> qhw | ||||||
|  | nrd AND vdt -> hwm | ||||||
|  | kjc AND fst -> rvg | ||||||
|  | y04 OR y02 -> fgs | ||||||
|  | y01 AND x02 -> pbm | ||||||
|  | ntg OR kjc -> kwq | ||||||
|  | psh XOR fgs -> tgd | ||||||
|  | qhw XOR tgd -> z09 | ||||||
|  | pbm OR djm -> kpj | ||||||
|  | x03 XOR y03 -> ffh | ||||||
|  | x00 XOR y04 -> ntg | ||||||
|  | bfw OR bqk -> z06 | ||||||
|  | nrd XOR fgs -> wpb | ||||||
|  | frj XOR qhw -> z04 | ||||||
|  | bqk OR frj -> z07 | ||||||
|  | y03 OR x01 -> nrd | ||||||
|  | hwm AND bqk -> z03 | ||||||
|  | tgd XOR rvg -> z12 | ||||||
|  | tnw OR pbm -> gnj | ||||||
							
								
								
									
										39
									
								
								res/examples/day25.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								res/examples/day25.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | ##### | ||||||
|  | .#### | ||||||
|  | .#### | ||||||
|  | .#### | ||||||
|  | .#.#. | ||||||
|  | .#... | ||||||
|  | ..... | ||||||
|  |  | ||||||
|  | ##### | ||||||
|  | ##.## | ||||||
|  | .#.## | ||||||
|  | ...## | ||||||
|  | ...#. | ||||||
|  | ...#. | ||||||
|  | ..... | ||||||
|  |  | ||||||
|  | ..... | ||||||
|  | #.... | ||||||
|  | #.... | ||||||
|  | #...# | ||||||
|  | #.#.# | ||||||
|  | #.### | ||||||
|  | ##### | ||||||
|  |  | ||||||
|  | ..... | ||||||
|  | ..... | ||||||
|  | #.#.. | ||||||
|  | ###.. | ||||||
|  | ###.# | ||||||
|  | ###.# | ||||||
|  | ##### | ||||||
|  |  | ||||||
|  | ..... | ||||||
|  | ..... | ||||||
|  | ..... | ||||||
|  | #.... | ||||||
|  | #.#.. | ||||||
|  | #.#.# | ||||||
|  | ##### | ||||||
							
								
								
									
										28
									
								
								res/examples/day5.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								res/examples/day5.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | 47|53 | ||||||
|  | 97|13 | ||||||
|  | 97|61 | ||||||
|  | 97|47 | ||||||
|  | 75|29 | ||||||
|  | 61|13 | ||||||
|  | 75|53 | ||||||
|  | 29|13 | ||||||
|  | 97|29 | ||||||
|  | 53|29 | ||||||
|  | 61|53 | ||||||
|  | 97|53 | ||||||
|  | 61|29 | ||||||
|  | 47|13 | ||||||
|  | 75|47 | ||||||
|  | 97|75 | ||||||
|  | 47|61 | ||||||
|  | 75|61 | ||||||
|  | 47|29 | ||||||
|  | 75|13 | ||||||
|  | 53|13 | ||||||
|  |  | ||||||
|  | 75,47,61,53,29 | ||||||
|  | 97,61,53,29,13 | ||||||
|  | 75,29,13 | ||||||
|  | 75,97,47,61,53 | ||||||
|  | 61,13,29 | ||||||
|  | 97,13,75,29,47 | ||||||
							
								
								
									
										10
									
								
								res/examples/day6.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								res/examples/day6.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | ....#..... | ||||||
|  | .........# | ||||||
|  | .......... | ||||||
|  | ..#....... | ||||||
|  | .......#.. | ||||||
|  | .......... | ||||||
|  | .#..^..... | ||||||
|  | ........#. | ||||||
|  | #......... | ||||||
|  | ......#... | ||||||
							
								
								
									
										9
									
								
								res/examples/day7.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								res/examples/day7.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | 190: 10 19 | ||||||
|  | 3267: 81 40 27 | ||||||
|  | 83: 17 5 | ||||||
|  | 156: 15 6 | ||||||
|  | 7290: 6 8 6 15 | ||||||
|  | 161011: 16 10 13 | ||||||
|  | 192: 17 8 14 | ||||||
|  | 21037: 9 7 18 13 | ||||||
|  | 292: 11 6 16 20 | ||||||
							
								
								
									
										12
									
								
								res/examples/day8.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								res/examples/day8.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | ............ | ||||||
|  | ........0... | ||||||
|  | .....0...... | ||||||
|  | .......0.... | ||||||
|  | ....0....... | ||||||
|  | ......A..... | ||||||
|  | ............ | ||||||
|  | ............ | ||||||
|  | ........A... | ||||||
|  | .........A.. | ||||||
|  | ............ | ||||||
|  | ............ | ||||||
							
								
								
									
										1
									
								
								res/examples/day9_1.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								res/examples/day9_1.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | 12345 | ||||||
							
								
								
									
										1
									
								
								res/examples/day9_2.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								res/examples/day9_2.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | 2333133121414131402 | ||||||
							
								
								
									
										143
									
								
								src/day10/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/day10/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  | #import "@preview/cetz:0.3.1": canvas, draw, matrix | ||||||
|  |  | ||||||
|  | #let offsets = ( | ||||||
|  |   (-1, 0), | ||||||
|  |   (0, -1), | ||||||
|  |   (1, 0), | ||||||
|  |   (0, 1) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let in-grid(w, h, x, y) = { | ||||||
|  |   return 0 <= x and x < w and 0 <= y and y < h | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let count-paths(grid, w, h, ox, oy) = { | ||||||
|  |   let tails = () | ||||||
|  |   let in-grid = in-grid.with(w, h) | ||||||
|  |  | ||||||
|  |   let to-visit = ((ox, oy),) | ||||||
|  |   while to-visit.len() != 0 { | ||||||
|  |     let (x, y) = to-visit.remove(0) | ||||||
|  |     let v = grid.at(y).at(x) | ||||||
|  |     for (dx, dy) in offsets { | ||||||
|  |       let (x2, y2) = (x + dx, y + dy) | ||||||
|  |       if not in-grid(x2, y2) { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       let v2 = grid.at(y2).at(x2) | ||||||
|  |       if v2 == v + 1 { | ||||||
|  |         let pos2 = (x2, y2) | ||||||
|  |         if v2 == 9 { | ||||||
|  |           if pos2 not in tails { | ||||||
|  |             tails.push(pos2) | ||||||
|  |           } | ||||||
|  |         } else { | ||||||
|  |           to-visit.push(pos2) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return tails.len() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let get-paths(grid, w, h, ox, oy) = { | ||||||
|  |   let paths = () | ||||||
|  |   let tails = () | ||||||
|  |   let in-grid = in-grid.with(w, h) | ||||||
|  |  | ||||||
|  |   let to-visit = ((ox, oy),) | ||||||
|  |   while to-visit.len() != 0 { | ||||||
|  |     let (x, y) = to-visit.remove(0) | ||||||
|  |     let v = grid.at(y).at(x) | ||||||
|  |     for (dx, dy) in offsets { | ||||||
|  |       let (x2, y2) = (x + dx, y + dy) | ||||||
|  |       if not in-grid(x2, y2) { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       let v2 = grid.at(y2).at(x2) | ||||||
|  |       if v2 == v + 1 { | ||||||
|  |         let pos2 = (x2, y2) | ||||||
|  |         if v2 == 9 { | ||||||
|  |           if pos2 not in tails { | ||||||
|  |             tails.push(pos2) | ||||||
|  |             paths.push(((ox, oy), (x2, y2))) | ||||||
|  |           } | ||||||
|  |         } else { | ||||||
|  |           to-visit.push(pos2) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return paths | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let grid = input.split("\n").map(l => l.clusters().map(int)) | ||||||
|  |  | ||||||
|  |   let w = grid.first().len() | ||||||
|  |   let h = grid.len() | ||||||
|  |  | ||||||
|  |   let count-paths = count-paths.with(grid, w, h) | ||||||
|  |  | ||||||
|  |   let total = 0 | ||||||
|  |   for y in range(h) { | ||||||
|  |     for x in range(w) { | ||||||
|  |       if grid.at(y).at(x) == 0 { | ||||||
|  |         total += count-paths(x, y) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let visualize(input) = { | ||||||
|  |   let grid = input.split("\n").map(l => l.clusters().map(int)) | ||||||
|  |  | ||||||
|  |   let w = grid.first().len() | ||||||
|  |   let h = grid.len() | ||||||
|  |  | ||||||
|  |   let count-paths = count-paths.with(grid, w, h) | ||||||
|  |   let get-paths = get-paths.with(grid, w, h) | ||||||
|  |  | ||||||
|  |   let total = 0 | ||||||
|  |   canvas({ | ||||||
|  |     let starts = () | ||||||
|  |     let c | ||||||
|  |     for y in range(h) { | ||||||
|  |       for x in range(w) { | ||||||
|  |         c = grid.at(y).at(x) | ||||||
|  |         draw.rect( | ||||||
|  |           (x, -y), | ||||||
|  |           (x + 1, -y - 1), | ||||||
|  |           fill: black.lighten((9 - c) / 9 * 90% + 10%) | ||||||
|  |         ) | ||||||
|  |         if c == 0 { | ||||||
|  |           starts.push((x, y)) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     for (ox, oy) in starts { | ||||||
|  |       let paths = get-paths(ox, oy) | ||||||
|  |       for path in paths { | ||||||
|  |         draw.line( | ||||||
|  |           ..path.map( | ||||||
|  |             ((x, y)) => (x + .5, -y - .5) | ||||||
|  |           ), | ||||||
|  |           stroke: red, | ||||||
|  |           mark: (end: ">") | ||||||
|  |         ) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   10, 1, | ||||||
|  |   solve, | ||||||
|  |   example: 36, | ||||||
|  |   visualize: visualize | ||||||
|  | ) | ||||||
							
								
								
									
										74
									
								
								src/day10/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/day10/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  | #let offsets = ( | ||||||
|  |   (-1, 0), | ||||||
|  |   (0, -1), | ||||||
|  |   (1, 0), | ||||||
|  |   (0, 1) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let in-grid(w, h, x, y) = { | ||||||
|  |   return 0 <= x and x < w and 0 <= y and y < h | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let count-paths(grid, w, h, ox, oy) = { | ||||||
|  |   let in-grid = in-grid.with(w, h) | ||||||
|  |   let rating = 0 | ||||||
|  |  | ||||||
|  |   let to-visit = ((ox, oy),) | ||||||
|  |   while to-visit.len() != 0 { | ||||||
|  |     let (x, y) = to-visit.remove(0) | ||||||
|  |     let v = grid.at(y).at(x) | ||||||
|  |     let branches = 0 | ||||||
|  |     for (dx, dy) in offsets { | ||||||
|  |       let (x2, y2) = (x + dx, y + dy) | ||||||
|  |       if not in-grid(x2, y2) { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       let v2 = grid.at(y2).at(x2) | ||||||
|  |       if v2 == v + 1 { | ||||||
|  |         let pos2 = (x2, y2) | ||||||
|  |         branches += 1 | ||||||
|  |         if v2 != 9 { | ||||||
|  |           to-visit.push(pos2) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     rating += if v == 0 { | ||||||
|  |       branches | ||||||
|  |     } else { | ||||||
|  |       branches - 1  // If no branch -> -1 | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return rating | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let grid = input.split("\n").map(l => l.clusters().map(int)) | ||||||
|  |  | ||||||
|  |   let w = grid.first().len() | ||||||
|  |   let h = grid.len() | ||||||
|  |  | ||||||
|  |   let count-paths = count-paths.with(grid, w, h) | ||||||
|  |  | ||||||
|  |   let ratings = () | ||||||
|  |   let total = 0 | ||||||
|  |   for y in range(h) { | ||||||
|  |     for x in range(w) { | ||||||
|  |       if grid.at(y).at(x) == 0 { | ||||||
|  |         let rating = count-paths(x, y) | ||||||
|  |         total += rating | ||||||
|  |         ratings.push(rating) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   let a = ratings | ||||||
|  |  | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   10, 2, | ||||||
|  |   solve, | ||||||
|  |   example: 81 | ||||||
|  | ) | ||||||
							
								
								
									
										64
									
								
								src/day11/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/day11/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  | #import "@preview/cetz:0.3.1": canvas | ||||||
|  | #import "@preview/cetz-plot:0.1.0": plot | ||||||
|  |  | ||||||
|  | #let process(rock) = { | ||||||
|  |   if rock == 0 { | ||||||
|  |     return (1,) | ||||||
|  |   } | ||||||
|  |   let rock-str = str(rock) | ||||||
|  |   if calc.rem(rock-str.len(), 2) == 0 { | ||||||
|  |     let hl = calc.div-euclid(rock-str.len(), 2) | ||||||
|  |     return ( | ||||||
|  |       int(rock-str.slice(0, hl)), | ||||||
|  |       int(rock-str.slice(hl)) | ||||||
|  |     ) | ||||||
|  |   } | ||||||
|  |   return (rock * 2024,) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let blink(rocks) = { | ||||||
|  |   let new-rocks = () | ||||||
|  |  | ||||||
|  |   for rock in rocks { | ||||||
|  |     new-rocks += process(rock) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return new-rocks | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let rocks = input.split(" ").map(int) | ||||||
|  |   for _ in range(25) { | ||||||
|  |     rocks = blink(rocks) | ||||||
|  |   } | ||||||
|  |   return rocks.len() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let visualize(input) = { | ||||||
|  |   let rocks = input.split(" ").map(int) | ||||||
|  |   let values = (rocks.len(),) | ||||||
|  |   for _ in range(25) { | ||||||
|  |     rocks = blink(rocks) | ||||||
|  |     values.push(rocks.len()) | ||||||
|  |   } | ||||||
|  |   canvas({ | ||||||
|  |     plot.plot( | ||||||
|  |       { | ||||||
|  |         plot.add(range(26).zip(values)) | ||||||
|  |       }, | ||||||
|  |       size: (6,6), | ||||||
|  |       x-tick-step: 5, | ||||||
|  |       y-tick-step: 10000, | ||||||
|  |       x-label: "Blinks", | ||||||
|  |       y-label: "Rocks" | ||||||
|  |     ) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   11, 1, | ||||||
|  |   solve, | ||||||
|  |   example: 55312, | ||||||
|  |   visualize: visualize | ||||||
|  | ) | ||||||
							
								
								
									
										36
									
								
								src/day11/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/day11/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let process(rock, depth) = { | ||||||
|  |   if depth == 0 { | ||||||
|  |     return 1 | ||||||
|  |   } | ||||||
|  |   if rock == 0 { | ||||||
|  |     return process(1, depth - 1) | ||||||
|  |   } | ||||||
|  |   let rock-str = str(rock) | ||||||
|  |   if calc.rem(rock-str.len(), 2) == 0 { | ||||||
|  |     let hl = calc.div-euclid(rock-str.len(), 2) | ||||||
|  |     let a = int(rock-str.slice(0, hl)) | ||||||
|  |     let b = int(rock-str.slice(hl)) | ||||||
|  |     return process(a, depth - 1) + process(b, depth - 1) | ||||||
|  |   } | ||||||
|  |   return process(rock * 2024, depth - 1) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let rocks = input.split(" ").map(int) | ||||||
|  |   let total = 0 | ||||||
|  |   for rock in rocks { | ||||||
|  |     total += process(rock, 75) | ||||||
|  |   } | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   11, 2, | ||||||
|  |   solve, | ||||||
|  |   only-example: true | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Too long to recompile everytime | ||||||
|  | #show-result(228651922369703) | ||||||
							
								
								
									
										68
									
								
								src/day12/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/day12/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let offsets = ( | ||||||
|  |   (-1, 0), | ||||||
|  |   (0, -1), | ||||||
|  |   (1, 0), | ||||||
|  |   (0, 1) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let in-grid(w, h, x, y) = { | ||||||
|  |   return 0 <= x and x < w and 0 <= y and y < h | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let grid = input.split("\n").map(l => l.clusters()) | ||||||
|  |   let w = grid.first().len() | ||||||
|  |   let h = grid.len() | ||||||
|  |   let in-grid = in-grid.with(w, h) | ||||||
|  |  | ||||||
|  |   let visited = ((false,) * w,) * h | ||||||
|  |   let total = 0 | ||||||
|  |  | ||||||
|  |   for y in range(h) { | ||||||
|  |     for x in range(w) { | ||||||
|  |       if visited.at(y).at(x) { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       let char = grid.at(y).at(x) | ||||||
|  |       let cells = () | ||||||
|  |       let borders = 0 | ||||||
|  |       let next-cells = ((x, y),) | ||||||
|  |       while next-cells.len() != 0 { | ||||||
|  |         let (cx, cy) = next-cells.remove(0) | ||||||
|  |         cells.push((cx, cy)) | ||||||
|  |         visited.at(cy).at(cx) = true | ||||||
|  |         for (dx, dy) in offsets { | ||||||
|  |           let (x2, y2) = (cx + dx, cy + dy) | ||||||
|  |           if (x2, y2) in cells or (x2, y2) in next-cells { | ||||||
|  |             continue | ||||||
|  |           } | ||||||
|  |           if in-grid(x2, y2) { | ||||||
|  |             let char2 = grid.at(y2).at(x2) | ||||||
|  |             if char2 == char { | ||||||
|  |               next-cells.push((x2, y2)) | ||||||
|  |             } else { | ||||||
|  |               borders += 1 | ||||||
|  |             } | ||||||
|  |           } else { | ||||||
|  |             borders += 1 | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       total += borders * cells.len() | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   12, 1, | ||||||
|  |   solve, | ||||||
|  |   example: ( | ||||||
|  |     "1": 140, | ||||||
|  |     "2": 772, | ||||||
|  |     "3": 1930 | ||||||
|  |   ) | ||||||
|  | ) | ||||||
							
								
								
									
										151
									
								
								src/day12/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								src/day12/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let offsets = ( | ||||||
|  |   (-1, 0), | ||||||
|  |   (0, -1), | ||||||
|  |   (1, 0), | ||||||
|  |   (0, 1) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let in-grid(w, h, x, y) = { | ||||||
|  |   return 0 <= x and x < w and 0 <= y and y < h | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let grid = input.split("\n").map(l => l.clusters()) | ||||||
|  |   let w = grid.first().len() | ||||||
|  |   let h = grid.len() | ||||||
|  |   let in-grid = in-grid.with(w, h) | ||||||
|  |  | ||||||
|  |   let zone-grid = ((none,) * w,) * h | ||||||
|  |   let zone-id = 0 | ||||||
|  |   let zone-sides = () | ||||||
|  |   let zone-areas = () | ||||||
|  |  | ||||||
|  |   for y in range(h) { | ||||||
|  |     for x in range(w) { | ||||||
|  |       if zone-grid.at(y).at(x) != none { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       let char = grid.at(y).at(x) | ||||||
|  |       let area = 0 | ||||||
|  |       let borders = 0 | ||||||
|  |       let next-cells = ((x, y),) | ||||||
|  |  | ||||||
|  |       while next-cells.len() != 0 { | ||||||
|  |         let (cx, cy) = next-cells.remove(0) | ||||||
|  |         zone-grid.at(cy).at(cx) = zone-id | ||||||
|  |         area += 1 | ||||||
|  |         for (dx, dy) in offsets { | ||||||
|  |           let (x2, y2) = (cx + dx, cy + dy) | ||||||
|  |           if (x2, y2) in next-cells { | ||||||
|  |             continue | ||||||
|  |           } | ||||||
|  |           if in-grid(x2, y2) { | ||||||
|  |             if zone-grid.at(y2).at(x2) == zone-id { | ||||||
|  |               continue | ||||||
|  |             } | ||||||
|  |             let char2 = grid.at(y2).at(x2) | ||||||
|  |             if char2 == char { | ||||||
|  |               next-cells.push((x2, y2)) | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       zone-areas.push(area) | ||||||
|  |       zone-sides.push(0) | ||||||
|  |       zone-id += 1 | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let first-zone0 = -1 | ||||||
|  |   let last-zone0 = -1 | ||||||
|  |   for y in range(h) { | ||||||
|  |     let first-zone = zone-grid.at(y).at(0) | ||||||
|  |     let last-zone = zone-grid.at(y).at(w - 1) | ||||||
|  |     if first-zone0 != first-zone { | ||||||
|  |       zone-sides.at(first-zone) += 1 | ||||||
|  |     } | ||||||
|  |     if last-zone0 != last-zone { | ||||||
|  |       zone-sides.at(last-zone) += 1 | ||||||
|  |     } | ||||||
|  |     first-zone0 = first-zone | ||||||
|  |     last-zone0 = last-zone | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   first-zone0 = -1 | ||||||
|  |   last-zone0 = -1 | ||||||
|  |   for x in range(w) { | ||||||
|  |     let first-zone = zone-grid.at(0).at(x) | ||||||
|  |     let last-zone = zone-grid.at(h - 1).at(x) | ||||||
|  |     if first-zone0 != first-zone { | ||||||
|  |       zone-sides.at(first-zone) += 1 | ||||||
|  |     } | ||||||
|  |     if last-zone0 != last-zone { | ||||||
|  |       zone-sides.at(last-zone) += 1 | ||||||
|  |     } | ||||||
|  |     first-zone0 = first-zone | ||||||
|  |     last-zone0 = last-zone | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for x in range(w - 1) { | ||||||
|  |     let zone-a0 = -1 | ||||||
|  |     let zone-b0 = -1 | ||||||
|  |     for y in range(h) { | ||||||
|  |       let zone-a = zone-grid.at(y).at(x) | ||||||
|  |       let zone-b = zone-grid.at(y).at(x + 1) | ||||||
|  |       if zone-a != zone-b { | ||||||
|  |         if zone-a != zone-a0 { | ||||||
|  |           zone-sides.at(zone-a) += 1 | ||||||
|  |         } | ||||||
|  |         if zone-b != zone-b0 { | ||||||
|  |           zone-sides.at(zone-b) += 1 | ||||||
|  |         } | ||||||
|  |         zone-a0 = zone-a | ||||||
|  |         zone-b0 = zone-b | ||||||
|  |       } else { | ||||||
|  |         zone-a0 = -1 | ||||||
|  |         zone-b0 = -1 | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for y in range(h - 1) { | ||||||
|  |     let zone-a0 = -1 | ||||||
|  |     let zone-b0 = -1 | ||||||
|  |     for x in range(w) { | ||||||
|  |       let zone-a = zone-grid.at(y).at(x) | ||||||
|  |       let zone-b = zone-grid.at(y + 1).at(x) | ||||||
|  |       if zone-a != zone-b { | ||||||
|  |         if zone-a != zone-a0 { | ||||||
|  |           zone-sides.at(zone-a) += 1 | ||||||
|  |         } | ||||||
|  |         if zone-b != zone-b0 { | ||||||
|  |           zone-sides.at(zone-b) += 1 | ||||||
|  |         } | ||||||
|  |         zone-a0 = zone-a | ||||||
|  |         zone-b0 = zone-b | ||||||
|  |       } else { | ||||||
|  |         zone-a0 = -1 | ||||||
|  |         zone-b0 = -1 | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let total = range(zone-id).map(i => { | ||||||
|  |     zone-sides.at(i) * zone-areas.at(i) | ||||||
|  |   }) | ||||||
|  |   return total.sum() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   12, 2, | ||||||
|  |   solve, | ||||||
|  |   example: ( | ||||||
|  |     "1": 80, | ||||||
|  |     "2": 436, | ||||||
|  |     "3": 1206, | ||||||
|  |     "4": 236, | ||||||
|  |     "5": 368 | ||||||
|  |   ) | ||||||
|  | ) | ||||||
							
								
								
									
										86
									
								
								src/day13/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/day13/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let parse-machine(lines) = { | ||||||
|  |   let lines = lines.split("\n") | ||||||
|  |   let match-a = lines.at(0).match(regex("Button A: X\\+(\d+), Y\\+(\d+)")) | ||||||
|  |   let match-b = lines.at(1).match(regex("Button B: X\\+(\d+), Y\\+(\d+)")) | ||||||
|  |   let match-p = lines.at(2).match(regex("Prize: X=(\d+), Y=(\d+)")) | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     a: ( | ||||||
|  |       x: int(match-a.captures.first()), | ||||||
|  |       y: int(match-a.captures.last()), | ||||||
|  |     ), | ||||||
|  |     b: ( | ||||||
|  |       x: int(match-b.captures.first()), | ||||||
|  |       y: int(match-b.captures.last()), | ||||||
|  |     ), | ||||||
|  |     prize: ( | ||||||
|  |       x: int(match-p.captures.first()), | ||||||
|  |       y: int(match-p.captures.last()), | ||||||
|  |     ) | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let in-line(v1, v2) = { | ||||||
|  |   let f1 = v2.x / v1.x | ||||||
|  |   let f2 = v2.y / v1.y | ||||||
|  |   return f1 == f2 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let machines = input.split("\n\n") | ||||||
|  |   machines = machines.map(parse-machine) | ||||||
|  |  | ||||||
|  |   let total = 0 | ||||||
|  |   for m in machines { | ||||||
|  |     let totals = () | ||||||
|  |     let are-inline = in-line(m.a, m.b) | ||||||
|  |     for b in range(101) { | ||||||
|  |       let bx = b * m.b.x | ||||||
|  |       let by = b * m.b.y | ||||||
|  |       let rx = m.prize.x - bx | ||||||
|  |       let ry = m.prize.y - by | ||||||
|  |       if rx < 0 or ry < 0 { | ||||||
|  |         break | ||||||
|  |       } | ||||||
|  |       let rax = calc.rem( | ||||||
|  |         rx, | ||||||
|  |         m.a.x | ||||||
|  |       ) | ||||||
|  |       let ray = calc.rem( | ||||||
|  |         ry, | ||||||
|  |         m.a.y | ||||||
|  |       ) | ||||||
|  |       if rax != 0 or ray != 0 { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       let a1 = calc.div-euclid( | ||||||
|  |         rx, | ||||||
|  |         m.a.x | ||||||
|  |       ) | ||||||
|  |       let a2 = calc.div-euclid( | ||||||
|  |         ry, | ||||||
|  |         m.a.y | ||||||
|  |       ) | ||||||
|  |       if a1 != a2 or a1 > 100 { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       totals.push(b + a1 * 3) | ||||||
|  |       if not are-inline { | ||||||
|  |         break | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if totals.len() != 0 { | ||||||
|  |       total += calc.min(..totals) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   13, 1, | ||||||
|  |   solve, | ||||||
|  |   example: 480 | ||||||
|  | ) | ||||||
							
								
								
									
										74
									
								
								src/day13/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/day13/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let parse-machine(lines) = { | ||||||
|  |   let lines = lines.split("\n") | ||||||
|  |   let match-a = lines.at(0).match(regex("Button A: X\\+(\d+), Y\\+(\d+)")) | ||||||
|  |   let match-b = lines.at(1).match(regex("Button B: X\\+(\d+), Y\\+(\d+)")) | ||||||
|  |   let match-p = lines.at(2).match(regex("Prize: X=(\d+), Y=(\d+)")) | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     a: ( | ||||||
|  |       x: int(match-a.captures.first()), | ||||||
|  |       y: int(match-a.captures.last()), | ||||||
|  |     ), | ||||||
|  |     b: ( | ||||||
|  |       x: int(match-b.captures.first()), | ||||||
|  |       y: int(match-b.captures.last()), | ||||||
|  |     ), | ||||||
|  |     prize: ( | ||||||
|  |       x: int(match-p.captures.first()) + 10000000000000, | ||||||
|  |       y: int(match-p.captures.last()) + 10000000000000, | ||||||
|  |     ) | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let mat-mul-v(mat, v) = { | ||||||
|  |   return ( | ||||||
|  |     mat.at(0).at(0) * v.at(0) + mat.at(0).at(1) * v.at(1), | ||||||
|  |     mat.at(1).at(0) * v.at(0) + mat.at(1).at(1) * v.at(1) | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let det(mat) = { | ||||||
|  |   return mat.at(0).at(0) * mat.at(1).at(1) - mat.at(1).at(0) * mat.at(0).at(1) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let machines = input.split("\n\n") | ||||||
|  |   machines = machines.map(parse-machine) | ||||||
|  |  | ||||||
|  |   let total = 0 | ||||||
|  |   for m in machines { | ||||||
|  |     let mat = ( | ||||||
|  |       (m.a.x, m.b.x), | ||||||
|  |       (m.a.y, m.b.y) | ||||||
|  |     ) | ||||||
|  |     let v = (m.prize.x, m.prize.y) | ||||||
|  |     let mat2 = ( | ||||||
|  |       (mat.at(1).at(1), -mat.at(0).at(1)), | ||||||
|  |       (-mat.at(1).at(0), mat.at(0).at(0)), | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     let (a, b) = mat-mul-v(mat2, v) | ||||||
|  |  | ||||||
|  |     let d = det(mat) | ||||||
|  |  | ||||||
|  |     // Check integer solution | ||||||
|  |     if calc.rem(a, d) != 0 or calc.rem(b, d) != 0 { | ||||||
|  |       continue | ||||||
|  |     } | ||||||
|  |     a = int(a / d) | ||||||
|  |     b = int(b / d) | ||||||
|  |  | ||||||
|  |     if a > 0 and b > 0 { | ||||||
|  |       total += a * 3 + b | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   13, 2, | ||||||
|  |   solve, | ||||||
|  |   example: 875318608908 | ||||||
|  | ) | ||||||
							
								
								
									
										65
									
								
								src/day14/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/day14/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let regexp = regex("^p=(.*?),(.*?) v=(.*?),(.*?)$") | ||||||
|  |  | ||||||
|  | #let simulate(v0, dv, max: 1, steps: 1) = { | ||||||
|  |   return calc.rem-euclid(v0 + dv * steps, max) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let parse-input(input) = { | ||||||
|  |   return input.split("\n").map(b => { | ||||||
|  |     let m = b.match(regexp) | ||||||
|  |     return ( | ||||||
|  |       pos: ( | ||||||
|  |         x: int(m.captures.at(0)), | ||||||
|  |         y: int(m.captures.at(1)), | ||||||
|  |       ), | ||||||
|  |       vel: ( | ||||||
|  |         x: int(m.captures.at(2)), | ||||||
|  |         y: int(m.captures.at(3)) | ||||||
|  |       ) | ||||||
|  |     ) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(w: 0, h: 0, steps: 100, input) = { | ||||||
|  |   assert(w != 0, message: "Width must be != 0") | ||||||
|  |   assert(h != 0, message: "Height must be != 0") | ||||||
|  |  | ||||||
|  |   let bots = parse-input(input) | ||||||
|  |  | ||||||
|  |   let quadrants = ( | ||||||
|  |     tl: 0, | ||||||
|  |     tr: 0, | ||||||
|  |     bl: 0, | ||||||
|  |     br: 0 | ||||||
|  |   ) | ||||||
|  |   let half-w = calc.div-euclid(w, 2) | ||||||
|  |   let half-h = calc.div-euclid(h, 2) | ||||||
|  |  | ||||||
|  |   let sim-x = simulate.with(max: w, steps: steps) | ||||||
|  |   let sim-y = simulate.with(max: h, steps: steps) | ||||||
|  |   for bot in bots { | ||||||
|  |     let x2 = sim-x(bot.pos.x, bot.vel.x) | ||||||
|  |     let y2 = sim-y(bot.pos.y, bot.vel.y) | ||||||
|  |  | ||||||
|  |     if x2 == half-w or y2 == half-h { | ||||||
|  |       continue | ||||||
|  |     } | ||||||
|  |     let quadrant = ( | ||||||
|  |       (if y2 < half-h {"t"} else {"b"}) + | ||||||
|  |       (if x2 < half-w {"l"} else {"r"}) | ||||||
|  |     ) | ||||||
|  |     quadrants.at(quadrant) += 1 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return quadrants.values().product() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   14, 1, | ||||||
|  |   solve.with(w: 101, h: 103), | ||||||
|  |   example: ( | ||||||
|  |     (result: 12, args: (w: 11, h: 7)), | ||||||
|  |   ) | ||||||
|  | ) | ||||||
							
								
								
									
										33
									
								
								src/day14/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/day14/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  | #import "@preview/cetz:0.3.1": canvas, draw | ||||||
|  | #import "puzzle1.typ": parse-input, simulate | ||||||
|  |  | ||||||
|  | #let input = get-input(14) | ||||||
|  | #let bots = parse-input(input) | ||||||
|  | #let res = 8270 | ||||||
|  | #let (width, height) = (101, 103) | ||||||
|  | #let sim-x = simulate.with(max: width, steps: res) | ||||||
|  | #let sim-y = simulate.with(max: height, steps: res) | ||||||
|  | #let size = 0.1 | ||||||
|  |  | ||||||
|  | #figure( | ||||||
|  |   canvas({ | ||||||
|  |     draw.rect( | ||||||
|  |       (0, 0), | ||||||
|  |       (width * size, -height * size) | ||||||
|  |     ) | ||||||
|  |     for bot in bots { | ||||||
|  |       let x = sim-x(bot.pos.x, bot.vel.x) | ||||||
|  |       let y = sim-y(bot.pos.y, bot.vel.y) | ||||||
|  |       draw.rect( | ||||||
|  |         (x * size, -y * size), | ||||||
|  |         ((x + 1) * size, -(y + 1) * size), | ||||||
|  |         stroke: none, | ||||||
|  |         fill: black | ||||||
|  |       ) | ||||||
|  |     } | ||||||
|  |   }), | ||||||
|  |   caption: "Christmas tree easter egg" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #show-result(res) | ||||||
							
								
								
									
										91
									
								
								src/day15/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/day15/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let WALL = "#" | ||||||
|  | #let BOX = "O" | ||||||
|  | #let BOT = "@" | ||||||
|  | #let EMPTY = "." | ||||||
|  |  | ||||||
|  | #let offsets = ( | ||||||
|  |   "^": (0, -1), | ||||||
|  |   "<": (-1, 0), | ||||||
|  |   "v": (0, 1), | ||||||
|  |   ">": (1, 0) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let compute-value(grid) = { | ||||||
|  |   let total = 0 | ||||||
|  |   for (y, row) in grid.enumerate() { | ||||||
|  |     for (x, type) in row.enumerate() { | ||||||
|  |       if type == BOX { | ||||||
|  |         total += y * 100 + x | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let (grid-data, move-data) = input.split("\n\n") | ||||||
|  |   let grid = grid-data.split("\n").map(r => r.clusters()) | ||||||
|  |   let rows = () | ||||||
|  |   let cols = () | ||||||
|  |    | ||||||
|  |   let w = grid.first().len() | ||||||
|  |   let h = grid.len() | ||||||
|  |   let bot-pos = none | ||||||
|  |  | ||||||
|  |   for y in range(h) { | ||||||
|  |     for x in range(w) { | ||||||
|  |       let type = grid.at(y).at(x) | ||||||
|  |       if type == BOT { | ||||||
|  |         bot-pos = (x, y) | ||||||
|  |         grid.at(y).at(x) = EMPTY | ||||||
|  |         break | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if bot-pos != none { | ||||||
|  |       break | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   let (bot-x, bot-y) = bot-pos | ||||||
|  |  | ||||||
|  |   let moves = move-data.replace("\n", "").clusters() | ||||||
|  |   for (move-i, move) in moves.enumerate() { | ||||||
|  |     let (dx, dy) = offsets.at(move) | ||||||
|  |     let (x2, y2) = (bot-x + dx, bot-y + dy) | ||||||
|  |     let type = grid.at(y2).at(x2) | ||||||
|  |     if type == WALL { | ||||||
|  |       continue | ||||||
|  |     } | ||||||
|  |     if type == EMPTY { | ||||||
|  |       (bot-x, bot-y) = (x2, y2) | ||||||
|  |       continue | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let type2 = type | ||||||
|  |     let (x3, y3) = (x2, y2) | ||||||
|  |     while type2 == BOX { | ||||||
|  |       x3 += dx | ||||||
|  |       y3 += dy | ||||||
|  |       type2 = grid.at(y3).at(x3) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if type2 == WALL { | ||||||
|  |       continue | ||||||
|  |     } | ||||||
|  |     grid.at(y3).at(x3) = BOX | ||||||
|  |     grid.at(y2).at(x2) = EMPTY | ||||||
|  |     (bot-x, bot-y) = (x2, y2) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return compute-value(grid) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   15, 1, | ||||||
|  |   solve, | ||||||
|  |   example: ( | ||||||
|  |     "1": 2028, | ||||||
|  |     "2": 10092 | ||||||
|  |   ) | ||||||
|  | ) | ||||||
							
								
								
									
										123
									
								
								src/day15/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/day15/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let WALL = "#" | ||||||
|  | #let BOX = "O" | ||||||
|  | #let BOX-L = "[" | ||||||
|  | #let BOX-R = "]" | ||||||
|  | #let BOT = "@" | ||||||
|  | #let EMPTY = "." | ||||||
|  |  | ||||||
|  | #let offsets = ( | ||||||
|  |   "^": (0, -1), | ||||||
|  |   "<": (-1, 0), | ||||||
|  |   "v": (0, 1), | ||||||
|  |   ">": (1, 0) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let compute-value(grid) = { | ||||||
|  |   let total = 0 | ||||||
|  |   for (y, row) in grid.enumerate() { | ||||||
|  |     for (x, type) in row.enumerate() { | ||||||
|  |       if type == BOX-L { | ||||||
|  |         total += y * 100 + x | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let can-move(grid, w, h, x, y, dx, dy) = { | ||||||
|  |   let (x2, y2) = (x + dx, y + dy) | ||||||
|  |   let type = grid.at(y2).at(x2) | ||||||
|  |   if type == EMPTY { | ||||||
|  |     return true | ||||||
|  |   } | ||||||
|  |   if type == WALL { | ||||||
|  |     return false | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Horizontal move | ||||||
|  |   if dy == 0 { | ||||||
|  |     return can-move(grid, w, h, x2, y2, dx, dy) | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   let lx = if type == BOX-L {x2} else {x2 - 1} | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     can-move(grid, w, h, lx, y2, dx, dy) and | ||||||
|  |     can-move(grid, w, h, lx + 1, y2, dx, dy) | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let do-move(grid, w, h, x, y, dx, dy) = { | ||||||
|  |   let type = grid.at(y).at(x) | ||||||
|  |   let (x2, y2) = (x + dx, y + dy) | ||||||
|  |   let type2 = grid.at(y2).at(x2) | ||||||
|  |    | ||||||
|  |   if type2 in (BOX-L, BOX-R) { | ||||||
|  |     grid = do-move(grid, w, h, x2, y2, dx, dy) | ||||||
|  |     // Vertical move | ||||||
|  |     if dx == 0 { | ||||||
|  |       let x3 = if type2 == BOX-L {x2 + 1} else {x2 - 1} | ||||||
|  |       grid = do-move(grid, w, h, x3, y2, dx, dy) | ||||||
|  |     } | ||||||
|  |     type2 = EMPTY | ||||||
|  |   } | ||||||
|  |   if type2 == EMPTY { | ||||||
|  |     grid.at(y2).at(x2) = type | ||||||
|  |     grid.at(y).at(x) = EMPTY | ||||||
|  |     return grid | ||||||
|  |   } | ||||||
|  |   if type2 == WALL { | ||||||
|  |     panic() | ||||||
|  |   } | ||||||
|  |   return grid | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let (grid-data, move-data) = input.split("\n\n") | ||||||
|  |   let grid = grid-data.split("\n").map(r => r.clusters()) | ||||||
|  |    | ||||||
|  |   let w = grid.first().len() | ||||||
|  |   let h = grid.len() | ||||||
|  |   let (w2, h2) = (w * 2, h) | ||||||
|  |   let bot-pos = none | ||||||
|  |   let grid2 = ((EMPTY,) * w2,) * h2 | ||||||
|  |  | ||||||
|  |   for y in range(h) { | ||||||
|  |     for x in range(w) { | ||||||
|  |       let type = grid.at(y).at(x) | ||||||
|  |       if type == BOT { | ||||||
|  |         bot-pos = (x*2, y) | ||||||
|  |         type = EMPTY | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       grid2.at(y).at(x*2) = if type == BOX {BOX-L} else {type} | ||||||
|  |       grid2.at(y).at(x*2 + 1) = if type == BOX {BOX-R} else {type} | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   let (bot-x, bot-y) = bot-pos | ||||||
|  |  | ||||||
|  |   let moves = move-data.replace("\n", "").clusters() | ||||||
|  |   for (move-i, move) in moves.enumerate() { | ||||||
|  |     let (dx, dy) = offsets.at(move) | ||||||
|  |     let (x2, y2) = (bot-x + dx, bot-y + dy) | ||||||
|  |  | ||||||
|  |     if not can-move(grid2, w2, h2, bot-x, bot-y, dx, dy) { | ||||||
|  |       continue | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     grid2 = do-move(grid2, w2, h2, bot-x, bot-y, dx, dy) | ||||||
|  |     (bot-x, bot-y) = (x2, y2) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return compute-value(grid2) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   15, 2, | ||||||
|  |   solve, | ||||||
|  |   example: ( | ||||||
|  |     "2": 9021 | ||||||
|  |   ) | ||||||
|  | ) | ||||||
							
								
								
									
										67
									
								
								src/day16/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/day16/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let START = "S" | ||||||
|  | #let END = "E" | ||||||
|  | #let WALL = "#" | ||||||
|  | #let EMPTY = "." | ||||||
|  |  | ||||||
|  | #let offsets = ( | ||||||
|  |   (1, 0), | ||||||
|  |   (0, 1), | ||||||
|  |   (-1, 0), | ||||||
|  |   (0, -1) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let grid = input.split("\n").map(l => l.clusters()) | ||||||
|  |   let w = grid.first().len() | ||||||
|  |   let h = grid.len() | ||||||
|  |  | ||||||
|  |   let (sx, sy) = (0, 0) | ||||||
|  |   let (ex, ey) = (0, 0) | ||||||
|  |   for y in range(h) { | ||||||
|  |     for x in range(w) { | ||||||
|  |       let c = grid.at(y).at(x) | ||||||
|  |       if c == START { | ||||||
|  |         (sx, sy) = (x, y) | ||||||
|  |       } else if c == END { | ||||||
|  |         (ex, ey) = (x, y) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let choices = ((sx, sy, 0, 0),) | ||||||
|  |   let (x, y, dir, score) = (0, 0, 0, 0) | ||||||
|  |   while choices.len() != 0 { | ||||||
|  |     let min-score = calc.min(..choices.map(c => c.last())) | ||||||
|  |     let i = choices.position(c => c.last() == min-score) | ||||||
|  |     (x, y, dir, score) = choices.remove(i) | ||||||
|  |     for (d, (dx, dy)) in offsets.enumerate() { | ||||||
|  |       // Ignore backflips | ||||||
|  |       if calc.abs(d - dir) == 2 { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       let (x2, y2) = (x + dx, y + dy) | ||||||
|  |       let c = grid.at(y2).at(x2) | ||||||
|  |       if c == WALL { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       let score2 = score + 1 + if d != dir {1000} else {0} | ||||||
|  |       if c == END { | ||||||
|  |         return score2 | ||||||
|  |       } | ||||||
|  |       choices.push((x2, y2, d, score2)) | ||||||
|  |     } | ||||||
|  |     break | ||||||
|  |   } | ||||||
|  |   return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   16, 1, | ||||||
|  |   solve, | ||||||
|  |   example: ( | ||||||
|  |     "1": 7036, | ||||||
|  |     "2": 11048 | ||||||
|  |   ) | ||||||
|  | ) | ||||||
							
								
								
									
										0
									
								
								src/day16/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/day16/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										87
									
								
								src/day17/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/day17/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let ADV = 0 | ||||||
|  | #let BXL = 1 | ||||||
|  | #let BST = 2 | ||||||
|  | #let JNZ = 3 | ||||||
|  | #let BXC = 4 | ||||||
|  | #let OUT = 5 | ||||||
|  | #let BDV = 6 | ||||||
|  | #let CDV = 7 | ||||||
|  |  | ||||||
|  | #let get-combo(regs, value) = { | ||||||
|  |   if value >= 7 { | ||||||
|  |     panic() | ||||||
|  |   } | ||||||
|  |   if value <= 3 { | ||||||
|  |     return value | ||||||
|  |   } | ||||||
|  |   return regs.at(value - 4) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let (registers, program) = input.split("\n\n") | ||||||
|  |  | ||||||
|  |   let regs = () | ||||||
|  |   for line in registers.split("\n") { | ||||||
|  |     regs.push( | ||||||
|  |       int( | ||||||
|  |         line.split(": ") | ||||||
|  |             .last() | ||||||
|  |       ) | ||||||
|  |     ) | ||||||
|  |   } | ||||||
|  |   program = program.split(": ") | ||||||
|  |                    .last() | ||||||
|  |                    .split(",") | ||||||
|  |                    .map(int) | ||||||
|  |  | ||||||
|  |   let out = () | ||||||
|  |   let pc = 0 | ||||||
|  |   while pc < program.len() { | ||||||
|  |     let op = program.at(pc) | ||||||
|  |     let val = program.at(pc + 1) | ||||||
|  |     if op == ADV { | ||||||
|  |       let num = regs.at(0) | ||||||
|  |       let den = get-combo(regs, val) | ||||||
|  |       let res = num.bit-rshift(den) | ||||||
|  |       regs.at(0) = res | ||||||
|  |     } else if op == BXL { | ||||||
|  |       regs.at(1) = regs.at(1).bit-xor(val) | ||||||
|  |     } else if op == BST { | ||||||
|  |       regs.at(1) = get-combo(regs, val).bit-and(0b111) | ||||||
|  |     } else if op == JNZ { | ||||||
|  |       if regs.at(0) != 0 { | ||||||
|  |         pc = val | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |     } else if op == BXC { | ||||||
|  |       regs.at(1) = regs.at(1).bit-xor(regs.at(2)) | ||||||
|  |     } else if op == OUT { | ||||||
|  |       out.push(get-combo(regs, val).bit-and(0b111)) | ||||||
|  |     } else if op == BDV { | ||||||
|  |       let num = regs.at(0) | ||||||
|  |       let den = get-combo(regs, val) | ||||||
|  |       let res = num.bit-rshift(den) | ||||||
|  |       regs.at(1) = res | ||||||
|  |     } else if op == CDV { | ||||||
|  |       let num = regs.at(0) | ||||||
|  |       let den = get-combo(regs, val) | ||||||
|  |       let res = num.bit-rshift(den) | ||||||
|  |       regs.at(2) = res | ||||||
|  |     } else { | ||||||
|  |       panic("Unknown instruction " + str(op)) | ||||||
|  |     } | ||||||
|  |     pc += 2 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return out.map(str).join(",") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   17, 1, | ||||||
|  |   solve, | ||||||
|  |   example: ( | ||||||
|  |     "1": "4,6,3,5,6,3,5,2,1,0" | ||||||
|  |   ) | ||||||
|  | ) | ||||||
							
								
								
									
										108
									
								
								src/day17/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/day17/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let ADV = 0 | ||||||
|  | #let BXL = 1 | ||||||
|  | #let BST = 2 | ||||||
|  | #let JNZ = 3 | ||||||
|  | #let BXC = 4 | ||||||
|  | #let OUT = 5 | ||||||
|  | #let BDV = 6 | ||||||
|  | #let CDV = 7 | ||||||
|  |  | ||||||
|  | #let ops = ("ADV", "BXL", "BST", "JNZ", "BXC", "OUT", "BDV", "CDV") | ||||||
|  |  | ||||||
|  | #let get-combo(regs, value) = { | ||||||
|  |   if value >= 7 { | ||||||
|  |     panic() | ||||||
|  |   } | ||||||
|  |   if value <= 3 { | ||||||
|  |     return value | ||||||
|  |   } | ||||||
|  |   return regs.at(value - 4) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let describe-combo(value) = { | ||||||
|  |   if value == 7 { | ||||||
|  |     return "<invalid>" | ||||||
|  |   } | ||||||
|  |   if value <= 3 { | ||||||
|  |     return str(value) | ||||||
|  |   } | ||||||
|  |   return "ABC".at(value - 4) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let describe(program) = { | ||||||
|  |   let res = "" | ||||||
|  |   res += program.map(str).join(",") | ||||||
|  |  | ||||||
|  |   for i in range(0, program.len(), step: 2) { | ||||||
|  |     res += "\n" | ||||||
|  |     let op = program.at(i) | ||||||
|  |     let val = program.at(i + 1) | ||||||
|  |     let combo = describe-combo(val) | ||||||
|  |  | ||||||
|  |     res += ops.at(op) + ": " | ||||||
|  |     if op == ADV { | ||||||
|  |       res += "A >>= " + combo | ||||||
|  |     } else if op == BXL { | ||||||
|  |       res += "B ^= " + str(val) | ||||||
|  |     } else if op == BST { | ||||||
|  |       res += "B = " + combo + " & 0b111" | ||||||
|  |     } else if op == JNZ { | ||||||
|  |       res += "IF A != 0 {PC = " + str(val) + "}" | ||||||
|  |     } else if op == BXC { | ||||||
|  |       res += "B ^= C" | ||||||
|  |     } else if op == OUT { | ||||||
|  |       res += "OUT(" + combo + " & 0b111)" | ||||||
|  |     } else if op == BDV { | ||||||
|  |       res += "B = A >> " + combo | ||||||
|  |     } else if op == CDV { | ||||||
|  |       res += "C = A >> " + combo | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return res | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let (registers, program) = input.split("\n\n") | ||||||
|  |  | ||||||
|  |   let regs = () | ||||||
|  |   for line in registers.split("\n") { | ||||||
|  |     regs.push( | ||||||
|  |       int( | ||||||
|  |         line.split(": ") | ||||||
|  |             .last() | ||||||
|  |       ) | ||||||
|  |     ) | ||||||
|  |   } | ||||||
|  |   program = program.split(": ") | ||||||
|  |                    .last() | ||||||
|  |                    .split(",") | ||||||
|  |                    .map(int) | ||||||
|  |  | ||||||
|  |   let out = () | ||||||
|  |   let pc = 0 | ||||||
|  |  | ||||||
|  |   regs.first() = 0 | ||||||
|  |  | ||||||
|  |   for n in program { | ||||||
|  |     let b = n | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   return raw(block: true, describe(program)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Example: | ||||||
|  | #solve(read(get-example-path(17, suffix: "2"))) | ||||||
|  |  | ||||||
|  | Input: | ||||||
|  | #solve(get-input(17)) | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | #show-puzzle( | ||||||
|  |   17, 2, | ||||||
|  |   solve, | ||||||
|  |   example: ( | ||||||
|  |     "2": 117440 | ||||||
|  |   ) | ||||||
|  | )*/ | ||||||
							
								
								
									
										143
									
								
								src/day18/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/day18/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let offsets = ( | ||||||
|  |   (1, 0), | ||||||
|  |   (0, 1), | ||||||
|  |   (-1, 0), | ||||||
|  |   (0, -1) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let get-lowest(nodes, scores) = { | ||||||
|  |   let lowest-score = none | ||||||
|  |   let lowest-i = none | ||||||
|  |   for (i, (x, y)) in nodes.enumerate() { | ||||||
|  |     let score = scores.at(y).at(x) | ||||||
|  |  | ||||||
|  |     if lowest-i == none or score < lowest-score { | ||||||
|  |       lowest-score = score | ||||||
|  |       lowest-i = i | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if lowest-i == none { | ||||||
|  |     panic() | ||||||
|  |   } | ||||||
|  |   return lowest-i | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let make-path(parents, end) = { | ||||||
|  |   let path = (end,) | ||||||
|  |   let (x, y) = end | ||||||
|  |   let pos = parents.at(y).at(x) | ||||||
|  |   while pos != none { | ||||||
|  |     (x, y) = pos | ||||||
|  |     path.insert(0, pos) | ||||||
|  |     pos = parents.at(y).at(x) | ||||||
|  |   } | ||||||
|  |   return path | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let find-path(w, h, grid) = { | ||||||
|  |   let end = (w - 1, h - 1) | ||||||
|  |   let (x, y) = (0, 0) | ||||||
|  |   let open = ((x, y),) | ||||||
|  |   let closed = () | ||||||
|  |   let g-scores = ((0,) * w,) * h | ||||||
|  |   let f-scores = ((calc.inf,) * w,) * h | ||||||
|  |   let parents = ((none,)*w,)*h | ||||||
|  |   while open.len() != 0 { | ||||||
|  |     let cur = open.remove(get-lowest(open, f-scores)) | ||||||
|  |      | ||||||
|  |     if cur == end { | ||||||
|  |       return make-path(parents, cur) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let g-score = g-scores.at(y).at(x) | ||||||
|  |     let f-score = f-scores.at(y).at(x) | ||||||
|  |      | ||||||
|  |     let (x, y) = cur | ||||||
|  |     for (dx, dy) in offsets { | ||||||
|  |       let (x2, y2) = (x + dx, y + dy) | ||||||
|  |       if x2 < 0 or x2 >= w or y2 < 0 or y2 >= h { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       if grid.at(y2).at(x2) { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       if (x2, y2) in closed { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       let g = x2 + y2 | ||||||
|  |       let h = calc.abs(end.first() - x2) + calc.abs(end.last() - y2) | ||||||
|  |       let f = g + h | ||||||
|  |  | ||||||
|  |       if f < f-scores.at(y2).at(x2) { | ||||||
|  |         g-scores.at(y2).at(x2) = g | ||||||
|  |         f-scores.at(y2).at(x2) = f | ||||||
|  |         parents.at(y2).at(x2) = (x, y) | ||||||
|  |         open.push((x2, y2)) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     closed.push(cur) | ||||||
|  |   } | ||||||
|  |   panic("No path was found") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input, w: 0, h: 0, n-bytes: 0) = { | ||||||
|  |   assert(w != 0, message: "Width cannot be 0") | ||||||
|  |   assert(h != 0, message: "Height cannot be 0") | ||||||
|  |   let grid = ((false,) * w,) * h | ||||||
|  |  | ||||||
|  |   let obstacles = input.split("\n") | ||||||
|  |   for obs in obstacles.slice(0, n-bytes) { | ||||||
|  |     let (x, y) = obs.split(",").map(int) | ||||||
|  |     grid.at(y).at(x) = true | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let path = find-path(w, h, grid) | ||||||
|  |   return path.len() - 1 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let visualize(input, w: 0, h: 0, n-bytes: 0, s: 2em) = { | ||||||
|  |   let grid_ = ((false,) * w,) * h | ||||||
|  |  | ||||||
|  |   let obstacles = input.split("\n") | ||||||
|  |   for obs in obstacles.slice(0, n-bytes) { | ||||||
|  |     let (x, y) = obs.split(",").map(int) | ||||||
|  |     grid_.at(y).at(x) = true | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let path = find-path(w, h, grid_) | ||||||
|  |   for (x, y) in path { | ||||||
|  |     grid_.at(y).at(x) = grid.cell(fill: green.lighten(60%))[O] | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   let cells = grid_.flatten().map(c => { | ||||||
|  |     if c == false [] | ||||||
|  |     else if c == true {grid.cell(fill: red.lighten(60%))[\#]} | ||||||
|  |     else {c} | ||||||
|  |   }) | ||||||
|  |    | ||||||
|  |   grid( | ||||||
|  |     columns: (s,) * w, | ||||||
|  |     rows: (s,) * h, | ||||||
|  |     align: center + horizon, | ||||||
|  |     stroke: black, | ||||||
|  |     ..cells | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   18, 1, | ||||||
|  |   solve.with(w: 71, h: 71, n-bytes: 1024), | ||||||
|  |   example: ( | ||||||
|  |     (result: 22, args: (w: 7, h: 7, n-bytes: 12)), | ||||||
|  |   ), | ||||||
|  |   visualize: visualize.with(w: 7, h: 7, n-bytes: 12) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | #pagebreak() | ||||||
|  | #set page(width: auto, height: auto) | ||||||
|  | #visualize(get-input(18), w: 71, h: 71, n-bytes: 1024, s: 1em) | ||||||
|  | */ | ||||||
							
								
								
									
										117
									
								
								src/day18/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/day18/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  | #let offsets = ( | ||||||
|  |   (1, 0), | ||||||
|  |   (0, 1), | ||||||
|  |   (-1, 0), | ||||||
|  |   (0, -1) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let get-lowest(nodes, scores) = { | ||||||
|  |   let lowest-score = none | ||||||
|  |   let lowest-i = none | ||||||
|  |   for (i, (x, y)) in nodes.enumerate() { | ||||||
|  |     let score = scores.at(y).at(x) | ||||||
|  |  | ||||||
|  |     if lowest-i == none or score < lowest-score { | ||||||
|  |       lowest-score = score | ||||||
|  |       lowest-i = i | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if lowest-i == none { | ||||||
|  |     panic() | ||||||
|  |   } | ||||||
|  |   return lowest-i | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let make-path(parents, end) = { | ||||||
|  |   let path = (end,) | ||||||
|  |   let (x, y) = end | ||||||
|  |   let pos = parents.at(y).at(x) | ||||||
|  |   while pos != none { | ||||||
|  |     (x, y) = pos | ||||||
|  |     path.insert(0, pos) | ||||||
|  |     pos = parents.at(y).at(x) | ||||||
|  |   } | ||||||
|  |   return path | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let find-path(w, h, grid, start: (0, 0), end: auto) = { | ||||||
|  |   let end = if end == auto {(w - 1, h - 1)} else {end} | ||||||
|  |   let (x, y) = start | ||||||
|  |   let open = ((x, y),) | ||||||
|  |   let closed = () | ||||||
|  |   let g-scores = ((0,) * w,) * h | ||||||
|  |   let f-scores = ((calc.inf,) * w,) * h | ||||||
|  |   let parents = ((none,)*w,)*h | ||||||
|  |   while open.len() != 0 { | ||||||
|  |     let cur = open.remove(get-lowest(open, f-scores)) | ||||||
|  |      | ||||||
|  |     if cur == end { | ||||||
|  |       return true | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let g-score = g-scores.at(y).at(x) | ||||||
|  |     let f-score = f-scores.at(y).at(x) | ||||||
|  |      | ||||||
|  |     let (x, y) = cur | ||||||
|  |     for (dx, dy) in offsets { | ||||||
|  |       let (x2, y2) = (x + dx, y + dy) | ||||||
|  |       if x2 < 0 or x2 >= w or y2 < 0 or y2 >= h { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       if grid.at(y2).at(x2) { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       if (x2, y2) in closed { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       let g = calc.abs(x2 - start.first()) + calc.abs(y2 - start.last()) | ||||||
|  |       let h = calc.abs(end.first() - x2) + calc.abs(end.last() - y2) | ||||||
|  |       let f = g + h | ||||||
|  |  | ||||||
|  |       if f < f-scores.at(y2).at(x2) { | ||||||
|  |         g-scores.at(y2).at(x2) = g | ||||||
|  |         f-scores.at(y2).at(x2) = f | ||||||
|  |         parents.at(y2).at(x2) = (x, y) | ||||||
|  |         open.push((x2, y2)) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     closed.push(cur) | ||||||
|  |   } | ||||||
|  |   return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input, w: 0, h: 0, n-bytes: 0) = { | ||||||
|  |   assert(w != 0, message: "Width cannot be 0") | ||||||
|  |   assert(h != 0, message: "Height cannot be 0") | ||||||
|  |   let grid = ((false,) * w,) * h | ||||||
|  |  | ||||||
|  |   let obstacles = input.split("\n") | ||||||
|  |   let a = 0 | ||||||
|  |   let b = obstacles.len() | ||||||
|  |  | ||||||
|  |   while b - a > 1 { | ||||||
|  |     let m = calc.div-euclid(a + b, 2) | ||||||
|  |     let grid2 = grid | ||||||
|  |     for obs in obstacles.slice(0, m) { | ||||||
|  |       let (x, y) = obs.split(",").map(int) | ||||||
|  |       grid2.at(y).at(x) = true | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if find-path(w, h, grid2) { | ||||||
|  |       a = m | ||||||
|  |     } else { | ||||||
|  |       b = m | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return obstacles.at(a) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   18, 2, | ||||||
|  |   solve.with(w: 71, h: 71), | ||||||
|  |   example: ( | ||||||
|  |     (result: "6,1", args: (w: 7, h: 7)), | ||||||
|  |   ) | ||||||
|  | ) | ||||||
							
								
								
									
										41
									
								
								src/day19/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/day19/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let is-possible(target, towels: none) = { | ||||||
|  |   if towels == none { | ||||||
|  |     panic() | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for towel in towels { | ||||||
|  |     if towel == target { | ||||||
|  |       return true | ||||||
|  |     } | ||||||
|  |     if target.starts-with(towel) { | ||||||
|  |       if is-possible(target.slice(towel.len()), towels: towels) { | ||||||
|  |         return true | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let (towels, targets) = input.split("\n\n") | ||||||
|  |  | ||||||
|  |   towels = towels.split(", ") | ||||||
|  |   let is-possible = is-possible.with(towels: towels) | ||||||
|  |  | ||||||
|  |   let total = 0 | ||||||
|  |   for target in targets.split("\n") { | ||||||
|  |     if is-possible(target) { | ||||||
|  |       total += 1 | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   19, 1, | ||||||
|  |   solve, | ||||||
|  |   example: 6 | ||||||
|  | ) | ||||||
							
								
								
									
										46
									
								
								src/day19/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/day19/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let (towels, targets) = input.split("\n\n") | ||||||
|  |  | ||||||
|  |   towels = towels.split(", ") | ||||||
|  |   let by-initial = (:) | ||||||
|  |   for towel in towels { | ||||||
|  |     let initial = towel.first() | ||||||
|  |     if initial not in by-initial { | ||||||
|  |       by-initial.insert(initial, ()) | ||||||
|  |     } | ||||||
|  |     by-initial.at(initial).push(towel) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let count(target) = { | ||||||
|  |     let initial = target.first() | ||||||
|  |     if initial not in by-initial { | ||||||
|  |       return 0 | ||||||
|  |     } | ||||||
|  |     let cnt = 0 | ||||||
|  |     for towel in by-initial.at(initial) { | ||||||
|  |       if towel == target { | ||||||
|  |         cnt += 1 | ||||||
|  |       } else if target.starts-with(towel) { | ||||||
|  |         cnt += count(target.slice(towel.len())) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return cnt | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let total = 0 | ||||||
|  |   for target in targets.split("\n") { | ||||||
|  |     total += count(target) | ||||||
|  |   } | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   19, 2, | ||||||
|  |   solve, | ||||||
|  |   example: 16, | ||||||
|  |   only-example: true | ||||||
|  | ) | ||||||
|  | #show-result(632423618484345) | ||||||
							
								
								
									
										128
									
								
								src/day20/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/day20/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let offsets = ( | ||||||
|  |   (1, 0), | ||||||
|  |   (0, 1), | ||||||
|  |   (-1, 0), | ||||||
|  |   (0, -1) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let EMPTY = "." | ||||||
|  | #let WALL = "#" | ||||||
|  | #let START = "S" | ||||||
|  | #let END = "E" | ||||||
|  |  | ||||||
|  | #let in-grid(w, h, x, y) = { | ||||||
|  |   return 0 <= x and x < w and 0 <= y and y < h | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let count-shortcuts(grid, w, h, save, path) = { | ||||||
|  |   let in-grid = in-grid.with(w, h) | ||||||
|  |   let total = 0 | ||||||
|  |   for (x1, y1) in path { | ||||||
|  |     let v1 = grid.at(y1).at(x1) | ||||||
|  |  | ||||||
|  |     for (dx1, dy1) in offsets { | ||||||
|  |       let (x2, y2) = (x1 + dx1, y1 + dy1) | ||||||
|  |       if not in-grid(x2, y2) { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       let v2 = grid.at(y2).at(x2) | ||||||
|  |       if v2 < 0 { | ||||||
|  |         for (dx2, dy2) in offsets { | ||||||
|  |           let (x3, y3) = (x2 + dx2, y2 + dy2) | ||||||
|  |           if not in-grid(x3, y3) { | ||||||
|  |             continue | ||||||
|  |           } | ||||||
|  |           let v3 = grid.at(y3).at(x3) | ||||||
|  |           if v3 > 0 { | ||||||
|  |             if v3 - v1 >= save + 2 { | ||||||
|  |               total += 1 | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input, save: 1) = { | ||||||
|  |   let input-grid = input.split("\n").map(l => l.clusters()) | ||||||
|  |  | ||||||
|  |   let w = input-grid.first().len() | ||||||
|  |   let h = input-grid.len() | ||||||
|  |   let in-grid = in-grid.with(w, h) | ||||||
|  |  | ||||||
|  |   let grid = ((-1,)*w,)*h | ||||||
|  |   let start = none | ||||||
|  |   let end = none | ||||||
|  |   for y in range(h) { | ||||||
|  |     for x in range(w) { | ||||||
|  |       let c = input-grid.at(y).at(x) | ||||||
|  |       if c == WALL { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       if c == START { | ||||||
|  |         start = (x, y) | ||||||
|  |       } else if c == END { | ||||||
|  |         end = (x, y) | ||||||
|  |       } | ||||||
|  |       grid.at(y).at(x) = 0 | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let (x, y) = start | ||||||
|  |   let (ex, ey) = end | ||||||
|  |   let path = () | ||||||
|  |  | ||||||
|  |   while x != ex or y != ey { | ||||||
|  |     path.push((x, y)) | ||||||
|  |     grid.at(y).at(x) = path.len() | ||||||
|  |     for (dx, dy) in offsets { | ||||||
|  |       let (x2, y2) = (x + dx, y + dy) | ||||||
|  |       if not in-grid(x2, y2) { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       let v = grid.at(y2).at(x2) | ||||||
|  |       if v == 0 { | ||||||
|  |         (x, y) = (x2, y2) | ||||||
|  |         break | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   path.push((x, y)) | ||||||
|  |   grid.at(y).at(x) = path.len() | ||||||
|  |  | ||||||
|  |   return count-shortcuts( | ||||||
|  |     grid, w, h, | ||||||
|  |     save, | ||||||
|  |     path | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let examples = ( | ||||||
|  |   (64, 1), | ||||||
|  |   (40, 1), | ||||||
|  |   (38, 1), | ||||||
|  |   (36, 1), | ||||||
|  |   (20, 1), | ||||||
|  |   (12, 3), | ||||||
|  |   (10, 2), | ||||||
|  |   (8, 4), | ||||||
|  |   (6, 2), | ||||||
|  |   (4, 14), | ||||||
|  |   (2, 14), | ||||||
|  | ) | ||||||
|  | #let n-tot = 0 | ||||||
|  | #let examples2 = () | ||||||
|  | #for (save, cnt) in examples { | ||||||
|  |   n-tot += cnt | ||||||
|  |   examples2.push((result: n-tot, args: (save: save))) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   20, 1, | ||||||
|  |   solve.with(save: 100), | ||||||
|  |   example: examples2 | ||||||
|  | ) | ||||||
							
								
								
									
										0
									
								
								src/day20/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/day20/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										100
									
								
								src/day21/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/day21/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let nums = ( | ||||||
|  |   "7": (0, 0), | ||||||
|  |   "8": (1, 0), | ||||||
|  |   "9": (2, 0), | ||||||
|  |   "4": (0, 1), | ||||||
|  |   "5": (1, 1), | ||||||
|  |   "6": (2, 1), | ||||||
|  |   "1": (0, 2), | ||||||
|  |   "2": (1, 2), | ||||||
|  |   "3": (2, 2), | ||||||
|  |   "0": (1, 3), | ||||||
|  |   "A": (2, 3), | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let arrows = ( | ||||||
|  |   "^": (1, 0), | ||||||
|  |   "A": (2, 0), | ||||||
|  |   "<": (0, 1), | ||||||
|  |   "v": (1, 1), | ||||||
|  |   ">": (2, 1), | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let get-num-path(code) = { | ||||||
|  |   let path = "" | ||||||
|  |   let (x, y) = nums.at("A") | ||||||
|  |   for char in code { | ||||||
|  |     let (x2, y2) = nums.at(char) | ||||||
|  |     let dy = y2 - y | ||||||
|  |     let dx = x2 - x | ||||||
|  |     let ver = if dy == 0 { | ||||||
|  |       "" | ||||||
|  |     } else { | ||||||
|  |       (if dy < 0 {"^"} else {"v"}) * calc.abs(dy) | ||||||
|  |     } | ||||||
|  |     let hor = if dx == 0 { | ||||||
|  |       "" | ||||||
|  |     } else { | ||||||
|  |       (if dx < 0 {"<"} else {">"}) * calc.abs(dx) | ||||||
|  |     } | ||||||
|  |     path += if y == 3 and x2 == 0 { | ||||||
|  |       ver + hor | ||||||
|  |     } else { | ||||||
|  |       hor + ver | ||||||
|  |     } + "A" | ||||||
|  |     (x, y) = (x2, y2) | ||||||
|  |   } | ||||||
|  |   return path | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let get-dir-path(code) = { | ||||||
|  |   let path = "" | ||||||
|  |   let (x, y) = arrows.at("A") | ||||||
|  |   for char in code { | ||||||
|  |     let (x2, y2) = arrows.at(char) | ||||||
|  |     let dy = y2 - y | ||||||
|  |     let dx = x2 - x | ||||||
|  |     let ver = if dy == 0 { | ||||||
|  |       "" | ||||||
|  |     } else { | ||||||
|  |       (if dy < 0 {"^"} else {"v"}) * calc.abs(dy) | ||||||
|  |     } | ||||||
|  |     let hor = if dx == 0 { | ||||||
|  |       "" | ||||||
|  |     } else { | ||||||
|  |       (if dx < 0 {"<"} else {">"}) * calc.abs(dx) | ||||||
|  |     } | ||||||
|  |     path += if x == 0 and dy < 0 { | ||||||
|  |       hor + ver | ||||||
|  |     } else { | ||||||
|  |       ver + hor | ||||||
|  |     } + "A" | ||||||
|  |     (x, y) = (x2, y2) | ||||||
|  |   } | ||||||
|  |   return path | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let get-path(code) = { | ||||||
|  |   let num-path = get-num-path(code) | ||||||
|  |   let dir-path-1 = get-dir-path(num-path) | ||||||
|  |   let dir-path-2 = get-dir-path(dir-path-1) | ||||||
|  |   return dir-path-2 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let codes = input.split("\n") | ||||||
|  |   let total = 0 | ||||||
|  |   for code in codes { | ||||||
|  |     let len = get-path(code).len() | ||||||
|  |     let num = int(code.slice(0, code.len() - 1)) | ||||||
|  |     total += len * num | ||||||
|  |   } | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  | #show-puzzle( | ||||||
|  |   21, 1, | ||||||
|  |   solve, | ||||||
|  |   example: 126384 | ||||||
|  | ) | ||||||
							
								
								
									
										0
									
								
								src/day21/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/day21/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										61
									
								
								src/day22/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/day22/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let random(prev) = { | ||||||
|  |   let step(a, shift) = { | ||||||
|  |     let s = if shift < 0 { | ||||||
|  |       a.bit-rshift(-shift) | ||||||
|  |     } else { | ||||||
|  |       a.bit-lshift(shift) | ||||||
|  |     } | ||||||
|  |     return s.bit-xor(a).bit-and(0xffffff) | ||||||
|  |   } | ||||||
|  |   let b = step(prev, 6) | ||||||
|  |   let c = step(b, -5) | ||||||
|  |   let d = step(c, 11) | ||||||
|  |   return d | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input, steps: 1) = { | ||||||
|  |   let values = input.split("\n").map(int) | ||||||
|  |   let total = 0 | ||||||
|  |   for value in values { | ||||||
|  |     for _ in range(steps) { | ||||||
|  |       value = random(value) | ||||||
|  |     } | ||||||
|  |     total += value | ||||||
|  |   } | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let examples = () | ||||||
|  | #let results = ( | ||||||
|  |   15887950, | ||||||
|  |   16495136, | ||||||
|  |   527345, | ||||||
|  |   704524, | ||||||
|  |   1553684, | ||||||
|  |   12683156, | ||||||
|  |   11100544, | ||||||
|  |   12249484, | ||||||
|  |   7753432, | ||||||
|  |   5908254 | ||||||
|  | ) | ||||||
|  | #for (i, res) in results.enumerate() { | ||||||
|  |   examples.push(( | ||||||
|  |     result: res, | ||||||
|  |     args: (steps: i + 1) | ||||||
|  |   )) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   22, 1, | ||||||
|  |   solve.with(steps: 2000), | ||||||
|  |   example: examples, | ||||||
|  |   only-example: true | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #show-result(18261820068) | ||||||
|  |  | ||||||
|  | //B = ((A << 6) ^ A) & 0xffffff\ | ||||||
|  | //C = ((B >> 5) ^ B) & 0xffffff\ | ||||||
|  | //D = ((C << 11) ^ C) & 0xffffff | ||||||
							
								
								
									
										0
									
								
								src/day22/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/day22/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										50
									
								
								src/day23/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/day23/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let links = input.split("\n") | ||||||
|  |  | ||||||
|  |   let links-dict = (:) | ||||||
|  |  | ||||||
|  |   let to-test = () | ||||||
|  |   for link in links { | ||||||
|  |     let (a, b) = link.split("-") | ||||||
|  |     if a not in links-dict { | ||||||
|  |       links-dict.insert(a, ()) | ||||||
|  |     } | ||||||
|  |     if b not in links-dict { | ||||||
|  |       links-dict.insert(b, ()) | ||||||
|  |     } | ||||||
|  |     links-dict.at(a).push(b) | ||||||
|  |     links-dict.at(b).push(a) | ||||||
|  |     if a.starts-with("t") { | ||||||
|  |       to-test.push(a) | ||||||
|  |     } | ||||||
|  |     if b.starts-with("t") { | ||||||
|  |       to-test.push(b) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let total = 0 | ||||||
|  |   let groups = () | ||||||
|  |   for comp1 in to-test.dedup() { | ||||||
|  |     for comp2 in links-dict.at(comp1) { | ||||||
|  |       for comp3 in links-dict.at(comp2) { | ||||||
|  |         if comp1 in links-dict.at(comp3) { | ||||||
|  |           let group = (comp1, comp2, comp3).sorted() | ||||||
|  |           if group not in groups { | ||||||
|  |             total += 1 | ||||||
|  |             groups.push(group) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   23, 1, | ||||||
|  |   solve, | ||||||
|  |   example: 7 | ||||||
|  | ) | ||||||
							
								
								
									
										104
									
								
								src/day23/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/day23/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let bron-kerbosch(links, R, P, X) = { | ||||||
|  |   if P.len() == 0 and X.len() == 0 { | ||||||
|  |     return if R.len() > 2 { | ||||||
|  |       R.sorted() | ||||||
|  |     } else { | ||||||
|  |       none | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let longest-len = 0 | ||||||
|  |   let longest = none | ||||||
|  |   let to-visit = P | ||||||
|  |   for v in to-visit { | ||||||
|  |     let neighbors = links.at(v) | ||||||
|  |     let clique = bron-kerbosch( | ||||||
|  |       links, | ||||||
|  |       R + (v,), | ||||||
|  |       P.filter(n => n in neighbors), | ||||||
|  |       X.filter(n => n in neighbors) | ||||||
|  |     ) | ||||||
|  |     if clique != none { | ||||||
|  |       let l = clique.len() | ||||||
|  |       if longest == none or l > longest-len { | ||||||
|  |         longest = clique | ||||||
|  |         longest-len = l | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     let _ = P.remove(0) | ||||||
|  |     X.push(v) | ||||||
|  |   } | ||||||
|  |   return longest | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let bron-kerbosch2(links, R, P, X) = { | ||||||
|  |   if P.len() == 0 and X.len() == 0 { | ||||||
|  |     return if R.len() > 2 { | ||||||
|  |       R.sorted() | ||||||
|  |     } else { | ||||||
|  |       none | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let pivot = if P.len() != 0 { | ||||||
|  |     P.first() | ||||||
|  |   } else { | ||||||
|  |     X.first() | ||||||
|  |   } | ||||||
|  |   let pivot-neighbors = links.at(pivot) | ||||||
|  |   let longest-len = 0 | ||||||
|  |   let longest = none | ||||||
|  |   let to-visit = P.filter(n => n not in pivot-neighbors) | ||||||
|  |   for v in to-visit { | ||||||
|  |     let neighbors = links.at(v) | ||||||
|  |     let clique = bron-kerbosch2( | ||||||
|  |       links, | ||||||
|  |       R + (v,), | ||||||
|  |       P.filter(n => n in neighbors), | ||||||
|  |       X.filter(n => n in neighbors) | ||||||
|  |     ) | ||||||
|  |     if clique != none { | ||||||
|  |       let l = clique.len() | ||||||
|  |       if longest == none or l > longest-len { | ||||||
|  |         longest = clique | ||||||
|  |         longest-len = l | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     let _ = P.remove(P.position(n => n == v)) | ||||||
|  |     X.push(v) | ||||||
|  |   } | ||||||
|  |   return longest | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let links = input.split("\n") | ||||||
|  |  | ||||||
|  |   let links-dict = (:) | ||||||
|  |  | ||||||
|  |   let to-test = () | ||||||
|  |   for link in links { | ||||||
|  |     let (a, b) = link.split("-") | ||||||
|  |     if a not in links-dict { | ||||||
|  |       links-dict.insert(a, ()) | ||||||
|  |     } | ||||||
|  |     if b not in links-dict { | ||||||
|  |       links-dict.insert(b, ()) | ||||||
|  |     } | ||||||
|  |     links-dict.at(a).push(b) | ||||||
|  |     links-dict.at(b).push(a) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let clique = bron-kerbosch2(links-dict, (), links-dict.keys(), ()) | ||||||
|  |  | ||||||
|  |   return clique.join(",") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   23, 2, | ||||||
|  |   solve, | ||||||
|  |   example: "co,de,ka,ta", | ||||||
|  |   only-example: true | ||||||
|  | ) | ||||||
|  | #show-result("ab,al,cq,cr,da,db,dr,fw,ly,mn,od,py,uh") | ||||||
							
								
								
									
										78
									
								
								src/day24/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/day24/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let (inputs, gates) = input.split("\n\n") | ||||||
|  |   let ids = () | ||||||
|  |   inputs = inputs.split("\n").map(i => { | ||||||
|  |     let (id, value) = i.split(": ") | ||||||
|  |     return (id: id, value: value == "1") | ||||||
|  |   }) | ||||||
|  |   ids += inputs.map(i => i.id) | ||||||
|  |   gates = gates.split("\n").map(g => { | ||||||
|  |     let (gate, output) = g.split(" -> ") | ||||||
|  |     let (i1, op, i2) = gate.split(" ") | ||||||
|  |     return ( | ||||||
|  |       in1: i1, | ||||||
|  |       in2: i2, | ||||||
|  |       op: op, | ||||||
|  |       out: output | ||||||
|  |     ) | ||||||
|  |   }) | ||||||
|  |   let gates-by-id = (:) | ||||||
|  |   for gate in gates { | ||||||
|  |     gates-by-id.insert(gate.out, gate) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ids += gates.map(g => g.out) | ||||||
|  |   ids = ids.dedup() | ||||||
|  |   let dbg = (inputs, gates) | ||||||
|  |  | ||||||
|  |   let values = (:) | ||||||
|  |   for input in inputs { | ||||||
|  |     values.insert(input.id, input.value) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let stack = ids.filter(id => id.starts-with("z")) | ||||||
|  |   let output = (0,) * stack.len() | ||||||
|  |   while stack.len() != 0 { | ||||||
|  |     let v = stack.pop() | ||||||
|  |     if v in values { | ||||||
|  |       if v.starts-with("z") { | ||||||
|  |         let i = int(v.slice(1)) | ||||||
|  |         output.at(i) = int(values.at(v)) | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       stack.push(v) | ||||||
|  |       let gate = gates-by-id.at(v) | ||||||
|  |       if gate.in1 in values and gate.in2 in values { | ||||||
|  |         let v1 = values.at(gate.in1) | ||||||
|  |         let v2 = values.at(gate.in2) | ||||||
|  |         let value = if gate.op == "AND" { | ||||||
|  |           v1 and v2 | ||||||
|  |         } else if gate.op == "OR" { | ||||||
|  |           v1 or v2 | ||||||
|  |         } else if gate.op == "XOR" { | ||||||
|  |           (v1 or v2) and not (v1 and v2) | ||||||
|  |         } | ||||||
|  |         values.insert(v, value) | ||||||
|  |       } else { | ||||||
|  |         stack.push(gate.in1) | ||||||
|  |         stack.push(gate.in2) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let result = output.rev() | ||||||
|  |                      .fold(0, (a, b) => a.bit-lshift(1).bit-or(b)) | ||||||
|  |  | ||||||
|  |   return result | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   24, 1, | ||||||
|  |   solve, | ||||||
|  |   example: ( | ||||||
|  |     "1": 4, | ||||||
|  |     "2": 2024 | ||||||
|  |   ) | ||||||
|  | ) | ||||||
							
								
								
									
										0
									
								
								src/day24/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/day24/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										61
									
								
								src/day25/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/day25/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let parse-schematic(schematic) = { | ||||||
|  |   let lines = schematic.split("\n") | ||||||
|  |   let is-key = "." in schematic.first() | ||||||
|  |  | ||||||
|  |   let n-cols = lines.first().len() | ||||||
|  |   let n-rows = lines.len() | ||||||
|  |   let heights = () | ||||||
|  |  | ||||||
|  |   for x in range(n-cols) { | ||||||
|  |     let y = if is-key {n-rows - 2} else {1} | ||||||
|  |     let h = 0 | ||||||
|  |     for i in range(n-rows) { | ||||||
|  |       if lines.at(y).at(x) == "." { | ||||||
|  |         break | ||||||
|  |       } | ||||||
|  |       h += 1 | ||||||
|  |       y += if is-key {-1} else {1} | ||||||
|  |     } | ||||||
|  |     heights.push(h) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return (if is-key {"key"} else {"lock"}, heights) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let fits(lock, key) = { | ||||||
|  |   let tmp = lock.zip(key).map(p => p.sum()) | ||||||
|  |   return calc.max(..tmp) <= 5 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let schematics = input.split("\n\n") | ||||||
|  |  | ||||||
|  |   let locks = () | ||||||
|  |   let keys = () | ||||||
|  |   for schematic in schematics { | ||||||
|  |     let (type, heights) = parse-schematic(schematic) | ||||||
|  |     if type == "key" { | ||||||
|  |       keys.push(heights) | ||||||
|  |     } else { | ||||||
|  |       locks.push(heights) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   let total = 0 | ||||||
|  |   for key in keys { | ||||||
|  |     for lock in locks { | ||||||
|  |       if fits(lock, key) { | ||||||
|  |         total += 1 | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   25, 1, | ||||||
|  |   solve, | ||||||
|  |   example: 3 | ||||||
|  | ) | ||||||
							
								
								
									
										0
									
								
								src/day25/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/day25/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -1,7 +1,8 @@ | |||||||
| #import "/src/utils.typ": * | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let reg = regex("mul\((\d{1,3}),(\d{1,3})\)") | ||||||
| #let solve(input) = { | #let solve(input) = { | ||||||
|   let matches = input.matches(regex("mul\((\d{1,3}),(\d{1,3})\)")) |   let matches = input.matches(reg) | ||||||
|   let total = matches.map(m => { |   let total = matches.map(m => { | ||||||
|     m.captures.map(int) |     m.captures.map(int) | ||||||
|               .product() |               .product() | ||||||
| @@ -9,8 +10,24 @@ | |||||||
|   return total |   return total | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #let visualize(input) = { | ||||||
|  |   [ | ||||||
|  |     #set text(size: 1.2em) | ||||||
|  |     #show reg: it => { | ||||||
|  |       let m = it.text.match(reg) | ||||||
|  |       let v = m.captures.map(int).product() | ||||||
|  |       math.underbrace( | ||||||
|  |         highlight(fill: red, raw(it.text)), | ||||||
|  |         text(size: 1.5em, str(v)) | ||||||
|  |       ) | ||||||
|  |     } | ||||||
|  |     #raw(input) | ||||||
|  |   ] | ||||||
|  | } | ||||||
|  |  | ||||||
| #show-puzzle( | #show-puzzle( | ||||||
|   3, 1, |   3, 1, | ||||||
|   solve, |   solve, | ||||||
|   example: ("1": 161) |   example: ("1": 161), | ||||||
|  |   visualize: visualize | ||||||
| ) | ) | ||||||
| @@ -1,10 +1,11 @@ | |||||||
| #import "/src/utils.typ": * | #import "/src/utils.typ": * | ||||||
|  | #import "@preview/cetz:0.3.1": canvas, draw | ||||||
|  |  | ||||||
| #let check-xmas(lines, ox, oy) = { | #let check-xmas(lines, ox, oy) = { | ||||||
|   let w = lines.first().len() |   let w = lines.first().len() | ||||||
|   let h = lines.len() |   let h = lines.len() | ||||||
|  |  | ||||||
|   let total = 0 |   let dirs = () | ||||||
|   for dy in (-1, 0, 1) { |   for dy in (-1, 0, 1) { | ||||||
|     for dx in (-1, 0, 1) { |     for dx in (-1, 0, 1) { | ||||||
|       if dx == 0 and dy == 0 { |       if dx == 0 and dy == 0 { | ||||||
| @@ -26,11 +27,11 @@ | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       if buffer == "XMAS" { |       if buffer == "XMAS" { | ||||||
|         total += 1 |         dirs.push((dx, dy)) | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   return total |   return dirs | ||||||
| } | } | ||||||
|  |  | ||||||
| #let solve(input) = { | #let solve(input) = { | ||||||
| @@ -42,7 +43,7 @@ | |||||||
|   for y in range(h) { |   for y in range(h) { | ||||||
|     for x in range(w) { |     for x in range(w) { | ||||||
|       if lines.at(y).at(x) == "X" { |       if lines.at(y).at(x) == "X" { | ||||||
|         total += check-xmas(lines, x, y) |         total += check-xmas(lines, x, y).len() | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -50,8 +51,41 @@ | |||||||
|   return total |   return total | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #let visualize(input) = { | ||||||
|  |   let lines = input.split("\n") | ||||||
|  |   let w = lines.first().len() | ||||||
|  |   let h = lines.len() | ||||||
|  |  | ||||||
|  |   canvas(length: 2em, { | ||||||
|  |     for y in range(h) { | ||||||
|  |       for x in range(w) { | ||||||
|  |         if lines.at(y).at(x) == "X" { | ||||||
|  |           let key = str(x) + "-" + str(y) | ||||||
|  |           let dirs = check-xmas(lines, x, y) | ||||||
|  |           draw.on-layer(2, { | ||||||
|  |             for (dx, dy) in dirs { | ||||||
|  |               draw.line( | ||||||
|  |                 (x + dx * 0.2, y + dy * 0.2), | ||||||
|  |                 (x + dx * 2.8, y + dy * 2.8), | ||||||
|  |                 stroke: red, | ||||||
|  |                 fill: red, | ||||||
|  |                 mark: (end: ">") | ||||||
|  |               ) | ||||||
|  |             } | ||||||
|  |           }) | ||||||
|  |         } | ||||||
|  |         draw.content( | ||||||
|  |           (x, y), | ||||||
|  |           lines.at(y).at(x) | ||||||
|  |         ) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
| #show-puzzle( | #show-puzzle( | ||||||
|   4, 1, |   4, 1, | ||||||
|   solve, |   solve, | ||||||
|   example: 18 |   example: 18, | ||||||
|  |   visualize: visualize | ||||||
| ) | ) | ||||||
| @@ -1,4 +1,5 @@ | |||||||
| #import "/src/utils.typ": * | #import "/src/utils.typ": * | ||||||
|  | #import "@preview/cetz:0.3.1": canvas, draw | ||||||
|  |  | ||||||
| #let solve(input) = { | #let solve(input) = { | ||||||
|   let lines = input.split("\n") |   let lines = input.split("\n") | ||||||
| @@ -26,8 +27,62 @@ | |||||||
|   return total |   return total | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #let visualize(input) = { | ||||||
|  |   let lines = input.split("\n") | ||||||
|  |   let w = lines.first().len() | ||||||
|  |   let h = lines.len() | ||||||
|  |  | ||||||
|  |   let perms = (("M", "S"), ("S", "M")) | ||||||
|  |   let positions = () | ||||||
|  |   for y in range(1, h - 1) { | ||||||
|  |     for x in range(1, w - 1) { | ||||||
|  |       if lines.at(y).at(x) == "A" { | ||||||
|  |         let tl = lines.at(y - 1).at(x - 1) | ||||||
|  |         let tr = lines.at(y - 1).at(x + 1) | ||||||
|  |         let bl = lines.at(y + 1).at(x - 1) | ||||||
|  |         let br = lines.at(y + 1).at(x + 1) | ||||||
|  |         let tlbr = (tl, br) | ||||||
|  |         let bltr = (bl, tr) | ||||||
|  |         if tlbr in perms and bltr in perms { | ||||||
|  |           positions.push((x, y)) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   canvas(length: 2em, { | ||||||
|  |     for y in range(h) { | ||||||
|  |       for x in range(w) { | ||||||
|  |         let valid = (x, y) in positions | ||||||
|  |         if valid { | ||||||
|  |           draw.circle( | ||||||
|  |             (x, y), | ||||||
|  |             radius: 0.3, | ||||||
|  |             stroke: red, | ||||||
|  |             name: str(x) + "-" + str(y) | ||||||
|  |           ) | ||||||
|  |           for dy in (-1, 1) { | ||||||
|  |             for dx in (-1, 1) { | ||||||
|  |               draw.line( | ||||||
|  |                 str(x) + "-" + str(y), | ||||||
|  |                 (x + dx * 0.75, y + dy * 0.75), | ||||||
|  |                 stroke: red | ||||||
|  |               ) | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         draw.content( | ||||||
|  |           (x, y), | ||||||
|  |           lines.at(y).at(x) | ||||||
|  |         ) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
| #show-puzzle( | #show-puzzle( | ||||||
|   4, 2, |   4, 2, | ||||||
|   solve, |   solve, | ||||||
|   example: 9 |   example: 9, | ||||||
|  |   visualize: visualize | ||||||
| ) | ) | ||||||
							
								
								
									
										127
									
								
								src/day5/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								src/day5/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  | #import "@preview/cetz:0.3.1": canvas, draw | ||||||
|  |  | ||||||
|  | #let make-rules-dict(rules) = { | ||||||
|  |   let dict = (:) | ||||||
|  |  | ||||||
|  |   for rule in rules { | ||||||
|  |     let (a, b) = rule | ||||||
|  |  | ||||||
|  |     if a not in dict { | ||||||
|  |       dict.insert(a, ()) | ||||||
|  |     } | ||||||
|  |     dict.at(a).push(b) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return dict | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let is-update-valid(dict, update) = { | ||||||
|  |   for i in range(update.len() - 1) { | ||||||
|  |     let a = str(update.at(i)) | ||||||
|  |     for j in range(i + 1, update.len()) { | ||||||
|  |       let b = str(update.at(j)) | ||||||
|  |       if a in dict.at(b, default: ()) { | ||||||
|  |         return false | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let (rules, updates) = input.split("\n\n") | ||||||
|  |   rules = rules.split("\n").map(l => l.split("|")) | ||||||
|  |   updates = updates.split("\n").map(l => l.split(",").map(int)) | ||||||
|  |  | ||||||
|  |   let total = 0 | ||||||
|  |   let rules-dict = make-rules-dict(rules) | ||||||
|  |  | ||||||
|  |   for update in updates { | ||||||
|  |     if is-update-valid(rules-dict, update) { | ||||||
|  |       total += update.at(calc.div-euclid(update.len(), 2)) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let visualize(input) = { | ||||||
|  |   let (rules, updates) = input.split("\n\n") | ||||||
|  |   rules = rules.split("\n").map(l => l.split("|").map(int)) | ||||||
|  |   updates = updates.split("\n").map(l => l.split(",").map(int)) | ||||||
|  |  | ||||||
|  |   let total = 0 | ||||||
|  |   //let rules-dict = make-rules-dict(rules) | ||||||
|  |  | ||||||
|  |   let diags = () | ||||||
|  |   for update in updates { | ||||||
|  |     let diag = canvas(length: 3em, { | ||||||
|  |       for (x, n) in update.enumerate() { | ||||||
|  |         draw.circle( | ||||||
|  |           (x, 0), | ||||||
|  |           radius: 0.4, | ||||||
|  |           name: str(x) | ||||||
|  |         ) | ||||||
|  |         draw.content( | ||||||
|  |           (x, 0), | ||||||
|  |           str(n) | ||||||
|  |         ) | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       let flip = false | ||||||
|  |       let c = 1 | ||||||
|  |       for (a, b) in rules { | ||||||
|  |         let i = update.position(n => n == a) | ||||||
|  |         let j = update.position(n => n == b) | ||||||
|  |         if i == none or j == none { | ||||||
|  |           continue | ||||||
|  |         } | ||||||
|  |         let anchor = if flip {".south"} else {".north"} | ||||||
|  |         let pt-i = str(i) + anchor | ||||||
|  |         let pt-j = str(j) + anchor | ||||||
|  |         let col = if j < i {red} else {green} | ||||||
|  |  | ||||||
|  |         draw.arc-through( | ||||||
|  |           pt-i, | ||||||
|  |           ( | ||||||
|  |             rel: (0, if flip {-c / 10} else {c / 10}), | ||||||
|  |             to: (pt-i, 50%, pt-j) | ||||||
|  |           ), | ||||||
|  |           pt-j, | ||||||
|  |           mark: (end: ">", fill: col), | ||||||
|  |           stroke: col | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         flip = not flip | ||||||
|  |         c += 1 | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     diags.push(diag) | ||||||
|  |  | ||||||
|  |     /*if is-update-valid(rules-dict, update) { | ||||||
|  |       total += update.at(calc.div-euclid(update.len(), 2)) | ||||||
|  |     }*/ | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   diags.last() = grid.cell( | ||||||
|  |     colspan: 2 - calc.rem(diags.len() - 1, 2), | ||||||
|  |     diags.last() | ||||||
|  |   ) | ||||||
|  |  | ||||||
|  |   grid( | ||||||
|  |     columns: 2, | ||||||
|  |     stroke: (paint: black, dash: "dashed"), | ||||||
|  |     align: center + horizon, | ||||||
|  |     inset: 0.4em, | ||||||
|  |     ..diags | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   5, 1, | ||||||
|  |   solve, | ||||||
|  |   example: 143, | ||||||
|  |   visualize: visualize | ||||||
|  | ) | ||||||
							
								
								
									
										63
									
								
								src/day5/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/day5/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let make-rules-dict(rules) = { | ||||||
|  |   let dict = (:) | ||||||
|  |  | ||||||
|  |   for rule in rules { | ||||||
|  |     let (a, b) = rule | ||||||
|  |  | ||||||
|  |     if a not in dict { | ||||||
|  |       dict.insert(a, ()) | ||||||
|  |     } | ||||||
|  |     dict.at(a).push(b) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return dict | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Bubble sort | ||||||
|  | #let fix-update(dict, update) = { | ||||||
|  |   let update = update | ||||||
|  |  | ||||||
|  |   let has-changed = false | ||||||
|  |   let changed = true | ||||||
|  |   while changed { | ||||||
|  |     changed = false | ||||||
|  |     for i in range(update.len() - 1) { | ||||||
|  |       let a = str(update.at(i)) | ||||||
|  |       let b = str(update.at(i + 1)) | ||||||
|  |        | ||||||
|  |       if a in dict.at(b, default: ()) { | ||||||
|  |         update.at(i) = int(b) | ||||||
|  |         update.at(i + 1) = int(a) | ||||||
|  |         changed = true | ||||||
|  |         has-changed = true | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return (has-changed, update) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let (rules, updates) = input.split("\n\n") | ||||||
|  |   rules = rules.split("\n").map(l => l.split("|")) | ||||||
|  |   updates = updates.split("\n").map(l => l.split(",").map(int)) | ||||||
|  |  | ||||||
|  |   let total = 0 | ||||||
|  |   let rules-dict = make-rules-dict(rules) | ||||||
|  |  | ||||||
|  |   for update in updates { | ||||||
|  |     let (has-changed, update) = fix-update(rules-dict, update) | ||||||
|  |     if has-changed { | ||||||
|  |       total += update.at(calc.div-euclid(update.len(), 2)) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   5, 2, | ||||||
|  |   solve, | ||||||
|  |   example: 123 | ||||||
|  | ) | ||||||
							
								
								
									
										152
									
								
								src/day6/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								src/day6/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  | #import "@preview/cetz:0.3.1": canvas, draw | ||||||
|  |  | ||||||
|  | #let offsets = ( | ||||||
|  |   (0, -1), | ||||||
|  |   (1, 0), | ||||||
|  |   (0, 1), | ||||||
|  |   (-1, 0) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let in-grid(x, y, w, h) = { | ||||||
|  |   return 0 <= x and x < w and 0 <= y and y < h | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let grid = input.split("\n").map(l => l.clusters()) | ||||||
|  |  | ||||||
|  |   let w = grid.first().len() | ||||||
|  |   let h = grid.len() | ||||||
|  |  | ||||||
|  |   let ox = 0 | ||||||
|  |   let oy = 0 | ||||||
|  |   let found-start = false | ||||||
|  |   for y in range(h) { | ||||||
|  |     for x in range(w) { | ||||||
|  |       if grid.at(y).at(x) == "^" { | ||||||
|  |         ox = x | ||||||
|  |         oy = y | ||||||
|  |         found-start = true | ||||||
|  |         break | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if found-start { | ||||||
|  |       break | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   grid.at(oy).at(ox) = "v" | ||||||
|  |  | ||||||
|  |   let x = ox | ||||||
|  |   let y = oy | ||||||
|  |   let dir = 0 | ||||||
|  |   let count = 1 | ||||||
|  |   let (dx, dy) = offsets.at(dir) | ||||||
|  |   while in-grid(x, y, w, h) { | ||||||
|  |     if grid.at(y).at(x) != "v" { | ||||||
|  |       grid.at(y).at(x) = "v" | ||||||
|  |       count += 1 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let x2 = x + dx | ||||||
|  |     let y2 = y + dy | ||||||
|  |     if not in-grid(x2, y2, w, h) { | ||||||
|  |       break | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     for _ in range(4) { | ||||||
|  |       let next = grid.at(y2).at(x2) | ||||||
|  |  | ||||||
|  |       if next == "#" { | ||||||
|  |         dir = calc.rem(dir + 1, 4) | ||||||
|  |         (dx, dy) = offsets.at(dir) | ||||||
|  |         x2 = x + dx | ||||||
|  |         y2 = y + dy | ||||||
|  |       } else { | ||||||
|  |         break | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     x = x2 | ||||||
|  |     y = y2 | ||||||
|  |   } | ||||||
|  |   return count | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let visualize(input) = { | ||||||
|  |   let grid = input.split("\n").map(l => l.clusters()) | ||||||
|  |  | ||||||
|  |   let w = grid.first().len() | ||||||
|  |   let h = grid.len() | ||||||
|  |  | ||||||
|  |   canvas(length: 2em, { | ||||||
|  |     let (ox, oy) = (0, 0) | ||||||
|  |     for y in range(h) { | ||||||
|  |       for x in range(w) { | ||||||
|  |         let c = grid.at(y).at(x) | ||||||
|  |         draw.circle( | ||||||
|  |           (x, -y), | ||||||
|  |           radius: if c == "#" {0.4} else {0.2}, | ||||||
|  |           fill: if c == "#" { | ||||||
|  |             red | ||||||
|  |           } else if c == "^" { | ||||||
|  |             green | ||||||
|  |           } else { | ||||||
|  |             gray.lighten(40%) | ||||||
|  |           } | ||||||
|  |         ) | ||||||
|  |         if c == "^" { | ||||||
|  |           (ox, oy) = (x, y) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let x = ox | ||||||
|  |     let y = oy | ||||||
|  |     let dir = 0 | ||||||
|  |     let path = () | ||||||
|  |     let (dx, dy) = offsets.at(dir) | ||||||
|  |     while in-grid(x, y, w, h) { | ||||||
|  |       path.push((x, y)) | ||||||
|  |  | ||||||
|  |       let x2 = x + dx | ||||||
|  |       let y2 = y + dy | ||||||
|  |       if not in-grid(x2, y2, w, h) { | ||||||
|  |         break | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       for _ in range(4) { | ||||||
|  |         let next = grid.at(y2).at(x2) | ||||||
|  |  | ||||||
|  |         if next == "#" { | ||||||
|  |           dir = calc.rem(dir + 1, 4) | ||||||
|  |           (dx, dy) = offsets.at(dir) | ||||||
|  |           x2 = x + dx | ||||||
|  |           y2 = y + dy | ||||||
|  |         } else { | ||||||
|  |           break | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       let col = if path.len() == 1 {green} else {blue} | ||||||
|  |       draw.line( | ||||||
|  |         (x, -y), | ||||||
|  |         (x2, -y2), | ||||||
|  |         mark: (end: ">"), | ||||||
|  |         fill: col, | ||||||
|  |         stroke: col | ||||||
|  |       ) | ||||||
|  |  | ||||||
|  |       x = x2 | ||||||
|  |       y = y2 | ||||||
|  |     } | ||||||
|  |     //draw.line(..path, stroke: blue) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   6, 1, | ||||||
|  |   solve, | ||||||
|  |   example: 41, | ||||||
|  |   visualize: visualize | ||||||
|  | ) | ||||||
							
								
								
									
										210
									
								
								src/day6/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								src/day6/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,210 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let empty = 0 | ||||||
|  | #let up = 1 | ||||||
|  | #let right = 2 | ||||||
|  | #let down = 4 | ||||||
|  | #let left = 8 | ||||||
|  | #let obstacle = 16 | ||||||
|  | #let possible-obstacle = 32 | ||||||
|  |  | ||||||
|  | #let offsets = ( | ||||||
|  |   str(up): (0, -1), | ||||||
|  |   str(right): (1, 0), | ||||||
|  |   str(down): (0, 1), | ||||||
|  |   str(left): (-1, 0) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let values = ( | ||||||
|  |   ".": empty, | ||||||
|  |   "#": obstacle, | ||||||
|  |   "^": up, | ||||||
|  |   "O": possible-obstacle | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let in-grid(x, y, w, h) = { | ||||||
|  |   return 0 <= x and x < w and 0 <= y and y < h | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let rotate(dir) = { | ||||||
|  |   return if dir == left {up} else {dir.bit-lshift(1)} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let puzzle1(grid, ox, oy, dir) = { | ||||||
|  |   let w = grid.first().len() | ||||||
|  |   let h = grid.len() | ||||||
|  |   let path = ((ox, oy, dir),) | ||||||
|  |   let (x, y) = (ox, oy) | ||||||
|  |    | ||||||
|  |   let (dx, dy) = (0, 0) | ||||||
|  |   let (x2, y2) = (x, y) | ||||||
|  |   while true { | ||||||
|  |     (dx, dy) = offsets.at(str(dir)) | ||||||
|  |     (x2, y2) = (x + dx, y + dy) | ||||||
|  |     grid.at(y).at(x) = grid.at(y).at(x).bit-or(dir) | ||||||
|  |     path.push((x2, y2, dir)) | ||||||
|  |      | ||||||
|  |     if not in-grid(x2, y2, w, h) { | ||||||
|  |       break | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Not relevant | ||||||
|  |     /* | ||||||
|  |     if grid.at(y2).at(x2).bit-and(dir) != 0 { | ||||||
|  |       loops = true | ||||||
|  |       break | ||||||
|  |     } | ||||||
|  |     */ | ||||||
|  |  | ||||||
|  |     if grid.at(y2).at(x2) == obstacle { | ||||||
|  |       dir = rotate(dir) | ||||||
|  |     } else { | ||||||
|  |       (x, y) = (x2, y2) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return path | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let add-obstacle(rows, cols, x, y) = { | ||||||
|  |   let add-in-line(line, v) = { | ||||||
|  |     if line.len() == 0 { | ||||||
|  |       line.push(v) | ||||||
|  |       return line | ||||||
|  |     } | ||||||
|  |     for (i, v2) in line.enumerate() { | ||||||
|  |       if v < v2 { | ||||||
|  |         line.insert(i, v) | ||||||
|  |         return line | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     line.push(v) | ||||||
|  |     return line | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   rows.at(y) = add-in-line(rows.at(y), x) | ||||||
|  |   cols.at(x) = add-in-line(cols.at(x), y) | ||||||
|  |   return (rows, cols) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let walk-loops(rows, cols, ox, oy, dir) = { | ||||||
|  |   let (x, y) = (ox, oy) | ||||||
|  |   let w = cols.len() | ||||||
|  |   let h = rows.len() | ||||||
|  |   let visited = () | ||||||
|  |  | ||||||
|  |   while true { | ||||||
|  |     let pos = (x, y, dir) | ||||||
|  |     if pos in visited { | ||||||
|  |       return true | ||||||
|  |     } | ||||||
|  |     visited.push(pos) | ||||||
|  |  | ||||||
|  |     let line = if dir in (up, down) {cols.at(x)} else {rows.at(y)} | ||||||
|  |     let v = if dir in (up, down) {y} else {x} | ||||||
|  |  | ||||||
|  |     // No obstacle | ||||||
|  |     if line.len() == 0 { | ||||||
|  |       return false | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Leave grid | ||||||
|  |     if dir in (up, left) { | ||||||
|  |       if v < line.first() { | ||||||
|  |         return false | ||||||
|  |       } | ||||||
|  |     } else if v > line.last() { | ||||||
|  |       return false | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let i = line.len() - 1 | ||||||
|  |     for (j, v2) in line.enumerate() { | ||||||
|  |       if v < v2 { | ||||||
|  |         i = j | ||||||
|  |         if dir in (left, up) { | ||||||
|  |           i -= 1 | ||||||
|  |         } | ||||||
|  |         break | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     let v2 = line.at(i) | ||||||
|  |     if dir == up { | ||||||
|  |       y = v2 + 1 | ||||||
|  |     } else if dir == right { | ||||||
|  |       x = v2 - 1 | ||||||
|  |     } else if dir == down { | ||||||
|  |       y = v2 - 1 | ||||||
|  |     } else { | ||||||
|  |       x = v2 + 1 | ||||||
|  |     } | ||||||
|  |     dir = rotate(dir) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let grid = input.split("\n").map(l => l.clusters()) | ||||||
|  |  | ||||||
|  |   let w = grid.first().len() | ||||||
|  |   let h = grid.len() | ||||||
|  |  | ||||||
|  |   let ox = 0 | ||||||
|  |   let oy = 0 | ||||||
|  |   for (y, line) in grid.enumerate() { | ||||||
|  |     for (x, cell) in line.enumerate() { | ||||||
|  |       let value = values.at(cell) | ||||||
|  |       if value == up { | ||||||
|  |         (ox, oy) = (x, y) | ||||||
|  |       } | ||||||
|  |       grid.at(y).at(x) = value | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let cols = () | ||||||
|  |   let rows = () | ||||||
|  |  | ||||||
|  |   for y in range(h) { | ||||||
|  |     let row = () | ||||||
|  |     for x in range(w) { | ||||||
|  |       if grid.at(y).at(x) == obstacle { | ||||||
|  |         row.push(x) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     rows.push(row) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for x in range(w) { | ||||||
|  |     let col = () | ||||||
|  |     for y in range(h) { | ||||||
|  |       if grid.at(y).at(x) == obstacle { | ||||||
|  |         col.push(y) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     cols.push(col) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let path = puzzle1(grid, ox, oy, up) | ||||||
|  |   let count = 0 | ||||||
|  |  | ||||||
|  |   for (x, y, _) in path.slice(1, path.len() - 1) { | ||||||
|  |     if x == ox and y == oy { | ||||||
|  |       continue | ||||||
|  |     } | ||||||
|  |     let cell = grid.at(y).at(x) | ||||||
|  |     if cell.bit-and(possible-obstacle) == 0 { | ||||||
|  |       let (rows2, cols2) = add-obstacle(rows, cols, x, y) | ||||||
|  |  | ||||||
|  |       if walk-loops(rows2, cols2, ox, oy, up) { | ||||||
|  |         count += 1 | ||||||
|  |         grid.at(y).at(x) = cell.bit-or(possible-obstacle) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return count | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   6, 2, | ||||||
|  |   solve, | ||||||
|  |   example: 6 | ||||||
|  | ) | ||||||
							
								
								
									
										160
									
								
								src/day7/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								src/day7/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,160 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  | #import "@preview/cetz:0.3.1": canvas, draw | ||||||
|  |  | ||||||
|  | #let solvable(values, target) = { | ||||||
|  |   if values.len() == 1 { | ||||||
|  |     return values.last() == target | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let values = values | ||||||
|  |   let v = values.pop() | ||||||
|  |   if calc.rem(target, v) == 0 { | ||||||
|  |     if solvable(values, target / v) { | ||||||
|  |       return true | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if v > target { | ||||||
|  |     return false | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return solvable(values, target - v) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let equations = input.split("\n") | ||||||
|  |  | ||||||
|  |   let total = 0 | ||||||
|  |   for equation in equations { | ||||||
|  |     let (target, values) = equation.split(": ") | ||||||
|  |     target = int(target) | ||||||
|  |     values = values.split(" ").map(int) | ||||||
|  |  | ||||||
|  |     if solvable(values, target) { | ||||||
|  |       total += target | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let visualize(input) = { | ||||||
|  |   let get-solution(values, target) = { | ||||||
|  |     if values.len() == 1 { | ||||||
|  |       return (values.last() == target, (target,)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let values = values | ||||||
|  |     let v = values.pop() | ||||||
|  |     if calc.rem(target, v) == 0 { | ||||||
|  |       let r = get-solution(values, target / v) | ||||||
|  |       if r.first() { | ||||||
|  |         return (true, r.last() + ("*", v)) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if v > target { | ||||||
|  |       return (false, ()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let r = get-solution(values, target - v) | ||||||
|  |     if r.first() { | ||||||
|  |       return (true, r.last() + ("+", v)) | ||||||
|  |     } | ||||||
|  |     return (false, ()) | ||||||
|  |   } | ||||||
|  |   let num(v, x, y, name) = { | ||||||
|  |     draw.circle( | ||||||
|  |       (x, y), | ||||||
|  |       radius: 0.4, | ||||||
|  |       fill: gray.lighten(60%), | ||||||
|  |       name: name | ||||||
|  |     ) | ||||||
|  |     draw.content((x, y), str(v)) | ||||||
|  |   } | ||||||
|  |   let ope(o, x, y, name) = { | ||||||
|  |     let s = ( | ||||||
|  |       "+": sym.plus, | ||||||
|  |       "*": sym.times | ||||||
|  |     ).at(o) | ||||||
|  |     draw.circle( | ||||||
|  |       (x, y), | ||||||
|  |       radius: 0.3, | ||||||
|  |       fill: orange.lighten(60%), | ||||||
|  |       name: name | ||||||
|  |     ) | ||||||
|  |     draw.content((x, y), s) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let equations = input.split("\n") | ||||||
|  |   let diags = () | ||||||
|  |   for equation in equations { | ||||||
|  |     let (target, values) = equation.split(": ") | ||||||
|  |     target = int(target) | ||||||
|  |     values = values.split(" ").map(int) | ||||||
|  |  | ||||||
|  |     let r = get-solution(values, target) | ||||||
|  |     if not r.first() { | ||||||
|  |       continue | ||||||
|  |     } | ||||||
|  |     let diag = canvas({ | ||||||
|  |       let lvl = 0 | ||||||
|  |       let steps = r.last() | ||||||
|  |       let prev = none | ||||||
|  |       let v = steps.remove(0) | ||||||
|  |  | ||||||
|  |       while true { | ||||||
|  |         num(v, lvl, -lvl, str(lvl) + "-0") | ||||||
|  |  | ||||||
|  |         if lvl != 0 { | ||||||
|  |           draw.line( | ||||||
|  |             str(lvl - 1) + "-1", | ||||||
|  |             str(lvl) + "-0" | ||||||
|  |           ) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if steps.len() == 0 { | ||||||
|  |           break | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let op = steps.remove(0) | ||||||
|  |         let v2 = steps.remove(0) | ||||||
|  |  | ||||||
|  |         ope(op, lvl + 1, -lvl, str(lvl) + "-1") | ||||||
|  |         num(v2, lvl + 2, -lvl, str(lvl) + "-2") | ||||||
|  |         draw.line( | ||||||
|  |           str(lvl) + "-0", | ||||||
|  |           str(lvl) + "-1" | ||||||
|  |         ) | ||||||
|  |         draw.line( | ||||||
|  |           str(lvl) + "-2", | ||||||
|  |           str(lvl) + "-1" | ||||||
|  |         ) | ||||||
|  |         if op == "+" { | ||||||
|  |           v += v2 | ||||||
|  |         } else if op == "*" { | ||||||
|  |           v *= v2 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         lvl += 1 | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |     diags.push(diag) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if calc.rem(diags.len(), 2) == 1 { | ||||||
|  |     diags.last() = grid.cell(colspan: 2, diags.last()) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   grid( | ||||||
|  |     columns: 2, | ||||||
|  |     stroke: (paint: black, dash: "dashed"), | ||||||
|  |     align: center + horizon, | ||||||
|  |     inset: 0.4em, | ||||||
|  |     ..diags | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   7, 1, | ||||||
|  |   solve, | ||||||
|  |   example: 3749, | ||||||
|  |   visualize: visualize | ||||||
|  | ) | ||||||
							
								
								
									
										197
									
								
								src/day7/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								src/day7/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,197 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  | #import "@preview/cetz:0.3.1": canvas, draw | ||||||
|  |  | ||||||
|  | #let concat(a, b) = { | ||||||
|  |   return int(str(a) + str(b)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solvable(values, target) = { | ||||||
|  |   if values.len() == 1 { | ||||||
|  |     return values.last() == target | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let values = values | ||||||
|  |   let v = values.pop() | ||||||
|  |   if calc.rem(target, v) == 0 { | ||||||
|  |     if solvable(values, target / v) { | ||||||
|  |       return true | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   let str-target = str(target) | ||||||
|  |   let str-v = str(v) | ||||||
|  |   if str-target == str-v { | ||||||
|  |     return false | ||||||
|  |   } | ||||||
|  |   if str-target.ends-with(str-v) { | ||||||
|  |     let target2 = str-target.slice( | ||||||
|  |       0, | ||||||
|  |       str-target.len() - str-v.len() | ||||||
|  |     ) | ||||||
|  |     if solvable(values, int(target2)) { | ||||||
|  |       return true | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if v > target { | ||||||
|  |     return false | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return solvable(values, target - v) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let equations = input.split("\n") | ||||||
|  |  | ||||||
|  |   let total = 0 | ||||||
|  |   for equation in equations { | ||||||
|  |     let (target, values) = equation.split(": ") | ||||||
|  |     target = int(target) | ||||||
|  |     values = values.split(" ").map(int) | ||||||
|  |  | ||||||
|  |     if solvable(values, target) { | ||||||
|  |       total += target | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let visualize(input) = { | ||||||
|  |   let get-solution(values, target) = { | ||||||
|  |     if values.len() == 1 { | ||||||
|  |       return (values.last() == target, (target,)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let values = values | ||||||
|  |     let v = values.pop() | ||||||
|  |     if calc.rem(target, v) == 0 { | ||||||
|  |       let r = get-solution(values, target / v) | ||||||
|  |       if r.first() { | ||||||
|  |         return (true, r.last() + ("*", v)) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     let str-target = str(target) | ||||||
|  |     let str-v = str(v) | ||||||
|  |     if str-target == str-v { | ||||||
|  |       return (false, ()) | ||||||
|  |     } | ||||||
|  |     if str-target.ends-with(str-v) { | ||||||
|  |       let target2 = str-target.slice( | ||||||
|  |         0, | ||||||
|  |         str-target.len() - str-v.len() | ||||||
|  |       ) | ||||||
|  |       let r = get-solution(values, int(target2)) | ||||||
|  |       if r.first() { | ||||||
|  |         return (true, r.last() + ("||", v)) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if v > target { | ||||||
|  |       return (false, ()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let r = get-solution(values, target - v) | ||||||
|  |     if r.first() { | ||||||
|  |       return (true, r.last() + ("+", v)) | ||||||
|  |     } | ||||||
|  |     return (false, ()) | ||||||
|  |   } | ||||||
|  |   let num(v, x, y, name) = { | ||||||
|  |     draw.circle( | ||||||
|  |       (x, y), | ||||||
|  |       radius: 0.4, | ||||||
|  |       fill: gray.lighten(60%), | ||||||
|  |       name: name | ||||||
|  |     ) | ||||||
|  |     draw.content((x, y), str(v)) | ||||||
|  |   } | ||||||
|  |   let ope(o, x, y, name) = { | ||||||
|  |     let s = ( | ||||||
|  |       "+": sym.plus, | ||||||
|  |       "*": sym.times, | ||||||
|  |       "||": sym.bar + sym.bar | ||||||
|  |     ).at(o) | ||||||
|  |     draw.circle( | ||||||
|  |       (x, y), | ||||||
|  |       radius: 0.3, | ||||||
|  |       fill: orange.lighten(60%), | ||||||
|  |       name: name | ||||||
|  |     ) | ||||||
|  |     draw.content((x, y), s) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let equations = input.split("\n") | ||||||
|  |   let diags = () | ||||||
|  |   for equation in equations { | ||||||
|  |     let (target, values) = equation.split(": ") | ||||||
|  |     target = int(target) | ||||||
|  |     values = values.split(" ").map(int) | ||||||
|  |  | ||||||
|  |     let r = get-solution(values, target) | ||||||
|  |     if not r.first() { | ||||||
|  |       continue | ||||||
|  |     } | ||||||
|  |     let diag = canvas({ | ||||||
|  |       let lvl = 0 | ||||||
|  |       let steps = r.last() | ||||||
|  |       let prev = none | ||||||
|  |       let v = steps.remove(0) | ||||||
|  |  | ||||||
|  |       while true { | ||||||
|  |         num(v, lvl, -lvl, str(lvl) + "-0") | ||||||
|  |  | ||||||
|  |         if lvl != 0 { | ||||||
|  |           draw.line( | ||||||
|  |             str(lvl - 1) + "-1", | ||||||
|  |             str(lvl) + "-0" | ||||||
|  |           ) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if steps.len() == 0 { | ||||||
|  |           break | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let op = steps.remove(0) | ||||||
|  |         let v2 = steps.remove(0) | ||||||
|  |  | ||||||
|  |         ope(op, lvl + 1, -lvl, str(lvl) + "-1") | ||||||
|  |         num(v2, lvl + 2, -lvl, str(lvl) + "-2") | ||||||
|  |         draw.line( | ||||||
|  |           str(lvl) + "-0", | ||||||
|  |           str(lvl) + "-1" | ||||||
|  |         ) | ||||||
|  |         draw.line( | ||||||
|  |           str(lvl) + "-2", | ||||||
|  |           str(lvl) + "-1" | ||||||
|  |         ) | ||||||
|  |         if op == "+" { | ||||||
|  |           v += v2 | ||||||
|  |         } else if op == "*" { | ||||||
|  |           v *= v2 | ||||||
|  |         } else if op == "||" { | ||||||
|  |           v = int(str(v) + str(v2)) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         lvl += 1 | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |     diags.push(diag) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   diags.last() = grid.cell( | ||||||
|  |     colspan: 3 - calc.rem(diags.len() - 1, 3), | ||||||
|  |     diags.last() | ||||||
|  |   ) | ||||||
|  |  | ||||||
|  |   grid( | ||||||
|  |     columns: 3, | ||||||
|  |     stroke: (paint: black, dash: "dashed"), | ||||||
|  |     align: center + horizon, | ||||||
|  |     inset: 0.4em, | ||||||
|  |     ..diags | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   7, 2, | ||||||
|  |   solve, | ||||||
|  |   example: 11387, | ||||||
|  |   visualize: visualize | ||||||
|  | ) | ||||||
							
								
								
									
										98
									
								
								src/day8/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/day8/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  | #import "@preview/cetz:0.3.1": canvas, draw | ||||||
|  |  | ||||||
|  | #let in-grid(w, h, x, y) = { | ||||||
|  |   return 0 <= x and x < w and 0 <= y and y < h | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input, return-data: false) = { | ||||||
|  |   let by-freq = (:) | ||||||
|  |   let antinodes = () | ||||||
|  |  | ||||||
|  |   let grid = input.split("\n").map(l => l.clusters()) | ||||||
|  |   let w = grid.first().len() | ||||||
|  |   let h = grid.len() | ||||||
|  |  | ||||||
|  |   let in-grid = in-grid.with(w, h) | ||||||
|  |  | ||||||
|  |   for y in range(h) { | ||||||
|  |     for x in range(w) { | ||||||
|  |       let c = grid.at(y).at(x) | ||||||
|  |       if c == "." { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       if c not in by-freq { | ||||||
|  |         by-freq.insert(c, ()) | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       for (x2, y2) in by-freq.at(c) { | ||||||
|  |         let (dx, dy) = (x2 - x, y2 - y) | ||||||
|  |         let node1 = (x - dx, y - dy) | ||||||
|  |         let node2 = (x2 + dx, y2 + dy) | ||||||
|  |  | ||||||
|  |         if in-grid(..node1) and node1 not in antinodes { | ||||||
|  |           antinodes.push(node1) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if in-grid(..node2) and node2 not in antinodes { | ||||||
|  |           antinodes.push(node2) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       by-freq.at(c).push((x, y)) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return if return-data { | ||||||
|  |     (grid, by-freq, antinodes) | ||||||
|  |   } else { | ||||||
|  |     antinodes.len() | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let visualize(solve, input) = { | ||||||
|  |   let (grid, by-freq, antinodes) = solve(input, return-data: true) | ||||||
|  |   let w = grid.first().len() | ||||||
|  |   let h = grid.len() | ||||||
|  |  | ||||||
|  |   let freqs = by-freq.keys() | ||||||
|  |   let n-freqs = freqs.len() | ||||||
|  |   let colors = gradient.linear(red, orange, yellow, green, aqua, blue, purple) | ||||||
|  |    | ||||||
|  |   canvas(length: 1.65em, { | ||||||
|  |     for y in range(h) { | ||||||
|  |       for x in range(w) { | ||||||
|  |         draw.circle( | ||||||
|  |           (x, y), | ||||||
|  |           radius: 0.1, | ||||||
|  |           fill: gray, | ||||||
|  |           stroke: none | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         for (i, freq) in freqs.enumerate() { | ||||||
|  |           let col = colors.sample(i * 100% / n-freqs) | ||||||
|  |           for (ax, ay) in by-freq.at(freq) { | ||||||
|  |             draw.circle( | ||||||
|  |               (ax, ay), | ||||||
|  |               radius: 0.4, | ||||||
|  |               fill: col | ||||||
|  |             ) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     for (anx, any) in antinodes { | ||||||
|  |       draw.rect( | ||||||
|  |         (anx - 0.4, any - 0.4), | ||||||
|  |         (anx + 0.4, any + 0.4), | ||||||
|  |       ) | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   8, 1, | ||||||
|  |   solve, | ||||||
|  |   example: 14, | ||||||
|  |   visualize: visualize.with(solve) | ||||||
|  | ) | ||||||
							
								
								
									
										72
									
								
								src/day8/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/day8/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  | #import "puzzle1.typ": visualize | ||||||
|  |  | ||||||
|  | #let in-grid(w, h, x, y) = { | ||||||
|  |   return 0 <= x and x < w and 0 <= y and y < h | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let get-antinodes(in-grid, x1, y1, x2, y2) = { | ||||||
|  |   let (dx, dy) = (x2 - x1, y2 - y1) | ||||||
|  |   let f = calc.gcd(dx, dy) | ||||||
|  |   dx /= f | ||||||
|  |   dy /= f | ||||||
|  |  | ||||||
|  |   let walk(ox, oy, dx, dy) = { | ||||||
|  |     let x = ox | ||||||
|  |     let y = oy | ||||||
|  |     let pos = () | ||||||
|  |     while in-grid(x, y) { | ||||||
|  |       pos.push((x, y)) | ||||||
|  |       x += dx | ||||||
|  |       y += dy | ||||||
|  |     } | ||||||
|  |     return pos | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   let antinodes = walk(x1, y1, dx, dy) + walk(x1, y1, -dx, -dy) | ||||||
|  |  | ||||||
|  |   return antinodes | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input, return-data: false) = { | ||||||
|  |   let by-freq = (:) | ||||||
|  |   let antinodes = () | ||||||
|  |  | ||||||
|  |   let grid = input.split("\n").map(l => l.clusters()) | ||||||
|  |   let w = grid.first().len() | ||||||
|  |   let h = grid.len() | ||||||
|  |  | ||||||
|  |   let in-grid = in-grid.with(w, h) | ||||||
|  |   let get-antinodes = get-antinodes.with(in-grid) | ||||||
|  |  | ||||||
|  |   for y in range(h) { | ||||||
|  |     for x in range(w) { | ||||||
|  |       let c = grid.at(y).at(x) | ||||||
|  |       if c == "." { | ||||||
|  |         continue | ||||||
|  |       } | ||||||
|  |       if c not in by-freq { | ||||||
|  |         by-freq.insert(c, ()) | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       for (x2, y2) in by-freq.at(c) { | ||||||
|  |         antinodes += get-antinodes(x, y, x2, y2) | ||||||
|  |       } | ||||||
|  |       by-freq.at(c).push((x, y)) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   antinodes = antinodes.dedup() | ||||||
|  |   return if return-data { | ||||||
|  |     (grid, by-freq, antinodes) | ||||||
|  |   } else { | ||||||
|  |     antinodes.len() | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   8, 2, | ||||||
|  |   solve, | ||||||
|  |   example: 34, | ||||||
|  |   visualize: visualize.with(solve) | ||||||
|  | ) | ||||||
							
								
								
									
										152
									
								
								src/day9/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								src/day9/puzzle1.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  |  | ||||||
|  | #let parse-input(input) = { | ||||||
|  |   let blocks = () | ||||||
|  |   let holes = () | ||||||
|  |  | ||||||
|  |   let block-i = 0 | ||||||
|  |   let is-block = true | ||||||
|  |   let pos = 0 | ||||||
|  |   for c in input { | ||||||
|  |     let block = (pos, int(c)) | ||||||
|  |     if is-block { | ||||||
|  |       block.push(block-i) | ||||||
|  |       block-i += 1 | ||||||
|  |       blocks.push(block) | ||||||
|  |     } else { | ||||||
|  |       holes.push(block) | ||||||
|  |     } | ||||||
|  |     pos += int(c) | ||||||
|  |     is-block = not is-block | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   return (blocks, holes) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let compute-checksum(blocks) = { | ||||||
|  |   let total = 0 | ||||||
|  |   for (i0, l, id) in blocks { | ||||||
|  |     total += id * range(i0, i0 + l).sum() | ||||||
|  |   } | ||||||
|  |   return total | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let insert(list, elmt) = { | ||||||
|  |   for (i, elmt2) in list.enumerate() { | ||||||
|  |     if elmt.first() < elmt2.first() { | ||||||
|  |       list.insert(i, elmt) | ||||||
|  |       return list | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   list.push(elmt) | ||||||
|  |   return list | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let (blocks, holes) = parse-input(input) | ||||||
|  |  | ||||||
|  |   for (hi, hl) in holes { | ||||||
|  |     while hl > 0 { | ||||||
|  |       if blocks.last().first() < hi + hl { | ||||||
|  |         break | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       let (bi, bl, bid) = blocks.pop() | ||||||
|  |  | ||||||
|  |       let len = calc.min(hl, bl) | ||||||
|  |  | ||||||
|  |       blocks.insert(0, (hi, len, bid)) | ||||||
|  |  | ||||||
|  |       if len < bl { | ||||||
|  |         blocks = insert(blocks, (bi, bl - len, bid)) | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       hl -= len | ||||||
|  |       hi += len | ||||||
|  |     } | ||||||
|  |     if hl > 0 { | ||||||
|  |       break | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   return compute-checksum(blocks) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let col-gradient = gradient.linear(red, orange, yellow, green, aqua, blue, purple) | ||||||
|  |  | ||||||
|  | #let show-fs(size, max-id, blocks) = { | ||||||
|  |   let cells = () | ||||||
|  |   for (bi, bl, bid) in blocks { | ||||||
|  |     cells.push( | ||||||
|  |       grid.cell( | ||||||
|  |         x: bi, | ||||||
|  |         colspan: bl, | ||||||
|  |         fill: col-gradient.sample(bid * 100% / max-id), | ||||||
|  |         str(bid) | ||||||
|  |       ) | ||||||
|  |     ) | ||||||
|  |   } | ||||||
|  |   grid( | ||||||
|  |     columns: (1fr,) * size, | ||||||
|  |     align: center + horizon, | ||||||
|  |     stroke: black, | ||||||
|  |     inset: 0.3em, | ||||||
|  |     ..cells | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let visualize(input) = { | ||||||
|  |   let (blocks, holes) = parse-input(input) | ||||||
|  |   let max-id = blocks.last().last() | ||||||
|  |  | ||||||
|  |   let last-block = blocks.last() | ||||||
|  |   let last-holes = holes.last() | ||||||
|  |   let show-fs = show-fs.with( | ||||||
|  |     calc.max( | ||||||
|  |       last-block.first() + last-block.at(1), | ||||||
|  |       last-holes.first() + last-holes.last() | ||||||
|  |     ), | ||||||
|  |     max-id | ||||||
|  |   ) | ||||||
|  |   let steps = () | ||||||
|  |   steps.push(show-fs(blocks)) | ||||||
|  |  | ||||||
|  |   for (hi, hl) in holes { | ||||||
|  |     while hl > 0 { | ||||||
|  |       if blocks.last().first() < hi + hl { | ||||||
|  |         break | ||||||
|  |       } | ||||||
|  |       let (bi, bl, bid) = blocks.pop() | ||||||
|  |       let len = calc.min(hl, bl) | ||||||
|  |       blocks.insert(0, (hi, len, bid)) | ||||||
|  |       if len < bl { | ||||||
|  |         blocks = insert(blocks, (bi, bl - len, bid)) | ||||||
|  |       } | ||||||
|  |       hl -= len | ||||||
|  |       hi += len | ||||||
|  |     } | ||||||
|  |     if hl > 0 { | ||||||
|  |       break | ||||||
|  |     } | ||||||
|  |     steps.push(show-fs(blocks)) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   stack( | ||||||
|  |     spacing: 0.5em, | ||||||
|  |     ..steps | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   9, 1, | ||||||
|  |   solve, | ||||||
|  |   example: ( | ||||||
|  |     "1": 60, | ||||||
|  |     "2": 1928 | ||||||
|  |   ), | ||||||
|  |   only-example: true, | ||||||
|  |   visualize: visualize | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Too long to recompile everytime | ||||||
|  | #show-result(6390180901651) | ||||||
							
								
								
									
										86
									
								
								src/day9/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/day9/puzzle2.typ
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | #import "/src/utils.typ": * | ||||||
|  | #import "puzzle1.typ": parse-input, compute-checksum, insert, show-fs | ||||||
|  |  | ||||||
|  | #let solve(input) = { | ||||||
|  |   let (blocks, holes) = parse-input(input) | ||||||
|  |    | ||||||
|  |   let blocks2 = () | ||||||
|  |   for (bi, bl, bid) in blocks.rev() { | ||||||
|  |     for (i, (hi, hl)) in holes.enumerate() { | ||||||
|  |       if hi < bi and bl <= hl { | ||||||
|  |         bi = hi | ||||||
|  |         holes.at(i).first() += bl | ||||||
|  |         holes.at(i).last() -= bl | ||||||
|  |         if bl == hl { | ||||||
|  |           holes.remove(i) | ||||||
|  |         } | ||||||
|  |         break | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     blocks2.push((bi, bl, bid)) | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   return compute-checksum(blocks2) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let visualize(input) = { | ||||||
|  |   let (blocks, holes) = parse-input(input) | ||||||
|  |   let max-id = blocks.last().last() | ||||||
|  |  | ||||||
|  |   let last-block = blocks.last() | ||||||
|  |   let last-holes = holes.last() | ||||||
|  |   let show-fs = show-fs.with( | ||||||
|  |     calc.max( | ||||||
|  |       last-block.first() + last-block.at(1), | ||||||
|  |       last-holes.first() + last-holes.last() | ||||||
|  |     ), | ||||||
|  |     max-id | ||||||
|  |   ) | ||||||
|  |   let steps = () | ||||||
|  |   steps.push(show-fs(blocks)) | ||||||
|  |  | ||||||
|  |   let ids = () | ||||||
|  |   let blocks2 = () | ||||||
|  |   for (bi, bl, bid) in blocks.rev() { | ||||||
|  |     let moved = false | ||||||
|  |     for (i, (hi, hl)) in holes.enumerate() { | ||||||
|  |       if hi < bi and bl <= hl { | ||||||
|  |         bi = hi | ||||||
|  |         holes.at(i).first() += bl | ||||||
|  |         holes.at(i).last() -= bl | ||||||
|  |         if bl == hl { | ||||||
|  |           _ = holes.remove(i) | ||||||
|  |         } | ||||||
|  |         moved = true | ||||||
|  |         break | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     ids.push(bid) | ||||||
|  |     blocks2.push((bi, bl, bid)) | ||||||
|  |     if moved { | ||||||
|  |       steps.push(show-fs( | ||||||
|  |         blocks.filter(b => b.last() not in ids) + | ||||||
|  |         blocks2 | ||||||
|  |       )) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   stack( | ||||||
|  |     spacing: 0.5em, | ||||||
|  |     ..steps | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #show-puzzle( | ||||||
|  |   9, 2, | ||||||
|  |   solve, | ||||||
|  |   example: ( | ||||||
|  |     "1": 132, | ||||||
|  |     "2": 2858 | ||||||
|  |   ), | ||||||
|  |   only-example: true, | ||||||
|  |   visualize: visualize | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Too long to recompile everytime | ||||||
|  | #show-result(6412390114238) | ||||||
							
								
								
									
										
											BIN
										
									
								
								src/main.pdf
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main.pdf
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -17,7 +17,6 @@ | |||||||
|  |  | ||||||
| #v(2cm) | #v(2cm) | ||||||
|  |  | ||||||
| /* |  | ||||||
| #align(center, canvas({ | #align(center, canvas({ | ||||||
|   draw.merge-path( |   draw.merge-path( | ||||||
|     { |     { | ||||||
| @@ -50,7 +49,7 @@ | |||||||
|     fill: rgb("#63584B"), |     fill: rgb("#63584B"), | ||||||
|     stroke: none |     stroke: none | ||||||
|   ) |   ) | ||||||
| }))*/ | })) | ||||||
|  |  | ||||||
|  |  | ||||||
| #v(1fr) | #v(1fr) | ||||||
|   | |||||||
| @@ -81,6 +81,12 @@ | |||||||
|       for (suffix, result) in example.pairs() { |       for (suffix, result) in example.pairs() { | ||||||
|         check-example(day, func, result, suffix: suffix) |         check-example(day, func, result, suffix: suffix) | ||||||
|       } |       } | ||||||
|  |     } else if type(example) == array { | ||||||
|  |       for ex in example { | ||||||
|  |         let suffix = ex.at("suffix", default: none) | ||||||
|  |         let args = ex.at("args", default: (:)) | ||||||
|  |         check-example(day, func.with(..args), ex.result, suffix: suffix) | ||||||
|  |       } | ||||||
|     } else { |     } else { | ||||||
|       check-example(day, func, example) |       check-example(day, func, example) | ||||||
|     } |     } | ||||||
| @@ -189,5 +195,12 @@ | |||||||
|  |  | ||||||
| #let template(body) = { | #let template(body) = { | ||||||
|   set text(font: "Source Sans 3") |   set text(font: "Source Sans 3") | ||||||
|  |   set page( | ||||||
|  |     footer: context { | ||||||
|  |       if counter(page).get().first() != 1 { | ||||||
|  |         align(center, counter(page).display("1 / 1", both: true)) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ) | ||||||
|   body |   body | ||||||
| } | } | ||||||
		Reference in New Issue
	
	Block a user