Home Journal Contact Me Tools Comments

Code
RAM Map Main Loop Video Kernel
Init Init Play Mode Play Mode
Init Select Mode Select Mode Init Go Mode
Go Mode Utils Init Music
Process Music Go FX Music Commands
Skill Values Images 6502 Vectors
; DoubleGap by Christopher Cantrell 2006



; TO DO
; - Expert switches are backwards
; - Debounce switches

; build-command java Blend gap.asm g2.asm
; build-command tasm -b -65 g2.asm g2.bin

; This file uses the "BLEND" program for assembly pre-processing
processor 6502

#include "Stella.asm" ; Equates to give names to hardware memory locations

; RAM
; RAM usage
;
TMP0      .EQU   0x80 ; Temporary storage
TMP1      .EQU   0x81 ; Temporary storage
TMP2      .EQU   0x82 ; Temporary storage
PLAYR0Y   .EQU   0x83 ; Player 0's Y location (normal or pro)
PLAYR1Y   .EQU   0x84 ; Player 1's Y location (normal, pro, or off)
MUS_TMP0  .EQU   0x85 ; Game-over mode sound FX storage (frame delay)
MUS_TMP1  .EQU   0x86 ; Game-over mode sound FX storage (frequency)
SCANCNT   .EQU   0x87 ; Scanline counter during screen drawing
MODE      .EQU   0x88 ; Game mode: 0=GameOver 1=Play 2=Select
WALL_INC  .EQU   0x89 ; How much to add to wall's Y position
WALLCNT   .EQU   0x8A ; Number of walls passed (score)
WALLDELY  .EQU   0x8B ; Wall movement frame skip counter
WALLDELYR .EQU   0x8C ; Number of frames to skip between wall increments
ENTROPYA  .EQU   0x8D ; Incremeneted with every frame
ENTROPYB  .EQU   0x8E ; SWCHA adds in
ENTROPYC  .EQU   0x8F ; Left/Right movements added to other entropies
DEBOUNCE  .EQU   0x90 ; Last state of the Reset/Select switches
WALLDRELA .EQU   0x91 ; PF0 pattern for wall
WALLDRELB .EQU   0x92 ; PF1 pattern for wall
WALLDRELC .EQU   0x93 ; PF2 pattern for wall
WALLSTART .EQU   0x94 ; Wall's Y position (scanline)
WALLHEI   .EQU   0x95 ; Height of wall
GAPBITS   .EQU   0x96 ; Wall's gap pattern (used to make WALLDRELx)
SCORE_PF1 .EQU   0x97 ; 6-bytes. PF1 pattern for each row of the score
SCORE_PF2 .EQU   0x9D ; 6-bytes. PF2 pattern for each row of the score
MUSADEL   .EQU   0xA3 ; Music A delay count
MUSAIND   .EQU   0xA4 ; Music A pointer
MUSAVOL   .EQU   0xA5 ; Music A volume
MUSBDEL   .EQU   0xA6 ; Music B delay count
MUSBIND   .EQU   0xA7 ; Music B pointer
MUSBVOL   .EQU   0xA8 ; Music B volume

; Remember, stack builds down from $FF ... leave some space
;
; 80 - A8 ... that's 41 bytes of RAM used


  .org 0xF000

; Main
main() {
  I_Flag = 1        ; Turn off interrupts
  D_Flag = 0        ; Clear the "decimal" flag
  X = 0xFF          ; Set the stack pointer ...
  S = X             ; ... to the end of RAM
  INIT()            ; Initialize game environment
  INIT_SELMODE()    ; Start out in SELECT-MODE
  VIDEO_KERNEL()    ; There should be no return from the KERNEL
}
  
