From d550a14cbb4de35cfb97951152361e2b73bc9394 Mon Sep 17 00:00:00 2001 From: Brian Peck Date: Mon, 30 Oct 2023 23:10:27 -0700 Subject: [PATCH] Day 22 2020 (#223) * Day 22 2020 * PR cleanup --- .../aoc/_2020/calendar/day22/CombatPlayer.kt | 29 +++++++ .../peckb/aoc/_2020/calendar/day22/Day22.kt | 76 +++++++++++++++++++ .../peckb/aoc/_2020/calendar/day22/README.md | 1 + .../_2020/calendar/day22/RecursiveCombat.kt | 67 ++++++++++++++++ .../calendar/day22/RecursiveCombatPlayer.kt | 43 +++++++++++ .../me/peckb/aoc/_2020/TestDayComponent.kt | 2 + .../aoc/_2020/calendar/day22/Day22Test.kt | 33 ++++++++ 7 files changed, 251 insertions(+) create mode 100644 src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/CombatPlayer.kt create mode 100644 src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/Day22.kt create mode 100644 src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/README.md create mode 100644 src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/RecursiveCombat.kt create mode 100644 src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/RecursiveCombatPlayer.kt create mode 100644 src/test/kotlin/me/peckb/aoc/_2020/calendar/day22/Day22Test.kt diff --git a/src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/CombatPlayer.kt b/src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/CombatPlayer.kt new file mode 100644 index 00000000..caea9ff2 --- /dev/null +++ b/src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/CombatPlayer.kt @@ -0,0 +1,29 @@ +package me.peckb.aoc._2020.calendar.day22 + +import java.util.LinkedList +import java.util.Queue + +data class CombatPlayer( + val id: Int, + val cardValues: Queue = LinkedList() +) { + fun addCard(toInt: Int) { + cardValues.add(toInt) + } + + fun hasCards(): Boolean = cardValues.peek() != null + + fun turnCard(): Int = cardValues.remove() + + fun addCards(higherCard: Int, lowerCard: Int) { + addCard(higherCard) + addCard(lowerCard) + } + + fun generateScore(): Long { + return cardValues.reversed().foldIndexed(0L) { index, total, card -> + val multiplier = index + 1 + total + (multiplier * card) + } + } +} diff --git a/src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/Day22.kt b/src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/Day22.kt new file mode 100644 index 00000000..43c9ed46 --- /dev/null +++ b/src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/Day22.kt @@ -0,0 +1,76 @@ +package me.peckb.aoc._2020.calendar.day22 + +import javax.inject.Inject + +import me.peckb.aoc.generators.InputGenerator.InputGeneratorFactory +import kotlin.math.max + +class Day22 @Inject constructor( + private val generatorFactory: InputGeneratorFactory, +) { + fun partOne(filename: String) = generatorFactory.forFile(filename).read { input -> + val players = mutableListOf() + + val iterator = input.iterator() + while (iterator.hasNext()) { + val next = iterator.next() + if (next.startsWith("Player ")) { + // new player + players.add(CombatPlayer(id = next.dropLast(1).takeLast(1).toInt())) + } else if (next.isEmpty()) { + // end of current player + } else { + players.last().addCard(next.toInt()) + } + } + + val playerOne = players.first() + val playerTwo = players.last() + + while (playerOne.hasCards() && playerTwo.hasCards()) { + val playerOneCard = playerOne.turnCard() + val playerTwoCard = playerTwo.turnCard() + + if (playerOneCard > playerTwoCard) { + playerOne.addCards(playerOneCard, playerTwoCard) + } else if (playerTwoCard > playerOneCard) { + playerTwo.addCards(playerTwoCard, playerOneCard) + } else { // cards are equal + playerOne.addCard(playerOneCard) + playerTwo.addCard(playerTwoCard) + } + } + + val p1Score = playerOne.generateScore() + val p2Score = playerTwo.generateScore() + + max(p1Score, p2Score) + } + + fun partTwo(filename: String) = generatorFactory.forFile(filename).read { input -> + val players = mutableListOf() + + val iterator = input.iterator() + while (iterator.hasNext()) { + val next = iterator.next() + if (next.startsWith("Player ")) { + // new player + players.add(RecursiveCombatPlayer(id = next.dropLast(1).takeLast(1).toInt())) + } else if (next.isEmpty()) { + // end of current player + } else { + players.last().addCard(next.toInt()) + } + } + + val playerOne = players.first() + val playerTwo = players.last() + + RecursiveCombat(playerOne, playerTwo).playGame() + + val p1Score = playerOne.generateScore() + val p2Score = playerTwo.generateScore() + + max(p1Score, p2Score) + } +} diff --git a/src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/README.md b/src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/README.md new file mode 100644 index 00000000..80e126b7 --- /dev/null +++ b/src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/README.md @@ -0,0 +1 @@ +## [Day 22: Crab Combat](https://adventofcode.com/2020/day/22) \ No newline at end of file diff --git a/src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/RecursiveCombat.kt b/src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/RecursiveCombat.kt new file mode 100644 index 00000000..93cd7b59 --- /dev/null +++ b/src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/RecursiveCombat.kt @@ -0,0 +1,67 @@ +package me.peckb.aoc._2020.calendar.day22 + +import me.peckb.aoc._2020.calendar.day22.RecursiveCombatWinner.* +import org.apache.commons.lang3.ObjectUtils.clone + +class RecursiveCombat( + private val playerOne: RecursiveCombatPlayer, + private val playerTwo: RecursiveCombatPlayer +) { + private val previouslySeenPlayerOneConfigurations = mutableSetOf() + private val previouslySeenPlayerTwoConfigurations = mutableSetOf() + + fun playGame(): RecursiveCombatWinner { + while (playerOne.hasCards() && playerTwo.hasCards()) { + val (winner, p1Card, p2Card) = deal() + if (p1Card == null || p2Card == null) return PLAYER_ONE + + when (winner) { + PLAYER_ONE -> playerOne.addCards(p1Card, p2Card) + PLAYER_TWO -> playerTwo.addCards(p2Card, p1Card) + NO_WINNER -> throw IllegalStateException("There must always be a winner!") + } + } + + if (playerOne.hasCards()) return PLAYER_ONE + return PLAYER_TWO + } + + private fun deal(): Triple { + val playerOneConfiguration = playerOne.deckConfiguration() + val playerTwoConfiguration = playerTwo.deckConfiguration() + + if ( + previouslySeenPlayerOneConfigurations.contains(playerOneConfiguration) && + previouslySeenPlayerTwoConfigurations.contains(playerTwoConfiguration) + ) { + return Triple(PLAYER_ONE, null, null) + } else { + previouslySeenPlayerOneConfigurations.add(playerOneConfiguration) + previouslySeenPlayerTwoConfigurations.add(playerTwoConfiguration) + } + + val playerOneCard = playerOne.turnCard() + val playerTwoCard = playerTwo.turnCard() + + val winner = if (playerOne.haveAtLeast(playerOneCard) && playerTwo.haveAtLeast(playerTwoCard)) { + RecursiveCombat( + playerOne.copy(cardValues = clone(playerOne.cardValues)).dropUntil(playerOneCard), + playerTwo.copy(cardValues = clone(playerTwo.cardValues)).dropUntil(playerTwoCard) + ).playGame() + } else { + if (playerOneCard > playerTwoCard) { + PLAYER_ONE + } else if (playerTwoCard > playerOneCard) { + PLAYER_TWO + } else { // cards are equal + NO_WINNER + } + } + + return Triple(winner, playerOneCard, playerTwoCard) + } +} + +enum class RecursiveCombatWinner { + PLAYER_ONE, PLAYER_TWO, NO_WINNER +} diff --git a/src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/RecursiveCombatPlayer.kt b/src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/RecursiveCombatPlayer.kt new file mode 100644 index 00000000..df251e46 --- /dev/null +++ b/src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/RecursiveCombatPlayer.kt @@ -0,0 +1,43 @@ +package me.peckb.aoc._2020.calendar.day22 + +import java.util.LinkedList +import java.util.Queue + +data class RecursiveCombatPlayer( + val id: Int, + val cardValues: Queue = LinkedList() +) { + fun addCard(toInt: Int) { + cardValues.add(toInt) + } + + fun hasCards(): Boolean = cardValues.peek() != null + + fun turnCard(): Int = cardValues.remove() + + fun addCards(higherCard: Int, lowerCard: Int) { + addCard(higherCard) + addCard(lowerCard) + } + + fun generateScore(): Long { + return cardValues.reversed().foldIndexed(0L) { index, total, card -> + val multiplier = index + 1 + total + (multiplier * card) + } + } + + fun deckConfiguration(): String { + return cardValues.toList().joinToString(",") + } + + fun haveAtLeast(deckSize: Int): Boolean { + return cardValues.toList().size >= deckSize + } + + fun dropUntil(deckSize: Int): RecursiveCombatPlayer = apply { + val topCards = cardValues.take(deckSize) + while(cardValues.poll() != null) { /* clear current queue */ } + topCards.forEach(::addCard) + } +} diff --git a/src/test/kotlin/me/peckb/aoc/_2020/TestDayComponent.kt b/src/test/kotlin/me/peckb/aoc/_2020/TestDayComponent.kt index 39ffe682..afeb09cc 100644 --- a/src/test/kotlin/me/peckb/aoc/_2020/TestDayComponent.kt +++ b/src/test/kotlin/me/peckb/aoc/_2020/TestDayComponent.kt @@ -21,6 +21,7 @@ import me.peckb.aoc._2020.calendar.day18.Day18Test import me.peckb.aoc._2020.calendar.day19.Day19Test import me.peckb.aoc._2020.calendar.day20.Day20Test import me.peckb.aoc._2020.calendar.day21.Day21Test +import me.peckb.aoc._2020.calendar.day22.Day22Test import javax.inject.Singleton import me.peckb.aoc.DayComponent @@ -51,4 +52,5 @@ internal interface TestDayComponent : DayComponent { fun inject(day19Test: Day19Test) fun inject(day20Test: Day20Test) fun inject(day21Test: Day21Test) + fun inject(day22Test: Day22Test) } diff --git a/src/test/kotlin/me/peckb/aoc/_2020/calendar/day22/Day22Test.kt b/src/test/kotlin/me/peckb/aoc/_2020/calendar/day22/Day22Test.kt new file mode 100644 index 00000000..6175847d --- /dev/null +++ b/src/test/kotlin/me/peckb/aoc/_2020/calendar/day22/Day22Test.kt @@ -0,0 +1,33 @@ +package me.peckb.aoc._2020.calendar.day22 + +import javax.inject.Inject + + +import me.peckb.aoc._2020.DaggerTestDayComponent +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +internal class Day22Test { + @Inject + lateinit var day22: Day22 + + @BeforeEach + fun setup() { + DaggerTestDayComponent.create().inject(this) + } + + @Test + fun testDay22PartOne() { + assertEquals(32102, day22.partOne(DAY_22)) + } + + @Test + fun testDay22PartTwo() { + assertEquals(34173, day22.partTwo(DAY_22)) + } + + companion object { + private const val DAY_22: String = "advent-of-code-input/2020/day22.input" + } +}