Skip to content

Commit 3dfb9f3

Browse files
Merge pull request #245 from amclin/feat/2023-02
Feat/2023 02
2 parents be075d5 + a3205bc commit 3dfb9f3

File tree

6 files changed

+472
-1
lines changed

6 files changed

+472
-1
lines changed

2023/day-02/game.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
const parseGame = (gameString) => {
2+
const data = gameString.split(':')
3+
const id = parseInt(
4+
data[0].match(/\d+/)[0] // find the game number
5+
)
6+
const draws = data[1].split(';') // split the game into draws
7+
.map((draw) => {
8+
const result = ['red', 'green', 'blue']
9+
.map((color) => { // extract count for each color
10+
const reg = new RegExp(`\\d+(?= ${color})`)
11+
console.debug(reg)
12+
const val = draw.match(reg) || [0]
13+
console.debug(`${color} ${val}`)
14+
return parseInt(val).toString(16).padStart(2, '0') // convert to hex
15+
})
16+
17+
return result.join('') // combine into a RGB hex color string
18+
})
19+
20+
return {
21+
id,
22+
draws
23+
}
24+
}
25+
26+
const parseHex = (hex) => {
27+
return {
28+
r: parseInt(hex.substring(0, 2), 16),
29+
g: parseInt(hex.substring(2, 4), 16),
30+
b: parseInt(hex.substring(4, 6), 16)
31+
}
32+
}
33+
34+
const validateDraw = (draw, limit) => {
35+
const data = parseHex(draw)
36+
const lim = parseHex(limit)
37+
return (data.r <= lim.r && data.g <= lim.g && data.b <= lim.b)
38+
}
39+
40+
const validateGame = (game, limit) => {
41+
// const lim = parseHex(limit)
42+
// const tally = game.draws.reduce((acc, draw) => {
43+
// const drawData = parseHex(draw)
44+
// return {
45+
// r: acc.r + drawData.r,
46+
// g: acc.g + drawData.g,
47+
// b: acc.b + drawData.b
48+
// }
49+
// }, { r: 0, g: 0, b: 0 })
50+
51+
// const result = (tally.r <= lim.r && tally.g <= lim.g && tally.b <= lim.b)
52+
// console.debug(`Game ${game.id} ${(result) ? 'passes' : 'fails'}`)
53+
// if (!result) {
54+
// console.debug(tally)
55+
// }
56+
57+
// If any draw fails, the full game fails
58+
const result = game.draws.reduce((res, draw) => {
59+
return (res && validateDraw(draw, limit))
60+
}, true)
61+
return result
62+
}
63+
64+
const checksumGameSet = (games, limit) => {
65+
// tally the IDs of valid games
66+
return games.reduce((acc, game) => {
67+
return validateGame(game, limit) ? acc + game.id : acc
68+
}, 0)
69+
}
70+
71+
const countCubesNeeded = (game) => {
72+
const max = game.draws.reduce((acc, draw) => {
73+
const drawData = parseHex(draw)
74+
return {
75+
r: Math.max(acc.r, drawData.r),
76+
g: Math.max(acc.g, drawData.g),
77+
b: Math.max(acc.b, drawData.b)
78+
}
79+
}, { r: 0, g: 0, b: 0 })
80+
81+
return max
82+
}
83+
84+
const power = (game) => {
85+
const needed = countCubesNeeded(game)
86+
return needed.r * needed.g * needed.b
87+
}
88+
89+
module.exports = {
90+
parseGame,
91+
validateGame,
92+
checksumGameSet,
93+
validateDraw,
94+
countCubesNeeded,
95+
power
96+
}

