TOP > プログラミング関係解説&調査 > 戦闘用乱数の計算

戦闘用乱数の計算

前ページのダメージ計算式の解説の続き。
前ページでは単発ダメージ量の計算について説明したが、ここではダメージ量に加わる乱数での計算値について説明する。
乱数といっても、コンピュータゲームの乱数は計算で作っているので疑似乱数だが、以下では単に乱数とする。

これまでにも何度か触れたが、本作の乱数は、戦闘中で乱数が必要となった時に計算して作る「戦闘用乱数」と、マップ(フィールド)にいる時に1/60秒に1回のサブルーチンで計算されて作られる「マップ乱数」の2種類ある。
ダメージ計算式の中にある「乱数」は、「戦闘用乱数」のことである。

・単発ダメージ:
(((min(max((8+技LV+自LV-敵LV),1),24)*LV差係数/8+(自能力値*係数))*((255-敵能力値)/2)/256)*2+攻撃力)*乱数(1~1.25)
(乱数幅は厳密には単発ダメージ*(0~63)/256)

前ページでは、

min(max((8+技LV+自LV-敵LV),1),24)*LV差係数/8+(自能力値*係数))*((255-敵能力値)/2)/256)*2+攻撃力

の部分について説明をした。
実際には戦闘用乱数から計算された数値が加わり、実際の単発ダメージ量は1~1.25倍のランダムな値になる。
戦闘用乱数の計算については、

(乱数幅は厳密には単発ダメージ*(0~63)/256)

とある通り、

実際の単発ダメージ量 = 単発ダメージ量 + 単発ダメージ*(0~63)/256

である。
「単発ダメージ*(0~63)/256」がどう計算されるのか、まず、本作の戦闘用乱数の計算方法について述べる。

プログラミング関係の表記及び解説 > 乱数で触れたが、本作の戦闘用乱数は、以下のような漸化式で作っている。
※下の計算式はあくまでも「戦闘用乱数」の生成方法で、マップでの乱数の計算方法は別である。マップ乱数生成を参照。

An = ( (An-3 * 256 + An-4 ) / 13) % 256

新たな乱数Anを生成するために、それより1~4つ前の乱数An-1An-4が記憶されており、そこからAn-3An-4を取り出して上のような計算をしている。
%」の記号は、割り算の余りである。つまり「% 256」は「256で割り算した時の余り」である。

戦闘以外、マップ上での乱数は、キャラクターが移動していない時でも常に、マップ乱数生成サブルーチンで計算されている。
一方、戦闘用乱数は、決まった行動をとった時に新たに計算される仕組みである。このため、プレイヤーが何かしら操作しなければ乱数は変動しない。
また、戦闘中、上のとおりに1~4つ前の乱数が保存されているのだが、そのアドレスは、

  • $00:0338(乱数4)
  • $00:0339(乱数3)
  • $00:033A(乱数2)
  • $00:033B(乱数1)

である。
上から順に古い乱数なので、ここでは便宜上、「乱数4」「乱数3」「乱数2」「乱数1」とする。
ゲームを開始した時点では、上のメモリに入っているのは「$00」のみで、戦闘に入ったとしても、乱数が必要となるまでは「$00」で埋まっているようだ。戦闘終了時の乱数は次の戦闘開始時までそのまま持ち越しになるようである。

なお、最初に計算される戦闘用乱数(初期値)については、このページの一番下のおまけ 戦闘用乱数初期値と戦闘中のタイマーに記した。

という点を踏まえた上で、前ページの単発ダメージ量計算のサブルーチンの続きを乗せる。
$C1/782Dで単発ダメージ量が計算終了し、A[$00:0320]に書き込まれている。

