8.10 パズルを作る(その4)

今回は Puzzle クラスの残りのメソッドを見ていくことにするね。
はーい。
じゃまずはピースのドラッグが開始された時に呼び出される onDragStarted メソッドからね。
onDragStarted メソッドってピースをクリックした時に呼び出されるんだったよね?
onDragStarted メソッドは PieceLayer クラスの onMouseDown メソッドから呼び出されるから、 正確にはピースの上でマウスのボタンを押した時だね。
onMouseDown メソッド 内の funcOnMouseDownonDragStarted メソッドへの参照になっています。
あそっか。
ちなみに、onDragStarted メソッドで何するんだったか覚えてる?
えっ? え〜っと…なんだったっけ?
あ、なんかピースの色を変えてたよーな?
PieceLayer クラスのテストの時は確かにそうしてたんだけど、 今回はドラッグするピースを一番手前に表示するために onDragStarted メソッドを使うの。
あれ、そーだったっけ?
まぁそれを説明したのってかなり前になるからねー。
§8.3 参照。
確かにだいぶ前だよねぇ。その後色々やったから忘れちゃってた。
それじゃ、スクリプトを見てくね。
うん。

<ピースのドラッグが開始された時に呼び出される onDragStarted メソッド>

function onDragStarted(piece)
{
    // ドラッグするピース以外のピースの重ね合わせ順序を1つずつ下げます
    for(var y=0;y<verticalBlocks;y++)
    {
        for(var x=0;x<horizontalBlocks;x++)
        {
            if(pieceLayers[y][x].absolute > piece.absolute)
                pieceLayers[y][x].absolute--;
        }
    }
    // ドラッグするピースを一番手前に表示されるようにします
    piece.absolute = horizontalBlocks * verticalBlocks;
}

大体何やってるか解る?
ん〜と、ドラッグするピースを一番手前に表示するから、 重ね合わせ順序の absolute プロパティをチェックしたり値を変えたりしてるんだよね?
そうそう。
for ループが二重になってるのは何でだかわかる?
ピース(pieceLayers)が2次元配列になってるからでしょ?
for ブロックの中に pieceLayers[y][x] って書いてあるし。
ん、そうだね。
じゃ for ループの中身がどうなってるかわかる?
えっと、最初の fory0 〜 (verticalBlocks - 1) の間ループしてて、 次の forx0 〜 (horizontalBlocks - 1) の間ループしてるから、 pieceLayers[y][x] で全部のピースをチェックしてるってことかな?
うんうん。
それから if の条件のとこで pieceLayers[y][x]pieceabsolute の値を比べてるみたいだけど… この piece って、ドラッグするピースになってるの?
それは PieceLayer クラスの onMouseDown メソッドを見てみればわかると思うよ。
funcOnMouseUp(this); ってトコ?
そ。funcOnMouseUponDragStarted メソッドへの参照になってるから、 onDragStarted メソッドの引数の piece はこの this になってるってワケだね。
this はこのオブジェクトっていう意味だから…
やっぱり piece はドラッグするピースになるよね。
だね。
じゃあ if の条件に戻って…
pieceLayers[y][x].absolute > piece.absolute だから、 pieceLayers[y][x] (上から y 番目、 左から x 番目にあるピースだよね)の absolute がドラッグするピースの absolute より大きかったら真になるね。
ん。
で、その時は pieceLayers[y][x]absolute の値を1減らしてるってことかな。
※後置デクリメント演算子(--)については §1.6 参照。
for ブロックの中身はそんな感じだね。
う〜ん、やってることは大体わかるんだけど、 なんで pieceLayers[y][x].absolute > piece.absolute が真の時だけ absolute の値を減らしてるの?
んー、それは図にしてみた方がわかりやすいかもね。
例えば…

<左から4番目のピースをドラッグする場合(ピースは全部で7つ)>

こんなふうに7つピースがあって、左から4番目のピースをドラッグするとするね。
うん。
ドラッグするピースを一番手前に表示しようと思ったら、 このピースの absolute の値はいくつにすればいい?
ピースが7つあるんだから、7 にすればいいんじゃない?
そだね。
ドラッグするピースの absolute の値を 7 にすると、こんな感じになるよね。

