サンプルコード

注意:ここのサンプルコードは特記がない限りDK2でしか使えません。DK1/3で使いたい場合はメモリアドレスを適宜変更すれば使えるかもしれません。

線形合同法による擬似乱数生成ルーチン

現在のゲーム内時間(RAM $7E00D5)を初期値に線形合同法を利用して擬似乱数を生成します。
なお、このルーチンは漸化式に従って計算しているだけのため乱数としての質はあまりよくないです。乱数の質を高めたい場合はWikipediaの記事などを参考にしてみてください。
 
線形合同法
Xn+1 = ( A×Xn+B ) mod M ( M > A, M > B, A > 0, B ≧ 0 )


コード例

RAMの空き領域をスタック代わりに利用しています。0ページ及び1ページは使用しないほうが好ましいです。
なお、このコードはDレジスタが0であることを前提としたコードです。
Dレジスタが0で無い場合は、スタックへ退避してDレジスタを更新するか、絶対(ロング)アドレス指定を使用してください。
また、コード中のMulti関数は、乗除算のページで紹介している16bit*16bitの計算ルーチンです。別途組み込んでおいてください。ただし、被乗数は$1F04-$1F05、乗数は$1F06-$1F07、結果は$1F04-$1F07にセットするようアドレスを変えておいてください。

今回、乱数の種(seed)は上にも書いた通り、乱数を初期化したときのゲーム内時間を使用します。
また、係数A,Bはそれぞれ0xC1685309,0xE580BD92を使用し、法(M)は0x100000000とします。すなわち、実際には除算を行わず、下位32bitをそのまま次の乱数とします。
この組み合わせで得られる乱数の周期は0x80000000です。実際にプレイする場合においてはまず1周することはありえないので十分な長さでしょう。
 
;乱数の種を取得する。これは最初にこのルーチンを呼び出すときだけ使用する。
LDA $D5        ;下位16bitを取得
STA $1F00      ;
LDA $D7        ;上位16bitを取得
STA $1F02      ;
BRA Calc       ;

Calc:
;下位16bit*0xC1685309
;ただし、この乱数計算において、この掛け算の上位16bitは不要。
;また、第3項以降の計算をする場合はここからはじめる。
LDA $1F00      ;\
STA $1F04      ;|
LDA #$5309     ;|下位16bit*0x5309
STA $1F06      ;|
JSR Multi      ;/
LDA $1F04      ;\
STA $1F08      ;|結果をコピー
LDA $1F06      ;|$1F08-$1F0Bに先ほどの計算結果が入る
STA $1F0A      ;/
LDA $1F00      ;\
STA $1F04      ;|
LDA #$C168     ;|下位16bit*0xC168
STA $1F06      ;|
JSR Multi      ;/
CLC            ;
LDA $1F0A      ;\中位16bit(足し算)
ADC $1F04      ;|$1F04-$1F07に
STA $1F0A      ;/下位16bit*0xC1685309の下位32bitだけが入る

;上位16bit*0xC1685309
;ただし、この乱数計算において、この掛け算の上位32bitは不要。
LDA $1F02      ;\
STA $1F04      ;|
LDA #$5309     ;|下位16bit*0x5309
STA $1F06      ;|この計算の下位16bitだけが必要。
JSR Multi      ;/

;上で得られたものを足して、32bit*32bitの答えを得る。
;ただし、Bは32bitなので下位32bitだけを計算したので構わない。
CLC            ;
LDA $1F0A      ;下位16bit*0xC1685309の上位16bit
ADC $1F06      ;上位16bit*0xC1685309の下位16bit
STA $1F0A      ;これで$1F08-$1F0Bに32bit*32bitの下位32bitが入る

;Bを足し、0x100000000で割る。ただし、実際には割り算をせずに
;足し算の下位32bitを取得しただけで答えが得られる。
CLC            ;
LDA #$BD92     ;\
ADC $1F08      ;|下位16bit同士を足す
STA $1F00      ;/
LDA #$E580     ;\
ADC $1F0A      ;|上位16bit同士と繰り上がり(キャリーフラグ)を足す
STA $1F02      ;/これで$1F00-$1F03に次の乱数値が入る
CLC            ;別の処理でバグを発生しないようきちんとキャリークリア
RTS










掛け算の計算方法

A.16bit*32bit (結果は最大48bit)
例)0x1234が被乗数のとき

0x    |    |1234
0x    |C168|5309
0x    |05E7|7FD4<-0x1234*0x00005309
0x0DC0|9920|    <-0x1234*0xC1680000
0x0DC0|9F07|7FD4
  |    |    └-----0x7FD4をそのまま下ろす
  |    └----------0x05E7+0x9920
  └---------------0x0DC0+繰り上がり

B.32bit*32bit (結果は最大64bit)
例)0x12345678が被乗数のとき

0x    |    |1234|5678
0x    |    |C168|5309
0x    |4153|B4CB|F238<-0x5678*0xC1685309
0x0DC0|9F07|7FD4|    <-0x1234*0xC1685309
0x0DC0|E05B|349F|F238

 
           └--------実際に必要なのはここまで
いずれも掛け算の筆算と同じ方法で計算する。






















