Skip to content

Commit

Permalink
Merge pull request #245 from amclin/feat/2023-02
Browse files Browse the repository at this point in the history
Feat/2023 02
  • Loading branch information
amclin committed Dec 15, 2023
2 parents be075d5 + a3205bc commit 3dfb9f3
Show file tree
Hide file tree
Showing 6 changed files with 472 additions and 1 deletion.
96 changes: 96 additions & 0 deletions 2023/day-02/game.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
const parseGame = (gameString) => {
const data = gameString.split(':')
const id = parseInt(
data[0].match(/\d+/)[0] // find the game number
)
const draws = data[1].split(';') // split the game into draws
.map((draw) => {
const result = ['red', 'green', 'blue']
.map((color) => { // extract count for each color
const reg = new RegExp(`\\d+(?= ${color})`)
console.debug(reg)
const val = draw.match(reg) || [0]
console.debug(`${color} ${val}`)
return parseInt(val).toString(16).padStart(2, '0') // convert to hex
})

return result.join('') // combine into a RGB hex color string
})

return {
id,
draws
}
}

const parseHex = (hex) => {
return {
r: parseInt(hex.substring(0, 2), 16),
g: parseInt(hex.substring(2, 4), 16),
b: parseInt(hex.substring(4, 6), 16)
}
}

const validateDraw = (draw, limit) => {
const data = parseHex(draw)
const lim = parseHex(limit)
return (data.r <= lim.r && data.g <= lim.g && data.b <= lim.b)
}

const validateGame = (game, limit) => {
// const lim = parseHex(limit)
// const tally = game.draws.reduce((acc, draw) => {
// const drawData = parseHex(draw)
// return {
// r: acc.r + drawData.r,
// g: acc.g + drawData.g,
// b: acc.b + drawData.b
// }
// }, { r: 0, g: 0, b: 0 })

// const result = (tally.r <= lim.r && tally.g <= lim.g && tally.b <= lim.b)
// console.debug(`Game ${game.id} ${(result) ? 'passes' : 'fails'}`)
// if (!result) {
// console.debug(tally)
// }

// If any draw fails, the full game fails
const result = game.draws.reduce((res, draw) => {
return (res && validateDraw(draw, limit))
}, true)
return result
}

const checksumGameSet = (games, limit) => {
// tally the IDs of valid games
return games.reduce((acc, game) => {
return validateGame(game, limit) ? acc + game.id : acc
}, 0)
}

const countCubesNeeded = (game) => {
const max = game.draws.reduce((acc, draw) => {
const drawData = parseHex(draw)
return {
r: Math.max(acc.r, drawData.r),
g: Math.max(acc.g, drawData.g),
b: Math.max(acc.b, drawData.b)
}
}, { r: 0, g: 0, b: 0 })

return max
}

const power = (game) => {
const needed = countCubesNeeded(game)
return needed.r * needed.g * needed.b
}

module.exports = {
parseGame,
validateGame,
checksumGameSet,
validateDraw,
countCubesNeeded,
power
}
228 changes: 228 additions & 0 deletions 2023/day-02/game.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/* eslint-env mocha */
const { expect } = require('chai')
const { parseGame, validateGame, checksumGameSet, validateDraw, countCubesNeeded, power } = require('./game')
const { linesToArray } = require('../../2018/inputParser')
const fs = require('fs')
const path = require('path')
const filePath = path.join(__dirname, 'input.txt')

