loading...
Cover image for Advent of Code 2019 Solution Megathread - Day 13: Care Package

Advent of Code 2019 Solution Megathread - Day 13: Care Package

jbristow profile image Jon Bristow ・2 min read

It's time to break out our IntCode interpreter and do something useful for once: play breakout.

Day 13 - The Problem

Man, flying through the void is really boring. Good thing those elves are looking out for us and sent us some in-flight entertainment. It seems to be a rudimentary breakout clone. Bounce the ball off your paddle to break all the bricks.

In order to check the status of the code, Part 1 asks us to verify that we see the proper number of blocks on the screen when the game starts.

Part 2 is to actually play the game and report our high score!

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 12

Under construction

Posted on by:

jbristow profile

Jon Bristow

@jbristow

503 - Internal Server Error - TooCoolForSchoolException

Discussion

pic
Editor guide
 

Surprisingly simple! No modifications needed to the IntCode after the Robot problem, so just plug in the interpreter and tell it how to play the game!

Kotlin solution:

import arrow.core.Either
import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.getOrElse
import arrow.core.left
import arrow.core.right
import arrow.core.some
import intcode.CurrentState
import intcode.handleCodePoint
import intcode.toIntCodeProgram
import util.TwoD

data class PointL(
    override val x: Long,
    override val y: Long
) : TwoD<Long> {
    companion object
}

sealed class GameTile {
    object Empty : GameTile()
    object Wall : GameTile()
    object Block : GameTile()
    object Paddle : GameTile()
    object Ball : GameTile()
}

fun GameTile?.toGlyph() = when (this) {
    GameTile.Wall -> "▫️"
    GameTile.Block -> "🎁"
    GameTile.Paddle -> "🏓"
    GameTile.Ball -> "🏐"
    else -> "◾️"
}

fun Long.toGameTile(): GameTile {
    return when (this) {
        0L -> GameTile.Empty
        1L -> GameTile.Wall
        2L -> GameTile.Block
        3L -> GameTile.Paddle
        4L -> GameTile.Ball
        else -> throw Error("Bad GameTile index: $this")
    }
}


data class ArcadeGame(
    val code: MutableMap<Long, Long>,
    val screen: MutableMap<PointL, GameTile> = mutableMapOf(),
    val state: Either<String, CurrentState> = CurrentState().right(),
    var display: Option<Long> = Option.empty()
) {
    tailrec fun draw(): ArcadeGame {
        return when (state) {
            is Either.Left<String> -> this
            is Either.Right<CurrentState> -> when {
                state.b.output.size >= 3 -> {
                    drawSingleTile(state)
                    draw()
                }
                else -> this
            }
        }
    }

    private fun drawSingleTile(state: Either.Right<CurrentState>) =
        when (val p = PointL(state.b.output.pop(), state.b.output.pop())) {
            PointL(-1, 0) -> display = state.b.output.pop().some()
            else -> screen[p] = state.b.output.pop().toGameTile()
        }
}

private fun <K, V : Any> MutableMap<K, V>.findFirst(paddle: V): K = filterValues(paddle::equals).keys.first()

private fun Either<String, CurrentState>.withJoystickPosition(screen: MutableMap<PointL, GameTile>) = map {
    if (it.waitingForInput) {
        it.inputs.add(
            screen.findFirst(GameTile.Ball).x
                .compareTo(screen.findFirst(GameTile.Paddle).x).toLong()
        )
    }
    it
}

object Day13 {
    private tailrec fun ArcadeGame.play(): Either<String, ArcadeGame> {
        return when (state) {
            is Either.Left<String> -> state
            is Either.Right<CurrentState> -> when (state.b.pointer) {
                is None -> right()
                is Some<Long> -> {
                    printScreen()
                    copy(
                        state = handleCodePoint(code, state.withJoystickPosition(screen))
                    ).draw().play()
                }
                else -> "Unknown error.".left()
            }
        }
    }

