見出し画像

疑似乱数発生器を移植する

ローグの疑似乱数は自前の関数rndで生成されている。関数rndは、第1引数で指定された数の剰余を取った疑似乱数値を返す。

--- main.c ---
214: /*
215:  * rnd:
216:  *    Pick a very random number.
217:  */
218: 
219: rnd(range)
220: register int range;
221: {
222:     return range == 0 ? 0 : abs(RN) % range;
223: }

RNはマクロで、ヘッダrogue.hに書かれている。

--- rogue.h ---
35: #define RN (((seed = seed*11109+13849) & 0x7fff) >> 1)

アルゴリズムは線形合同法といって、1949年にデリック・レーマー(Derrick Lehmer)が考案したレーマー乱数生成器を応用したものを使っている。線形合同法とは、ある初期値(シードと呼ばれる)を元に、乗算と加算を使って大きな値を求め、その値の余剰を取った値でシードを更新するアルゴリズム。

よい疑似乱数発生器とするためには、なるべく同じ数が発生しないように循環サイクルを長くする必要があって、乗数と加数のさじ加減が大切になってくる。

以上の基礎知識を元にふたたびマクロRNに書かれている式を見ると、純粋な線形合同法ではないことがわかる。シードの更新は、乗算と加算だけで行っていて余剰を取っていない。おそらくラップ・アラウンドに任せているのだと思う。当時のUNIXのint型だから16ビット長だろうか。

シード値の設定

シードの値はソースファイルmain.cで取得している。

--- main.c ---
93: time(&now);
94: lowtime = (int) now;
95: dnum = (wizard && getenv("SEED") != NULL ?
96: atoi(getenv("SEED")) :
97: lowtime + getpid());

処理はウィザードモードであるかどうかで変わる。変数wizardはウィザードモードを識別する値で、デバッグ時に「なんでもできる魔法使い」を召喚するできる。つまり、デバッグをする人のことだ。

ウィザードは、シード値を任意に指定することができる。同じシード値を指定した疑似乱数発生器は、疑似乱数値を同じ順番で生成する。プレイに再現性が生まれるので、疑似乱数が絡んでいるプログラムにおいてもデバッグが容易となる。

ウィザードではないのは普通のプレイヤーで、その場合はUNIXのエポック時間とプロセスIDを使ってシード値が設定される。ウィザードの場合でも環境変数SEEDの値が得られなければ、こちらのシード値が採用されるようになっている。

移植する

Pythonで関数rndを実装するための一番手っ取り早い方法は、パッケージrandomをインポートして、メソッドrandintを呼び出すこと。

def rnd(range):
    return random.randint(0, range - 1)と

ところが、この実装のままでローグの移植を進めていたら、戦闘ルーチンでプレイヤーの攻撃がまったく当たらない。どうやら疑似乱数値が適切ではないとうまく当たらないように工夫されているらしい。

どうやらオリジナルのソースコードを参考にして、再現する必要がありそう。今回は、関数rndを作り、内部で疑似乱数発生器クラスを呼び出すようにするようにした。ウィザードモードは実装していない。

# rnd:
# Pick a very random number.
seed = int(time.time()) + os.getpid()
def rnd(range):
   global seed

   seed = seed * 11109 + 13849
   r =  seed & 0x7FFF >> 1
   return 0 if range == 0 else abs(r) % range

UNIXのtime(&now)に相当するpythonのコードはパッケージtimeのメソッドtime。戻ってくる値が浮動小数点数なので整数値に変換する必要がある。

getpidはパッケージosに同名のメソッドがあるのでそれを利用する。

LinerRandomizerクラスを定義して、派生クラスを作成すれば再利用性は高くなるけれど、そこまでするのは冗長すぎると思う。

class LinearRandomizer(object):
   def __init__(self, seed, multiplier, addend, divisor):
       self.seed = seed
       self.multiplier = multiplier
       self.addend = addend
       self.divisor = divisor
   
   def next(self):
       self.seed = (self.seed * self.multiplier + self.addend) % self.divisor
       return self.seed









この記事が気に入ったらサポートをしてみませんか?