3-2.各種処理関数を作る

処理関数を細かく分けると作りやすくなる

samplejpg

前節では本章で作成する15パズルゲームの大体の構造を決めました。しかし、各種処理関数については手付かずの状態でした。

本節では、それらの各種処理関数のコードを記述し、ゲームが動作するようにしてみます。

画像ファイルの読み込み関数

function loadpicture(gameno) {
  picture = new Image();
  picture.onload = function() {
                     ctx1.drawImage(picture,0,0);
                     loaded = true;
		   };
  picture.src = "./pic/" + gameno + ".jpg";
}

上記のコードでは、イメージオブジェクトを作成し、読み込み完了イベント関数を登録してから、画像ファイルのソースパスを指定しています。 読み込む画像ファイルは、htmlファイルがあるフォルダの下のpicフォルダ内に、0.jpg,1.jpg・・・として格納される事を前提としています。

画像ファイルの読み込みが完了すると、canvas1に描画し、loaded変数にtrueを代入します。

ゲームの初期化

function initgame() {
  var pos;

  pos = 0;
  ctx1.font = "bold 14px 'Times New Roman'"; 
  ctx1.fillStyle = "rgb(255,255,255)";
  for (var y = 0; y < 4; y++) {
    for (var x = 0; x < 4; x++) {
      ctx1.fillText(String(pos+1),x*48+40,y*48+40);
      piecedata[pos] = ctx1.getImageData(x*48,y*48,48,48);
      pieceno[pos] = pos;
      pos += 1;
    }
  }
  pieceno[15] = -1;
}

上記のコードでは、canvas1に番号を描画した後、イメージデータを採取しています。 同時にpieceno配列に番号を登録しています。

ここで採取したイメージデータは15パズルのピース描画用に使うことになります。 また、pieceno配列の中にある番号は左上から順番に、どのピースが置かれているかを示すために使うことになります。 このため、開きマスとする15の位置には-1を登録し、ピースが存在しない事を示すようにしています。

ピースのシャッフル

function shuffle() {
  var dirnum;
  var dirx,diry;
  var x,y;
  var movedir;

  dirx = new Array(4);
  diry = new Array(4);
  for (var = 0; i < 500; i++) {
    for (var i = 0; i < 16; i++) {
      if (pieceno[i] < 0) {
        break;
      }
    }
    x = i%4;
    y = Math.floor(i/4);
    dirnum = 0;
    if (x >= 1) {
      dirx[dirnum] = -1;
      diry[dirnum] = 0;
      dirnum += 1;
    }
    if (x <= 2) {
      dirx[dirnum] = 1;
      diry[dirnum] = 0;
      dirnum += 1;
    }
    if (y >= 1) {
      dirx[dirnum] = 0;
      diry[dirnum] = -1;
      dirnum += 1;
    }
    if (y <= 2) {
      dirx[dirnum] = 0;
      diry[dirnum] = 1;
      dirnum += 1;
    }
    movedir = Math.floor(Math.random()*dirnum);
    move(x+dirx[movedir],y+diry[movedir]);
  }
}

上記のコードでは、空きマスの位置を検出後、左右上下にピースがあるかどうかをチェックすることで移動可能なピースを洗い出し、それらの中から移動するピースをランダムに選択して移動させる処理を500回繰り返しています。

15パズルでは、初期状態によっては、解けなくなる場合もあるため、少し大げさですが、上記のように実際に移動を繰り返させて安全な初期状態を作っています。

ピースの移動関係

function move(piecex,piecey) {
  var blankx,blanky;
  var dx,dy;

  for (var i = 0; i < 16; i++) {
    if (pieceno[i] < 0) {
      blankx = i%4;
      blanky = Math.floor(i/4);
      break;
    }
  }
  if (piecey == blanky) {
    dy = 0;
    if (blankx < piecex) {
      dx = 1;
    } else {
      dx = -1;
    }
  } else {
    dx = 0;
    if (blanky < piecey) {
      dy = 1;
    } else {
      dy = -1;
    }
  }
  pieceno[blankx+blanky*4] = pieceno[(blankx+dx)+(blanky+dy)*4];
  pieceno[(blankx+dx)+(blanky+dy)*4] = -1;
  if (piecey == (blanky+dx) && piecex == (blankx+dx)) {
    return;
  }
  move(piecex,piecey);
}

