summaryrefslogtreecommitdiffstats
path: root/day12/__init__.py
blob: ef03ee0d5d1c0db7bd8fbc01cd6a135257853a73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# -*- coding: utf-8 -*-
import sys
from abc import ABC
from math import inf, sqrt, floor
from typing import List, Tuple, Iterator, Callable, Any

from aoc import BaseAssignment
from aoc.mixins import AStarMixin


class Map:
    def __init__(self, map: List[str]):
        self.map = map

        self.height = len(map)
        self.width = len(map[0])

        try:
            self.start = next(self.find_position("S"))
        except StopIteration:
            self.start = (0, 0)

        try:
            self.end = next(self.find_position("E"))
        except StopIteration:
            self.start = (0, 0)

    def find_position(self, value: str) -> Iterator[Tuple[int, int]]:
        for y in range(self.height):
            for x in range(self.width):
                if self.map[y][x] == value:
                    yield x, y

    def neighbours(self, x: int, y: int) -> Iterator[Tuple[int, int]]:
        for dy in range(max(0, y - 1), min(self.height, y + 2)):
            for dx in range(max(0, x - 1), min(self.width, x + 2)):
                if (
                    (dy == y and dx == x)
                    or (dy == y - 1 and dx == x - 1)
                    or (dy == y - 1 and dx == x + 1)
                    or (dy == y + 1 and dx == x - 1)
                    or (dy == y + 1 and dx == x + 1)
                ):
                    continue
                yield dx, dy

    def elevation(self, x: int, y: int):
        character = self.map[y][x]

        match character:
            case "S":
                elevation = ord("a")
            case "E":
                elevation = ord("z")
            case _:
                elevation = ord(character)

        return elevation - 96


class Assignment(BaseAssignment, AStarMixin[Tuple[int, int]], ABC):
    def distance(self, map: Map, a: Tuple[int, int], b: Tuple[int, int]):
        distance = map.elevation(*b) - map.elevation(*a)

        if distance > 1:
            return inf

        return 1


class AssignmentOne(Assignment):
    example_result = 31

    def heuristic(self, map: Map, a: Tuple[int, int]):
        dx = abs(map.start[0] - a[0])
        dy = abs(map.start[1] - a[1])
        return floor(sqrt(dx**2 + dy**2))

    def run(self, input: Iterator) -> Any:
        map = Map(map=list(input))

        path = self.a_star(
            map.start,
            lambda coordinate, _: coordinate == map.end,
            neighbours=lambda a: map.neighbours(*a),
            distance=lambda a, b: self.distance(map, a, b),
            heuristic=lambda a: self.heuristic(map, a),
        )

        return len(path) - 1


class AssignmentTwo(Assignment):
    example_result = 29

    def run(self, input: Iterator) -> Any:
        map = Map(list(input))

        path = self.a_star(
            map.end,
            lambda coordinate, _: map.elevation(*coordinate) == 1,
            neighbours=lambda a: map.neighbours(*a),
            distance=lambda a, b: self.distance(map, b, a),
        )

        return len(path) - 1