describe('--- Day 2: Cube Conundrum ---', () => {
describe('Part 1', () => {
describe('parseGame', () => {
it('extracts a game string into a data object with RGB hex values for draws', () => {
const data = [
'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 result = [
{
id: 1,
draws: [
'040003',
'010206',
'000200'
]
}, {
id: 2,
draws: [
'000201',
'010304',
'000101'
]
}, {
id: 3,
draws: [
'140806',
'040d05',
'010500'
]
}, {
id: 4,
draws: [
'030106',
'060300',
'0e030f'
]
}, {
id: 5,
draws: [
'060301',
'010202'
]
}
]

data.forEach((game, idx) => {
expect(parseGame(game)).to.deep.equal(result[idx])
})
})
})

describe('validateGame', () => {
it('checks if the game is valid given the limits', () => {
const limits = '0c0d0e' // 12 red cubes, 13 green cubes, and 14 blue cubes
const data = [
{
id: 1,
draws: [
'040003',
'010206',
'000200'
]
}, {
id: 2,
draws: [
'000201',
'010304',
'000101'
]
}, {
id: 3,
draws: [
'140806',
'040d05',
'010500'
]
}, {
id: 4,
draws: [
'030106',
'060300',
'0e030f'
]
}, {
id: 5,
draws: [
'060301',
'010202'
]
}
]
const result = [true, true, false, false, true]
data.forEach((game, idx) => {
expect(validateGame(game, limits)).to.equal(result[idx])
})
})
})

describe('checksumGameSet', () => {
it('tallies the IDs of valid games', () => {
const limits = '0c0d0e' // 12 red cubes, 13 green cubes, and 14 blue cubes
const data = [
{
id: 1,
draws: [
'040003',
'010206',
'000200'
]
}, {
id: 2,
draws: [
'000201',
'010304',
'000101'
]
}, {
id: 3,
draws: [
'140806',
'040d05',
'010500'
]
}, {
id: 4,
draws: [
'030106',
'060300',
'0e030f'
]
}, {
id: 5,
draws: [
'060301',
'010202'
]
}
]

expect(checksumGameSet(data, limits)).to.equal(8)
})
})

describe('validateDraw', () => {
it('validates an individual draw is within limits', () => {
const limit = '0c0d0e'
expect(validateDraw('010206', limit)).to.equal(true)
expect(validateDraw('060301', limit)).to.equal(true)
expect(validateDraw('040d05', limit)).to.equal(true)
expect(validateDraw('140806', limit)).to.equal(false) // game 3 draw 1 has 20 reds
expect(validateDraw('0e030f', limit)).to.equal(false) // game 4 draw 3 has 15 blues
})
})

describe.skip('integration test', () => {
let initData
before((done) => {
fs.readFile(filePath, { encoding: 'utf8' }, (err, rawData) => {
if (err) throw err
initData = linesToArray(rawData.trim()).map(parseGame)
// Deep copy to ensure we aren't mutating the original data
// data = JSON.parse(JSON.stringify(initData))
done()
})
})

it('result matches what we know about the answer', () => {
const limit = [12, 13, 14] // 12 red, 13 green, 14 blue
.map((num) => num.toString(16).padStart(2, '0'))
.join('')

expect(checksumGameSet(initData, limit)).to.be.gt(177) // 177 is too low
expect(checksumGameSet(initData, limit)).to.be.gt(1452) // 1452 (from creating the limit in hex wrong, and assuming cubes are not returned to the bag after each draw) is too low
})
})
})

describe('Part 2', () => {
describe('countCubesNeeded', () => {
it('counts how many cubes are needed for a game', () => {
const data = [
'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 result = [
{ r: 4, g: 2, b: 6 },
{ r: 1, g: 3, b: 4 },
{ r: 20, g: 13, b: 6 },
{ r: 14, g: 3, b: 15 },
{ r: 6, g: 3, b: 2 }
]
data.forEach((game, idx) => {
expect(countCubesNeeded(parseGame(game))).to.deep.equal(result[idx])
})
})
})
describe('power', () => {
it('calculates the power for a game', () => {
const data = [
'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 result = [48, 12, 1560, 630, 36]
data.forEach((game, idx) => {
expect(power(parseGame(game))).to.equal(result[idx])
})
})
})
})
})
3 changes: 3 additions & 0 deletions 2023/day-02/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// eslint-disable-next-line no-unused-vars
const console = require('../helpers')
require('./solution')
Loading

0 comments on commit 3dfb9f3

Please sign in to comment.