Riot.jsでブロックを落とすゲーム


## 背景
前回、蛇のゲームつくったので他のも作ってみようと思った。
そして完成したものは下記
__ブロック落とすゲーム__
http://hasito.com/block-down/

__※蛇のゲーム__
https://qiita.com/hashito/items/a38a25665ad7befb5483
http://hasito.com/snake/

## 仕様
* ブロックは2個1組で落下で回転可能
* 色はランダムで決定
* 同一色4つがつながった場合に削除
* 下のブロックが消えた場合は落下

### コード

#### ブロックオブジェクト
* 位置情報、色情報、ポーズ情報を保持
* 実態としてマップは参照しない

``` javascript
//色は3種類として、poseは全4種類と定義
    var colors = [0xff0000,0xffff00,0x0000ff];
    var poses  = [[[0,0],[-1,0]],[[0,0],[0,-1]],[[-1,0],[0,0]],[[0,-1],[0,0]]];
    var Blok=(point,pose,cols)=>{
//初期化、とりあえず指定ない場合はランダム
      var _point  = point;
      var _pose   = (pose!=undefined)?pose:Math.floor(Math.random()*poses.length);
      var _colors = (cols!=undefined)?cols:[colors[Math.floor(Math.random()*colors.length)],colors[Math.floor(Math.random()*colors.length)]];
      return {
//Point取得関数
        point   :()=>{return _point; },
//pose取得関数
        pose    :()=>{return poses[_pose];  },
//次のposeへ変更
        poseNext:()=>{
          _pose = (++_pose<poses.length)?_pose:0;
          },
//color取得関数
        colors  :()=>{return _colors;},
//ブロック位置情報取得
        get     :function(){return this._get([0,0]);},
//特定の場所分移動させた場合のPointの取得
        _get    :(p)=>{
          return  [
            [poses[_pose][0][0]+_point[0]+p[0],poses[_pose][0][1]+_point[1]+p[1]],
            [poses[_pose][1][0]+_point[0]+p[0],poses[_pose][1][1]+_point[1]+p[1]]
          ];
        },
//ポイント移動用関数
        set     :(p)=>{_point = [_point[0]+p[0],_point[1]+p[1]];}
      };
    };
```


#### ゲームオブジェクト
* メインのオブジェクト
* 描画先マップ、仮想のマップ、ブロック、ゲーム状態などを保持

