{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Day 11 - Langton's Ant\n", "\n", "- https://adventofcode.com/2019/day/11\n", "\n", "Today we use our Intcode computer to direct a painting robot, using rules that echo [Langton's Ant](https://en.wikipedia.org/wiki/Langton%27s_ant), a 2D cellular automaton. Celular automatons are another of AoC's recurring themes. However, this specific ant doesn't quite have the same simple rules Langton gave his ant..\n", "\n", "I've opted for implementing the robot as a Python generator function, having the Intcode CPU's print opcode send its output to the generator using it's [`generator.send()` method](https://docs.python.org/3/reference/expressions.html#generator.send). Input is then a simple deque (add robot observations to one end, the Intcode input opcode takes values from the other).\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from __future__ import annotations\n", "\n", "from collections import deque\n", "from enum import Enum\n", "from typing import (\n", " TYPE_CHECKING,\n", " ContextManager,\n", " Deque,\n", " Dict,\n", " Generator,\n", " List,\n", " Literal,\n", " NamedTuple,\n", " Optional,\n", " Tuple,\n", " Union,\n", " cast,\n", ")\n", "\n", "from intcode import CPU, Instruction, InstructionSet, base_opcodes\n", "\n", "TURN = Literal[0, 1]\n", "COLOUR = Literal[0, 1]\n", "\n", "\n", "class Pos(NamedTuple):\n", " x: int = 0\n", " y: int = 0\n", "\n", " def move(self, dx: int, dy: int) -> Pos:\n", " return self._replace(x=self.x + dx, y=self.y + dy)\n", "\n", "\n", "Hullmap = Dict[Pos, COLOUR]\n", "\n", "\n", "class Direction(Enum):\n", " # dx, dy, turn l, turn r\n", " up = 0, -1, \"left\", \"right\"\n", " left = -1, 0, \"down\", \"up\"\n", " down = 0, 1, \"right\", \"left\"\n", " right = 1, 0, \"up\", \"down\"\n", "\n", " if TYPE_CHECKING:\n", " dx: int\n", " dy: int\n", " _turns: Tuple[str, str]\n", "\n", " def __new__(cls, dx: int, dy: int, turn_left: str, turn_right: str) -> Direction:\n", " self = object.__new__(cls)\n", " self._value_ = (dx, dy)\n", " self.dx, self.dy = dx, dy\n", " self._turns = (turn_left, turn_right)\n", " return self\n", "\n", " def turn(self, direction: TURN) -> Direction:\n", " return type(self)[self._turns[direction]]\n", "\n", " def __call__(self, pos: Pos) -> Pos:\n", " \"\"\"Move forward one step\"\"\"\n", " return pos.move(self.dx, self.dy)\n", "\n", "\n", "PaintRunner = Generator[None, Union[TURN, COLOUR], None]\n", "\n", "\n", "class HullPaintingRobot(ContextManager[PaintRunner]):\n", " painted: Hullmap\n", " _runner: Optional[PaintRunner] = None\n", "\n", " def __init__(self, output: Deque[COLOUR]) -> None:\n", " self.painted = {}\n", " self._output = output\n", "\n", " def powerdown(self) -> None:\n", " if self._runner is not None:\n", " self._runner.close()\n", " self._runner = None\n", "\n", " def __enter__(self) -> PaintRunner:\n", " if self._runner is None:\n", " self._runner = self.run()\n", " # prime the runner, outputting its first observation\n", " # and waiting for input.\n", " next(self._runner)\n", " return self._runner\n", "\n", " def __exit__(self, *exc) -> None:\n", " self.powerdown()\n", "\n", " def run(self) -> Generator[None, Union[TURN, COLOUR], None]:\n", " pos: Pos = Pos()\n", " direction: Direction = Direction.up\n", " hull = self.painted\n", "\n", " while True:\n", " self._output.append(hull.get(pos, 0))\n", " self.painted[pos] = yield None\n", " direction = direction.turn((yield None))\n", " pos = direction(pos)\n", "\n", "\n", "def testrun() -> None:\n", " output: Deque[COLOUR] = deque()\n", " robot = HullPaintingRobot(output)\n", " with robot as run:\n", " # and so any input instructions at this point should be provided 0\n", " assert output[0] == 0\n", "\n", " # Suppose the robot eventually outputs 1 (paint white) and then 0 (turn left)\n", " run.send(cast(COLOUR, 1))\n", " run.send(cast(TURN, 0))\n", " assert robot.painted == {Pos(0, 0): 1}\n", " # Input instructions should still be provided 0\n", " assert output[-1] == 0\n", " assert len(output) == 2\n", "\n", " # Next, the robot might output 0 (paint black) and then 0 (turn left):\n", " run.send(cast(COLOUR, 0))\n", " run.send(cast(TURN, 0))\n", " assert robot.painted == {Pos(0, 0): 1, Pos(-1, 0): 0}\n", "\n", " # After more outputs (1,0, 1,0):\n", " for c, t in ((1, 0), (1, 0)):\n", " run.send(cast(COLOUR, c))\n", " run.send(cast(TURN, t))\n", " assert robot.painted == {\n", " Pos(0, 0): 1,\n", " Pos(-1, 0): 0,\n", " Pos(-1, 1): 1,\n", " Pos(0, 1): 1,\n", " }\n", " # ... because it is now on a white panel, input instructions should be provided 1\n", " assert output[-1] == 1\n", "\n", " # After several more outputs (0,1, 1,0, 1,0)\n", " for c, t in ((0, 1), (1, 0), (1, 0)):\n", " run.send(cast(COLOUR, c))\n", " run.send(cast(TURN, t))\n", " assert robot.painted == {\n", " Pos(0, 0): 0,\n", " Pos(-1, 0): 0,\n", " Pos(-1, 1): 1,\n", " Pos(0, 1): 1,\n", " Pos(1, 0): 1,\n", " Pos(1, -1): 1,\n", " }\n", "\n", "\n", "testrun()\n", "\n", "\n", "def execute_robot(memory: List[int], hull: Optional[Hullmap] = None) -> Hullmap:\n", " outputs: Deque[COLOUR] = deque()\n", " robot = HullPaintingRobot(outputs)\n", " if hull is not None:\n", " robot.painted = hull\n", " with robot as runner:\n", " opcodes: InstructionSet = {\n", " **base_opcodes,\n", " 3: Instruction(outputs.popleft, output=True),\n", " 4: Instruction(runner.send, 1),\n", " }\n", " CPU(opcodes).reset(memory).execute()\n", " return robot.painted" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import aocd\n", "\n", "data = aocd.get_data(day=11, year=2019)\n", "memory = list(map(int, data.split(\",\")))" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Part 1: 1771\n" ] } ], "source": [ "part1_hull = execute_robot(memory)\n", "print(\"Part 1:\", len(part1_hull))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Part 2\n", "\n", "Part 2 simply changes the first position to be white instead of black, so we simply add `Pos(0, 0): 1` to the initial mapping of our hull.\n", "\n", "To output the result, I'm generating a PIL image.\n" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Part 2:\n" ] }, { "data": { "image/jpeg": "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", "image/png": "iVBORw0KGgoAAAANSUhEUgAAAcIAAABQAQAAAACiukgUAAAA50lEQVR4Ae2WUQ6CMAyGW2fiI9zAo4yjs6N4A/dowlL/DphiYkCND2T9H7q2GyT9aDqITEbgZwLNQOSvRCLZiMgY9ngzck6IcER6guexC091yPYbY0+uUdspISfMZ3RTYOZHidw9fPWednda57KeDZHVuQbpiAM6aaIejIi8JNgGqSL4qQRumFrM2BYmb5yaCCXmTjF4yXNI2youhhG3up3V3OYpVROhufrP1hoJhXbJCH9Br/IX7bCsGgnNtW9bayFUWiLxeGV5LNOoiSeh0LoRGOOSQ14vudBpqhZCVqd+bZMR+CeBOx0WO/lbVu9PAAAAAElFTkSuQmCC", "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from PIL import Image\n", "\n", "\n", "def show_hull(hull: Hullmap, scale: int = 10, border: bool = True) -> Image:\n", " minx, maxx = min(p.x for p in hull), max(p.x for p in hull)\n", " miny, maxy = min(p.y for p in hull), max(p.y for p in hull)\n", " W, H = maxx - minx + 1, maxy - miny + 1\n", " img = Image.new(\"1\", (W, H), None)\n", " for pos, colour in hull.items():\n", " img.putpixel(pos.move(-minx, -miny), colour)\n", " if border:\n", " with_border = Image.new(\"1\", (W + 2, H + 2))\n", " with_border.paste(img, (1, 1))\n", " img = with_border\n", " if scale != 1:\n", " img = img.resize((img.width * scale, img.height * scale))\n", " return img\n", "\n", "\n", "part2_hull = execute_robot(memory, {Pos(0, 0): 1})\n", "print(\"Part 2:\")\n", "show_hull(part2_hull)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Animation\n", "\n", "The painting process begs to be animated.\n" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/var/folders/zr/sp474f_d38xfvml_n2y_8tfr0000gn/T/ipykernel_84374/1377379208.py:37: MatplotlibDeprecationWarning: The get_cmap function was deprecated in Matplotlib 3.7 and will be removed two minor releases later. Use ``matplotlib.colormaps[name]`` or ``matplotlib.colormaps.get_cmap(obj)`` instead.\n", " image = ax.imshow(grid, vmin=0, vmax=1, cmap=get_cmap(\"binary\").reversed())\n" ] }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%matplotlib inline\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "from matplotlib import animation, rc\n", "from matplotlib.cm import get_cmap\n", "from matplotlib.figure import Figure\n", "\n", "rc(\"animation\", html=\"html5\")\n", "\n", "\n", "def animate_robot(\n", " memory: List[int],\n", " start: COLOUR = 1,\n", " size: int = 12,\n", " delay: int = 60,\n", ") -> animation.FuncAnimation:\n", " frames: List[Optional[Tuple[Pos, COLOUR]]] = []\n", "\n", " class RecordingDict(Hullmap):\n", " def __setitem__(self, pos: Pos, colour: COLOUR) -> None:\n", " frames.append((pos, colour))\n", " super().__setitem__(pos, colour)\n", "\n", " hull = execute_robot(memory, RecordingDict({Pos(0, 0): start}))\n", "\n", " frames += [None] * 64 # full fade for remainder\n", "\n", " minx, maxx = min(p.x for p in hull), max(p.x for p in hull)\n", " miny, maxy = min(p.y for p in hull), max(p.y for p in hull)\n", " W, H = maxx - minx + 1, maxy - miny + 1\n", "\n", " fig, ax = plt.subplots(figsize=(size, size * (H / W)))\n", " fig.subplots_adjust(left=0, bottom=0, right=1, top=1)\n", " ax.set_axis_off()\n", "\n", " grid = np.zeros((H + 2, W + 2), dtype=np.int8)\n", " image = ax.imshow(grid, vmin=0, vmax=1, cmap=get_cmap(\"binary\").reversed())\n", "\n", " def render(move: Optional[Tuple[Pos, COLOUR]]) -> Tuple[Figure]:\n", " if move is not None:\n", " a = image.get_array()\n", " pos, colour = move\n", " a[pos.y + 1 - miny, pos.x + 1 - minx] = colour\n", " return (image,)\n", "\n", " anim = animation.FuncAnimation(\n", " fig, render, frames, interval=delay, blit=True, repeat_delay=1000\n", " )\n", " plt.close(fig)\n", " return anim\n", "\n", "\n", "animate_robot(memory)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Part 1 chaotic path\n", "\n", "For completion's sake, here are the image and animation produced for part 1, which is quite ant-like in the chaotic lines it created.\n", "\n", "(Rendering the animation takes a while, this is mostly ffmpeg's fault, running in the background to produce the MP4).\n" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "image/jpeg": "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", "image/png": "iVBORw0KGgoAAAANSUhEUgAAAU8AAAFKAQAAAAC1e+DsAAAFRklEQVR4Ae1aQXKrOBBthVTBrOAG+CIxyZFm+avssSdO1V/OfWYzJv8icIKPd1Bl3PNaCnxhk2SUKhXGNVoY0cjNe2qp1S1B9H+ZaQ/ER6KsCngc/t24eEx6ZU2/F8TpdgwoZFeG9R2URjzAemgy3lWtYq5G/jRoOvLcEk3e9N4Co6sPKmClfp6LcT851q8BCE/xPalArNUmt0OreaqorJcxsxrtl1HhCP1rMKwFK8yrVcon2tyAJ7Tn1pLCPPqbW1VHFtu+OidrWbSau1ObHX77+VfRMxlU5kTLAs55RRTu421qCa3qTGlhbiHAuI0ow7IGFuEN1wRPaAmt6pys9Qtr2jyCQ6Sql9LiYlV/NbWE49XJm3awxEZL2hSkqk50fp0c6xcAHLZUv4JI2CbgNlq+oHVUz1DoV2u8XoQnahfNE6vF8MXdnV8A3Vs+uDoA6LWsqF4SpfCEveis4qB18qYWdIS6RxrPSXSrybG6A8jg240nfLWIDqvuWof/H7/zo1W/K5XxR3yKd1zf0iCUAKOoFYOWqm7BE/bWWqWaG0zGhRZe/PgZLn60GvCwUcDhnoKCMr6F4EnTQvIoNsK0kuD9FjbUhBZstKSVrjGy4/eKn+HiT2ubIdUKCoy+RsHLa4Ij3PwBGHnZmMgBAGEyHdOQq0CcINzhmD4jc9A6eVPScTv2m7ayeGm/cRsOXkeCJLQoht1OmGowmSSUZ2VyEzgB+FGtyjorjxTRIcwxEr9lkcIeaHTGama7ug8JPDonVBonmKf3JW6/FxdhlFNnnffJu/d+tJq5heCdZdRhbuXMEkZtL2N5PwD8aBVaRBh62MvFPnWBhRkTLAetWc8tGRwbsdFRXIb2G7sCxtsaM8rjrvjpVz9agV7HTViLGTYCNylmLYOooyRXPwD8aBUKKCbVwtAzRW8/Sfj7dq8vfgD40spqvdAnP/GzFTdhawNjEt5RhqMpvgB0+j+9OgFQfE+ICb+pZg233ukOivifZEVNUHWSWQ1C3QNltKQX4j3Ojg2Jh6TNsBHaZsjB+uLUWf2/Pqv41NrcHXYyjyxr5Yc/DttSrROkLab4BPD2io8vTgDyRO/ONErdxWtlFP+o2kWEeCMlVfbnQk5aPwZoPfWoNVUqedHbn2/v4wTxxolWJeO4q8PgEUD3io+vDgBEkZwYy86nHNvpAjLMITM+qtHHDUbqoHXypoI4YOEhhyVItWRDQ590SagheUpfJsfqAKADDVpvJ3YwFLjBgnvJvDLWt9LMQevkTQ0t8eM6g5R490g6J8HAZESHGKLgtp0ZLXPSf4jiitRj/PtCu75omYCGKhXVT5VUdZncBI4AEPpl1Z8q3FU4tnvGF0IIerGMieenV8w8/Ehx1Kr/8/mPP62tohxfpdUkR5KsOMftsVSPmFtmA9HsIvoD8Dl53cIBgNGo5xGqGyTGFfYJsRGFLzRwC7shEYPfKOZlLaGFU4U9HIXZA4X/Q+ZVyYcnqME77iQRk8zLobMmbyq0YBRZt8Rk2AOFZWS1SuVcSGpiwVnS0gcKq9T4dm0eGE/y5E0hJivMp2v7yU3gAECsBcvID6KlnY4EJWRCGimrFRhhOOoVbH60hJrmBjIKPgJzCwOT4Tx08PS2gePQWZM31Yz6H6xRhFGnnYcMve4B/EY9K5cx7FesYERIjJEdL+AETQgC0aFR8UwHYWcZuV58fSdB/rAH7OYX9cmbXiASgXHw548mx+oA4By7uZeTLjiP7eCpg9brbBo+g0/cKmTHVrlOrBbA/1QVVz9oeBu09JJl87oNWjaj2dX/BaRAONATmU0XAAAAAElFTkSuQmCC", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stderr", "output_type": "stream", "text": [ "/var/folders/zr/sp474f_d38xfvml_n2y_8tfr0000gn/T/ipykernel_84374/1377379208.py:37: MatplotlibDeprecationWarning: The get_cmap function was deprecated in Matplotlib 3.7 and will be removed two minor releases later. Use ``matplotlib.colormaps[name]`` or ``matplotlib.colormaps.get_cmap(obj)`` instead.\n", " image = ax.imshow(grid, vmin=0, vmax=1, cmap=get_cmap(\"binary\").reversed())\n" ] }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "display(show_hull(part1_hull, scale=5))\n", "animate_robot(memory, 0, size=5, delay=6)" ] } ], "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.0" } }, "nbformat": 4, "nbformat_minor": 2 }