<左から4番目のピースの absolute7 に設定した状態>

この状態だと absolute7 になってるピースが2つあるから、 最初の状態みたいに absolute1〜7 になるようにしようと思ったら、どうすればいいと思う?
一番右のピースの absolute4 にすればいいんじゃない?
それだと元々 紫のピース より奥にあった 水色のピース青のピース紫のピース より手前に表示されちゃうでしょ。
あ、そっか。
えと、じゃあ 水色のピース青のピース紫のピースabsolute をそれぞれ 4, 5, 6 にすればいーよね?
そうそう。つまり、こうなればいいわけね。

absolute を正しく設定した状態>

で、それぞれのピースの absolute の値は、 ドラッグ前とドラッグ後でこう変わるから…

absolute の変化>

ドラッグ前の absolute の値が piece.absolute (=4) より大きいピースだけ、 absolute の値を 1 減らせばいいの。
なるほど、だから pieceLayers[y][x].absolute > piece.absolute が真になるピースの absolute だけを 1 減らすんだね。
で、最後にドラッグするピースの absolute の値を変更すれば OK。
horizontalBlocks * verticalBlocks になってるみたいだけど、 これって(横方向のピースの数)×(縦方向のピースの数)だから、ピースの個数になるんだよね?
一番手前に表示したい時は absolute をピースの個数に設定すればいいよね。
さっきの例でもドラッグするピースの absolute はピースの個数と同じ 7 に設定してるでしょ。
なるほどね。
それじゃ次はピースのドラッグが終わった時に呼び出される onDragFinished メソッドを見ていくね。

<ピースのドラッグが終了した時に呼び出される onDragFinished メソッド>

function onDragFinished(piece)
{
    with(piece)
    {
        // ピースの x 座標が goalLeft±2 ピクセルの範囲にあって…
        if(.left >= .goalLeft - 2 && .left <= .goalLeft + 2)
        {
            // さらに、ピースの y 座標が goalTop±2 ピクセルの範囲にあれば、
            // ピースが正しい位置に置かれたとみなします
            if(.top >= .goalTop - 2 && .top <= .goalTop + 2)
            {
                // ピースを正しい位置に置いて、
                .setPos(.goalLeft, .goalTop);
                // それ以上動かせなくします
                .enabled = false;
                // 正しい位置に置かれたピース以外のピースの重ね合わせ順序を1つずつ上げます
                for(var y=0;y<verticalBlocks;y++)
                {
                    for(var x=0;x<horizontalBlocks;x++)
                    {
                        if(pieceLayers[y][x].absolute < .absolute)
                            pieceLayers[y][x].absolute++;
                    }
                }
                // 正しい位置に置かれたピースを一番奥に表示されるようにします
                .absolute = 1;
                // 残りのピースの数を1つ減らします
                if(--remainingPieces == 0)
                {
                    // すべてのピースが正しい位置に置かれたら、指定されたシナリオ/ラベルにジャンプします
                    kag.process(storageOnCompletion, targetOnCompletion);
                }
            }
        }
    }
}

