LoginSignup
1
1

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-01-07

製作の経緯

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

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

成果物

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

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

プログラミング

ソースコード

アルゴリズム

目的

ランダムに与えられたスタートとゴールをうまくカモフラージュしながら結ぶ

流れ

  1. スタートとゴールを最短で行く場合の道を得る。
  2. 1の途中に寄り道を追加する。例えば2本目から3本目に行く道の前に、2本目から1本目に行く道と1本目から2本目に行く道を追加する。
  3. 自分が通らないダミーの道を追加する

全体

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

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個セットになっちゃうのは美しくないかなぁ、改善の余地はまだまだありそうだ!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1