Little Strange Software

スマホアプリの開発を行う LittleStrangeSoftware のブログです。

【JavaScript】落ち物パズル制作? その4.5【コードと部分的な解説】

 どうも!LSSです!!

 

作り始めてから一週間ほどで、ひとまずゲームとして遊べる形になりました^^

 

 今回はその時に省略したコードの紹介と部分的な解説になります。
※つまり、わけわからん事を書きますwww

 

  

コード全文

<style><!--
#gamen{
position:relative;
background-color: lightblue;
background-image:radial-gradient(circle at 70% 30%,#ffffffc0,transparent);
}
.km{
position:absolute;
border-radius:5px;
text-align:center;
border-width:3px;
border-style:outset;
background-image:radial-gradient(circle at 70% 30%,#ffffffc0,transparent);
}
.c1{background-color:yellowgreen;border-color: yellowgreen;}
.c2{background-color:pink;border-color: pink;}
.c3{background-color:aqua;border-color: aqua;}
.c4{background-color:dodgerblue;border-color: dodgerblue;}
.c5{background-color:salmon;border-color: salmon;}
.c6{background-color:orange;border-color: orange;}
.c7{background-color:violet;border-color: violet;}
.c8{background-color:beige;border-color: beige;}
.c9{background-color:gold;border-color: gold;}
.btnlr{
display:inline-block;
position:relative;
width:110px;
background-color: lightcyan;
text-align:center;
border-radius:5px;
border:5px outset lightcyan;
background-image:radial-gradient(circle at 70% 30%,#ffffffc0,transparent);
}
.btndp{
display:inline-block;
position:relative;
background-color: orangered;
text-align:center;
border-radius:5px;
border:5px outset orangered;
background-image:radial-gradient(circle at 70% 30%,#ffffffc0,transparent);
}
--></style>
<div id="gamen"> </div>
<div id="g2"> </div>
<p>
<script>// <![CDATA[
wMax=7;//横マス数
hMax=11;//縦マス数
kms=9;//コマ種類数
sz=40;//1マスあたりのサイズ
sy=5;//落下速度調節用
psf=false;//一時停止管理フラグ

scr=0;//スコア
fws=0;//花の数
masu=[ ];
for(i=0;i<wMax;i++){
 masu[i]=[ ];
 for(j=0;j<hMax;j++){
  masu[i][j]=0;
 }
}
w=wMax*sz;//画面幅
h=hMax*sz;//画面高さ
document.getElementById("gamen").style.width=w+'px';
document.getElementById("gamen").style.height=h+'px';
mx=0;//操作中コマ横位置
my=0;//操作中コマ縦位置
mz=0;//操作中コマ種類
mz_n=0;//NEXTコマ種類
gamen_i();

function tmc(){
 if((my<h-sz) && (masu[mx][Math.floor(my/sz)+1]==0)){
  my+=sz/sy;
  kmv();
 }else{
  masu[mx][Math.floor(my/sz)]=mz;
  clearInterval(tm);
  kensa();
  tms();
  if(my<sz){
   clearInterval(tm);
   document.getElementById("gamen").style.backgroundColor='gray';
   txt='ゲームオーバー<br/>';
   txt+='スコア:'+scr+' 花:'+fws;
   g2.innerHTML=txt;
  }else{
   kminit();
   gamen_w();
   kmv();
  }
 }
}

function tms(){
 tm=setInterval("tmc()",200);
}
function rakka(){
 while(masu[mx][Math.floor(my/sz)+1]==0){
  scr++;
  my=Math.floor(my/sz)*sz+sz;
 }
}

function gamen_i(){
txt='<a href="#!" onclick="tms();kminit();gamen_w();return false;">ここをクリックしてスタート!</a>';
txt+='<br/><br/><br/><a href="#!" onclick="kms=4;tms();kminit();gamen_w();return false;">イージーモード</a>';
gamen.innerHTML=txt;
}
function gamen_w(){
txt='';
txt='<div style="position:absolute;height:'+sz*0.6+'px;font-size:'+sz/4+'px;top:0px;left:0px;padding:3px 3px;">Score:'+scr+' 花:'+fws+'</div>';
txt+='<div class="km c'+mz_n+'" style="height:'+sz*0.6+'px;font-size:'+sz/4+'px;top:0px;right:0px;padding:0px 10px;">NEXT '+mz_n+'</div>';
for(i=0;i<wMax;i++){
 for(j=0;j<hMax;j++){
  if(masu[i][j]>0){
   txt+='<div class="km c'+masu[i][j]+'"  style="top:'+j*sz+'px;left:'+i*sz+'px;width:'+sz+'px;height:'+sz+'px;">'+masu[i][j]+'</div>';
  }
 }
}
txt+='<div id="koma" class="km c'+mz+'" style="top:'+my+'px;left:'+mx*sz+'px;width:'+sz+'px;height:'+sz+'px;">'+mz+'</div>';
gamen.innerHTML=txt;
txt2='';
if(psf){
 txt2+='<a href="#!" onmousedown="tms();psf=false;gamen_w();">再開</a> ';
}else{
 txt2+='<a href="#!" class="btnlr" onmousedown="mx-=(mx>0 && masu[mx-1][Math.ceil(my/sz)]==0?1:0);kmv();" ondoubleclick="return false;">←に移動</a>';//何故かonclickだと反応が鈍い
 txt2+='<a href="#!" class="btnlr" onmousedown="mx+=(mx<wMax-1 && masu[mx+1][Math.ceil(my/sz)]==0?1:0);kmv();" ondoubleclick="return false;">→に移動</a>';
 txt2+='<a href="#!" class="btndp" onmousedown="rakka();kmv();" ondoubleclick="return false;">落とす</a> ';
 txt2+='<br/>';
 txt2+='<br/>';
 txt2+='<a href="#!" onmousedown="clearInterval(tm);psf=true;gamen_w();">一時停止</a>';
}
g2.innerHTML=txt2;
}

function kmv(){
 document.getElementById('koma').style.top=my+'px';
 document.getElementById('koma').style.left=mx*sz+'px';
}

function kminit(){
 mx=3;
 my=0;
 do{
  mz=mz_n;
  mz_n=Math.floor(1+Math.random()*kms);
 }while(mz==0)
}

function kensa(){
 ksloop=1;
 do{
  kscnt=0;//消しカウント
//判定配列ks初期化
  ks=[ ];
  for(i=0;i<wMax;i++){
   ks[i]=[ ];
   for(j=0;j<hMax;j++){
    ks[i][j]=0;
   }
  }
//全マス検査開始
  for(i=1;i<wMax-1;i++){
   for(j=1;j<hMax-1;j++){
    if(masu[i][j]>0 && masu[i-1][j]==masu[i+1][j] && masu[i][j-1]==masu[i][j+1] && masu[i-1][j]==masu[i][j-1] && masu[i-1][j]>0 && masu[i-1][j]!=masu[i][j]){
     ks[i][j]=1;
     ks[i-1][j]=1;
     ks[i+1][j]=1;
     ks[i][j-1]=1;
     ks[i][j+1]=1;
     kscnt++;
     fws++;
    }
   }
  }
  for(i=0;i<wMax;i++){
   for(j=0;j<hMax;j++){
    if(ks[i][j]>0){masu[i][j]=0;scr+=kscnt*kscnt*100*ksloop;}
   }
  }
  gamen_w();

//消滅後落下
  if(kscnt>0){
   ksloop++;
   for(i=0;i<wMax;i++){
    for(j=hMax-1;j>0;j--){
     if(masu[i][j]==0){
     k=j;
     while(masu[i][j]==0 && k>=0){
      k--;
      if(masu[i][k]>0){
      masu[i][j]=masu[i][k];
      masu[i][k]=0;
     }
    }
   }
  }
 }
 gamen_w();
}
}while(kscnt>0)
}
// ]]></script>
</p>

  

 

functionの役割

コードのところどころに、

function ○○(){
処理
}

みたいなのが出てきます。

 

これは「関数」ですが、使われ方としては関数というより「処理をひとまとめにしたもの」として使用しています。

 

例えば、

function gamen_w(){

で始まる一連の処理は「画面を書き出す」という処理をずらずらと書いたもので、これを用意しておくと、違うところから
gamen_w();

と呼び出すだけで、画面の再描画が行われます。

 

 

このコードで使っている関数とその役割

function tmc()

ゲーム動作中、0.2秒毎に呼び出している関数です。

時間経過によってコマがゆっくり落ちていく処理と、着地判定をここで行っています。

着地時には「消しの判定」であるkensa()を呼び出したりしているのもここからです。

 

function tms()

ゲームスタート時に「0.2秒毎にtmc()を呼び出す」タイマーをセットしている関数です。

 

function rakka()

「落とす」ボタンを押した時の処理を行う関数です。

着地判定と1マスごとにスコア+1をここで行っています。


function gamen_i()

ゲームが始まる前の「ここをクリックしてスタート」を書き出す関数です。

 

function gamen_w()

先に書きましたが「画面を書き出す」関数です。

その時点での変数の状態に応じて画面を作るという処理を行っています。

 

function kmv()

操作中のコマの移動処理を行う関数です。

操作中のコマの座標を書き換えるだけの処理で、gamen_w()で全てを描き直すよりも処理がスムーズになります^^


function kminit()

コマの初期化を行う関数です。

ゲーム開始時と、コマが着地して、「次のコマ」を用意する時に呼び出しています。


 mx=3;
 my=0;
 do{
  mz=mz_n;
  mz_n=Math.floor(1+Math.random()*kms);
 }while(mz==0)

mx(コマの横位置)を3にセット、my(コマの縦位置)を0(一番上)にセット。

「mz_n’(NEXTのコマ種類番号)をmz(操作するコマの種類番号)にコピーして、mz_nには新たに乱数で次のコマ種類番号をセット」をmzが0以外の数値になるまで繰り返し行う(ただし、最初から0以上だった場合でも1回は行う)

という事を行っています。

※後半がややこしいのは「ゲーム開始時に操作コマとNEXTコマの両方を決めないといけない&ゲーム中に次のコマに変わった時にも1回はNEXTコマに変更しないといけない」からです。

 

 この「最低でも一回は行う、あとは条件が真である間は何度も繰り返す」という処理を行う時は
do{処理}while(条件)
が適しています^^

 

function kensa()

コマ着地時に呼び出され、「コマを消す条件が成立しているかの判定、成立していたら消す、消した後の積まれていたコマの落下処理」を行う、ゲームルールの要となる処理がここに集中しています。

 

大きく分けて

  • 判定のために配列変数を用意
  • 全てのマスに対して検査(条件が成立していれば前述の配列変数にマークをつける&作った花の数加算)
  • マークをつけたマスを空きマスにする&スコア加算
  • 全てのマスに対して落下判定と落下処理

という処理を行っており、またその4処理を「条件成立数が0になるまで繰り返し」しています。(積み上げたコマが落ちた結果、新たに条件成立するか、の判定のため)

  

 

消し判定の一部を書き換えるだけで別ルールが実現

する構造になっています。

 

 具体的には、

if(masu[i][j]>0 && masu[i-1][j]==masu[i+1][j] && masu[i][j-1]==masu[i][j+1] && masu[i-1][j]==masu[i][j-1] && masu[i-1][j]>0 && masu[i-1][j]!=masu[i][j]){
 ks[i][j]=1;
 ks[i-1][j]=1;
 ks[i+1][j]=1;
 ks[i][j-1]=1;
 ks[i][j+1]=1;
 kscnt++;
 fws++;
}

 ↑この部分ですね。

 

masu[i][j](花の中心となるマス)が0以上(空きマスでない)、

masu[i-1][j](中心の1つ左のマス)の種類がmasu[i+1][j](中心の1つ右のマス)の種類と同じ、

masu[i][j-1](中心の1つ上のマス)の種類がmasu[i][j+1](中心の1つ下のマス)の種類と同じ、

masu[i-1][j](中心の1つ左のマス)の種類がmasu[i][j-1](中心の1つ上のマス)の種類と同じ、

masu[i-1][j](中心の1つ左のマス)が空きマスではなく、

masu[i-1][j](中心の1つ左のマス)がmasu[i][j](花の中心となるマス)と種類が異なる、

…の全てが成立した時に「花」が完成したと判断し、「花」部分の5か所を配列変数ksにマークしています。

 

この部分を書き換えると、違うルールになり、カメさんが考えてくれたルールにする事もできそうです^^ 

 

 

 

 

ってなとこで、今回はこのへんで!

次回もまた、よろしくお願いします^^