diff --git a/.gitignore b/.gitignore index 485dee6..b55f499 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.idea +**/inputs +*.txt \ No newline at end of file diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..8d8b1ba --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +python 3.12.6 diff --git a/2024/day06.py b/2024/day06.py new file mode 100644 index 0000000..f261773 --- /dev/null +++ b/2024/day06.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +import collections + +class Direction: + def __init__(self, pos_change_x, pos_change_y): + self.pos_change_x = pos_change_x + self.pos_change_y = pos_change_y + + def __eq__(self, other): + if not isinstance(other, Direction): + return False + + return self.pos_change_x == other.pos_change_x and self.pos_change_y == other.pos_change_y + + def __hash__(self): + return hash((self.pos_change_x, self.pos_change_y)) + + def __repr__(self): + return f"Direction({self.pos_change_x}, {self.pos_change_y})" + +class Point: + def __init__(self, x, y): + self.x = x + self.y = y + + def __eq__(self, other): + if not isinstance(other, Point): + return False + + return self.x == other.x and self.y == other.y + + def __hash__(self): + return hash((self.x, self.y)) + + def __repr__(self): + return f"Point({self.x}, {self.y})" + +class Guard: + def __init__(self, pos, direction): + self.pos = pos + self.direction = direction + + def __repr__(self): + return f"Guard({self.pos}, {self.direction})" + + def move(self, obstacles): + if self.direction == UP: + if (Point(self.pos.x, self.pos.y - 1) in obstacles): + self.direction = RIGHT + return Point(self.pos.x, self.pos.y - 1) + else: + self.pos.y -= 1 + elif self.direction == DOWN: + if (Point(self.pos.x, self.pos.y + 1) in obstacles): + self.direction = LEFT + return Point(self.pos.x, self.pos.y + 1) + else: + self.pos.y += 1 + elif self.direction == LEFT: + if (Point(self.pos.x - 1, self.pos.y) in obstacles): + self.direction = UP + return Point(self.pos.x - 1, self.pos.y) + else: + self.pos.x -= 1 + elif self.direction == RIGHT: + if (Point(self.pos.x + 1, self.pos.y) in obstacles): + self.direction = DOWN + return Point(self.pos.x + 1, self.pos.y) + else: + self.pos.x += 1 + +with open("./2024/inputs/day06.txt") as f: + m = [l.strip() for l in f.readlines()] + +UP = Direction(0, -1) +DOWN = Direction(0, 1) +LEFT = Direction(-1, 0) +RIGHT = Direction(1, 0) + +guard_starting_pos = None +guard_starting_direction = None +guard = None +obstacle_pos = set() +visited_pos = set() + +for y in range(len(m)): + for x in range(len(m[y])): + if m[y][x] in ["^", "v", "<", ">"]: + guard_pos = Point(x, y) + + if m[y][x] == "^": + guard_direction = UP + elif m[y][x] == "v": + guard_direction = DOWN + elif m[y][x] == "<": + guard_direction = LEFT + elif m[y][x] == ">": + guard_direction = RIGHT + + guard = Guard(guard_pos, guard_direction) + + elif m[y][x] == "#": + obstacle_pos.add(Point(x, y)) + +guard_starting_pos = Point(guard.pos.x, guard.pos.y) +guard_starting_direction = Direction(guard.direction.pos_change_x, guard.direction.pos_change_y) + +# if the guard is still in the map, move him and register his position in the +# visited_pos set if he hasn't been there before. to move him, check if he has +# an obstacle right in front of him: if he does he moves 90 degrees to the right, +# if he doesn't, he takes a step forward. +while guard.pos.x >= 0 and guard.pos.x < len(m[0]) and guard.pos.y >= 0 and guard.pos.y < len(m): + if guard.pos not in visited_pos: + visited_pos.add(guard.pos) + + guard.move(obstacle_pos) + +print(len(visited_pos)) + +# now we have to find the number of positions where an obstacle can be added so that +# the guard is forced in a loop and never exits the map. +# we can do this by saving a set of the last 4 obstacles hit, and checking if a new set of 4 obstacles is +# the same 4 obstacles. if it is, we have found a loop. +loop_obstacle_pos = set() + +for y in range(len(m)): + for x in range(len(m[y])): + if Point(x, y) in obstacle_pos or Point(x, y) == guard_starting_pos: + continue + + last_visited_obstacles = collections.deque() + shadow_visited_obstacles = collections.deque() + possible_obstacle_pos = Point(x, y) + new_obstacles = obstacle_pos.copy() + new_obstacles.add(possible_obstacle_pos) + guard = Guard(Point(guard_starting_pos.x, guard_starting_pos.y), guard_starting_direction) + + # now solve the map checking for the last 4 obstacles, if a loop is found add the new possible obstacle to the set + while guard.pos.x >= 0 and guard.pos.x < len(m[0]) and guard.pos.y >= 0 and guard.pos.y < len(m): + # save the last 4 obstacles + current_obstacle = guard.move(new_obstacles) + if current_obstacle is not None: + if current_obstacle in last_visited_obstacles: + if current_obstacle in shadow_visited_obstacles: + # guard is in a loop! + loop_obstacle_pos.add(possible_obstacle_pos) + break + else: + shadow_visited_obstacles.append(current_obstacle) + else: + last_visited_obstacles.append(current_obstacle) + shadow_visited_obstacles = collections.deque() + +print(len(loop_obstacle_pos)) \ No newline at end of file diff --git a/viz2024-06-01.js b/viz2024-06-01.js new file mode 100644 index 0000000..d7a3c8d --- /dev/null +++ b/viz2024-06-01.js @@ -0,0 +1,108 @@ +let inputData = ``; +let m = []; +let guard, UP, DOWN, LEFT, RIGHT; +let obstacles = new Set(); +let visitedPos = new Set(); +let cellSize = 800 / 130; + +function setup() { + createCanvas(800, 800); + frameRate(120); + + // Parse input data + m = inputData.split('\n').map(line => line.trim()); + + // Define directions + UP = { x: 0, y: -1 }; + DOWN = { x: 0, y: 1 }; + LEFT = { x: -1, y: 0 }; + RIGHT = { x: 1, y: 0 }; + + // Initialize guard and obstacles + for (let y = 0; y < m.length; y++) { + for (let x = 0; x < m[y].length; x++) { + let cell = m[y][x]; + if (["^", "v", "<", ">"].includes(cell)) { + guard = { + pos: { x, y }, + direction: + cell === "^" ? UP : + cell === "v" ? DOWN : + cell === "<" ? LEFT : + RIGHT + }; + } + + if (cell === "#") { + obstacles.add(`${x},${y}`); + } + } + } +} + +function draw() { + background(220); + + moveGuard(); + + drawGrid(); +} + +function moveGuard() { + if (!visitedPos.has(`${guard.pos.x},${guard.pos.y}`)) { + visitedPos.add(`${guard.pos.x},${guard.pos.y}`); + } + + let nextPos = { + x: guard.pos.x + guard.direction.x, + y: guard.pos.y + guard.direction.y + }; + + if (isObstacle(nextPos)) { + // Change direction if obstacle encountered + if (guard.direction === UP) guard.direction = RIGHT; + else if (guard.direction === RIGHT) guard.direction = DOWN; + else if (guard.direction === DOWN) guard.direction = LEFT; + else if (guard.direction === LEFT) guard.direction = UP; + } else { + // Move guard + guard.pos = nextPos; + } +} + +function isObstacle(pos) { + return obstacles.has(`${pos.x},${pos.y}`) || + pos.x < 0 || pos.x >= m[0].length || + pos.y < 0 || pos.y >= m.length; +} + +function drawGrid() { + // Draw visited positions + fill(255, 255, 0, 100); // Transparent yellow + visitedPos.forEach(pos => { + let [x, y] = pos.split(',').map(Number); + rect(x * cellSize, y * cellSize, cellSize, cellSize); + }); + + // Draw obstacles + fill(255, 0, 0, 100); // Transparent red + obstacles.forEach(pos => { + let [x, y] = pos.split(',').map(Number); + rect(x * cellSize, y * cellSize, cellSize, cellSize); + }); + + // Draw guard + fill(0, 0, 255); // Blue + rect(guard.pos.x * cellSize, guard.pos.y * cellSize, cellSize, cellSize); + + // Draw guard direction + fill(0); + textAlign(CENTER, CENTER); + text( + guard.direction === UP ? "^" : + guard.direction === DOWN ? "v" : + guard.direction === LEFT ? "<" : ">", + guard.pos.x * cellSize + cellSize/2, + guard.pos.y * cellSize + cellSize/2 + ); +} \ No newline at end of file diff --git a/viz2024-06-01.py b/viz2024-06-01.py new file mode 100644 index 0000000..b5d7d86 --- /dev/null +++ b/viz2024-06-01.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +import time + +class Direction: + def __init__(self, pos_change_x, pos_change_y): + self.pos_change_x = pos_change_x + self.pos_change_y = pos_change_y + + def __eq__(self, other): + if not isinstance(other, Direction): + return False + + return self.pos_change_x == other.pos_change_x and self.pos_change_y == other.pos_change_y + + def __hash__(self): + return hash((self.pos_change_x, self.pos_change_y)) + + def __repr__(self): + return f"Direction({self.pos_change_x}, {self.pos_change_y})" + +class Point: + def __init__(self, x, y): + self.x = x + self.y = y + + def __eq__(self, other): + if not isinstance(other, Point): + return False + + return self.x == other.x and self.y == other.y + + def __hash__(self): + return hash((self.x, self.y)) + + def __repr__(self): + return f"Point({self.x}, {self.y})" + +class Guard: + def __init__(self, pos, direction): + self.pos = pos + self.direction = direction + + def __repr__(self): + return f"Guard({self.pos}, {self.direction})" + + def move(self, obstacles): + if self.direction == UP: + if (Point(self.pos.x, self.pos.y - 1) in obstacles): + self.direction = RIGHT + return Point(self.pos.x, self.pos.y - 1) + else: + self.pos.y -= 1 + elif self.direction == DOWN: + if (Point(self.pos.x, self.pos.y + 1) in obstacles): + self.direction = LEFT + return Point(self.pos.x, self.pos.y + 1) + else: + self.pos.y += 1 + elif self.direction == LEFT: + if (Point(self.pos.x - 1, self.pos.y) in obstacles): + self.direction = UP + return Point(self.pos.x - 1, self.pos.y) + else: + self.pos.x -= 1 + elif self.direction == RIGHT: + if (Point(self.pos.x + 1, self.pos.y) in obstacles): + self.direction = DOWN + return Point(self.pos.x + 1, self.pos.y) + else: + self.pos.x += 1 + +def draw(m, visited_pos, obstacle_pos, guard): + print("\033c") # clear screen + + # draw the guard in blue, the obstacles in red, and the path in yellow + for y in range(len(m)): + for x in range(len(m[y])): + if guard.pos.x == x and guard.pos.y == y: + print("\033[34m", end="") + if guard.direction == UP: + print("^", end="") + elif guard.direction == DOWN: + print("v", end="") + elif guard.direction == LEFT: + print("<", end="") + elif guard.direction == RIGHT: + print(">", end="") + print("\033[0m", end="") + else: + if Point(x, y) in visited_pos: + print("\033[33m", end="") + print(".", end="") + print("\033[0m", end="") + elif Point(x, y) in obstacle_pos: + print("\033[31m", end="") + print("#", end="") + print("\033[0m", end="") + else: + print(" ", end="") + + print() + +with open("./2024/inputs/day06.txt") as f: + m = [l.strip() for l in f.readlines()] + +UP = Direction(0, -1) +DOWN = Direction(0, 1) +LEFT = Direction(-1, 0) +RIGHT = Direction(1, 0) + +guard = None +obstacle_pos = set() +visited_pos = set() + +for y in range(len(m)): + for x in range(len(m[y])): + if m[y][x] in ["^", "v", "<", ">"]: + guard_pos = Point(x, y) + + if m[y][x] == "^": + guard_direction = UP + elif m[y][x] == "v": + guard_direction = DOWN + elif m[y][x] == "<": + guard_direction = LEFT + elif m[y][x] == ">": + guard_direction = RIGHT + + guard = Guard(guard_pos, guard_direction) + + elif m[y][x] == "#": + obstacle_pos.add(Point(x, y)) + +while guard.pos.x >= 0 and guard.pos.x < len(m[0]) and guard.pos.y >= 0 and guard.pos.y < len(m): + if guard.pos not in visited_pos: + visited_pos.add(guard.pos) + + guard.move(obstacle_pos) + draw(m, visited_pos, obstacle_pos, guard) + time.sleep(0.1) +