DEV Community

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

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

Jon Bristow on December 13, 2019

It's time to break out our IntCode interpreter and do something useful for once: play breakout. Day 13 - The Problem Man, flying throug...
Collapse
 
jbristow profile image
Jon Bristow • Edited

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()
            }
        }
    )
}

Collapse
 
maxart2501 profile image
Massimo Artizzu

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.

Collapse
 
neilgall profile image
Neil Gall

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!

Collapse
 
johnnyjayjay profile image
Johnny

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...

Collapse
 
rizzu26 profile image
Rizwan

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/...