Advent of Code 2019 Solution Megathread - Day 11: Space Police
Jon Bristow

Jon Bristow @jbristow

About: 503 - Internal Server Error - TooCoolForSchoolException

Location:
San Diego
Joined:
Mar 12, 2017

Advent of Code 2019 Solution Megathread - Day 11: Space Police

Publish Date: Dec 11 '19
7 11

IntCode's back, but his brain is inside the robot now.

Day 11 - The Problem

Oh no! The police have pulled us over! Apparently, not properly displaying registration markings is a bookable offense. Luckily, we are able to get our registration from the control-room elves. We just need our hull painting robot to take the code and paint our mark. We do have a hull painting robot, don't we?

Part 1 has us building an IntCode interpreter powered robot. We need to send the interpreter input (the color of the hull where it's standing) and use the output to move the robot and paint the hull.

Unfortunately, the simulation goes awry, and the robot paints a deliriously beautiful but meaningless picture. Worse, it's not a registration mark. We re-calibrate our starting color, and successfully. generate our registration code to complete Part 2.

Ongoing Meta

Dev.to List of Leaderboards

If you were part of Ryan Palo's leaderboard last year, you're still a member of that!

If you want me to add your leaderboard code to this page, reply to one of these posts and/or send me a DM containing your code and any theming or notes you’d like me to add. (You can find your private leaderboard code on your "Private Leaderboard" page.)

I'll edit in any leaderboards that people want to post, along with any description for the kinds of people you want to have on it. (My leaderboard is being used as my office's leaderboard.) And if I get something wrong, please call me out or message me and I’ll fix it ASAP.

There's no limit to the number of leaderboards you can join, so there's no problem belonging to a "Beginner" and a language specific one if you want.

Neat Statistics

I'm planning on adding some statistics, but other than "what languages did we see yesterday" does anyone have any ideas?

Languages Seen On Day 10
  • JavaScript x 2
  • Python x 2
  • Kotlin
  • Swift
Completion Stats

By applying a best-fit descending power trend-line to the 1 and 2 star columns on the stats page, my estimates predict:

2 stars on day 25: 6358 people

1 star on day 25: 420 people. Nice

If you're still going, take heart in that you are in a rapidly dwindling pool of people.

Doing other questionable statistics gives me: 415/6248

