Last active
December 12, 2020 05:51
-
-
Save warriordog/3a75501a638abb19e778ed46e90be8e4 to your computer and use it in GitHub Desktop.
Solution to Advent of Code 2020 Day 11 Part 2
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
const { parseInputFile } = require('../utils/parser'); | |
/** | |
* @readonly | |
* @enum {number} | |
*/ | |
const Seat = { | |
FLOOR: 0, | |
EMPTY: 1, | |
TAKEN: 2 | |
}; | |
/** | |
* @readonly | |
* @enum {Seat} | |
*/ | |
const SeatLookup = { | |
'.': Seat.FLOOR, | |
'L': Seat.EMPTY, | |
'#': Seat.TAKEN | |
}; | |
/** | |
* @typedef Sig Hash-like signature that identifies a distinct room configuration | |
* @type {number} | |
*/ | |
/** | |
* @typedef Room | |
* @type {object} | |
* @property {Seat[][]} seats 2D array of seats in the room | |
* @property {number} iteration Current iteration number | |
* @property {Sig[]} trace Array of signatures representing all past states | |
*/ | |
/** | |
* @param {Seat[][]} seats | |
* @returns {Room} | |
*/ | |
function createRoom(seats) { | |
return { | |
seats, | |
iteration: 0, | |
trace: [ computeSig(seats) ] | |
}; | |
} | |
// parse input file into 2D aray | |
/** @type {Seat[][]} */ | |
const initialSeats = parseInputFile('day11-input.txt', /^([\.L#]+)$/gm) | |
.map(([_, line]) => Array.from(line).map(seatCode => SeatLookup[seatCode])) | |
; | |
function createFreshRoom() { | |
return createRoom(initialSeats.map(row => Array.from(row))); | |
} | |
/** | |
* @param {Seat[][]} seats | |
* @returns {Sig} | |
*/ | |
function computeSig(seats) { | |
let sig = 0; | |
let i = 1; | |
for (let y = 0; y < seats.length; y++) { | |
const row = seats[y]; | |
const sigY = Math.max(Math.pow(y + 1, 3), 1); | |
for (let x = 0; x < row.length; x++) { | |
const sigX = Math.max(Math.pow(x + 1, 2), 1); | |
sig = sig ^ (sigY * sigX * (row[x] + 1) * i); | |
i++; | |
} | |
} | |
return sig; | |
} | |
/** | |
* @param {Seat[][]} seats | |
* @param {number} startX | |
* @param {number} startY | |
* @param {number} xOff | |
* @param {number} yOff | |
* @returns {number} | |
*/ | |
function countRay(seats, startX, startY, xOff, yOff) { | |
for (let x = startX + xOff, y = startY + yOff; y >= 0 && y < seats.length && x >= 0 && x < seats[y].length; x += xOff, y += yOff) { | |
const seat = seats[y][x]; | |
if (seat !== Seat.FLOOR) { | |
return (seats[y][x] === Seat.TAKEN) ? 1 : 0; | |
} | |
} | |
return 0; | |
} | |
/** | |
* @param {Seat[][]} seats | |
* @param {number} x | |
* @param {number} y | |
* @returns {number} | |
*/ | |
function countAdjacentSeats(seats, x, y) { | |
return ( | |
countRay(seats, x, y, 1, 0) + | |
countRay(seats, x, y, 1, 1) + | |
countRay(seats, x, y, 0, 1) + | |
countRay(seats, x, y, -1, 1) + | |
countRay(seats, x, y, -1, 0) + | |
countRay(seats, x, y, -1, -1) + | |
countRay(seats, x, y, 0, -1) + | |
countRay(seats, x, y, 1, -1) | |
); | |
} | |
/** | |
* @param {Seat[][]} seats | |
* @param {number} x | |
* @param {number} y | |
* @returns {Seat} | |
*/ | |
function getNextSeat(seats, x, y) { | |
const seat = seats[y][x]; | |
// floor doesn't change | |
if (seat === Seat.FLOOR) { | |
return Seat.FLOOR; | |
} | |
const numAdjacentSeats = countAdjacentSeats(seats, x, y); | |
if (seat === Seat.EMPTY) { | |
return (numAdjacentSeats === 0) ? Seat.TAKEN : Seat.EMPTY; | |
} else { | |
return (numAdjacentSeats >= 5) ? Seat.EMPTY : Seat.TAKEN; | |
} | |
} | |
/** | |
* @param {Room} room | |
* @returns {Sig} signature of this stage | |
*/ | |
function simulateStage(room) { | |
room.iteration++; | |
const oldSeats = room.seats; | |
const newSeats = oldSeats.map(row => Array.from(row)); | |
room.seats = newSeats; | |
for (let y = 0; y < newSeats.length; y++) { | |
const row = newSeats[y]; | |
for (let x = 0; x < row.length; x++) { | |
row[x] = getNextSeat(oldSeats, x, y); | |
} | |
} | |
const newSig = computeSig(newSeats); | |
room.trace.push(newSig); | |
return newSig; | |
} | |
/** | |
* @param {Room} room | |
*/ | |
function simulateToStable(room) { | |
let lastSig = null; | |
// loop until this trace shows up more than once | |
while (room.trace.filter(s => s === lastSig).length < 2) { | |
lastSig = simulateStage(room); | |
console.log(`Iteration ${ room.iteration }: sig=${ lastSig }`); | |
} | |
} | |
/** | |
* @param {Seat[][]} seats | |
* @returns {number} | |
*/ | |
function countOccupiedSeats(seats) { | |
let count = 0; | |
for (let y = 0; y < seats.length; y++) { | |
const row = seats[y]; | |
for (let x = 0; x < row.length; x++) { | |
if (row[x] === Seat.TAKEN) { | |
count++; | |
} | |
} | |
} | |
return count; | |
} | |
// Part 2 | |
const part2Room = createFreshRoom(); | |
simulateToStable(part2Room); | |
const part2Occupied = countOccupiedSeats(part2Room.seats); | |
console.log(`Part 2: Reached stability after ${ part2Room.iteration } rounds. Found ${ part2Occupied } occupied seats.`); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment