type InputData = {
map: Array<Array<SymbolType>>,
initial: Coordinates,
instructions: Array<Direction>,
};
enum SymbolType {
WALL = '#',
EMPTY = '.',
BOX = 'O',
ROBOT = '@',
BOX_START = '[',
BOX_END = ']',
};
type Coordinates = {
x: number,
y: number,
};
enum Direction {
UP = '^',
DOWN = 'v',
LEFT = '<',
RIGHT = '>',
};
const solution: Fn = async ({ map: data, initial, instructions }) => {
const promise = new Promise<number>((resolve) => {
setTimeout(() => {
const map = copyMap(data);
let current = initial;
for (const direction of instructions) {
const next = getNext(current, direction, map);
if (!next) { return false; }
if (isBox(next, map)) {
if (!canPush(next, direction, map)) { continue; }
if (!moveBox(next, direction, map)) { continue; }
}
if (!moveRobot(current, direction, map)) { continue; }
current = next;
}
const total = calculate(map);
resolve(total);
});
});
return promise;
};
const isBox: CheckFn = (location, map) => {
if (!isValidCoordinate(location, map)) { return false; }
const { x, y } = location;
return map[y][x] === SymbolType.BOX
|| map[y][x] === SymbolType.BOX_START
|| map[y][x] === SymbolType.BOX_END;
}
const canPush: PushFn = (initial, direction, map) => {
if (!isValidCoordinate(initial, map)) { return false; }
if (!isBox(initial, map)) { return false; }
const next = getNext(initial, direction, map);
if (!next) { return false; }
if (isBox(next, map)) {
return canPush(next, direction, map);
}
const { x: nextX, y: nextY } = next;
return map[nextY][nextX] === SymbolType.EMPTY;
};
const getNext: GetNextFn = (initial, direction, map) => {
const { x, y } = initial;
let xOffset = 0;
let yOffset = 0;
switch(direction) {
case Direction.UP:
yOffset = y > 0 ? -1 : 0;
break;
case Direction.DOWN:
yOffset = y < map.length - 1 ? 1 : 0;
break;
case Direction.LEFT:
xOffset = x > 0 ? -1 : 0;
break;
case Direction.RIGHT:
xOffset = x < map[y].length - 1 ? 1 : 0;
break;
default:
return null;
}
if (xOffset === 0 && yOffset === 0) { return null; }
const nextX = x + xOffset;
const nextY = y + yOffset;
return { x: nextX, y: nextY };
};
const moveBox: MoveFn = (initial, direction, map) => {
if (!isValidCoordinate(initial, map)) { return false; }
if (!isBox(initial, map)) { return false; }
const next = getNext(initial, direction, map);
if (!next) { return false; }
if (isBox(next, map)) {
const moved = moveBox(next, direction, map);
if (!moved) { return false; }
}
const { x, y } = initial;
const { x: nextX, y: nextY } = next;
if (map[nextY][nextX] === SymbolType.EMPTY) {
map[nextY][nextX] = map[y][x];
map[y][x] = SymbolType.EMPTY;
return true;
}
return false;
};
const moveRobot: MoveFn = (initial, direction, map) => {
if (!isValidCoordinate(initial, map)) { return false; }
const { x, y } = initial;
if (map[y][x] !== SymbolType.ROBOT) { return false; }
const next = getNext(initial, direction, map);
if (!next) { return false; }
const { x: nextX, y: nextY } = next;
if (map[nextY][nextX] === SymbolType.EMPTY) {
map[nextY][nextX] = SymbolType.ROBOT;
map[y][x] = SymbolType.EMPTY;
return true;
}
return false;
};
const calculate: CalculateFn = (map) => {
let total = 0;
for (let y = 0; y < map.length; y++) {
for (let x = 0; x < map[y].length; x++) {
if (map[y][x] === SymbolType.BOX
|| map[y][x] === SymbolType.BOX_START) {
total += (100 * y) + x;
}
}
}
return total;
};
const solution: Fn = async ({ map: data, initial: start, instructions }) => {
const promise = new Promise<number>((resolve) => {
setTimeout(() => {
const { map, initial } = expand(data, start);
let current = initial;
for (const direction of instructions) {
// check part 1 for getNext definition
const next = getNext(current, direction, map);
if (!next) { return false; }
// check part 1 for isBox definition
if (isBox(next, map)) {
if (!canPush(next, direction, map)) { continue; }
if (!moveBox(next, direction, map)) { continue; }
}
// check part 1 for moveRobot definition
if (!moveRobot(current, direction, map)) { continue; }
current = next;
}
// check part 1 for calculate definition
const total = calculate(map);
resolve(total);
});
});
return promise;
};
const canPush: PushFn = (initial, direction, map) => {
if (direction === Direction.LEFT || direction === Direction.RIGHT) {
// check part 1 canPush for definition
return canPushSingle(initial, direction, map);
}
if (!isValidCoordinate(initial, map)) { return false; }
// check part 1 for isBox definition
if (!isBox(initial, map)) { return false; }
const { x, y } = initial;
const start = map[y][x] === SymbolType.BOX_START
? initial
: { x: x - 1, y };
const end = map[y][x] === SymbolType.BOX_START
? { x: x + 1, y }
: initial;
// check part 1 for getNext definition
const nextStart = getNext(start, direction, map);
const nextEnd = getNext(end, direction, map);
if (!nextStart || !nextEnd) { return false; }
let canPushStart = map[nextStart.y][nextStart.x] === SymbolType.EMPTY
// check part 1 for isBox definition
if (isBox(nextStart, map)) {
canPushStart = canPush(nextStart, direction, map);
}
let canPushEnd = map[nextEnd.y][nextEnd.x] === SymbolType.EMPTY
// check part 1 for isBox definition
if (isBox(nextEnd, map)) {
canPushEnd = canPush(nextEnd, direction, map);
}
return canPushStart && canPushEnd;
};
const moveBox: MoveFn = (initial, direction, map) => {
if (direction === Direction.LEFT || direction === Direction.RIGHT) {
// check part 1 moveBox for definition
return moveBoxSingle(initial, direction, map);
}
if (!isValidCoordinate(initial, map)) { return false; }
// check part 1 for isBox definition
if (!isBox(initial, map)) { return false; }
const { x, y } = initial;
const start = map[y][x] === SymbolType.BOX_START
? initial
: { x: x - 1, y };
const end = map[y][x] === SymbolType.BOX_START
? { x: x + 1, y }
: initial;
// check part 1 for getNext definition
const nextStart = getNext(start, direction, map);
const nextEnd = getNext(end, direction, map);
if (!nextStart || !nextEnd) { return false; }
const { x: nextStartX, y: nextStartY } = nextStart;
const { x: nextEndX, y: nextEndY } = nextEnd;
// check part 1 for isBox definition
const isNextStartBox = isBox(nextStart, map);
const isNextEndBox = isBox(nextEnd, map);
const nextStartSymbol = map[nextStartY][nextStartX];
const nextEndSymbol = map[nextEndY][nextEndX];
let movedStart = false;
if (isNextStartBox) {
movedStart = moveBox(nextStart, direction, map);
}
let movedEnd = nextStartSymbol === SymbolType.BOX_START
? movedStart
: false;
// skip if end symbol is a box end,
// since it would have been moved as part of moving the box start
if (isNextEndBox
&& nextEndSymbol !== SymbolType.BOX_END) {
movedEnd = moveBox(nextEnd, direction, map);
}
if (isNextStartBox && !movedStart) { return false; }
if (isNextEndBox && !movedEnd) { return false; }
if (map[nextStartY][nextStartX] !== SymbolType.EMPTY) { return false; }
if (map[nextEndY][nextEndX] !== SymbolType.EMPTY) { return false; }
const { x: startX, y: startY } = start;
map[nextStartY][nextStartX] = SymbolType.BOX_START;
map[startY][startX] = SymbolType.EMPTY;
const { x: endX, y: endY } = end;
map[nextEndY][nextEndX] = SymbolType.BOX_END;
map[endY][endX] = SymbolType.EMPTY
return true;
};
const expand: ExpandFn = (map, initial) => {
const copy = copyMap(map);
const expanded: Array<Array<SymbolType>> = [];
for (let y = 0; y < copy.length; y++) {
const row = [];
for (let x = 0; x < copy[y].length; x++) {
const symbol1 = copy[y][x] === SymbolType.BOX
? SymbolType.BOX_START
: copy[y][x];
row.push(symbol1);
let symbol2: SymbolType;
switch (copy[y][x]) {
case SymbolType.BOX:
symbol2 = SymbolType.BOX_END;
break;
case SymbolType.ROBOT:
symbol2 = SymbolType.EMPTY;
break;
default:
symbol2 = copy[y][x];
break;
}
row.push(symbol2);
}
expanded.push(row);
}
return {
map: expanded,
initial: { x: initial.x * 2, y: initial.y },
};
};