昨日、“ボウリングの紆余曲折 スコア分析画面が進化!”との記事を書いた。

かなりテンションが上がったけど、いやあ、素直にうれしかったね。

結果的には大したコードは書いていないのだけれど、そこはphpに関して“中途半端”なスキルしか身に付けていないボクのこと、色々と“紆余曲折”があり、丸一日かかってしまった。

一番苦労したのは、タイトルにもあるようにphpの“null”と“0”の扱い。

いや、そんなに難しくないのよ、実は。

ただ、ボクが“中途半端”なだけ。

以下に、今回のコードの概略を記す。(必要最低限のコードです。本当は変数の初期化を行ったり、入力が完了していないゲームはカウントしないようにしたり色々あるのですが、その辺は省略しました。)

// SQLの実行結果を連想配列として取得する
while($row = mysql_fetch_assoc($res)){
  // 1~10フレーム
  for($i = 1; $i <= 10; $i++){
    $key = "frame_{$i}_1";
    if($row[$key] == "strike"){
      $rest['head'][$i] = array();
      $score['head'][$i] = 10;
      ++$head;
    }else{
      $pin[$i] = explode(",", $row[$key]);
      if(!in_array(1, $pin[$i])){
        $rest['head'][$i] = $pin[$i];
        $score['head'][$i] = 10 - count($pin[$i]);
        ++$head;
      }else{
        $rest['nohead'][$i] = $pin[$i];
        $score['nohead'][$i] = 10 - count($pin[$i]);
      }
    }
    // 1~10ピン(残ったピン)
    for($j = 1; $j <= 10; $j++){
      if(isset($rest['head'][$i]) && in_array($j, $rest['head'][$i])) ++$sum_rest['head'][$j];
      if(isset($rest['nohead'][$i]) && in_array($j, $rest['nohead'][$i])) ++$sum_rest['nohead'][$j];
      if((isset($rest['head'][$i]) && in_array($j, $rest['head'][$i])) || isset($rest['nohead'][$i]) && in_array($j, $rest['nohead'][$i])) ++$sum_rest['total'][$j];
    }
    // 0~10本(倒れた本数)
    for($k = 0; $k <= 10; $k++){
      if($score['head'][$i] === $k) ++$sum_score['head'][$k];
      if($score['nohead'][$i] === $k) ++$sum_score['nohead'][$k];
      if($score['head'][$i] === $k || $score['nohead'][$i] === $k) ++$sum_score['total'][$k];
    }
  }
  ++$game;
  $sum_head += $head;
}

ね?大したことないでしょ?

あとは$sum_restや$sum_scoreの平均値を求めてround関数で丸めると出来上がり、ってなカンジ。

上記のコードを簡単に説明すると、そもそもmySQLには“frame_1_1”、“frame_1_2”、“frame_2_1”、、、という名前のカラムがあり、それぞれが1フレーム目の1投目、1フレーム目の2投目、2フレーム目の1投目、、、のデータが格納されている。

今回は“1投目に関する分析”なので、“frame_n_1(1≦n≦10)”のカラムを対象とする(4、5行目)。

各カラムには、ストライクなら“strike”という文字、それ以外なら“7,9,10”のように残ったピン番号がカンマ区切りで格納されている。

したがって、“strike”の場合は残りピンの配列($rest)は空、スコア($score)は10となる(6~8行目)。

一方、ストライクでない場合は、カンマ区切りの文字列を$pinという配列に変換し、$pinに1が含まれている場合とそうでない場合(つまりノーヘッドかどうか)で場合分けし、$restに残ったピンの配列、$scoreにスコア(10 - 残ったピンの数)を格納する(11~18行目)。

あとは、$restにj番ピンが含まれていたら$sum_rest[$j]を1つ増やす(22~25行目)、および$scoreがk本だったら$sum_score[$k]を1つ増やす(28~31行目)。

これを繰り返して、全部でj番ピンが何回残ったか($sum_rest[$j])、全部で何回k本倒したか($sum_score[$k])を求めて、最後に平均値を求める(これは省略)。

以上が、概要である。

で、どこにハマったかというと、“isset()”の部分(23~25行目)と“===”の部分(29~31行目)。

一つ目の“isset()”の条件を加えずに計算させたところ、

Warning: in_array() expects parameter 2 to be array, null given in ~

で画面が埋め尽くされてしまった。

「in_array()関数は2つのパラメータが必要だけど、“null”が与えられてますよ。」って警告ですな。

例えばiフレーム目がストライクだった場合、

$rest['head'][$i] = array();

とセットされるので、in_array($j, $rest['head'][$i])の2つ目のパラメータ($rest['head'][$i])が“null”となるワケだ。

つまり、値が含まれているか検査する対象が“ない”状態なので、そりゃムリですな。

そこで、対象が存在することを確認するためにisset()関数が必要になるのである。

また、二つ目の“===”を単に“==”とすると、ノーヘッド時のガーターの確率が1000%を超えるなどのワケの分からない結果になってしまった。

これは、$kが文字としての“0”であっても、$score[$i]の値が空(つまり“null”)のときに自動的にtrueと判断されてしまったからである。

そう、これがまさしくphpの長所でありかつ短所でもあるところなのだが、phpは変数を定義するときに“型宣言”をしない仕様になっている。

例えば、「$a = 1」のよう記述すれば自動的に変数$aは“整数型”となり、あとで「$a = "のぬけす"」と上書きすれば自動的に変数$aは“文字列型”に変換されるのである。

このようにphpでは自動的に型を判断する(および変換する)ため、通常はいちいち変数の“型”を気にすることなく柔軟なコードを記述することができるようになるのである。

phpの“簡単さ”を実現するために開発者側はあえてこのような仕様にしたのであろう。

しかし、である。

この“親切機能”(悪く言うなら“おせっかい機能”?)により、逆に“型”を意識していないと思わぬトラップにハマることがある。

今回ボクがハマった例では、整数型の“0”と文字列型の“”(空、つまり“null”)を比較したときに、phpとしては整数型の“0”を自動型変換して論理型の“false”と解釈し、一方の文字列型の“”も論理型の“false”と解釈し、その結果両者が等しいということで“true”を返してきたのである。

これを回避するためにはいくつかの方法があり、1つは「型キャスト」と呼ばれる仕組みを利用して強制的に型を決めてしまう方法である。

例えば、“(string)$a”としてやれば$aの型は“文字列型”となり、“(int)$a”としてやれば$aの型は“整数型”となる。

つまり今回の場合だと、

(string)$score['head'][$i] == (string)$k

とすることで正しく真偽の判断ができる。

もう1つ(今回採用したのがコレ)は、比較演算子に“===”を使うという方法である。

“==”は値のみを比較するため型が異なっても値が等しければtrueが返されてしまうが、“===”は値と型の両方を比較するため今回の場合ではfalseが返される。

いずれにせよ、変数の比較においてはphpであっても結局は“型”を意識しておかなければならないということである。

これまで、いつも“また、やってもた、、、”の繰り返し。

いい加減、この辺もスマートにこなしていかなアカンのになあ、、、中途半端。orz

まあ、なにはともあれ、“紆余曲折”を経てボウリングスコア分析画面が完成しましたとさ。

、、、寝よ。