    private const val FILENAME = "src/main/resources/day13.txt"
    private val fileData = FILENAME.toIntCodeProgram()


    fun part1() {
        val game = ArcadeGame(code = fileData.toMutableMap())
        val finished = game.play()
        println(
            finished.fold({ "Problem: $it" }, { it.screen.filterValues { v -> v is GameTile.Block }.count() })
        )
    }

    fun part2() {
        val game = ArcadeGame(code = fileData.toMutableMap().apply { this[0] = 2 })
        val finished = game.play()
        println(
            finished.fold(
                { "Problem: $it" },
                { it.display.getOrElse { "No score displayed." } }
            )
        )
    }
}

fun main() {
    Day13.part1()
    Day13.part2()
}

fun ArcadeGame.printScreen() {
    val topLeft = PointL(
        screen.keys.map(PointL::x).min() ?: 0L,
        screen.keys.map(PointL::y).min() ?: 0L
    )
    val bottomRight = PointL(
        screen.keys.map(PointL::x).max() ?: 0L,
        screen.keys.map(PointL::y).max() ?: 0L
    )
    println(
        (topLeft.y..bottomRight.y).joinToString("\n") { y ->
            (topLeft.x..bottomRight.x).joinToString("") { x ->
                screen[PointL(x, y)].toGlyph()
            }
        }
    )
}

 

This was much easier indeed!

Part One

The only thing I've given for granted is that the machine, on the first part, never overwrites a tile, but I was ready to track that down eventually. I'm reporting just the relevant parts in JavaScript:

const game = createProgramInstance(codes, 1);
let blocks = 0;
while (true) {
  const { value: x } = game.next();
  if (typeof x === 'undefined') {
    break;
  }
  game.next();
  const { value: draw } = game.next();
  if (draw === 2) {
    blocks++;
  }
}
console.log(blocks);

Part Two

I don't know if you noticed the hidden message in the text: "You do have crew quarters, but they won't fit in the machine." 😂
Anyway, after the initial confusion about "how to play this game?!", I realized this is just Arkanoid/Breakout! 😄 I just have to move the paddle left and right to reach the ball.
The only change I did to the main routing was to set a fixed input value instead of a stack of values (joystickPosition).

const game = createProgramInstance(codes, 1);
let score;
let ballX;
let paddleX;
while (true) {
  const { value: x } = game.next();
  if (typeof x === 'undefined') {
    break;
  }
  const { value: y } = game.next();
  const { value } = game.next();
  if (x === -1 && y === 0) {
    score = value;
  } else if (value === 3) {
    paddleX = x;
  } else if (value === 4) {
    ballX = x;
  }
  joystickPosition = Math.sign(ballX - paddleX);
}
console.log(score);

Get my input at my repo.

 

