言語/Language: ja

JavaScriptでスライドパズルを作った

FlashとかJavaアプレットとかの時代に、あえてJavaScriptオンリーでスライドパズル(15パズル)を作りました。(作ったのはだいぶ前だがwwサイトも引っ越したしw)

完成品はこちら

スライドパズルとは

スライドパズルとか15パズルとか言われるパズルがある。
4×4の正方形の枡の中に、15枚の小さなパネルがでたらめな順番で入っている。16マスなのに15枚なのは、1枚はパネルを動かすための空きだからだ。
パネルを空いたマスにスライドすることを繰り返して、全部のパネルを元通りの順番に並べればゴール。
これをJavaScriptで作ってみよう。ただし、単純に作るだけではすぐできてしまうし、ほかにも作っている人がいっぱいいてつまらないので、ちょっと厳しい要求をつけてみる。

スライドパズルをJavaScriptで作るというのはそれほど珍しくはない。
しかし、見てきた限りでは、以下の欠点がある。
画像を最初に小さく切り分けておく必要がある。たとえば1.jpgから16.jpgなんてものをセコセコと画像編集ソフトで作らないといけない。新しい画像を使いたいときは面倒だ。
マスの数を固定にしている。16マス固定ならば、マスを作るとき律儀に16回同じコードを繰り返すようなコーディングでもいけたが、マス数が可変だったらそうはいかない。
……ならばそんなことをしなくてもいいようなものを作ってやる。

要求仕様

完成品

上の仕様を満たした完成品はこちら。IE7とFireFox3で動作確認している。
具体的な処理はソースを参考のこと。
上に書いた機能に加え、パネル番号の表示切り替えと、画像のソースの選択機能をつけてある。

問題点とその解決法

画像読み込み完了を待つには?

このパズルでは、画像をあらかじめ読み込んでおいて、そのサイズを変数に格納する必要がある。画像の読み込み完了を待たないと、画像サイズが(0,0)となってしまい、まともに動かなくなる。
解決法としては、画像読み込みが完了したかどうかを定期的にチェックし、完了していた(または一定時間が経った)ら次の処理に進むようにすればよい。
パズルのソースから、読み込み完了にかかわる部分を抜き出したものは以下の通りである。

…前略…

/* 画像とその名前 */
var img, imgName;

/* 画像読み込みタイマー、画像読み込みチェック回数とその最大値 */
var imgTimerID;
var checkCount, checkCountMax = 10;

…中略…

/***** 初期化関数 *****/
function init()
{
  …中略…

  /* 画像のロード開始 */
  img=new Image();
  img.src=imgName;

  window.status = "画像読み込み中";

  /* タイマー起動 */
  checkCount = 0;
  clearInterval(imgTimerID);
  imgTimerID = setInterval("loadImage()", 500);
}

/* 画像ロードを待つための関数 */
function loadImage()
{
  checkCount++;
  /* 画像読み込み完了または一定回数実行後は次の処理へ */
  if (img.complete || (checkCount > checkCountMax)) {
    if (img.complete) {
      window.status = "画像読み込み完了";
    } else {
      window.status = "画像読み込み失敗";
      alert("画像が読み込めませんでした。");
    }
    clearInterval(imgTimerID);
    init2();
  }
}