Comments 11 total

  • Leon Fedotov
    Leon FedotovDec 11, 2019

    not knowing size in advance made it tricky to print the result at first
    github.com/LeonFedotov/advent-of-c...

  • Einar Norðfjörð
    Einar NorðfjörðDec 11, 2019

    Solution in JS

    I used an object to represent the grid to make counting the "painted" block easier.

    Had a bit of trouble realizing that the top left of the grid is 0,0 and not the bottom left :)

    const INSTRUCTIONS = {
      ADD: 1,
      MULT: 2,
      INPUT: 3,
      OUTPUT: 4,
      JUMP_IF_TRUE: 5,
      JUMP_IF_FALSE: 6,
      LESS_THAN: 7,
      EQUALS: 8,
      ADJUST_RELATIVE_BASE: 9,
      HALT: 99
    }
    
    function runProgram(instructions) {
      instructions = instructions.slice()
    
      return function* amplifier() {
        let lastOutput = null
        let relativeBase = 0
        for (let i = 0; i < instructions.length; ++i) {
          const instruction = instructions[i]
          const parsed = String(instruction)
            .padStart(5, '0')
            .split('')
          const getValue = (value, mode = '0') => {
            if (mode === '0') {
              return instructions[value] || 0
            } else if (mode === '1') {
              return value
            } else if (mode === '2') {
              return instructions[relativeBase + value] || 0
            }
          }
          const setValue = (index, value, mode = '0') => {
            if (mode === '0') {
              instructions[index] = value
            } else if (mode === '2') {
              instructions[relativeBase + index] = value
            }
          }
          const opCode = Number(parsed.slice(3).join(''))
          const modes = parsed.slice(0, 3)
          switch (opCode) {
            case INSTRUCTIONS.ADD: {
              const x = getValue(instructions[++i], modes[2])
              const y = getValue(instructions[++i], modes[1])
              setValue(instructions[++i], x + y, modes[0])
              break
            }
            case INSTRUCTIONS.MULT: {
              const x = getValue(instructions[++i], modes[2])
              const y = getValue(instructions[++i], modes[1])
              setValue(instructions[++i], x * y, modes[0])
              break
            }
            case INSTRUCTIONS.INPUT: {
              setValue(instructions[++i], yield { type: 'INPUT' }, modes[2])
              break
            }
            case INSTRUCTIONS.OUTPUT: {
              lastOutput = getValue(instructions[++i], modes[2])
              yield { type: 'OUTPUT', value: lastOutput }
              break
            }
            case INSTRUCTIONS.JUMP_IF_TRUE: {
              const compare = getValue(instructions[++i], modes[2])
              const jumpTo = getValue(instructions[++i], modes[1]) - 1
              if (compare != 0) {
                i = jumpTo
              }
              break
            }
            case INSTRUCTIONS.JUMP_IF_FALSE: {
              const compare = getValue(instructions[++i], modes[2])
              const jumpTo = getValue(instructions[++i], modes[1]) - 1
              if (compare == 0) {
                i = jumpTo
              }
              break
            }
            case INSTRUCTIONS.LESS_THAN: {
              const x = getValue(instructions[++i], modes[2])
              const y = getValue(instructions[++i], modes[1])
              setValue(instructions[++i], x < y ? 1 : 0, modes[0])
              break
            }
            case INSTRUCTIONS.EQUALS: {
              const x = getValue(instructions[++i], modes[2])
              const y = getValue(instructions[++i], modes[1])
              setValue(instructions[++i], x === y ? 1 : 0, modes[0])
              break
            }
            case INSTRUCTIONS.ADJUST_RELATIVE_BASE: {
              const adjustBy = getValue(instructions[++i], modes[2])
              relativeBase += adjustBy
              break
            }
            case INSTRUCTIONS.HALT:
              return lastOutput
          }
        }
      }
    }
    
    const directions = {
      UP: 0,
      RIGHT: 1,
      DOWN: 2,
      LEFT: 3
    }
    const modulo = (x, n) => ((x % n) + n) % n
    
    const runRobotProgram = (robot, initialBlockColor = 0) => {
      const grid = {}
      let location = [0, 0]
      grid[location.join(':')] = initialBlockColor
      let direction = directions.UP
      robot.next()
      while (true) {
        const input = grid[location.join(':')] || 0
        const {
          value: { value: color }
        } = robot.next(input)
        grid[location.join(':')] = color
        const {
          value: { value: turn }
        } = robot.next()
        const { done } = robot.next()
        if (done) return { grid }
        if (turn === 0) {
          direction = modulo(direction - 1, 4)
        } else if (turn === 1) {
          direction = (direction + 1) % 4
        }
    
        if (direction === directions.DOWN) {
          location = [location[0], location[1] + 1]
        } else if (direction === directions.RIGHT) {
          location = [location[0] + 1, location[1]]
        } else if (direction === directions.UP) {
          location = [location[0], location[1] - 1]
        } else if (direction === directions.LEFT) {
          location = [location[0] - 1, location[1]]
        }
      }
    }
    
    function printGrid(grid) {
      const [[minX, maxX], [minY, maxY]] = Object.keys(grid)
        .map(x => x.split(':').map(Number))
        .reduce(
          (state, [x, y]) => {
            if (state[0][0] > x) state[0][0] = x
            if (state[0][1] < x) state[0][1] = x
            if (state[1][0] > y) state[1][0] = y
            if (state[1][1] < y) state[1][1] = y
            return state
          },
          [
            [Infinity, -Infinity],
            [Infinity, -Infinity]
          ]
        )
    
      let gridMap = ''
      for (let y = minY; y <= maxY; ++y) {
        for (let x = minX; x <= maxX; ++x) {
          gridMap += grid[`${x}:${y}`] === 1 ? '#' : ' '
        }
        gridMap += '\n'
      }
      return gridMap
    }
    
    function part1(instructions) {
      const { grid } = runRobotProgram(runProgram(instructions)())
    
      const gridMap = printGrid(grid)
      console.log(gridMap)
      console.log(Object.keys(grid).length)
    }
    
    function part2(instructions) {
      const { grid } = runRobotProgram(runProgram(instructions)(), 1)
    
      const gridMap = printGrid(grid)
      console.log(gridMap)
    }
    
    const input = require('fs')
      .readFileSync(require('path').resolve(__dirname, './input.txt'))
      .toString()
    
    const instructions = input.split(',').map(Number)
    
    part1(instructions)
    part2(instructions)
    
  • Ali Spittel
    Ali SpittelDec 11, 2019

    Python!

    def get_modes(modes):
        return [int(mode) for mode in [modes[2], modes[1], modes[0], modes[3:]]]
    
    
    class Computer:
        def __init__(self, data):
            self.idx = 0
            self.data = data[:] + [0] * 3000
            self.done = False
            self.output = None
            self.inputs = []
            self.relative_base = 0
    
        def get_params(self, mode1, mode2, mode3):
            return self.get_param(mode1, 1), self.get_param(mode2, 2), self.get_param(mode3, 3)
    
        def get_param(self, mode, increment):
            if mode == 0:
                return self.data[self.idx + increment]
            elif mode == 1:
                return self.idx + increment
            else:
                return self.relative_base + self.data[self.idx + increment]
    
        def add(self, mode1, mode2, mode3):
            param1, param2, param3 = self.get_params(mode1, mode2, mode3)
            self.data[param3] = self.data[param1] + self.data[param2]
            self.idx += 4
    
        def multiply(self, mode1, mode2, mode3):
            param1, param2, param3 = self.get_params(mode1, mode2, mode3)
            self.data[param3] = self.data[param1] * self.data[param2]
            self.idx += 4
    
        def take_input(self, mode1):
            param1 = self.get_param(mode1, 1)
            self.data[param1] = self.inputs.pop(0)
            self.idx += 2
    
        def create_output(self, mode1):
            param1 = self.get_param(mode1, 1)
            self.output = self.data[param1]
            self.idx += 2
            return self.output
    
        def less_than(self, mode1, mode2, mode3):
            param1, param2, param3 = self.get_params(mode1, mode2, mode3)
            self.data[param3] = 1 if self.data[param1] < self.data[param2] else 0
            self.idx += 4
    
        def equals(self, mode1, mode2, mode3):
            param1, param2, param3 = self.get_params(mode1, mode2, mode3)
            self.data[param3] = 1 if self.data[param1] == self.data[param2] else 0
            self.idx += 4
    
        def jump_if_true(self, mode1, mode2, mode3):
            param1, param2, param3 = self.get_params(mode1, mode2, mode3)
            self.idx = self.data[param2] if self.data[param1] != 0 else self.idx + 3
    
        def jump_if_false(self, mode1, mode2, mode3):
            param1, param2, param3 = self.get_params(mode1, mode2, mode3)
            self.idx = self.data[param2] if self.data[param1] == 0 else self.idx + 3
    
        def relative_offset(self, mode1):
            param1 = self.get_param(mode1, 1)
            self.relative_base += self.data[param1]
            self.idx += 2
    
        def calculate(self, input_val=None):
            if input_val is not None: self.inputs.append(input_val)
            modes = {
                1: lambda: self.add(mode1, mode2, mode3),
                2: lambda: self.multiply(mode1, mode2, mode3),
                3: lambda: self.take_input(mode1),
                5: lambda: self.jump_if_true(mode1, mode2, mode3),
                6: lambda: self.jump_if_false(mode1, mode2, mode3),
                7: lambda: self.less_than(mode1, mode2, mode3),
                8: lambda: self.equals(mode1, mode2, mode3),
                9: lambda: self.relative_offset(mode1)
            }
            while True:
                mode1, mode2, mode3, opcode = get_modes(f"{self.data[self.idx]:05}")
                if opcode in modes:
                    modes[opcode]()              
                elif opcode == 4:
                    return self.create_output(mode1)                
                elif opcode == 99:
                    self.done = True
                    return self.output
    
    
    class Painting:
        def __init__(self, input_vals, initial_color=0):
            self.computer = Computer(input_vals)
            self.direction = 0
            self.x, self.y = 0, 0
            self.painted = {(self.x, self.y): initial_color}
    
        def paint(self):
            while not self.computer.done:
                starting_color = self.painted[(self.x, self.y)] if (self.x, self.y) in self.painted else 0
                self.painted[(self.x, self.y)] = self.computer.calculate(starting_color)
                self.change_direction(self.computer.calculate())
                self.rotate()
    
        def change_direction(self, rotate_direction):
            if rotate_direction == 0:
                self.direction = (self.direction - 1) % 4
            else:
                self.direction = (self.direction + 1) % 4
    
        def rotate(self):
            if self.direction == 0:
                self.y += 1
            elif self.direction == 1:
                self.x += 1
            elif self.direction == 2:
                self.y -= 1
            elif self.direction == 3:
                self.x -= 1        
    
        def show_painting(self):
            data = [[" " for _ in range(50)] for _ in range(6)]
            for x, y in self.painted.keys():
                color = self.painted[(x, y)]
                data[abs(y)][x] = " " if color == 0 else "|"
            for row in data:
                print(''.join(row))
    
    
    with open("input.txt") as _file:
        for line in _file:
            input_vals = [int(num) for num in line.split(",")]
            painting = Painting(input_vals)
            painting.paint()
            print(f"Part 1: {len(painting.painted.keys())}")
    
            letter_painting = Painting(input_vals, 1)
            letter_painting.paint()
            print(f"Part 2: ")
            letter_painting.show_painting()
    
  • Rizwan
    RizwanDec 11, 2019

    bit late but here you go Swift solution

    class Opcode {
        var numbers: [Int]
        var inputIds: [Int] = []
        var inputId: Int
        var done = false
        var index = 0
        var relativeBase = 0
        var output: Int!
        var outputs: [Int] = []
    
        init(_ input: [Int], _ inputId: Int = 0) {
            self.numbers = input
            numbers.append(contentsOf: Array.init(repeating: 0, count:1000))
            self.inputId = inputId
            self.inputIds.append(inputId)
        }
    
        func run() -> Int {
            guard done == false else { return output }
            while true {
                let digits = numbers[index]
                let opCode = ((digits.digits.dropLast().last ?? 0) * 10) + digits.digits.last!
                var instruction = digits.digits
                //print("Numbers \(numbers)")
                //print("Running opcode \(instruction)")
    
                func getValue(_ aIndex: Int, _ mode: Int = 0) -> Int {
                    switch mode {
                    case 0:
                        return numbers[numbers[index + 1 + aIndex]];
                    case 1:
                        return numbers[index + 1 + aIndex];
                    case 2:
                        return numbers[safe: relativeBase + numbers[index + 1 + aIndex]] ?? 0;
                    default:
                        fatalError("unexpected mode \(mode)")
                    }
                }
    
                func setValue(_ index: Int, _ value: Int, _ mode: Int = 0) {
                    //print("set index is : \(index) value s \(value) mode is \(mode)")
                    switch mode {
                    case 0:
                        numbers[index] = value
                    //print("set index is : \(index)")
                    case 2:
                        //print(relativeBase + index)
                        numbers[relativeBase + index] = value
                    default:
                        break
                    }
                }
    
                func getParam(_ aIndex: Int) -> Int {
                    if let mode = instruction.dropLast(2 + aIndex).last {
                        //print("mode is : \(mode)")
                        //print("value is : \(getValue(aIndex,mode))")
                        return getValue(aIndex,mode)
                    }
                    return getValue(aIndex)
                }
    
                func setParam(_ aIndex: Int, _ value: Int) {
                    //print("set value is : \(value)")
                    //print("setting apram \(aIndex))")
                    if let mode = instruction.dropLast(2 + aIndex).last {
                        setValue(aIndex, value, mode)
                        return
                    }
                    setValue(aIndex, value)
                }
    
                func getMode(_ aIndex: Int) -> Int {
                    //print(instruction)
                    if let mode = instruction.dropLast(2 + aIndex).last {
                        return mode
                    }
                    return 0
                }
    
                switch opCode {
                case 1:
                    let p1 = getParam(0)
                    let p2 = getParam(1)
                    let p3 = numbers[index + 3]
                    setValue(p3, p1 + p2,getMode(2))
                    index += 4
    
                case 2:
                    let p1 = getParam(0)
                    let p2 = getParam(1)
                    let p3 = numbers[index + 3]
                    setValue(p3, p1 * p2,getMode(2))
                    index += 4
    
                case 3:
                    let p1 = numbers[index + 1]
                    setValue(p1, inputIds.last!,getMode(2))
                    //                setParam(p1, inputIds.last!)
                    index += 2
                case 4:
                    let p1 = getParam(0)
                    output = p1
                    //print("output is :\(p1)")
                    index += 2
                    return output
    
                case 5:
                    let p1 = getParam(0)
                    let p2 = getParam(1)
                    if p1 != 0 {
                        index = p2
                    }
                    else {
                        index += 3
                    }
    
                case 6:
                    let p1 = getParam(0)
                    let p2 = getParam(1)
                    if p1 == 0 {
                        index = p2
                    }
                    else {
                        index += 3
                    }
    
                case 7:
                    let p1 = getParam(0)
                    let p2 = getParam(1)
                    let p3 = numbers[index + 3]
                    setValue(p3, p1 < p2 ? 1 : 0,getMode(2))
                    index += 4
    
                case 8:
                    let p1 = getParam(0)
                    let p2 = getParam(1)
                    let p3 = numbers[index + 3]
                    setValue(p3, p1 == p2 ? 1 : 0,getMode(2))
                    index += 4
    
                case 9:
                    let p1 = getParam(0)
                    relativeBase += p1
                    index += 2
                    //            print("New R Base \(relativeBase)")
    
                case 99:
                    done = true
                    return output
    
                default:
                    fatalError("Unknown opcode \(opCode)")
                }
            }
        }
    }
    
    enum Direction: Int {
        case up = 0
        case left = 1
        case right = 2
        case down = 3
    
        mutating func turn(_ direction: Int) {
            if direction == 0 {
                switch self {
                case .up:
                    self = Direction.left
                case .down:
                    self = Direction.right
                case .left:
                    self = Direction.down
                case .right:
                    self = Direction.up
                }
            }
            else if direction == 1 {
                switch self {
                case .up:
                    self = Direction.right
                case .down:
                    self = Direction.left
                case .left:
                    self = Direction.up
                case .right:
                    self = Direction.down
                }
            }
    
        }
    }
    
    struct Point: CustomStringConvertible, Equatable, Hashable {
        var x: Int
        var y: Int
    
        var description: String {
            get {
                return "X: \(self.x) Y: \(self.y)"
            }
        }
    
        mutating func move(in direction: Direction) {
            switch direction {
            case .up: self.y += 1
            case .right: self.x += 1
            case .down: self.y -= 1
            case .left: self.x -= 1
            }
        }
    }
    
    extension Array where Element : Hashable {
        var unique: [Element] {
            return Array(Set(self))
        }
    }
    enum Color: Int {
        case black = 0
        case white = 1
    }
    
    class Robot {
        let startColor: Color
        var paint: [Point: Int] = [:]
    
        init(_ startColor: Color) {
            self.startColor = startColor
        }
    
        @discardableResult func run() -> Int {
            let computer = Opcode(input,startColor.rawValue)
            var direction: Direction = .up
            var currentPosition = Point(x: 0, y: 0)
            paint = [currentPosition:0]
            while !computer.done {
                let color = computer.run()
                let turn = computer.run()
    
                paint[currentPosition] = color
                direction.turn(turn)
                currentPosition.move(in: direction)
                computer.inputIds.append(paint[currentPosition] ?? 0)
            }
            return Set(paint.keys).count
        }
    
        func paintHull() {
            let minX = paint.keys.min{ a,b in a.x < b.x }!.x
            let minY = paint.keys.min{ a,b in a.y < b.y }!.y
    
            let maxX = paint.keys.min{ a,b in a.x > b.x }!.x
            let maxY = paint.keys.min{ a,b in a.y > b.y }!.y
    
            var line: [[String]] = []
            for y in (minY ..< maxY+1) {
                var row: [String] = []
                for x in (minX ..< maxX+1) {
                    let point = Point.init(x: x, y: y)
                    let char = paint[point]
                    row.append(char == Color.white.rawValue ? "#" : " ")
                }
                line.insert(row, at: 0)
            }
            for aLine in line {
                print(aLine, separator:"")
            }
        }
    }
    
    
    
    func partOne() {
        let result = Robot(.black).run()
        print("Part 1 answer is :\(result)")
    }
    
    func partTwo() {
        let robot = Robot(.white)
        robot.run()
        robot.paintHull()
    }
    
    partOne()
    partTwo()
    
  • Massimo Artizzu
    Massimo ArtizzuDec 11, 2019

    I knew we were going to use IntCodes again! 🧐

    But fortunately at this point I'm pretty confident of my routines, so I just copy+pasted most of it. Tweaked a bit since after each output it could or could not receive an input (the scanned color). Also ditched BigInts since they are not necessary.

    So the main running cycle became (JavaScript ahead!):

    for (const instruction of getInstructions()) {
      if (instruction.opcode === 99) {
        return;
      }
      const output = execute(instruction);
      if (typeof output === 'number') {
        const newValue = yield output;
        if (typeof newValue !== 'undefined') {
          stack.push(newValue);
        }
      }
    }
    

    For the rest, I'm keeping track of the coordinates, the direction (an integer between 0 and 3) and the last read panel color. Everything worked at the first try!

    Part One

    // `createProgramInstance` as defined on day 9, with the above corrections
    const paintedPanels = new Map();
    const robot = createProgramInstance(codes, 0);
    let direction = 0;
    let row = 0;
    let column = 0;
    let currentPaint;
    while (true) {
      const { value: paint } = robot.next(currentPaint);
      const coords = column + ',' + row;
      if (paintedPanels.get(coords) === paint) {
        break; // It breaks out after 9515 cycles for me...
      }
      paintedPanels.set(coords, paint);
      const { value: turn } = robot.next();
      direction = (direction + (turn === 0 ? 3 : 1)) % 4;
      switch (direction) {
        case 0: row--; break;
        case 1: column++; break;
        case 2: row++; break;
        case 3: column--; break;
      }
      currentPaint = paintedPanels.get(column + ',' + row) || 0;
    }
    console.log(paintedPanels.size);
    

    Part Two

    // As above, except it starts with 1 instead of 0:
    const robot = createProgramInstance(codes, 1);
    
    // This comes instead of the last console.log
    const paintedPanelsCoords = [ ...paintedPanels.keys() ].map(coords => coords.split(','));
    const paintedColumns = paintedPanelsCoords.map(([ column ]) => column);
    const paintedRows = paintedPanelsCoords.map(([ , row ]) => row);
    const minColumn = Math.min(...paintedColumns);
    const maxColumn = Math.max(...paintedColumns);
    const minRow = Math.min(...paintedRows);
    const maxRow = Math.max(...paintedRows);
    
    // Pretty printing the result
    for (let row = minRow; row <= maxRow; row++) {
      const line = Array.from({ length: maxColumn - minColumn + 1 }, (_, index) => {
        return paintedPanels.get(minColumn + index + ',' + row) ? '#' : '.';
      }).join('');
      console.log(line);
    }
    
  • Neil Gall
    Neil GallDec 11, 2019

    Substantial refactor to my C IntCode machine for this one. I replaced the input/output buffers with function calls which call into the robot code. I also had the robot change the output function pointer on each paint/move, which seemed the simplest way to keep track of what to do on each output.

    For part 2 I calculated the extent of painted panels and just visualised that rectangle. I needed a 200x100 grid to run my input program without running off the edge.

    Despite a few segfaults in the early part of developing this one, I'm finding the C implementations of these problems the easiest, which is quite interesting as although I did my spell writing embedded firmware and Linux device drivers for a living 20 years ago, I haven't really done much C for ages.

    Execution time today is 11ms.

  • Johnny
    JohnnyDec 11, 2019

    Ah shit, here we go again.
    (Dirty) Solution in Clojure:
    github.com/jkoenig134/AdventOfCode...

  • Jon Bristow
    Jon BristowDec 11, 2019

    Phew! More dumb bugs slowed me down! However, once I figured out what was wrong, I was able to simplify my input/output functions a little but overall just use the intcode from the Amplifier day.

    Starting to experiment more with lenses, they're neat, but seem a little unnecessary so far? I don't think I've seen the use-case that really calls for it. Like seeing a food processor next to a nice knife block; why wouldn't I just use a knife to chop things?

    import arrow.core.Either
    import arrow.core.None
    import arrow.core.Some
    import arrow.core.right
    import arrow.optics.optics
    import arrow.syntax.function.andThen
    import intcode.CurrentState
    import intcode.handleCodePoint
    import java.nio.file.Files
    import java.nio.file.Paths
    
    
    object Day11 {
        private const val FILENAME = "src/main/resources/day11.txt"
        val fileData =
            Files.readAllLines(Paths.get(FILENAME)).first()
                .splitToSequence(",")
                .mapIndexed { i, it -> i.toLong() to it.toLong() }
                .toMap()
                .toMutableMap()
    
    }
    
    sealed class Direction {
        object Up : Direction()
        object Down : Direction()
        object Left : Direction()
        object Right : Direction()
    }
    
    val Direction.glyph: String
        get() = when (this) {
            is Direction.Up -> "⬆️"
            is Direction.Down -> "⬇️"
            is Direction.Left -> "⬅️"
            is Direction.Right -> "➡️"
        }
    
    
    sealed class HullColor {
        object Black : HullColor()
        object White : HullColor()
    }
    
    private fun HullColor?.toInput(): Long {
        return when (this) {
            is HullColor.White -> 1L
            is HullColor.Black -> 0L
            else -> 0L
        }
    }
    
    @optics
    data class Robot(
        val location: Point = Point(0, 0),
        val direction: Direction = Direction.Up,
        val state: Either<String, CurrentState> = CurrentState().right(),
        val code: MutableMap<Long, Long>,
        val hull: MutableMap<Point, HullColor> = mutableMapOf(),
        val bodyCounter: Int = 0,
        val brainCounter: Int = 0
    ) {
        companion object
    }
    
    
    fun Robot.turnLeft(): Robot = copy(
        direction = when (direction) {
            Direction.Up -> Direction.Left
            Direction.Down -> Direction.Right
            Direction.Left -> Direction.Down
            Direction.Right -> Direction.Up
        }
    )
    
    fun Robot.turnRight(): Robot = copy(
        direction = when (direction) {
            Direction.Up -> Direction.Right
            Direction.Down -> Direction.Left
            Direction.Left -> Direction.Up
            Direction.Right -> Direction.Down
        }
    )
    
    fun Robot.goForward(): Robot = copy(
        location = when (direction) {
            Direction.Up -> Point.y.set(location, location.y - 1)
            Direction.Down -> Point.y.set(location, location.y + 1)
            Direction.Left -> Point.x.set(location, location.x - 1)
            Direction.Right -> Point.x.set(location, location.x + 1)
        }
    )
    
    fun Robot.paint(instr: Long) = apply {
        hull[location] = when (instr) {
            0L -> HullColor.Black
            1L -> HullColor.White
            else -> throw Error("Illegal paint instruction: $instr")
        }
    }
    
    fun Robot.move(instr: Long): Robot = when (instr) {
        0L -> turnLeft()
        1L -> turnRight()
        else -> throw Error("Bad move instruction: $instr")
    }.goForward()
    
    
    tailrec fun Robot.bodyStep(): Robot = when {
        state is Either.Left<String> || state is Either.Right<CurrentState> && state.b.output.size < 2 -> this
        state is Either.Right<CurrentState> ->
            paint(state.b.output.pop())
                .move(state.b.output.pop())
                .copy(bodyCounter = bodyCounter + 1)
                .bodyStep()
        else -> throw Error("BodyStep Problem: $this")
    }
    
    
    private fun Either<String, CurrentState>.withUpdatedInputs(hullColor: HullColor?) =
        map {
            it.apply {
                if (waitingForInput) {
                    inputs.add(hullColor.toInput())
                }
            }
        }
    
    private fun Either<String, Robot>.fullOutput(): String {
        return fold({
            it
        }, {
            val topLeft = Point(it.hull.keys.map { p -> p.x }.min() ?: 0, it.hull.keys.map { p -> p.y }.min() ?: 0)
            val bottomRight = Point(it.hull.keys.map { p -> p.x }.max() ?: 0, it.hull.keys.map { p -> p.y }.max() ?: 0)
            return (topLeft.y..bottomRight.y).joinToString("\n") { y ->
                (topLeft.x..bottomRight.x).joinToString("") { x ->
                    when {
                        Point(x, y) == it.location -> it.direction.glyph
                        it.hull[Point(x, y)] is HullColor.White -> "◽️"
                        it.hull[Point(x, y)] is HullColor.Black -> "◼️"
                        else -> "◼️"
                    }
                }
            }
    
        })
    }
    
    fun Robot.brainStep(): Either<String, Robot> =
        when (state) {
            is Either.Left<String> -> state
            is Either.Right<CurrentState> -> when (state.b.pointer) {
                is None -> this.right()
                is Some<Long> -> {
                    copy(
                        state = handleCodePoint(
                            code,
                            state.withUpdatedInputs(hull[location])
                        )
                        , brainCounter = brainCounter + 1
                    ).bodyStep().brainStep()
                }
            }
        }
    
    fun main() {
        println("Part 1")
        Robot(code = Day11.fileData).brainStep().let {
            println(it.map(Robot.hull::get andThen Map<Point, HullColor>::size))
            println(it.fullOutput())
        }
    
        println("\nPart 2")
        println(
            Robot(code = Day11.fileData, hull = mutableMapOf(Point(0, 0) to HullColor.White)).brainStep().fullOutput()
        )
    }
    
    
    • Jon Bristow
      Jon BristowDec 11, 2019
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◼️◼️◽️◽️◼️◽️◽️◼️◽️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◼️◽️◼️◽️◽️◽️◼️◼️◽️◽️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◽️◽️◽️◽️◼️◼️◼️◼️◼️◽️◼️◽️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◼️◼️◼️◼️◽️◽️◽️◼️◼️◽️◽️◽️◼️◽️◼️◼️◽️◼️◼️◼️◼️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◽️◽️◽️◽️◽️◼️◼️◼️◼️◽️◽️◼️◼️◽️◽️◽️◼️◽️◼️◽️◼️◼️◼️◼️◽️◽️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◽️◼️◼️◼️◽️◼️◽️◼️◼️◽️◼️◽️◽️◽️◽️◽️◽️◼️◽️◼️◽️◽️◽️◼️◼️◽️◼️◽️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◽️◽️◼️◼️◽️◼️◽️◼️◽️◽️◼️◼️◽️◼️◼️◽️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◽️◽️◼️◼️◽️◽️◼️◼️◽️◼️◽️◼️◽️◽️◼️◽️◽️◼️◼️◽️◼️◼️◽️◼️◼️◽️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◼️◼️◼️◼️◽️◽️◼️◽️◼️◼️◽️◽️◽️◼️◽️◼️◼️◽️◽️◽️◼️◼️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◽️◼️◽️◽️◽️◽️◼️◽️◼️◼️◽️◼️◼️◽️◼️◼️◽️◽️◽️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◼️◼️◼️◽️◼️◽️◽️◼️◼️◽️◼️◼️◽️◽️◽️◼️◽️◼️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◽️◼️◽️◼️◼️◽️◽️◼️◼️◽️◼️◼️◽️◽️◽️◼️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◽️◽️◽️◽️◽️◽️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◽️◽️◼️◽️◼️◽️◽️◼️◼️◼️◼️◼️◽️◽️◽️◼️◼️◽️◼️◼️◼️◽️◼️◼️◽️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◼️◽️◼️◼️◽️◼️◼️◽️◼️◽️◽️◽️◼️◼️◽️◽️◼️◽️◼️◽️◼️◽️◼️◽️◼️◽️◽️◽️◼️◼️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◽️◽️◽️◽️◽️◽️◼️◽️◽️◽️◽️◽️◽️◼️◽️◽️◼️◼️◽️◼️◼️◼️◽️◽️◽️◽️◼️◼️◽️◽️◼️◼️◼️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◼️◽️◼️◽️◽️◽️◼️◼️◼️◼️◽️◼️◼️◽️◼️◼️◼️◽️◼️◼️◼️◼️◼️◽️◽️◼️◽️◼️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◽️◼️◽️◽️◽️◽️◼️◼️◼️◼️◽️◼️◼️◼️◼️◼️◽️◼️◼️◽️◽️◽️◼️◽️◽️◼️◽️◽️◽️◼️◼️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◽️◼️◽️◽️◽️◽️◽️◽️◽️◼️◽️◼️◽️◽️◽️◼️◽️◽️◽️◽️◽️◽️◼️◽️◽️◼️◼️◽️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◽️◼️◽️◼️◽️◼️◽️◼️◼️◽️◼️◽️◽️◼️◽️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◼️◽️◼️◼️◽️◽️◽️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◼️◽️◽️◼️◼️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◼️◼️◼️◼️◽️◽️◼️◽️◼️◽️◽️◽️◼️◽️◼️◼️◽️◼️◼️◼️◼️◽️◽️◼️◽️◽️◽️◼️◼️◼️◼️◽️◽️◼️◼️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◽️◽️◼️◼️◽️◼️◽️◼️◽️◼️◼️◽️◽️◼️◼️◽️◽️◽️◼️◼️◼️◼️◽️◽️◽️◽️◽️◼️◼️◽️◽️◼️◼️◼️◽️◼️◽️◼️◽️◼️◼️◼️◽️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◽️◼️◼️◽️◼️◼️◽️◼️◼️◼️◼️◽️◼️◽️◽️◽️◽️◽️◽️◼️◼️◼️◼️◽️◽️◽️◼️◼️◽️◼️◼️◽️◽️◽️◼️◽️◽️◽️◼️◽️◽️◽️◽️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◽️◼️◼️◽️◽️◽️◼️◽️◼️◽️◽️◼️◼️◼️◽️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◽️◼️◽️◼️◼️◽️◼️◽️◼️◽️◼️◽️◽️◼️◽️◽️◽️◽️◽️◽️◽️◼️◼️◼️◼️◽️◼️◼️◽️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◼️◼️◼️◼️◼️◽️◽️◼️◽️◽️◽️◼️◼️◽️◼️◼️◽️◼️◼️◽️◽️◽️◼️◽️◽️◼️◽️◼️◽️◽️◽️◽️◽️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◽️◽️◽️◼️◼️◽️◼️◽️◼️◼️◼️◼️◽️◼️◼️◽️◽️◽️◽️◼️◼️◽️◽️◼️◽️◽️◽️◼️◽️◽️◽️◽️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◼️◼️◼️◼️◽️◼️◼️◽️◼️◽️◽️◼️◼️◼️◽️◽️◽️◼️◼️◽️◼️◽️◽️◽️◽️◼️◼️◼️◼️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◽️◼️◼️◼️◼️◽️◼️◼️◽️◽️◽️◽️◼️◽️◼️◽️◼️◼️◽️◽️◽️◼️◼️◼️◽️◽️◼️◽️◼️◽️◽️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◼️◽️◽️◼️◽️◼️◽️◼️◽️◽️◽️◽️◼️◼️◼️◼️◼️◽️◼️◼️◼️◼️◽️◽️◼️◼️◽️◽️◼️◼️◼️◽️◽️◼️◽️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◼️◽️◽️◽️◽️◽️◽️◼️◼️◼️◼️◽️◽️◼️◼️◼️◽️◼️◽️◽️◽️◽️◼️◽️◼️◽️◽️◽️◽️◼️◽️◽️◼️◼️◼️◽️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◽️◽️◼️◽️◽️◽️◽️◽️◽️◼️◼️◼️◽️◼️◽️◽️◽️◼️◼️◽️◼️◼️◼️◽️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◽️◼️◽️◼️◽️◼️◽️◽️◼️◽️◽️◽️◼️◽️◽️◽️◽️◼️◼️◽️◽️◼️◽️◼️◽️◼️◼️◽️◼️◽️◽️◽️◼️◼️◽️◽️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◽️◽️◽️◽️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◼️◽️◼️◽️◼️◽️◼️◽️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◽️◽️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◼️◽️◽️◽️◽️◽️◼️◽️◽️◼️◼️◽️◼️◼️◽️◽️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◽️◽️◼️◽️◽️◼️◽️◽️◽️◽️◽️◼️◼️◽️◼️◼️◽️◼️◽️◽️◼️◼️◽️◽️◽️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◼️◼️◼️◽️◽️◼️◽️◽️◽️◼️◼️◼️◼️◽️◼️◽️◽️◼️◼️◽️◽️◼️◽️◼️◼️◽️◽️◼️◼️◽️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◽️◽️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◽️◽️◽️◽️◼️◽️◼️◼️◼️◼️◽️◼️◼️◼️◽️◼️◽️◽️◽️◽️◽️◼️◼️◼️◽️◼️◼️◼️◽️◼️◼️◽️◽️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◽️◽️◼️◼️◽️◽️◼️◽️◼️◼️◽️◼️◼️◼️◽️◼️◼️◽️◼️◽️◽️◼️◽️◽️◼️◽️◽️◽️◽️◼️◽️◽️◼️◽️◼️◽️◽️◽️◼️◽️◼️◼️◽️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◼️◼️◽️◼️◼️◽️◼️◽️◽️◽️◽️◼️◽️◼️◼️◽️◽️◽️◽️◽️◼️◼️◽️◼️◼️◽️◽️◼️◽️◽️◼️◽️◽️◽️◼️◼️◽️◽️◽️◼️◽️◽️◽️◽️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◽️◽️◼️◽️◽️◽️◽️◽️◽️◼️◽️◼️◼️◼️◽️◽️◽️◽️◽️◽️◽️◽️◽️◼️◼️◽️◽️◽️◽️◼️◼️◽️◽️◽️◼️◽️◽️◽️◼️◽️◼️◽️◽️◼️◼️◽️◽️◼️◽️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◽️◽️◼️◼️◽️◽️◽️◼️◽️◽️◽️◼️◽️◼️◽️◼️◼️◼️◼️◽️◽️◼️◼️◽️◽️◼️◽️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◽️◼️◽️◽️◽️◼️◼️◼️◼️◽️◽️◽️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◽️◼️◽️◽️◽️◽️◼️◼️◼️◽️◼️◽️◽️◼️◼️◼️◽️◼️◼️◼️◽️◽️◽️◽️◽️◼️◼️◼️◽️◽️◼️◼️◼️◽️◽️◽️◼️◼️◽️◼️◼️◽️◼️◽️◽️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◼️◽️◼️◽️◼️◼️◼️◽️◽️◽️◼️◽️◽️◽️◼️◽️◼️◽️◽️◼️◽️◽️◽️◼️◼️◽️◼️◼️◽️◽️◽️◼️◽️◼️◼️◼️◽️◽️◼️◽️◼️◽️◼️◽️◼️◼️◼️◽️◼️◽️◼️◽️◼️◽️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◽️◽️◽️◽️◼️◽️◽️◽️◽️◽️◽️◼️◽️◼️◼️◽️◽️◽️◽️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◽️◽️◽️◽️◽️◼️◼️◽️◽️◼️◼️◼️◽️◼️◽️◽️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◼️◼️◽️◽️◽️◼️◽️◼️◼️◽️◽️◼️◼️◽️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◼️◼️◽️◽️◽️◼️◽️◼️◽️◽️◼️◽️◽️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◼️◼️◽️◽️◽️◽️◽️◽️◽️◼️◼️◽️◽️◽️◼️◼️◽️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️⬅️◼️◽️◼️◽️◼️◽️◼️◼️◽️◽️◼️◼️◼️◼️◽️◽️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◽️◽️◽️◽️◼️◼️◽️◽️◽️◼️◽️◽️◽️◽️◼️◽️◼️◼️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◼️◽️◼️◼️◽️◽️◼️◽️◽️◼️◽️◽️◽️◽️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◽️◼️◼️◼️◽️◼️◽️◽️◽️◽️◽️◽️◽️◽️◽️◼️◽️◽️◼️◽️◽️◽️◽️◼️◽️◼️◼️◼️◼️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◼️◽️◼️◽️◼️◼️◽️◼️◼️◼️◽️◼️◼️◽️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◽️◼️◽️◽️◽️◼️◽️◼️◽️◼️◽️◽️◼️◽️◽️◽️◽️◽️◼️◽️◼️◽️◼️◼️◽️◽️◼️◼️◼️◽️◼️◼️◼️◽️◼️◼️◼️◼️◼️◼️◽️◽️◽️◽️◼️◼️◼️◽️◽️◽️◼️◼️◽️◽️◼️◽️◼️◽️◽️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◼️◽️◼️◼️◽️◽️◽️◼️◽️◽️◽️◼️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◽️◽️◽️◽️◽️◽️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◼️◽️◼️◽️◽️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◼️◽️◼️◼️◽️◽️◼️◼️◼️◼️◽️◼️◼️◼️◽️◽️◽️◽️◼️◼️◼️◼️◼️◽️◼️◼️◽️◽️◼️◽️◽️◽️◼️◽️◼️◽️◼️◼️◼️◼️◽️◽️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◽️◼️◼️◼️◼️◼️◽️◽️◼️◽️◽️◼️◽️◽️◽️◼️◼️◼️◽️◼️◼️◼️◼️◽️◽️◽️◼️◼️◼️◼️◽️◼️◽️◽️◽️◽️◼️◼️◼️◼️◽️◼️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◽️◼️◼️◼️◼️◽️◽️◼️◽️◼️◽️◽️◼️◽️◽️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◼️◼️◼️◽️◽️◼️◽️◼️◽️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◽️◽️◼️◽️◽️◼️◼️◼️◽️◽️◼️◼️◽️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◽️◼️◼️◽️◼️◼️◽️◼️◼️◼️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◼️◽️◼️◽️◼️◼️◼️◽️◼️◽️◽️◼️◼️◼️◽️◽️◼️◽️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◽️◼️◽️◼️◼️◼️◼️◽️◽️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◽️◽️◼️◽️◽️◼️◽️◼️◼️◼️◼️◼️◽️◼️◼️◼️◼️◽️◼️◼️◽️◼️◼️◽️◽️◽️◼️◽️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◽️◼️◽️◽️◼️◽️◽️◼️◽️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◽️◽️◼️◽️◼️◼️◽️◽️◼️◼️◽️◼️◽️◼️◼️◽️◼️◼️◽️◽️◼️◽️◽️◼️◽️◼️◽️◽️◼️◼️◽️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◽️◼️◼️◽️◼️◽️◽️◼️◼️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◽️◽️◼️◼️◽️◽️◼️◽️◽️◼️◼️◼️◼️◽️◼️◽️◼️◼️◽️◽️◽️◽️◼️◽️◽️◽️◽️◼️◽️◼️◽️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◽️◼️◼️◼️◽️◽️◽️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◼️◽️◽️◼️◽️◽️◽️◼️◽️◼️◼️◽️◼️◽️◽️◽️◼️◽️◼️◼️◼️◼️◽️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◽️◼️◼️◼️◽️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◽️◼️◼️◽️◽️◼️◼️◽️◽️◼️◼️◽️◼️◼️◽️◽️◽️◼️◼️◽️◽️◼️◼️◼️◽️◽️◽️◽️◽️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◽️◼️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◽️◽️◽️◽️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◽️◼️◼️◽️◽️◽️◽️◼️◽️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◽️◽️◽️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◽️◼️◽️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◽️◼️◼️◼️◽️◼️◼️◽️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◼️◼️◼️◼️◽️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◽️◼️◼️◽️◼️◽️◼️◼️◽️◽️◼️◽️◼️◼️◽️◽️◼️◼️◼️◽️◼️◽️◼️◼️◽️◼️◽️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◽️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◽️◼️◼️◼️◽️◼️◽️◽️◼️◼️◼️◽️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◽️◼️◼️◽️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◽️◼️◼️◽️◽️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◽️◼️◽️◽️◽️◼️◽️◽️◽️◽️◽️◽️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◽️◼️◼️◽️◼️◽️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◽️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◽️◼️◼️◽️◽️◽️◽️◼️◽️◼️◽️◼️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◼️◽️◽️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◽️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◽️◼️◼️◽️◽️◼️◼️◽️◼️◽️◽️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◽️◽️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◽️◽️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◼️◼️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◽️◼️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      ◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◽️◽️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️
      
  • Mustafa Haddara
    Mustafa HaddaraDec 13, 2019

    I ranked on the global leaderboard! Never thought I'd make it. Came in at 87th for both stars and 83rd for the first star.

    Ruby solution for part 2

    class IntCode
        def initialize(instructions)
            @instructions = instructions
            @ptr = 0
            @in_buff = []
            @out_buff = []
            @rel_base = 0
            @blocked = false
        end
    
        def send_input(thing)
            @in_buff.push(thing)
        end
    
        def read_output()
            return @out_buff.shift()
        end
    
        def inspect()
            puts @instructions.to_s
            puts @out_buff.to_s
        end
    
        def isBlocked()
            return @blocked
        end
    
        def read_mem(idx)
            if idx < 0
                puts "wat: negative index"
            end
            val = @instructions[idx]
            if val == nil
                @instructions[idx] = 0
                return 0
            else
                return val
            end
        end
    
        def write_mem(idx)
        end
    
        def run()
            @blocked = false
            while @ptr < @instructions.length do
                # puts @ptr, @instructions.to_s
                base_op = read_mem(@ptr)
                # puts base_op
                op = base_op % 100  # get last 2 digits
                flags = base_op / 100 # top n digits
                arg1 = read_value(@instructions, @ptr+1, flags%10)
                if op == 1
                    flags = flags / 10
                    arg2 = read_value(@instructions, @ptr+2, flags%10)
                    flags = flags / 10
                    arg3 = read_value(@instructions, @ptr+3, flags%10)
                    r = read_mem(arg1) + read_mem(arg2)
                    @instructions[arg3] = r
                    @ptr += 4
                elsif op == 2
                    flags = flags / 10
                    arg2 = read_value(@instructions, @ptr+2, flags%10)
                    flags = flags / 10
                    arg3 = read_value(@instructions, @ptr+3, flags%10)
                    r = read_mem(arg1) * read_mem(arg2)
                    @instructions[arg3] = r
                    @ptr += 4
                elsif op == 3
                    if @in_buff.empty?
                        # puts "waiting for input"
                        @blocked = true
                        return false
                    end
                    input = @in_buff.shift()
                    # puts "got input #{input}, putting in location #{arg1}"
                    @instructions[arg1] = input.to_i
                    @ptr += 2
                elsif op == 4
                    output = read_mem(arg1)
                    @out_buff.push(output)
                    # puts "output: #{output}"
                    @ptr += 2
                elsif op == 5
                    flags = flags / 10
                    arg2 = read_value(@instructions, @ptr+2, flags%10)
                    if read_mem(arg1) != 0
                        @ptr = read_mem(arg2)
                    else
                        @ptr += 3
                    end
                elsif op == 6
                    flags = flags / 10
                    arg2 = read_value(@instructions, @ptr+2, flags%10)
                    if read_mem(arg1) == 0
                        @ptr = read_mem(arg2)
                    else
                        @ptr += 3
                    end
                elsif op == 7
                    flags = flags / 10
                    arg2 = read_value(@instructions, @ptr+2, flags%10)
                    flags = flags / 10
                    arg3 = read_value(@instructions, @ptr+3, flags%10)
                    if read_mem(arg1) < read_mem(arg2)
                        @instructions[arg3] = 1
                    else
                        @instructions[arg3] = 0
                    end
                    @ptr += 4
                elsif op == 8
                    flags = flags / 10
                    arg2 = read_value(@instructions, @ptr+2, flags%10)
                    flags = flags / 10
                    arg3 = read_value(@instructions, @ptr+3, flags%10)
                    if read_mem(arg1) == read_mem(arg2)
                        @instructions[arg3] = 1
                    else
                        @instructions[arg3] = 0
                    end
                    @ptr += 4
                elsif op == 9
                    @rel_base += read_mem(arg1)
                    # puts "updated relative base to #{@rel_base}"
                    @ptr += 2
                elsif op == 99
                    # puts "halting!"
                    break
                else
                    puts "wat"
                    return @instructions
                end
            end
            return @instructions
        end
    
        def read_value(instructions, arg, flag)
            if flag == 1
                return arg
            elsif flag == 2
                v = read_mem(arg) + @rel_base
                return v
            else
                return read_mem(arg)
            end
        end
    end
    
    def paint(vm)
        grid = []
    
        grid[100] = []
        grid[100][100] = '#'
    
        # assume the bot starts at 100,100
        x = 100
        y = 100
    
        orientation = 'U'
    
        total_painted = 0
    
        while true
            if grid[y] == nil
                grid[y] = []
            end
    
            current_cell = grid[y][x]
            if current_cell == nil || current_cell == '.'
                current_cell = 0
            else
                current_cell = 1
            end
            vm.send_input(current_cell)
    
            vm.run()
    
            paint_color = vm.read_output()
    
            if grid[y][x] == nil
                total_painted += 1
            end
    
            if paint_color == 0
                grid[y][x] = '.'
            elsif paint_color == 1
                grid[y][x] = '#'
            else
                puts "wat"
            end
    
            new_dir = vm.read_output()
            orientation = rotate(orientation, new_dir)
    
            pair = move(x,y, orientation)
            x = pair[0]
            y = pair[1]
    
            if !vm.isBlocked
                break
            end
        end
        return grid
    end
    
    def rotate(orientation, rotate)
        if rotate == 0
            # left aka ccw
            if orientation == 'U'
                return 'L'
            elsif orientation == 'L'
                return 'D'
            elsif orientation == 'D'
                return 'R'
            elsif orientation == 'R'
                return 'U'
            end
        else
            # right aka cw
            if orientation == 'U'
                return 'R'
            elsif orientation == 'R'
                return 'D'
            elsif orientation == 'D'
                return 'L'
            elsif orientation == 'L'
                return 'U'
            end
        end
    end
    
    def move(x,y, dir)
        if dir == 'U'
            return [x, y-1]
        elsif dir == 'R'
            return [x+1, y]
        elsif dir == 'D'
            return [x, y+1]
        elsif dir == 'L'
            return [x-1, y]
        end
    end
    
    if __FILE__ == $0
        instructions = []
        File.open(ARGV[0], "r") do |file_handle|
            file_handle.each_line do |line|
                instructions = line.split(",").map { |n| n.to_i } 
                break
            end
        end
    
    
        vm = IntCode.new(instructions)
        g = paint(vm)
        g.each do |row|
            if row == nil
                next
            end
            # puts row.to_s
            row.each do |n|
                if n == nil || n == '.'
                    print ' '
                else
                    print n
                end
            end
            puts ""
        end
    end
    
  • Ryan Palo
    Ryan PaloDec 16, 2019

    Not too bad. Follow the steps: Input -> Process -> Output -> Repeat and keep track of the touched squares in a hashmap. Could have gone full-hog and made structs out of points and headings, but this makes sorting easier. Here's my Rust solution:

    /// Day 11: Space Police
    /// 
    /// Repaint your ship to comply with the fuzz
    
    use std::collections::HashMap;
    use std::fs;
    
    use crate::intcode::IntcodeInterpreter;
    
    #[derive(Eq, PartialEq, PartialOrd, Ord, Debug)]
    enum Color {
        White,
        Black,
    }
    
    /// A Position is a ordered pair (x, y) in 2D space.
    type Position = (isize, isize);
    
    /// A Heading is a direction vector (x, y) with y positive being up.
    type Heading = (isize, isize);
    
    /// Parses the input.  Expects a single line of integers separated by
    /// commas
    fn parse_input() -> Vec<isize> {
        let text: String = fs::read_to_string("data/day11.txt").unwrap();
        let cleaned = text.trim();
        cleaned.split(",").map( |c| c.parse().unwrap()).collect()
    }
    
    /// Turns right or left, returns the new heading
    /// For direction, 1 means turn right, 0 means turn left
    /// No need for a "clever" turning function I think.
    fn turn(heading: Heading, direction: isize) -> Heading {
        if direction == 1 {
            match heading {
                (0, 1) => (1, 0),
                (1, 0) => (0, -1),
                (0, -1) => (-1, 0),
                (-1, 0) => (0, 1),
                _ => panic!("Unrecognized heading!")
            }
        } else {
            match heading {
                (0, 1) => (-1, 0),
                (-1, 0) => (0, -1),
                (0, -1) => (1, 0),
                (1, 0) => (0, 1),
                _ => panic!("Unrecognized heading!")
            }
        }
    }
    
    /// Paints a spacecraft hull using a painter bot that reads the current
    /// tile color to decide what to do next
    fn paint_hull(bot: &mut IntcodeInterpreter) -> HashMap<Position, Color> {
        let mut tiles: HashMap<Position, Color> = HashMap::new();
        let mut position = (0, 0);
        tiles.insert(position, Color::White);
        let mut heading = (0, 1);
    
        loop {
            // Read the tile color and pass it to the bot
            let input = match tiles.entry(position).or_insert(Color::Black) {
                Color::Black => 0,
                Color::White => 1,
            };
            bot.push_input(input);
    
            // Process input
            bot.run();
    
            // Read the color to paint and direction to turn from the bot
            let output_color = match bot.shift_output() {
                1 => Color::White,
                0 => Color::Black,
                _ => panic!("Bad bot color output")
            };
            let output_turn = bot.shift_output();
    
            tiles.insert(position, output_color);
            heading = turn(heading, output_turn);
            position = (position.0 + heading.0, position.1 + heading.1);
    
            // Return if the bot has halted completely
            if bot.current_instruction() == 99 {
                break;
            }
        }
    
        tiles
    }
    
    /// I cheated and printed the tiles map out to see the ranges of x and y
    /// Prints black tiles as plain terminal and white as #
    fn display_tiles(mut tiles: HashMap<Position, Color>) {
        for row in (-5..=0).rev() {
            for col in 0..42 {
                let out = match tiles.entry((col, row)).or_insert(Color::Black) {
                    Color::White => "#",
                    Color::Black => " ",
                };
                print!("{}", out);
            }
            println!("");
        }
    }
    
    pub fn run() {
        let mut bot = IntcodeInterpreter::new(parse_input());
        let tiles = paint_hull(&mut bot);
        println!("Touched {} squares", tiles.len());
        display_tiles(tiles);
    }
    
Add comment