type InputData = {
robots: Array<Robot>,
};
type Robot = {
position: Coordinate,
velocity: Coordinate,
};
type Coordinate = {
x: number,
y: number,
}
type Size = {
width: number,
height: number,
};
const solution: Fn = async ({ robots: data }, size) => {
const robots = copy(data);
const newState: Array<Robot | null> = [];
for (let i = 0; i < robots.length; i++) {
newState.push(null);
}
const iteration: number = 100;
const tasks = [];
for (let i = 0; i < robots.length; i++) {
const robot = robots[i];
tasks.push(setNextStateAsync(robot, size, iteration)
.then(state => newState[i] = state)
);
}
await Promise.all(tasks);
const finalState = newState.map(robot => robot!);
const safetyFactor = calculateSafetyFactor(finalState, size);
return safetyFactor;
};
const setNextStateAsync: SetAsyncFn = (robot, mapSize, iteration = 1) => {
const promise = new Promise<Robot>((resolve) => {
setTimeout(() => {
setNextState(robot, mapSize, iteration);
resolve(robot);
});
});
return promise;
};
const setNextState: SetFn = (robot, mapSize, iteration = 1) => {
for (let i = 0; i < iteration; i++) {
move(robot, mapSize);
}
};
const move: MoveFn = (robot, mapSize) => {
robot.position.x += robot.velocity.x;
robot.position.y += robot.velocity.y;
if (robot.position.x < 0) {
const diff = Math.abs(robot.position.x);
robot.position.x = mapSize.width - diff;
}
if (robot.position.x >= mapSize.width) {
robot.position.x = robot.position.x - mapSize.width;
}
if (robot.position.y < 0) {
const diff = Math.abs(robot.position.y);
robot.position.y = mapSize.height - diff;
}
if (robot.position.y >= mapSize.height) {
robot.position.y = robot.position.y - mapSize.height;
}
};
const calculateSafetyFactor: CalculateFn = (robots, mapSize) => {
const quad1 = getQuadrant(robots, mapSize, 1);
const quad2 = getQuadrant(robots, mapSize, 2);
const quad3 = getQuadrant(robots, mapSize, 3);
const quad4 = getQuadrant(robots, mapSize, 4);
return quad1.length * quad2.length * quad3.length * quad4.length;
}
const getQuadrant: getFn = (robots, mapSize, quadrant) => {
const quad = [];
const midx = Math.floor(mapSize.width / 2);
const midy = Math.floor(mapSize.height / 2);
for (const robot of robots) {
if (quadrant === 1 && robot.position.x < midx && robot.position.y < midy) {
quad.push(robot);
} else if (quadrant === 2 && robot.position.x > midx && robot.position.y < midy) {
quad.push(robot);
} else if (quadrant === 3 && robot.position.x < midx && robot.position.y > midy) {
quad.push(robot);
} else if (quadrant === 4 && robot.position.x > midx && robot.position.y > midy) {
quad.push(robot);
}
}
return quad;
};
const solution: Fn = async ({ robots: data }, mapSize) => {
const promise = new Promise<number>((resolve, reject) => {
setTimeout(() => {
const robots = copy(data);
let iteration: number = 0;
// may need to be adjusted
const maxIterations = 100000;
while (true) {
const newState: Array<Robot> = [];
for (const robot of robots) {
// check part 1 for setNextState definition
setNextState(robot, mapSize);
newState.push(robot);
}
iteration++;
if (iteration > maxIterations) { break; }
const map = plotRobots(newState, mapSize);
if (checkPattern(map)) { return resolve(iteration); }
}
return reject();
});
});
return promise;
};
const plotRobots: PlotFn = (robots, mapSize) => {
const map: Array<Array<string>> = [];
for (let y = 0; y < mapSize.height; y++) {
map[y] = [];
for (let x = 0; x < mapSize.width; x++) {
map[y][x] = '.';
}
}
for (const robot of robots) {
map[robot.position.y][robot.position.x] = 'X';
}
return map;
};
const checkPattern: CheckFn = (map)=> {
// the Christmas tree pattern was found by manually inspecting the logs
// when using the print function (see the print function below)
// initially, blindly logging the state of the map every iteration
// revealed a somewhat emerging pattern on iteration 9 and 65
// the "pattern" on iteration 9 repeats every 101 iterations
// while the "pattern" on iteration 65 repeats every 103 iterations
// logging the state of the map on these iterations helped reduce the noise
// which eventually led to a state where the Christmas tree pattern appeared
// the pattern was then coded into this solution to be used
let topBorderY = -1;
let topBorderX = -1;
for (let i = 0; i < map.length - pattern.length; i++) {
const row = map[i];
const line = row.join('');
topBorderX = line.indexOf(pattern[0]);
if (topBorderX !== -1) {
topBorderY = i;
break;
}
}
if (topBorderX === -1) { return false; }
for (let y = 0; y < pattern.length; y++) {
const row = map[topBorderY + y];
const line = row.slice(topBorderX, topBorderX + pattern[y].length).join('');
if (line !== pattern[y]) {
return false;
}
}
return true;
};
const pattern = [
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"X.............................X",
"X.............................X",
"X.............................X",
"X.............................X",
"X..............X..............X",
"X.............XXX.............X",
"X............XXXXX............X",
"X...........XXXXXXX...........X",
"X..........XXXXXXXXX..........X",
"X............XXXXX............X",
"X...........XXXXXXX...........X",
"X..........XXXXXXXXX..........X",
"X.........XXXXXXXXXXX.........X",
"X........XXXXXXXXXXXXX........X",
"X..........XXXXXXXXX..........X",
"X.........XXXXXXXXXXX.........X",
"X........XXXXXXXXXXXXX........X",
"X.......XXXXXXXXXXXXXXX.......X",
"X......XXXXXXXXXXXXXXXXX......X",
"X........XXXXXXXXXXXXX........X",
"X.......XXXXXXXXXXXXXXX.......X",
"X......XXXXXXXXXXXXXXXXX......X",
"X.....XXXXXXXXXXXXXXXXXXX.....X",
"X....XXXXXXXXXXXXXXXXXXXXX....X",
"X.............XXX.............X",
"X.............XXX.............X",
"X.............XXX.............X",
"X.............................X",
"X.............................X",
"X.............................X",
"X.............................X",
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
];