スマホで操作するテトリスを作ってみました。「スマホで操作」の意味がわかりにくいかもしれませんが、スマホの画面上でフリックしたりするわけではなく、スマホをリモートコントローラーとして振ったり傾けたりして操作する、という意味です。

実現する上で Phone Sensor サービスを使いました:
http://phonesensor.mybluemix.net/

ロジックとしてはこんな感じです。まずスマホでは JavaScript でスマホのジャイロ情報を取得し、Phone Sensor サービスを使ってその情報を MQTT ブローカーに送ります。そして IBM Bluemix の Node-RED フローエディタを使って、その MQTT ブローカーに送られたジャイロデータを取り出して、WebSocket に送り続ける、というサーバーサイド処理を記述します。最後にゲームを HTML5 で作り、そのコントロール部分を WebSocket に送られてくるスマホのジャイロデータを元にして操作するよう記述します:
2015061900


ちょっとだけアピールしておくと、MQTT ブローカーを挟んでゲーム画面部分(テトリス)とコントロール部分(Phone Sensor)が独立して分離している点が特徴です。この特徴があるので、例えば同じコントロールの仕組みを使いながら画面はもっと大きな街中のビジョンに投影してもいいし、あるいはコントロール部分を別のデバイスにしたり、別の方法(例えば温度が上がったら右、下がったら左、のよう)に変更することも容易です。


実際の使い方はこんな感じです。まず IBM Bluemix にアクセスして Node-RED プロジェクトを作成します。この辺りの手続き等の詳細はこちらを参照ください:
Bluemix の Node-RED サービスで IoT アプリを作る(1/2)


ただプロジェクト作成において気をつけていただく点が何箇所かあります。まずボイラープレートを選ぶ際の選択肢です。Node-RED を使うアプリケーションプロジェクトをボイラープレートから作成する場合、"Internet of Things Foundation Starter" か "Node RED Starter" かのいずれかを選択することになるのですが、ここでは "Node RED Starter" のボイラープレートを選択してください:
2015061901


そしてアプリケーション名を指定して Node-RED Starter ボイラープレートからアプリケーションを「作成」します。ここではアプリケーション名に "dotnsf-nodered" と入力しています:
2015061902


作成後(ステージング中でも構いません)、左メニューの「コーディングの開始」を選択すると、作成した Node-RED アプリケーションのカスタマイズ方法を紹介する内容が画面右側に表示されます:
2015061903


ここを下にスクロールして "Customizing your Node-RED instance" と書かれた所に実際のカスタマイズ手順が紹介されています(この紹介が IoT Foundation Stater ボイラープレートにはありません)。ここで紹介されている2つの手順を行う必要があります。具体的には cf ツールのインストールと、スターターコードのダウンロードです:
2015061904


cf ツールは既にダウンロード&インストール済みであれば行う必要はありません。まだ cf ツールをインストールしたことがない場合はガイドに従って自分の環境にあったインストール用バイナリをダウンロードし、インストールしてください:
2015061907


一方、スターターコードは必ず必要になるため、ダウンロードのボタンをクリックします:
2015061908


自分が付けたプロジェクト名.zip という名前の zip ファイルのダウンロードが始まります。これを保存します:
2015061905


保存した zip ファイルを展開するとこんな内容になっています。これらはこの後ですぐに使うことになるので、適当なフォルダ(例えば /tmp/dotnsf-nodered)に展開しておきます:
2015061906



次にゲームを用意します。今回はオープンソースで公開されている HTML5 ベースのテトリスゲームを使うことにします。こちらを参考にさせていただきました:
http://coderecipe.jp/recipe/iHjJBJx9Si/

上記サイトで紹介されている6つのファイルをダウンロード/作成します。作成後、index.html だけは tetris.html とファイル名を変えておきます:
- index.html → tetris.html とリネーム
- style.css
- tetris.js
- render.js
- controller.js
- pop.ogg(効果音ファイル)

この6つのファイルを先程ダウンロード&展開した zip ファイルの展開先の、public フォルダに全てコピーします。このフォルダにはもともと css フォルダ、images フォルダと index.html ファイルが存在しているはずなので、6つのファイルをコピーした後はこのようなファイル構成になります:
2015061901