/***** 初期化関数その2 *****/
function init2()
{
…後略…

setIntervalという関数で、loadImageという関数を一定時間(この場合500ミリ秒)ごとに実行するようタイマーを起動する。
わざわざタイマーを使ったのは、単なるループ文にすると、一見何もしていないようでも処理がずっとJavaScriptに費やされ、画像の読み込みが進まない(らしい)からだ。タイマーを使うと、JavaScriptは本当に処理を休むので、その間に画像が読み込まれる。

このloadImageという関数は、画像読み込みが完了したら、setIntervalによって起動されたタイマーを解除し、初期化の続き(init2)を呼び出す。
ただし、何らかの事情で画像が永久に読み込まれない場合に無限ループに陥るのを防ぐため、loadImageの実行回数が一定値を超えても初期化の続きに移るようにしている。

問題の自動生成

初期配置でランダムにパネルを置いた場合、その配置から元の状態に戻せる確率は2分の1らしい。つまり、問題の生成でランダムにパネルを置く方法を使うと、絶対に解けない問題ができてしまうという問題がある。
(ランダムに置いた配置が、解けるかどうかを簡単に判定する方法があればいいのだが。実際はあるらしいがよく知らない……。)
幸い、このパズルでは、パネルをスライドさせても必ず元に戻せることが保証されている。つまり、完成された状態からランダムでパネルをスライドさせていけば、解が確実に存在する問題を作ることができる。

画像を小さいパネルに切り分けるには?

JavaScriptには、画像データを表示させる機能はあっても、画像を編集する機能などない。また、外部の画像処理プログラムを呼び出すこともできない。つまり、本当に切り分けた画像を作ることはできない。
ならどうするか?
答えは「画像の一部だけを表示してやればいい」だ。
今回は、テーブルを用いて解決した。セルの背景として、セルのサイズよりも大きい画像を用い、画像の表示位置をずらせば背景画像の一部しか表示されない。このことをうまく利用する。
マスに入っているパネルの番号によって、各マスの背景画像の表示位置を変える。そうすれば、パネルごとに画像の違う位置が表示されるようになり、見かけ上画像を切り分けたように見える。

パネルの枚数を変化させるには?

単純なダイナミックHTMLのテクニックで、HTMLの一部を書き換えただけ。
具体的には、パネルの枚数に応じてテーブルを作る。そして、テーブルの各セルに対し、固有のIDと、クリックしたときの関数を設定している。
ここで、JavaScriptで生成したタグに対しても、JavaScriptでのアクセスが可能なのがポイント。
たとえば、JavaScriptで次のような文字列を生成し、ソースを書き換えるとする。ここで、numは1とする。

document.all.hoge.innerHTML=
    "<TD ID=cell"+num+" onClick=clickPanel("+num+")></TD>";

あらかじめ、ソース中に<DIV ID=”hoge”></DIV>と書いておくと、上のコードを実行した時点でこのタグの中の部分に<TD ID=cell1 onClick=clickPanel(1)></TD>というタグが埋め込まれる。
このJavaScriptで動的に生成されたタグは、もともとソースに書かれているタグと同じように機能する。
つまり、JavaScriptのコードでdocument.all.cell1と書けばこのタグにアクセス可能であるし、このタグの部分をブラウザ上でクリックしたらJavaScriptのclickPanel(1)という関数が呼び出される。
マスごとにnumの値を変化させて各マスのタグを生成すれば、クリックしたマスをclickPanel()の引数で区別することが可能になる。

IEとFireFoxの両対応について

最初はIE6(!)だけで動作確認していたが、FireFoxで見たらまるで動かなかった。いつの間にかゲイツの罠にはまっていったわけだが、大きく3つの問題点があった。

1つ目、テーブルのTDタグの中にSPANタグを挿入し、中のSPANタグに対してスタイルシートを設定していた。たとえば<TD ID=cell1 onClick=clickPanel(1)></TD>となるところを<TD><SPAN ID=cell1 onClick=clickPanel(1)></SPAN></TD>と書いていた。
しかしこれをすると、IEではちゃんとテーブルが表示されるが、FireFoxでは表示されなくなってしまった。もともと冗長という気はしていたので、これを機にSPANタグを使わないようにした。(これはどっちが正しいんでしょうか?)

また、画像を一部表示させるためにテーブルのセルの背景をずらすという手法を使ったわけだが、最初は、スタイルシートのbackground- position-xとbackground-position-yというプロパティによって、x方向とy方向の背景のずれを設定していた。
ところが、これがIE専用の拡張プロパティだということに、うかつにも気づかなかった。代わりにbackground-positionプロパティで設定すればFireFoxでも動くようになった。

もう1つ、FireFoxではJavaScriptの要素innerTextが使えないらしい。これで地味にはまったww

なぜ作ったのか?

このようなものを公開している方がいることを、ふとしたことで知った。くまパズルというらしい。
画像さえあれば簡単にパズルが作れるということで、いろんなところで使われているようだ。
ところが、これはJavaのアプレットだ。Javaアプレットはプラグインを起動するのに時間がかかるので嫌いなのだ。パズルのルール自体は単純なので、これぐらい(失礼)なら重たいJavaを使わなくても、JavaScriptで十分できるんじゃないか。
このアプレットには、クリアするとご褒美画像を表示したり、暗号化した画像を取り込んだりする機能があるみたいだが、さすがにそれは無理だろう。
しかし、ゲームの本質的な機能だけならJavaScriptで再現できそうだ。
……とふと思って作ってみた。

結局、ルールをまねるのは気が引けるので、ルール部分だけはありきたりのスライドパズルにした。
スライドパズルは、数枚いっぺんに動かしたり、スライドの可不可の判定があったりするので、実はコーディングはくまパズルより難しいのだ。ただ、くまパズルのほうがコーディングは簡単だがゲームとしてはあっちのほうがおもしろいな……

ライセンス

公開しないで使う場合はご自由にどうぞ。ただし何が起こっても責任は取りませんw
自分のサイトで公開したいという奇特な方はご連絡ください。
ダウンロードはこちらのソースをそのまま保存してください。


Copyright © 2009-2017 recyclebin5385 All rights reserved.
Generated by webgen