~srpablo/advent_of_code_2021

ec258082870bacca97882eb92cede7a59d241f6d — Pablo Meier 2 years ago f99ec23 main
Day 21 in Scala
4 files changed, 134 insertions(+), 0 deletions(-)

M README.md
A day21/Day21.scala
A day21/Makefile
A day21/input.txt
M README.md => README.md +2 -0
@@ 24,6 24,7 @@ of the solutions 😄
* [Day 17][day17]: Raku
* [Day 18][day18]: Clojure
* [Day 20][day20]: Lua
* [Day 21][day21]: Scala

   [day1]: https://git.sr.ht/~srpablo/advent_of_code_2021/tree/main/item/day1/day1.factor
   [day2]: https://git.sr.ht/~srpablo/advent_of_code_2021/tree/main/item/day2/day2.sml


@@ 44,4 45,5 @@ of the solutions 😄
   [day17]: https://git.sr.ht/~srpablo/advent_of_code_2021/tree/main/item/day17/day17.raku
   [day18]: https://git.sr.ht/~srpablo/advent_of_code_2021/tree/main/item/day18/day18/src/day18/core.clj
   [day20]: https://git.sr.ht/~srpablo/advent_of_code_2021/tree/main/item/day20/day20.lua
   [day21]: https://git.sr.ht/~srpablo/advent_of_code_2021/tree/main/item/day21/Day21.scala
   [aoc]: https://adventofcode.com/2021

A day21/Day21.scala => day21/Day21.scala +117 -0
@@ 0,0 1,117 @@
import scala.collection.mutable.HashMap
import scala.math.BigInt

object Day21 {

  def newPlayerPos(pos: Int, inc: Int): Int = {
    var incBy = inc % 10
    if (incBy == 0)
      incBy = 10
    var newPos = (pos + incBy) % 10
    if (newPos == 0)
      newPos = 10
    newPos
  }

  def incOrReset(value: Int): Int = {
    val newValue = value + 1
    if (newValue == 101)
      100
    newValue
  }

  def roll_die(position: Int): (Int, Int) = {
    val p1 = incOrReset(position)
    val p2 = incOrReset(p1)
    val p3 = incOrReset(p2)
    ((position + p1 + p2), p3)
  }

  def part1(): Int = {
    var isP1Turn = true
    var p1Score = 0
    var p1Pos = 2
    var p2Score = 0
    var p2Pos = 10
    
    var dieRolls = 0
    var diePosition = 1
    
    while (p1Score < 1000 && p2Score < 1000) {
      dieRolls += 3
      var (totalMove, dp) = roll_die(diePosition)
      diePosition = dp
      if (isP1Turn) {
        p1Pos = newPlayerPos(p1Pos, totalMove)
        p1Score += p1Pos
      } else {
        p2Pos = newPlayerPos(p2Pos, totalMove)
        p2Score += p2Pos
      }
      isP1Turn = !isP1Turn
    }
    
    dieRolls * p1Score.min(p2Score)
  }

  def runQuantumGame(
    p1Score: Int,
    p1Pos: Int,
    p2Score: Int,
    p2Pos: Int,
    isP1Turn: Boolean,
    memo: HashMap[(Int, Int, Int, Int, Boolean), (BigInt, BigInt)]
    ): (BigInt, BigInt) = {
    val params = (p1Score, p1Pos, p2Score, p2Pos, isP1Turn)
    memo.get(params) match {
      case Some(v) => v
      case None => {
        val result = if (p1Score >= 21) {
            (BigInt(1), BigInt(0))
          }
          else if (p2Score >= 21) {
            (BigInt(0), BigInt(1))
          }
          else {
            var allP1Wins = BigInt(0)
            var allP2Wins = BigInt(0)

            val die = List(1,2,3)
            val combos = for { x <- die; y <- die; z <- die } yield (x, y, z)
            for (roll <- combos) {
              val totalMove = roll._1 + roll._2 + roll._3
              val simulation: (BigInt, BigInt) = if (isP1Turn) {
                  val newP1Pos = newPlayerPos(p1Pos, totalMove)
                  val newP1Score = p1Score + newP1Pos
                  runQuantumGame(newP1Score, newP1Pos, p2Score, p2Pos, !isP1Turn, memo)
                } else {
                  val newP2Pos = newPlayerPos(p2Pos, totalMove)
                  val newP2Score = p2Score + newP2Pos
                  runQuantumGame(p1Score, p1Pos, newP2Score, newP2Pos, !isP1Turn, memo)
                }
              val theseP1Wins = simulation._1
              val theseP2Wins = simulation._2
              allP1Wins += theseP1Wins
              allP2Wins += theseP2Wins
            }
            (allP1Wins, allP2Wins)
          }
        memo.put(params, result)
        result
      }
    }
  }

  def part2(): BigInt = {
    val (allP1Wins, allP2Wins) = runQuantumGame(0, 2, 0, 10, true, HashMap())
    allP1Wins.max(allP2Wins)
  }


  def main(args: Array[String]) = {
    val p1 = part1()
    println(s"Part 1: $p1")
    val p2 = part2().toString()
    println(s"Part 2: $p2")
  }
}

A day21/Makefile => day21/Makefile +13 -0
@@ 0,0 1,13 @@
BUILD=build

run: build
	scala -cp $(BUILD) Day21

build: prepare
	scalac -d $(BUILD) Day21.scala

prepare:
	[ -d $(BUILD) ] || mkdir $(BUILD)

clean:
	rm -rf $(BUILD)

A day21/input.txt => day21/input.txt +2 -0
@@ 0,0 1,2 @@
Player 1 starting position: 2
Player 2 starting position: 10