; Kernel
VIDEO_KERNEL() {
;  (start here at the END of every frame)
;
  while(true) {

      A = 2          ; D1 bit ON
      WSYNC  = A     ; Wait for the end of the current line
      VBLANK = A     ; Turn the electron beam off
      WSYNC  = A     ; Wait for all ...
      WSYNC  = A     ; ... the electrons ...
      WSYNC  = A     ; ... to drain out.
      VSYNC  = A     ; Trigger the vertical sync signal
      WSYNC  = A     ; Hold the vsync signal for ...
      WSYNC  = A     ; ... three ...
      WSYNC  = A     ; ... scanlines
      HMOVE  = A     ; Tell hardware to move all game objects
      A = 0          ; D1 bit OFF
      VSYNC  = A     ; Release the vertical sync signal
      A  = 43        ; Set timer to 43*64 = 2752 machine ...
      TIM64T = A     ; ... cycles 2752/(228/3) = 36 scanlines

      ; ***** LENGTHY GAME LOGIC PROCESSING BEGINS HERE *****

      ; Do one of 3 routines while the beam travels back to the top
      ; 0 = Game Over processing
      ; 1 = Playing-Game processing
      ; 2 = Selecting-Game processing

      ++ENTROPYA        ; Counting video frames as part of the random number
      A = MODE          ; What are we doing between frames?

      if(A==0) {
          GOMODE()      ; Game-over processing
      } else if(A==1) {
          PLAYMODE()    ; Playing-game processing
      } else {
          SELMODE()     ; Selecting game processing
      }
      
      ; ***** LENGTHY GAME LOGIC PROCESSING ENDS HERE *****

      do {
          A = INTIM     ; Wait for the visible area of the screen
      } while(A!=0);

      WSYNC = A         ; 37th scanline
      A = 0             ; Turn the ...
      VBLANK = A        ; ... electron beam back on
            
      A = 0             ; Zero out ...
      SCANCNT = A       ; ... scanline count ...
      TMP0 = A          ; ... and all ...
      TMP1 = A          ; ... returns ...
      TMP2 = A          ; ... expected ...
      X = A             ; ... to come from ...
      Y = A             ; ... BUILDROW

      CXCLR = A         ; Clear collision detection

      ; BEGIN VISIBLE PART OF FRAME

      do {

          A = TMP0      ; Get A ready (PF0 value)
          WSYNC = A     ; Wait for very start of row
          GRP0 = X      ; Player 0 -- in X
          GRP1 = Y      ; Player 1 -- in Y
          PF0 = A       ; PF0      -- in TMP0 (already in A)
          A = TMP1      ; PF1      -- in TMP1
          PF1 = A       ; ...
          A = TMP2      ; PP2      -- in TMP2
          PF2 = A       ; ...

          BUILDROW()    ; This MUST take through to the next line

          ++SCANCNT     ; Next scan line
          A = SCANCNT   ; Do 109*2 = 218 lines

      } while(A!=109)

      ; END VISIBLE PART OF FRAME

      A = 0             ; Turning off visuals
      WSYNC = A         ; Next scanline
      PF0 = A           ; Play field 0 off
      GRP0 = A          ; Player 0 off
      GRP1 = A          ; Player 1 off
      PF1 = A           ; Play field 1 off
      PF2 = A           ; Play field 2 off
      WSYNC = A         ; Next scanline
      
  }

}

; ======================================