``` javascript
    var Game=(view)=>{//view=描画用2次元配列
      var _top_buff = 2;//高さの余分な部分（不要？）
      var _w        = view.length;//幅情報(描画範囲を参照)
      var _h        = view[0].length+_top_buff;//縦幅
      var _map      = false;//マップの実態　初期化されるまではfalse
      var _blok     = false;//ブロックの実態 初期化されるまではfalse
      var _view     = view;//表示用マップ
      var _status   = 0;//ゲームステータス　0：停止,1：ゲーム中,2：ゲーム終了
      var _his      = [];//削除履歴　cnt:連鎖情報,delblock:削除ブロック数,step:何step目情報
      var _step_cnt = 0;//stepカウント
      return {
//削除履歴取得
        get_his:function(){
          return _his;
        },
//表示処理
        drawn:function(){
          _map.forEach((w,wi)=>{
              w.forEach((h,hi)=>{
                if(hi<_top_buff){
                  return;
                }else{
                  _view[wi][hi-_top_buff]=h;
                }
              });
          });
          /*blockの表示 */
          var bp  = _blok._get([0,-_top_buff]);
          var bpc = this.map2block_chk(bp);
          if(bpc[0]===0)_view[bp[0][0]][bp[0][1]]=_blok.colors()[0];
          if(bpc[1]===0)_view[bp[1][0]][bp[1][1]]=_blok.colors()[1];
        },
//ブロック初期化関数　
//戻り値として出現場所にブロックがあるかを返す(呼び出し元で参照しゲーム終了か判断するのに使っています…)
        block_init:function(){
          var p=[Math.floor(_w/2),1];
          _blok=Blok(p);
          return (this.map2block_chk([p])[0]===0);
        },
//マップ初期化関数
        map_init:()=>{
          _map=new Array(_w);
          for(i=0;i<_w;i++){
            _map[i] = new Array(_h);
            _map[i].fill(0);
          }
        },
//マップ情報確認関数
//ポイントの配列で対象箇所にブロック(数値)があるか枠外（false）かを返します
        map2block_chk:function(points,f){
          var ret=[];
          points.forEach(v=>{
            var f1 = _w >  v[0];
            var f2 = 0  <= v[0];
            var f3 = _h >  v[1];
            var f4 = 0  <= v[1];
            ret.push(f1&&f2&&f3&&f4&&(_map[v[0]][v[1]]));
          });
          return ret;
        },
// 削除対象ブロック検索処理
// 戻り値は削除件数
        map2dels:function (){
          var _search=(p,chain,c,zumi)=>{
            var cc = zumi.filter((v)=>{return (v[0]===p[0])&&(v[1]===p[1]);})
            zumi.push(p);
            if((cc.length==0)&& this.map2block_chk([p])[0]&&(_map[p[0]][p[1]]==c)){
              chain.push(p);
              _search([p[0]+1,p[1]  ],chain,c,zumi);
              _search([p[0]-1,p[1]  ],chain,c,zumi);
              _search([p[0]  ,p[1]+1],chain,c,zumi);
              _search([p[0]  ,p[1]-1],chain,c,zumi);
            }
          };
          // 削除処理
          var dels=[];
          _map.forEach((w,wi)=>{
            w.forEach((h,hi)=>{
              var _chain = [];
              var _zumi  = dels.slice(0, dels.length);;
              (h)&&_search([wi,hi],_chain,h,_zumi);
              if(_chain.length>=4){
                dels = _chain.concat(dels);
              }
            });
          });
          return dels;  
        },
// 降下処理
        map2down:function(){
          var cnt=0;
          _map.forEach((w,wi)=>{
            w.forEach((h,hi)=>{
              var f={};
              var np    = [wi,hi+1];
              var npchk = this.map2block_chk([np],f)[0];
              var f1 = h;
              var f2 = (npchk===0);
              if(f1 && f2){
                cnt++;
                _map[np[0]][np[1]]=_map[wi][hi];
                _map[wi][hi]=0;
              }
            });
          });
          return cnt;
        },
//ブロック移動イベント
        left:function(){
          var c=this.map2block_chk(_blok._get([-1,0]));
          if((c[0]===0) && (c[1]===0)){_blok.set([-1,0])}
        },
//ブロック移動イベント
        right:function(){
          var c=this.map2block_chk(_blok._get([1,0]));
          if((c[0]===0) && (c[1]===0)){_blok.set([1,0])}
        },
//ブロック降下イベント
        down:function(){
            var p=_blok.get();
            _map[p[0][0]][p[0][1]]=_blok.colors()[0];
            _map[p[1][0]][p[1][1]]=_blok.colors()[1];
            this.block_init();
        },
//ブロック回転イベント
        space:()=>{
          _blok.poseNext();
        },
//ブロック/マップ初期化関数
        init:function(){
          this.map_init();
          this.block_init();
        },
//ステータスの変更
        statusChange:function(){
          if(++_status>=3){
            _status=0;
            this.init();
          }
        },
//step処理(main)
        step:function(){
          _step_cnt++;
          if(_status!=1) return;
          // 降下処理
          var f = {};
          var c = this.map2block_chk(_blok._get([0,1]),f);
          if((c[0]===0) && (c[1]===0)){
            _blok.set([0,1]);
          }else{
            // 移動できない場合は固定ブロックに変換
            var p=_blok.get();
            _map[p[0][0]][p[0][1]]=_blok.colors()[0];
            _map[p[1][0]][p[1][1]]=_blok.colors()[1];
            if(!this.block_init()){
              _status=2;
            }
          }
          var _delcnt=0;
          do{
            var downs= this.map2down();// 全体降下処理
            var dels = this.map2dels();// 削除対象の検索
            dels.forEach(v=>{
              _map[v[0]][v[1]]=0;
            });
            if(dels.length>0){
              _delcnt++;
              _his.push({step:_step_cnt,cnt:_delcnt,delblock:dels.length});
            }
          }while((dels.length>0)||(downs!=0))// 落下移動がなく削除もない場合は終了
        }
      }
    }

```

### ソース全体

