Advent-of-Code-2022/23/solution.mjs

201 lines
4.5 KiB
JavaScript
Raw Normal View History

2023-01-01 10:44:41 -08:00
import { readFileSync } from 'node:fs';
const input = readFileSync('input', 'utf-8');
export class InfiniteGrid {
constructor() {
this.ox = null;
this.oy = null;
this.rows = [];
}
get height() {
return this.rows.length;
}
get width() {
return this.rows[0]?.length ?? 0;
}
get(x, y) {
return this.rows[y - (this.oy ?? y)]?.[x - (this.ox ?? x)];
}
set(x, y, v) {
this.expandToFit(x, y);
const ax = x - this.ox;
const ay = y - this.oy;
this.rows[ay][ax] = v;
}
expandToFit(x, y) {
if (this.ox === null) this.ox = x;
if (this.oy === null) this.oy = y;
const left = Math.max(0, this.ox - x);
const up = Math.max(0, this.oy - y);
const right = Math.max(0, (x - this.ox + 1) - this.width);
const down = Math.max(0, (y - this.oy + 1) - this.height);
if (up || right || down || left) {
this.rows = [
...new Array(up).fill(new Array(this.width + left + right).fill()),
...this.rows.map(row =>
[...new Array(left), ...row, ...new Array(right)]
),
...new Array(down).fill(new Array(this.width + left + right).fill())
];
}
this.ox -= left;
this.oy -= up;
}
getBounds() {
return {
minX: this.ox,
maxX: this.ox + (this.rows[0]?.length ?? 1) - 1,
minY: this.oy,
maxY: this.oy + (this.rows.length || 1) - 1
};
}
printDefined() {
console.log(this.rows
.map(row => row
.map(cell => cell ? '#' : '.')
.join('')
)
.join('\n')
);
}
countEmpty() {
return this.rows.reduce(
(total, row) => total + row.reduce(
(rowTotal, cell) => rowTotal + Number(!cell),
0
),
0
);
}
}
class Elf {
constructor(grid) {
this.grid = grid;
this.pos = undefined;
}
moveTo(x, y) {
if (this.pos) {
this.grid.set(...this.pos, undefined);
}
this.pos = [x, y];
this.grid.set(...this.pos, this);
}
proposePosition(directions) {
if (!this.pos) throw new Error('Elf is not on the grid');
const [x, y] = this.pos;
const N = this.grid.get(x, y - 1);
const NE = this.grid.get(x + 1, y - 1);
const E = this.grid.get(x + 1, y);
const SE = this.grid.get(x + 1, y + 1);
const S = this.grid.get(x, y + 1);
const SW = this.grid.get(x - 1, y + 1);
const W = this.grid.get(x - 1, y);
const NW = this.grid.get(x - 1, y - 1);
if (!(N || NE || E || SE || S || SW || W || NW)) {
// Don't move
return;
}
for (const direction of directions) {
if (direction === 'N' && !(NW || N || NE)) {
return [x, y - 1];
} else if (direction === 'E' && !(NE || E || SE)) {
return [x + 1, y];
} else if (direction === 'S' && !(SW || S || SE)) {
return [x, y + 1];
} else if (direction === 'W' && !(NW || W || SW)) {
return [x - 1, y];
}
}
}
}
const elfPositions = input
.split('\n')
.map(line => line.trim())
.filter(line => !!line)
.flatMap((line, y) => line
.split('')
.map((c, x) => {
if (c === '#') {
return [x, y];
}
})
.filter(coords => !!coords)
);
const grid = new InfiniteGrid();
const elves = [];
for (const pos of elfPositions) {
const elf = new Elf(grid);
elves.push(elf);
elf.moveTo(...pos);
}
const directions = ['N', 'S', 'W', 'E'];
let round = 0;
function processRound() {
const propositions = elves.flatMap(elf => {
const pos = elf.proposePosition(directions);
if (!pos) return [];
return [{elf, x: pos[0], y: pos[1]}];
});
propositions.sort((p1, p2) => (p1.x - p2.x) || (p1.y - p2.y));
const validPropositions = propositions
.reduce((groups, proposition) => {
if (groups.length) {
const lastGroup = groups[groups.length - 1];
const {x: lastX, y: lastY} = lastGroup[0];
if (proposition.x === lastX && proposition.y === lastY) {
lastGroup.push(proposition);
return groups;
}
}
groups.push([proposition]);
return groups;
}, [])
.filter(group => group.length === 1)
.flat();
const moved = validPropositions.length;
for (const proposition of validPropositions) {
proposition.elf.moveTo(proposition.x, proposition.y);
}
// Reorder directions
directions.push(directions.shift());
round++;
return moved;
}
do {
processRound();
} while (round <= 10)
console.log('The number of empty ground tiles is:', grid.countEmpty());
while (processRound() !== 0) {};
console.log('The number of rounds to completion is:', round);