[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.

day04-2.py 10KB

2 weeks ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. import logging
  2. import random
  3. import re
  4. from enum import IntEnum
  5. from sys import stdout
  6. from typing import List, Tuple
  7. # As the search for the Chief continues, a small Elf who lives on the
  8. # station tugs on your shirt; she'd like to know if you could help her
  9. # with her word search (your puzzle input). She only has to find one word:
  10. # XMAS.
  11. # This word search allows words to be horizontal, vertical, diagonal,
  12. # written backwards, or even overlapping other words. It's a little unusual,
  13. # though, as you don't merely need to find one instance of XMAS - you need to
  14. # find all of them.
  15. LOG_FILENAME = "./day04-1.log"
  16. INPUT_FILENAME = "./input04.txt"
  17. logger = logging.Logger(__name__)
  18. formatter = logging.Formatter('[%(asctime)s][%(levelname)s] %(message)s')
  19. sh = logging.StreamHandler(stdout)
  20. sh.setLevel(logging.INFO)
  21. sh.setFormatter(formatter)
  22. fh = logging.FileHandler(LOG_FILENAME, mode="w", encoding="utf-8")
  23. fh.setLevel(logging.DEBUG)
  24. fh.setFormatter(formatter)
  25. logger.addHandler(sh)
  26. logger.addHandler(fh)
  27. class LetterValues(IntEnum):
  28. X = 1
  29. M = 2
  30. A = 4
  31. S = 8
  32. def get_word_value(word):
  33. total = 0
  34. for i, letter in enumerate(word):
  35. total += ((3**i) * LetterValues[letter])
  36. return total
  37. XMAS_VALUE = get_word_value("XMAS")
  38. SAMX_VALUE = get_word_value("SAMX")
  39. TEST_LINES = """MMMSXXMASM
  40. MSAMXMSMSA
  41. AMXSXMAAMM
  42. MSAMASMSMX
  43. XMASAMXAMM
  44. XXAMMXXAMA
  45. SMSMSASXSS
  46. SAXAMASAAA
  47. MAMMMXMMMM
  48. MXMXAXMASX"""
  49. class WSGrid:
  50. def __init__(self, lines: List[List[str]], word: str, print_intermediate_grids: bool=False):
  51. self.grid = [list(line) for line in lines]
  52. self.width = len(self.grid[0])
  53. self.height = len(self.grid)
  54. self.final_grid = [['.' for _ in range(self.width)] for _ in range(self.height)]
  55. self.word = word
  56. self.word_edges = (0-(len(self.word)//2), len(self.word)//2+1)
  57. self.words_found = 0
  58. self.found_positions = []
  59. self.print_intermediate_grids = print_intermediate_grids
  60. self.width
  61. def add_found_position(self: object, position: Tuple[int,int], direction: str) -> None:
  62. self.found_positions.append((position, direction))
  63. logger.info(f"Found a match for {self.word} at {position} going {direction}.")
  64. if self.print_intermediate_grids:
  65. print("\n".join(["".join(line) for line in self.final_grid]))
  66. def check_edges(self: object, x: int, y: int) -> bool:
  67. # x and y can never be along the edge; they have to be at least 1 away
  68. return not (
  69. x > self.width-2
  70. or x < 1
  71. or y < 1
  72. or y > self.height-2
  73. )
  74. def search_east(self: object, position: Tuple[int,int]) -> bool:
  75. x, y = position
  76. if not self.check_edges(x, y):
  77. return False
  78. try:
  79. found_word = "".join([self.grid[y][x+i] for i in range(self.word_edges[0],self.word_edges[1])])
  80. except IndexError:
  81. return False
  82. if found_word == self.word:
  83. self.add_found_position(position, "east")
  84. return found_word == self.word
  85. def search_west(self: object, position: Tuple[int,int]) -> bool:
  86. x, y = position
  87. if not self.check_edges(x, y):
  88. return False
  89. try:
  90. found_word = "".join([self.grid[y][x-i] for i in range(self.word_edges[0],self.word_edges[1])])
  91. except IndexError:
  92. return False
  93. if found_word == self.word:
  94. self.add_found_position(position, "west")
  95. return True
  96. return False
  97. def search_south(self: object, position: Tuple[int,int]) -> bool:
  98. x, y = position
  99. if not self.check_edges(x, y):
  100. return False
  101. try:
  102. found_word = "".join([self.grid[y+i][x] for i in range(self.word_edges[0],self.word_edges[1])])
  103. except IndexError:
  104. return False
  105. if found_word == self.word:
  106. self.add_found_position(position, "south")
  107. return found_word == self.word
  108. def search_north(self: object, position: Tuple[int,int]) -> bool:
  109. x, y = position
  110. if not self.check_edges(x, y):
  111. return False
  112. try:
  113. found_word = "".join([self.grid[y-i][x] for i in range(self.word_edges[0],self.word_edges[1])])
  114. except IndexError:
  115. return False
  116. if found_word == self.word:
  117. self.add_found_position(position, "north")
  118. return found_word == self.word
  119. def search_northwest(self: object, position: Tuple[int,int]) -> bool:
  120. x, y = position
  121. if not self.check_edges(x, y):
  122. return False
  123. try:
  124. found_word = "".join([self.grid[y-i][x-i] for i in range(self.word_edges[0],self.word_edges[1])])
  125. except IndexError:
  126. return False
  127. if found_word == self.word:
  128. self.add_found_position(position, "northwest")
  129. return found_word == self.word
  130. def search_northeast(self: object, position: Tuple[int,int]) -> bool:
  131. x, y = position
  132. if not self.check_edges(x, y):
  133. return False
  134. try:
  135. found_word = "".join([self.grid[y-i][x+i] for i in range(self.word_edges[0],self.word_edges[1])])
  136. except IndexError:
  137. return False
  138. if found_word == self.word:
  139. self.add_found_position(position, "northeast")
  140. return found_word == self.word
  141. def search_southwest(self: object, position: Tuple[int,int]) -> bool:
  142. x, y = position
  143. if not self.check_edges(x, y):
  144. return False
  145. try:
  146. found_word = "".join([self.grid[y+i][x-i] for i in range(self.word_edges[0],self.word_edges[1])])
  147. except IndexError:
  148. return False
  149. if found_word == self.word:
  150. self.add_found_position(position, "southwest")
  151. return found_word == self.word
  152. def search_southeast(self: object, position: Tuple[int,int]) -> bool:
  153. x, y = position
  154. if not self.check_edges(x, y):
  155. return False
  156. try:
  157. found_word = "".join([self.grid[y+i][x+i] for i in range(self.word_edges[0],self.word_edges[1])])
  158. except IndexError:
  159. return False
  160. if found_word == self.word:
  161. self.add_found_position(position, "southeast")
  162. return found_word == self.word
  163. def check_left(self: object, x: int, y: int, letter: str) -> bool:
  164. return (
  165. self.grid[y-1][x-1] == letter
  166. and self.grid[y+1][x-1] == letter
  167. )
  168. def check_top(self: object, x: int, y: int, letter: str) -> bool:
  169. return (
  170. self.grid[y-1][x-1] == letter
  171. and self.grid[y-1][x+1] == letter
  172. )
  173. def check_right(self: object, x: int, y: int, letter: str) -> bool:
  174. return (
  175. self.grid[y-1][x+1] == letter
  176. and self.grid[y+1][x+1] == letter
  177. )
  178. def check_bottom(self: object, x: int, y: int, letter: str) -> bool:
  179. return (
  180. self.grid[y+1][x-1] == letter
  181. and self.grid[y+1][x+1] == letter
  182. )
  183. def set_fg_mas(self: object, x: int, y: int, start_dir: str) -> bool:
  184. def logit():
  185. logger.info(f"Found a match for {self.word} at {(x,y)} starting from the {start_dir}.")
  186. match start_dir:
  187. case "left":
  188. self.final_grid[y][x] = "A"
  189. self.final_grid[y-1][x-1] = "M"
  190. self.final_grid[y+1][x-1] = "M"
  191. self.final_grid[y-1][x+1] = "S"
  192. self.final_grid[y+1][x+1] = "S"
  193. logit()
  194. case "top":
  195. self.final_grid[y][x] = "A"
  196. self.final_grid[y-1][x-1] = "M"
  197. self.final_grid[y-1][x+1] = "M"
  198. self.final_grid[y+1][x-1] = "S"
  199. self.final_grid[y+1][x+1] = "S"
  200. logit()
  201. case "right":
  202. self.final_grid[y][x] = "A"
  203. self.final_grid[y-1][x+1] = "M"
  204. self.final_grid[y+1][x+1] = "M"
  205. self.final_grid[y-1][x-1] = "S"
  206. self.final_grid[y+1][x-1] = "S"
  207. logit()
  208. case "bottom":
  209. self.final_grid[y][x] = "A"
  210. self.final_grid[y+1][x+1] = "M"
  211. self.final_grid[y+1][x-1] = "M"
  212. self.final_grid[y-1][x+1] = "S"
  213. self.final_grid[y-1][x-1] = "S"
  214. logit()
  215. case _:
  216. logger.warning(f"dir was not a valid value: {start_dir}")
  217. if self.print_intermediate_grids:
  218. print("\n".join(["".join(line) for line in self.final_grid]))
  219. def find_word_at_position(self: object, position: Tuple[int,int]) -> int:
  220. x, y = position
  221. # fuck it
  222. if self.grid[y][x] != "A":
  223. return
  224. if (
  225. self.check_left(x, y, "M")
  226. and self.check_right(x, y, "S")
  227. ):
  228. self.words_found += 1
  229. self.set_fg_mas(x, y, "left")
  230. if (
  231. self.check_right(x, y, "M")
  232. and self.check_left(x, y, "S")
  233. ):
  234. self.words_found += 1
  235. self.set_fg_mas(x, y, "right")
  236. if (
  237. self.check_top(x, y, "M")
  238. and self.check_bottom(x, y, "S")
  239. ):
  240. self.words_found += 1
  241. self.set_fg_mas(x, y, "top")
  242. if (
  243. self.check_bottom(x, y, "M")
  244. and self.check_top(x, y, "S")
  245. ):
  246. self.words_found += 1
  247. self.set_fg_mas(x, y, "bottom")
  248. def find_all_words(self: object) -> int:
  249. for y in range(1,len(self.grid)-1):
  250. for x in range(1,len(self.grid[0])-1):
  251. self.find_word_at_position((x,y))
  252. return self.words_found
  253. def main042(run_test=False, print_intermediate_grids=False, print_final_grid=False):
  254. SEARCH_WORD = "MAS"
  255. if run_test:
  256. lines = TEST_LINES.split("\n")
  257. else:
  258. with open("./input04.txt", "r", encoding="utf-8") as f:
  259. lines = [l.strip() for l in f.readlines()]
  260. finder = WSGrid(lines, SEARCH_WORD, print_intermediate_grids)
  261. total_found = finder.find_all_words()
  262. logger.info(f"Found {total_found} instances of {SEARCH_WORD}.")
  263. if print_final_grid:
  264. print("\n".join(["".join(line) for line in finder.final_grid]))
  265. if __name__ == "__main__":
  266. main042(
  267. run_test=False,
  268. print_intermediate_grids=False,
  269. print_final_grid=False
  270. )