$C1/782D STA $20    ;Aを[$00:0320]に書き込み ※単発ダメージ量
$C1/782F LSR A      ;論理右シフト(A/2)
$C1/7830 LSR A      ;論理右シフト(A/2)
$C1/7831 STA $22    ;Aを[$00:0322]に書き込み※1/4単発値
$C1/7833 SEP #$20   ;MフラグON。Aレジスタは8bit幅
$C1/7835 LDA $22    ;Aに[$00:0322]をロード※1/4単発値
;
;符号付8bit x 8bit処理:
$C1/7837 STA $4202  ;Aを被乗数として[$00:4202]にセット
$C1/783A JSR $6150  ;PCの値をスタックにプッシュし[$C1:6150]にジャンプ
;
$C1/6150 PHX        ;Xをスタックにプッシュ
$C1/6151 PHB        ;DBレジスタをスタックにプッシュ
$C1/6152 LDA #$00   ;Aに$00をロード
$C1/6154 PHA        ;Aをスタックにプッシュ
$C1/6155 PLB        ;DBレジスタに値($00)をプル
$C1/6156 LDA $38    ;Aに[$00:0338]をロード
$C1/6158 ORA $39    ;Aと[$00:0339]で論理和
$C1/615A ORA $3A    ;Aと[$00:033A]で論理和
$C1/615C ORA $3B    ;Aと[$00:033B]で論理和
$C1/615E BNE $24    ;ゼロフラグが立っていないとき[$6184]分岐
;
;符号付16bit / 8bit処理:
$C1/6184 LDX $38    ;Xに[$00:0338]をロード
$C1/6186 STX $4204  ;Xを被除数として[$00:4204]にセット
$C1/6189 LDA #$0D   ;Aに$0D(13)をロード
$C1/618B STA $4206  ;Aを除数として[$00:4206]にセット
$C1/618E JSR $46C8  ;PCの値をスタックにプッシュし[$C1:46C8]にジャンプ
;
$C1/46C8 NOP        ;何もしない(符号付16bit / 8bitの処理待機)
$C1/46C9 RTS        ;サブルーチン戻り($C1/618Eの続きへ)
;
$C1/6191 LDA $39    ;Aに[$00:0339]をロード
$C1/6193 STA $38    ;Aを[$00:0338]に書き込み
$C1/6195 LDA $3A    ;Aに[$00:033A]をロード
$C1/6197 STA $39    ;Aを[$00:0339]に書き込み
$C1/6199 LDA $3B    ;Aに[$00:033B]をロード
$C1/619B STA $3A    ;Aを[$00:033A]に書き込み
$C1/619D LDA $4214  ;符号付16bit / 8bitの商(下位バイト)
$C1/61A0 STA $3B    ;Aを[$00:033B]に書き込み
$C1/61A2 PLB        ;DBレジスタに値をプル
$C1/61A3 PLX        ;Xレジスタに値をプル($1B30)
$C1/61A4 RTS        ;サブルーチン戻り($C1/783Aの続きへ)
;
$C1/783D STA $4203  ;Aを乗数として[$00:4203]にセット
$C1/7840 JSR $46C9  ;PCの値をスタックにプッシュし[$C1/46C9]にジャンプ
;
$C1/46C9 RTS        ;サブルーチン戻り($C1/7840の続きへ)
;
$C1/7843 LDA $4217  ;掛け算の結果の上位バイト[$00:4217]をAにロード
$C1/7846 REP #$21   ;Aレジスタは16bit幅, Cフラグクリア
$C1/7848 AND #$00FF ;Aと$00FFで論理積(上位2ビット$00、下位2ビットそのまま)
$C1/784B ADC $20    ;A + $00:0320 ※$00:0320は単発値
$C1/784D STA $20    ;A値を$00:0320に書き込み
$C1/784F SEP #$20   ;Mフラグを立てる Aレジスタは8bit幅
$C1/7851 RTS        ;サブルーチン戻り($C1/D9A4の続きへ)

何が行われているのか、順に見ていく。

$C1/782D STA $20    ;Aを[$00:0320]に書き込み ※単発ダメージ量
$C1/782F LSR A      ;論理右シフト(A/2)
$C1/7830 LSR A      ;論理右シフト(A/2)
$C1/7831 STA $22    ;Aを[$00:0322]に書き込み
$C1/7833 SEP #$20   ;MフラグON。Aレジスタは8bit幅
$C1/7835 LDA $22    ;Aに[$00:0322]をロード