Pretty straightforward today, which was nice after yesterday's tricky part 2 that I haven't solved yet (I didn't have as much time as usual). I adapted the idea from the painting robot to screen drawing, and used a very simple input logic for part 2 to make the paddle track the ball's x coordinate.

opcode screen_input(void *io_context) {
    struct screen *screen = (struct screen *)io_context;

    if (screen->paddle_x < screen->ball_x)
        return 1;
    else if (screen->paddle_x > screen->ball_x)
        return -1;
    else
        return 0;
}

Job done!

 

Clojure solution:

(load-file "intcode.clj")

(ns Day13
  (:require intcode))

; Adds or updates a tile in the provided map "tiles" given the 3-size output sequence in the format of (x y tile-type)
(defn assoc-tile [tiles tile-output]
  (let [x (nth tile-output 0)
        y (nth tile-output 1)
        type (nth tile-output 2)]
    (assoc tiles [x y] type)))

; Takes all outputs from a process state and updates the given map of tiles with them.
(defn update-tiles [tiles state]
  (reduce assoc-tile tiles (partition 3 (:outputs state))))

; Loads the game with no quarter provided and returns a map of all tiles that are created.
(defn load-game [code]
  (let [result (intcode/run code)]
    (update-tiles {} result)))

; Counts the amount of tiles of the given type in a tiles map
(defn count-tiles [tiles tile-type]
  (count (filter #{tile-type} (vals tiles))))

; In a map of tiles, finds the first key that is associated with the given tile type.
(defn find-tile-position [tiles tile-type]
  (first (filter #(= tile-type (tiles %)) (keys tiles))))

; Plays and beats the game, returns the score at the end.
(defn play-game [code]
  (loop [state (intcode/run (assoc code 0 2))
         tiles (update-tiles {} state)]
    (if (:terminated state)
      (tiles [-1 0])
      (let [ball-position (find-tile-position tiles 4)
            paddle-position (find-tile-position tiles 3)
            joystick-tilt (compare (ball-position 0) (paddle-position 0))
            next-state (intcode/continue state joystick-tilt)]
        (recur next-state (update-tiles tiles next-state))))))

(def input (intcode/parse-intcodes (slurp (first *command-line-args*))))
(println "Number of block tiles:" (count-tiles (load-game input) 2))
(println "Game score:" (play-game input))

As always, full code: github.com/jkoenig134/AdventOfCode...

 

No change in OpCode computer and it runs smoothly now.

Swift solution here

enum Joystick: Int {
    case left   = -1
    case right  = 1
    case neutral = 0
}

enum TileId: Int, CustomStringConvertible {
    var description: String {
        get {
            desc()
        }
    }

    func desc() -> String {
        switch self {
        case .empty:
            return "Empty"
        case .wall:
            return "Wall"
        case .block:
            return "Block"
        case .hPaddle:
            return "H Paddle"
        case .ball:
            return "Ball"
        }
    }

    case empty = 0
    case wall = 1
    case block = 2
    case hPaddle = 3
    case ball = 4

}

struct Point: CustomStringConvertible, Equatable, Hashable {
    var x: Int
    var y: Int

    var description: String {
        get {
            return "X: \(self.x) Y: \(self.y)"
        }
    }
}

class Arcade {
    var grid: [Point: TileId] = [:]
    var memory: [Int]

    init(_ memory: [Int]) {
        self.memory = memory
    }

    func compute() {
        let computer = Opcode(memory,0)

        while !computer.done {
            let x = computer.run()
            let y = computer.run()
            let id = TileId(rawValue: computer.run())

            grid[Point.init(x: x, y: y)] = id
        }
    }

    func getNumberOfTiles(for tile: TileId) -> Int {
        return grid.values.filter { $0 == tile }.count
    }

    func setQuarters(_ value: Int) {
        memory[0] = value
    }

    func beat() -> Int {
        let computer = Opcode(memory,0)
        var score = 0
        var ball = Point.init(x: 0, y: 0)
        var paddle = Point.init(x: 0, y: 0)

        while !computer.done {
            let x = computer.run()
            let y = computer.run()
            let value = computer.run()

            if x == -1 && y == 0 {
                score = value
            }
            else {
                let pos = Point.init(x: x, y: y)
                if value == 4 {
                    ball = pos
                }
                if value == 3 {
                    paddle = pos
                }
            }

            if paddle.x < ball.x {
                computer.inputIds.append(Joystick.right.rawValue)
            }
            else if paddle.x > ball.x {
                computer.inputIds.append(Joystick.left.rawValue)
            }
            else {
                computer.inputIds.append(Joystick.neutral.rawValue)
            }
        }
        return score
    }
}

func partOne() {
    let arcade = Arcade(input)
    arcade.compute()
    print(arcade.grid)
    print("Part 1 answer is :\(arcade.getNumberOfTiles(for: .block))")
}

func partTwo() {
    let arcade = Arcade(input)
    arcade.setQuarters(2)
    print("Part 2 answer is :\(arcade.beat())")
}

partOne()
partTwo()

Opcode can be found here in GitHub -> github.com/rizwankce/AdventOfCode/...