``` html:index.html
<html>
  <head>
    <title>Hello Riot.</title>
    <meta charset="UTF-8"/>
  </head>
  <body>
    <block-down></block-down>
    <script type="riot/tag" src="block-down.tag"></script>
   <script src="riot/riot+compiler.min.js"></script>
    <script>riot.mount('block-down')</script>


  </body>
</html>
```
``` html:lock-down.tag

<block-down>
    <h1>ブロックおとすやつ</h1>
    <h3>ESCキーで開始してください</h3>
    <svg riot-width={ w*15 } riot-height={ h*15 } >
      <g each={d,i in view} transform={('translate('+(i*15)+',0)')}>
        <rect each={d2,i2 in d} x="0" riot-y={i2*15} data-point='{(JSON.stringify({x:i,y:i2}))}' width="15" height="15" title=""  fill="#{('000000'+d2.toString(16)).slice(-6)}"></rect>
      </g>
    </svg>
    <div style="float:right;">
      <h3 each={his}>{step}step目:{cnt}連鎖成功:{delblock}個</h3>
    </div>
  <script>
    var self   = this;
    self.tme   = false;
    self.tme2  = false;
    self.view  = [];
    self.his   = [];

    var colors = [0xff0000,0xffff00,0x0000ff];
    var poses  = [[[0,0],[-1,0]],[[0,0],[0,-1]],[[-1,0],[0,0]],[[0,-1],[0,0]]];
    var Blok=(point,pose,cols)=>{
      var _point  = point;
      var _pose   = (pose!=undefined)?pose:Math.floor(Math.random()*poses.length);
      var _colors = (cols!=undefined)?cols:[colors[Math.floor(Math.random()*colors.length)],colors[Math.floor(Math.random()*colors.length)]];
      return {
        point   :()=>{return _point; },
        pose    :()=>{return poses[_pose];  },
        poseNext:()=>{
          _pose = (++_pose<poses.length)?_pose:0;
          },
        colors  :()=>{return _colors;},
        get     :function(){return this._get([0,0]);},
        _get    :(p)=>{
          return  [
            [poses[_pose][0][0]+_point[0]+p[0],poses[_pose][0][1]+_point[1]+p[1]],
            [poses[_pose][1][0]+_point[0]+p[0],poses[_pose][1][1]+_point[1]+p[1]]
          ];
        },
        set     :(p)=>{_point = [_point[0]+p[0],_point[1]+p[1]];}
      };
    };
    var Game=(view)=>{
      var _top_buff = 2;
      var _w        = view.length;
      var _h        = view[0].length+_top_buff;
      var _map      = false;
      var _blok     = false;
      var _view     = view;
      var _status   = 0;
      var _his      = [];
      var _step_cnt = 0;
      return {
        get_his:function(){
          return _his;
        },
        drawn:function(){
          _map.forEach((w,wi)=>{
              w.forEach((h,hi)=>{
                if(hi<_top_buff){
                  return;
                }else{
                  _view[wi][hi-_top_buff]=h;
                }
              });
          });
          /*blockの表示 */
          var bp  = _blok._get([0,-_top_buff]);
          var bpc = this.map2block_chk(bp);
          if(bpc[0]===0)_view[bp[0][0]][bp[0][1]]=_blok.colors()[0];
          if(bpc[1]===0)_view[bp[1][0]][bp[1][1]]=_blok.colors()[1];
        },
        block_init:function(){
          var p=[Math.floor(_w/2),1];
          _blok=Blok(p);
          return (this.map2block_chk([p])[0]===0);
        },
        map_init:()=>{
          _map=new Array(_w);
          for(i=0;i<_w;i++){
            _map[i] = new Array(_h);
            _map[i].fill(0);
          }
        },
        map2block_chk:function(points,f){
          var ret=[];
          points.forEach(v=>{
            var f1 = _w >  v[0];
            var f2 = 0  <= v[0];
            var f3 = _h >  v[1];
            var f4 = 0  <= v[1];
            ret.push(f1&&f2&&f3&&f4&&(_map[v[0]][v[1]]));
          });
          return ret;
        },
        // 削除対象ブロック検索処理
        map2dels:function (){
          var _search=(p,chain,c,zumi)=>{
            var cc = zumi.filter((v)=>{return (v[0]===p[0])&&(v[1]===p[1]);})
            zumi.push(p);
            if((cc.length==0)&& this.map2block_chk([p])[0]&&(_map[p[0]][p[1]]==c)){
              chain.push(p);
              _search([p[0]+1,p[1]  ],chain,c,zumi);
              _search([p[0]-1,p[1]  ],chain,c,zumi);
              _search([p[0]  ,p[1]+1],chain,c,zumi);
              _search([p[0]  ,p[1]-1],chain,c,zumi);
            }
          };
          // 削除処理
          var dels=[];
          _map.forEach((w,wi)=>{
            w.forEach((h,hi)=>{
              var _chain = [];
              var _zumi  = dels.slice(0, dels.length);;
              (h)&&_search([wi,hi],_chain,h,_zumi);
              if(_chain.length>=4){
                dels = _chain.concat(dels);
              }
            });
          });
          return dels;  
        },
        // 降下処理
        map2down:function(){
          var cnt=0;
          _map.forEach((w,wi)=>{
            w.forEach((h,hi)=>{
              var f={};
              var np    = [wi,hi+1];
              var npchk = this.map2block_chk([np],f)[0];
              var f1 = h;
              var f2 = (npchk===0);
              if(f1 && f2){
                cnt++;
                _map[np[0]][np[1]]=_map[wi][hi];
                _map[wi][hi]=0;
              }
            });
          });
          return cnt;
        },
        left:function(){
          var c=this.map2block_chk(_blok._get([-1,0]));
          if((c[0]===0) && (c[1]===0)){_blok.set([-1,0])}
        },
        right:function(){
          var c=this.map2block_chk(_blok._get([1,0]));
          if((c[0]===0) && (c[1]===0)){_blok.set([1,0])}
        },
        down:function(){
            var p=_blok.get();
            _map[p[0][0]][p[0][1]]=_blok.colors()[0];
            _map[p[1][0]][p[1][1]]=_blok.colors()[1];
            this.block_init();
        },
        space:()=>{
          _blok.poseNext();
        },
        init:function(){
          this.map_init();
          this.block_init();
        },
        statusChange:function(){
          if(++_status>=3){
            _status=0;
            this.init();
          }
        },
        step:function(){
          _step_cnt++;
          if(_status!=1) return;
          // 降下処理
          var f = {};
          var c = this.map2block_chk(_blok._get([0,1]),f);
          if((c[0]===0) && (c[1]===0)){
            _blok.set([0,1]);
          }else{
            // 移動できない場合は固定ブロックに変換
            var p=_blok.get();
            _map[p[0][0]][p[0][1]]=_blok.colors()[0];
            _map[p[1][0]][p[1][1]]=_blok.colors()[1];
            if(!this.block_init()){
              _status=2;
            }
          }
          var _delcnt=0;
          do{
            var downs= this.map2down();// 全体降下処理
            var dels = this.map2dels();// 削除対象の検索
            dels.forEach(v=>{
              _map[v[0]][v[1]]=0;
            });
            if(dels.length>0){
              _delcnt++;
              _his.push({step:_step_cnt,cnt:_delcnt,delblock:dels.length});
            }
          }while((dels.length>0)||(downs!=0))// 落下移動がなく削除もない場合は終了
        }
      }
    }

    /* タイマーセット関数(一定ごとに時を進める用)
    */
    var tm_set=()=>{
      if(self.tme)clearInterval(self.tme);
      self.tme=setInterval(()=>{
        self.tm-=0.1;
        tm_set2(self.tm);
      },1000);
    };
    /* タイマーセット関数(メイン処理用)
    */
    var tm_set2=(v)=>{
      if(self.tme2)clearInterval(self.tme2);
      self.tme2=setInterval(()=>{
        self.game.step();
        self.game.drawn();
        self.his=self.game.get_his();
        self.update();
      },v);
    };
    /* マウント前イベント
    */
    self.on("before-mount",(v)=>{
      self.h=40;
      self.w=20;
      self.view=new Array(self.w);
      for(i=0;i<self.w;i++){
        self.view[i] = new Array(self.h);
        self.view[i].fill(0);
      }
      self.game=Game(self.view);
      self.game.init();
      self.tm=300;
      document.onkeydown = function (e){
        var k = e.keyCode;
        var g = self.game;
        if(k==37){g.left();};//左
        if(k==40){g.down();};//落下
        if(k==39){g.right();};//右
        if(k==32){g.space();}//回転
        if(k==27){g.statusChange();}//game開始
      };
      tm_set();
    });


  </script>
</block-down>

```

## 残課題
* 2度目にいろいろな情報が残ってしまう(雑)
* 細かすぎてどこに落ちて言っているかがわかりにくい
* 横移動時の反応が悪い気がする
* 消えた時に一括で消えるのでどう連鎖しているか不明