{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 155,
   "metadata": {},
   "outputs": [],
   "source": [
    "#import lru cache decorator\n",
    "from functools import lru_cache\n",
    "from copy import deepcopy\n",
    "\n",
    "\n",
    "def load_map(file_path='testinput'):\n",
    "    with open(file_path, 'r') as file:\n",
    "        content = file.readlines()\n",
    "    return [list(line.strip()) for line in content]\n",
    "\n",
    "def get_map():\n",
    "    return deepcopy(load_map())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 156,
   "metadata": {},
   "outputs": [],
   "source": [
    "emptymap = load_map()\n",
    "map = load_map()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 157,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Position: (6, 4) facing ^.\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "(6, 4, '^')"
      ]
     },
     "execution_count": 157,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def get_start_position(map):\n",
    "    for x, row in enumerate(map):\n",
    "        for dir in ['^', '<', '>', 'V']:\n",
    "            if dir in row:\n",
    "                y = row.index(dir)\n",
    "                pos = (x, y, dir)\n",
    "                print(f\"Position: ({pos[0]}, {pos[1]}) facing {pos[2]}.\")\n",
    "                return pos\n",
    "\n",
    "    return None\n",
    "\n",
    "\n",
    "get_start_position(map)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 158,
   "metadata": {},
   "outputs": [],
   "source": [
    "def printmap(map):\n",
    "    for row in map:\n",
    "        print(''.join(row))\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 159,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "....#.....\n",
      ".........#\n",
      "..........\n",
      "..#.......\n",
      ".......#..\n",
      "..........\n",
      ".#..^.....\n",
      "........#.\n",
      "#.........\n",
      "......#...\n"
     ]
    }
   ],
   "source": [
    "printmap(load_map())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 160,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(5, 4)"
      ]
     },
     "execution_count": 160,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def calculate_next_position(x, y, dir):\n",
    "    if dir == '^':\n",
    "        return (x - 1, y)\n",
    "    elif dir == '<':\n",
    "        return (x, y - 1)\n",
    "    elif dir == '>':\n",
    "        return (x, y + 1)\n",
    "    elif dir == 'V':\n",
    "        return (x + 1, y)\n",
    "\n",
    "def rotate(dir):\n",
    "    if dir == '^':\n",
    "        return '>'\n",
    "    elif dir == '<':\n",
    "        return '^'\n",
    "    elif dir == '>':\n",
    "        return 'V'\n",
    "    elif dir == 'V':\n",
    "        return '<'\n",
    "\n",
    "def in_bounds(map, x, y):\n",
    "    return 0 <= x < len(map) and 0 <= y < len(map[0])\n",
    "\n",
    "calculate_next_position(6, 4, '^')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 161,
   "metadata": {},
   "outputs": [],
   "source": [
    "class LoopDetected(Exception):\n",
    "    pass\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 162,
   "metadata": {},
   "outputs": [],
   "source": [
    "def move(map, pos=None, visited=None):\n",
    "    # Initialize visited list if not provided\n",
    "    if visited is None:\n",
    "        visited = []\n",
    "    history = deepcopy(visited)\n",
    "    # Get the current position and direction if not provided\n",
    "    if pos is None:\n",
    "        pos = get_start_position(map)\n",
    "    \n",
    "    \n",
    "    if pos:\n",
    "        x, y, dir = pos  \n",
    "\n",
    "        new_x, new_y = calculate_next_position(*pos)\n",
    "\n",
    "        # Handle obstacles by rotating until an open path is found\n",
    "        while in_bounds(map, new_x, new_y) and map[new_x][new_y] == '#':\n",
    "            dir = rotate(dir)  # Rotate direction\n",
    "            new_x, new_y = calculate_next_position(x, y, dir)\n",
    "\n",
    "        # Move to the new position if valid, otherwise return None\n",
    "        if in_bounds(map, new_x, new_y) and map[new_x][new_y] != '#':\n",
    "            pos = (new_x,new_y,dir)\n",
    "            history.append(pos)\n",
    "        else:\n",
    "            pos = None\n",
    "\n",
    "        \n",
    "    # No valid move was possible or guard is out of bounds\n",
    "    return pos, history\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 163,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "....#.....\n",
      ".........#\n",
      "..........\n",
      "..#.......\n",
      ".......#..\n",
      "..........\n",
      ".#..^.....\n",
      "........#.\n",
      "#.........\n",
      "......#...\n"
     ]
    }
   ],
   "source": [
    "printmap(map)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 164,
   "metadata": {},
   "outputs": [],
   "source": [
    "def merge_element(new,old):\n",
    "\n",
    "    mergetable = {\n",
    "    # Straight line merges\n",
    "    ('─', '─'): '─',\n",
    "    ('│', '│'): '│',\n",
    "    ('─', '│'): '┼',\n",
    "    ('│', '─'): '┼',\n",
    "\n",
    "    # Corners with straight lines\n",
    "    ('─', '┌'): '┬',\n",
    "    ('─', '┐'): '┬',\n",
    "    ('─', '└'): '┴',\n",
    "    ('─', '┘'): '┴',\n",
    "    ('│', '┌'): '├',\n",
    "    ('│', '┐'): '┤',\n",
    "    ('│', '└'): '├',\n",
    "    ('│', '┘'): '┤',\n",
    "\n",
    "    # Corners with corners\n",
    "    ('┌', '┐'): '┬',\n",
    "    ('┌', '└'): '├',\n",
    "    ('┌', '┘'): '┼',\n",
    "    ('┐', '└'): '┼',\n",
    "    ('┐', '┘'): '┤',\n",
    "    ('└', '┘'): '┴',\n",
    "\n",
    "    # Straight lines with T-junctions\n",
    "    ('─', '┬'): '┬',\n",
    "    ('─', '┴'): '┴',\n",
    "    ('─', '├'): '┼',\n",
    "    ('─', '┤'): '┼',\n",
    "    ('│', '┬'): '┼',\n",
    "    ('│', '┴'): '┼',\n",
    "    ('│', '├'): '├',\n",
    "    ('│', '┤'): '┤',\n",
    "\n",
    "    # Corners with T-junctions\n",
    "    ('┌', '┬'): '┬',\n",
    "    ('┌', '┴'): '├',\n",
    "    ('┌', '├'): '├',\n",
    "    ('┌', '┤'): '┼',\n",
    "    ('┐', '┬'): '┬',\n",
    "    ('┐', '┴'): '┤',\n",
    "    ('┐', '├'): '┼',\n",
    "    ('┐', '┤'): '┤',\n",
    "    ('└', '┬'): '├',\n",
    "    ('└', '┴'): '┴',\n",
    "    ('└', '├'): '├',\n",
    "    ('└', '┤'): '┼',\n",
    "    ('┘', '┬'): '┤',\n",
    "    ('┘', '┴'): '┴',\n",
    "    ('┘', '├'): '┼',\n",
    "    ('┘', '┤'): '┤',\n",
    "\n",
    "    # T-junctions with T-junctions\n",
    "    ('┬', '┬'): '┬',\n",
    "    ('┬', '┴'): '┼',\n",
    "    ('┬', '├'): '┼',\n",
    "    ('┬', '┤'): '┼',\n",
    "    ('┴', '┴'): '┴',\n",
    "    ('┴', '├'): '┼',\n",
    "    ('┴', '┤'): '┼',\n",
    "    ('├', '├'): '├',\n",
    "    ('├', '┤'): '┼',\n",
    "    ('┤', '┤'): '┤',\n",
    "\n",
    "    # Full crossings\n",
    "    ('─', '┼'): '┼',\n",
    "    ('│', '┼'): '┼',\n",
    "    ('┌', '┼'): '┼',\n",
    "    ('┐', '┼'): '┼',\n",
    "    ('└', '┼'): '┼',\n",
    "    ('┘', '┼'): '┼',\n",
    "    ('┬', '┼'): '┼',\n",
    "    ('┴', '┼'): '┼',\n",
    "    ('├', '┼'): '┼',\n",
    "    ('┤', '┼'): '┼',\n",
    "    ('┼', '┼'): '┼',\n",
    "    }\n",
    "\n",
    "    if old and new:\n",
    "        if (old,new) in mergetable:\n",
    "            return mergetable[(old,new)]\n",
    "        if (new,old) in mergetable:\n",
    "            return mergetable[(new,old)]\n",
    "    if new:\n",
    "        return new\n",
    "    return old\n",
    "\n",
    "def get_draw_element(current_dir, new_dir, existing_symbol=None):\n",
    "    direction_merge = {\n",
    "    # Straight Movements\n",
    "    ('^', '^'): '│',  # Continue moving up\n",
    "    ('V', 'V'): '│',  # Continue moving down\n",
    "    ('<', '<'): '─',  # Continue moving left\n",
    "    ('>', '>'): '─',  # Continue moving right\n",
    "\n",
    "    # Turns from Up ('^')\n",
    "    ('^', '>'): '┌',  # Turn right from up\n",
    "    ('^', '<'): '┐',  # Turn left from up\n",
    "\n",
    "    # Turns from Down ('v')\n",
    "    ('V', '>'): '└',  # Turn right from down\n",
    "    ('V', '<'): '┘',  # Turn left from down\n",
    "\n",
    "    # Turns from Left ('<')\n",
    "    ('<', '^'): '└',  # Turn up from left\n",
    "    ('<', 'V'): '┌',  # Turn down from left\n",
    "\n",
    "    # Turns from Right ('>')\n",
    "    ('>', '^'): '┘',  # Turn up from right\n",
    "    ('>', 'V'): '┐',  # Turn down from right\n",
    "\n",
    "    # Reverse Movements\n",
    "    ('^', 'V'): '│',  # Reverse direction (up to down)\n",
    "    ('V', '^'): '│',  # Reverse direction (down to up)\n",
    "    ('<', '>'): '─',  # Reverse direction (left to right)\n",
    "    ('>', '<'): '─',  # Reverse direction (right to left)\n",
    "    }\n",
    "    return merge_element(direction_merge[(current_dir,new_dir)],existing_symbol)\n",
    "\n",
    "def drawroute(map,visited):\n",
    "    temp_map = deepcopy(map)\n",
    "    for cur,new in zip(visited,visited[1:]):\n",
    "        if cur is None or new is None:\n",
    "            raise ValueError(f\"visited had a None entry! cur:{cur} new:{new}\")\n",
    "        if len(new) <3:\n",
    "            raise ValueError(f\"visited had a malformed entry : cur:{cur}\")\n",
    "        x,y,dir = cur\n",
    "        nx,ny,ndir = new\n",
    "        temp_map[x][y] = get_draw_element(dir,ndir,temp_map[x][y])\n",
    "        \n",
    "    temp_map[nx][ny] = get_draw_element(ndir,ndir)  # Mark start point\n",
    "\n",
    "    printmap(temp_map)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 165,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Position: (6, 4) facing ^.\n",
      "....#.....\n",
      ".........#\n",
      "..........\n",
      "..#.......\n",
      ".......#..\n",
      "..........\n",
      ".#..^.....\n",
      "........#.\n",
      "#.........\n",
      "......#...\n",
      "....#.....\n",
      "....┌───┐#\n",
      "....│...│.\n",
      "..#.│...│.\n",
      "..┌─┼─┐#│.\n",
      "..│.│.│.│.\n",
      ".#└───┼─┘.\n",
      ".┌────┼┐#.\n",
      "#└────┘│..\n",
      "......#│..\n"
     ]
    }
   ],
   "source": [
    "pos,visited = move(map)\n",
    "printmap(map)\n",
    "\n",
    "while pos:\n",
    "    pos,visited = move(map,pos,visited)\n",
    "\n",
    "drawroute(map,visited)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 166,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(41, 44)"
      ]
     },
     "execution_count": 166,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def count_visited(visited):\n",
    "    visited = set([(x,y) for x,y,dir in visited])  # Convert list to set to remove duplicates\n",
    "    return len(visited)\n",
    "\n",
    "count_visited(visited), len(visited)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 99,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[34]"
      ]
     },
     "execution_count": 99,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[idx for idx,(x,y,d) in enumerate(visited) if x == 8 and y ==1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "((8, 2, '<'), (8, 1, '<'), 32, 44)"
      ]
     },
     "execution_count": 100,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\n",
    "test_position = 34\n",
    "guardposition = visited[test_position-1]\n",
    "obsticalposition = visited[test_position]\n",
    "\n",
    "history = visited[:test_position-2]\n",
    "guardposition,obsticalposition, len(history), len(visited)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "....#.....\n",
      ".........#\n",
      "..........\n",
      "..#.......\n",
      ".......#..\n",
      "..........\n",
      ".#..^.....\n",
      "........#.\n",
      "##........\n",
      "......#...\n",
      "....#.....\n",
      "....┌───┐#\n",
      "....│...│.\n",
      "..#.│...│.\n",
      "..┌─┼─┐#│.\n",
      "..│.│.│.│.\n",
      ".#└───┼─┘.\n",
      "......│.#.\n",
      "##..──┘...\n",
      "......#...\n"
     ]
    }
   ],
   "source": [
    "# create copy of map with additional obstacle at pos\n",
    "def add_obstacle(pos):\n",
    "    # create a copy of the original map\n",
    "    new_map = get_map()\n",
    "    # add the obstacle to the new map\n",
    "    x,y,dir = pos\n",
    "    new_map[x][y] = '#'\n",
    "    return new_map\n",
    "\n",
    "testmap = add_obstacle(obsticalposition)\n",
    "printmap(testmap)\n",
    "drawroute(testmap,history)\n",
    "pos = guardposition"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(7, 2, '^')\n"
     ]
    }
   ],
   "source": [
    "newpos,newhistory = move(testmap,guardposition,history)\n",
    "print(newpos)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "....#.....\n",
      "....┌───┐#\n",
      "....│...│.\n",
      "..#.│...│.\n",
      "..┌─┼─┐#│.\n",
      "..│.│.│.│.\n",
      ".#└───┼─┘.\n",
      "......│.#.\n",
      "##..──┘...\n",
      "......#...\n"
     ]
    }
   ],
   "source": [
    "drawroute(testmap,history)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 143,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "....#.....\n",
      "....┌───┐#\n",
      "....│...│.\n",
      "..#.│...│.\n",
      "..┌─┼─┐#│.\n",
      "..│.│.│.│.\n",
      ".#├───┼─┘.\n",
      "..│...│.#.\n",
      "##└─┴─┘...\n",
      "......#...\n"
     ]
    }
   ],
   "source": [
    "pos,history = move(testmap,pos,history)\n",
    "drawroute(testmap,history)\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 168,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Loop detected with obstacle at (4, 4, '^')\n",
      "Loop detected with obstacle at (3, 4, '^')\n",
      "Loop detected with obstacle at (2, 4, '^')\n",
      "Loop detected with obstacle at (1, 4, '^')\n",
      "Loop detected with obstacle at (1, 5, '>')\n",
      "Loop detected with obstacle at (1, 6, '>')\n",
      "Loop detected with obstacle at (1, 7, '>')\n",
      "Loop detected with obstacle at (1, 8, '>')\n",
      "Loop detected with obstacle at (2, 8, 'V')\n",
      "Loop detected with obstacle at (3, 8, 'V')\n",
      "Loop detected with obstacle at (4, 8, 'V')\n",
      "Loop detected with obstacle at (5, 8, 'V')\n",
      "Loop detected with obstacle at (6, 8, 'V')\n",
      "Loop detected with obstacle at (6, 7, '<')\n",
      "Loop detected with obstacle at (6, 6, '<')\n",
      "Loop detected with obstacle at (6, 5, '<')\n",
      "Loop detected with obstacle at (6, 4, '<')\n",
      "Loop detected with obstacle at (6, 3, '<')\n",
      "Loop detected with obstacle at (6, 2, '<')\n",
      "Loop detected with obstacle at (5, 2, '^')\n",
      "Loop detected with obstacle at (4, 2, '^')\n",
      "Loop detected with obstacle at (4, 3, '>')\n",
      "Loop detected with obstacle at (4, 4, '>')\n",
      "Loop detected with obstacle at (4, 5, '>')\n",
      "Loop detected with obstacle at (4, 6, '>')\n",
      "Loop detected with obstacle at (5, 6, 'V')\n",
      "Loop detected with obstacle at (6, 6, 'V')\n",
      "Loop detected with obstacle at (7, 6, 'V')\n",
      "Loop detected with obstacle at (8, 6, 'V')\n",
      "Loop detected with obstacle at (8, 5, '<')\n",
      "Loop detected with obstacle at (8, 4, '<')\n",
      "Loop detected with obstacle at (8, 3, '<')\n",
      "Loop detected with obstacle at (8, 2, '<')\n",
      "Loop detected with obstacle at (8, 1, '<')\n",
      "Loop detected with obstacle at (7, 1, '^')\n",
      "Loop detected with obstacle at (7, 2, '>')\n",
      "Loop detected with obstacle at (7, 3, '>')\n",
      "Loop detected with obstacle at (7, 4, '>')\n",
      "Loop detected with obstacle at (7, 5, '>')\n",
      "Loop detected with obstacle at (7, 6, '>')\n",
      "Loop detected with obstacle at (7, 7, '>')\n",
      "Loop detected with obstacle at (8, 7, 'V')\n",
      "Loop detected with obstacle at (9, 7, 'V')\n",
      "Total loop positions: 43\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[(4, 4, '^'),\n",
       " (3, 4, '^'),\n",
       " (2, 4, '^'),\n",
       " (1, 4, '^'),\n",
       " (1, 5, '>'),\n",
       " (1, 6, '>'),\n",
       " (1, 7, '>'),\n",
       " (1, 8, '>'),\n",
       " (2, 8, 'V'),\n",
       " (3, 8, 'V'),\n",
       " (4, 8, 'V'),\n",
       " (5, 8, 'V'),\n",
       " (6, 8, 'V'),\n",
       " (6, 7, '<'),\n",
       " (6, 6, '<'),\n",
       " (6, 5, '<'),\n",
       " (6, 4, '<'),\n",
       " (6, 3, '<'),\n",
       " (6, 2, '<'),\n",
       " (5, 2, '^'),\n",
       " (4, 2, '^'),\n",
       " (4, 3, '>'),\n",
       " (4, 4, '>'),\n",
       " (4, 5, '>'),\n",
       " (4, 6, '>'),\n",
       " (5, 6, 'V'),\n",
       " (6, 6, 'V'),\n",
       " (7, 6, 'V'),\n",
       " (8, 6, 'V'),\n",
       " (8, 5, '<'),\n",
       " (8, 4, '<'),\n",
       " (8, 3, '<'),\n",
       " (8, 2, '<'),\n",
       " (8, 1, '<'),\n",
       " (7, 1, '^'),\n",
       " (7, 2, '>'),\n",
       " (7, 3, '>'),\n",
       " (7, 4, '>'),\n",
       " (7, 5, '>'),\n",
       " (7, 6, '>'),\n",
       " (7, 7, '>'),\n",
       " (8, 7, 'V'),\n",
       " (9, 7, 'V')]"
      ]
     },
     "execution_count": 168,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def find_loops(map, visited):\n",
    "    loop_positions = []  # Array to store positions leading to loops\n",
    "    \n",
    "    for idx, obstacle_position in enumerate(visited[1:]):  # Skip the starting position\n",
    "        # Determine the position before the obstacle\n",
    "        if idx == 0:\n",
    "            guard_start_position = visited[0]\n",
    "        else:\n",
    "            guard_start_position = visited[idx - 1]\n",
    "        \n",
    "        # Create a new map with the obstacle added\n",
    "        test_map = add_obstacle(obstacle_position)\n",
    "        \n",
    "        # Initialize history from the visited list up to the current index\n",
    "        history = deepcopy(visited[:idx])\n",
    "        \n",
    "        # Start simulation\n",
    "        pos = guard_start_position\n",
    "        while pos and pos not in history:\n",
    "            pos, history = move(test_map, pos, history)\n",
    "            \n",
    "        # Check if a loop was detected\n",
    "        if pos and pos in history:\n",
    "            loop_positions.append(obstacle_position)\n",
    "            print(f\"Loop detected with obstacle at {obstacle_position}\")\n",
    "    \n",
    "    print(f\"Total loop positions: {len(loop_positions)}\")\n",
    "    return loop_positions\n",
    "\n",
    "find_loops(map,visited)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
