Skip to content

Commit 2706538

Browse files
feat(2018 day-12): generate plants from input and pattern rules
1 parent 6cbd22d commit 2706538

File tree

3 files changed

+242
-7
lines changed

3 files changed

+242
-7
lines changed

2018/day-12/plants.js

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
class Plants {
2-
constructor (initial) {
2+
constructor (initial, rules) {
33
this.generations = []
4+
this.rules = {}
45
this._setInitialGeneration(initial)
6+
if (rules && rules.length > 0) {
7+
rules.forEach((rule) => {
8+
this.addRule(rule)
9+
})
10+
}
511
}
612

713
_setInitialGeneration (initial) {
@@ -14,6 +20,108 @@ class Plants {
1420
})
1521
)
1622
}
23+
24+
/**
25+
* Adds a rule to the rules list
26+
* @param {Object} rule {id: "..#..", generate: "#"}
27+
* @returns {Object} Plants instance for chaining
28+
*/
29+
addRule (rule) {
30+
this.rules[rule.id] = rule.generate
31+
return this // allow chaining
32+
}
33+
34+
/**
35+
* Determines the the pot state based on the pattern provided
36+
* @param {String} pattern "..#.."
37+
* @returns {String} "." or "#"
38+
*/
39+
predictPlant (pattern) {
40+
const result = this.rules[pattern] || '.'
41+
return result
42+
}
43+
44+
/**
45+
* Advances the plants by one generation
46+
* @returns {Array} The results of the new generation
47+
*/
48+
advance () {
49+
const prevGen = this.generations[this.generations.length - 1]
50+
51+
// Loop through all pots in the last generation to create a new generation
52+
const nextGen = prevGen.map((pot) => {
53+
// Assemble pattern for the given pot
54+
let pattern = ''
55+
for (let x = pot.position - 2; x <= pot.position + 2; x++) {
56+
let pp = prevGen.find((p) => p.position === x)
57+
pattern += (pp) ? pp.state : '.'
58+
}
59+
const state = this.predictPlant(pattern)
60+
61+
return {
62+
position: pot.position,
63+
state: state
64+
}
65+
})
66+
// Add 2 pots at the beginning and end to support future generation
67+
for (let x = 1; x <= 2; x++) {
68+
const first = nextGen[0].position
69+
const last = nextGen[nextGen.length - 1].position
70+
nextGen.splice(0, 0, { position: first - 1, state: '.' })
71+
nextGen.push({ position: last + 1, state: '.' })
72+
}
73+
74+
// Store the new generation
75+
this.generations.push(nextGen)
76+
return this.generations[this.generations.length - 1]
77+
}
78+
79+
/**
80+
* Renders out a visual display of the pots in all generations
81+
* Wrapper for getDisplay()
82+
* @param {Number} start First pot to include
83+
* @param {Number} end Last pot to include
84+
*/
85+
display (start, end) {
86+
console.log(this.getDisplay(start, end))
87+
}
88+
89+
/**
90+
* Scans the generations to find the leftmost and rightmost pot with a plant
91+
* @returns {Array} [left, right] position of first and last pots
92+
*/
93+
findPotBoundaries () {
94+
return this.generations.reduce((acc, gen) => {
95+
let pots = gen.filter((p) => p.state === '#')
96+
return [
97+
acc[0] < pots[0].position ? acc[0] : pots[0].position,
98+
acc[1] < pots[pots.length - 1].postion ? acc[1] : pots[pots.length - 1].position
99+
]
100+
}, [0, 0])
101+
}
102+
103+
/**
104+
* Generates a visual display of the pots in all generations. Accepts optional boundaries.
105+
* @param {Number} start First pot to include
106+
* @param {Number} end Last pot to include.
107+
*/
108+
getDisplay (start, end) {
109+
// Find boundaries if not provided
110+
if (!start || !end) {
111+
const boundaries = this.findPotBoundaries()
112+
start = start || boundaries[0] - 1
113+
end = end || boundaries[1] + 1
114+
}
115+
let output = ''
116+
this.generations.forEach((gen) => {
117+
for (let p = start; p <= end; p++) {
118+
const plant = gen.find((pot) => pot.position === p)
119+
output += (plant) ? plant.state : '.'
120+
}
121+
output += '\n'
122+
})
123+
return output.trim()
124+
}
17125
}
18126

