-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #245 from amclin/feat/2023-02
Feat/2023 02
- Loading branch information
Showing
6 changed files
with
472 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') |
Oops, something went wrong.