SDK3の乱数計算ルーチン

XOR演算とビットシフトを利用したきわめて高速なルーチンです。
なお、SDK3では乱数の種は0x31273127を使用しており、周期は0x7FFFFFFFです。
種に0x0または0x80000000を指定した場合は0を出力し続けます。
計算方法からSで初期化した場合も0x80000000+Sで初期化した場合も同じ乱数列を生成することが容易に分かります。
なお、コードはSDK3に実際に記述されているものそのものです。$80:8C4Fにあります。

コード例
 
LDA $02        ;
STA $1C        ;
ASL A          ;左シフト(2倍)
LDA $04        ;
ROL A          ;左シフト(2倍)
STA $1A        ;
LDA $03        ;
EOR $1A        ;
STA $02        ;
LDA $1C        ;
STA $04        ;
LDA $02        ;
RTS            ;

線形帰還シフトレジスタによる乱数生成ルーチン

ビットシフトとXOR演算を用いたビット単位の乱数生成ルーチンです。
実際にはデジタル回路を用いてハードウェア的に実装するものですが、計算量が多くないのでソフトウェア的にも実装できます。


コード例
フィボナッチLFSRと呼ばれる回路をソフトウェア的に実装します。bit0,bit2,bit3,bit5を順にXORしていった結果を保存し、その17bitを右シフトして16bitの乱数を得ます。
この実装は、種を0にしない限り0x1~0xFFFFまでの全ての値を取ります。つまり、周期は0xFFFFです。(32bitの実装も容易に可能です)
なお、乱数のアドレスは空きRAMの$1F00にしました。

(計算例)
bit0   2 3   5                   15
   1 0 1 0 1 1 0 0 1 1 1 0 0 0 0 1  1<─┐
   └───┴─┴───┴─────────────────────────┘
                   XOR

LDA $1F00         ;
LSR A             ;
LSR A             ;
STA $1F02         ;
EOR $1F00         ;これでbit0とbit2をXORしたことになる
STA $1F04         ;
LDA $1F02         ;
LSR A             ;
STA $1F02         ;
EOR $1F04         ;これでbit0とbit2とbit3をXORしたことになる
STA $1F04         ;
LDA $1F02         ;
LSR A             ;
LSR A             ;
EOR $1F04         ;これでbit0とbit2とbit3とbit5をXORしたことになる
AND $0001         ;bit0だけを残す。これがXOR演算の答え。
CLC               ;
ROR A             ;bit0がキャリーフラグに移動
ROR A             ;キャリーフラグがbit15に移動
STA $1F04         ;
LDA $1F00         ;
LSR A             ;
ORA $1F04         ;bit15を結合
STA $1F00         ;
RTS               ;

 

表引きによる乱数生成

乱数表を用意し、その中から毎回適当な場所の値を取得する方法です。きわめて古典的には、ROMデータ自体を乱数表と見立てて使用していました。
ただしこの方法は空き領域を参照すると同じ値が出続ける、規則的なデータが連続する場所では比較的短い周期で同じ値が繰り返されるといった欠点があります。基本的にこの方法は採用すべきではありません。


コード例
現在のゲーム内時間の下位22bitをアドレスとして取得し、ROM内のそのアドレスにある16bitを乱数として採用します。
実際のRAM上ではROMはバンクC0以降に格納されていることに注意してください。Dレジスタは0を前提にしています。
PHB              ;DBレジスタを退避
LDA $D7          ;ゲーム内時間の上位16bitを取得
AND #$003F       ;ROMのバンクを取得する
CLC              ;
ADC #$00C0       ;0xC0を足す。これで実際に参照すべきバンクが得られる。
PHA              ;DBレジスタには直接値を渡せないのでスタック経由で渡す
PLB              ;DB=0
PLB              ;DB=Bank
LDA ($D5)        ;ゲーム内時間下位16bitをアドレス、DBレジスタをバンクとして16bit読込
PLB              ;DBレジスタを元に戻す
RTS              ;

 

ワールドマップごとにBGMを変える

各ワールドごとに任意のBGMを使用できるようにします。

コード例
 
PHX
LDX $06B1
LDA songtable,x
PLX
AND #$00FF
CMP $1C
BEQ $821B
BRA $820A

実際のROMに組み込む場合はB4:81F2~に次のように組み込んでください。
B4:81F2 DA          PHX
B4:81F3 AE B1 06    LDX $06B1         ;ワールド番号の取得
B4:81F6 BF 00 FF B5 LDA $B5FF00,x     ;曲テーブルからBGM番号を取得
B4:81FA FA          PLX
B4:81FB 29 FF 00    AND #$00FF        ;上位バイトをクリア
B4:81FE C5 1C       CMP $1C
B4:8200 F0 19       BEQ $821B
B4:8202 80 06       BRA $820A

B5:FF00 ワールドマップ用BGMテーブル
要素数は15。ワールド番号の若い順に使用したいBGMのBGM番号(1バイト)を記述する。
原作と同じ設定とする場合は以下のようになる
01 01 01 01 01 01 01 01 01 01 1C 1C 1C 1C 1C
全 W1 W2 W3 W4 W5 W6 W7 W2 W4 L2 L3 L4 L5 L6