BUILDROW() {        
  
  A = SCANCNT    ; Current scanline

  if(A<6) {      ; Top 6 rows are for the score

      A = A & 7          ; Only need the lower 3 bits
      Y = A              ; Soon to be an index into a list

      ; At this point, the beam is past the loading of the
      ; playfield for the left half. We want to make sure
      ; that the right half of the playfield is off, so do that
      ; now.

      X =  0             ; Blank bit pattern
      TMP0 = X           ; This will always be blank
      PF1 = X            ; Turn off playfield ...
      PF2 = X            ; ... for right half of the screen
      
      X = A              ; Another index
      A = SCORE_PF1[Y]   ; Lookup the PF1 graphics for this row
      TMP1 = A           ; Return it to the caller
      Y = A              ; We'll need this value again in a second
      A = SCORE_PF2[X]   ; Lookup the PF2 graphics for this row
      TMP2 = A           ; Return it to the caller
      
      WSYNC = A          ; Now on the next row
      
      PF1 = Y            ; Repeat the left-side playfield ...
      PF2 = A            ; ... onto the new row

      A =  SCORE_PF2[X]  ; Kill some time waiting for the ...
      A =  SCORE_PF2[X]  ; ... beam to pass the left half ...
      A =  SCORE_PF2[X]  ; ... of the playfield again
      A =  SCORE_PF2[X]  ; 
      A =  SCORE_PF2[X]  ; 
      A =  SCORE_PF2[X]  ; 

      X  =  0            ; Return 0 (off) for player 0 ...
      Y  = 0             ; ... and player 1

      ; The beam is past the left half of the field again.
      ; Turn off the playfield.

      PF1 = X            ; 0 to PF1 ...
      PF2 = X            ; ... and PF2

  } else {   ; Rest of the rows are for the game area
      
      A = A &7           ; Lower 3 bits as an index again
      Y = A              ; Using Y to lookup graphics
      A =  GR_PLAYER[Y]  ; Get the graphics (if enabled on this row)
      X = A              ; Hold it (for return as player 0)
      Y = A              ; Hold it (for return as player 1)
      A = SCANCNT        ; Scanline count again
      A>>1               ; This time ...
      A>>1               ; ... we divide ...
      A>>1               ; ... by eight (8 rows in picture)

      if(A!=PLAYR0Y) {
          X = 0          ; Not time for Player 0 ... no graphics
      }

      if(A!=PLAYR1Y) {
          Y = 0          ; Not time for Player 0 ... no graphics
      }

      A =  WALLSTART     ; Calculate ...
      A = A + WALLHEI    ; ... the bottom of ...  
      TMP0 = A           ; ... the wall

      A = SCANCNT        ; Scanline count

      if(A>=WALLSTART && A; Init
; ======================================
INIT() {          
;
; This function is called ONCE at power-up/reset to initialize various
; hardware and temporaries.
;
  A =  0x40         ; Playfield ...
  COLUPF = A        ; ... redish
  A =   0x7E        ; Player 0 ...
  COLUP0 = A        ; ... white
  A =  0            ; Player 1 ...
  COLUP1 = A        ; ... black

  A = 5             ; Right half of playfield is reflection of left ...
  CTRLPF = A        ; ... and playfield is on top of players

  X =  4            ; Player 0 position count
  Y =  3            ; Player 1 position count
  WSYNC = A         ; Get a fresh scanline

  do {
      --X           ; Kill time while the beam moves 
  } while(X!=0);
  RESP0 = A         ; Mark player 0's X position

  do {
      --Y           ; Kill more time
  } while(Y!=0);
  RESP1 = A         ; Mark player 1's X position

  EXPERTISE()       ; Initialize the players' Y positions base on expert-settings
  
  A  = 10           ; Wall is ...
  WALLHEI = A       ; ... 10 double-scanlines high
    
  A  = 0            ; Set score to ...
  WALLCNT = A       ; ... 0
  MAKE_SCORE()      ; Blank the score digits
  A  = 0            ; Blank bits ...
  SCORE_PF2[5] = A  ; ... on the end of each ...
  SCORE_PF1[5] = A  ; ... digit pattern

  ADJUST_DIF()      ; Initialize the wall parameters
  NEW_GAPS()        ; Build the wall's initial gap
  
  A =  112          ; Set wall position off bottom ...
  WALLSTART = A     ; ... to force a restart on first move

  A = 0             ; Zero out ...
  HMP0 = A          ; ... player 0 motion ...
  HMP1 = A          ; ... and player 1 motion
  
}

; InitPlayMode
; ===================================
INIT_PLAYMODE() {
;
; This function initializes the game play mode
;
  A = 0xC0        ; Background color ...
  COLUBK = A      ; ... greenish
  A = 1           ; Game mode is ...
  MODE = A        ; ... SELECT
  A = 255         ; Restart wall score to ...
  WALLCNT = A     ; ... 0 on first move
  A = 112         ; Force wall to start ...
  WALLSTART = A   ; ... over on first move
  INIT_MUSIC()    ; Initialize the music
}

; PlayMode
; ======================================
PLAYMODE() {  
;
; This function is called once per frame to process the main game play.
;
  
  SEL_RESET_CHK()     ; Check to see if Reset/Select has changed

  if(A!=0) {          ; A!=0 if reset/select has been toggled
      DEBOUNCE = X    ; Restore the old value ...
      INIT_SELMODE()  ; ... and let select-mode process the toggle
      return
  }
  
  PROCESS_MUSIC()     ; Process any playing music
  MOVE_WALLS()        ; Move the walls

  if(A==1) {          ; A==1 if wall reached bottom
      ++WALLCNT       ; Bump the score
      ADJUST_DIF()    ; Change the wall parameters based on score
      A =  WALLCNT    ; Change the ...
      MAKE_SCORE()    ; ... score pattern
      NEW_GAPS()      ; Calculate the new gap position
  }

  A =  CXP0FB         ; Player 0 collision with playfield
  TMP0 = A            ; Hold it
  A =  CXP1FB         ; Player 1 collision with playfield
  A = A | TMP0        ; Did either ...
  A = A & 0x80        ; ... player collide with wall?

  if(A!=0) {
      INIT_GOMODE()   ; Go to Game-Over mode
      return
  }

  A =  SWCHA                 ; Joystick
  A = A + C_Flag + ENTROPYB  ; Add to ...  
  ENTROPYB = A               ; ... entropy

  A =  SWCHA          ; Joystick
  A = A & 0x80        ; Player 0 left switch
  if(A==0) {          ; A==0 if joystick-left
      A =  0xF0       ; Moving left value
  } else {
      A =  SWCHA      ; Joystick
      A = A & 0x40    ; Player 0 right switch
      if(A==0) {      ; A==0 if joystick-right
          ++ENTROPYC  ; Yes ... increase entropy
          A =  0x10   ; Moving right value
      } else {
          A =   0     ; Not moving value
      }
  }
  HMP0 = A            ; New movement value P0

  A = SWCHA           ; Joystick
  A = A & 0x08        ; Player 1 left switch
  if(A==0) {
      A =  0xF0       ; Moving left value
  } else {
      A = SWCHA       ; Joystick
      A = A & 0x04    ; Player 1 right switch
      if(A==0) {
          A =  0x10   ; Moving right value
      } else {
          ++ENTROPYC  ; Increase entropy
          A = 0       ; Not moving value
      }
  }
  HMP1 = A            ; New movement value P1

}

; InitSeletMode
; ===================================
INIT_SELMODE() {
;
; This function initializes the games SELECT-MODE
;          
  A =  0        ; Turn off ...
  AUDV0 = A     ; ... all ...
  AUDV1 = A     ; ... sound
  A = 0xC8      ; Background color ...
  COLUBK = A    ; ... greenish bright
  A = 2         ; Now in ...
  MODE = A      ; SELECT game mode
}

; SelectMode
; ======================================
SELMODE() {
;
; This function is called once per frame to process the SELECT-MODE.
; The wall moves here, but doesn't change or collide with players.
; This function selects between 1 and 2 player game.
;
  MOVE_WALLS()         ; Move the walls
  SEL_RESET_CHK()      ; Check the reset/select switches
  
  if(A==1 || A==3) {
      INIT_PLAYMODE()  ; Reset toggled ... start game
  } else if(A==2) {
      A =  PLAYR1Y     ; Select toggled. Get player 1 Y coordinate
      if(A==255) {
          A =  12      ; Onscreen if it is currently off
      } else {
          A =  255     ; Offscreen if it is currently on
      }
      STA  PLAYR1Y     ; Toggled Y coordinate
  }

  EXPERTISE()          ; Adjust both players for pro settings
}

; InitGoMode
; ======================================
INIT_GOMODE() {
;
; This function initializes the GAME-OVER game mode.
;   
  HMCLR = A             ; Stop both players from moving
  A = CXP0FB            ; P0 collision with wall
  A = A & 0x80          ; Did player 0 collide?

  if(A==0) {
      A  = 2            ; No ... move player 0 ...
      PLAYR0Y = A       ; ... up the screen
  }

  A = CXP1FB            ; P1 collision with wall
  A = A & 0x80          ; Did player 1 collide?

  if(A==0 ) {
      A =  PLAYR1Y
      if(A!=255) { 
          A =  2        ; Player 1 is onscreen and didn't collide ...
          PLAYR1Y = A   ; ... move up the screen
      }
  }

  A =  0         ; Going to ...
  MODE = A       ; ... game-over mode
  AUDV0 = A      ; Turn off any ...
  AUDV1 = A      ; ... sound
  INIT_GO_FX()   ; Initialize sound effects
}

; GoMode
; ======================================
GOMODE() {
;
; This function is called every frame to process the game
; over sequence. When the sound effect has finished, the
; game switches to select mode.
;
  PROCESS_GO_FX()       ; Process the sound effects
  if(A!=0) {
      INIT_SELMODE()    ; When effect is over, go to select mode
  }
}                       ; Keep coming back till the effect is over

; Utils
; ======================================
MOVE_WALLS() {
;
; This function moves the wall down the screen and back to position 0
; when it reaches (or passes) 112.
;
;
  --WALLDELY     ; Time to move the wall
  A =  WALLDELY

  if(A!=0) {               ; A!=0 if still delaying wall movement
      A = 0                ; Return flag that wall did NOT restart
  } else {
      A =  WALLDELYR       ; Reset the ...
      WALLDELY = A         ; ... delay count
      A =  WALLSTART       ; Current wall position
      A = A + WALL_INC     ; Increment wall position      
      if(A<112) {          ; A>=112 if wall is off screen
          WALLSTART = A    ; Store new wall position
          A = 0            ; Return flag that wall did NOT restart
      } else {
          A = 0            ; Else restart ...
          WALLSTART = A    ; ... wall at top of screen
          A = 1            ; Return flag that wall DID restart
      }
  }
  
}

; ======================================
NEW_GAPS() {
;
; This function builds the PF0, PF1, and PF2 graphics for a wall
; with the gap pattern (GAPBITS) placed at random in the 20 bit
; area.
;
  A =  255         ; Start with ...
  WALLDRELA = A    ; ... solid wall in PF0 ...
  WALLDRELB = A    ; ... and PF1
  A = GAPBITS      ; Store the gap pattern ...
  WALLDRELC = A    ; ... in PF2
  
  A =  ENTROPYA               ; Get ...
  A = A + C_Flag + ENTROPYB   ; ... a randomish ...
  A = A + C_Flag + ENTROPYC   ; ... number ...  
  ENTROPYC = A
  A = A &15                   ; ... 0 to 15

  if(A>12) {
      ;A = A - 9
      A = A - C_Flag - 9
  }
  
  while(A!=0) {
      C_Flag = 1       ; Roll gap ...
      ROR  WALLDRELC   ; ... left ...
      ROL  WALLDRELB   ; ... desired ...
      ROR  WALLDRELA   ; ... times ...
      A = A - 1
  }

}

; ======================================
MAKE_SCORE() {
;
; This function builds the PF1 and PF2 graphics rows for
; the byte value passed in A. The current implementation is
; two-digits only ... PF2 is blank.
;
  X =  0             ; 100's digit
  Y = 0              ; 10's digit

  while(A>=100) {
      ++X            ; Count ...
      A = A - 100    ; ... value
  }

  while(A>=10) {
      ++Y            ; Count ...
      A = A - 10     ; ... value
  }

  A<<1               ; One's digit ...
  A<<1               ; ... *8 ....
  A<<1               ; ... to find picture
  X = A              ; One's digit picture to X
  A = Y              ; Now the 10's digit
  A<<1               ; Multiply ...
  A<<1               ; ... by 8 ...
  A<<1               ; ... to find picture
  Y = A              ; 10's picture in Y
  
  A =  DIGITS[Y]     ; Get the 10's digit
  A = A & 0xF0       ; Only use the left side of the picture
  SCORE_PF1 = A      ; Store left side
  A = DIGITS[X]      ; Get the 1's digit
  A = A & 0x0F       ; Only use the right side of the picture
  A = A | SCORE_PF1  ; Put left and right half together
  SCORE_PF1 = A      ; And store image

  A = DIGITS+1[Y]    ; Repeat for 2nd line of picture
  A = A & 0xF0
  SCORE_PF1[1] = A
  A = DIGITS+1[X]
  A = A & 0x0F
  A = A | SCORE_PF1[1]
  SCORE_PF1[1] = A

  A = DIGITS+2[Y]    ; Repeat for 3nd line of picture
  A = A & 0xF0
  SCORE_PF1[2] = A
  A = DIGITS+2[X]
  A = A & 0x0F
  A = A | SCORE_PF1[2]
  SCORE_PF1[2] = A

  A = DIGITS+3[Y]    ; Repeat for 4th line of picture
  A = A & 0xF0
  SCORE_PF1[3] = A
  A = DIGITS+3[X]
  A = A & 0x0F
  A = A | SCORE_PF1[3]
  SCORE_PF1[3] = A

  A = DIGITS+4[Y]    ; Repeat for 5th line of picture
  A = A & 0xF0
  SCORE_PF1[4] = A
  A = DIGITS+4[X]
  A = A & 0x0F
  A = A | SCORE_PF1[4]
  SCORE_PF1[4] = A

  A = 0              ; For now ...
  SCORE_PF2 = A      ; ... there ...
  SCORE_PF2[1] = A   ; ... is ...
  SCORE_PF2[2] = A   ; ... no ...
  SCORE_PF2[3] = A   ; ... 100s ...
  SCORE_PF2[4] = A   ; ... digit drawn

}

; ======================================
EXPERTISE() {
;
; This function changes the Y position of the players based on the
; position of their respective pro/novice switches. The player 1
; position is NOT changed if the mode is a single-player game.
;
  A =  SWCHB          ; Pro/novice settings
  A = A & 0x80        ; Novice for Player 0?
  if(A==0) {
      A =  12         ; Novice ... near the bottom
  } else {
      A = 8           ; Pro ... near the top
  }
  PLAYR0Y = A         ; ... to Player 0
  
  X = PLAYR1Y
  if(X!=255) {        ; Only move player 1 if it is a 2-player game
      A = SWCHB
      A = A & 0x40
      if(A==0) {
          X = 12      ; Novice ... near the bottom
      } else {
          X = 8       ; Pro ... near the top
      }
      PLAYR1Y = X
  }

}

; ======================================
ADJUST_DIF() {
;
; This function adjusts the wall game difficulty values based on the
; current score. The music can also change with the difficulty. A single
; table describes the new values and when they take effect.
;              
  X = 0  ; Starting at index 0

  while(true) {

      A = SKILL_VALUES[X]       ; Get the score match
      if(A==255) {
          return                ; End of the table ... leave it alone
      }
      if(A==WALLCNT) {
          ++X                   ; Copy ...
          A = SKILL_VALUES[X]   ; ... new ...
          WALL_INC = A          ; ... wall increment
          ++X                   ; Copy ...
          A = SKILL_VALUES[X]   ; ... new ...
          WALLDELY = A          ; ... wall ...
          WALLDELYR = A         ; ... delay
          ++X                   ; Copy ...
          A = SKILL_VALUES[X]   ; ... new ...
          GAPBITS = A           ; ... gap pattern
          ++X                   ; Copy ...
          A = SKILL_VALUES[X]   ; ... new ...
          MUSAIND = A           ; ... MusicA index
          ++X                   ; Copy ...
          A = SKILL_VALUES[X]   ; ... new ...
          MUSBIND = A           ; ... MusicB index
          A = 1                 ; Force ...
          MUSADEL = A           ; ... music to ...
          MUSBDEL = A           ; ... start new
          return
      }
      
      ++X     ; Move ...
      ++X     ; ... X ...
      ++X     ; ... to ...
      ++X     ; ... next ...
      ++X     ; ... row of ...
      ++X     ; ... table
  }
}  

; ======================================
SEL_RESET_CHK() {
;
; This function checks for changes to the reset/select
; switches and debounces the transitions.
;
  
  X =  DEBOUNCE     ; Hold onto old value
  A =  SWCHB        ; New value
  A = A & 3         ; Only need bottom 2 bits

  if(A==DEBOUNCE) {
      A = 0         ; Return 0 ... nothing changed
  } else {
      DEBOUNCE = A  ; Hold new value
      A = A ^ 0xFF  ; Complement the value (active low hardware)
      A = A & 3     ; Only need select/reset
  }
  
}

; InitMusic
; ======================================
INIT_MUSIC() {
;
; This function initializes the hardware and temporaries
; for 2-channel music
;
  A = 0x06     ; Initialize sound ...
  AUDC0 = A    ; ... to pure ...
  AUDC1 = A    ; ... tones
  A = 0        ; Turn off ...
  AUDV0 = A    ; ... all ...
  AUDV1 = A    ; ... sound
  MUSAIND = A  ; Music pointers ...
  MUSBIND = A  ; ... to top of data
  A = 1        ; Force ...
  MUSADEL = A  ; ... music ...
  MUSBDEL = A  ; ... reload
  A = 15       ; Set volume levels ...
  MUSAVOL = A  ; ... to ...
  MUSBVOL = A  ; ... maximum
}

; ProcessMusic
; ======================================
PROCESS_MUSIC() {
;
; This function is called once per frame to process the
; 2 channel music. Two tables contain the commands/notes
; for individual channels. This function changes the
; notes at the right time.
;           
  --MUSADEL               ; Last note ended?
  if(ZERO_SET) {

      do {
          X = MUSAIND            ; Voice-A index
          A = MUSICA[X]          ; Get the next music command
          if(A==0) {             ; A==0 for JUMP command
              ++X                ; Point to jump value
              A = X              ; X to ...
              Y = A              ; ... Y (pointer to jump value)
              ++X                ; Point one past jump value
              A = X              ; Into A so we can subtract
              A = A - MUSICA[Y]  ; New index
              MUSAIND = A        ; Store it
              A = 0              ; Continue processing
          } else if(A==1) {      ; A==1 for CONTROL command
              ++X                ; Point to the control value
              ++MUSAIND          ; Bump the music pointer
              A = MUSICA[X]      ; Get the control value
              ++MUSAIND          ; Bump the music pointer
              AUDC0 = A          ; Store the new control value
              A = 0              ; Continue processing
          } else if(A==2) {      ; A==2 for VOLUME command
              ++X                ; Point to volume value
              ++MUSAIND          ; Bump the music pointer
              A = MUSICA[X]      ; Get the volume value
              ++MUSAIND          ; Bump the music pointer
              MUSAVOL=A          ; Store the new volume value
              A = 0              ; Continue processing
          }
      } while(A==0);

      Y = MUSAVOL     ; Get the volume
      A = A & 31      ; Lower 5 bits are frequency
      if(A==31) {
          Y = 0       ; Frequency of 31 flags silence
      }
      AUDF0 = A       ; Store the frequency
      AUDV0 = Y       ; Store the volume
      A = MUSICA[X]   ; Get the note value again
      ++MUSAIND       ; Bump to the next command
      ROR  A          ; The upper ...
      ROR  A          ; ... three ...
      ROR  A          ; ... bits ...
      ROR  A          ; ... hold ...
      ROR  A          ; ... the ...
      A = A & 7       ; ... delay
      C_Flag = 0      ; No accidental carry
      ROL  A          ; Every delay tick ...
      ROL  A          ; ... is *4 frames
      MUSADEL = A     ; Store the note delay
  }
  
  --MUSBDEL           ; Repeat Channel A sequence for Channel B
  if(ZERO_SET) {
      
      do {
          X = MUSBIND
          A = MUSICB[X]
          if(A==0) {
              ++X  
              A = X     
              Y = A     
              ++X     
              A = X         
              A = A - MUSICB[Y]  
              MUSBIND    = A
              A = 0
          } else if(A==1) {
              ++X           
              ++MUSBIND  
              A = MUSICB[X]
              ++MUSBIND   
              AUDC1 = A
              A = 0
          } else if(A==2) {
              ++X            
              ++MUSBIND   
              A = MUSICB[X]
              ++MUSBIND  
              MUSBVOL = A  
              A = 0
          }
      } while(A==0);

      Y = MUSBVOL
      A = A & 31
      if(A==31) {
          Y = 0
      }
      AUDF1 = A
      AUDV1 = Y
      A = MUSICB[X]
      ++MUSBIND
      ROR  A
      ROR  A
      ROR  A
      ROR  A
      ROR  A
      A = A & 7
      C_Flag = 0
      ROL  A
      ROL  A
      MUSBDEL = A
  }
}

; GoFX
; ======================================
INIT_GO_FX() {
;
; This function initializes the hardware and temporaries
; to play the soundeffect of a player hitting the wall
;
  A = 5         ; Set counter for frame delay ...
  MUS_TMP1 = A  ; ... between frequency change
  A = 3         ; Tone type ...
  AUDC0 = A     ; ... poly tone
  A = 15        ; Volume A ...
  AUDV0 = A     ; ... to max
  A = 0         ; Volume B ...
  AUDV1 = A     ; ... silence
  A = 240       ; Initial ...
  MUS_TMP0 = A  ; ... sound ...
  AUDF0 = A     ; ... frequency
}

; ======================================
PROCESS_GO_FX() {
;
; This function is called once per scanline to play the
; soundeffects of a player hitting the wall.
;        
  --MUS_TMP1        ; Time to change the frequency?
  if(ZERO_SET) {
      A = 5         ; Reload ...
      MUS_TMP1 = A  ; ... the frame count
      ++MUS_TMP0    ; Increment ...
      A = MUS_TMP0  ; ... the frequency divisor
      AUDF0 = A     ; Change the frequency
      if(A==0) {
          A = 1     ; All done ... return 1
          return
      }
  }
  A = 0
  
}

; MusicCommands
; ======================================
; Music commands for Channel A and Channel B
  
  ; A word on music and wall timing ...

  ; Wall moves between scanlines 0 and 111 (112 total)

  ; Wall-increment   frames-to-top
  ;      3             336
  ;      2             224
  ;      1             112
  ;     0.5             56  ; Ah ... but we are getting one less
  
  ; Each tick is multiplied by 4 to yield 4 frames per tick
  ; 32 ticks/song = 32*4 = 128 frames / song

  ; We want songs to start with wall at top ...

  ; Find the least-common-multiple
  ; 336 and 128 : 2688 8 walls, 21 musics
  ; 224 and 128 :  896 4 walls,  7 musics
  ; 112 and 128 :  896 8 walls,  7 musics
  ;  56 and 128 :  896 16 walls, 7 musics

  ; Wall moving every other gives us 112*2=224 scanlines
  ; Song and wall are at start every 4
  ; 1 scanline, every 8
  ; Wall delay=3 gives us 128*3=336 scanlines 2

; MUSIC EQUATES
;
MUSCMD_JUMP    .equ 0
MUSCMD_CONTROL .equ 1
MUSCMD_VOLUME  .equ 2
MUS_REST       .equ 31
MUS_DEL_1      .equ 32*1
MUS_DEL_2      .equ 32*2
MUS_DEL_3      .equ 32*3
MUS_DEL_4      .equ 32*4

MUSICA

MA_SONG_1

  .BYTE MUSCMD_CONTROL, 0x0C  ; Control (pure tone)
  .BYTE MUSCMD_VOLUME,  15   ; Volume (full)

MA1_01
  .BYTE MUS_DEL_3  +  15
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_3  +  15
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  7
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  7
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_2  +  MUS_REST
  .BYTE MUS_DEL_1  +  8
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_4  +  MUS_REST
  .BYTE MUS_DEL_2  +  17
  .BYTE MUS_DEL_2  +  MUS_REST
  .BYTE MUS_DEL_2  +  17
  .BYTE MUS_DEL_2  +  MUS_REST
  .BYTE MUS_DEL_3  +  16
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUSCMD_JUMP, (MA1_END - MA1_01)  ; Repeat back to top
MA1_END

MA_SONG_2

  .BYTE MUSCMD_CONTROL, 0x0C
  .BYTE MUSCMD_VOLUME,  15

MA2_01
  .BYTE MUS_DEL_1  +  15
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  15
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_2  +  MUS_REST
  .BYTE MUS_DEL_4  +  7
  .BYTE MUS_DEL_4  +  MUS_REST
  .BYTE MUS_DEL_2  +  15
  .BYTE MUS_DEL_4  +  MUS_REST
  .BYTE MUS_DEL_2  +  12
  .BYTE MUS_DEL_2  +  MUS_REST
  .BYTE MUS_DEL_2  +  15
  .BYTE MUS_DEL_2  +  MUS_REST
  .BYTE MUS_DEL_2  +  17
  .BYTE MUS_DEL_2  +  MUS_REST  
  .BYTE MUSCMD_JUMP, (MA2_END - MA2_01) ; Repeat back to top
MA2_END



MUSICB

MB_SONG_1

  .BYTE MUSCMD_CONTROL, 0x08  ; Control (white noise)
  .BYTE MUSCMD_VOLUME,  8    ; Volume (half)

MB1_01     
  .BYTE MUS_DEL_1  +  10
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  20
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  30
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  15
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  10
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  20
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  30
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  15
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUSCMD_JUMP, (MB1_END - MB1_01) ; Repeat back to top
MB1_END

MB_SONG_2

  .BYTE MUSCMD_CONTROL, 0x08
  .BYTE MUSCMD_VOLUME,  8

MB2_01    
  .BYTE MUS_DEL_1  +  1
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  1
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  1
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  1
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  30
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  30
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  30
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUS_DEL_1  +  30
  .BYTE MUS_DEL_1  +  MUS_REST
  .BYTE MUSCMD_JUMP, (MB2_END - MB2_01) ; Repeat back to top
MB2_END


; SkillValues
; ======================================
SKILL_VALUES
;
; This table describes how to change the various
; difficulty parameters as the game progresses.
; For instance, the second entry in the table 
; says that when the score is 4, change the values of
; wall-increment to 1, frame-delay to 2, gap-pattern to 0,
; MusicA to 24, and MusicB to 22.
;
; A 255 on the end of the table indicates the end 
;
  ; Wall  Inc  Delay     Gap   MA                 MB
  .BYTE  0,    1,   3,     0  ,MA_SONG_1-MUSICA , MB_SONG_1-MUSICB
  .BYTE  4,    1,   2,     0  ,MA_SONG_2-MUSICA , MB_SONG_2-MUSICB
  .BYTE  8,    1,   1,     0  ,MA_SONG_1-MUSICA , MB_SONG_1-MUSICB
  .BYTE 16,    1,   1,     1  ,MA_SONG_2-MUSICA , MB_SONG_2-MUSICB
  .BYTE 24,    1,   1,     3  ,MA_SONG_1-MUSICA , MB_SONG_1-MUSICB
  .BYTE 32,    1,   1,     7  ,MA_SONG_2-MUSICA , MB_SONG_2-MUSICB
  .BYTE 40,    1,   1,    15  ,MA_SONG_1-MUSICA , MB_SONG_1-MUSICB
  .BYTE 48,    2,   1,     0  ,MA_SONG_2-MUSICA , MB_SONG_2-MUSICB
  .BYTE 64,    2,   1,     1  ,MA_SONG_1-MUSICA , MB_SONG_1-MUSICB
  .BYTE 80,    2,   1,     3  ,MA_SONG_2-MUSICA , MB_SONG_2-MUSICB
  .BYTE 96 ,   2,   1,     7  ,MA_SONG_1-MUSICA , MB_SONG_1-MUSICB
  .BYTE 255

; Images
; ======================================
; Image for players
GR_PLAYER:    
  .BYTE 0x10 ; ...*....
  .BYTE 0x10 ; ...*....
  .BYTE 0x28 ; ..*.*...
  .BYTE 0x28 ; ..*.*...
  .BYTE 0x54 ; .*.*.*..
  .BYTE 0x54 ; .*.*.*..
  .BYTE 0xAA ; *.*.*.*.
  .BYTE 0x7C ; .*****..

; ======================================
; Images for numbers
DIGITS: 
; We only need 5 rows, but the extra space on the end makes each digit 8 rows,
; which makes it the multiplication easier.

  .BYTE  0x0E ,0x0A ,0x0A ,0x0A ,0x0E, 0,0,0 ; 00
  .BYTE  0x22 ,0x22 ,0x22 ,0x22 ,0x22, 0,0,0 ; 11
  .BYTE  0xEE ,0x22 ,0xEE ,0x88 ,0xEE, 0,0,0 ; 22
  .BYTE  0xEE ,0x22 ,0x66 ,0x22 ,0xEE, 0,0,0 ; 33
  .BYTE  0xAA ,0xAA ,0xEE ,0x22 ,0x22, 0,0,0 ; 44
  .BYTE  0xEE ,0x88 ,0xEE ,0x22 ,0xEE, 0,0,0 ; 55
  .BYTE  0xEE ,0x88 ,0xEE ,0xAA ,0xEE, 0,0,0 ; 66
  .BYTE  0xEE ,0x22 ,0x22 ,0x22 ,0x22, 0,0,0 ; 77
  .BYTE  0xEE ,0xAA ,0xEE ,0xAA ,0xEE, 0,0,0 ; 88
  .BYTE  0xEE ,0xAA ,0xEE ,0x22 ,0xEE, 0,0,0 ; 99

; Vectors
; ====================================== 
; 6502 Hardware vectors at the end of memory
.org 0xF7FA  ; Ghosting to 0xFFFA for 2K part
  .WORD  0x0000   ; NMI vector (not used)
  .WORD  main     ; Reset vector (top of program)
  .WORD  0x0000   ; IRQ and BRK vector (not used)

.end