平成生まれのエンジニアが初めてIoTに触れるのに丁度良い、生き残りゲーム～令和最新版～のプロトタイプをobnizで作る

先日、手軽にIoTに触れられると噂の「obniz」を入手しました。
ラズパイに比べて、圧倒的に早く、楽に電子デバイスを操れるのでうっかりスキルが向上したか？と錯覚するほどです。
この手軽さを活かす先は、プロトタイプでしょ？！ということで、さっそく行動してみました。
#生き残りゲームの令和最新版をプロトしよう
時代背景・コロナ感染予防・働き方・IoT・サーボモーターの動き、、、パッとピラめいたのが
　**生き残りゲーム**
レトロラブな私としては、これの令和最新版が作りたい！新しい形で対戦してみたい！　これです。　
**コレは絶対楽しいやつ**にしか思えないので、**プロトしてポテンシャルを測ってみたい**と思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365273/e3e14ef3-6e86-fde7-ee99-7455d34b7975.png)
ちなみに、この生き残りゲーム、平成生まれの方はあまり知らないらしいですね。そんな記事をネットで見ました。
ご存じない方は「生き残りゲーム　沈没」でググってみてください。
そして、私のこの記事をこのまま読み進めていただき、私のような昭和生まれのエンジニアに対して「IoT触ってみましたよ！生き残りゲームでｗ」と会話のネタにしていただければ幸いです。上司や仕事仲間と良好な人間関係を構築することができることでしょう。

#まん防に対応しても当時の興奮そのまま！そしてニューノーマルへ

コンセプトはズバリこれ↑です
生き残りゲーム（物理的なゲーム）をIoTデバイスを駆使してリモートで対戦できるようにします。
いろいろなものが電子化され、新型コロナのおかげでさらに拍車がかかっています。そんな時だからこそ、現物にこだわりたい！
生き残りゲームの醍醐味は、球がドボンするところ。やはり物理的なモノがあってナンボです。**ガシャ！ってならないと興奮しません**。**素朴なギミックを大事にしたい**ですね

#仕掛けはこんな感じ
さて、実現可能なのでしょうか？面白いのでしょうか？**プロトしてみましょう。**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365273/2d356e35-9374-60e1-c751-b3bcdba609b8.png)
#出来上がったのがこちらです
アイデアを可視化しそこから新たな学びを得ることがプロトの目的ですので、「さっさと作る」が肝要。
そこらへんに転がっているもので、さっさと作りましょう。本物の生き残りゲームは、もう手に入らないしね。。。
できたアウトプットも「さっさと見せて」しまいましょう。
ということで**完成品がこちら**です。　本人いたって真剣です（笑
<img width="600" alt="代替テキスト" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365273/aa0519e3-ec19-efa8-f58d-749e4e2687da.jpeg">
2×2マスの**ミニマム仕様**です。
縦方向に走る緑色の板がリモート参加者用のドボンバー（正式名称不明）。
ドボンバーはキングピン代わりのカーボン抵抗を介してサーボアームと直結、obnizで制御され上へ下へと動きます。
横方向のドボンバーは実機参加者用で、手動となります。
ドボン機構の再現には、材料の穴あけ容易性とそこそこの強度が必要となります。いろいろな素材を試した結果、某おもちゃショップのポイントカードと某アイドルグループのメンバーズカードを採用するに至りました。

#制作概要
* 使ったもの
    * obniz board 1Y
    * サーボモーター×2
    * ジャンパワイヤ
    * カーボン抵抗
    * 古びたタッパー
    * 無用になったメンバーズカード2枚
    * ビーズ
    * 両面テープ
    * ガムテープ
    * 猫頭付箋
    * 貯まりにたまったAmazonの段ボール
* 仕掛け
    * リモート側のユーザインターフェースとしてLINE Botを採用
    * Node.jsでLINE Bot とobnizを制御
    * リモートユーザのドボンバー2本を 2つのサーボモータでリモート操作
* 作成風景
    * 穴あけ作業がちょいちょい発生するので、ピンバイス、ドリルなどあると良いです。

<img width="600" alt="代替テキスト" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365273/332d7050-e317-4b57-0864-f01151b26c69.jpeg">

#Node.jsによる制御（コード）
* 技術要素
    * LINE Botを使ったユーザとのメッセージ授受
    * obnizを使ったサーボモータの制御
    * expressを使ったLINEとobnizの連携
  
expressをつかって手持ちのパソコンでサーブ。
ngrokをつかってインターネット上のLINEのサービスと接続した。

```javascript:dobon.js
// ########################################
//          obniz処理部分
//  Obniz_ID：自分のobniz ID（XXXX-XXXX）
// ########################################

const Obniz = require('obniz');
const obniz = new Obniz('XXXX-XXXX');
// obnizと接続確立したとき
obniz.onconnect = async () => {
    obniz.display.clear();
    obniz.display.print('obniz Ready');

}

//サーボの動作
const svRFunc = async (deg) => {

    const sv = obniz.wired('ServoMotor', { signal: 2 });
    sv.angle(deg);
}

const svLFunc = async (deg) => {

    const sv = obniz.wired('ServoMotor', { gnd: 9, vcc: 10, signal: 11 });
    //let degrees = i;
    sv.angle(deg);
}


// ########################################
//          LINEBot イベント処理部分
//  channelSecret：LINE Developers → チャネル基本設定 → チャネルシークレット
//  channelAccessToken：LINE Developers → Messaging API設定 → チャネルアクセストークン（長期）
// ########################################
const config = {
    channelSecret: 'XXXXXXXXX',
    channelAccessToken: 'XXXXXXXXX'    
};
const line = require('@line/bot-sdk');
const client = new line.Client(config);
// ExpressからMessaging APIイベントを渡されて処理するところ
const handleEvent = async (event) => {
    // テキストメッセージ以外を受信したときは何も行わずresolveを返す
    if (event.type !== 'message' || event.message.type !== 'text') {
        return Promise.resolve(null);
    }
    // テキストメッセージを受信したとき
    if (event.message.text === '下のレバーを左') {
        // 受け付けたということを「リプライ」で先に返す
        await client.replyMessage(event.replyToken, {
            type: 'text',
            text: '了解'
        });
        // obnizのサーボ動かす（ブロッキング・時間のかかる処理で一旦ここで止まる）
        const temp = await svRFunc(120.0);
        // 処理完了を「プッシュ」で送信する
        client.pushMessage(event.source.userId, {
            type: 'text',
            text: '下のレバーを左に動かしたよ',
        });
    //以下 各サーボ毎にふるまいを設定
    }else if (event.message.text === '下のレバーを真ん中') {
        await client.replyMessage(event.replyToken, {
            type: 'text',
            text: '了解'
        });
        const temp = await svRFunc(90.0);
        client.pushMessage(event.source.userId, {
            type: 'text',
            text: '下のレバーを真ん中に動かしたよ',
        });

    }else if (event.message.text === '下のレバーを右') {
        await client.replyMessage(event.replyToken, {
            type: 'text',
            text: '了解'
        });
        const temp = await svRFunc(60.0);
        client.pushMessage(event.source.userId, {
            type: 'text',
            text: '下のレバーを右に動かしたよ',
        });

    }else if (event.message.text === '上のレバーを左') {
        await client.replyMessage(event.replyToken, {
            type: 'text',
            text: '了解'
        });
        const temp = await svLFunc(45.0);
        client.pushMessage(event.source.userId, {
            type: 'text',
            text: '上のレバーを左に動かしたよ',
        });

    }else if (event.message.text === '上のレバーを真ん中') {
        await client.replyMessage(event.replyToken, {
            type: 'text',
            text: '了解'
        });
        const temp = await svLFunc(90.0);
        client.pushMessage(event.source.userId, {
            type: 'text',
            text: '上のレバーを真ん中に動かしたよ',
        });
    }else if (event.message.text === '上のレバーを右') {
        await client.replyMessage(event.replyToken, {
            type: 'text',
            text: '了解'
        });
        const temp = await svLFunc(130.0);
        client.pushMessage(event.source.userId, {
            type: 'text',
            text: '上のレバーを右に動かしたよ',
        });

    

    } else {
        // LINEから飛んできたメッセージの中身が指定の文言以外だったとき
        client.replyMessage(event.replyToken, {
            type: 'text',
            text: 'メニューボタンで操作してね'
        });
    }
    // resolveを返す
    return Promise.resolve(null);
}



// ########################################
//          Expressサーバー部分
// ########################################
const express = require('express');
const PORT = process.env.PORT || 3000;
const app = express();
// 「(サーバーURL)/webhook」にアクセス（LINEサーバーからのWebhook）があったとき
app.post('/webhook', line.middleware(config), (req, res) => {
    // 受信したイベントをターミナルに表示
    console.log(req.body.events);
    // イベントをhandleEventに渡して1つずつ処理
    Promise.all(
        req.body.events.map(handleEvent)
    ).then(
        result => res.json(result)
    );
});
// PORT番号のポートでサーバーを開始
app.listen(PORT);
console.log('express runnning: PORT =', PORT);

```

#プロトタイプテストの様子
被験者には実際に離れたところから、今回開発したLINEのアプリをつかってゲームに参加していただいた。
生き残りゲーム本体の様子は別途Zoomで中継した。
![screen4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365273/3b5b809a-0dd5-0759-6e72-6e3757e87bd8.gif)


#プロトタイプから得られた学び
* 良かった点
    * 見た目を含め**実にアホくさくて非常にウケてもらえた。作ってよかったと感じた。**（改めて、自分はこの手のやつが大好物だということを思い知った）
    * 仕事の合間、**WEB会議で雰囲気が悪くなった時など**に、こいつをいそいそ出してくると**ウケる**かもしれないとおもった。やはり**ニューノーマルガジェットの筆頭に**なるか？
    * サーボの勢いがありすぎて**球が**下におちるのではなく**外に飛び出した**時（想定外の動き）が一番盛り上がった。そういえば当時、その手のチートを繰り出す輩もいたよねと**昔の思い出話に花が咲いた**。このゲームを令和最新版にするにあたり**こだわるポイントは物理的アクション**で間違いはないと確信した。
* もっと深堀したい点
    * 昭和時代の人に当てたらどれだけウケるか？
    * **サイズ感**はどれくらいがよいか？実際にプロトで触ってみて、通常版（フルスペック）をネット越しでガチでやるのはちょっと想像ができなかった。今回のような2×2またはもうちょっと大きいくらいがよいだろうか。**ユースケースは**やはり**WEB会議のアイスブレイク**などではないだろうか
    * 付加機能があったほうがいいのか？それともシンプルが一番なのか？なんとなくシンプルのほうが良い気がする。
    * 当たり前だが、**本物の意匠をどれだけ再現できるか**？にかかっているとおもう。
    * リモートだと**ほんのり動かして穴の様子を探るチートができない**点をどうするか？LINEボタンではなく、**フリック入力などで力加減を伝えられる仕組み**も面白いかもしれない。
    * やはり平成生まれには刺さらないだろうか？
    * **完全フルオートはどうだろうか？**球の回収・セッティングまで全自動。オンラインUFOキャッチャーのようなイメージ
    * **メーカーさん製品化してくれないだろうか？**


生き残りゲーム～令和最新版～のプロトタイピングは以上です。
ご意見お待ちしております。