19127
module.exports = {

2018/day-12/plants.test.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,27 @@
11
/* eslint-env mocha */
22
const expect = require('chai').expect
3+
const {
4+
parseLine
5+
} = require('./helpers')
36
const {
47
Plants
58
} = require('./plants')
69

710
const initialState = '#..#.#..##......###...###'
11+
const rules = `...## => #
12+
..#.. => #
13+
.#... => #
14+
.#.#. => #
15+
.#.## => #
16+
.##.. => #
17+
.#### => #
18+
#.#.# => #
19+
#.### => #
20+
##.#. => #
21+
##.## => #
22+
###.. => #
23+
###.# => #
24+
####. => #`.split('\n').map(parseLine)
825

926
describe('--- Day 12: Subterranean Sustainability ---', () => {
1027
describe('Plants:', () => {
@@ -42,5 +59,99 @@ describe('--- Day 12: Subterranean Sustainability ---', () => {
4259
expect(actual).to.deep.equal(expected)
4360
})
4461
})
62+
describe('advance()', () => {
63+
it('advances the next generation', () => {
64+
const expected = [
65+
{ position: -2, state: '.' },
66+
{ position: -1, state: '.' },
67+
{ position: 0, state: '#' },
68+
{ position: 1, state: '.' },
69+
{ position: 2, state: '.' },
70+
{ position: 3, state: '.' },
71+
{ position: 4, state: '#' },
72+
{ position: 5, state: '.' },
73+
{ position: 6, state: '.' },
74+
{ position: 7, state: '.' },
75+
{ position: 8, state: '.' },
76+
{ position: 9, state: '#' },
77+
{ position: 10, state: '.' },
78+
{ position: 11, state: '.' },
79+
{ position: 12, state: '.' },
80+
{ position: 13, state: '.' },
81+
{ position: 14, state: '.' },
82+
{ position: 15, state: '#' },
83+
{ position: 16, state: '.' },
84+
{ position: 17, state: '.' },
85+
{ position: 18, state: '#' },
86+
{ position: 19, state: '.' },
87+
{ position: 20, state: '.' },
88+
{ position: 21, state: '#' },
89+
{ position: 22, state: '.' },
90+
{ position: 23, state: '.' },
91+
{ position: 24, state: '#' },
92+
{ position: 25, state: '.' },
93+
{ position: 26, state: '.' }
94+
]
95+
let plantTracker = new Plants(initialState, rules)
96+
plantTracker.advance()
97+
const actual = plantTracker.generations[1]
98+
expect(actual).to.deep.equal(expected)
99+
})
100+
})
101+
describe('predictPlant(pattern)', () => {
102+
it('retrieves the expected state based on the specified pattern', () => {
103+
const pattern = '#.#.#'
104+
const expected = '#'
105+
let plantTracker = new Plants(initialState, rules)
106+
const actual = plantTracker.predictPlant(pattern)
107+
expect(actual).to.equal(expected)
108+
})
109+
})
110+
describe('getDisplay()', () => {
111+
it('gets a visual display of the generations', () => {
112+
let expected = `...#..#.#..##......###...###...........
113+
...#...#....#.....#..#..#..#...........
114+
...##..##...##....#..#..#..##..........`
115+
expected = expected.replace(/ /g, '')
116+
const plantTracker = new Plants(initialState, rules)
117+
for (let gen = 1; gen <= 2; gen++) {
118+
plantTracker.advance()
119+
}
120+
const actual = plantTracker.getDisplay(-3, 35)
121+
expect(actual).to.equal(expected)
122+
})
123+
})
124+
describe('getDisplay()', () => {
125+
it('supports optional boundaries', () => {
126+
let expected = `...#..#.#..##......###...###...........
127+
...#...#....#.....#..#..#..#...........
128+
...##..##...##....#..#..#..##..........
129+
..#.#...#..#.#....#..#..#...#..........
130+
...#.#..#...#.#...#..#..##..##.........
131+
....#...##...#.#..#..#...#...#.........
132+
....##.#.#....#...#..##..##..##........
133+
...#..###.#...##..#...#...#...#........
134+
...#....##.#.#.#..##..##..##..##.......
135+
...##..#..#####....#...#...#...#.......
136+
..#.#..#...#.##....##..##..##..##......
137+
...#...##...#.#...#.#...#...#...#......
138+
...##.#.#....#.#...#.#..##..##..##.....
139+
..#..###.#....#.#...#....#...#...#.....
140+
..#....##.#....#.#..##...##..##..##....
141+
..##..#..#.#....#....#..#.#...#...#....
142+
.#.#..#...#.#...##...#...#.#..##..##...
143+
..#...##...#.#.#.#...##...#....#...#...
144+
..##.#.#....#####.#.#.#...##...##..##..
145+
.#..###.#..#.#.#######.#.#.#..#.#...#..
146+
.#....##....#####...#######....#.#..##.`
147+
expected = expected.replace(/ /g, '')
148+
const plantTracker = new Plants(initialState, rules)
149+
for (let gen = 1; gen <= 20; gen++) {
150+
plantTracker.advance()
151+
}
152+
const actual = plantTracker.getDisplay()
153+
expect(actual).to.equal(expected)
154+
})
155+
})
45156
})
46157
})

2018/day-12/solution.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,27 @@
1-
// const {
2-
// parseLine
3-
// } = require('./helpers')
1+
const {
2+
parseLine
3+
} = require('./helpers')
44
const {
55
Plants
66
} = require('./plants')
77

88
const initialState = '#..#.#..##......###...###'
9-
const plantTracker = new Plants(initialState)
10-
11-
console.log(plantTracker.generations[0])
9+
const rules = `...## => #
10+
..#.. => #
11+
.#... => #
12+
.#.#. => #
13+
.#.## => #
14+
.##.. => #
15+
.#### => #
16+
#.#.# => #
17+
#.### => #
18+
##.#. => #
19+
##.## => #
20+
###.. => #
21+
###.# => #
22+
####. => #`.split('\n').map(parseLine)
23+
const plantTracker = new Plants(initialState, rules)
24+
for (let gen = 1; gen <= 20; gen++) {
25+
plantTracker.advance()
26+
}
27+
plantTracker.display()

0 commit comments

Comments
 (0)