File size: 4,158 Bytes
a4da721
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def parse_input(filename):
    with open(filename, 'r') as f:
        lines = f.read().strip().split('\n')
    
    # Find empty line that separates map and moves
    split_idx = lines.index('')
    map_lines = lines[:split_idx]
    moves = ''.join(lines[split_idx+1:]).strip()
    
    return map_lines, moves

def find_robot_and_boxes(grid):
    robot = None
    boxes = set()
    for y in range(len(grid)):
        for x in range(len(grid[y])):
            if grid[y][x] == '@':
                robot = (x, y)
            elif grid[y][x] == 'O':
                boxes.add((x, y))
    return robot, boxes

def find_robot_and_boxes_wide(grid):
    robot = None
    boxes = set()
    for y in range(len(grid)):
        for x in range(len(grid[y])-1):
            if grid[y][x:x+2] == '[]':
                boxes.add((x, y))
            elif grid[y][x] == '@':
                robot = (x, y)
    return robot, boxes

def move_direction(direction):
    if direction == '^': return (0, -1)
    if direction == 'v': return (0, 1)
    if direction == '<': return (-1, 0)
    if direction == '>': return (1, 0)
    return (0, 0)

def is_wall(grid, x, y):
    return grid[y][x] == '#'

def simulate_move(grid, robot, boxes, dx, dy):
    new_x, new_y = robot[0] + dx, robot[1] + dy
    
    # Check if moving into wall
    if is_wall(grid, new_x, new_y):
        return robot, boxes
    
    # Check if moving into box
    if (new_x, new_y) in boxes:
        box_new_x, box_new_y = new_x + dx, new_y + dy
        # Check if box would move into wall or another box
        if is_wall(grid, box_new_x, box_new_y) or (box_new_x, box_new_y) in boxes:
            return robot, boxes
        # Move box
        boxes.remove((new_x, new_y))
        boxes.add((box_new_x, box_new_y))
    
    return (new_x, new_y), boxes

def simulate_move_wide(grid, robot, boxes, dx, dy):
    new_x, new_y = robot[0] + dx, robot[1] + dy
    
    # Check if moving into wall
    if is_wall(grid, new_x, new_y):
        return robot, boxes
    
    # Check if moving into box
    box_pos = None
    for box in boxes:
        if new_x in (box[0], box[0] + 1) and new_y == box[1]:
            box_pos = box
            break
    
    if box_pos:
        box_new_x, box_new_y = box_pos[0] + dx, box_pos[1] + dy
        # Check if box would move into wall or another box
        if (is_wall(grid, box_new_x, box_new_y) or 
            is_wall(grid, box_new_x + 1, box_new_y) or
            any(b[0] in (box_new_x, box_new_x + 1) and b[1] == box_new_y for b in boxes if b != box_pos)):
            return robot, boxes
        # Move box
        boxes.remove(box_pos)
        boxes.add((box_new_x, box_new_y))
    
    return (new_x, new_y), boxes

def calculate_gps_sum(boxes, height):
    return sum((box[1] + 1) * 100 + (box[0] + 1) for box in boxes)

def calculate_gps_sum_wide(boxes, height):
    return sum((box[1] + 1) * 100 + (box[0] + 1) for box in boxes)

def scale_map(map_lines):
    scaled = []
    for line in map_lines:
        new_line = ''
        for c in line:
            if c == '#':
                new_line += '##'
            elif c == 'O':
                new_line += '[]'
            elif c == '.':
                new_line += '..'
            elif c == '@':
                new_line += '@.'
        scaled.append(new_line)
    return scaled

def solve_part1(map_lines, moves):
    grid = map_lines
    robot, boxes = find_robot_and_boxes(grid)
    
    for move in moves:
        dx, dy = move_direction(move)
        robot, boxes = simulate_move(grid, robot, boxes, dx, dy)
    
    return str(calculate_gps_sum(boxes, len(grid)))

def solve_part2(map_lines, moves):
    grid = scale_map(map_lines)
    robot, boxes = find_robot_and_boxes_wide(grid)
    
    for move in moves:
        dx, dy = move_direction(move)
        robot, boxes = simulate_move_wide(grid, robot, boxes, dx, dy)
    
    return str(calculate_gps_sum_wide(boxes, len(grid)))

def main():
    map_lines, moves = parse_input('./input.txt')
    
    # Part 1
    print(solve_part1(map_lines, moves))
    
    # Part 2
    print(solve_part2(map_lines, moves))

if __name__ == "__main__":
    main()