まず、単発ダメージ量が入っているAに対し、$C1/782F$C1/7830で論理右シフトが行われている。
論理右シフトは「÷2」のことなので、2回「÷2」をしたので「÷4」である。
この後に乱数0~255を求めるのだが、それを4で割ることで乱数は0~63の間に収まる。
乱数幅は厳密には単発ダメージ*(0~63)/256」とあるが、ここで予め、4で割っておくことで、乱数幅を0~63にしたのだろう。
つまりここまでで、Aには単発ダメージ量/4が入ったことになる。

;符号付8bit x 8bit処理:
$C1/7837 STA $4202  ;Aを被乗数として[$00:4202]にセット
$C1/783A JSR $6150  ;PCの値をスタックにプッシュし[$C1:6150]にジャンプ
;
$C1/6150 PHX        ;Xをスタックにプッシュ
$C1/6151 PHB        ;DBレジスタをスタックにプッシュ
$C1/6152 LDA #$00   ;Aに$00をロード
$C1/6154 PHA        ;Aをスタックにプッシュ
$C1/6155 PLB        ;DBレジスタに値($00)をプル
$C1/6156 LDA $38    ;Aに[$00:0338]をロード
$C1/6158 ORA $39    ;Aと[$00:0339]で論理和
$C1/615A ORA $3A    ;Aと[$00:033A]で論理和
$C1/615C ORA $3B    ;Aと[$00:033B]で論理和
$C1/615E BNE $24    ;ゼロフラグが立っていない場合[$6184]分岐

$C1/7837から符号付8bit x 8bitの掛け算の処理が始まる。
既に記したとおり、$4202に被乗数をセット、$4203に乗数をセットすると、$4216に掛け算の結果が出力される仕組みである。
$C1/7837STA $4202により、Aの値、つまり単発ダメージ量/4が被乗数としてセットされる。
だが、この後しばらく乗数セットの$4203がない。
単発ダメージ量/4に掛け算したい値は0~255の乱数なので、まず乱数計算をする必要がある。

さて、$C1/6150からの処理であるが、「スタック」というのは一時的に値を保存するメモリ領域の一種のこと。
スタックについて
細長い箱に順番にものを出し入れするようなイメージであり、「最後に入れた(プッシュ)ものが、最初に出る(プル)」という仕組みになっている。
とりあえずは色々出し入れした結果、$C1/6152A$00が書き込まれた。

そして$C1/6156からであるが、まず[$00:0338]が読み込まれている。
先に述べたとおりに、これは4つの乱数の中で一番古い「乱数4」である。
更にこの値に、4つの乱数の残り3つを次々呼び出して、論理和を計算している。
最後の$C1/615Eでは、合計3回の論理和の結果、ゼロフラグが立たない(つまり計算結果が0ではない)時に$C1/6184へ飛ぶように指示されている。

ここで何を処理しているのか。
ビット演算における論理和(論理和 - Wikipedia)は、2進法で2つの値を比較して、「両方、あるいは、どちらかが1なら1とする」計算のこと。
16進数の$12$A0の論理和であったら、$12は2進法で「00010010」、$ABは2進法で「10100000」であるから、両方またはどちらかで1になっている位のみ1として、「10110010」($B2)が論理和になる。

では逆に考えて、論理和を計算して0になる時はどういう時だろうか?
「両方またはどちらかが1なら1とする」なのだから、0になる時は「どちらも0」の時だけである。
つまり、合計3回の論理和で答えが0だったら、$00:0338$00:0339$00:033A$00:033Bすべてが「0」のはずである。
乱数が作られていないのでは乱数での計算ができないので、ゼロフラグが立ったのなら、まず乱数を作るサブルーチンへと移動することになる。
(乱数が作られていない状況というのは、ゲームのロード直後で戦闘用乱数が一度も計算されていないか、疑似乱数を何度も計算した結果全て0になってしまったかのどちらかと思われるが、後者についてはそのような状況になりえるのか、筆者にはよくわからない)
既に乱数が4つ存在している場合、$C1/6184に飛ぶ。

