Tom Hummel

2023 - Day 2

Intro

The puzzle prompt on adventofcode.com

# run solution with test data
zx content/advent-of-code/2023/2/_index.md

# run solution with input file data
zx content/advent-of-code/2023/2/_index.md content/advent-of-code/2023/2/input.txt

Code

const assert = require('assert')
debug(argv)
const [inputFile] = argv._

const sampleData = {
  part1: `Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green`
}

const bagContents = {
  red: 12,
  green: 13,
  blue: 14
}

const sampleSolutions = {
  part1: 8,
  part2: 2286
}

let inputContent
if (inputFile) {
  debug(`input file specified, loading data from: ${inputFile}`)
  inputContent = await fs.readFile(inputFile, 'utf-8')
} else {
  debug(`no input file specified, using part 1 sample data`)
  inputContent = sampleData.part1
}

const games = inputContent
  .split('\n')
  .map(value => value.toLowerCase())
  .map(value => {
    const gameNumber = parseInt(value.match(/^game [0-9]*/)[0].split(' ')[1], 10)
    const gameText = value.replace(/^game [0-9]*\: /, '')
    const grabs = gameText.split('; ')
    // debug(grabs)
    const grabReveals = grabs
      .map(sr => sr.split(', '))
      .map(rev => {
        // debug(rev)
        const samples = rev
          .map(s => {
            const [quantity, color] = s.split(' ')
            return {
              quantity: parseInt(quantity, 10),
              color
            }
          })
        // debug(samples)
        return samples
        // return {color, quantity}
      })

    return {
      gameNumber,
      gameText,
      grabs,
      grabReveals
    }
  })

// if (!inputFile) debug(games[0].grabReveals)

games.forEach(game => {
  let gameIsPossible = true
  const maxByColor = {
    red: -1,
    green: -1,
    blue: -1,
  }
  game.grabReveals.forEach(gr => {
    gr.forEach(reveal => {
      if (reveal.quantity > bagContents[reveal.color]) gameIsPossible = false
      if (reveal.quantity > maxByColor[reveal.color]) maxByColor[reveal.color] = reveal.quantity
    })
  })
  game.isPossible = gameIsPossible
  game.maxByColor = maxByColor
})

if (!inputFile) debug(games[0])

const possibleGame = games.filter(game => game.isPossible)
const possibleGameIdsSummed = possibleGame.reduce((memo, game) => {
  memo += game.gameNumber
  return memo
}, 0)

console.log(`Part 1: Sum of valid game ids: ${possibleGameIdsSummed}`)

if (!inputFile) assert.equal(possibleGameIdsSummed, sampleSolutions.part1, 'part 1 solution should match expected')

const gamePowers = games.map(g => {
  const gamePower = Object.values(g.maxByColor).reduce((memo, val) => {
    if (memo === 0) return val
    memo *= val
    return memo
  }, 0)
  return gamePower
})

const powerSums = gamePowers.reduce((memo, power) => {
  memo += power
  return memo
}, 0)

if (!inputFile) assert.equal(powerSums, sampleSolutions.part2, 'part 2 solution should match expected')

console.log(`Part 2: Sum of game powers: ${powerSums}`)

function debug (msg) {
  if (process.env.DEBUG) console.log(msg)  
}