どれだけ数字が好きか体で示してもらおうか

#数字が好きか
皆さんは数字は好きですか？
僕は幼い時、自由帳へひたすら数字やら記号やらを書きなぐる変態少年だったらしく、
「__ああ、この子絶対理系だわ・・・__」
と親が察するぐらいには数字・数学が好きですｳｯﾋｮｫｫｵｱ

ところが、先日東京ビックサイトで催されたMaker Fair Tokyoのとある作品にインスパイアを受け、
「__いや、好きとかは口で言うのは簡単なんだよな。好きはちゃんと体で表現しないと。__」
と思ったんです。

__何を言ってるか分からない？？__
__僕も分かりません。__

ということで今回は、__全身で数字を表現するダンスゲームNumDance__を作ってみました。
アプリは公開しているので皆さんも全身全霊全身全力でやってみてくださいね。
Maker Fair Tokyoのとある作品については最後に触れようと思います。

Appは[こちらから](https://musing-minsky-34983e.netlify.app)
コード(Gist)は[こちらから](https://gist.github.com/canonno/ab8072405c1dce0764f9be1d16374af7)

#完成デモ
__お題に書かれている数字に7秒間なりきる__のが基本ルールです。
1から始まるので、まずは__1を体で全力で表現します__。
WebCameraからその姿が__1かどうかが厳密に判定される__ので__1判定の状態を7秒間維持します__。

7秒間維持をクリアしたら次の数字に進みます。
スタート時点で60秒の制限時間があり、__各数字をクリアごとに60秒プラスされます__。
1から9まで続き、__9をクリアして全ステージクリア__です。

__ちなみに「2」がすでにむちゃくちゃむずいです__。
全クリしたら教えてください。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">どれだけ数字が好きか体で示してもらおうか<a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://t.co/p186Ui683h">pic.twitter.com/p186Ui683h</a></p>&mdash; canonno (@canonno_blog) <a href="https://twitter.com/canonno_blog/status/1314150442847281153?ref_src=twsrc%5Etfw">October 8, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#実装こまごま
##狂気の教師データ
今回は[Teachable Machine](https://teachablemachine.withgoogle.com/)を使って数字判定を実装しました。
最初は純粋に「1と判定される学習データだから、パワポかなんかで1って書いてスクショ取ればいいや」とか思っていました。
こんな感じです。
<img width=400 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666990/73d89bdc-3bc6-d914-0a8c-1f2f0b835ab6.png">

しかし実装が進むにつれ、「__体で数字で表現するゲームなんだから、体で表現した学習データがないとおかしくないか__」ということに気づき始めます。
ということで集めた学習データがこちら。

<img width=400 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666990/30dc9402-0302-f845-adf8-39f8976552a5.png">

__楽しそうですね。__

これらをTeachable Machineに食わせて数字判定学習器を作りました。
__学習データの枚数がむちゃくちゃ少ないので、判定ガバガバだと思います__。
でもそこをどうにかするのが数字愛なのではないか、と考えております（強引）

##判定実行してブラウザ上に表示
[こちらの記事](https://qiita.com/canonno/items/964b9ff613ced1b217c2)でも書きましたが、Teachable Machineで作ったモデルをはクラウドへデプロイできます。
ブラウザ上で実行するコードもTeachable Machine側で用意してくれるので非常にﾍﾞﾝﾘｨｨｲｲです。
判定結果はlabelという変数に入れられ、画面に表示されます。

```js
// Classifier Variable
  let classifier;
  // Model URL
  let imageModelURL = 'URLをここに';
  
  // Video
  let video;
  let flippedVideo;
  // To store the classification
  let label = "";
  let label_before = "";
　
  // Load the model first
  function preload() {
    classifier = ml5.imageClassifier(imageModelURL + 'model.json');
  }

  //初期化処理
  function setup() {
    createCanvas(640, 640);
    
    // Create the video
    video = createCapture(VIDEO);
    video.size(640, 480);
    video.hide();

    flippedVideo = ml5.flipImage(video);
    // Start classifying
    classifyVideo();
  }
　　
  // Get a prediction for the current video frame
  function classifyVideo() {
    flippedVideo = ml5.flipImage(video)
    classifier.classify(flippedVideo, gotResult);
    flippedVideo.remove();

  }

  // When we get a result
  function gotResult(error, results) {
    // If there is an error
    if (error) {
      console.error(error);
      return;
    }
    label_before = label;
    // The results are in an array ordered by confidence.
    // console.log(results[0]);
    label = results[0].label;

    // Classifiy again!
    classifyVideo();
  }
```

##ゲームロジック
あとはここに細かいゲームロジックを追加していくだけです。
p5jsのお作法については[こちらの記事](https://qiita.com/canonno/items/00738c7d928c3ec655d5)や[こちらの記事](https://qiita.com/canonno/items/2ccc66ae9fa131e3fd9e)で紹介していますので、こちらもご覧になってくださいね。
modeでゲーム中かゲームクリアかゲームオーバーかを定義し、それに応じて処理を変更しています。

```js

//常にここが実行される
  function draw() {
    //ゲームの状態によって処理を変える
    switch (mode){
      case "game":
        play_game();
        break;
      case "game_over":
        game_over();
        break;
      case "game_clear":
        game_clear();
        break;
    }
  }

```

ゲーム中は基本的には画像判定とタイマーのカウントダウンぐらいしか処理していません。
タイマーの実装は[こちらのサイト](https://himco.jp/2019/02/23/7_5%EF%BC%9A%E3%82%BF%E3%82%A4%E3%83%9E%E3%83%BC-p5-js-javascript/)を参考にしています。

```js

//ゲーム中の処理
  function play_game(){
    background(0);
    // Draw the video
    image(flippedVideo, 0, 0);

    // Draw the label
    fill(255);

    //画面右側の文字群
    textSize(50);
    textAlign(CENTER);
    text(label, 450, height - 4);
    textSize(25);
    textAlign(CENTER);
    text("あと"+sec+"秒耐えろ！", 450, 560);

    //画面左側の文字群
    textSize(50);
    textAlign(CENTER);
    text("お題："+odai_num, 190, height - 4);
    textSize(25);
    textAlign(CENTER);
    text("残り時間あと"+time_limit+"秒", 190, 560);

    //経過時間を測定
    const now = millis();
    elapsedTime = now - startTime;

    //1000ms経過したらtime_limitを1下げる
    if(elapsedTime >= oneSec){
      time_limit--;
      startTime = millis();

      //time_limitが0になったらmodeを変える
      if (time_limit <= 0){
        mode = "game_over";
      }
    }  
  }
```
ゲームオーバーとゲームクリアはそれぞれ画面を上塗りするだけの処理です。

```js
//ゲームクリアの処理
  function game_clear(){
    background(0);
    text("数字の気持ちが完璧に分かりました", 250, 300);
    textSize(25);
    text("Press r button to retry !", 220, 350);
    if (key == "r") {
        key = 0;
        setup();
    }
  }
  
  //ゲームオーバーの処理
  function game_over(){
    background(0)
    text(odai_num-1+"まで気持ちが分かりました", 250, 300);
    textSize(25);
    text("Press r button to retry !", 220, 350);
    if (key == "r") {
      key = 0;
      setup();
    }
  }

```

##お題と同じ数字である時間だけカウントダウン
これが地味に難しかったので備忘録的にメモします。
「別の数字になったらリセットする」というところをどう実装しようか少し悩みました。
結局直前の判定結果がlabel_beforeに入れ、今の結果labelと一致するかどうかで条件分岐しました。

```js
function gotResult(error, results) {
    // If there is an error
    if (error) {
      console.error(error);
      return;
    }
    label_before = label;
    // The results are in an array ordered by confidence.
    // console.log(results[0]);
    label = results[0].label;

    if (label_before==label && label == odai_num){
      const now = millis();
      elapsed_sec = now - start_sec;
      if (elapsed_sec >= 1000){
        sec--;
        start_sec = millis();
        if(sec <= 0){
          next_stage();
        }
      }
    }else{
      sec = sec_max;
    }
    // Classifiy again!
    classifyVideo();
  }

```

#余談
この記事を作るきっかけになった作品が、Maker Fair Tokyoで発表された、AIプログラミング「ドレミダンス」です。
[こちらのYoutube](https://www.youtube.com/watch?v=KuYVJJ9la7Y&feature=youtu.be)から3:18:45あたりでご覧になれます。
全身の動きを使って音楽を奏でるという発想に感銘を受けました。

ゲームを作るときってやっぱりキーボードとかコントローラーとか、手先で操作できるものを想像してしまうんですよね。
画像認識技術を使えば__体の形に意味を持たせられる__という点がなるほどなあと思った部分です。
可能性が広がりますね。

#さいごに
画像認識周りは技術が進歩して実装が簡単になりましたし、本当に可能性にあふれているなあという印象。
そしてp5js便利すぎる。
引き続き色んなゲーム作っていきたいなあと思います。

最後までご覧いただきありがとうございました！
LGTMいただけると励みになります！

<img width=200 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666990/616b5815-7971-f7bb-3e51-05b3e988c9c1.png">

LGTMのほどよろしくお願いいたします！！！！！！！！！！！！！！！！！！！！！