;符号付16bit / 8bit処理:
$C1/6184 LDX $38    ;Xに[$00:0338]をロード
$C1/6186 STX $4204  ;Xを被除数として[$00:4204]にセット
$C1/6189 LDA #$0D   ;Aに$0D(13)をロード
$C1/618B STA $4206  ;Aを除数として[$00:4206]にセット
$C1/618E JSR $46C8  ;PCの値をスタックにプッシュし[$C1:46C8]にジャンプ
;
$C1/46C8 NOP        ;何もしない(符号付16bit / 8bitの処理待機)
$C1/46C9 RTS        ;サブルーチン戻り($C1/618Eの続きへ)

まず、$C1/6184にて、X[$00:0338]、つまり「乱数4」が呼び出されている。
ポイントは、この時Xは16bitモードだということ(ここよりもずいぶん前にXが16bitモードに切り替えされている)。
Xに値を読み込む命令「LDX」は、16bitモードの時、指定アドレスの内容を下位バイト、指定アドレス+1にある内容を上位バイトとして読みこむ。
つまりここでは、Xの上位バイトに[$00:0339]、下位バイトに[$00:0338]が呼び出される。
[$00:0339]は「乱数3」が収納されているため、実質、ここで「乱数3」「乱数4」が同時に呼び出されている。

ただし「乱数3」は上位バイトに呼び出されている。
前ページにも記したが、16進法では、16で割るということは小数点が1つ左に移動することであり、16倍するということは小数点が1つ右に移動することである。
よって、Xに呼び出された「乱数3」は、実際には「乱数3」を162倍(256倍)した数値である。

$C1/6186からは、割り算「符号付16bit / 8bit」の処理が開始される。
乗除算 - 65C816命令表 ロード・ストア命令
にある通り、$4204$4205に被除数をセット、$4206に除数をセットすると、$4214に商の下位バイト、$4215に商の上位バイト、$4216に余りが入る。
[$00:4204]にセットされるのはXの値(16bit)なので、「乱数3」が上位バイト、「乱数4」が下位バイトとしてセットされる。
続いて、除数は$C1/6189にてAにセットされた16進数$0D(10進数の13)である。
$C1/618Bで除数としてセットされ、(おそらく)割り算の計算の時間稼ぎに$C1/46C8に飛び、$C1/46C9で元のサブルーチンに戻る。

ここで何が行われたのか。
乱数の計算式は、

An = ( (An-3 * 256 + An-4 ) / 13) % 256

である。
先に記したが、被除数としてセットされたXは、「乱数3」を162倍した値と、「乱数4」の値の合計である。

An-3 * 256 + An-4

の部分に他ならない。
そして16進数$0D(10進数の13)が除数にセットされたのだから、この割り算は、

(An-3 * 256 + An-4 ) / 13

の計算ということになる。

$C1/6191 LDA $39    ;Aに[$00:0339]をロード
$C1/6193 STA $38    ;Aを[$00:0338]に書き込み
$C1/6195 LDA $3A    ;Aに[$00:033A]をロード
$C1/6197 STA $39    ;Aを[$00:0339]に書き込み
$C1/6199 LDA $3B    ;Aに[$00:033B]をロード
$C1/619B STA $3A    ;Aを[$00:033A]に書き込み
$C1/619D LDA $4214  ;符号付16bit / 8bitの商(下位バイト)
$C1/61A0 STA $3B    ;Aを[$00:033B]に書き込み
$C1/61A2 PLB        ;DBレジスタに値をプル
$C1/61A3 PLX        ;Xに値をプル
$C1/61A4 RTS        ;サブルーチン戻り($C1/783Aの続きへ)

割り算の答えは$C1/619Dで読み込みしているが、それまでしばらく計算時間を稼いでいることがわかる。
それまでに、$C1/6191$C1/619Bでやっていることは、乱数の移動である。
「乱数3を読み込んで乱数4の位置に書き込み」
「乱数2を読み込んで乱数3の位置に書き込み」
「乱数1を読み込んで乱数2の位置に書き込み」
と、4つの乱数の中で1~3番目の乱数をそれぞれ2~4番目に移動している。
ここで一番古かった乱数4が廃棄されて、計算結果より新たな乱数1をメモリ[$00:033B]に入れる準備をした、ということである。

