ケータイFlashゲーム制作レクチャー

第16回パズルゲームレクチャー(2)パズルを完成させる!

2次元の擬似配列をあやつる!

みんな、擬似配列を覚えているかい?以前のレクチャーで、カードをシャッフルさせるために使ったね。今回は、2次元配列に挑戦だ!パズルでも、バリバリ使うよ!がんばるピピィ~!

ピースクリップに対応する変数

前回ステージクリップに、図1のようにインスタンス名を付けてピースクリップを配置したが、次に、各ピースクリップに対応する変数を用意し、この変数に、各ピースクリップが表示するピースが置かれたフレーム番号を格納する。この変数とピースクリップを関連付けるために擬似配列を用いる。これは、以前のレクチャーでカードゲーム(神経衰弱)を作る時に、各カードのスーツ及びナンバーを示すフレーム番号を管理した方法と同じ手法だ。ただし、15パズルのピースは、縦横2次元に動くため、管理方法は、もう少し複雑になる。擬似配列も2次元で考える必要が出て来る訳だ。

各ピースクリップと表示するピースが置かれたフレーム番号を保持する変数の対応をまとめると表1のようになる。下2桁の数字で対応を表していることが、見て取れると思う。この下2桁の数字1桁ずつが、2次元配列の縦横のインデックスを表している。

図1 ステージクリップに配置したピースクリップのインスタンス名(再録)
図1 ステージクリップに配置したピースクリップのインスタンス名(再録)
表1 ピースクリップの各インスタンスとその制御のための変数の対応
インスタンス名対応する変数名設定する初期値
p00v001
p01v012
p02v023
p03v034
p10v105
p11v116
p12v127
p13v138
p20v209
p21v2110
p22v2211
p23v2312
p30v3013
p31v3114
p32v3215
p33v3316

この対応付けは、メインとなるFlashムービー(以下、シーン1)の2フレーム目のフレームアクションによって、次のように行なう。

図2 各ピースクリップと変数の対応付け
図2 各ピースクリップと変数の対応付け
リスト1 各ピースクリップを変数の対応付けるスクリプト
k = 1;
for (i = 0; i < 4; i++) {
 for (j = 0; j < 4; j++) {
  set ("/:v" add i add j, k++);
  tellTarget ("stage/p" add i add j) {
   gotoAndStop (eval ("/:v" add ../../:i add ../../:j));
  }
 }
}

リスト1のように2重のfor文で変数iおよびjをそれぞれ0,1,2,3と変化させて、add演算子によって文字列 "/:v" として連結し変数名とする。これにset関数を使って初期値をセットし、同様に文字列 "stage/p" にi,jを連結してインスタンス名とする。こうして名前の下2桁で対応するピースクリップと変数の組ができる。ここでピースクリップの表示フレームを対応する変数の値で設定すると、図3のように一枚の完成画のようにピースが並ぶことになる。

図3 各ピースクリップを対応する変数の初期値で設定
図3 各ピースクリップを対応する変数の初期値で設定

実際のゲームではここで操作ガイドを表示し、5キーの押下を待って16番目のピースを一枚抜いて空きを作り、シャッフルした上で、ゲーム開始となる。

16番目のピースを一枚抜く

