Disassembly Notes (NES)
Demo
If you sit idle on the title screen for a while, a demo plays. It's triggered by the frame counter rolling over to 00 8 times in a row. The frame counter cycles through from 00-FF. So this means it takes anywhere from ~29-34 seconds to start ((256 frames * 7 + [1,256] frames) / 60 frames per second). It can be disabled a number of ways, but the simplest way is editing the jump offset (to 0) when it decides to load the demo code (at $9905), accomplishable through the Game Genie code AAAPSP. Next, while the demo board is a generatable one (seed 03BE), it's stored directly in the ROM ($CF00-$CF7F) and loaded into memory. The capsules generated do not match this seed, but they're stored right after the board ($CF80-$CFFF). The controls are stored in pairs of bytes, the first byte being the buttons pressed (bitmask - A, B, Select, Start, Up, Down, Left, Right) and the second being the number of frames to hold down those buttons. These are stored right after the board/capsules at $D000-$D1FF.
Pill movement
This is all the code responsible for moving the pill each frame. The disassembly and function descriptions are from Brian Huffman's disassembly; this version has added detailed English descriptions of the code. I also did a cursory look through the code for what $06f1 does, but I couldn't find anything obvious that read from that address. So either it's dead code, or it's read via indirect addressing.
The input handling deserves a brief comment as well. L5C_INPUT_OLD stores which buttons the user is currently pressing (despite its name suggesting otherwise), one button per bit, in standard controller order, with the bottom bit being the right arrow. L5B_INPUT_NEW has the same shape as L5C_INPUT_OLD, but each bit is set if the corresponding button started being pressed this frame; that is, if it is pressed this frame but was not pressed last frame.
jsr R8D70_DROP_PILL ; may go to state 1 ; 8D66 20 70 8D
jsr R8DBF_PILL_MOVE ; 8D69 20 BF 8D
jsr R8E2B_PILL_ROTATE ; 8D6C 20 2B 8E
; --------------------------------------
; Called once, in L8D56.
R8D70_DROP_PILL:
begin
lda L43_CLOCK ; 8D70 A5 43
and #$01 ; 8D72 29 01
beq L8D7E ; 8D74 F0 08 Every other frame,
lda L5C_INPUT_OLD ; 8D76 A5 5C
and #$0F ; 8D78 29 0F
cmp #$04 ; "down" ; 8D7A C9 04 if the user is pressing down but not left, right, or up,
beq L8D94 ; 8D7C F0 16 skip ahead to L8D94.
L8D7E:
inc L92_DROP_TIMER ; 8D7E E6 92 Increase the drop counter.
lda L8A_EXTRA_SPEED ; 8D80 A5 8A
sta LA0_TEMP ; redundant copy ; 8D82 85 A0
ldx L8B_SPEED ; 8D84 A6 8B
lda LA38D,x ; $0F,$19,$1F ; 8D86 BD 8D A3 Load the base speed offset for the player-selected speed (low, med, hi),
add LA0_TEMP ; 8D89 18 65 A0 then add in the offset for having played a long time.
tax ; 8D8C AA
lda LA795,x ; drop timer threshold ; 8D8D BD 95 A7 Load the gravity value.
cmp L92_DROP_TIMER ; 8D90 C5 92 If the drop counter is <= the gravity value,
bcs break ; 8D92 B0 2A return from this function.
L8D94:
dec L86_PILL_ROW ; 8D94 C6 86 Move down.
lda #$00 ; 8D96 A9 00
sta L92_DROP_TIMER ; 8D98 85 92 Reset the drop counter to 0.
jsr R90D3_COLLISION ; 8D9A 20 D3 90
if_eq ; 8D9D D0 06 If we are over in-bounds empty space,
lda L86_PILL_ROW ; 8D9F A5 86
cmp #$FF ; 8DA1 C9 FF and not below the board,
bne break ; 8DA3 D0 19 return from this function.
end_if
inc L86_PILL_ROW ; 8DA5 E6 86 Move up.
lda #$07 ; 8DA7 A9 07
sta L06F1_ ; 8DA9 8D F1 06 Set $06F1 to 7.
jsr R8F52_PLACE_PILL ; 8DAC 20 52 8F Lock the pill in place.
; make the program crash if L0740_CHECKSUM_FAIL is nonzero
lda L0740_CHECKSUM_FAIL ; 8DAF AD 40 07
if_ne ; 8DB2 F0 05
lda L53_SPRITE_NUM ; 8DB4 A5 53
pha ; 8DB6 48
pha ; 8DB7 48
pha ; 8DB8 48
end_if
inc L97_GAME_STATE ; 8DB9 E6 97
jmp break ; 8DBB 4C BE 8D
end
rts ; 8DBE 60
; --------------------------------------
; Increment or decrement pill column based on controller input.
; Repeat every 6 frames after holding for 16.
R8DBF_PILL_MOVE:
begin
lda L5B_INPUT_NEW ; 8DBF A5 5B
and #$03 ; left or right ; 8DC1 29 03
if_eq ; 8DC3 D0 15 If the user did not start pressing left or right,
lda L5C_INPUT_OLD ; 8DC5 A5 5C
and #$03 ; left or right ; 8DC7 29 03
beq break ; 8DC9 F0 5F and neither left nor right is currently pressed, return from this function. Otherwise,
inc L93_REPEAT_TIMER ; 8DCB E6 93 increase the DAS counter.
lda L93_REPEAT_TIMER ; 8DCD A5 93
cmp #$10 ; 8DCF C9 10
bmi break ; 8DD1 30 57 If the DAS counter is under 16, return from this function. Otherwise,
lda #$0A ; 8DD3 A9 0A
sta L93_REPEAT_TIMER ; 8DD5 85 93 set the DAS counter to 10.
else ; 8DD7 4C E3 8D If the user started pressing left or right,
lda #$00 ; 8DDA A9 00
sta L93_REPEAT_TIMER ; 8DDC 85 93 set the DAS counter to 0,
lda #$03 ; 8DDE A9 03
sta L06F1_ ; 8DE0 8D F1 06 and set $06F1 to 3.
end_if
lda L5C_INPUT_OLD ; 8DE3 A5 5C
and #$01 ; "right" ; 8DE5 29 01
if_ne ; 8DE7 F0 20 If the user is pressing right,
lda LA5_PILL_DIR ; 8DE9 A5 A5
and #$01 ; 1 = U/D, 0 = L/R ; 8DEB 29 01
add #$06 ; 8DED 18 69 06
cmp L85_PILL_COLUMN ; 8DF0 C5 85
if_ne ; 8DF2 F0 15 and the pill is not in the rightmost column for its orientation,
inc L85_PILL_COLUMN ; 8DF4 E6 85 move right.
jsr R90D3_COLLISION ; 8DF6 20 D3 90
if_eq ; 8DF9 D0 08 If that would put us over in-bounds empty space,
lda #$03 ; 8DFB A9 03
sta L06F1_ ; 8DFD 8D F1 06 set $06F1 to 3.
else ; 8E00 4C 09 8E If that would put us out of bounds or over a non-empty space,
dec L85_PILL_COLUMN ; 8E03 C6 85 move left
lda #$0F ; 8E05 A9 0F
sta L93_REPEAT_TIMER ; 8E07 85 93 and set the DAS counter to 15.
end_if
end_if
end_if
lda L5C_INPUT_OLD ; 8E09 A5 5C
and #$02 ; "left" ; 8E0B 29 02
if_ne ; 8E0D F0 1B If the user is pressing left,
lda L85_PILL_COLUMN ; 8E0F A5 85
cmp #$00 ; 8E11 C9 00
if_ne ; 8E13 F0 15 and we're not in the left-most column,
dec L85_PILL_COLUMN ; 8E15 C6 85 move left.
jsr R90D3_COLLISION ; 8E17 20 D3 90
if_eq ; 8E1A D0 08 If that would put us over in-bounds empty space,
lda #$03 ; 8E1C A9 03
sta L06F1_ ; 8E1E 8D F1 06 set $06F1 to 3.
else ; 8E21 4C 2A 8E If that would put us out of bounds or over a non-empty space,
inc L85_PILL_COLUMN ; 8E24 E6 85 move right,
lda #$0F ; 8E26 A9 0F
sta L93_REPEAT_TIMER ; 8E28 85 93 and set the DAS counter to 16.
end_if
end_if
end_if
end
rts ; 8E2A 60
; ----------------------------------------------------------------------------
; Pill collision detection.
; Returns A = 0, Z set for no collision.
; Returns A = FF, Z clear for collision.
R90D3_COLLISION:
.scope
ldx LA6_PILL_SIZE ; 90D3 A6 A6
dex ; 90D5 CA
dex ; 90D6 CA
lda LA5_PILL_DIR ; 90D7 A5 A5
and #$01 ; 1 = U/D, 0 = L/R ; 90D9 29 01
if_eq ; 90DB D0 13 If the current pill is horizontal,
lda LA455,x ; $00,$FF,$FF ; 90DD BD 55 A4
add L85_PILL_COLUMN ; 90E0 18 65 85 and the pill's column is negative,
bmi collision ; 90E3 30 4C it's a collision.
lda LA458,x ; $F9,$F9,$FA ; 90E5 BD 58 A4
add L85_PILL_COLUMN ; 90E8 18 65 85 and the pill's column - 7 is nonnegative,
bpl collision ; 90EB 10 44 it's a collision.
else ; 90ED 4C F8 90 If the current pill is vertical,
lda LA45B,x ; $00,$FF,$FF,... ; 90F0 BD 5B A4
add L86_PILL_ROW ; 90F3 18 65 86 and the pill's row is negative,
bmi collision ; 90F6 30 39 it's a collision.
end_if
lda #$00 ; 90F8 A9 00
sta L57_ ; 90FA 85 57 $57 = 0
ldx L86_PILL_ROW ; 90FC A6 86
lda LA474,x ; 120 - x*8 ; 90FE BD 74 A4
add L85_PILL_COLUMN ; 9101 18 65 85
sta L47_TEMP ; 9104 85 47 $47 = pill's current position
lda LA6_PILL_SIZE ; 9106 A5 A6
sub #$02 ; 9108 38 E9 02
asl a ; 910B 0A
sta L49_TEMP ; 910C 85 49 $49 = 0
; L49_TEMP = LA6_PILL_SIZE - 2
lda LA5_PILL_DIR ; 910E A5 A5
and #$01 ; 1 = U/D, 0 = L/R ; 9110 29 01
add L49_TEMP ; 9112 18 65 49
asl a ; 9115 0A
asl a ; 9116 0A
tax ; 9117 AA x = 0 for horizontal pill, 4 for vertical pill
; x = ((LA5_PILL_DIR & #1) + L49_TEMP) << 2
lda #$04 ; 9118 A9 04
sta L48_TEMP ; 911A 85 48 for(i=0; i<4; i++)
loop
lda LA4C4,x ; 0,1,0,0, f8,0,0,0 ; 911C BD C4 A4
add L47_TEMP ; 911F 18 65 47
tay ; 9122 A8 y = pill's position + { horizontal pill -> 0 or 1; vertical pill -> 0 or -8 }
lda ($57),y ; 9123 B1 57
cmp #$FF ; 9125 C9 FF If the board at index y is not empty,
bne collision ; 9127 D0 08 it's a collision.
inx ; 9129 E8
dec L48_TEMP ; 912A C6 48
while_ne ; 912C D0 EE
lda #$00 ; 912E A9 00 It's not a collision.
rts ; 9130 60
collision:
lda #$FF ; 9131 A9 FF
rts ; 9133 60
.endscope
; --------------------------------------
; Increment or decrement LA5_PILL_DIR (mod 4) depending on controller input.
R8E2B_PILL_ROTATE:
lda LA5_PILL_DIR ; 8E2B A5 A5
sta L4A_TEMP ; 8E2D 85 4A
lda L85_PILL_COLUMN ; 8E2F A5 85
sta L4B_TEMP ; 8E31 85 4B
lda L5B_INPUT_NEW ; 8E33 A5 5B
and #$80 ; "A" ; 8E35 29 80
if_ne ; 8E37 F0 10 If the user started pressing A,
lda #$05 ; 8E39 A9 05
sta L06F1_ ; 8E3B 8D F1 06 set $06f1 to 5,
dec LA5_PILL_DIR ; 8E3E C6 A5
lda LA5_PILL_DIR ; 8E40 A5 A5
and #$03 ; 8E42 29 03
sta LA5_PILL_DIR ; 8E44 85 A5 subtract one (mod 4) from the current rotation count,
jsr R8E60_CHECK_ROTATE ; 8E46 20 60 8E and then roll back the change if there's a problem.
end_if
lda L5B_INPUT_NEW ; 8E49 A5 5B
and #$40 ; "B" ; 8E4B 29 40 If the user started pressing B,
if_ne ; 8E4D F0 10
lda #$05 ; 8E4F A9 05
sta L06F1_ ; 8E51 8D F1 06 set $06f1 to 5,
inc LA5_PILL_DIR ; 8E54 E6 A5
lda LA5_PILL_DIR ; 8E56 A5 A5
and #$03 ; 8E58 29 03
sta LA5_PILL_DIR ; 8E5A 85 A5 add one (mod 4) to the current rotation count,
jsr R8E60_CHECK_ROTATE ; 8E5C 20 60 8E and then roll back the change if there's a problem.
end_if
rts ; 8E5F 60
; --------------------------------------
; Adjust or undo pill rotation if there are collisions.
; Previous DIR and COLUMN values are passed in L4A_TEMP/L4B_TEMP.
R8E60_CHECK_ROTATE:
begin
lda LA5_PILL_DIR ; 8E60 A5 A5
and #$01 ; 1 = U/D, 0 = L/R ; 8E62 29 01
if_eq ; 8E64 D0 19 If the current pill is horizontal,
jsr R90D3_COLLISION ; 8E66 20 D3 90
if_eq ; 8E69 D0 12 and over in-bounds empty space,
lda L5C_INPUT_OLD ; 8E6B A5 5C
and #$02 ; "left" ; 8E6D 29 02
beq break ; 8E6F F0 1B and the user is not pressing left, return from this function.
dec L85_PILL_COLUMN ; 8E71 C6 85 Move left.
jsr R90D3_COLLISION ; 8E73 20 D3 90
beq break ; 8E76 F0 14 If the pill is over in-bounds empty space, return from this function.
inc L85_PILL_COLUMN ; 8E78 E6 85 Move right.
jmp break ; 8E7A 4C 8C 8E Return from this function.
end_if
dec L85_PILL_COLUMN ; 8E7D C6 85 Move left.
end_if
jsr R90D3_COLLISION ; 8E7F 20 D3 90 If the current pill is out of bounds or over non-empty space,
beq break ; 8E82 F0 08
lda L4A_TEMP ; 8E84 A5 4A
sta LA5_PILL_DIR ; 8E86 85 A5
lda L4B_TEMP ; 8E88 A5 4B
sta L85_PILL_COLUMN ; 8E8A 85 85 roll back to the position and rotation we were at before we started the rotation.
end
rts ; 8E8C 60
External resources
Brian Huffman's disassembly notes