Skip to content

Commit

Permalink
Day 22 2020 (#223)
Browse files Browse the repository at this point in the history
* Day 22 2020

* PR cleanup
  • Loading branch information
peckb1 authored Oct 31, 2023
1 parent 5507219 commit d550a14
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 0 deletions.
29 changes: 29 additions & 0 deletions src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/CombatPlayer.kt
Original file line number Diff line number Diff line change
@@ -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<Int> = 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)
}
}
}
76 changes: 76 additions & 0 deletions src/main/kotlin/me/peckb/aoc/_2020/calendar/day22/Day22.kt
Original file line number Diff line number Diff line change
@@ -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<CombatPlayer>()

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

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)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
## [Day 22: Crab Combat](https://adventofcode.com/2020/day/22)
Original file line number Diff line number Diff line change
@@ -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<String>()
private val previouslySeenPlayerTwoConfigurations = mutableSetOf<String>()

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<RecursiveCombatWinner, Int?, Int?> {
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
}
Original file line number Diff line number Diff line change
@@ -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<Int> = 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)
}
}
2 changes: 2 additions & 0 deletions src/test/kotlin/me/peckb/aoc/_2020/TestDayComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -51,4 +52,5 @@ internal interface TestDayComponent : DayComponent {
fun inject(day19Test: Day19Test)
fun inject(day20Test: Day20Test)
fun inject(day21Test: Day21Test)
fun inject(day22Test: Day22Test)
}
33 changes: 33 additions & 0 deletions src/test/kotlin/me/peckb/aoc/_2020/calendar/day22/Day22Test.kt
Original file line number Diff line number Diff line change
@@ -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"
}
}

0 comments on commit d550a14

Please sign in to comment.