Ben Ashton
1 year ago
2 changed files with 271 additions and 0 deletions
@ -0,0 +1,71 @@ |
|||||||
|
#.##..##.#..#.##..##....##..#..#####...#.###.#..##..#..#.#...##..##.... |
||||||
|
##..###.#.#.#..##.##.####.........#...#..#..#.#..#.#....##..###.#.##..# |
||||||
|
#.......###....#.#.##.#.#.#.##..###...###..##.....#...#.##.##.......##. |
||||||
|
...##.#......#.#.#..#.###..#.###....#....#.###.##....####....##....##.. |
||||||
|
.#...##..###...##.#.#..#...####.#.##.#.#..#.##.###.###.#.##.##..#.#...# |
||||||
|
#..##....##.#..##.##...###..##.#...##...#####...#####.##..#.###....#.#. |
||||||
|
###....#....#.##.####.###.#..####..##..##..#..#.##.#######.##....###... |
||||||
|
###..###.....###..##..##....#......#.#.##...####.######..###.###....### |
||||||
|
#.#.###.###.....###.####.#.####...#.#.###.#...#.##..##.##.#..####..#... |
||||||
|
####.##...#....##.##.#.#....##.###....#.#..#.....##...##.#...###.##.#.# |
||||||
|
...#.#..####.###...###.#....###...##.####..#.######.##.##..#.#..##...#. |
||||||
|
#####......#.####.#####...#...#..#..#.#.#.#.#.#.#.#.##.#..#.###.#....#. |
||||||
|
#..#.#.#..#...#.#.##..#.######.##..#.###...#.#.#...#.####..##..##..#.#. |
||||||
|
..#.#..###....#.##.##....####.#########..#.########..##....#..#..##..#. |
||||||
|
.#.#.#...##...##.#.#..#..#####..###....#..#...####.##.####.###.##..##.. |
||||||
|
.##.####..#.#######..#.#######..##..###....#.#..#..##.#..#.#..###.....# |
||||||
|
#....#.#####..#.##...###.###....#.#.#.#..#.##.####...#...##..#####.##.. |
||||||
|
...##.###.##...#.##.###.#.###....####.....#.##.##.##.###...###.###.#.## |
||||||
|
..###...#...##....#.###.....##..####..#.###.####..##.###....##.#....##. |
||||||
|
...#..##..#..##.###.#....###....#..##..#...##.#..##.#.####...##.##..### |
||||||
|
######.......####..#..#.#####.......#####..#.#..##..#......#.#.#.#####. |
||||||
|
.#...#..#.##.###..#.#.#..####.#.########.#.###..####.#..#.#..###.#####. |
||||||
|
...####.#..#..#.##...#.#####.########.##..##....#####.#.##..######.#.## |
||||||
|
.....#..##.#.....#..#.#.##..######..####.#..#....##.....###.#.#..##..#. |
||||||
|
..##.##..##.#.#.##...##.##...#..##.....#..#..#.#.#.....#.#.##.###...### |
||||||
|
##.#..#.#.####.##....#...##.#...#...#..#.##..#...##.....#.#.##.#####... |
||||||
|
...#..##..##...#..###....#..#.###..####...#.#..##.###.##......###.#.#.# |
||||||
|
..##.#.#.##..###.....#..##.#....#....#....#.##..#.#.####.#....##..#..## |
||||||
|
.##..##..#.#.....###..#.#..#.##.#.#.#..#..#.##.#.###...#..#..##.#....## |
||||||
|
......#...######....###.##..#.###.####.##.##...#.###...####.##..#..#... |
||||||
|
#..##.#.###.##......####...#.#.##.#.#....##.##..#.#.....####...#..###.. |
||||||
|
..##...#.##..#.....#######.#.#.###.#.##...####..#.#....##.###..##.#.### |
||||||
|
...####..#..##.########..#####.##..##...#..##..##..#...###.###....#.### |
||||||
|
.###.#..#.##.#..###.#.##.#.#...#.#.#.##..##.#....###.#..#.....#..##..#. |
||||||
|
...##.##...#..#.####.....#.##....####.#.###..##...#..#.####...##..##.## |
||||||
|
###...##....##..##.#...##......#...##.##.#.....##..##.#..#.##......#..# |
||||||
|
.......#.######...#####....#...#####.#####...##..#..#.#.#..........#.#. |
||||||
|
#######.#...#.#..#..#..##..#.####..#.##..###..###.##.#...#.#..#.####.## |
||||||
|
.#..#......#.#....###.#..##..####.#..#.#..##..#####.#....##.#####.#.#.# |
||||||
|
.#.###.###..#..#.#.#.#..##.#......###.##.#....#######.#..#.#####.....## |
||||||
|
#.####..###.#..###.#.#...##.##....###.#.#.#.###..#...#...#.##.###...#.. |
||||||
|
#..##..##.##..##....###..######....#.#####.##..#...#....#.#.###.###.... |
||||||
|
#.###..##.##..#.....##...#..#..#####.#.##..##..#.#...#.....##.#..#.##.# |
||||||
|
##...#...#.##..####..#..#.###....#...#.....#.#.#.###...###...#####.#.## |
||||||
|
.#.###.....#.#..#.###########.######..#########.##.##..#.#.#...#..##.## |
||||||
|
##.#################.#.##.##....####.....#..#.#.##.###.##..#..#.##..#.# |
||||||
|
.##.####.#.#..##.##.#.#..#.###.#..#..#.####....#.###.####.#.#..##..#### |
||||||
|
##....###.#.####.#.#.............##.....###.##.#.###.####...#####.##.#. |
||||||
|
...##.#.#.#####.####.##.#..##.###.###.####.#.#.....#.#.#...#####..####. |
||||||
|
######.#....#...#.#.######.#.#....#.##..#..#..#..#.......#..#.##.#.#... |
||||||
|
...#..#....#.#.#...###.####..##.#.#..#.#.#.####.#..###...###.######...# |
||||||
|
#.#.###......##...#...##....###......#.###...#...###..#..####..##...#.# |
||||||
|
#..##.....#........###.##..#.#.#.#.##..#.##.#..###.##.#.####..###.##.#. |
||||||
|
.##....#..##.###.#.#.#..#..###..#..#####.##....#..#####.#.##..#.##.###. |
||||||
|
.#.##.###.####.#.#..#.###.###.#.......####.##...#..##.#.##.##..#.###... |
||||||
|
#.##...#####.#...#.#.##..###.#.##.###########..#...#..#.#.###.#.##..#.# |
||||||
|
##...#...#.###.#..##..#...##....#...#.#....###.###.#.#..#...#.#.#.#.### |
||||||
|
#.#.######.##.#....#.....#..##...#.#####...##.##...#..###.#...##.#..##. |
||||||
|
##.#######.....#....#.###..#..#.###.###..#.....####....#.####..#.....#. |
||||||
|
#..#..##..##.#.##......#....#.#.....#......#...##.###..#.#.###.......## |
||||||
|
#..#.#..######.#.......##..#...#....#####.#...#.##...#...###..####.##.. |
||||||
|
.....#.....#.#.##..###.###.##..#.##.##..#.#####..##.##..###..###..#.### |
||||||
|
...#.###.#.#.#.##...##.##...#..#..#..####.#.##.#...#.#..##.#.#######.## |
||||||
|
.####.####..#.####...#...#.#..##...##.##..#....#.#.###.###..#####...##. |
||||||
|
..##.#.###.####.#####...#......#.#.###...###..##.##.###.####.###.#..#.# |
||||||
|
#.##..####.#.#..#.##..#.##.#.##.#..###.####.#.#.#.##..#.....##.#.#...#. |
||||||
|
.##...#.#.#####.###.#...###.#......#####.....#.#######.##.#..##...###.. |
||||||
|
##.#.#####......####.####...#####....#..####.#.#.####...####.###.###.## |
||||||
|
#.#.#.####..##.##.##.#.##.#.#####.##...#....##..##...#.######.....#..## |
||||||
|
.#.....#...###.#.#.###.#..#...###...##.......####...####.###..#....#... |
||||||
|
..##.##..#......###...##.....##.#..#...###.##.#.##.##...#.#...#.#..#... |
@ -0,0 +1,200 @@ |
|||||||
|
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); |
Loading…
Reference in new issue