3-1.プログラムの構造を作る

最初に構造を作ると、後の作業が楽になる

samplejpg

プログラムの開発手法には色々なものがありますが、最初に処理の流れを決め、後から詳細を検討してコーディングしていく方法をトップダウン手法と呼びます。

多人数での大規模開発では、トップダウン手法を用いると、開発に携わる人達が全体像を把握した上で作業できるため、仕様上の不整合が発生し辛い、というメリットがあります。

しかし、個人がプログラムを作成する場合にも、プログラミングを数日に分けて行うことになった時に、次の作業がやりやすくなる、というメリットがあります。

このため、本章のサンプルゲーム作成では、トップダウン的な手法を用いる事とし、最初にプログラムの大体の構造を決めておく事にします。

画面表示と操作方法

プログラムの構造を決めるためには、それ以前に、どのようなプログラムにするのか、という事を決めておかなければなりません。 このため、ゲームの大体の仕様は最初に決めておく事にします。

まず、作成する15パズルは、スマートフォン/タブレットデバイスでも遊べるようにする事にします。 このため、画面サイズはPCで遊ぶ場合には小さめになりますが、48x48のピースを使用し、両端と間に1ピクセル置く事にします。 つまり、画面サイズは縦横共に、48*4+5 = 197ピクセルにします。 また、操作方法に関しても、スマートフォン/タブレットデバイスを考慮して、全てマウスクリックで行えるようにします。

パズルの各スライドには写真に数字を追加した画像を表示することにします。 写真は4枚用意し、パズルをクリアする毎に次の写真に切り替え、最後まで行くと最初に戻します。 音声出力をサポートしているデバイスでは、効果音も鳴るようにしておきます。

プログラムの構造

///////////////////////////
// グローバル変数の定義
///////////////////////////
var canvas1,ctx1;
var canvas2,ctx2;
var phase_load = 0;
var phase_init = 1;
var phase_idle = 2;
var phase_anime = 3;
var phase_finish = 4;
var phase;
var picture;
var pieceno,piecedata;
var lastpieceno,animeseq;
var loaded;
var gameno;
var width = (1+49*4);
var height = (1+49*4);

///////////////////////////
// 初期化
//////////////////////////
onload = function() {
  // 描画コンテキストの取得
  canvas1 = document.getElementById('id_canvas1');
  if (!canvas1 || !canvas1.getContext) {
    alert("本ページの閲覧はHTML5対応ブラウザで行ってください");
    return false;
  }
  ctx1 = canvas1.getContext('2d');
  canvas2 = document.getElementById('id_canvas2');
  ctx2 = canvas2.getContext('2d');
  // ゲームの初期化
  ctx2.fillStyle = "rgb(0,0,0)";
  ctx2.fillRect(0,0,width,height);
  pieceposx = new Array(16);
  pieceposy = new Array(16);
  pieceno = new Array(16);
  piecedata = new Array(16);
  lastpieceno = new Array(16);
  gameno = localStorage.getItem("ishii_15puzzle_no");
  if (gameno == null) {
    gameno = 0;
  } else {
    gameno = parseInt(gameno);
  }
  phase = phase_load;
  loaded = false;
  loadpicture(gameno);
  // インターバルタイマー関数の登録
  var timerID = setInterval('timerfunc()', 50);
  // マウスイベント関数の登録
  canvas2.addEventListener('click',clickfunc,true);
};
/////////////////////////////
// 各種処理関数
/////////////////////////////
function loadpicture(gameno) {
}

function initgame() {
}

function shuffle() {
}

function move(piecex,piecey) {
}

function checkmove(piecex,piecey) {
}

function checkfinish() {
}

function initanime() {
}

function exeanime() {
}

function checkanimeend() {
}

function draw() {
}

function drawfinish() {
}

function playaudio(audioid) {
}

/////////////////////////////
// イベント処理関数
/////////////////////////////
// マウスイベントの処理関数
function clickfunc(event) {
  var rect = event.target.getBoundingClientRect();
 var x = event.clientX - rect.left;
 var y = event.clientY - rect.top;
  var piecex = Math.floor((x-1)/49);
  var piecey = Math.floor((y-1)/49);

  switch (phase) {
  case phase_init:
    if (piecex == 3 && piecey == 3) {
      // 空きマスがクリックされた場合はゲーム番号をリセット
      gameno = 0;
      localStorage.removeItem("ishii_15puzzle_no");
      loaded = false;
      loadpicture(gameno);
      phase = phase_load:
    } else {
      // ゲームを開始
      shuffle();
      draw();
      phase = phase_idle;
    }
    break;
  case phase_idle:
    if (checkmove(piecex,piecey)) {
      initanime();
      move(piecex,piecey);
      playaudio("id_audiomove");
      phase = phase_anime;
    }
    break;
  case phase_finish:
    loaded = false;
    loadpicture(gameno);
    phase = phase_load;
    break;
  }
}
// インターバルタイマーの処理関数
function timerfunc() {
  switch (phase) {
  case phase_load:
    if (loaded) {
      initgame();
      draw();
      phase = phase_init;
    }
    break;
  case phase_anime;
    if (checkanimeend()) {
      if (checkfinish()) {
        gameno += 1;
        if (gameno == 4) {
          gameno = 0;
        }
        localStorage.setItem("ishii_15puzzle_no",String(gameno));
        drawfinish();
        playaudio("id_audiofinish");
        phase = phase_finish;
      } else {
        phase = phase_idle;
      }
  } else {
      exeanime();
      draw();
    }
    break;
  }
}

上記のコードには、各種処理関数の記述が無いため、このままでは動作しませんが、プログラムの構造と大まかな処理の流れは記述されています。

作成しようとしている15パズルは、ゲーム番号に対応した画像ファイルを読み込み、それを元にピースを作成した後、マウスクリックのみによって操作されるようになっています。

ピースの移動時にはアニメーション表示が行われるため、インターバルタイマーも使用されています。 また、画像の読み込み完了時の処理についても、プログラムコードの見通しを良くするために、インターバルタイマールーチン内で行うようになっています。

ゲーム番号はローカルストレージに記憶され、次回のプログラム実行時には前回の続きの画像から表示されます。 また、効果音も鳴らされるようになっています。

上記のように、プログラムの構造と処理の流れを最初に作っておくと、後は各種処理関数を記述するだけで動作するプログラムを完成できます。 このため、以降の作業は気楽に行えるようになります。

ただし、具体的な処理内容を検討せずにプログラムの構造を決めてしまった場合、各種処理関数を記述し始めてから、その構造では上手く行かない事が発覚する場合もあります。 そのような場合には、プログラム構造からの作り直しが必要となり、二度手間になってしまいますので、プログラムの構造を決める際には、具体的な処理内容についてもある程度は検討しておく必要があります。

なお、上記にはHTML/CSS関係の記述がありませんが、これらについては前章で紹介した内容とほぼ同様になるため、割愛しました。