Advent of Code, Day 22

Day 22 of Advent of Code is here! YES! It’s nice to complete one in the same day again. As mentioned before, I only have so much time in a day to work on these, so the more complex ones get pushed out. This one was rather simple. See all of my other solutions here. If you’re interested, check out what my colleagues at Slalom are doing with their Advent of Code! You can see this and my other solutions on github.

Day 22: Crab Combat

Part I

I enjoyed this one. Playing the card game “War” with my kids, I always think about how I would code this game. I get to today! With one little difference, this version of it has no ties, I guess that was going to be too hard (although Part 2 surpasses the difficulty of ties).

Here’s our two players’ list of cards, our data input

Let’s import that data and get it into our player’s hands

const fs = require('fs')
let data = fs.readFileSync('./input.txt', 'utf-8').trim().split('\n')

const player1 = data.slice(1, data.indexOf('')).map(d => Number(d))
const player2 = data.slice(data.indexOf('') + 2).map(d => Number(d))

This will look familiar, we’ve done similar before. Get the entire array of rows from the input, and then slice out player one’s cards, and then player two’s cards.

Let’s play! Now we loop through, one card at a time

while(player1.length > 0 && player2.length > 0) {
  const oneCard = player1.splice(0, 1)[0]
  const twoCard = player2.splice(0, 1)[0]

  if(oneCard > twoCard) {
    player1.push(oneCard)
    player1.push(twoCard)
  } else {
    player2.push(twoCard)
    player2.push(oneCard)
  }
}
  • We loop through all the cards, as long as both players still have cards to play.
  • Let’s take off the first card from the deck. I’m using a splice() as that will move that value from that array to a new array. Using a splice() will remove the card from the player’s deck.
  • Then if player one’s card is greater than player two’s card, we add these cards to the bottom of the deck. And the same if player two wins it. It’s important that the winning card goes on the bottom first.
const winner = [...player1, ...player2]
const length = winner.length

const score = winner.reduce((a, b, index) => a + ((length - index) * b), 0)

console.log('The winning score is', score)
  • Once a player wins, we have to calculate their score. Their last card is worth 1x that value, their second to last card is worth 2x that value and so on. Their first card is worth the total number of cards times that value
  • Instead of doing a condition of is player1 empty, then set as winner, if player2 is empty, blah blah blah. Since one wins by having them all, combining both array won’t hurt.
  • Then we use reduce() to find the score. To calculate as we loop, I take the length minus the current index of the value, so if there are 10 cards, we minus 0 from 10 (which gets us 10) and multiply that by the value, then add it to the growing total. Blam, our answer.

Part II

The AoC team hates us :*. Let’s play recursively! Using the same imports to get the data as Part 1. Our first big change is that while loop

