123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- import logging
- import random
- import re
-
- from enum import IntEnum
- from sys import stdout
- from typing import List, Tuple
-
- # As the search for the Chief continues, a small Elf who lives on the
- # station tugs on your shirt; she'd like to know if you could help her
- # with her word search (your puzzle input). She only has to find one word:
- # XMAS.
-
- # This word search allows words to be horizontal, vertical, diagonal,
- # written backwards, or even overlapping other words. It's a little unusual,
- # though, as you don't merely need to find one instance of XMAS - you need to
- # find all of them.
-
- LOG_FILENAME = "./day04-1.log"
- INPUT_FILENAME = "./input04.txt"
-
- logger = logging.Logger(__name__)
- formatter = logging.Formatter('[%(asctime)s][%(levelname)s] %(message)s')
- sh = logging.StreamHandler(stdout)
- sh.setLevel(logging.INFO)
- sh.setFormatter(formatter)
- fh = logging.FileHandler(LOG_FILENAME, mode="w", encoding="utf-8")
- fh.setLevel(logging.DEBUG)
- fh.setFormatter(formatter)
- logger.addHandler(sh)
- logger.addHandler(fh)
-
- class LetterValues(IntEnum):
- X = 1
- M = 2
- A = 4
- S = 8
-
- def get_word_value(word):
- total = 0
- for i, letter in enumerate(word):
- total += ((3**i) * LetterValues[letter])
- return total
-
- XMAS_VALUE = get_word_value("XMAS")
- SAMX_VALUE = get_word_value("SAMX")
-
- TEST_LINES = """MMMSXXMASM
- MSAMXMSMSA
- AMXSXMAAMM
- MSAMASMSMX
- XMASAMXAMM
- XXAMMXXAMA
- SMSMSASXSS
- SAXAMASAAA
- MAMMMXMMMM
- MXMXAXMASX"""
-
- class WSGrid:
- def __init__(self, lines: List[List[str]], word: str, print_intermediate_grids: bool=False):
- self.grid = [list(line) for line in lines]
- self.width = len(self.grid[0])
- self.height = len(self.grid)
- self.final_grid = [['.' for _ in range(self.width)] for _ in range(self.height)]
- self.word = word
- self.word_edges = (0-(len(self.word)//2), len(self.word)//2+1)
- self.words_found = 0
- self.found_positions = []
- self.print_intermediate_grids = print_intermediate_grids
- self.width
-
- def add_found_position(self: object, position: Tuple[int,int], direction: str) -> None:
- self.found_positions.append((position, direction))
- logger.info(f"Found a match for {self.word} at {position} going {direction}.")
- if self.print_intermediate_grids:
- print("\n".join(["".join(line) for line in self.final_grid]))
-
- def check_edges(self: object, x: int, y: int) -> bool:
- # x and y can never be along the edge; they have to be at least 1 away
- return not (
- x > self.width-2
- or x < 1
- or y < 1
- or y > self.height-2
- )
-
- def search_east(self: object, position: Tuple[int,int]) -> bool:
- x, y = position
- if not self.check_edges(x, y):
- return False
- try:
- found_word = "".join([self.grid[y][x+i] for i in range(self.word_edges[0],self.word_edges[1])])
- except IndexError:
- return False
- if found_word == self.word:
- self.add_found_position(position, "east")
- return found_word == self.word
-
- def search_west(self: object, position: Tuple[int,int]) -> bool:
- x, y = position
- if not self.check_edges(x, y):
- return False
- try:
- found_word = "".join([self.grid[y][x-i] for i in range(self.word_edges[0],self.word_edges[1])])
- except IndexError:
- return False
- if found_word == self.word:
- self.add_found_position(position, "west")
- return True
- return False
-
- def search_south(self: object, position: Tuple[int,int]) -> bool:
- x, y = position
- if not self.check_edges(x, y):
- return False
- try:
- found_word = "".join([self.grid[y+i][x] for i in range(self.word_edges[0],self.word_edges[1])])
- except IndexError:
- return False
- if found_word == self.word:
- self.add_found_position(position, "south")
- return found_word == self.word
-
- def search_north(self: object, position: Tuple[int,int]) -> bool:
- x, y = position
- if not self.check_edges(x, y):
- return False
- try:
- found_word = "".join([self.grid[y-i][x] for i in range(self.word_edges[0],self.word_edges[1])])
- except IndexError:
- return False
- if found_word == self.word:
- self.add_found_position(position, "north")
- return found_word == self.word
-
- def search_northwest(self: object, position: Tuple[int,int]) -> bool:
- x, y = position
- if not self.check_edges(x, y):
- return False
- try:
- found_word = "".join([self.grid[y-i][x-i] for i in range(self.word_edges[0],self.word_edges[1])])
- except IndexError:
- return False
- if found_word == self.word:
- self.add_found_position(position, "northwest")
- return found_word == self.word
-
- def search_northeast(self: object, position: Tuple[int,int]) -> bool:
- x, y = position
- if not self.check_edges(x, y):
- return False
- try:
- found_word = "".join([self.grid[y-i][x+i] for i in range(self.word_edges[0],self.word_edges[1])])
- except IndexError:
- return False
- if found_word == self.word:
- self.add_found_position(position, "northeast")
- return found_word == self.word
-
- def search_southwest(self: object, position: Tuple[int,int]) -> bool:
- x, y = position
- if not self.check_edges(x, y):
- return False
- try:
- found_word = "".join([self.grid[y+i][x-i] for i in range(self.word_edges[0],self.word_edges[1])])
- except IndexError:
- return False
- if found_word == self.word:
- self.add_found_position(position, "southwest")
- return found_word == self.word
-
- def search_southeast(self: object, position: Tuple[int,int]) -> bool:
- x, y = position
- if not self.check_edges(x, y):
- return False
- try:
- found_word = "".join([self.grid[y+i][x+i] for i in range(self.word_edges[0],self.word_edges[1])])
- except IndexError:
- return False
- if found_word == self.word:
- self.add_found_position(position, "southeast")
- return found_word == self.word
-
- def check_left(self: object, x: int, y: int, letter: str) -> bool:
- return (
- self.grid[y-1][x-1] == letter
- and self.grid[y+1][x-1] == letter
- )
-
- def check_top(self: object, x: int, y: int, letter: str) -> bool:
- return (
- self.grid[y-1][x-1] == letter
- and self.grid[y-1][x+1] == letter
- )
-
- def check_right(self: object, x: int, y: int, letter: str) -> bool:
- return (
- self.grid[y-1][x+1] == letter
- and self.grid[y+1][x+1] == letter
- )
-
- def check_bottom(self: object, x: int, y: int, letter: str) -> bool:
- return (
- self.grid[y+1][x-1] == letter
- and self.grid[y+1][x+1] == letter
- )
-
- def set_fg_mas(self: object, x: int, y: int, start_dir: str) -> bool:
- def logit():
- logger.info(f"Found a match for {self.word} at {(x,y)} starting from the {start_dir}.")
- match start_dir:
- case "left":
- self.final_grid[y][x] = "A"
- self.final_grid[y-1][x-1] = "M"
- self.final_grid[y+1][x-1] = "M"
- self.final_grid[y-1][x+1] = "S"
- self.final_grid[y+1][x+1] = "S"
- logit()
- case "top":
- self.final_grid[y][x] = "A"
- self.final_grid[y-1][x-1] = "M"
- self.final_grid[y-1][x+1] = "M"
- self.final_grid[y+1][x-1] = "S"
- self.final_grid[y+1][x+1] = "S"
- logit()
- case "right":
- self.final_grid[y][x] = "A"
- self.final_grid[y-1][x+1] = "M"
- self.final_grid[y+1][x+1] = "M"
- self.final_grid[y-1][x-1] = "S"
- self.final_grid[y+1][x-1] = "S"
- logit()
- case "bottom":
- self.final_grid[y][x] = "A"
- self.final_grid[y+1][x+1] = "M"
- self.final_grid[y+1][x-1] = "M"
- self.final_grid[y-1][x+1] = "S"
- self.final_grid[y-1][x-1] = "S"
- logit()
- case _:
- logger.warning(f"dir was not a valid value: {start_dir}")
-
- if self.print_intermediate_grids:
- print("\n".join(["".join(line) for line in self.final_grid]))
-
- def find_word_at_position(self: object, position: Tuple[int,int]) -> int:
- x, y = position
- # fuck it
- if self.grid[y][x] != "A":
- return
- if (
- self.check_left(x, y, "M")
- and self.check_right(x, y, "S")
- ):
- self.words_found += 1
- self.set_fg_mas(x, y, "left")
- if (
- self.check_right(x, y, "M")
- and self.check_left(x, y, "S")
- ):
- self.words_found += 1
- self.set_fg_mas(x, y, "right")
- if (
- self.check_top(x, y, "M")
- and self.check_bottom(x, y, "S")
- ):
- self.words_found += 1
- self.set_fg_mas(x, y, "top")
- if (
- self.check_bottom(x, y, "M")
- and self.check_top(x, y, "S")
- ):
- self.words_found += 1
- self.set_fg_mas(x, y, "bottom")
-
-
- def find_all_words(self: object) -> int:
- for y in range(1,len(self.grid)-1):
- for x in range(1,len(self.grid[0])-1):
- self.find_word_at_position((x,y))
-
- return self.words_found
-
- def main042(run_test=False, print_intermediate_grids=False, print_final_grid=False):
- SEARCH_WORD = "MAS"
- if run_test:
- lines = TEST_LINES.split("\n")
- else:
- with open("./input04.txt", "r", encoding="utf-8") as f:
- lines = [l.strip() for l in f.readlines()]
- finder = WSGrid(lines, SEARCH_WORD, print_intermediate_grids)
- total_found = finder.find_all_words()
- logger.info(f"Found {total_found} instances of {SEARCH_WORD}.")
- if print_final_grid:
- print("\n".join(["".join(line) for line in finder.final_grid]))
-
- if __name__ == "__main__":
- main042(
- run_test=False,
- print_intermediate_grids=False,
- print_final_grid=False
- )
|