LoginSignup
2
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-03-24

背景

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

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

仕様

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

コード

ブロックオブジェクト

  • 位置情報、色情報、ポーズ情報を保持
  • 実態としてマップは参照しない
//色は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]];}
      };
    };

ゲームオブジェクト

  • メインのオブジェクト
  • 描画先マップ、仮想のマップ、ブロック、ゲーム状態などを保持
    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))// 落下移動がなく削除もない場合は終了
        }
      }
    }

ソース全体

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>
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度目にいろいろな情報が残ってしまう(雑)
  • 細かすぎてどこに落ちて言っているかがわかりにくい
  • 横移動時の反応が悪い気がする
  • 消えた時に一括で消えるのでどう連鎖しているか不明
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1