Edited at

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

More than 1 year has passed since last update.


背景

前回、蛇のゲームつくったので他のも作ってみようと思った。

そして完成したものは下記

ブロック落とすゲーム

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度目にいろいろな情報が残ってしまう(雑)

  • 細かすぎてどこに落ちて言っているかがわかりにくい

  • 横移動時の反応が悪い気がする

  • 消えた時に一括で消えるのでどう連鎖しているか不明