結構長いねぇ。
でも半分くらいは前に作った onDragFinished メソッドとおんなじだよ。
ホントだ。最初の方は似てるね。
ところで onDragFinished メソッドで何をやるんだったかは覚えてる?
ん〜と、ピースが正しい位置に置かれてるかどうかチェックするんじゃなかったっけ?
そうそう。じゃピースが正しい位置に置かれてたら?
パズルが完成してるかどうかチェックするんだよね?
そ。完成してたら予め設定しといたラベルにジャンプするんだったよね。
あと、正しい位置に置かれたピースを一番奥に表示するってのもあったよね。
§8.6 参照。
そーいえばそれってピースを見やすくするためにやるんだったね。
onDragFinished メソッドでやることは大体そんなとこだから、 改めてスクリプトを見ていこっか。
そだね。
まず、2つの if の部分は前にもやったからわかるよね?
この if の条件でピースが正しい位置に置かれてるかどうかチェックしてるんだよね。
あと、+ 2 とか - 2 がついてるのは、 ±2ピクセルまでだったらずれててもいいよっていう意味だったよね。
ん、この辺は §8.5 でやったよね。
if ブロックの中の setPos メソッドを呼び出してるとこenabled を設定してるとこも前と同じなんだけど、覚えてる?
えっと、ちょっとずれた位置にピースを置いても正しい位置に置いたってみなしてるけど、 ピースを表示する時にはちゃんとした位置に表示しないといけないから、 setPos メソッドでピースを正しい位置に表示してて、 あとピースは正しい位置に置かれたらそれ以上動かせないようにするから、 enabledfalse に設定してるんだよね。
ん、そのとーり。
じゃここまでは大丈夫だと思うから、その次の for ブロックのとこから見ていくね。
これってさっきの onDragStarted メソッドの中にあった for ブロックと似てるよね。
さっきはドラッグするピースを「一番手前」に表示してたけど、 今度は正しい位置に置かれたピースを「一番奥」に表示してるんだ。
どっちも基本的にやり方は同じだからスクリプトも似てるってワケ。
なるほどねぇ。
えっと、if の条件式は pieceLayers[y][x].absolute < .absolute だけど、 with ブロックの中にあるから pieceLayers[y][x].absolute < piece.absolute ってことだよね。
ん。
これって onDragStarted メソッドif の条件と不等号の向きが逆になってるよね。
あと条件式が真だった時は absolute の値を1増やしてるね。
これも onDragStarted メソッドの時と逆になってるみたいだけど、 今度はピースを一番奥に表示するから逆になってるの?
そうだよ。
ちなみに図にするとこんな感じね。

absolute の変化>

えっと、緑のピース を一番奥にしようと思ったら、 緑のピースabsolute1 にして、 赤のピースオレンジのピース黄色のピースabsolute をそれぞれ 2, 3, 4 にすればいいから…
piece.absolute=4)より absolute の値が小さいピースの absolute を1ずつ増やせばいいってコトなのかな?
そういうこと。
これで for ブロックの後の .absolute = 1; ってのもわかるでしょ?
正しい位置に置かれたピース(piece)を一番奥に表示してるんだよね。
ん、そう。
じゃあその次の if何してるかわかる?
んっと、条件式は --remainingPieces == 0 になってるね。
これって --remainingPieces の左側についてるから、 先に remainingPieces の値を 1 減らしてから 0 かどうかチェックするんだったっけ?
そうだよ。前置デクリメント演算子の場合は、変数の値を先に 1 減らしてから比較するからね。
つまりこう書くのとおんなじなわけね。
※前置デクリメント演算子については §1.6 参照。