2023/day-02/game.test.js

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/* eslint-env mocha */
2+
const { expect } = require('chai')
3+
const { parseGame, validateGame, checksumGameSet, validateDraw, countCubesNeeded, power } = require('./game')
4+
const { linesToArray } = require('../../2018/inputParser')
5+
const fs = require('fs')
6+
const path = require('path')
7+
const filePath = path.join(__dirname, 'input.txt')
8+
9+
describe('--- Day 2: Cube Conundrum ---', () => {
10+
describe('Part 1', () => {
11+
describe('parseGame', () => {
12+
it('extracts a game string into a data object with RGB hex values for draws', () => {
13+
const data = [
14+
'Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green',
15+
'Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue',
16+
'Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red',
17+
'Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red',
18+
'Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green'
19+
]
20+
const result = [
21+
{
22+
id: 1,
23+
draws: [
24+
'040003',
25+
'010206',
26+
'000200'
27+
]
28+
}, {
29+
id: 2,
30+
draws: [
31+
'000201',
32+
'010304',
33+
'000101'
34+
]
35+
}, {
36+
id: 3,
37+
draws: [
38+
'140806',
39+
'040d05',
40+
'010500'
41+
]
42+
}, {
43+
id: 4,
44+
draws: [
45+
'030106',
46+
'060300',
47+
'0e030f'
48+
]
49+
}, {
50+
id: 5,
51+
draws: [
52+
'060301',
53+
'010202'
54+
]
55+
}
56+
]
57+
58+
data.forEach((game, idx) => {
59+
expect(parseGame(game)).to.deep.equal(result[idx])
60+
})
61+
})
62+
})
63+
64+
describe('validateGame', () => {
65+
it('checks if the game is valid given the limits', () => {
66+
const limits = '0c0d0e' // 12 red cubes, 13 green cubes, and 14 blue cubes
67+
const data = [
68+
{
69+
id: 1,
70+
draws: [
71+
'040003',
72+
'010206',
73+
'000200'
74+
]
75+
}, {
76+
id: 2,
77+
draws: [
78+
'000201',
79+
'010304',
80+
'000101'
81+
]
82+
}, {
83+
id: 3,
84+
draws: [
85+
'140806',
86+
'040d05',
87+
'010500'
88+
]
89+
}, {
90+
id: 4,
91+
draws: [
92+
'030106',
93+
'060300',
94+
'0e030f'
95+
]
96+
}, {
97+
id: 5,
98+
draws: [
99+
'060301',
100+
'010202'
101+
]
102+
}
103+
]
104+
const result = [true, true, false, false, true]
105+
data.forEach((game, idx) => {
106+
expect(validateGame(game, limits)).to.equal(result[idx])
107+
})
108+
})
109+
})
110+
111+
describe('checksumGameSet', () => {
112+
it('tallies the IDs of valid games', () => {
113+
const limits = '0c0d0e' // 12 red cubes, 13 green cubes, and 14 blue cubes
114+
const data = [
115+
{
116+
id: 1,
117+
draws: [
118+
'040003',
119+
'010206',
120+
'000200'
121+
]
122+
}, {
123+
id: 2,
124+
draws: [
125+
'000201',
126+
'010304',
127+
'000101'
128+
]
129+
}, {
130+
id: 3,
131+
draws: [
132+
'140806',
133+
'040d05',
134+
'010500'
135+
]
136+
}, {
137+
id: 4,
138+
draws: [
139+
'030106',
140+
'060300',
141+
'0e030f'
142+
]
143+
}, {
144+
id: 5,
145+
draws: [
146+
'060301',
147+
'010202'
148+
]
149+
}
150+
]
151+
152+
expect(checksumGameSet(data, limits)).to.equal(8)
153+
})
154+
})
155+
156+
describe('validateDraw', () => {
157+
it('validates an individual draw is within limits', () => {
158+
const limit = '0c0d0e'
159+
expect(validateDraw('010206', limit)).to.equal(true)
160+
expect(validateDraw('060301', limit)).to.equal(true)
161+
expect(validateDraw('040d05', limit)).to.equal(true)
162+
expect(validateDraw('140806', limit)).to.equal(false) // game 3 draw 1 has 20 reds
163+
expect(validateDraw('0e030f', limit)).to.equal(false) // game 4 draw 3 has 15 blues
164+
})
165+
})
166+
167+
describe.skip('integration test', () => {
168+
let initData
169+
before((done) => {
170+
fs.readFile(filePath, { encoding: 'utf8' }, (err, rawData) => {
171+
if (err) throw err
172+
initData = linesToArray(rawData.trim()).map(parseGame)
173+
// Deep copy to ensure we aren't mutating the original data
174+
// data = JSON.parse(JSON.stringify(initData))
175+
done()
176+
})
177+
})
178+
179+
it('result matches what we know about the answer', () => {
180+
const limit = [12, 13, 14] // 12 red, 13 green, 14 blue
181+
.map((num) => num.toString(16).padStart(2, '0'))
182+
.join('')
183+
184+
expect(checksumGameSet(initData, limit)).to.be.gt(177) // 177 is too low
185+
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
186+
})
187+
})
188+
})
189+
190+
describe('Part 2', () => {
191+
describe('countCubesNeeded', () => {
192+
it('counts how many cubes are needed for a game', () => {
193+
const data = [
194+
'Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green',
195+
'Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue',
196+
'Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red',
197+
'Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red',
198+
'Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green'
199+
]
200+
const result = [
201+
{ r: 4, g: 2, b: 6 },
202+
{ r: 1, g: 3, b: 4 },
203+
{ r: 20, g: 13, b: 6 },
204+
{ r: 14, g: 3, b: 15 },
205+
{ r: 6, g: 3, b: 2 }
206+
]
207+
data.forEach((game, idx) => {
208+
expect(countCubesNeeded(parseGame(game))).to.deep.equal(result[idx])
209+
})
210+
})
211+
})
212+
describe('power', () => {
213+
it('calculates the power for a game', () => {
214+
const data = [
215+
'Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green',
216+
'Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue',
217+
'Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red',
218+
'Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red',
219+
'Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green'
220+
]
221+
const result = [48, 12, 1560, 630, 36]
222+
data.forEach((game, idx) => {
223+
expect(power(parseGame(game))).to.equal(result[idx])
224+
})
225+
})
226+
})
227+
})
228+
})

2023/day-02/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// eslint-disable-next-line no-unused-vars
2+
const console = require('../helpers')
3+
require('./solution')

0 commit comments

Comments
 (0)