Riot.jsでゲームを作る

この記事は [Riot.js Advent Calendar 2016](http://qiita.com/advent-calendar/2016/riotjs) の最終日の記事です。ギリギリの更新ですみません；

## ゲームにも使えるRiot.js

Riot.js、いいですよね。
実用的なアプリで利用されている印象ですが、ゲーム開発にも便利に使えそうだと感じたので、試しに使ってみました。

とりあえず3Dっぽい迷路をうろうろするゲームを作ってみましたが、1フレーム単位でシビアなタイミングが要求されるゲームでなければ非常に実用的だと思います。

デモは↓こちら。
[Plunkerでのデモ](https://embed.plnkr.co/SaDsXJ3o5yRynqHVNMWu/)
![壁にぶつかったときの*OUCH*は譲れないポイント](https://i.gyazo.com/87e4323e5a2cdcf77f13117924f07a78.gif)


## 簡単な解説

 * 迷路データ(0:通路、1:壁、など)を配列に格納
 * 移動用のボタンを用意し、押下時に現在地データ(座標と方角)を書き換え
 * 現在地データと迷路データをもとに、迷路画像などの表示を切り替え
 * Plunker便利(Riot.js関係ない)

## ソース

やっつけ感漂うソースですみません。特に`setToView`ファンクションのベタベタ感が……。

```html:index.html
<html>
  <body>
    <mz-main></mz-main>
    <script type="riot/tag" src="mz-main.html"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/riot/3.0.2/riot+compiler.min.js
"></script>
    <script>
riot.mount('mz-main',{
  current: {
    x:2,
    y:2,
    direction:'E'
  },
  maze: [
//  0:passageway
//  1:wall
//  9:goal
           /*N*/
      [1,1,1,1,1,1,1,1,1],
      [1,1,1,1,1,1,1,1,1],
      [1,1,0,0,0,0,0,1,1],
      [1,1,1,0,1,0,1,1,1],
/*W*/ [1,1,1,1,1,0,1,1,1], /*E*/
      [1,1,1,0,0,0,0,1,1],
      [1,1,1,1,1,1,9,1,1],
      [1,1,1,1,1,1,1,1,1],
      [1,1,1,1,1,1,1,1,1]
           /*S*/
  ],
  view: [
    [0,0,0],
    [0,0,0],
    [0,0,0]
  ],
  message: ""
});
    </script>
  </body>
</html>
```

```html:mz-main.html
<mz-main>
  <img style="position:absolute;top:4px;left:4px;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwAgMAAABDOwEBAAAACVBMVEUAAAD/////AP/gXFgRAAAAA3RSTlP//wDXyg1BAAAAJUlEQVRo3u3BAQ0AAADCoPdPbQ43oAAAAAAAAAAAAAAAAAAAvg05MAABhS0NogAAAABJRU5ErkJggg==">
  <!--20--><img if="{ opts.view[0][2] == 1 }" style="position:absolute;top:4px;left:4px;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwAgMAAABDOwEBAAAACVBMVEUAAAD/AP////+5HGLvAAAAA3RSTlP/AP9p7B++AAAAm0lEQVRo3u3YsQ2DQAAEQfQZhVAS/VjOXMCX4MRVugAIQJuhuXyCS3fZwxYYhmEYhmEYhmEYhuEH4d9h8zo+bHwL/hT8Lvg1C94KXgseBS+z4K3gteBR8NXT53greIVhGIZhGIZhGIZhGD7BKTjMgFNkSXknhaUZcIppKeOlgDgDTtE05doboTgl6j0MhmEYhmEYhmEYhmH4QfgPHchQ+/8y2XcAAAAASUVORK5CYII=">
  <!--22--><img if="{ opts.view[2][2] == 1 }" style="position:absolute;top:4px;left:4px;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwAgMAAABDOwEBAAAACVBMVEUAAAD/AP////+5HGLvAAAAA3RSTlP/AP9p7B++AAAAlklEQVRo3u3YsQ3CMAAAQYsdsSKqKJNYjMOUDJAUkb6D+/4W+PEMDRiGYRiGYRiGYRiG4X/E83PqPn6tceo2Pgp+BzwfAW8F7wWvgOcIeCt4L3gFPEfAW8E7DMMwDMMwDMMwDMPwRWk4pNWRJkvbO2kspaWVZlrbeGkgpnWZpmnbtWkUp0V9EQzDMAzDMAzDMAzD8A/hLxZbbLXR08o2AAAAAElFTkSuQmCC">
  <!--21--><img if="{ opts.view[1][2] == 1 }" style="position:absolute;top:4px;left:4px;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwAgMAAABDOwEBAAAACVBMVEUAAAD/AP////+5HGLvAAAAA3RSTlP/AP9p7B++AAAAWklEQVRo3u3YQQ0AIAwAsQWP+EEPKjEAYcl+S+9fAxezUMAwDMMwDMMwDMMwDLfG+1EKr7g2YBiGYRiGYRiGYRiGYRiGYfiPS4s6HwzDMAzDMAzDMAzDcCN8ABfVAM1V0PxpAAAAAElFTkSuQmCC">
  <!--10--><img if="{ opts.view[0][1] == 1 }" style="position:absolute;top:4px;left:4px;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwAgMAAABDOwEBAAAACVBMVEUAAAD/AP////+5HGLvAAAAA3RSTlP/AP9p7B++AAAA7ElEQVRo3u3aoQ0DQQxE0ZWZC3FJ108UlgJcQkiqDDspd8j7ScA3f2CoZ9YBbonFYrFYLBaLt/HnvJ7j8+JN8IvgJ8GPJrgIToKD4NUEF8FJcBA8Df2Li+AkOAgehr7gIjgJDoJnoa+4CE6Cg+BR6BsugpPgIHgS+o6L4CQ4CB6EFovFYrFYLBaLxWKxWCwWi8VisfhvMHqOo7c8KgQaYFSCoPoFFT8NMCq7UM2GCr4GGJWaqE5FRW4DjMprVJujwr4BRiMFNI9Aw4wGGI1R0AwGDXAaYDQ6QnOnjaEVmngd4MRisVgsFovF2/cFpGO+FroQDfoAAAAASUVORK5CYII=">
  <!--12--><img if="{ opts.view[2][1] == 1 }" style="position:absolute;top:4px;left:4px;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwAgMAAABDOwEBAAAACVBMVEUAAAD/AP////+5HGLvAAAAA3RSTlP/AP9p7B++AAAA7UlEQVRo3u3aPUqEMRhG0eAe/QhWwZUEl+Mq/RkZbCcHBOG+/SnS5j7jGW6Ew+FwOBwOh/8OX+/3exy/7HG/h/Gr4DfA1xPgKXgJ3oCvAXgKXoI34GsAnoKX4A3488nneApegjfgrycf4yl4Cd6Av598iqfgJXgDvj35EE/BS/AG/PPkcDgcDofD4XA4HA6Hw+FwOBwOh/8Vps9x+5anIEApgiKI5RcKP5ScKHZZZqPAR2mRoqblVAq5lJApXls2p2BPUwEaKdg8goYZNAmhMYrNYGiAQ9MfGh3Z3ImGVjTx+nXhcDgcDofD4eP7ADDdswa+58hZAAAAAElFTkSuQmCC">
  <!--11--><img if="{ opts.view[1][1] == 1 }" style="position:absolute;top:4px;left:4px;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwAgMAAABDOwEBAAAACVBMVEUAAAD/AP////+5HGLvAAAAA3RSTlP/AP9p7B++AAAAWUlEQVRo3u3aMREAIAwAsR4e8YMeVGKAobSM+T0OPmajgGEYhmEYhj/gneyKV6QaMAzDMAzDMAzDMAzDMAzDMAzDMAzDMAzDMAzDcAW3Fq/3YBiGYRiG4XIHzWHmM1h1cIAAAAAASUVORK5CYII=">
  <!--00--><img if="{ opts.view[0][0] == 1 }" style="position:absolute;top:4px;left:4px;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwAgMAAABDOwEBAAAACVBMVEUAAAD/////AP/gXFgRAAAAA3RSTlP//wDXyg1BAAAA7UlEQVRo3u3bsRECAQwDQZFQxJfo0qiSHtiImXO+weXy7vP77RH8FvwSvBP8CH4LfgmG6En0JHoSPYmeRE+iJ9GT6En0JHoSPYmeRE+iJ9GT6En0JHoSPYmeRE+iJ9GT6En0JHoSPYmeRE+iJ9GT6HA4HA6Hw+FwOBwOh8PhcDgcDofD4XA4HA6Hw+FwOBwO/wGmcRnN2mhQd4BpREjzRRpOHmAai9JMlQayB5hGwTRHpiH0AabxN83OafB+gGnkT+8F9NhwgOmZg95I6IHlANPTDr0L0aPSAabnLHoLo4e0A0xPePT+R4+HkPz5AqMk9H6B56i0AAAAAElFTkSuQmCC">
  <!--02--><img if="{ opts.view[2][0] == 1 }" style="position:absolute;top:4px;left:4px;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwAgMAAABDOwEBAAAACVBMVEUAAAD/AP////+5HGLvAAAAA3RSTlP/AP9p7B++AAAA8ElEQVRo3u3boRGAABADwWsSBkkplE4PrGPyfsX5fMf3OwN8Cb4FP0lykpwkJ8lJcpKcJCfJSXKSnCQnyUlykpwkJ8lJcpKcJCfJSXKSnCQnyUlykpwkJ8lJcpKcJCfJSXKSnCQnyUny8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8I8wjcts1kaDOpry0YjQ5os0nKTJJo1FbaZKA1ma5tIo2ObINISmCTaNv212ToN3mtrTyN/eC+ixgV4q6JnD3kjogYVeZ+hpx96F6FGJXqToOcvewughjV7h6AnP3v/o8fB8AUGcKJHXD01AAAAAAElFTkSuQmCC">
  <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
  <div>
    ({ opts.current.x },{ opts.current.y })
    { opts.current.direction }
    { opts.message }
  </div>
  <table>
    <thead></thead>
    <tbody>
      <tr>
        <td></td>
        <td><button onclick={ moveToFront }>↑</button></td>
        <td></td>
      </tr>
      <tr>
        <td><button onclick={ turnToLeft }>←</button></td>
        <td><button onclick={ turnToBack }>↓</button></td>
        <td><button onclick={ turnToRight }>→</button></td>
      </tr>
    </tbody>
  </table>
  <script>
    this.on('mount', function(){
      console.log(this);
      this.setToView();
      this.update();
    });
    this.moveToFront = function(e) {
      var d = opts.current.direction;
      var x = opts.current.x;
      var y = opts.current.y;
      if (d == 'N') y--;
      if (d == 'S') y++;
      if (d == 'E') x++;
      if (d == 'W') x--;
      if (opts.maze[y][x] != null && opts.maze[y][x] != 1) {
        opts.current.x = x;
        opts.current.y = y;
        if (opts.maze[y][x] == 9) {
          opts.message = "*GOAL*";
        } else {
          opts.message = "";
        }
        this.setToView();
      } else {
        opts.message = "*OUCH*";
      }
    }
    this.turnToLeft = function(e) {
      var d = opts.current.direction;
      if (d == 'N') opts.current.direction = 'W';
      if (d == 'S') opts.current.direction = 'E';
      if (d == 'E') opts.current.direction = 'N';
      if (d == 'W') opts.current.direction = 'S';
      opts.message = "";
      this.setToView();
    }
    this.turnToRight = function(e) {
      var d = opts.current.direction;
      if (d == 'N') opts.current.direction = 'E';
      if (d == 'S') opts.current.direction = 'W';
      if (d == 'E') opts.current.direction = 'S';
      if (d == 'W') opts.current.direction = 'N';
      opts.message = "";
      this.setToView();
    }
    this.turnToBack = function(e) {
      var d = opts.current.direction;
      if (d == 'N') opts.current.direction = 'S';
      if (d == 'S') opts.current.direction = 'N';
      if (d == 'E') opts.current.direction = 'W';
      if (d == 'W') opts.current.direction = 'E';
      opts.message = "";
      this.setToView();
    }
    this.setToView = function() {
      opts.view[1][0] = opts.maze[opts.current.y][opts.current.x];
      var d = opts.current.direction;
      if (d == 'N') {
        opts.view[0][2] = opts.maze[opts.current.y-2][opts.current.x-1];
        opts.view[1][2] = opts.maze[opts.current.y-2][opts.current.x];
        opts.view[2][2] = opts.maze[opts.current.y-2][opts.current.x+1];
        opts.view[0][1] = opts.maze[opts.current.y-1][opts.current.x-1];
        opts.view[1][1] = opts.maze[opts.current.y-1][opts.current.x];
        opts.view[2][1] = opts.maze[opts.current.y-1][opts.current.x+1];
        opts.view[0][0] = opts.maze[opts.current.y][opts.current.x-1];
        opts.view[2][0] = opts.maze[opts.current.y][opts.current.x+1];
      }
      if (d == 'S') {
        opts.view[0][2] = opts.maze[opts.current.y+2][opts.current.x+1];
        opts.view[1][2] = opts.maze[opts.current.y+2][opts.current.x];
        opts.view[2][2] = opts.maze[opts.current.y+2][opts.current.x-1];
        opts.view[0][1] = opts.maze[opts.current.y+1][opts.current.x+1];
        opts.view[1][1] = opts.maze[opts.current.y+1][opts.current.x];
        opts.view[2][1] = opts.maze[opts.current.y+1][opts.current.x-1];
        opts.view[0][0] = opts.maze[opts.current.y][opts.current.x+1];
        opts.view[2][0] = opts.maze[opts.current.y][opts.current.x-1];
      }
      if (d == 'E') {
        opts.view[0][2] = opts.maze[opts.current.y-1][opts.current.x+2];
        opts.view[1][2] = opts.maze[opts.current.y][opts.current.x+2];
        opts.view[2][2] = opts.maze[opts.current.y+1][opts.current.x+2];
        opts.view[0][1] = opts.maze[opts.current.y-1][opts.current.x+1];
        opts.view[1][1] = opts.maze[opts.current.y][opts.current.x+1];
        opts.view[2][1] = opts.maze[opts.current.y+1][opts.current.x+1];
        opts.view[0][0] = opts.maze[opts.current.y-1][opts.current.x];
        opts.view[2][0] = opts.maze[opts.current.y+1][opts.current.x];
      }
      if (d == 'W') {
        opts.view[0][2] = opts.maze[opts.current.y+1][opts.current.x-2];
        opts.view[1][2] = opts.maze[opts.current.y][opts.current.x-2];
        opts.view[2][2] = opts.maze[opts.current.y-1][opts.current.x-2];
        opts.view[0][1] = opts.maze[opts.current.y+1][opts.current.x-1];
        opts.view[1][1] = opts.maze[opts.current.y][opts.current.x-1];
        opts.view[2][1] = opts.maze[opts.current.y-1][opts.current.x-1];
        opts.view[0][0] = opts.maze[opts.current.y+1][opts.current.x];
        opts.view[2][0] = opts.maze[opts.current.y-1][opts.current.x];
      }
    }
  </script>
</mz-main>
```