詐欺あみだくじを作ろう！！

# 製作の経緯
年初めは運勢占いがたくさんあるよね。
自分がそれを制作したら自分有利の運勢になるやん！！...

ということで、選ばれたスタートを自分の選んだゴールに繋げる盤面を自動で作る仕組みを実装してみました〜

# 成果物
どの選択肢を選んでも「みたち」に行き着きます。リロードして何回か試してみてください。

https://mitachi-h.github.io/2023/main.html

スマホ用に作ったのでパソコンだと不恰好かも...


# プログラミング
### ソースコード
https://github.com/Mitachi-H/2023

### アルゴリズム
#### 目的
ランダムに与えられたスタートとゴールをうまくカモフラージュしながら結ぶ
#### 流れ
1. スタートとゴールを最短で行く場合の道を得る。
1. 1の途中に寄り道を追加する。例えば2本目から3本目に行く道の前に、2本目から1本目に行く道と1本目から2本目に行く道を追加する。
1. 自分が通らないダミーの道を追加する

### 全体
おおみそかに慌てて作ったので見やすさ絶望的です。

```html:main.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .canvas-parent {
        width: 100%;
        height: 200vh;
      }
    </style>
  </head>
  <body>
    <div>
      <h2 style="text-align: center">今年大事にするべきものあみだ</h2>
      <div style="text-align: center">
        スタート地点を選択したら<br />「あみだくじ！」をクリック！！
      </div>
    </div>
    <div class="canvas-parent" id="canvasParent">
      <canvas id="canvas"></canvas>
    </div>
    <script>
      const FRAMERATE = 60;
      let canvasParent = document.getElementById("canvasParent");
      let canvas = document.getElementById("canvas");
      let context = canvas.getContext("2d");

      let x = canvas.width;
      let y = canvas.height;

      canvas.addEventListener("click", (e) => {
        const rect = canvas.getBoundingClientRect();
        const point = {
          x: e.clientX - rect.left,
          y: e.clientY - rect.top,
        };

        if (!clicked) {
          if (
            //「あみだくじ！」をクリックしたら
            point.y < 155 &&
            point.y > 105 &&
            point.x > x / 2 - button1.width / 2 &&
            point.x < x / 2 + button1.width / 2
          ) {
            //ゴールに表示する5文字を初期化
            wordList = shuffle(wordList);
            wordList.splice(Math.floor(Math.random() * 5), 0, "みたち");

            let index = wordList.indexOf("みたち");
            let numList = [];

            //自分の動くべき道追加
            for (
              let i = Math.min(index, checked);
              i < Math.max(index, checked) + 1;
              i++
            ) {
              numList.push(i);
            }
            if (index < checked) {
              numList.reverse();
            }
            if (numList.length == 1) {
            }
            lineList.push([checked]); //-1で取り出せるように2重
            //lineListには[[前の位置,後の位置],現在の位置]が入る
            //lineList2　に自分を動かさないダミーは別で保持したので、現在の位置いらなかったかも...
            for (let i = 0; i < numList.length - 1; i++) {
              lineList.push([[numList[i], numList[i + 1]], numList[i + 1]]);
            }
            //自分動かすダミー道追加
            let last = [checked];
            while (lineList.length < 8) {//8はテキトー　、もっと適切な数字があるかも
              let rand = Math.floor(Math.random() * lineList.length);
              let before = lineList[rand][lineList[rand].length - 1];
              let middleWay = Math.random() > 0.5 ? 1 : -1;
              let middle = before + middleWay;
              //できるだけ同じ場所を往復することを避ける
              if (last.includes(middle)) {
                middle = middle - 2 * middleWay;
              }
              if (middle == -1) {
                middle = 1;
              } else if (middle == 5) {
                middle = 3;
              }
              last = [before, middle];
              lineList.splice(rand + 1, 0, [[before, middle], middle]);
              lineList.splice(rand + 2, 0, [[middle, before], before]);
              if (middle != 0 && middle != 4) {
                //できるだけ横方向の移動をして欲しいから追加した
                //ダミー道の途中に再度ダミー道追加
                let middle2 = middle + (middle - before);
                lineList.splice(rand + 2, 0, [[middle, middle2], middle2]);
                lineList.splice(rand + 3, 0, [[middle2, middle], middle]);
              }
            }
            clicked = true;
            //自分動かさないダミー道追加
            lineList2.push([checked]); //-1で取り出せるように
            for (line of lineList) {
              if (line.length != 1) {
                let minNum = Math.min(...line[0]);
                let before, middle;
                if (minNum == 0) {
                //なぜ0.7にしたか忘れた、普通に0.5でいいかも
                  if (Math.random() > 0.7) {
                    before = 2;
                    middle = 3;
                  } else {
                    before = 3;
                    middle = 4;
                  }
                }
                if (minNum == 1) {
                  before = 3;
                  middle = 4;
                }
                if (minNum == 2) {
                  before = 0;
                  middle = 1;
                }
                if (minNum == 3) {
                  if (Math.random() > 0.7) {
                    before = 0;
                    middle = 1;
                  } else {
                    before = 1;
                    middle = 2;
                  }
                }

                lineList2.push([before, middle]);
              }
            }
          }
          for (let i = 0; i < 5; i++) {
            if (
              //スタート地点がクリックされたら
              point.y < 230 &&
              point.y > 170 &&
              point.x < (x / 6) * (1 + i) + 20 &&
              point.x > (x / 6) * (1 + i) - 20
            ) {
              checked = i;
            }
          }
        }
      });

      let resize = () => {
        //画面サイズをレスポンシブ対応
        canvas.width = canvasParent.clientWidth;
        canvas.height = canvasParent.clientHeight;
        x = canvas.width;
        y = canvas.height;
      };

      let clicked = false;
      let checked = 0;
      let wordList = [
        "お金",
        "家族",
        "就活",
        "サークル",
        "食事",
        "努力",
        "友達",
        "恋人",
      ];
      let lineList = [];
      let lineList2 = [];
      const shuffle = ([...array]) => {
        for (let i = array.length - 1; i >= 0; i--) {
          const j = Math.floor(Math.random() * (i + 1));
          [array[i], array[j]] = [array[j], array[i]];
        }
        return array;
      };

      let draw = () => {
        if (clicked) {
          //最後の挨拶描画
          context.beginPath();
          context.font = "15pt Arial";
          context.textAlign = "center";
          context.fillStyle = "rgba(255, 0, 0, 1)";
          context.fillText("あけましておめでとうございます", x / 2, 700);
          context.stroke();
          context.beginPath();
          context.font = "15pt Arial";
          context.textAlign = "center";
          context.fillStyle = "rgba(255, 0, 0, 1)";
          context.fillText("今年もよろしくお願いします", x / 2, 720);
          context.stroke();
          for (let i = 0; i < 5; i++) {
            //縦棒描画
            context.beginPath();
            context.lineWidth = 4;
            context.moveTo((x / 6) * (1 + i), 210);
            context.lineTo((x / 6) * (1 + i), 600);
            context.stroke();
          }
          for (let i = 0; i < 5; i++) {
            //ゴール文字描画
            context.beginPath();
            context.font = "10pt Arial";
            context.textAlign = "center";
            context.fillStyle = "rgba(0, 0, 0, 1)";
            context.fillText(wordList[i], (x / 6) * (1 + i), 630);
            context.stroke();
          }
          const l = lineList.length;
          let i = 0;
          for (line of lineList) {
            if (line.length != 1) {
              //自分の通る横棒の描画
              context.beginPath();
              context.lineWidth = 4;
              context.moveTo((x / 6) * (1 + line[0][0]), 250 + (i * 300) / l);
              context.lineTo((x / 6) * (1 + line[0][1]), 250 + (i * 300) / l);
              context.stroke();
              i++;
            }
          }
          i = -1;
          for (line of lineList2) {
            if (lineList2.length != 1) {
              //自分の通らない横棒の描画
              context.beginPath();
              context.lineWidth = 4;
              context.moveTo((x / 6) * (1 + line[0]), 250 + (i * 300) / l);
              context.lineTo((x / 6) * (1 + line[1]), 250 + (i * 300) / l);
              context.stroke();
              i++;
            }
          }
        }
        for (let i = 0; i < 5; i++) {
          //スタート地点描画
          context.beginPath();
          context.arc((x / 6) * (1 + i), 200, 10, 0, Math.PI * 2, true);
          context.strokeStyle = "black";
          context.lineWidth = 2;
          context.stroke();
        }
        //選ばれたスタート地点の色変更
        context.beginPath();
        context.arc((x / 6) * (1 + checked), 200, 10, 0, Math.PI * 2, true);
        context.fillStyle = "rgba(255,0,0,0.8)";
        context.fill();
        context.stroke();
      };
      let button1;

      let drawButton = () => {
        //あみだくじ！ボタン作成　、　ボタンの横幅をテキストと合わせるための処理
        context.beginPath();
        context.font = "40pt Arial";
        context.textAlign = "center";
        var button = context.measureText("あみだくじ！");
        button1 = button;
        context.fillStyle = "rgba(255, 0, 0, 0.25)";
        context.fillRect(x / 2 - button.width / 2, 155, button.width, -50);

        context.fillStyle = "rgba(255, 0, 0, 1)";
        context.fillText("あみだくじ！", x / 2, 150);
        context.stroke();
      };

      let world = () => {
        resize();
        draw();
        drawButton();
      };

      setInterval(() => world(), 1000 / FRAMERATE);
    </script>
  </body>
</html>

```

# 感想
canvasを初めて使用したが、pygameと似てる部分があってすんなり実装できた。アルゴリズム作るのはやっぱり楽しい！！！
アルゴリズムは割とうまくいったと思うけど、自分が通る道が絶対2個セットになっちゃうのは美しくないかなぁ、改善の余地はまだまだありそうだ！