Back to Page
Revision 1 (current)
Edited by Randomno on 7/3/2023 1:40 PM
! RNG
RndVal1_A = $0037%%%
RndVal1_B = $0038%%%
RndVal1_C = $0039%%%
RndVal2 = $0302%%%
RndCount1 = $0027%%%
RndCount2 = $00F7
Execution of RndFunc1: The current 24-bit[#1] random seed RndVal1 is left-shifted by 1.
The new entering bit is 0 if the 3rd and 4th bits from the left are the same, and 1 otherwise.%%%
This is an event of what is better known as a [http://en.wikipedia.org/wiki/Linear_feedback_shift_register|linear feedback shift register].
Execution of RndFunc2: Execute RndFunc1 5 times and add 35 to RndVal2 (mod 256).
Randomization occurs every frame (except those which are lagging or playing sound effects).
Which randomization occurs on a frame depends on which stage the game is at.
Note: All divisions and moduli operate on ''unsigned'' 8-bit integers.
Before the game:
* Random seed at frame 7: 3E2AD2
* Increment of RndCount1 begins after a button is pressed to begin.
* Game is not at enter-name screen: Increment RndCount1. Then execute RndFunc1 once. (RndCount1++, RndVal1 << 1)
* Game is at enter-name screen: Increment RndCount1. Then execute RndFunc1 once. Then take RndCount1, mod 32, and execute RndFunc1 that many times. Then execute RndFunc1 once. If A is pressed, reset RndCount1. (RndCount1++, RndVal1 << 2-33)
* Game is setting card decks:
## Execute RndFunc1 once.
## Reset RndCount2.
## Ready the Chance deck array.
## Execute RndFunc1 three times.
## Take last 8 bits of RndVal1, XOR with RndCount2, integer divide by 16, and use this value as an offset to the deck array.
## Check deck array. If already occupied, increment RndCount2. Otherwise occupy offset, and try next card.
## Go back to step 4 unless the deck is full (no more cards). In that case go back to step 3, set RndCount2 to 16, and repeat with the Community Chest deck array. When both decks are done, stop.
** (RndVal1 << a lot)
During the game:
* Game is not showing overhead board: Execute RndFunc1 once. (RndVal1 << 1)
* Player is waiting to roll: Just before this happens, reset RndCount1. Increment RndCount1. Then execute RndFunc1 once, then RndFunc2 once, then, if not turn after double or opposing player menu, execute RndFunc1 twice. (RndCount1++, RndVal1 << 8 or 6, RndVal2 += 35)
* Player is rolling: Increment RndCount1. Then execute RndFunc1 once, then RndFunc2 once, then RndFunc2 twice, then if RndCount1 is divisible by 4, execute RndFunc1 twice. (RndCount1++, RndVal1 << 18 or 16, RndVal2 += 105)
* Player has thrown the dice: Execute RndFunc1 once, then RndFunc2 once. (RndVal1 << 6, RndVal2 += 35)
* On the frame to get dice roll:
## Execute RndFunc1 once, then RndFunc2 once, then RndFunc1 five times.
## Take last 8 bits of RndVal1, XOR with RndVal2, mod 6, and increment for Die 2.
## Execute RndFunc1 five times.
## Take last 8 bits of RndVal1, mod 6, and increment for Die 1.
** (RndVal1 << 16, RndVal2 += 35).
%%TAB Minimize Tab
%%TAB Monopoly RNG Disassembly
Everything marked (Acmlm) was found by [user:Acmlm].
Everything else was found by [user:FractalFusion].
Some additional comments were added to the code.
* Random functions:
%%SRC_EMBED snescom
RndVal1_A = $0037
RndVal1_B = $0038
RndVal1_C = $0039
RndVal2 = $0302
;(Acmlm)
;****************************************
;* Shift RndVal1 left by 1 bit *
;* New bit entering RndVal1_C = ? *
;* Load random from RndVal1_C *
;****************************************
$C13E ; RndVal1 - A - C
RndFunc1: LDA RndVal1_A ;01111000 11111111 00110110 - 01111000 - 1
ASL ; - 11110000 - 0
EOR RndVal1_A ; - 10001000 - 0
ASL ; - 00010000 - 1
ASL ; - 00100000 - 0
ASL ; - 01000000 - 0 <-- entering bit
ROL RndVal1_C ;01111000 11111111 01101100 - 01000000 - 0
ROL RndVal1_B ;01111000 11111110 01101100 - 01000000 - 1
ROL RndVal1_A ;11110001 11111110 01101100 - 01000000 - 0
LDA RndVal1_C ; - 01101100 - 0
RTS
;****************************************
;* Increment RndVal2 by 35 ($23) *
;* Run RndFunc1 5 times *
;****************************************
$C56D
RndFunc2: LDX #$05
loop: LDA RndVal2
CLC
ADC #$07
STA RndVal2
JSR RndFunc1
DEX
BNE loop
RTS
%%END_EMBED
* Random analysis:
(Acmlm)
37 38 39 0302
----------------------------------------
70 19 EE = 011100000001100111101110 ¦ 00 Before starting (random 1)
E0 33 DC = 111000000011001111011100 ¦ 00 $0037-0039 << 1
C0 67 B9 = 110000000110011110111001 ¦ 00 $0302 + 00
80 CF 72 = 100000001100111101110010 ¦ 00
01 9E E4 = 000000011001111011100100 ¦ 00
03 3D C8 = 000000110011110111001000 ¦ 00
06 7B 90 = 000001100111101110010000 ¦ 00
0C F7 20 = 000011001111011100100000 ¦ 00
19 EE 40 = 000110011110111001000000 ¦ 00
33 DC 81 = 001100111101110010000001 ¦ 00
67 B9 02 = 011001111011100100000010 ¦ 00
80 CF 72 = 100000001100111101110010 ¦ 00 Before rolling (random 2)
CF 72 05 = 110011110111001000000101 ¦ 23 $0037-0039 << 8, $0027 is reset when this activates
72 05 46 = 111000100000010101000110 ¦ 46 $0302 +$23
05 46 58 = 000001010100011001011000 ¦ 69
3F 2B A1 = 001111110010101110100001 ¦ D2 While rolling (random 3)
84 17 CE = 100001000001011111001110 ¦ 3B $0037-0039 <<18,16,16,16,18,16,16,16,... depending on $0027
CE 30 E1 = 110011100011000011100001 ¦ A4 $0302 +$69
E1 49 44 = 111000010100100101000100 ¦ 0D
44 8F 6F = 010001001000111101101111 ¦ 76
BC D9 1B = 101111001101100100011011 ¦ DF
After rolling (random 4)
$0037-0039 << 6, 16 when getting dice values
$0302 +$23
* before game:
%%SRC_EMBED snescom
;frame 7: 3E 2A D2
;RAM(27)++ starts 3 frames after first press of A and occurs on every frame
; *****
; executes first
; *****
...
$C053 JSR RndFunc1 ; executes always on every frame
$C056 JSR $C2DE
...
; *****
; executes when entering name
; starts on accept input
; stops one frame after OK
; *****
;RAM($27)++ (somewhere)
...
$94D6 LDA $27
$94D8 AND #$1F ;mod 32
$94DA TAX
Iterate: ; iterates RAM($27)%32+1 times.
$94DB JSR RndFunc1
$94DE DEX
$94DF BPL Iterate
...
; *****
; set up cards
; *****
...
$84A2 JSR Chance ; if jump, chance first, otherwise comm chest
$84A5 LDX #$10
Chance:
$84A7 STX $F6 ; 0 if chance, 16 if comm chest
$84A9 STX $F7
$84AB LDY #$F
$84AD LDA #$FF
Init: ;sets chance/comm chest entries to $FF
$84AF STA $472,X
$84B2 INX
$84B3 DEY
$84B4 BPL Init
$84B6 LDY #$F
TryInsertCard:
$84B8 JSR RndFunc1
$84BB JSR RndFunc1
$84BE JSR RndFunc1
$84C1 EOR $F7
$84C3 LSR ;
$84C4 LSR ;
$84C5 LSR ;
$84C6 LSR ; idiv by 16, reduce to <16
$84C7 ORA $F6 ; +0 if chance, +16 if comm chest
$84C9 TAX ;deck offset
$84CA LDA $472,X
$84CD BMI InsertCard ;vacant spot
$84CF INC $F7
$84D1 JMP TryInsertCard
InsertCard:
$84D4 TYA
$84D5 STA $472,X
$84D8 DEY ;Next Card
$84D9 BPL TryInsertCard
$84DB RTS ;Done
...
%%END_EMBED
* during game:
%%SRC_EMBED snescom
; (included again)
; *****
; executes first
; *****
...
$C053 JSR RndFunc1 ; executes always on every frame
$C056 JSR $C2DE
...
; *****
; executes when on overhead board view
; *****
...
$8110 JSR $8585
$8113 JSR RndFunc2 ;executes on all but random 1
$8116 JSR $8585
$8119 LDA $26 ;0=not rolling 1=rolling
$811B BEQ $8120 ;not sure why they double-check; see below (RandomWhenRolling)
$811D JMP RandomWhenRolling
...
; *****
; executes only on random 2
; *****
...
$FA1B JSR RndFunc1 ;executes only on random 2
$FA1E AND #$1F ;mod 32
$FA20 CMP #$A ;mod 10
$FA22 BCC $FA28
$FA24 SBC #$A
$FA26 BCS $FA20
$FA28 STA $5D
$FA2A JSR RndFunc1 ;executes only on random 2
$FA2D AND #$D0
...
;(Acmlm)
;****************************************
;* Randomness variation when rolling *
;****************************************
RandomWhenRolling:
$816E: LDA $0026
CMP #$01 ;equal when rolling
BNE NotRolling
JSR RndFunc2
JSR RndFunc2
INC $0027
LDA $0027
AND #$03 ;mod 4
BNE Skip ;... if RAM($27)%4==0 (i.e. every 4 frames) ...
JSR RndFunc1 ;do this ...
STA $00FA
AND #$42
BNE $8190
LDA #$02
JSR $CB9A
$8190: JSR RndFunc1 ;... and this
AND #$12
BEQ $819C
LDA #$01
STA $0504
Skip:
$819C: ...
NotRolling:
JSR $8585
;(Acmlm)
;****************************************
;* Determine (and display) the dice *
;* Values are randomized between them *
;****************************************
$81EB: LDX #$01
NextDice: STX $00FB
...
$8214: JSR RndFunc1 ;Randomize and load random value
JSR RndFunc1
JSR RndFunc1
JSR RndFunc1
JSR RndFunc1
CPX #$01 ;If second dice, XOR with RndVal2
BNE Mod6
EOR RndVal2
Mod6: CMP #$06 ;modulo 6
BCC Save
SBC #$06
BCS Mod6
Save: ADC #$01 ;0-5 -> 1-6
STA $0300,X ;store dice
...
$825E: LDX $00FB
DEX
BPL NextDice
%%END_EMBED
%%TAB_END
(RNG = Random Number Generator)
----
[1] The RndVal1 RNG is actually 22-bit, not 24-bit, because the first two bits are superfluous.
Some trivia:
* The RndVal1 RNG has a period of 2^22-1. Since 0 is a pathological case, it means that all nonzero 22-bit numbers are contained in this period.
* The RndVal1 RNG is slightly biased toward the die numbers 1, 2, 3, 4, because of mod 6 operating on (unsigned) 8-bit numbers.
* The RndVal1 RNG is reversible, a fact which can also be derived from its period.