tetris.html ファイルを少し編集します。上記サイトでは pop.ogg ファイルを sound フォルダに、*.js ファイルを js フォルダに入れる想定で記述されています。そのような構成に作り変えてもいいのですが、tetris.html ファイルを変えて対応することにしました。tetris.html 内の <body> 部分を以下のように(同じフォルダ上のファイルを参照するように)書き換えます。また body タグの onload 属性に connect() を指定して、このファイルが読み込まれたタイミングで JavaScript の connect() 関数が実行されるよう指定します(青字部分を変更):
<!DOCTYPE html>
<html>
    <head>
        <title>HTML5 Tetris</title>
        <link rel='stylesheet' href='style.css' />
    </head>
    <body onload="connect()">
        <audio id="clearsound" src="./pop.ogg" preload="auto"></audio>
        <canvas width='300' height='600'></canvas>
        <script src='./tetris.js'></script>
        <script src='./controller.js'></script>
        <script src='./render.js'></script>
    </body>
</html>

そして controller.js も変更します。このファイルが入力キーによるゲームのコントロールを行う部分を担当しており、変更後はキー入力ではなく、WebSocket から取得するデータの内容によっていずれかのキーが押された時と同じ処理をするように変更します(青字部分を追加):
/* もとのキーボードによるコントール操作部分をすべてコメントアウトして無効にする
 キーボードを入力した時に一番最初に呼び出される処理
document.body.onkeydown = function( e ) {
  // キーに名前をセットする
  var keys = {
    37: 'left',
    39: 'right',
    40: 'down',
    38: 'rotate'
  };

  if ( typeof keys[ e.keyCode ] != 'undefined' ) {
    // セットされたキーの場合はtetris.jsに記述された処理を呼び出す
    keyPress( keys[ e.keyCode ] );
    // 描画処理を行う
    render();
  }
};
*/

// 無効にした部分を WebSocket からの入力に従って実行するように追加
var socket;
var th = 5; //. しきい値
var wsUrl = 'ws://' + location.hostname + '/ws/sensor';
function connect(){ // この処理が tetris.html のロード直後に呼ばれる
  socket = new WebSocket(wsUrl);
  socket.onmessage = function(e) {
    var sensorData = JSON.parse(e.data);

    if( sensorData.LR >= 50 ){ 
      // デバイスを右に50度傾けたら右方向キーが押されたとみなす
      keyPress( 'right' );
      render();
    }else if( sensorData.LR <= -50 ){
      // デバイスを左に50度傾けたら左方向キーが押されたとみなす
      keyPress( 'left' );
      render();
    }else if( sensorData.FB >= 50 || sensorData.FB <= -50 ){
      // デバイスを前後に50度傾けたら回転キーが押されたとみなす
      keyPress( 'rotate' );
      render();

      // 下に押されたとみなすキーは定義しない
    }
  }
}