そして$C1/619DAに割り算の答えがロードされているが、割り算の答えは$4215に商の上位バイト、$4214に商の下位バイトが出力される。
だが、$4214の商の下位バイトしかロードしていない。
前ページの小数点を動かす話と同じ理屈になるのだが、「小数点を左に2つ動かす(256で割る)と上位バイトの値になり、小数点以下、つまり256で割った余り分が下位バイト」となる。
ここで$4214だけを取り出すことは、256で割った余り( % 256)と同じ意味になるのである。
(また、256で割った余りだから、答えは必ず0~255である)
よって、

An = ( (An-3 * 256 + An-4 ) / 13) % 256

の計算がここで終了し、新たな乱数1が計算された。
$C1/61A0ではAを乱数1として[$00:033B]に書き込んでいる。
$C1/61A2$C1/61A3ではスタックからそれぞれのレジスタに値を戻し、サブルーチンを終了して$C1/783Aの続きの$C1/783Dへジャンプする。

$C1/783D STA $4203  ;Aを乗数として[$00:4203]にセット
$C1/7840 JSR $46C9  ;PCの値をスタックにプッシュし[$C1/46C9]にジャンプ
;
$C1/46C9 RTS        ;サブルーチン戻り($C1/7840の続きへ)

そこそこ前の$C1/7837で、符号付8bit x 8bitの計算が開始されていたが、$C1/783Dでようやく乗数(= 新たな乱数1)が入る。

$C1/7843 LDA $4217  ;掛け算の結果の上位バイト[$00:4217]をAにロード
$C1/7846 REP #$21   ;Aレジスタは16bit幅, Cフラグクリア
$C1/7848 AND #$00FF ;Aと$00FFで論理積(上位2ビット$00、下位2ビットそのまま)
$C1/784B ADC $20    ;A + $00:0320 ※$00:0320は単発ダメージ値
$C1/784D STA $20    ;A値を$00:0320に書き込み
$C1/784F SEP #$20   ;Mフラグを立てる Aレジスタは8bit幅
$C1/7851 RTS        ;サブルーチン戻り($C1/D9A4の続きへ)

$C1/7843で掛け算の結果の上位バイトがAに出力されるのだが、前ページ同様、掛け算の答えの上位バイトの結果は「掛け算の答え/256」と同値である。
まとめると、$C1/7843でロードされるAは、

単発ダメージ量/4 × 新たな乱数1(0~255) / 256

である。
まとめると最初にあったとおり、
「乱数幅は厳密には単発ダメージ*(0~63)/256」
になる。

最後に$C1/784Bで、単発ダメージを足し算しているので、本来のダメージ量、

単発ダメージ+乱数から計算したダメージ量

が計算できた。

さて、単発ダメージ量の計算方法がどんな手順で行われていたのかが判明したが、この一連の手順に従わない例外もある。
最終編ラストバトルにおけるオディオモールがその例外のひとつで、「ほいこーろー」以外の技のダメージ量・回復技回復量の単発値を「1」にしてしまう。
この例外はどのようにして処理されているのか、次ページで紹介する。

おまけ 戦闘用乱数初期値と戦闘中のタイマー

上の$C1/6156$C1/615Eでは、「4つの乱数の論理和を取って、ゼロフラグが立つかどうか」で、記録されている4個の乱数全てが0かどうかの確認を行っていた。
上では、既に何らかの乱数が入っている、という前提で説明をしたが、では本当に「4個の乱数がすべて0」の時は、どうやって最初の乱数、つまり初期値が決まるのだろうか。

実際に、$C1/615Eでゼロフラグが立った時以降の処理は、以下のようになっている。

$C1/615E BNE $24     ;ゼロフラグが立っていない場合[$6184]分岐
$C1/6160 LDX #$162E  ;Xに$162Eをロード
$C1/6163 STX $38     ;Xを[$00:0338]に書き込み
$C1/6165 LDX #$1234  ;Xに$1234をロード
$C1/6168 STX $3A     ;Xを[$00:033A]に書き込み
$C1/616A LDA $0070   ;Aに[$00:0070]をロード
$C1/616D CMP #$01    ;Aと$01を減算比較
$C1/616F BEQ $13     ;ゼロフラグが立っている場合[$6184]分岐
$C1/6171 REP #$20    ;Aレジスタは16bit幅, Cフラグクリア
$C1/6173 LDA $000C   ;Aに[$00:000C]をロード(マップ乱数)
$C1/6176 EOR $38     ;Aと[$00:0338]で排他的論理和
$C1/6178 STA $38     ;Aに[$00:0338]を書き込み
$C1/617A LDA $7EFEF0 ;Aに[$7E:FEF0]をロード(戦闘中タイマー)
$C1/617E EOR $3A     ;Aと[$00:033A]で排他的論理和
$C1/6180 STA $3A     ;Aに[$00:033A]を書き込み
$C1/6182 SEP #$20    ;Mフラグを立てる Aレジスタは8bit幅
;
$C1/6184 LDX $38     ;(乱数計算のサブルーチンに戻る)

