Advent of Code 2020 Day 12 - Solution + Tutorial (TypeScript)
- [ 1 ]
- [ 2 ]
- [ 3 ]
- [ 4 ]
- [ 5 ]
- [ 6 ]
- [ 7 ]
- [ 8 ]
- [ 9 ]
- [ 10 ]
- [ 11 ]
- [ 12 ]
- [ 13 ]
- [ 14 ]
- [ 15 ]
- [ 16 ]
- [ 17 ]
- [ 18 ]
I'll try to update asap. Please bear in a mind that I'll probably lag one or multiple days behind.
Prerequisites
I assume you've put your puzzle input into an array called lines
where each array item is a line of the input text file.
It's up to you to either parse the text file or create an array by hand.
const lines = [
"N2",
"L180",
"S5",
"L90",
"E3",
…
];
Solution
Preface
Starting with Day 10, I'll just publish my solution for both parts without explaining every single step. Unfortunately, I can't continue providing full step-by-step tutorials for each day. The concepts used get more difficult day by day. So, I've concluded that it's better if I wrote separate blog posts about these concepts later.
Also, it's holiday season. This makes it much more difficult creating well-thought-out tutorials. However, I'll try to annotate my code examples a little. This way, you might understand what I've done.
I will now move on to sharing useful tips for web developers regularly. These should help you become a better developer. Also, the shared tips should help with solving problems like those we encounter in Advent of Code. Here's my first post: 14 Awesome JavaScript Array Tips You Should Know About
Puzzle
Just to make sure, you know what I'm talking about, take a look at today's puzzle:
Part 1
Today, we'll move a ship according to the instructions (our puzzle input). It's possible to move in every compass direction, to turn left or right, and to move forward. Moving forward means to move in the direction the ship is currently facing.
This puzzle can be solved in many ways. In my solution, I've created a Ship
class that encapsulates
the ship's behavior. Also, I've initialized a variable named compass
to help with turning. It has
the degrees as keys, and the compass direction as values.
A Ship
object has a current position and degrees, that are telling us which direction the ship is facing.
Depending on the move direction and value, the ship's position and degrees will change. This is implemented
with a switch
-statement. The position is thought as a coordinate system having an x
and y
value.
For example, moving north increases the y
value (position[1]
), and moving west decreases
the x
value (position[0]
). If we want to move forward, we check our current degrees, and our compass
to determine the correct compass direction.
With that done, we just have to parse each line and use the Ship#move
method. After that, retrieve
the x
and y
value from the ship's position and return the sum of their absolute values. Done!
Here's the full solution:
const compass = {
0: "N",
90: "E",
180: "S",
270: "W",
};
class Ship {
protected _degrees: keyof typeof compass;
protected _position: [x: number, y: number];
public constructor() {
this._degrees = 90;
this._position = [0, 0];
}
public move(direction: string, value: number) {
switch (direction) {
case "N":
this._position[1] += value;
break;
case "E":
this._position[0] += value;
break;
case "S":
this._position[1] -= value;
break;
case "W":
this._position[0] -= value;
break;
case "F":
this.move(compass[this._degrees], value);
break;
case "L":
this._degrees -= value;
if (this._degrees < 0) this._degrees += 360;
break;
case "R":
this._degrees += value;
if (this._degrees >= 360) this._degrees -= 360;
break;
}
}
public get position(): [x: number, y: number] {
return [...this._position];
}
}
const ship = new Ship();
lines.forEach((line) => {
const action = line.substr(0, 1);
const value = parseInt(line.substr(1));
ship.move(action, value);
});
const [x, y] = ship.position;
return Math.abs(x) + Math.abs(y);
Part 2
Now, part 2 changes the rules a bit. This time our ship orients itself to a waypoint placed relative to the ship.
Therefore, we have to modify our Ship
class. Again, I see the whole thing as a coordinate system.
We don't need our compass
variable from part 1 anymore, but we'll add a waypoint
property to our Ship
class.
Thus, we can adjust the ship's position, and the waypoint position.
This time, moving in a compass direction means moving the waypoint
. We do so like we moved the ship in part 1.
However, moving forward means moving the ship to the waypoint multiple times. Therefore, we'll add
multiples of the waypoint's coordinates to our ship's position. Also, turning left and right has changed a bit.
We'll have to move our waypoint accordingly.
After implementing all of this, we can reuse some more code from part 1. The parsing of the lines and summing up the absolute values of the position stays the same. Then, we've solved the puzzle.
For completeness, here's the full solution:
class Ship {
protected _position: [x: number, y: number];
protected _waypoint: [x: number, y: number];
public constructor() {
this._position = [0, 0];
this._waypoint = [10, 1];
}
public move(direction: string, value: number) {
switch (direction) {
case "N":
this._waypoint[1] += value;
break;
case "E":
this._waypoint[0] += value;
break;
case "S":
this._waypoint[1] -= value;
break;
case "W":
this._waypoint[0] -= value;
break;
case "F":
this._position[0] += this._waypoint[0] * value;
this._position[1] += this._waypoint[1] * value;
break;
case "L":
switch (value) {
case 90:
this._waypoint.reverse();
this._waypoint[0] *= -1;
break;
case 180:
this._waypoint[0] *= -1;
this._waypoint[1] *= -1;
break;
case 270:
this._waypoint.reverse();
this._waypoint[1] *= -1;
break;
}
break;
case "R":
switch (value) {
case 90:
this._waypoint.reverse();
this._waypoint[1] *= -1;
break;
case 180:
this._waypoint[0] *= -1;
this._waypoint[1] *= -1;
break;
case 270:
this._waypoint.reverse();
this._waypoint[0] *= -1;
break;
}
break;
}
}
public get position(): [x: number, y: number] {
return [...this._position];
}
}
const ship = new Ship();
lines.forEach((line) => {
const action = line.substr(0, 1);
const value = parseInt(line.substr(1));
ship.move(action, value);
});
const [x, y] = ship.position;
return Math.abs(x) + Math.abs(y);
Conclusion
Today's puzzle somehow reminded me of the game Battleship (in Germany known as Schiffe versenken).
Using the Ship
class to encapsulate the behavior of the ship made the code much cleaner, in my opinion.
All in all, the puzzle felt easier than the days before. Depending on your implementation for part 1
this may differ: For me, part 2 was solved very quickly, because I only had to do some small changes.
Thanks a lot for reading this post. Please consider sharing it with your friends and colleagues. See you tomorrow!