水圧ピストン(パスカルの原理) 第三回

~ゲーム仕立てにする~

1.企画

どんなゲームにするか考えます
今回は

  • 右に動物
  • 左に数個の錘、自由に載せたりおろしたりできる
  • 左右の重さが揃ったところでドアが開き、動物が脱出する

という仕様で作ってみることにします

▲TOP

2.HTML作成

実行時画像

表示用のHTMLを作成します

実行時画像

いろいろ作りこみますが、最終的にスタート画面だけ残して非表示にします

▲TOP

3.プログラム解説

(1)main関数

$(main);

function main(){
    $("#start").click( Start );
    $("#clear").click( Start );

    for( var i=0 ; i<5 ; i++ )
    {
	Weight[i] = new CWeight(i);
    }
}
		

main関数では スタート画面をクリックするとスタート関数へ飛ぶというイベントの関連付けと
CWeightオブジェクトの生成を行っています

(2)CWeightクラス

var CWeight = function( id ){
...    
this.target[0].click(function(obj){ return function(){obj.SetState( true );};}(this));
};
		

CWeightクラスのコンストラクタです 前半部は単に変数の初期化なので省略
後半は錘をクリックした時の処理を関連付けています
かなりアクロバティックな記述で、ちょっと目眩がしてきます
感覚的には

    this.target[0].click( this.SetState(true) );
		

とでも記述したいところなのですが 上記のやり方ではまともに作動しません
これに関しては長くなるのであとで詳しく解説します

(3)Start関数

function Start(){
    ...
    //	メイン画面表示
    $("#start").hide();
    $("#main").show();

    //	問題の生成
    var cat = GetRandom(4);
    var man = GetRandom(4);
    if( cat === 0 && man === 0 ){   Cage = new CCage(1,0,0);	    }
    else{			   Cage = new CCage(0,man,cat);    }
    
    Piston = GetStage();

    ...    
 
    Piston.Update();
    Cage.UpdateView();
}
		

スタート画面を消去し、メイン画面を表示しています
その後、ランダムで動物の数を決めています 牛は一匹でも重すぎるくらいなので 出現するときは必ず単独です

(4)GetStage関数

function GetStage(){
    var Result = Array();

    for( var s0 = S_MIN[0] ; s0 < S_MAX - S_MIN[1] ; s0 += 1000 )
    {
	for( var s1 = S_MIN[1] ; s1 < S_MAX - s0 ; s1 += 1000 )
	{
	    var stage = new CPiston;
	    stage.s[0] = s0;
	    stage.s[1] = s1;
	    if( CheckStage( stage ) )
	    {
		Result.push( stage );
	    }
	}
    }
    return Result[ GetRandom( Result.length ) ];    
}
		

Start関数内で右辺の重さが決まったので 今度はシリンダーの大きさを決めます
左右シリンダー断面積の最小値から最大値までを二重for文で網羅的にチェックしていきます
この中で、有意な回答が存在する組み合わせだけが Result 配列に追加されていきます
最終的に Result配列の中からランダムで一つを選択して戻り値とします

function CheckStage( stage )
{
    //	右辺が錘何個と釣り合うか計算
    var w0 = Cage.GetWeight() * stage.s[0] / stage.s[1] / 10000;

    //	四捨五入して整数値に
    var w1 = Math.round( w0 );
    
    //	錘の数が回答可能な範囲外なら不可
    if( w1 < 1 )    return false;
    if( 31 < w1)    return false;

    //	四捨五入結果と元数値の差が大きすぎる場合は不可
    if( w1 < w0 * 0.9 )	return false;
    if( w0 * 1.1 < w1 ) return false;
    
    //	整数個の錘とぴったり吊り合うように 右辺値を微調整
    stage.w[1] = w1 * stage.s[1] / stage.s[0] * 10000;

    return true;
}
		

左右シリンダーの断面積と、右辺の重さが与えられたので、それに釣り合う左辺の錘の重量を求めることができます
実際には左辺の錘の重量は10kg刻みで10kg ~ 310kgの範囲しか取り得ないので それにうまく吊り合うように数値を微調整します
微調整で足りない場合は回答不可としてfalseを返します

(5)クリア判定、クリア処理

CPiston.prototype ={
    Update: function(){
	..
	if( Math.abs( this.h[0] - this.h[1] ) < 0.01 ){	Clear();    }
    }
}		    
		

クリアしたかどうかの判定はCPiston.Update関数で行っています
floatの演算誤差の関係で、左右の高さが完全に一致しない場合があるので
差が十分に小さくなった時点でクリアとしています

function Clear(){
    $("#clear").show();
    $("#door1").show();
    
    $("#w1").css("transition-duration","3s");
    var x = $("#s1").width() + 30;
    $("#w1").css("left",x);
    
    CWeight.lock = true;
}		    
		

クリアーメッセージと、開いたドアを表示し
右辺の動物の位置をずらします
また、錘をいじれないようにロックをかけています

▲TOP

4.ブリッジ関数

プログラム解説 で宿題にしておいた CWeightのコンストラクタ中の処理

var CWeight = function( id ){
...    
this.target[0].click(function(obj){ return function(){obj.SetState( true );};}(this));
};
		

の解説をします
これは、JavaScriptの仕様である
「イベント処理中はthisがオブジェクト自身を参照しない」という仕様への対策です
上のコードは

function OnClick01(obj){
    return function(){  obj.SetState(true);	};
};
this.target[0].click( OnClick01(this) );
		

と書き換えることができます
更に分解すると

function OnClick01(obj){
    return function(){  obj.SetState(true);	};
};
var OnClick00 = OnClick01(this);
this.target[0].click( OnClick00 );
		

こうなります
これは 別の関数を橋渡しにすることで、イベントハンドラがthisを直接参照しないようにしているわけです

▲TOP

5.動作確認

実行時画像

画面左上の錘をクリックすと、左ピストンに重りを載せることができます
左ピストン上の錘をクリックすると、台上にもどすことができます
左辺の重量が右辺とちょうど釣り合うと扉が開き、猫や牛が外にでることができます

▲TOP