Skip to content

Commit 2e53b2d

Browse files
committed
Solution for day 20 (2020)
1 parent 0f82244 commit 2e53b2d

File tree

3 files changed

+2073
-0
lines changed

3 files changed

+2073
-0
lines changed

2020/20/index.js

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
const fs = require('fs')
2+
3+
const seaMonsterRegExpTop = /^..................#./g
4+
const seaMonsterRegExpMid = /#....##....##....###/g
5+
const seaMonsterRegExpBot = /^.#..#..#..#..#..#.../g
6+
7+
fs.readFile(`${__dirname}/input.txt`, (_e, data) => {
8+
const puzzle = data.toString().split('\n\n').filter(piece => piece.length).map(mapPiece)
9+
puzzle.forEach(p => p.setup(puzzle))
10+
console.log('Part 1', part1(puzzle))
11+
console.log('Part 2', part2(puzzle))
12+
})
13+
14+
function part1(puzzle) {
15+
return puzzle.filter(p => p.corner).map(p => p.id).reduce((a, b) => a * b, 1)
16+
}
17+
18+
function part2(puzzle) {
19+
// Choose random starting corner piece and rotate to top left
20+
const start = puzzle.filter(piece => piece.corner)[0]
21+
while (start.matches.s !== 1 || start.matches.e !== 1) {
22+
start.rotate()
23+
}
24+
start.place(0, 0)
25+
26+
// Setup grid
27+
const size = puzzle.length ** 0.5;
28+
const completed = [[start]]
29+
30+
// Fill grid
31+
for (let i = 1; i < puzzle.length; i++) {
32+
const x = i % size
33+
const y = Math.floor(i / size)
34+
const next = findPiece(puzzle, completed, x, y)
35+
next.place(x, y)
36+
completed[y] = completed[y] || []
37+
completed[y][x] = next
38+
}
39+
40+
const rendered = render(completed)
41+
// const rendered =
42+
// `..#.....#......#.#.........#..#......#.....#.#..#.#....##.......##..#..........#.#.##.#...#.....
43+
// ........#..#....#......##............#........................##..#......#........#.....#.......
44+
// #.....#..###......##...###..#..#..#....#....##....##....###..####.#.....#....#...#.#.#....##....
45+
// ....##......#...##...###...##....###..##..#...####..##.#.##..##......#..###......#...#......#...
46+
// #.#...........#.###.#..#..####..#.......#..#..#....................#..#.##....##....###.......#.
47+
// #...#.....#...#......###.#..............###.#...................#..##.##..#..#..#..#.#.##..####.`
48+
return roughness(rendered)
49+
}
50+
51+
function findPiece(puzzle, completed, x, y) {
52+
const size = puzzle.length ** 0.5
53+
// Filter by piece already placed and type (corner, edge, other)
54+
const corner = x === 0 && y === 0 || x === size - 1 && y === 0 || x === 0 && y === size - 1 || x === size - 1 && y === size - 1
55+
const side = !corner && (x === 0 || y === 0 || x === size - 1 || y === size - 1)
56+
const pieces = puzzle.filter(p => p.placed === false && p.corner === corner && p.side === side)
57+
58+
// Do any of them fit?
59+
const left = completed[y] && completed[y][x - 1] ? completed[y][x - 1].edges.e : null
60+
const top = completed[y - 1] && completed[y - 1][x] ? completed[y - 1][x].edges.s : null
61+
for (let i = 0; i < pieces.length; i++) {
62+
const piece = pieces[i]
63+
for (let j = 0; j < 8; j++) {
64+
if ((!left || piece.edges.w === left) && (!top || piece.edges.n === top)) return piece
65+
piece.rotate()
66+
if (j % 4 === 3) piece.flip()
67+
}
68+
}
69+
70+
// No :(
71+
throw 'No piece found'
72+
}
73+
74+
function roughness(rendered) {
75+
let rendersWithMonsters = []
76+
for (let i = 0; i < 8; i++) {
77+
rendersWithMonsters[i] = findSeaMonsters(rendered)
78+
// Rotate
79+
rendered = rotateMatrix(rendered.split('\n').map(line => line.split(''))).map(line => line.join('')).join('\n')
80+
if (i % 4 === 3) { // Flip
81+
rendered = rendered.split('\n').reverse().join('\n')
82+
}
83+
}
84+
const roughest = rendersWithMonsters.map(render => ({ render, roughness: render.match(/#/g).length }))
85+
roughest.sort((a, b) => a.roughness - b.roughness)
86+
87+
console.log(roughest[0].render.replace(/O/g, '\x1b[32mO\x1b[0m'))
88+
return roughest[0].roughness
89+
}
90+
91+
function findSeaMonsters(rendered) {
92+
const lines = rendered.split('\n')
93+
94+
// Find sea monsters
95+
let seaMonsters = []
96+
for (let y = 1; y < lines.length - 2; y++) {
97+
let match = null
98+
seaMonsterRegExpMid.lastIndex = 0
99+
while (match = seaMonsterRegExpMid.exec(lines[y])) {
100+
seaMonsterRegExpTop.lastIndex = 0
101+
seaMonsterRegExpBot.lastIndex = 0
102+
seaMonsterRegExpMid.lastIndex = match.index + 1
103+
const x = match.index
104+
const top = seaMonsterRegExpTop.exec(lines[y - 1].substr(x))
105+
const bot = seaMonsterRegExpBot.exec(lines[y + 1].substr(x))
106+
if (top && bot) seaMonsters.push({ x, y })
107+
}
108+
}
109+
110+
// Render the monsters
111+
for (let i = 0; i < seaMonsters.length; i++) {
112+
const mon = seaMonsters[i]
113+
lines[mon.y - 1] = lines[mon.y - 1].split('').map((c, j) => [18].includes(j - mon.x) ? 'O' : c).join('')
114+
lines[mon.y] = lines[mon.y].split('').map((c, j) => [0,5,6,11,12,17,18,19].includes(j - mon.x) ? 'O' : c).join('')
115+
lines[mon.y + 1] = lines[mon.y + 1].split('').map((c, j) => [1,4,7,10,13,16].includes(j - mon.x) ? 'O' : c).join('')
116+
}
117+
return lines.join('\n')
118+
}
119+
120+
function render(complete) {
121+
let out = ''
122+
const pieceSize = complete[0][0].pixels.length
123+
for (let y = 0; y < complete.length; y++) {
124+
for (let py = 1; py < pieceSize - 1; py++) {
125+
complete[y].forEach(piece => out += piece.pixels[py].slice(1, 9).join(''))
126+
out += '\n'
127+
}
128+
}
129+
return out
130+
}
131+
132+
function mapPiece(piece) {
133+
const lines = piece.split('\n')
134+
const id = parseInt(lines[0].substr(5))
135+
const pixels = lines.slice(1).map(line => line.split(''))
136+
return new PuzzlePiece(id, pixels)
137+
}
138+
139+
class PuzzlePiece {
140+
141+
#puzzle = null
142+
143+
constructor(id, pixels) {
144+
this.id = id
145+
this.used = false
146+
this.pixels = pixels
147+
this.edges = getEdges(this.pixels)
148+
this.matches = { n: 0, e: 0, s: 0, w: 0 }
149+
this.adjacent = null
150+
this.#puzzle = null
151+
this.x = null
152+
this.y = null
153+
this.placed = false
154+
}
155+
156+
setup(puzzle) {
157+
this.#puzzle = puzzle
158+
this.getMatches()
159+
}
160+
161+
getMatches() {
162+
this.matches = { n: 0, e: 0, s: 0, w: 0 }
163+
this.#puzzle.filter(p => p !== this).forEach(p => {
164+
if (Object.values(p.edges).includes(this.edges.n)) this.matches.n++
165+
if (Object.values(p.edges).includes(this.edges.e)) this.matches.e++
166+
if (Object.values(p.edges).includes(this.edges.s)) this.matches.s++
167+
if (Object.values(p.edges).includes(this.edges.w)) this.matches.w++
168+
})
169+
this.adjacent = Object.values(this.matches).reduce((a, b) => a + b, 0)
170+
}
171+
172+
rotate() {
173+
this.pixels = rotateMatrix(this.pixels)
174+
this.edges = getEdges(this.pixels)
175+
this.getMatches()
176+
}
177+
178+
flipVertical() {
179+
this.pixels.reverse()
180+
this.edges = getEdges(this.pixels)
181+
this.getMatches()
182+
}
183+
184+
flip() {
185+
this.flipVertical()
186+
}
187+
188+
get corner() {
189+
return this.adjacent === 2
190+
}
191+
192+
get side() {
193+
return this.adjacent === 3
194+
}
195+
196+
place(x, y) {
197+
this.x = x
198+
this.y = y
199+
this.placed = true
200+
}
201+
}
202+
203+
function getEdges(pixels) {
204+
const size = pixels.length
205+
return {
206+
n: toBin(pixels[0]),
207+
e: toBin(pixels.map(row => row[size - 1])),
208+
s: toBin(pixels[size - 1]),
209+
w: toBin(pixels.map(row => row[0])),
210+
nf: toBin(pixels[0], true),
211+
ef: toBin(pixels.map(row => row[size - 1]), true),
212+
sf: toBin(pixels[size - 1], true),
213+
wf: toBin(pixels.map(row => row[0]), true)
214+
}
215+
}
216+
217+
function toBin(edge, reverse = false) {
218+
edge = JSON.parse(JSON.stringify(edge))
219+
if (reverse) edge.reverse()
220+
return parseInt(edge.join('').replace(/\./g, '0').replace(/\#/g, '1'), 2)
221+
}
222+
223+
function rotateMatrix(matrix) {
224+
const n = matrix.length
225+
const x = Math.floor(n / 2)
226+
const y = n - 1
227+
for (let i = 0; i < x; i++) {
228+
for (let j = i; j < y - i; j++) {
229+
k = matrix[i][j]
230+
matrix[i][j] = matrix[y - j][i]
231+
matrix[y - j][i] = matrix[y - i][y - j]
232+
matrix[y - i][y - j] = matrix[j][y - i]
233+
matrix[j][y - i] = k
234+
}
235+
}
236+
return matrix
237+
}

0 commit comments

Comments
 (0)