2022 Advent Of Code (day9) - Rope Bridge
Recently, I’ve been observing problems to see if they look too complicated before deciding whether to start coding. I really need to change this bad habit QQ.
Part 1
After reading the problem description, the more challenging part is determining how to move the tail and whether the tail is adjacent. I opted to use a simple 2D vector for this.
function isNeighbor(a, b) {
return (
(a.x === b.x ||
a.x - 1 === b.x ||
a.x + 1 === b.x) &&
(a.y === b.y || a.y - 1 === b.y || a.y + 1 === b.y)
);
}
Note: After implementing this, I realized I could just calculate the distance between the two points, eliminating the need for individual comparisons.
Input Handling
The input is relatively straightforward, essentially mapping movements in each direction to vectors:
U: (0, 1)
D: (0, -1)
R: (1, 0)
L: (-1, 0)
Tail Movement Logic
When the head of the rope moves, the tail will also move if it is not adjacent. We can use the dot product to calculate the angle and determine how the tail should move. By calculating the angle with the x unit vector and the y unit vector, we can ascertain the tail’s movement direction.
if (!this.head.isNeighbor(this.tail)) {
const vec = this.head.vector(this.tail);
const degree = vec.product(new Point(1, 0)) / vec.length();
const degree2 = vec.product(new Point(0, 1)) / vec.length();
if (degree === 0) {
if (degree2 > 0) {
this.tail.add(0, 1);
} else {
this.tail.add(0, -1);
}
} else if (Math.abs(degree) === 1) {
if (degree === 1) {
this.tail.add(1, 0);
} else {
this.tail.add(-1, 0);
}
} else {
this.tail.add(degree > 0 ? 1 : -1, degree2 > 0 ? 1 : -1);
}
}
The task asks which points the tail has visited, which can be easily accomplished using a Set. The complete implementation is as follows:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
isNeighbor(point) {
const vec = new Point(point.x - this.x, point.y - this.y);
return vec.length() <= 1;
}
add(x, y) {
this.x += x;
this.y += y;
}
vector(point) {
return new Point(this.x - point.x, this.y - point.y);
}
length() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
product(point) {
return point.x * this.x + point.y * this.y;
}
}
class Rope {
constructor(isFirst = false) {
this.isFirst = isFirst;
this.head = new Point(0, 0);
this.tail = new Point(0, 0);
}
move(direction) {
if (this.isFirst) {
switch (direction) {
case "U":
this.head.add(0, 1);
break;
case "D":
this.head.add(0, -1);
break;
case "L":
this.head.add(-1, 0);
break;
case "R":
this.head.add(1, 0);
break;
}
}
if (!this.head.isNeighbor(this.tail)) {
// calculate vector
// move to direction
const vec = this.head.vector(this.tail);
const degree = vec.product(new Point(1, 0)) / vec.length();
const degree2 = vec.product(new Point(0, 1)) / vec.length();
if (degree === 0) {
// horizontal, vertical
if (degree2 > 0) {
this.tail.add(0, 1);
} else {
this.tail.add(0, -1);
}
} else if (Math.abs(degree) === 1) {
if (degree === 1) {
this.tail.add(1, 0);
} else {
this.tail.add(-1, 0);
}
} else {
this.tail.add(degree > 0 ? 1 : -1, degree2 > 0 ? 1 : -1);
}
}
}
}
I was curious if there might be any hidden surprises in the problem, specifically if the shape created by the tail would form any specific image, so I plotted the rope’s coordinates as well.

However, it seems there isn’t anything particularly special.
Part 2
Initially, we only needed to consider the head and the tail, but now that the length of the rope is 10, the movement dynamics will change a bit. Nevertheless, the overall logic remains quite similar; we can achieve this by connecting the original Rope’s head and tail together.
const ropes = [
new Rope(true),
new Rope(),
new Rope(),
new Rope(),
new Rope(),
new Rope(),
new Rope(),
new Rope(),
new Rope(),
];
const set = new Set();
direction.forEach((dir, ii) => {
const [d, step] = dir.split(" ");
for (let i = 0; i < Number(step); i++) {
ropes.forEach((rope, j) => {
if (j === 0) {
rope.move(d);
} else {
rope.head = ropes[j - 1].tail;
rope.move(d);
}
});
set.add(
`${ropes[ropes.length - 1].tail.x} ${ropes[ropes.length - 1].tail.y}`
);
}
}); Related Posts
- When Measurement Becomes the Goal: From Window Tax to PR Counts I once wrote a small tool to count how many PRs I had contributed in a quarter, how many reviews I had left, and how many tickets I had closed, hoping to use the numbers to prove my output to my manager. My manager simply said performance is not judged by output alone. Only years later did I understand—when measurement becomes the goal, it is no longer a good measure. From Britain’s window tax and the Hanoi rat bounty to evaluating developers by PR count today, the mechanism is exactly the same.
- Using Cloudflare Images for Image Storage and Transformation Putting an image on a web page is the easiest thing in frontend. But doing it properly — resizing, generating every format, holding up under traffic — turns into a whole solution of its own. I ended up handing all of it to Cloudflare Images and keeping just one original.
- Stop Using Access Keys Already Access Keys are an easily overlooked security risk on AWS. Use OIDC with IAM Roles so GitHub Actions can securely access AWS resources without any secrets.
- Database Primary Keys: AUTO_INCREMENT, UUID, and UUIDv7 Backend developers often have to decide on a primary key: auto increment or UUID? What about collisions? How much faster is UUIDv7 compared with created_at + index? After benchmarking 20 million rows and looking at the design trade-offs, this post gives you the answer.