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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy