obnizでじゃんけん大会プログラムを作ってみた

この記事はObniz Advent Calendar 2018の17日目の記事です。

ときどき勉強会で、司会者と参加者でじゃんけんして勝ち残った人にプレゼントが贈呈されることがあります。このじゃんけんをobnizでやるプログラムを試作してみました。

作成のきっかけ

スイッチサイエンスの動画「micro:bitファンミーティングの話」を見ていたら、5分3秒ぐらいからmicro:bitを43台使って抽選を行った話が出てきました。obnizを使って似たようなことができないかなと思いました。

作成した物の動作

司会者と参加者が各1台ずつobnizを持ち、ダイアルスイッチを使ってじゃんけんの手(?)「グー」「チョキ」「パー」を入力します。司会者に勝った人だけが、次のじゃんけんに参加できます。

試作品なので最低限の情報しか画面に表示していません。

起動直後は、各obnizに親か子の情報が表示されています。

じゃんけんの手を全員が入力し終わると、子供には勝敗が表示されます。

勝敗決定後のブラウザ画面はこうなります。

2回戦に進んだ画面はこうなります。child  2は1回戦で負けたため2回戦には参加できません。

プログラムを書いて気づいたこと

複数台のobnizの制御

obnizサイトのドキュメントにある「obniz2つを連携」を真似して作り始めました。ここで解説されている方法は、obniz.onconnectに渡す関数を入れ子にする方法です。obnizAのonconnect関数内にobnizBのインスタンス作成とonconnect関数が記載されています。

var obnizA = new Obniz("0000-0000");
obnizA.onconnect = async function () {
  var obnizB = new Obniz("1234-5678");
  obnizB.onconnect = async function() {
    // integration here 
  }
}

この方法には次の点でじゃんけんプログラムには適しません。

  • 参加人数が多くなると入れ子がどんどん深くなる。
  • onlineにならないobnizがあると、それより先のobnizは接続できない。

そこで、入れ子にするのはやめて、単純にループで回すようにしました。

parent = new Obniz(parentID);
parent.onconnect = function () {
  parent.display.clear();
  parent.display.print("parent");
  parent.switch.onchange = parentSwitch;
}
for (var i = 0; i < childrenIDs.length; i++) {
  child = new Obniz(childrenIDs[i]);
  child.onconnect = createChildFunction(i, child);
  child.onclose = createChildOnCloseFunction(i,child) 
  children.push(child);
}

子供のonconnect関数内で、自分が何番目の子供であるかのインデックス番号と自分自身を表すobnizオブジェクトを使うので、それらが参照できるようにしなければなりません。そこで「関数を返す関数」を用意しました。

function createChildFunction(index,obniz) {
  return async function () {
    online[index]=true;
    updateBrowser();
    obniz.display.clear();
    obniz.display.print("child " + index);
    obniz.switch.onchange = createJogdialCallback(obniz, index);
  }
}

同様に、obniz.switch.onchangeも「関数を返す関数」を使って設定しています。

function createJogdialCallback(obniz, index) {
  return function (state) {
    if (state !== "none") {
      if (step == 'VOTING') {
        obniz.display.clear();
        obniz.display.print(""+index+" : "+handName[state]);
        childHands[index] = handName[state];
        updateBrowser();
      }
    }
  }
}

こういったコーディングをすることで、手持ちの4台のobnizでプログラムが動作するようになりました。ただ、よく考えてみると数十台のobnizとの接続は、OSやブラウザの限界を超えてしまう可能性があることに気づきました。

コールバックの並行実行の可能性

参加者がほぼ同時に手を入力した場合、obniz.switch.onchange( )関数が一斉に発火します。それらの関数が1個ずつ処理されるのか並行に処理されるのかは、obnizのJavaScriptライブラリの実装方法や、ブラウザのJavaScriptエンジンの実行方法に依存します。obnizのドキュメントを見た限りではその辺の解説は見つかりませんでした。今回は「逐次実行だ!」と信じて排他制御は考えませんでした。

状態遷移の設計

設計せずにいきなりコードを書き始めたのでかなり手戻りがありました。

最初は単純に次のような状態遷移を考えていました。

プログラムを書いて動かしてみたら、次のような問題があることに気がつきました。

  • 3の遷移はこんなに単純ではない
    • 手を入力しない人がいるかもしれない
    • 何らかの原因でオフラインになったobnizからは手が入力されない

この問題は、全員の入力を待つことなく強制的に「勝敗を判定」に遷移する方法を用意することで回避しました。obnizのオフライン判定は一定のタイムアウト時間後になるので即時性がないため自動判定にはしませんでした。複数台のobnizを制御するプログラムの難しさがここに潜んでいます。

  •  4の遷移はこんなに単純ではない。
    • 勝った人がいない場合は、同じ参加者でじゃんけんをやり直す
    • 賞品が3つあって勝者が2人の場合、勝った2人を除いた人達でじゃんけんを再開したい
    • 賞品をゲットした人は除いて次のじゃんけんをはじめたい
    • その他いろいろ

この問題の1つ目は単純な考慮漏れだったのですぐ直せましたが、2つ目以降はやりたいことが多岐にわたるので対応を諦めました。

おわりに

一応、それっぽく動くプログラムはできましたが、

  • 何台まで対応できるかわからない
  • 参加者全員のobniz IDを入力する必要があるが、現状ではプログラムに直書きしているため、実用には程遠い

という問題があります。また、複数のobnizを使う場合に注意すべき点もまだまだありそうです。修行を続けたいと思います。

(ソースコードは後日公開する予定です)