2022 Advent Of Code(day9)– ロープブリッジ

作成者:カランカラン
💡

質問やフィードバックがありましたら、フォームからお願いします

本文は台湾華語で、ChatGPT で翻訳している記事なので、不確かな部分や間違いがあるかもしれません。ご了承ください

Day9

最近、問題を確認するときに、面倒だと思ったら、書き始めるかどうかを決めるという悪い習慣がついてしまっています。この習慣は直すべきですねQQ。

Part1

問題の説明を読んだ後、特に考えるべきポイントは、尾の移動をどう決定するかと、隣接しているかどうかの判断です。ここでは、シンプルな 2-d ベクトルを直接書いてみました。

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)
    );
  }

注:書き終えた後、実際には2点間の距離を直接求めればよいことに気付きました。1つ1つ比べる必要はありませんでした。

入力処理

入力は比較的簡単で、基本的には各方向の移動をベクトルにマッピングするだけです:

U: (0, 1)
D: (0, -1)
R: (1, 0)
L: (-1, 0)

尾の移動ロジック

ロープの頭が移動すると、尾が隣接していなければ尾も移動します。これは内積を使用して角度を求めることで、尾がどのように移動すべきかを計算できます。x 単位ベクトルと y 単位ベクトルの内積を求めることで、尾の移動方法がわかります。

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);
  }
}

問題は、尾が通った点を問うもので、Setを使用して簡単に実装できます。完全なコード実装は以下の通りです:

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)) {
      // ベクトルを計算
      // 指定方向へ移動
      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);
      }
    }
  }
}

問題には何か隠されたお楽しみがあるのではないかと思い、尾が描く形がどんな画像になるかを考えたので、ロープの座標も描いてみました。

繩子的移動軌跡

しかし、特に何もなさそうです。

Part2

最初は頭と尾だけを考えればよかったのですが、今はロープの長さが10になったので、移動方法も少し異なります。しかし、全体のロジックは大まかには同じで、元のロープの頭と尾をつなげることで達成できます。

 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}`
    );
  }
});

この記事が役に立ったと思ったら、下のリンクからコーヒーを奢ってくれると嬉しいです ☕ 私の普通の一日が輝かしいものになります ✨

Buy me a coffee