8 votes

Day 13: Care Package

Today's problem description: https://adventofcode.com/2019/day/13


Join the Tildes private leaderboard! You can do that on this page, by entering join code 730956-de85ce0c.

Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python with any of the "short names" listed in this page of supported languages):

<details>
<summary>Part 1</summary>

```python
Your code here.
```

</details>

5 comments

  1. Macil
    Link
    Part 2: I made the game interactive, and used the crossterm Rust library to help render the field and receive keyboard inputs nicely. The game only steps forward when I press left, right, or...

    Part 2: I made the game interactive, and used the crossterm Rust library to help render the field and receive keyboard inputs nicely. The game only steps forward when I press left, right, or spacebar. I wasn't super great at the game and messed up a lot, so I made "z" step the game backwards. I implemented rewind by recording all of my inputs into a list, and when I rewind, I reset the game state to the original state and replay all of my inputs except for the last one. (I could have optimized this by snapshotting the game state every N inputs, and replaying from the most recent snapshot, but my intcode emulator was fast enough to not need that at all for this game.) I then played the game to the end manually (with a lot of rewind usage).

    3 votes
  2. PapaNachos
    (edited )
    Link
    Part 1 seems pretty simple. Part 2 looks like it'll be a fair bit more complex. Fuck. Part 1 - Python I just glued a simple script onto intputer that splits it into chunks and counts the number of...

    Part 1 seems pretty simple. Part 2 looks like it'll be a fair bit more complex. Fuck.

    Part 1 - Python I just glued a simple script onto intputer that splits it into chunks and counts the number of block chunks. It's like 10 lines.
    import math
    from itertools import permutations
    from itertools import cycle
    import itertools
    verbose_mode = False
    super_verbose_mode = False
    automatic_io = True
    
    
    class intputer:
        def __init__(self,initial_memory, name):
            #makes the int-puter
            self.memory = [int(x) for x in initial_memory.split(",")]
            self.memory = self.memory + ([0] * 10000)
            self.input_buffer = []
            self.output_buffer = []
            self.input_index = 0
            self.instruction_pointer = 0
            self.name = name
            self.op_code, self.parameters = self.read_instruction(self.memory[self.instruction_pointer])
            self.relative_base = 0
        def add_input(self,val):
            self.input_buffer.append(val)
        def get_output(self):
            return self.output_buffer[-1]
        def get_output_history(self, index):
            return self.output_buffer[-index]
        def get_name(self):
            return self.name
        def read_instruction(self,instruction):
            #In order to deal with shorter length instructions we're going to 0-pad everything out to len==5
            #and then divide it back up
            if verbose_mode:
                print(f"Reading Instruction: {instruction}")
            instruction_components = [int(d) for d in str(instruction)]
            if len(instruction_components) == 1:
                #Pads opcode with non-functional 0, now len==2
                instruction_components.insert(0,0)
            if len(instruction_components) == 2:
                #Brings parameter_1 from null to 0, now len==3
                instruction_components.insert(0,0)
            if len(instruction_components) == 3:
                #Brings parameter_2 from null to 0, now len==4
                instruction_components.insert(0,0)
            if len(instruction_components) == 4:
                #Brings parameter_3 from null to 0, now len==5
                instruction_components.insert(0,0)
            opcode_portion = instruction_components[-2:]
            parameters_reversed = instruction_components[0:3]
    
    
    
            parameters = [int(parameters_reversed[2]), int(parameters_reversed[1]), int(parameters_reversed[0])]
    
            opcode = int((10 * opcode_portion[0]) + opcode_portion[1])
    
            if verbose_mode:
                print(f"Op Code {opcode}")
                print(f"Parameters {parameters}")
    
            return opcode, parameters
    
        def execute_instruction(self, op_code, parameter_codes):
            address_0 = self.instruction_pointer + 1
            address_1 = self.instruction_pointer + 2
            address_2 = self.instruction_pointer + 3
    
            if super_verbose_mode:
                print(self.memory)
    
            if op_code == 1:
                val = self.read_value(address_0, parameter_codes[0]) + self.read_value(address_1, parameter_codes[1])
                self.write_value(address_2, parameter_codes[2], val)
                self.instruction_pointer = self.instruction_pointer + 4
            elif op_code == 2:
                val = self.read_value(address_0, parameter_codes[0]) * self.read_value(address_1, parameter_codes[1])
                self.write_value(address_2, parameter_codes[2], val)
                self.instruction_pointer = self.instruction_pointer + 4
            elif op_code == 3:
                if automatic_io == True:
                    if self.input_index >= len(self.input_buffer):
                        #We have less input than we need, wait
                        return self.instruction_pointer, 0
                    val = self.input_buffer[self.input_index]
                    self.input_index = self.input_index + 1
                else:
                    val = input("Input Requested: ")
                self.write_value(address_0, parameter_codes[0], val)
                self.instruction_pointer = self.instruction_pointer + 2
            elif op_code == 4:
                if automatic_io == True:
                    self.output_buffer.append(self.read_value(address_0, parameter_codes[0]))
                else:
                    print(self.read_value(address_0, parameter_codes[0]))
                self.instruction_pointer = self.instruction_pointer + 2
            elif op_code == 5:
                val = self.read_value(address_0, parameter_codes[0])
                if val != 0:
                    self.instruction_pointer = self.read_value(address_1, parameter_codes[1])
                else:
                    self.instruction_pointer = self.instruction_pointer + 3
            elif op_code == 6:
                val = self.read_value(address_0, parameter_codes[0])
                if val == 0:
                    self.instruction_pointer = self.read_value(address_1, parameter_codes[1])
                else:
                    self.instruction_pointer = self.instruction_pointer + 3
            elif op_code == 7:
                val_0 = self.read_value(address_0, parameter_codes[0])
                val_1 = self.read_value(address_1, parameter_codes[1])
    
                if val_0 < val_1:
                    self.write_value(address_2, parameter_codes[2], 1)
                else:
                    self.write_value(address_2, parameter_codes[2], 0)
                self.instruction_pointer = self.instruction_pointer + 4
            elif op_code == 8:
                val_0 = self.read_value(address_0, parameter_codes[0])
                val_1 = self.read_value(address_1, parameter_codes[1])
    
                if val_0 == val_1:
                    self.write_value(address_2, parameter_codes[2], 1)
                else:
                    self.write_value(address_2, parameter_codes[2], 0)
                self.instruction_pointer = self.instruction_pointer + 4
            elif op_code == 9:
                #adjust relative base
                
                if verbose_mode:
                    print("Adjusting Relative Base by {self.read_value(address_0, parameter_codes[0])}")
                    print("Initial Relative Base {self.relative_base}")
                self.relative_base = self.relative_base + self.read_value(address_0, parameter_codes[0])
                self.instruction_pointer = self.instruction_pointer + 2
                if verbose_mode:
                    print("New Relative Base: {self.relative_base}")
            else:
                print('Something fucking broke')
    
            return self.instruction_pointer, 1
    
        def read_value(self,address, parameter):
            if verbose_mode:
                print("Reading memory")
                print(f"Address: {address}")
                print(f"Parameter: {parameter}")
            if super_verbose_mode:
                print(self.memory)
            #If parameter is 0, use the address stored in address
            if parameter == 0:
                new_address = self.memory[address]
                value = self.memory[new_address]
            #If parameter is 1, us the value stored in address
            elif parameter == 1:
                value = self.memory[address]
            #If parameter is 2, use relative base mode
            elif parameter == 2:
                new_address = self.relative_base + self.memory[address]
                value = self.memory[new_address]
            return int(value)
    
        def write_value(self, address, parameter, value):
            if parameter == 0 or parameter == 1:
                new_address = self.memory[address]
                self.memory[new_address] = value
            elif parameter == 2:
                new_address = self.relative_base + self.memory[address]
                self.memory[new_address] = value
    
        def run_puter(self):
            #SWITCH THIS TO JUST RUN 1 STEP AT A TIME
            status = 1
            while True:
                if self.op_code == 99:
                    #computer is done executing
                    return -1
                elif status == 0:
                    #pause to resume later
                    #IF WE PAUSE, LEAVE THE OPCODE AND PARAMETERS THE SAME, WE'RE WAITING FOR INPUT
                    return 0
                else:
                    self.instruction_pointer, status = self.execute_instruction(self.op_code, self.parameters)
                    self.op_code, self.parameters = self.read_instruction(self.memory[self.instruction_pointer])
            return self.memory
    
            
    initial_mem = "MEMORY GOES HERE"
    puter = intputer(initial_mem, 'puter')
    
    puter.run_puter()
    output = puter.output_buffer
    n = 3
    chunks = [output[i * n:(i + 1) * n] for i in range((len(output) + n - 1) // n )] 
    block_count = 0
    for chunk in chunks:
        if chunk[2] == 2:
            block_count = block_count + 1
    print(block_count)
    

    Running in Jupyter Notebook might really bite me in the ass for Part 2. We'll see

    Edit: I'm going to scream, I think I got a really shitty seed and it's causing me problems with my ball ricocheting off into unreachable areas. It's fixable, but I don't think it's going to be easy. God damn it.

    Part 2 - Python Minor update to Intputer, it now has the ability to clear it's output buffer.
    import math
    from itertools import permutations
    from itertools import cycle
    import itertools
    from IPython.display import clear_output
    verbose_mode = False
    super_verbose_mode = False
    automatic_io = True
    
    
    class intputer:
        def __init__(self,initial_memory, name):
            #makes the int-puter
            self.memory = [int(x) for x in initial_memory.split(",")]
            self.memory = self.memory + ([0] * 10000)
            self.input_buffer = []
            self.output_buffer = []
            self.input_index = 0
            self.instruction_pointer = 0
            self.name = name
            self.op_code, self.parameters = self.read_instruction(self.memory[self.instruction_pointer])
            self.relative_base = 0
        def add_input(self,val):
            self.input_buffer.append(val)
        def get_output(self):
            return self.output_buffer[-1]
        def clear_output(self):
            self.output_buffer = []
        def get_output_history(self, index):
            return self.output_buffer[-index]
        def get_name(self):
            return self.name
        def read_instruction(self,instruction):
            #In order to deal with shorter length instructions we're going to 0-pad everything out to len==5
            #and then divide it back up
            if verbose_mode:
                print(f"Reading Instruction: {instruction}")
            instruction_components = [int(d) for d in str(instruction)]
            if len(instruction_components) == 1:
                #Pads opcode with non-functional 0, now len==2
                instruction_components.insert(0,0)
            if len(instruction_components) == 2:
                #Brings parameter_1 from null to 0, now len==3
                instruction_components.insert(0,0)
            if len(instruction_components) == 3:
                #Brings parameter_2 from null to 0, now len==4
                instruction_components.insert(0,0)
            if len(instruction_components) == 4:
                #Brings parameter_3 from null to 0, now len==5
                instruction_components.insert(0,0)
            opcode_portion = instruction_components[-2:]
            parameters_reversed = instruction_components[0:3]
    
    
    
            parameters = [int(parameters_reversed[2]), int(parameters_reversed[1]), int(parameters_reversed[0])]
    
            opcode = int((10 * opcode_portion[0]) + opcode_portion[1])
    
            if verbose_mode:
                print(f"Op Code {opcode}")
                print(f"Parameters {parameters}")
    
            return opcode, parameters
    
        def execute_instruction(self, op_code, parameter_codes):
            address_0 = self.instruction_pointer + 1
            address_1 = self.instruction_pointer + 2
            address_2 = self.instruction_pointer + 3
    
            if super_verbose_mode:
                print(self.memory)
    
            if op_code == 1:
                val = self.read_value(address_0, parameter_codes[0]) + self.read_value(address_1, parameter_codes[1])
                self.write_value(address_2, parameter_codes[2], val)
                self.instruction_pointer = self.instruction_pointer + 4
            elif op_code == 2:
                val = self.read_value(address_0, parameter_codes[0]) * self.read_value(address_1, parameter_codes[1])
                self.write_value(address_2, parameter_codes[2], val)
                self.instruction_pointer = self.instruction_pointer + 4
            elif op_code == 3:
                if automatic_io == True:
                    if self.input_index >= len(self.input_buffer):
                        #We have less input than we need, wait
                        return self.instruction_pointer, 0
                    val = self.input_buffer[self.input_index]
                    self.input_index = self.input_index + 1
                else:
                    val = input("Input Requested: ")
                self.write_value(address_0, parameter_codes[0], val)
                self.instruction_pointer = self.instruction_pointer + 2
            elif op_code == 4:
                if automatic_io == True:
                    self.output_buffer.append(self.read_value(address_0, parameter_codes[0]))
                else:
                    print(self.read_value(address_0, parameter_codes[0]))
                self.instruction_pointer = self.instruction_pointer + 2
            elif op_code == 5:
                val = self.read_value(address_0, parameter_codes[0])
                if val != 0:
                    self.instruction_pointer = self.read_value(address_1, parameter_codes[1])
                else:
                    self.instruction_pointer = self.instruction_pointer + 3
            elif op_code == 6:
                val = self.read_value(address_0, parameter_codes[0])
                if val == 0:
                    self.instruction_pointer = self.read_value(address_1, parameter_codes[1])
                else:
                    self.instruction_pointer = self.instruction_pointer + 3
            elif op_code == 7:
                val_0 = self.read_value(address_0, parameter_codes[0])
                val_1 = self.read_value(address_1, parameter_codes[1])
    
                if val_0 < val_1:
                    self.write_value(address_2, parameter_codes[2], 1)
                else:
                    self.write_value(address_2, parameter_codes[2], 0)
                self.instruction_pointer = self.instruction_pointer + 4
            elif op_code == 8:
                val_0 = self.read_value(address_0, parameter_codes[0])
                val_1 = self.read_value(address_1, parameter_codes[1])
    
                if val_0 == val_1:
                    self.write_value(address_2, parameter_codes[2], 1)
                else:
                    self.write_value(address_2, parameter_codes[2], 0)
                self.instruction_pointer = self.instruction_pointer + 4
            elif op_code == 9:
                #adjust relative base
                
                if verbose_mode:
                    print("Adjusting Relative Base by {self.read_value(address_0, parameter_codes[0])}")
                    print("Initial Relative Base {self.relative_base}")
                self.relative_base = self.relative_base + self.read_value(address_0, parameter_codes[0])
                self.instruction_pointer = self.instruction_pointer + 2
                if verbose_mode:
                    print("New Relative Base: {self.relative_base}")
            else:
                print('Something fucking broke')
    
            return self.instruction_pointer, 1
    
        def read_value(self,address, parameter):
            if verbose_mode:
                print("Reading memory")
                print(f"Address: {address}")
                print(f"Parameter: {parameter}")
            if super_verbose_mode:
                print(self.memory)
            #If parameter is 0, use the address stored in address
            if parameter == 0:
                new_address = self.memory[address]
                value = self.memory[new_address]
            #If parameter is 1, us the value stored in address
            elif parameter == 1:
                value = self.memory[address]
            #If parameter is 2, use relative base mode
            elif parameter == 2:
                new_address = self.relative_base + self.memory[address]
                value = self.memory[new_address]
            return int(value)
    
        def write_value(self, address, parameter, value):
            if parameter == 0 or parameter == 1:
                new_address = self.memory[address]
                self.memory[new_address] = value
            elif parameter == 2:
                new_address = self.relative_base + self.memory[address]
                self.memory[new_address] = value
    
        def run_puter(self):
            #SWITCH THIS TO JUST RUN 1 STEP AT A TIME
            status = 1
            while True:
                if self.op_code == 99:
                    #computer is done executing
                    return -1
                elif status == 0:
                    #pause to resume later
                    #IF WE PAUSE, LEAVE THE OPCODE AND PARAMETERS THE SAME, WE'RE WAITING FOR INPUT
                    return 0
                else:
                    self.instruction_pointer, status = self.execute_instruction(self.op_code, self.parameters)
                    self.op_code, self.parameters = self.read_instruction(self.memory[self.instruction_pointer])
            return self.memory
    def display_screen(output, screen):
        chunks = [output[i * n:(i + 1) * n] for i in range((len(output) + n - 1) // n )]
        #print(chunks)
        score = scan_for_score(chunks)
        paddle_location = 18
        ball_location = 16
        blocks_remaining = False
        for chunk in chunks:
            x_pos = chunk[0]
            y_pos = chunk[1]
            block_type = chunk[2]
            
            if block_type == 0:
                #empty tile
                screen[y_pos][x_pos] = ' '
            elif block_type == 1:
                #wall tile
                screen[y_pos][x_pos] = '|'
            elif block_type == 2:
                #block tile
                screen[y_pos][x_pos] = 'â–ˆ'
                blocks_remaining = True
            elif block_type == 3:
                #paddle tile
                screen[y_pos][x_pos] = '='
                paddle_location = x_pos
            elif block_type == 4:
                #ball tile
                screen[y_pos][x_pos] = '*'
                ball_location = x_pos
        for row in screen:
            output_row = ""
            for cell in row:
                output_row = output_row + cell
            print(output_row)
        return score, ball_location, paddle_location, blocks_remaining
    
    def scan_for_score(chunks):
        for chunk in chunks:
            if chunk[0] == -1 and chunk[1] == 0 and chunk[2]:
                return chunk[2]
    def calc_ball_direction(ball_location, old_ball_location):
        ball_direction = None
        if ball_location < old_ball_location:
            #ball_moving left
            ball_direction = "left"
        elif ball_location > old_ball_location:
            #ball moving right
            ball_direction = "right"
        else:
            #ball moving straight up and down
            #unclear if possible
            ball_direction = "none"
        return ball_direction
    def calc_paddle_direction(ball_location, paddle_location, ball_direction):
        next_ball_location = ball_location
        #if paddle_location == next_ball_location:
        #    return 0
        if ball_direction == "left":
            next_ball_location = next_ball_location - 1
        elif ball_direction == "right":
            next_ball_location = next_ball_location + 1
        if paddle_location > next_ball_location and ball_direction == 'left':
            return -1
        elif paddle_location < next_ball_location and ball_direction == 'right':
            return 1
        else:
            return 0
            
    initial_mem = "MEMORY GOES HERE, DON'T FORGET TO CHANGE THE FIRST ADDRESS TO 2"
    
    
    puter = intputer(initial_mem, 'puter')
    
    puter.run_puter()
    output = puter.output_buffer
    n = 3
     
    
    width = 35
    height = 25
    
    screen = []
    for i in range(height):
        row = list([' '] * width)
        screen.append(row)
        
    result = 1
    old_ball_location = 0
    old_paddle_location = 0
    steps = 0
    while result != -1:
        steps = steps + 1
        #clear_output()
        result = puter.run_puter()
        output = puter.output_buffer
        puter.clear_output()
        score, ball_location, paddle_location, blocks_remaining = display_screen(output, screen)
        ball_direction = calc_ball_direction(ball_location, old_ball_location)
        paddle_direction = calc_paddle_direction(ball_location, paddle_location, ball_direction)
        val = paddle_direction
        
        print(f"Score: {score}")
        print(f"Paddle at location {paddle_location}")
        print(f"Ball at location {ball_location}")
        print(f"Ball moving in direction {ball_direction}")
        print(f"Paddle input: {val}")
        #val = input("Manual Override")
        puter.add_input(int(val))
        old_ball_location = ball_location
    print(f"Game Over! in {steps} turns")
    

    Yeah, this one was extremely fucky. I'll have some tips in a second once I wrap my head around how I actually got it to work.

    Part 2 Tips - Not Really Spoilers The X:-1, Y:0 location shows up for every output, not just at game over. But it's result at game over is your score. I had built my script to end when it saw that as an output, but it shows up immediately
    Part 2 Tips - Minor Spoilers I highly recommend building a tiny bot to make the game play for you. Manual input is going to be a huge pain. My game ran for like 15k steps. You don't want to do that by hand
    Part 2 Tips - Moderate Spoilers Tracking the ball direction between frames is relatively easy. If you do that you can figure out the ball's direction and predict where it's going to be. Figuring out the appropriate paddle behavior was much more annoying
    Part 2 Tips - Moderate Spoilers How your paddle is moving when the ball hits it determines the angle the ball shoots off in. This had me stuck for a long time because my ball kept shooting off at an impossible angle. Eventually I modified my paddle logic to make it reflect at more reasonable logic. I'm not entirely sure why it works.
    2 votes
  3. Crespyl
    (edited )
    Link
    This was really fun, and I was able to re-use most of my Day 11 code as a baseline. Part 1 Part 1 was more or less exactly the same as my Day 11: run a program, paint a display on output, count...

    This was really fun, and I was able to re-use most of my Day 11 code as a baseline.

    Part 1 Part 1 was more or less exactly the same as my Day 11: run a program, paint a display on output, count the painted tiles. The only real difference is that now I have to wait for a set of three outputs before I can paint, instead of just two.

    Having switched to lambda/callback driven IO also helped here.

    Code for P1/P2 - Crystal

    Once again my VM code itself is pretty much unchanged

    For Part 2, I already had a debugger that I'd made yesterday, so once I knew what I was looking for I could load it up and find the memory addresses for the ball and paddle, and use that information to create an autopilot to find the answer.

    To find the paddle, I watched the input routine to see what it examined and output to, there was an address that always either increased or decreased by the appropriate amount (there was also a check to stop it going over the borders, which was another hint), so that wasn't too hard.

    Once I had the paddle, I could disassemble the rest of the program and look for all the places that address was used to find where the ball-paddle check was happening, and determine the address for the balls x and y positions there. I briefly explored patching the game itself to always register a paddle hit, but ended up just writing an autopilot function into my arcade machine input routine.

    After I had all that working, I went back to work on the input and display and found a Crystal binding for ncurses. It took a while to get it working the way I wanted, but I'm pretty happy with the result.

    #!/usr/bin/env crystal
    require "crt"
    require "readline"
    require "../lib/utils.cr"
    require "../lib/vm2.cr"
    
    class Display
      property width : Int32
      property height : Int32
      property tiles : Array(Array(Int64))
      property segment : Int64
    
      property crt : Crt::Window | Nil
    
    
      def initialize(width, height, curses=false)
        @width = width
        @height = height
        @segment = 0
        @tiles = [] of Array(Int64)
        height.times { @tiles << [0_i64] * width }
    
        @colormap = {} of Int32 => Crt::ColorPair
    
        @tilemap = {
          0 => " ",
          1 => " ",
          2 => "=",
          3 => "@",
          4 => "o",
          -1 => "?"
        }
    
        if curses
          @colormap = {
            0 => Crt::ColorPair.new(Crt::Color::Default, Crt::Color::Default),
            1 => Crt::ColorPair.new(Crt::Color::White, Crt::Color::White),
            2 => Crt::ColorPair.new(Crt::Color::White, Crt::Color::Default),
            3 => Crt::ColorPair.new(Crt::Color::White, Crt::Color::Green),
            4 => Crt::ColorPair.new(Crt::Color::Blue, Crt::Color::Default),
            5 => Crt::ColorPair.new(Crt::Color::White, Crt::Color::Blue),
            -1 => Crt::ColorPair.new(Crt::Color::White, Crt::Color::Red),
          }
    
          @crt = Crt::Window.new(height,width)
        else @crt = nil
        end
      end
    
      def get(x,y)
        @tiles[y][x]
      end
    
      def set(x,y,val)
        if x < 0
          # segment display
          @segment = val
        else
          @tiles[y][x] = val
          @crt.try { |crt|
            crt.attribute_on colormap(val)
            crt.print(y.to_i32, x.to_i32, tilemap(val))
            crt.refresh
          }
        end
      end
    
      def count_painted(color=nil)
        @tiles.flat_map { |row| row }.reduce(0) { |sum,tile|
          if color
            tile == color ? sum + 1 : sum
          else
            tile != 0 ? sum + 1 : sum
          end
        }
      end
    
      def print_display
        if !@crt
          @tiles.each_with_index do |row,y|
            row.each_with_index do |tile,x|
              print tilemap(tile)
            end
            print "\n"
          end
          puts "SCORE: %i" % @segment
          puts ""
        else
          @crt.try { |crt|
            crt.attribute_on colormap(0)
            crt.attribute_on Crt::Attribute::Bold
            crt.print(@height-1,0, "SCORE: %i" % segment)
            crt.attribute_off Crt::Attribute::Bold
            crt.refresh
          }
        end
      end
    
      def colormap(val)
        @colormap[val.to_i32]? || @colormap[-1]
      end
    
      def tilemap(val)
        @tilemap[val.to_i32] || @tilemap[-1]
      end
    
    end
    
    class ArcadeCabinet
      property cpu : VM2::VM
      property display : Display
      property always_print : Bool
      property draw_buffer : Array(Int64)
      property do_hack : Bool
    
      def initialize(cpu, curses=false)
        @cpu = cpu
        @display = Display.new(45,26,curses)
        @always_print = false
        @do_hack = false
        @draw_buffer = [] of Int64
    
        @cpu.debug = false
        @cpu.output_fn = ->(x: Int64) { proccess_output(x) }
    
        if curses = false
          @cpu.input_fn = ->() { get_input }
        else
          @cpu.input_fn = ->() { get_curses_input }
        end
      end
    
      def set_free_play
        @cpu.write_mem(0,2_i64)
      end
    
      def autopilot
        if @cpu.read_mem(392) < @cpu.read_mem(388)
          1_i64
        elsif @cpu.read_mem(392) > @cpu.read_mem(388)
          -1_i64
        else
          0_i64
        end
      end
    
      def proccess_output(val : Int64) : Nil
        @draw_buffer << val
        if @draw_buffer.size >= 3
          x,y,id = @draw_buffer.shift(3)
    
          @display.set(x,y,id)
          @display.print_display if @always_print
        end
      end
    
      def get_input(display=true) : Int64
        if @do_hack
          return autopilot
        end
    
        @display.print_display
        input = Readline.readline("> ", true)
    
        case input
        when "h" then -1_i64
        when "l" then 1_i64
        when "?"
          puts "PC: %i" % @cpu.pc
          puts "paddle: %i" % @cpu.read_mem(392)
          puts "ballx: %i" % @cpu.read_mem(388)
          puts "bally: %i" % @cpu.read_mem(389)
          get_input(false)
        when "x"
          autopilot
        else
          0_i64
        end
      end
    
      def get_curses_input
        return autopilot if @do_hack
    
        @display.crt.try { |crt|
          c = crt.getch
          log "\n>%i<\n" % c
          case c
          when 260 then return -1_i64 # left arrow
          when 104 then return -1_i64 # h
    
          when 261 then return  1_i64 # right arrow
          when 108 then return  1_i64 # l
    
          when 120 then return autopilot # x
    
          when 33 then @do_hack=true; autopilot # !
    
          # exit on q, ^c or esc
          when 113 then exit
          when 27  then exit
          when 3   then exit
          end
        }
        return 0_i64
      end
    
      def log(msg)
        puts msg if Utils.enable_debug_output?
      end
    
      def run
        @cpu.run
      end
    
    end
    
    if ARGV[0]? == "play"
      Crt.init
      Crt.start_color
      Crt.raw
    
      cab = ArcadeCabinet.new(VM2.from_file("day13/input.txt"), true)
      cab.set_free_play
      cab.always_print = true
      cab.do_hack = ARGV[1]? == "hax"
      cab.run
    
      Crt.done
      puts "Game Over\nFinal Score: %i" % cab.display.segment
    else
      prog = Utils.get_input_file(Utils.cli_param_or_default(0, "day13/input.txt"))
      # p1
      cab = ArcadeCabinet.new(VM2.from_string(prog))
      cab.always_print = false
      cab.run
      puts "P1: %i" % cab.display.count_painted(2)
    
      # reset for p2
      cab = ArcadeCabinet.new(VM2.from_string(prog))
      cab.always_print = false
      cab.set_free_play
      cab.do_hack = true
      cab.run
      puts "P2: %i" % cab.display.segment
    end
    
    1 vote
  4. Gyrfalcon
    Link
    I liked this one. I maybe overcomplicated it in displaying the board but oh well. Code is in its happy little Python modules again for both parts 1 and 2. Intputer.py class Intputer(object): def...

    I liked this one. I maybe overcomplicated it in displaying the board but oh well.

    Code is in its happy little Python modules again for both parts 1 and 2.

    Intputer.py
    class Intputer(object):
    
        def __init__(self, instructions, pointer=0, relative_base=0, output_block=False):
    
            self.instructions = instructions
            self.pointer = pointer
            self.relative_base = relative_base
            self.curr_instruction = [int(num) for num in str(self.instructions[self.pointer])]
            self.output_block = output_block
            self.output_buffer = []
            self.input_buffer = []
            self.block = False
            self.isHalted = False
    
        def get_operands(self):
            for i in range(5 - len(self.curr_instruction)):
                self.curr_instruction.insert(0,0) # Pad to get the right number of 0s
    
            if self.curr_instruction[0] == 0:
                out_location = self.instructions[self.pointer + 3]
            elif self.curr_instruction[0] == 2:
                out_location = self.instructions[self.pointer + 3] + self.relative_base
            else:
                raise ValueError("Incorrect mode for output operand!")
    
            if self.curr_instruction[1] == 0:
                op_2_location = self.instructions[self.pointer+2]
            elif self.curr_instruction[1] == 1:
                op_2_location = self.pointer + 2
            elif self.curr_instruction[1] == 2:
                op_2_location = self.instructions[self.pointer+2] + self.relative_base
            else:
                raise ValueError("Incorrect mode for second operand!")
    
            if self.curr_instruction[2] == 0:
                op_1_location = self.instructions[self.pointer+1]
            elif self.curr_instruction[2] == 1:
                op_1_location = self.pointer + 1
            elif self.curr_instruction[2] == 2:
                op_1_location = self.instructions[self.pointer+1] + self.relative_base
            else:
                raise ValueError("Incorrect mode for first operand!")
    
            # We handle big memory on write, so this covers it on read
            try:
                op_1 = self.instructions[op_1_location]
            except IndexError:
                op_1 = 0
    
            try:
                op_2 = self.instructions[op_2_location]
            except IndexError:
                op_2 = 0
    
            return [op_1, op_2, out_location]
    
        def get_io_operand(self):
    
            for i in range(3 - len(self.curr_instruction)):
                self.curr_instruction.insert(0,0) # Pad to get the right number of 0s
    
            if self.curr_instruction[0] == 0:
                op = self.instructions[self.pointer + 1]
            elif self.curr_instruction[0] == 1:
                op = self.pointer + 1
            elif self.curr_instruction[0] == 2:
                op = self.instructions[self.pointer + 1] + self.relative_base
            else:
                raise ValueError("Incorrect mode for I/O operand!")
    
            return op
    
        def write_result(self, result, location):
    
            # Extend memory if needed
            try:
                self.instructions[location] = result
            except IndexError:
                self.instructions.extend([0] * ((location + 1) - len(self.instructions)))
                self.instructions[location] = result
    
        def add_instruction(self):
            operands = self.get_operands()
    
            result = operands[0] + operands[1]
            self.write_result(result, operands[2])
            self.pointer += 4
    
        def mult_instruction(self):
            operands = self.get_operands()
    
            result = operands[0] * operands[1]
            self.write_result(result, operands[2])
            self.pointer += 4
    
        def output_instruction(self):
    
            operand = self.get_io_operand()
    
            self.output_buffer.append(self.instructions[operand])
    
            if self.output_block:
                self.block = True
    
            self.pointer += 2
    
        def input_instruction(self):
    
            # Check for no input, and block if input is empty
            if len(self.input_buffer) < 1:
                self.block = True
                return
    
            operand = self.get_io_operand()
    
            self.write_result(self.input_buffer.pop(), operand)
            self.pointer += 2
    
        def jump_instruction(self, jump_type):
    
            operands = self.get_operands()
    
            if ((operands[0] and jump_type) or (not operands[0] and not jump_type)):
                self.pointer = operands[1]
            else:
                self.pointer += 3
    
        def less_than_instruction(self):
    
            operands = self.get_operands()
    
            if operands[0] < operands[1]:
                self.write_result(1, operands[2])
            else:
                self.write_result(0, operands[2])
    
            self.pointer += 4
    
        def equals_instruction(self):
    
            operands = self.get_operands()
    
            if operands[0] == operands[1]:
                self.write_result(1, operands[2])
            else:
                self.write_result(0, operands[2])
    
            self.pointer += 4
    
        def modify_base_instruction(self):
    
            operand = self.get_io_operand()
    
            self.relative_base += self.instructions[operand]
    
            self.pointer += 2
    
        def process_instructions(self, inputs=None):
    
            if inputs is not None:
                try:
                    for item in inputs:
                        self.input_buffer.insert(0, item)
                except TypeError: # Catch singular inputs here
                    self.input_buffer.insert(0,inputs)
            else:
                inputs = []
    
            while True:
    
                if self.pointer > (len(self.instructions) - 1):
                    break
                elif self.instructions[self.pointer] == 99:
                    self.isHalted = True
                    break
    
                self.curr_instruction = [int(num) for num in str(self.instructions[self.pointer])]
    
                if self.curr_instruction[-1] == 1:
                    self.add_instruction()
                elif self.curr_instruction[-1] == 2:
                    self.mult_instruction()
                elif self.curr_instruction[-1] == 3:
                    self.input_instruction()
                elif self.curr_instruction[-1] == 4:
                    self.output_instruction()
                elif self.curr_instruction[-1] == 5:
                    self.jump_instruction(True)
                elif self.curr_instruction[-1] == 6:
                    self.jump_instruction(False)
                elif self.curr_instruction[-1] == 7:
                    self.less_than_instruction()
                elif self.curr_instruction[-1] == 8:
                    self.equals_instruction()
                elif self.curr_instruction[-1] == 9:
                    self.modify_base_instruction()
    
                if self.block:
                    self.block = False
                    return
    
        def clear_output_buffer(self):
            self.output_buffer = []
    
    def load_instructions(input_file):
    
        with open(input_file, 'r') as fid:
            data = fid.readline()
    
        instructions = data.strip().split(',')
    
        for idx in range(len(instructions)):
            instructions[idx] = int(instructions[idx])
    
        return instructions
    
    CarePackage.py
    import Intputer
    import copy
    import random
    import time
    import os # To make a prettier display lol
    
    def update_play_field(play_field, updates):
    
        for idx in range(0, len(updates), 3):
    
            location = (updates[idx], updates[idx+1])
            item = updates[idx+2]
    
            play_field[location] = item
    
        return play_field
    
    def count_blocks(play_field):
    
        num_blocks = 0
        for key, value in play_field.items():
            if value == 2:
                num_blocks += 1
    
        return num_blocks
    
    def update_display(display, play_field):
    
        for idx in range(len(display)):
            for jdx in range(len(display[0])):
    
                display[idx][jdx] = play_field[(jdx, idx)]
    
    def show_display(display):
    
        for idx in range(len(display)):
            for jdx in range(len(display[0])):
    
                if display[idx][jdx] == 0:
                    print(" ", end='')
                elif display[idx][jdx] == 1:
                    print("W", end='')
                elif display[idx][jdx] == 2:
                    print("B", end='')
                elif display[idx][jdx] == 3:
                    print("~", end='')
                elif display[idx][jdx] == 4:
                    print("o", end='')
    
            print("")
    
    input_file = "PuzzleInput.txt"
    
    instructions = Intputer.load_instructions(input_file)
    
    play_field = {}
    
    arcade_computer = Intputer.Intputer(copy.copy(instructions))
    
    arcade_computer.process_instructions()
    
    play_field = update_play_field(play_field, arcade_computer.output_buffer)
    
    num_blocks = count_blocks(play_field)
    
    print(num_blocks)
    
    initial_blocks = num_blocks
    
    instructions[0] = 2 # Gotta put in those quarters!
    
    score = 0
    
    play_field = {}
    
    arcade_computer = Intputer.Intputer(copy.copy(instructions))
    
    arcade_computer.process_instructions()
    
    play_field = update_play_field(play_field, arcade_computer.output_buffer)
    
    # Have to fudge these over one for some reason
    x_max = max([key[0] for key in play_field.keys()]) + 1
    y_max = max([key[1] for key in play_field.keys()]) + 1
    
    display = [[0 for i in range(x_max)] for j in range(y_max)]
    
    update_display(display, play_field)
    
    num_blocks = count_blocks(play_field)
    
    while num_blocks != 0:
    
        arcade_computer.clear_output_buffer()
        os.system('clear')
        show_display(display)
    
        paddle_x = 0
        ball_x = 0
    
        for keys, items in play_field.items():
            if items == 4:
                ball_x = keys[0]
            elif items == 3:
                paddle_x = keys[0]
    
            if ball_x and paddle_x:
                break
    
        user_input = 0
    
        if ball_x < paddle_x:
            user_input = -1
        elif ball_x > paddle_x:
            user_input = 1
    
        arcade_computer.process_instructions(user_input)
    
        play_field = update_play_field(play_field, arcade_computer.output_buffer)
    
        update_display(display, play_field)
    
        num_blocks = count_blocks(play_field)
    
        if arcade_computer.isHalted:
            print("GAME OVER")
            break
    
        time.sleep(0.01)
    
    score = play_field[(-1, 0)]
    print(initial_blocks - num_blocks)
    
    print(score)
    
    Part 2 Spoilers

    I used a super simple bot for playing the game. All it does is move towards the ball, and that was enough for me to get it. I tried playing by hand but it was frustrating, and I figured doing all the work of a replay system like I saw discussed here and then having to play would be more effort than just making a simple bot. I did try to just have it move randomly but that did not really work so I gave up on it.

    1 vote
  5. nothis
    (edited )
    Link
    It's fun to let what is basically a virtual machine you programmed yourself run a little game like that! But I found a frustrating amount of work to go into basically reverse-engineering the...

    It's fun to let what is basically a virtual machine you programmed yourself run a little game like that! But I found a frustrating amount of work to go into basically reverse-engineering the actual game code. God knows when the paddle is actually redrawn.

    The intcode interpreter is also the first time I did something proper with generators and yield in js. I still don't get why I have to run an empty next() before actually putting in the input?

    Intcode module
    // advent of code 2019
    // Intcode interpreter
    
    const LOG = (text, verbose) => {
      if (verbose) {
        console.log(text);
      }
    };
    
    const opNames = [
      "unknown",
      "add",
      "multiply",
      "input",
      "output",
      "jump if not zero",
      "jump if zero",
      "less",
      "equal",
      "adjust relative base"
    ];
    
    function* runOps(ops, verbose) {
      let relativeBase = 0,
        p = 0,
        pos = 0;
    
      // increase memory, fill with 0
      const memorySize = 4096;
      for (m = ops.length; m < memorySize; m++) {
        ops[m] = 0;
      }
    
      while (ops[pos] != 99) {
        // get opcode digits
        const op = getDigits(ops[pos]);
        const currentOp = op.pop();
    
        switch (currentOp) {
          case 1:
            // add
            p = parameters(ops, op, pos, relativeBase, 3);
            ops[p[2]] = ops[p[0]] + ops[p[1]];
            pos += 4;
            break;
          case 2:
            // multiply
            p = parameters(ops, op, pos, relativeBase, 3);
            ops[p[2]] = ops[p[0]] * ops[p[1]];
            pos += 4;
            break;
          case 3:
            // input
            yield -99;
            let input = yield;
            p = parameters(ops, op, pos, relativeBase, 1);
            LOG(`Input: ${input}`, verbose);
            ops[p[0]] = input;
            pos += 2;
            break;
          case 4:
            // output
            p = parameters(ops, op, pos, relativeBase, 1);
            let output = ops[p[0]];
            LOG(`Output: ${output}`, verbose);
            yield output;
            pos += 2;
            break;
          case 5:
            // jump if not zero
            p = parameters(ops, op, pos, relativeBase, 2);
            if (ops[p[0]] !== 0) {
              pos = ops[p[1]];
            } else {
              pos += 3;
            }
            break;
          case 6:
            // jump if zero
            p = parameters(ops, op, pos, relativeBase, 2);
            if (ops[p[0]] === 0) {
              pos = ops[p[1]];
            } else {
              pos += 3;
            }
            break;
          case 7:
            // less
            p = parameters(ops, op, pos, relativeBase, 3);
            if (ops[p[0]] < ops[p[1]]) {
              ops[p[2]] = 1;
            } else {
              ops[p[2]] = 0;
            }
            pos += 4;
            break;
          case 8:
            // equal
            p = parameters(ops, op, pos, relativeBase, 3);
            if (ops[p[0]] === ops[p[1]]) {
              ops[p[2]] = 1;
            } else {
              ops[p[2]] = 0;
            }
            pos += 4;
            break;
          case 9:
            // adjust relative base
            p = parameters(ops, op, pos, relativeBase, 1);
            relativeBase += ops[p[0]];
            pos += 2;
            break;
          default:
            throw console.error(`Unknown op "${currentOp}"`);
        }
      }
    
      return -999;
    }
    
    const parameters = (ops, op, pos, relativeBase, parameterCount) => {
      let parameterList = [];
      let mode = 0;
      let p = 0;
      for (let i = 1; i < parameterCount + 1; i++) {
        mode = op.pop();
        if (mode === 0) {
          p = ops[pos + i];
        } else if (mode === 1) {
          p = pos + i;
        } else if (mode === 2) {
          p = relativeBase + ops[pos + i];
        } else {
          throw console.error(`Unknown mode "${mode}"`);
        }
        parameterList.push(p);
      }
      return parameterList;
    };
    
    const getDigits = number => {
      let digits = [];
      for (let d = 10000; d >= 10; d = d / 10) {
        if (d === 10) {
          digits.push(number);
          break;
        }
        let digit = Math.floor(number / d);
        digits.push(digit);
        number -= digit * d;
      }
      return digits;
    };
    
    const readInput = input => {
      return input.split(",").map(numberString => parseInt(numberString));
    };
    
    exports.readInput = readInput;
    exports.runOps = runOps;
    
    Part 2
    // advent of code 2019
    // day 13
    
    const intCode = require("./intcode.js");
    const input = require("./input.js");
    
    let ops = intCode.readInput(input.inputString);
    
    async function arcade() {
      let screen = [];
      for (let row = 0; row <= 22; row++) {
        screen[row] = [];
        for (let column = 0; column <= 35; column++) {
          screen[row][column] = 0;
        }
      }
    
      let run = intCode.runOps(ops, 0);
      let ballX = 0;
      let paddleX = 0;
      let paddleDirection = 0;
      let score = 0;
      while (true) {
        let x = run.next().value;
        if (x === -999) {
          display(screen, score);
          console.log("      GAME OVER, MAN, GAME OVER!");
          return;
        } else if (x === -99) {
          run.next();
          x = run.next(paddleDirection).value; // "joystick" input
        }
        let y = run.next().value;
        let value = run.next().value;
    
        if (x >= 0) {
          screen[y][x] = value;
          if (value > 4) {
            console.log(`unrecognized tile: ${tile}`);
          } else if (value === 4) {
            // ball tile
            ballX = x;
            display(screen, score);
            await sleep(16);
          } else if (value === 3) {
            // paddle tile
            paddleX = x;
          }
          paddleDirection = Math.sign(ballX - paddleX);
        } else if (x === -1) {
          score = value;
        }
      }
    }
    
    function sleep(ms) {
      return new Promise(resolve => {
        setTimeout(resolve, ms);
      });
    }
    
    const display = (screen, score) => {
      console.clear();
      const tiles = [" ", "#", "o", "-", "*"];
      for (let row = 0; row < screen.length; row++) {
        screenRow = "";
        for (let column = 0; column < screen[row].length; column++) {
          screenRow += tiles[screen[row][column]];
        }
        console.log(screenRow);
      }
      console.log(`       ***  SCORE: ${score}   ***`);
    };
    
    arcade();
    
    1 vote