// 残りのピースの数を1つ減らします
remainingPieces--;
if(remainingPieces == 0)
{
(以下略)

で、remainingPieces はまだ正しい位置に置かれてないピースの数を表してるから、 それが 0 になってるってことは…
全部のピースが正しい位置に置かれてるってコト?
そ。つまりパズルが完成したってことだね。
じゃあ、その時は kag.process メソッドでパズルが完成した時に実行するシナリオにジャンプするってことだね。
process メソッドについては §7.4 参照。
ん、そういうこと。
これで onDragFinished メソッドは OK かな?
うん。
んじゃ最後は onGiveUp メソッドね。
onGiveUp メソッドって右クリックフックに登録するメソッドだよね?
右クリックでギブアップできるようにするために。
※右クリックフックについては §8.9 参照。
そだよ。
ちなみに今回はギブアップすると、今の状態をリセットしてはじめからやり直すってことにしてるから。
それってギブアップってゆーか、リトライって感じだよね?
まぁね。
もっと複雑なギブアップ処理をしたいって時は、例えば kag.process メソッドを呼び出して、 KAG スクリプトを使ってギブアップ処理を書いたりしても OK だよ。
なるほどね。
とりあえず今回はシンプルに最初からやり直すことにするね。
で、これが onGiveUp メソッドのスクリプト。

<右クリックした時に呼び出される onGiveUp メソッド(右クリックフックに登録しているメソッド)>

function onGiveUp()
{
    // ギブアップするかどうか確認のダイアログボックスを表示します
    if(askYesNo("ギブアップしますか?"))
    {
        // リトライする場合はピースをリセットします
        for(var y=0;y<horizontalBlocks;y++)
        {
            for(var x=0;x<verticalBlocks;x++)
            {
                with(pieceLayers[y][x])
                {
                    // ランダムな位置に配置します
                    .setPos(int (Math.random() * (kag.fore.base.width - pieceWidth)), int (Math.random() * (kag.fore.base.height - pieceHeight)));
                    // 重ね合わせ順序を元に戻します
                    .absolute = x + y * horizontalBlocks + 1;
                    // 動かせるようにします
                    .enabled = true;
                }
            }
        }
        remainingPieces = horizontalBlocks * verticalBlocks;
    }
    return true// デフォルトの右クリックの動作は実行しません
}

わりと複雑そーだね…
まぁやってることはそんなに複雑じゃないから。
とりあえず最初から見ていくね。
うん。
まず最初の if のとこでギブアップするかどうかを訊いてるんだけど、これは大丈夫?
askYesNo って確か「はい」か「いいえ」を選ぶダイアログボックスを表示するメソッドだったよね?
そう。§6.10 で使ったよね。
で、askYesNo メソッドは「はい」が選択されると true、 「いいえ」が選択されると false を返すから…
「はい」が選択されると if ブロックの中身が実行されて、 「いいえ」が選択されると何もせずに true を返すんだね。
「いいえ」が選択されたってことはギブアップしないってことだから、何もする必要ないよね。
だね。
じゃ if ブロックの中身を見ていくね。
ここで最初からやり直す処理をしてるんだけど、コンストラクタでやってる初期化の処理と似てるから大体わかるんじゃない?
えっと、for ループが2つあって、 その中に with(pieceLayers[y][x]) っていうブロックがあるから、 全部のピースに対して setPos メソッドを呼び出したりしてるんだよね?
そうだね。
setPos メソッドの引数はコンストラクタでピースを作った時とおんなじみたいだから、 ピースをランダムな位置に表示してるんだね。
ん。最初からやり直すわけだから、ピースの位置はバラバラになるようにしとかなきゃね。
そだね。
えっと、次の absolute の値コンストラクタでピースを作った時とおんなじだね。
まぁ重ね合わせ順序は別にリセットしなくてもいいかなーと思ったんだけど、 一応初期状態に戻しとくことにしたんだ。
そーなんだ。
あとは enabledtrue に設定してるみたいだけど、 これって動かせなくなってるピースを動かせるようにするためだよね?
そうそう。
ピースは正しい位置に置かれると enabledfalse にして動かせないようにしてたよね。
最後に remainingPieceshorizontalBlocks * verticalBlocks にしてるのは、はじめからやり直すと正しい位置に置かれてるピースが1つもなくなるからだよね?
そ。remainingPieces は正しい位置に置かれてないピースの数だから、 正しい位置に置かれてるピースが1つもないってことは、正しい位置に置かれてないピースの数は (horizontalBlocks * verticalBlocks) 個ってことになるよね。
あと、onGiveUp メソッドが true を返すとどうなるんだったか覚えてる?
デフォルトの右クリックの動作が実行されなくなるんだよね。
§8.9 参照。
ん、つまりパズルをやってる間はデフォルトの右クリックの動作は実行されないってことになるね。
もしパズルをやってる間もデフォルトの右クリックの動作を実行したいって時は false を返すようにしてね。
それじゃこれで Puzzle クラスのメソッドは一通りチェックできたから、 今回はここまでにしとくね。
Puzzle クラスってこれで完成なの?
そうだよ。
次回はパズルを始めたり終わったりするマクロを作って、もうちょっと使いやすくするつもり。
そっか。うん、わかった。
それじゃ、また次回ね。


前へ | TOP | 次へ