201 lines
4.5 KiB
JavaScript
201 lines
4.5 KiB
JavaScript
|
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);
|