// キーボードが押された時に呼び出される関数
function keyPress( key ) {
  :

これだけでゲームの改良は完成です。基本的にはキー操作部分を変更しただけです。ではこのゲーム部分を追加/変更したアプリケーションをデプロイします。

シェルやコマンドプロンプトを開き、zip ファイルを展開したディレクトリ(public フォルダが見えているディレクトリ)に移動してから Bluemix にログインし、アプリケーションをプッシュ(デプロイ)します:
# cd /tmp/dotnsf-nodered (カレントディレクトリを変更)

# cf login -a https://api.ng.bluemix.net/ (米国データセンターの場合)
# cf login -a https://api.eu-gb.bluemix.net/ (英国データセンターの場合)

# cf push dotnsf-nodered (作成したプロジェクト名を指定してプッシュ)


ゲームアプリのプッシュが完了すれば、後はセンサーデータを WebSocket に送る Node-RED 側を定義するだけです。

Node-RED のフローエディタを開き、中に何も記述されていない状態を作ります。何か書かれている場合は全て削除します。そして右上のハンバーガーメニューから Import > Clipboard を選択します:
2015061901



クリップボード画面が表示されます:
2015061902


この中に以下の内容をクリップボードにコピー&ペーストして OK ボタンをクリックします:
[
 {
  "id":"298b5c83.185f3c",
  "type":"websocket-listener",
  "path":"/ws/sensor",
  "wholemsg":"false"
 },
 {
  "id":"5bcc699c.5ca31",
  "type":"ibmiot in",
  "authentication":"quickstart",
  "apiKey":"",
  "inputType":"evt",
  "deviceId":"434444964396",
  "applicationId":"",
  "deviceType":"+",
  "eventType":"+",
  "commandType":"",
  "format":"json",
  "name":"Phone Sensor",
  "service":"quickstart",
  "allDevices":"",
  "allApplications":"",
  "allDeviceTypes":true,
  "allEvents":true,
  "allCommands":"",
  "allFormats":"",
  "x":82,
  "y":263,
  "z":"4fdac9e9.134208",
  "wires":[
   ["ed5efd2.39d978"]
  ]
 },
 {
  "id":"b4dded1e.e1a35",
  "type":"debug",
  "name":"",
  "active":false,
  "console":"false",
  "complete":"false",
  "x":537,
  "y":109,
  "z":"4fdac9e9.134208",
  "wires":[]
 },
 {
  "id":"ed5efd2.39d978",
  "type":"function",
  "name":"ペイロードデータ",
  "func":"\nreturn {payload:msg.payload.d};",
  "outputs":1,
  "valid":true,
  "x":220,
  "y":189,
  "z":"4fdac9e9.134208",
  "wires":[
   [
    "b4dded1e.e1a35",
    "771cdce9.22bb1c"
   ]
  ]
 },
 {
  "id":"f611f711.bd56d",
  "type":"debug",
  "name":"",
  "active":false,
  "console":"false",
  "complete":"false",
  "x":556,
  "y":192,
  "z":"4fdac9e9.134208",
  "wires":[]
 },
 {
  "id":"771cdce9.22bb1c",
  "type":"function",
  "name":"整形",
  "func":"\nreturn {payload:{\n    AX: msg.payload.accelX,\n    AY: msg.payload.accelY,\n    AZ: msg.payload.accelZ,\n    RX: msg.payload.rotationX,\n    RY: msg.payload.rotationY,\n    RZ: msg.payload.rotationZ,\n    LR: msg.payload.tiltLR,\n    FB: msg.payload.tiltFB\n}};",
  "outputs":1,
  "valid":true,
  "x":350,
  "y":240,
  "z":"4fdac9e9.134208",
  "wires":[
   [
    "f611f711.bd56d",
    "f7300e6c.db39"
   ]
  ]
 },
 {
  "id":"f7300e6c.db39",
  "type":"websocket out",
  "name":"",
  "server":"298b5c83.185f3c",
  "client":"",
  "x":533,
  "y":243,
  "z":"4fdac9e9.134208",
  "wires":[]
 }
]

こんな感じのフローがインポートされるはずです。内容は非常にシンプルで、Phone Sensor から送られてくるスマホの動きや傾きの情報を扱いやすいように整形した上で、 /ws/sensor というパスへ WebSocket で一方的に送っているだけです。2つほど無効なデバッグノードを使ってますが、興味ある方は有効にして送られてくるデータを確認してみてください:
2015061903


次にコントローラーとして使うスマホのブラウザで上記 Phone Sensor サイトにアクセスします。そこで以下の様な画面が表示されるので、赤枠部分に表示される数字文字列(=デバイスID この例だと 434367967328)をメモしておきます。なお、このページは(デバイスの動きを検知して情報を送信する目的で使うため)ゲームを終えるまで閉じないでください:
2015061603


先程の Node-RED エディタ画面に戻って "Phone Sensor" と書かれたノードをダブルクリックします:
2015061904


先程の Phone Sensor ページでメモしたデバイス ID を Device Id 欄に入力して OK ボタンをクリック:
2015061905


最後のフローエディタ右上の "Deploy" ボタンをクリックしてアプリケーションを有効にします。これで Node-RED 側の準備も完了です:
2015061906


では実際にゲームを動かしてみます。PC など、コントローラーとするスマホ以外の環境から http://(プロジェクトのサーバー)/tetris.html にアクセスします。テトリスのゲームがはじまり、ブロックが落ちてくるはずです:
2015061901


コントローラーとして使うスマホ(http://phonesensor.mybluemix.net/ を開いているはずです)を左右に傾けるとブロックは左右に、前後に傾けるとブロックは回転するはずです。上手く操作して遊んでみてください:
2015061902



なお、現状ではかなり雑な操作ロジックにしているので、必ずしも操作しやすいわけではないです(苦笑)。このあたり、controller.js を変更することでもう少し使いやすくしたり、あるいは別の動作(振る、とか)をブロックの操作に変えたりもできるはずです。是非挑戦してみてください。

(プレイ動画)