function checkmove(piecex,piecey) {
  var blankx,blanky;

  for (var i = 0; i < 16; i++) {
    if (pieceno[i] < 0) {
      blankx = i%4;
      blanky = Math.floor(i/4);
      break;
    }
  }
  if (blanky == piecey || blankx == piecex) {
    return true;
  }
  return false;
}

function checkfinish() {
  for (var i = 0; i < 15; i++) {
    if (pieceno[i] != i) {
      return false;
    }
  }
  return true;
}

上記のコードでは、ピースの移動と移動できるかどうかの判定関数の処理を記述しています。

15パズルでは、空きマスに隣接したピースの移動が可能です。 上記コードでは、それに加えて空きマスとそのピースの延長線上にあるピースについても、移動可能とし、移動が要求された場合には、一つずつ移動する処理を再帰呼び出しする事で、複数のピースを同時に移動させています。

パズルが解けたかどうかの判定関数では、単純に現在のピースの並びが順番通りになっているかどうかで判定を行っています。

移動アニメーション関係

function initanime() {
  for (var i = 0; i < 16; i++) {
    lastpieceno[i] = pieceno[i];
  }
  animeseq = 0;
}

function exeanime() {
  animeseq += 1;  
}

function checkanimeend() {
  if (animeseq == 6) {
    return true;
  }
  return false;
}


上記のコードでは、移動アニメーション関係の処理を記述しています。 アニメーション用コードの実体はdraw関数に置く事にしたため、ここでのコードは簡潔になっています。

移動アニメーションでは、移動前の位置と移動後の位置を比較し、異なる場合には、その途中の位置にピースを表示するようにします。 移動前と移動後のどちらに近い位置に表示するかはanimeseqの値で判定し、6回に分けてアニメーション表示することにします。

画面表示用の処理関数

function draw() {
  var lx,ly,posx,posy;

  ctx2.fillStyle = "rgb(200,200,200)";
  ctx2.fillRect(0,0,width,height);
  for (y = 0; y < 4; y++) {
    for (x = 0; x < 4; x++) {
      no = pieceno[x+y*4];
      if (no < 0) {
        continue;
      }
      if (no == lastpieceno[x+y*4] || phase != phase_anime) {
        ctx2.putImageData(piecedata[no],1+x*49,1+y*49);
        continue;
      }
      for (var i = 0; i < 16; i++) {
        if (lastpieceno[i] == no) {
          lx = i%4;
          ly = Math.floor(i/4);
          break;
        }
      }
      posx = 1+lx*49;
      posy = 1+ly*49;
      if (lx == x) {
        if (ly > y) {
          posy -= animeseq*8; 
        } else {
          posy += animeseq*8; 
        }
      } else {
        if (lx > x) {
          posx -= animeseq*8;
        } else {
          posx += animeseq*8;
        }
      }
      ctx2.putImageData(piecedata[no],posx,posy);
    }
  }
}

上記のコードでは、canvas2を灰色で塗りつぶしてから、各ピースを描画しています。 この時、アニメーション期間中ではないか、アニメーション対象のピース以外については定位置に描画し、アニメーション対象ピースについては定位置からズレた位置に描画しています。

アニメーション期間中には、この描画位置のズレが変化していくため、ピースが移動しているように表示されます。

その他の処理関数

function drawfinish() {
  var str,tm;
  var imagedata;
  var datasize;

  imagedata = ctx2.getImageData(0,0,width,height);
  datasize = width*height*4;
  for (var pos = 0; pos < datasize;) {
    imagedata.data[pos++] /= 2;
    imagedata.data[pos++] /= 2;
    imagedata.data[pos++] /= 2;
    pos += 1;
  }
  ctx2.putImageData(imagedata,0,0);
  ctx2.fillStyle = "rgb(255,255,255)";
  ctx2.font = "20px 'Times New Roman'";
  str = "Completed!!";
  tm = ctx2.measureText(str);
  ctx2.fillText(str,(width-tm.width)/2,height/2+10);
}

function playaudio(audioid) {
  try {
    document.getElementById(audioid).currentTime = 0;
    document.getElementById(audioid).play();
  } catch (e) {
    document.getElementById(audioid).play();
  }
}

上記のコードでは、パズルが完成した時用に"Completed!!"と表示する処理と、効果音の再生処理を記述しています。

"Completed!!"の表示では、文字列を目立たせるために、その時点で表示している画像を取り込み、輝度を下げる加工を行って再描画した後に、文字列の描画を行っています。 効果音の再生では、データロード前にcurrentTimeが設定されると例外が発生するブラウザがあるため、例外をキャプチャーして動作が停止しないように配慮しています。