16番目のピースを抜く処理は、シーン1の3フレーム目に置いたボタンのオブジェクトアクションとして記述する(ここでは、ピースのシャッフル処理も行なっているが、それについては後で説明する⁠⁠。

図4 16番目のピースを抜く処理の記述位置
図4 16番目のピースを抜く処理の記述位置
リスト2 16番目のピースを抜くスクリプト
on(keyPress "5"){
 /:i_17=3;        // 空きピースがある縦インデックス
 /:j_17=3;        //      〃     横インデックス
 /:v33 = 17;      // ピースに対応する変数にフレーム番号17をセット
    ・
    ・  (ピースをシャッフルする処理)
    ・
}

ピースを抜いた所は、本来は空きとなるが、実際にはピースクリップの17フレーム目に置いた黒(空き)のイメージと置き換える。

変数i_17とj_17は、現在黒(空き)のイメージが表示フレームとなっているピース位置の縦横のインデックス(0~3)を保持する。ここに16番目のピース位置(3,3)を記録し、変数v33に17をセットする。このため、ピースp33の表示フレームは17フレーム目となり、空きフレームとなる。

ピースを移動させる

シャッフル後、シーン1の4~6フレームでループを作る。このループがゲームのメインループとなる。ピースを移動するためのキー入力は、このループ内に置いたボタンのオブジェクトアクションとして記述し、リスト3のようになる(リスト3は、2キーが押下された場合のスクリプト例であるが、4キー、6キー、8キーの押下時も同様なスクリプトとなる。それぞれの詳細は、実際のflaを参照されたい⁠⁠。

リスト3 2キー押下時にピースをスライドさせるスクリプト
on(keyPress "2"){
 if(/:i_17<3){
  old=/:i_17++;
  tellTarget("stage/p" add /:i_17 add /:j_17){
   gotoAndStop(17);
  }
  set("/:v" add old add /:j_17,
                  eval("/:v" add /:i_17 add /:j_17));
  set("/:v" add /:i_17 add /:j_17,17);
  tellTarget("stage/p" add old add /:j_17){
   gotoAndStop(eval("/:v" add ../../:old add /:j_17));
  }
  tellTarget ("stage/p" add i_17 add j_17) {
   /guide:_x = _x+30; 
   /guide:_y = _y+30;
  }
  call("checkStage");
  if(/:checkSts == 1){
   gotoAndStop("stage_ed");
  }
 }
}

2キーが押下された時、空きピースの下にあるピースを上方にスライドさせる。これは先にも記したように空きピースも実はピースクリップの17フレーム目を表示しているピースであるため、実際には上下の表示ピースの交換という形を取る。

ここで、処理の最後にcheckStageというサブルーチンcallしているが、これはスライドの結果すべてのピースが定位置に揃ったか否か(すなわち"パズルの完成")を判定するための関数である。

すべてのピースが揃ったか確認する

すべてのピースが揃ったか否かの判定は、シーン1の9フレーム目に設定したフレームアクションによって判定する。このフレームは、checkStageというラベルが設定されており、他のフレームやオブジェクトのアクションからcall命令により呼び出すことにより、サブルーチンとして機能する。

図5 checkStageサブルーチンの記述位置
図5 checkStageサブルーチンの記述位置
リスト4 checkStageサブルーチンのスクリプト
/:checkSts=1;
k=1;
for(i=0;i<4;i++){
 for(j=0;j<4;j++){
  if(eval("/:v" add i  add j ) != k++){
   if( i != /:i_17 || j != /:j_17){
    /:checkSts=0;
    i=4;
    j=4;
   }
  }
 }
}

戻り値としての変数checkStsにあらかじめ"1(=すべて一致)"をセットしておき、2重のfor文によって変数v00~v33の値を確認する。空きピースのセル以外に初期値と異なる値が発見された場合、checkStsを"0(=不一致あり)"にリセットする。サブルーチンを抜けるまで、checkStsが1のままなら、パズルは完成されているという訳だ。

ピースをシャッフルする

14、15番目のピースの入れ替わりを防止しつつシャッフルする

最後に、先送りにしておいたピースのシャッフルを説明しよう。リスト5を見ればわかる通り、ピースのシャッフルは適当な回数(ここでは200回⁠⁠、ランダム(乱数)にピースの移動を行ない、それによってシャッフルを実現している。

こんなことをしなくても、以前カードゲーム(神経衰弱)でやったようにランダムで2つのピースを選び出し交換するという事を繰り返せば良さそうに思うかも知れない。ただ、残念ながら、今回はそう簡単な方法でシャッフルすることはできない。なぜなら、⁠14-15問題」があるからだ。前回説明したように、15パズルは、14番目と15番目のピースのみを入れ替えた場合、決して解くことができない。この問題に陥らないために、実際にピース(と対応する変数の値)をスライドさせながら、パズルをシャッフルさせているのである。

リスト5 ピースをシャッフルするスクリプト
for(k=0;k<200;k++){
  switch(random(4)){
  case 0:
   if(/:i_17>0){
    old=/:i_17--;
    set("/:v" add old add /:j_17,
                  eval("/:v" add /:i_17 add /:j_17));
    set("/:v" add /:i_17 add /:j_17,17);
   }
   break;
  case 1:
      ・
      ・  (同様に乱数値が1~3の時、残る3方向にスライド)
      ・
  }
 }
 for(i=0;i<4;i++){
  for(j=0;j<4;j++){
   tellTarget("stage/p" add i add j){
    gotoAndStop(eval("/:v" add ../../:i add ../../:j));
   }
  }
 }
16回目はここまで!Flashは、今回のようにプログラムライクな作り方も出来るのだ!次回は、この15ゲームを元にオリジナルゲームを考察するよ! ピピ!

おすすめ記事

記事・ニュース一覧