$C1/6160以降について、各部を説明する。

$C1/6160 LDX #$162E  ;Xに$162Eをロード
$C1/6163 STX $38     ;Xを[$00:0338]に書き込み
$C1/6165 LDX #$1234  ;Xに$1234をロード
$C1/6168 STX $3A     ;Xを[$00:033A]に書き込み

まずこの部分で、[$00:0338]$162Eが、[$00:033A]$1234が書き込まれた。
ということは、この時点で、

アドレス
$00:0338$2E
$00:0339$16
$00:033A$34
$00:033B$12

このように乱数保存用のアドレスに値が入ったことになる。
これが乱数の初期値かと思いきや、まだ続きがある。

$C1/616A LDA $0070  [$00:0070] ;Aに[$00:0070]をロード
$C1/616D CMP #$01              ;Aと$01を減算比較
$C1/616F BEQ $13    [$6184]    ;ゼロフラグが立っている場合[$6184]分岐

Aにロードされた[$00:0070]の値から$01を引き算して、0だったら[$6184]にジャンプ、ということになる。
[$6184]は通常の乱数計算の続きなので、[$00:0070]$01が入っていたら、上の$00:0338~$00:033Bの値のまま、乱数計算ということになる。
だが、この[$00:0070]というアドレスは、戦闘中、通常は$00が入っている。
$01が入るのはいつなのかというと、ゲームを開始してそのまま放置していると見られる、デモ画面である。
タイトルが表示された後に、おぼろ丸が戦闘を行うデモ画面になるが、この時以降のデモ画面の戦闘は[$00:0070]$01が入っているので、この状況のまま乱数計算される。
つまり、デモ画面の戦闘は、通常の戦闘と同じくきちんと乱数計算をして戦闘をしているということがわかる。
最初の乱数が上の値で完全に固定されているため、デモ画面でのダメージ量など、挙動は常に同じということである。
デモ画面で毎回異なる乱数を使って計算すると、想定外の挙動をする可能性があるため(例えば味方キャラが倒されてしまうなど)、固定にしているのだろうと推測される。

デモ画面以外での[$00:0070]は、どうやら$00のままらしい。
よってプレイヤーがゲームをプレイしている最中は、[$6184]にジャンプせずにこのまま続く。

$C1/6171 REP #$20   ;Aレジスタは16bit幅, Cフラグクリア
$C1/6173 LDA $000C  ;Aに[$00:000C]をロード(マップ乱数)
$C1/6176 EOR $38    ;Aと[$00:0338]で排他的論理和
$C1/6178 STA $38    ;Aに[$00:0338]を書き込み

まず、16bitモードに切り替えてから、[$00:000C]Aにロードする。
この[$00:000C]という値は、戦闘用乱数とは別に、マップで計算されるマップ乱数の値である(詳しくは、後のマップ乱数生成についてを参照)。
マップ乱数はマップにいる時、1/60秒(1フレーム)に1回計算されて作られるのだが、その乱数は[$00:000C]に入っている16進数4桁の数の上位2桁に当たる。
つまり実際には[$00:000C][$00:000D]の両方に値が入っているので、16bitモードで[$00:000C]をロードすると、[$00:000C]を下位ビット、[$00:000D]を上位ビットとする16進数4桁が呼び出されることになる。
[$00:000C]を読み込み、続いて[$00:0338]に入っている、$162Eとの排他的論理和を取って、新たに[$00:0338]に書き込む、という作業をしている。
排他的論理和とは、2進数でそれぞれの桁を比較した時、「どちらか一方だけが1なら1、両方とも1か、両方とも0なら0にする」という計算のこと。
例えば、[$00:000C]$5678という値が入っていたとする。
$5678を2進数にすると、「0101 0110 0111 1000」である。
$162Eを2進数にすると、「0001 0110 0010 1110」である。
この16桁の数字の各桁を見比べて、「どちらか一方だけが1なら1、両方とも1か、両方とも0なら0にする」処理をする。

