[Advent of Code 2024](https://adventofcode.com/2024)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import argparse
  2. import enum
  3. import logging
  4. from sys import stdout
  5. from typing import List, Tuple
  6. logger = logging.Logger(__name__)
  7. logger_2 = logging.Logger(f"{__name__}_2")
  8. formatter = logging.Formatter('[%(asctime)s][%(levelname)s] %(message)s')
  9. sh = logging.StreamHandler(stdout)
  10. sh.setLevel(logging.INFO)
  11. sh.setFormatter(formatter)
  12. fh = logging.FileHandler("./day02-2.log", mode="w", encoding="utf-8")
  13. fh_2 = logging.FileHandler("./day02-2_round2.log", mode="w", encoding="utf-8")
  14. fh.setLevel(logging.DEBUG)
  15. fh.setFormatter(formatter)
  16. fh_2.setLevel(logging.DEBUG)
  17. fh_2.setFormatter(formatter)
  18. logger.addHandler(sh)
  19. logger.addHandler(fh)
  20. logger_2.addHandler(fh_2)
  21. class Direction(enum.Enum):
  22. UP = 0
  23. RIGHT = 1
  24. DOWN = 2
  25. LEFT = 3
  26. class Guard:
  27. def __init__(self, grid: List[List[str]], initial_x: int = None, initial_y: int = None, initial_dir: int = None, test_mode: bool = False) -> object:
  28. self.grid = grid
  29. self.height = len(self.grid)
  30. self.width = len(self.grid[0])
  31. logger.info(f"Received a grid with {self.height} lines and {self.width} characters per line.")
  32. self.initial_x = initial_x
  33. self.x = self.initial_x
  34. self.initial_y = initial_y
  35. self.y = self.initial_y
  36. self.initial_dir = initial_dir
  37. self.dir = self.initial_dir
  38. self.test_mode = test_mode
  39. self.visited = set()
  40. self.traveled = 1
  41. def whereami(self) -> Tuple[int,int]:
  42. # Identify the guard's initial position and direction in the grid
  43. breakout = False
  44. x,y,dir = None,None,None
  45. for j, line in enumerate(self.grid):
  46. if breakout:
  47. break
  48. for i, char in enumerate(line):
  49. if char in ["^",">","<","v"]:
  50. x, y = i, j
  51. match char:
  52. case "^":
  53. dir = Direction.UP
  54. case ">":
  55. dir = Direction.RIGHT
  56. case "v":
  57. dir = Direction.DOWN
  58. case "<":
  59. dir = Direction.LEFT
  60. case "_":
  61. raise ValueError(f"char must be one of '^','>','v','<', received {char}")
  62. breakout = True
  63. break
  64. self.initial_x = x
  65. self.x = self.initial_x
  66. self.initial_y = y
  67. self.y = self.initial_y
  68. self.initial_dir = dir
  69. self.dir = self.initial_dir
  70. return (x,y)
  71. def proceed(self):
  72. if self.dir is None:
  73. logger.error("You can't move until you have a direction set.")
  74. raise ValueError
  75. logger.info(f"Proceeding {self.dir}")
  76. match self.dir:
  77. case Direction.UP:
  78. return self.go_north()
  79. case Direction.RIGHT:
  80. return self.go_east()
  81. case Direction.DOWN:
  82. return self.go_south()
  83. case Direction.LEFT:
  84. return self.go_west()
  85. case _:
  86. logger.error(f"Unknown direction! {self.dir}")
  87. raise ValueError
  88. def go_north(self) -> bool:
  89. path = [self.grid[y][self.x] for y in range(self.y-1, -1, -1)]
  90. logger.info(f"Path created from {self.x},{self.y} to {self.x},{0}: {path}.")
  91. exited = "#" not in path
  92. if exited:
  93. blocked = 999999
  94. else:
  95. blocked = path.index("#")
  96. if self.test_mode:
  97. logger.info(f"Found a blocker at ({self.x}, {self.y-blocked-1}).")
  98. for i, char in enumerate(path[:blocked+1]):
  99. j = i + 1 if exited else i
  100. if self.test_mode:
  101. logger.info(f"Walked to ({self.x}, {self.y-j}).")
  102. self.visited.add((self.x, self.y-j))
  103. self.traveled += 1
  104. new_y = self.y - (blocked)
  105. self.y = new_y
  106. self.dir = Direction.RIGHT
  107. return exited
  108. def go_east(self) -> bool:
  109. path = [self.grid[self.y][x] for x in range(self.x+1, self.width)]
  110. logger.info(f"Path created from {self.x},{self.y} to {self.width-1},{self.y}: {path}.")
  111. exited = "#" not in path
  112. if exited:
  113. blocked = 999999
  114. else:
  115. blocked = path.index("#")
  116. if self.test_mode:
  117. logger.info(f"Found a blocker at ({self.x+blocked+1}, {self.y}).")
  118. for i, char in enumerate(path[:blocked+1]):
  119. j = i + 1 if exited else i
  120. if self.test_mode:
  121. logger.info(f"Walked to ({self.x+j}, {self.y}).")
  122. self.visited.add((self.x+j, self.y))
  123. self.traveled += 1
  124. new_x = self.x + (blocked)
  125. self.x = new_x
  126. self.dir = Direction.DOWN
  127. return exited
  128. def go_south(self) -> bool:
  129. path = [self.grid[y][self.x] for y in range(self.y+1, self.height)]
  130. logger.info(f"Path created from {self.x},{self.y} to {self.x},{self.height}: {path}.")
  131. exited = "#" not in path
  132. if exited:
  133. blocked = 999999
  134. else:
  135. blocked = path.index("#")
  136. if self.test_mode:
  137. logger.info(f"Found a blocker at ({self.x}, {self.y+blocked+1}).")
  138. for i, char in enumerate(path[:blocked+1]):
  139. j = i + 1 if exited else i
  140. if self.test_mode:
  141. logger.info(f"Walked to ({self.x}, {self.y+j}).")
  142. self.visited.add((self.x, self.y+j))
  143. self.traveled += 1
  144. new_y = self.y + (blocked)
  145. self.y = new_y
  146. self.dir = Direction.LEFT
  147. return exited
  148. def go_west(self) -> bool:
  149. path = [self.grid[self.y][x] for x in range(self.x-1, -1, -1)]
  150. logger.info(f"Path created from {self.x},{self.y} to {0},{self.y}: {path}.")
  151. exited = "#" not in path
  152. if exited:
  153. blocked = 999999
  154. else:
  155. blocked = path.index("#")
  156. if self.test_mode:
  157. logger.info(f"Found a blocker at ({self.x-blocked-1}, {self.y}).")
  158. for i, char in enumerate(path[:blocked+1]):
  159. j = i + 1 if exited else i
  160. if self.test_mode:
  161. logger.info(f"Walked to ({self.x-j}, {self.y}).")
  162. self.visited.add((self.x-j, self.y))
  163. self.traveled += 1
  164. new_x = self.x - (blocked)
  165. self.x = new_x
  166. self.dir = Direction.UP
  167. return exited
  168. def pathfind(self):
  169. exited = False
  170. while not exited:
  171. exited = self.proceed()
  172. logger.info(f"Found an exit after {self.traveled} steps!")
  173. parser = argparse.ArgumentParser()
  174. parser.add_argument("--test", action="store_true", help="Do a test run instead of the full puzzle")
  175. def main061():
  176. args = parser.parse_args()
  177. if args.test:
  178. input_grid = """....#.....
  179. .........#
  180. ..........
  181. ..#.......
  182. .......#..
  183. ..........
  184. .#..^.....
  185. ........#.
  186. #.........
  187. ......#..."""
  188. grid = [list(l) for l in input_grid.split("\n")]
  189. print("\n".join(["".join(line) for line in grid]))
  190. else:
  191. with open("input06.txt", "r", encoding="utf-8") as f:
  192. grid = [list(l) for l in f.readlines()]
  193. guard = Guard(grid, test_mode=args.test)
  194. logger.info(f"Guard has been created: x:{guard.x}, y:{guard.y}, dir:{guard.dir}, test_mode:{guard.test_mode}, width:{guard.width}, height:{guard.height}.")
  195. guard_position = guard.whereami()
  196. logger.info(f"Starting guard's walk at {guard_position}.")
  197. guard.pathfind()
  198. logger.info(f"The guard visited {len(guard.visited)} unique positions.")
  199. if __name__ == "__main__":
  200. main061()