Navigate the ship by following the instructions. What is the Manhattan distance between the ship's final and starting locations?
const input = `
F10
N3
F7
R90
F11
`.trim()
const Dir = {
E: 0,
S: 90,
W: 180,
N: 270,
}
const result = input
.split('\n')
.map((line) => line.match(/(?<action>\w)(?<value>\d+)/).groups)
.map(({ action, value }) => ({ action, value: +value }))
.reduce(
(pos, { action, value }) => {
if (action === 'E') return { ...pos, x: pos.x + value }
if (action === 'S') return { ...pos, y: pos.y + value }
if (action === 'W') return { ...pos, x: pos.x - value }
if (action === 'N') return { ...pos, y: pos.y - value }
if (action === 'R') {
return { ...pos, dir: (pos.dir + value) % 360 }
}
if (action === 'L') {
return { ...pos, dir: (pos.dir + (360 - value)) % 360 }
}
if (action === 'F') {
if (pos.dir === Dir.E) return { ...pos, x: pos.x + value }
if (pos.dir === Dir.S) return { ...pos, y: pos.y + value }
if (pos.dir === Dir.W) return { ...pos, x: pos.x - value }
if (pos.dir === Dir.N) return { ...pos, y: pos.y - value }
throw new Error('Invalid current direction: ' + pos.dir)
}
throw new Error('Invalid action: ' + action + '; value: ' + value)
},
{ dir: Dir.E, x: 0, y: 0 }
)
console.log(result)
console.log(Math.abs(result.x) + Math.abs(result.y))
The navigation instructions are to be interpreted slightly differently. What is the Manhattan distance now?
const result = input
.split('\n')
.map((line) => line.match(/(?<action>\w)(?<value>\d+)/).groups)
.map(({ action, value }) => ({ action, value: +value }))
.reduce(
(acc, { action, value }) => {
const { ship, waypoint } = acc
if (action === 'E') {
return {
ship,
waypoint: {
x: waypoint.x + value,
y: waypoint.y,
},
}
}
if (action === 'S') {
return {
ship,
waypoint: {
x: waypoint.x,
y: waypoint.y + value,
},
}
}
if (action === 'W') {
return {
ship,
waypoint: {
x: waypoint.x - value,
y: waypoint.y,
},
}
}
if (action === 'N') {
return {
ship,
waypoint: {
x: waypoint.x,
y: waypoint.y - value,
},
}
}
if (
(action === 'R' && value === 90) ||
(action === 'L' && value === 270)
) {
return {
ship,
waypoint: {
x: -waypoint.y,
y: waypoint.x,
},
}
}
if ((action === 'R' || action === 'L') && value === 180) {
return {
ship,
waypoint: {
x: -waypoint.x,
y: -waypoint.y,
},
}
}
if (
(action === 'R' && value === 270) ||
(action === 'L' && value === 90)
) {
return {
ship,
waypoint: {
x: waypoint.y,
y: -waypoint.x,
},
}
}
if (action === 'F') {
return {
ship: {
x: ship.x + waypoint.x * value,
y: ship.y + waypoint.y * value,
},
waypoint,
}
}
throw new Error('Invalid action: ' + action + '; value: ' + value)
},
{ ship: { x: 0, y: 0 }, waypoint: { x: 10, y: -1 } }
)
console.log(result)
console.log(Math.abs(result.ship.x) + Math.abs(result.ship.y))
The code works,
but the reducer's return
statements are annoyingly verbose.
It's also easy to forget something from the return
s,
leading to annoying bugs.
That's why I made another version using
Mergerino.
Now the code is less verbose and the return
s are simpler,
so it's easier to see what's actually being changed:
const merge = mergerino
const result = input
.split('\n')
.map((line) => line.match(/(?<action>\w)(?<value>\d+)/).groups)
.map(({ action, value }) => ({ action, value: +value }))
.reduce(
(acc, { action, value }) => {
const { ship, waypoint } = acc
if (action === 'E') {
return merge(acc, { waypoint: { x: waypoint.x + value } })
}
if (action === 'S') {
return merge(acc, { waypoint: { y: waypoint.y + value } })
}
if (action === 'W') {
return merge(acc, { waypoint: { x: waypoint.x - value } })
}
if (action === 'N') {
return merge(acc, { waypoint: { y: waypoint.y - value } })
}
if (
(action === 'R' && value === 90) ||
(action === 'L' && value === 270)
) {
return merge(acc, {
waypoint: ({ x, y }) => ({ x: -y, y: x }),
})
}
if ((action === 'R' || action === 'L') && value === 180) {
return merge(acc, {
waypoint: ({ x, y }) => ({ x: -x, y: -y }),
})
}
if (
(action === 'R' && value === 270) ||
(action === 'L' && value === 90)
) {
return merge(acc, {
waypoint: ({ x, y }) => ({ x: y, y: -x }),
})
}
if (action === 'F') {
return merge(acc, {
ship: {
x: ship.x + waypoint.x * value,
y: ship.y + waypoint.y * value,
},
})
}
throw new Error('Invalid action: ' + action + '; value: ' + value)
},
{ ship: { x: 0, y: 0 }, waypoint: { x: 10, y: -1 } }
)
console.log(result)
console.log(Math.abs(result.ship.x) + Math.abs(result.ship.y))
Another option would have been to use Immer. I find Mergerino and Immer quite similar, at least for simple use cases like this puzzle.
In this case, I prefer Mergerino, because Mergerino's minzipped size is just 415 bytes, whereas Immer's minzipped size is 5.4 kilobytes. The latter might be acceptable in a larger project where you utilize Immer more heavily. The former is so tiny that I feel okay using it even in a tiny puzzle like this.
Nothing, but I'm content that I got to use Mergerino. I like it!