16進数2進数
$56780101 0110 0111 1000
$162E0001 0110 0010 1110
排他的論理和0100 0000 0101 0110

答えは2進数で「0100 0000 0101 0110」、16進数の「$4056」となり、これが[$00:0338]に入るから、

アドレス
$00:0338$56
$00:0339$40

となる。
続いて、

$C1/617A LDA $7EFEF0 ;Aに[$7E:FEF0]をロード(戦闘用タイマー)
$C1/617E EOR $3A     ;Aと[$00:033A]で排他的論理和
$C1/6180 STA $3A     ;Aに[$00:033A]を書き込み
$C1/6182 SEP #$20    ;Mフラグを立てる Aレジスタは8bit幅

アドレス[$7E:FEF0]の値をAにロードしている。
[$7E:FEF0]は、+1したアドレス[$7E:FEF1]と合わせて、戦闘中のタイマーのような役割を果たしているらしい。
戦闘に入ると、どちらにも初期値として$FF(10進数255)が入る。
[$7E:FEF1]を上2桁、[$7E:FEF0]を下2桁の4桁の16進数とすると、初期値は$FFFF(10進数65535)である。
この値は、0.5フレームに1回、-1され続ける。コマンド選択中や、技のモーション中などでもずっとカウントダウンしている。
下2桁の[$7E:FEF0]は、$FFから$00まで減少するのに255/120秒、つまり2秒+15/120秒かかる。
上2桁の[$7E:FEF1]は、$FFから$00まで減少するのに65535/120秒、つまり546秒+15/120秒9分6秒+15/120秒かかる。
「すべて砂が落ちるまで9分6秒+15/120秒かかる砂時計を、砂がなくなるたびにひっくり返して時間経過を計測している」ようなものである。
このタイマーのような数値を仮に「戦闘用タイマー」としておく。
戦闘用乱数は必要となった時しか計算されないが、この戦闘用タイマーは常に動き続けている。
コマンド入力の最中でも、技のモーションの最中でも動いており、「$C1/617A」の処理をする瞬間の[$7E:FEF0]の値がロードされるのだと思われる。

説明が長くなったが、$C1/617E$C1/6180では、[$00:033A]に入っている$1234と、戦闘用タイマーの値とで排他的論理和を取り、これを新たに[$00:033A]に入れている。
戦闘用タイマーの値が仮に$ABCDだったとしたら、$1234$ABCDで排他的論理和を取り$B9F9であるから、

アドレス
$00:033A$F9
$00:033B$B9

となる。
ここまでで、

アドレス
$00:0338$56
$00:0339$40
$00:033A$F9
$00:033B$B9

となったところで、乱数計算の$C1/6184に合流し、$00:0338~$00:033Bを使った符号付16bit / 8bit処理へと続く。

このように、戦闘用乱数初期値は、マップ用乱数と戦闘用タイマーを使って、毎回異なる値になるように計算されていることがわかる。
もし、最初に[$00:0338]$162Eが、[$00:033A]$1234が入った時点で初期値が決まっていたら、ゲームを開始する度に乱数初期値が変わらず、あるセーブデータから開始した直後の戦闘が固定戦闘(ボス戦やイベント戦闘など)の時、ダメージ量などが固定されたままでランダム要素がまったくなくなってしまう。
マップ用乱数と戦闘用タイマーを使う理由はここにある。

(それでも1フレーム単位でコントローラーのボタンを押せるという猛者ならば、ゲーム開始からマップ用乱数と戦闘用タイマーの値を再現などということもできるだろうし、そういうタイムアタックの方法もあるらしいが、そのあたりについては筆者が踏み込める領域ではないので横に置く)



このページをシェアする

上へ