2011/04/28

種文字列から一定の乱数列を生成する

NILScriptのCipherユニットには、種文字列を指定して乱数列を得るRandomクラスが用意されています。
通常のMath.random()と違うのは、同じ種文字列を指定すると常に同じ乱数列が生成されるという点です。
この機能を利用すれば、ゲームのステージ生成などで、生成されたステージを丸ごと保存しなくても、種文字列を保存しておくだけで過去に生成したステージを再現できるようになります。

下記の例は、Cipher::Randomを利用して迷路を生成するというスクリプトです。
コマンドライン引数に横の広さ、縦の広さ、種文字列を指定すると、迷路が生成され表示されます。
種文字列が省略された場合は、String.random()で生成したランダムな文字列が使用されます。
var w=(Main.params[0]||32)*2-1, h=(Main.params[1]||18)*2-1;
var r=new (require('Cipher').Random)(Main.params[2]||String.random(16)); //(1)
var map=[],joints=[];
//周辺の枠の生成
map.push(('-'.times(w-1)+'+').split(''));
for(var x=2,m=w-2;x<m;x+=2){
    joints.push({x:x,y:0});
    joints.push({x:x,y:h-1});
}
for(var y=1;y<(h-1);++y){
    map.push(('|'+' '.times(w-2)+'|').split(''));
    if(!(y&1)){
        joints.push({x:0,y:y});
        joints.push({x:w-1,y:y});
    }
}
map.push(('+'+'-'.times(w-1)).split(''));

//壁の生成
var j;
while(joints.length){
 //新たに生やし始める箇所を選択する
    if(!j){
        j=joints[r.next(joints.length)]; //(3)
    }
    //壁を延ばす方向の候補を列挙
    var c=[];
    if((map[j.y-2]||[])[j.x]==' '){
        c.push({via:{x:j.x  ,y:j.y-1}, next:{x:j.x  ,y:j.y-2}, mark:'|'});
    }
    if((map[j.y+2]||[])[j.x]==' '){
        c.push({via:{x:j.x  ,y:j.y+1}, next:{x:j.x  ,y:j.y+2}, mark:'|'});
    }
    if((map[j.y]||[])[j.x-2]==' '){
        c.push({via:{x:j.x-1,y:j.y  }, next:{x:j.x-2,y:j.y  }, mark:'-'});
    }
    if((map[j.y]||[])[j.x+2]==' '){
        c.push({via:{x:j.x+1,y:j.y  }, next:{x:j.x+2,y:j.y  }, mark:'-'});
    }
    //延ばせる方向があれば、そのうちのどこかに延ばす
    if(c.length){
        var m=c[r.next(c.length)]; //(4)
        map[m.via.y][m.via.x]=map[m.next.y][m.next.x]=m.mark;
        if(map[j.y][j.x]!=m.mark){
            map[j.y][j.x]='+';
        }
        joints.push(j=m.next);
    }else{
        joints.remove(j);
        j=null;
    }
}
map[1][0]=map[h-2][w-1]=' ';
//表示
for(var y=0;y<h;++y){
    var l=[];
    for(var x=0;x<w;++x){
        l.push(map[y][x]);
        if(x&1){
            l.push(map[y][x]);
        }
    }
    println(' '+l.join(''));
}
println('Seed='+r.seed);
free(r); //(2)

最初に「r」変数にRandomオブジェクトを格納します。(1)
Randomオブジェクトは、不要になったら「free()」でリソースを解放する必要があります。(2)

迷路の生成には、既存の壁から壁を生やして、他の壁にぶつからないように延ばしていくという方式を使用しています。
新たに壁を生やし始める箇所を選択する処理(3)と、壁を延ばす方向を選択する処理(4)で乱数を使用しています。
Randomオブジェクトのnext()メソッドは、Number.random()と同じように、指定した範囲の整数を返します。

上記のスクリプトを「20 10 XYZ」を引数にして実行すると、以下のような迷路が表示されるはずです。
 ---------------------------------------------------------+
                                                          |
 |  ---+  +-----------+  +-----+---  +--------+  +--+  +--+
 |     |  |           |  |     |     |        |  |  |  |  |
 |  +--+  |  +-----+  |  |  |  +-----+  +--+  |  |  |  |  |
 |  |     |  |     |     |  |           |  |  |  |  |  |  |
 |  +--+  +--+---  +-----+  +--------+  |  |  +--+  +--+  |
 |     |                             |  |  |           |  |
 |  +--+  +--------+  +-----+-----+--+  |  +--+  +--+  |  |
 |  |     |        |  |     |     |     |     |  |  |     |
 |  +-----+  +-----+  +--+  |  +--+  +--+--+  +--+  +--+  |
 |           |           |  |  |     |     |           |  |
 +--+  ---+  +--------+  |  |  +-----+  +--+  +-----+  |  |
 |  |     |           |  |  |           |     |     |  |  |
 |  +--+  |  +---  +--+  |  |  |  +-----+  +--+  +--+  |  |
 |     |  |  |     |  |     |  |  |        |  |  |     |  |
 |  ---+--+  +-----+  +-----+--+  +---  +--+  |  +-----+  |
 |                                      |
 +--------------------------------------+------------------

0 件のコメント:

コメントを投稿