const deckConfigs = new Map()
while(player1.length > 0 && player2.length > 0) {
  gameCounter += 1
  const subConfig = ['1:', ...player1, '2:', ...player2].join()
  if(deckConfigs.get(subConfig)) {
    console.log('PLAYER 1 WINS GAME BY RECURSION')
    break
  }
  deckConfigs.set(subConfig, true)

  const oneCard = player1.splice(0, 1)[0]
  const twoCard = player2.splice(0, 1)[0]

  if(oneCard <= player1.length && twoCard <= player2.length) {
    const player1subgame = player1.slice(0, oneCard)
    const player2subgame = player2.slice(0, twoCard)
    const whoWon = playSubGame(player1subgame, player2subgame)
    if(whoWon === 1) {
      console.log('PLAYER 1 WON ROUND', gameCounter)
      player1.push(oneCard)
      player1.push(twoCard)
    } else {
      console.log('PLAYER 2 WON ROUND', gameCounter)
      player2.push(twoCard)
      player2.push(oneCard)
    }
  } else {
    if(oneCard > twoCard) {
      console.log('PLAYER 1 WON ROUND', gameCounter)
      player1.push(oneCard)
      player1.push(twoCard)
    } else {
      console.log('PLAYER 2 WON ROUND', gameCounter)
      player2.push(twoCard)
      player2.push(oneCard)
    }
  }
}
  • Using the same while, as long as the users have cards to play, let’s play
  • There is a possibility for infinite looping, yay, so the rule is if that happens, player 1 wins that round automatically. To check for infinite loops, I am using a Map() object to store a “serialized” version of the two player’s decks, just converting both arrays to a single string. if this string exists, then we played it already, so we’re looping, so player 1 wins this round.
  • Then we get the two cards to play, still using splice()
  • There is a new rule, if the card they draw is less than the amount of cards they have in their deck, then they play a subgame using a copy of the cards from the deck, equal to the card number they drew. Weird rules…
    • Here I get the cards to play in this subgame, and I’m using a slice() (not a splice()) to copy out the new array
    • We send these new arrays to the function playSubGame, see below. That determines who wins, returning a 1 for player one, and 2 for player two
    • Then based on who wins we put the winning cards (the original cards that triggered this subgame) into the winner’s deck. Again, this subgame was a copy of cards, there is no other change to the players’ decks
  • If the cards they draw are greater than the total cards they have in their deck, we play as normal.

playSubGame is as follows

const playSubGame = (play1, play2) => {
  const subDeckConfigs = new Map()
  subGameCounter += 1
  subRoundCounter = 0
  while(play1.length > 0 && play2.length > 0) {
    const subConfig = ['1:', ...play1, '2:', ...play2].join()
    if(subDeckConfigs.get(subConfig)) {
      console.log('  PLAYER 1 WINS SUBGAME BY RECURSION')
      return 1
    }
    subDeckConfigs.set(subConfig, true)
    subRoundCounter += 1
    subRoundCounter % 1000 === 0 ? console.log('  SUBGAME', subRoundCounter) : null
    const oneCard = play1.splice(0, 1)[0]
    const twoCard = play2.splice(0, 1)[0]

    if(oneCard <= play1.length && twoCard <= play2.length) {
      const play1subgame = play1.slice(0, oneCard)
      const play2subgame = play2.slice(0, twoCard)
      const whoWon = playSubGame(play1subgame, play2subgame)
      if(whoWon === 1) {
        console.log('PLAYER 1 WON ROUND', gameCounter)
        play1.push(oneCard)
        play1.push(twoCard)
      } else {
        console.log('PLAYER 2 WON ROUND', gameCounter)
        play2.push(twoCard)
        play2.push(oneCard)
      }      
    } else {
      if(oneCard > twoCard) {
        play1.push(oneCard)
        play1.push(twoCard)
      } else {
        play2.push(twoCard)
        play2.push(oneCard)
      }

    }
  }

  console.log('  SUBGAME', play1.length > 0 ? 'PLAYER 1 WON!' : 'PLAYER 2 WON!')
  return play1.length > 0 ? 1 : 2
}
  • Much of this will match what we see in Part 1 and Part 2, just blocked off to a new function so we can recursively keep playing
  • You’ll see at line 20, we call playSubGame if the cards drawn are less than the cards in the hand. Recursiv-licious

And just like in Part 1, once all that is done, we log out the winning score

const winner = [...player1, ...player2]
const length = winner.length

const score = winner.reduce((a, b, index) => a + ((length - index) * b), 0)

console.log('The winning score is', score)

There is a decent amount of duplicate code in here. If I had more time, I could reduce that, using some methods for setting decks, etc. But for now, this works.

How did it go for you?

How did you find the answer on your own? How did you do it? Anything stump you? I’d love to hear how you did! Please comment below! I have also loaded up just the challenge and code from this and my other days on github, you can follow along there too: pretty Git pages or the code in the repo.

One thought on “Advent of Code, Day 22

Add yours

Leave a Reply

Powered by WordPress.com.

Up ↑

%d bloggers like this: