Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
2930 lines (2268 sloc) 100 KB
; This is Gene Medic version 1.0
; by Jason H. Moore, Ph.D.
; File genemedic.asm
; Last updated 12/29/17
; Compile with DASM genemedic.asm -ogenemedic.bin -f3
; The goal of the game is to save a patient with a common
; disease such as cancer by editing mutations in their
; genes. Mutations can have known or unknown consequences upon
; editing (joystick down) with some revealed by consulting the
; electronic health record powerup (computer) or the scientific
; literature (text) power ups (joystick up). Each gene edit costs
; money that can be replenished with the money bag power up.
; The goal is to improve the patient health ("P" top score)
; before the financial resources are exhausted ("$" lower score).
; For more information see http://GeneMedic.org
; Special thanks to Andrew Davies and Darrell Spice for their
; great tutorials that helped with the development of this
; edutainment game. Thanks to countless others who posted
; useful comments and code on various websites and forums
; including the Stella mailing list and AtariAge.com.
; I also found the book "Making games for the Atari 2600"
; by Steven Hugg to be useful. I have tried to comment the code
; as much as I possibly can without writing my own book.
; Programming for the 2600 is not easy! You have been warned :)
; ****************************************************************
; Tell the assembler we are working with the 6502 processor
; ****************************************************************
PROCESSOR 6502
; ****************************************************************
; Include the vcs header file with the 2600 register and
; memory map definitions. Also include the macro file that
; has some useful functions such as 'sleep' used here.
; ****************************************************************
INCLUDE VCS.h
INCLUDE macro.h
; ****************************************************************
; Constants set here
; ****************************************************************
P0HEIGHT = 55 ; height of player 0 sprite in scanlines
PUHEIGHT = 16 ; height of powerup sprite in scanlines
SCHEIGHT = 14 ; height of the score playfield
CYTHEIGHT = 14 ; height of the cytoplasm playfield
DNAHEIGHT = 11 ; height of the DNA helix playfield
HHEIGHT = 36 ; height of the heart playfield
GHEIGHT = 14 ; height of the gene name playfield
JHEIGHT = 14 ; height of the jockey name playfield
XMAX = 150 ; max X position to right for player
XMIN = 5 ; min X position to left for player
P0XSTART = 20 ; player 0 starting X position
P0YSTART = 80 ; player 0 starting Y position
P1RESX = 38 ; player 1 starting X position for research
; powerup - in sleep cycles
P1MONX = 54 ; player 1 starting X position for money
; bag powerup - in sleep cycles
P1EHRX = 22 ; player 1 starting X position for EHR
; powerup - in sleep cycles
M0X = 28 ; missile 0 starting X position for in sleep
; cycles. This M is used to queue position
; of nucleotide to edit triggering tricorder
; graphic above player 0.
; ****************************************************************
; Tell the assembler the memory locations of our game variables.
; ds is a pseudo-op for assembler that stands for 'define space'
; ds 1 = one byte and ds 2 = two bytes. Remember that the 2600
; only has 128 bytes of RAM!
; ****************************************************************
SEG.U variables ; pseudo-op for the assembler
ORG $80 ; the starting location of RAM
P0X ds 1 ; player 0 X coordinate
P0Y ds 1 ; player 0 Y coordinate
P0Pos ds 1 ; byte for P0 fine (high nibble) and
; course (low nibble) horizontal position
P0LineColor ds 1 ; player 0 color slice for that scanline
P0GfxFlag ds 1 ; flag for which P0 graphic to use
; 1 = no tricorder
; 2 = health tricorder [+]
; 3 = harm tricorder [!]
; 4 = unknown tricorder [-]
; 5 = context dependent tricorder [::]
ResearchFlag ds 1 ; flag for drawing the research powerup
; 1 = draw, 0 = don't draw
EHRFlag ds 1 ; flag for drawing the EHR powerup
; 1 = draw, 0 = don't draw
MoneyFlag ds 1 ; flag for drawing the money bag powerup
; 1 = draw, 0 = don't draw
NewLevelFlag ds 1 ; time to draw a new playfield? This gets set
; to 1 when player reaches right side of screen
ScreenFlag ds 1 ; which screen to draw? A or B?
; this help set up alternating screens with
; different playfield graphics for sense
; of movement through the cell
PScore ds 1 ; this is the patient health score
DScore ds 1 ; this is the dollars score
DScoreFLag ds 1 ; flag to indicate whether DScore = 0
; to prevent going negative
ScoreGfx ds 1 ; holds the current score graphics line
UpFlag ds 1 ; flag for joystick moved up (for power ups)
DownFlag ds 1 ; flag for joystick moved down (for edits)
MutationFlag ds 1 ; holds info about nature of mutation
; set each screen by a random number
; 0 = no mutation
; 1 = mutation - known, helpful
; 2 = mutation - known, harmful
; 3 = mutation - unknown, helpful
; 4 = mutation - unknown, harmful
; 5 = mutation - context dependent, helpful
; 6 = mutation - context dependent, harmful
EditFlag ds 1 ; keeps track of successful edits
; so score only modified once per screen
MBUsedFlag ds 1 ; keeps track of whether money bag powerup
; has been used for that screen. Only used once.
WinFlag ds 1 ; this indicates that the player has won the game
LoseFlag ds 1 ; this indicates that the player has lost the game
EndSoundFlag ds 1 ; flag indicating final sound effects are done
; so they only play once at end of game
RandomPU ds 1 ; holds 8-bit pseudo random number for powerups
RandomMut ds 1 ; holds 8-bit pseudo random number for mutations
GameLaunchFlag ds 1 ; a flag for indicating the game has booted.
; this is used to indicate the start screen
; should be drawn until fire button pressed
SFX_LEFT ds 1 ; index for sound effect functions
SFX_RIGHT ds 1 ; index for sound effect functions
; ****************************************************************
; Tell the assembler where the origin (ORG) of the program is
; ROM is memory location $F000 to $FFFF
; ****************************************************************
SEG ; pseudo-op to set current segment
ORG $F000 ; let the assembler know start address
; ****************************************************************
; This initializes the RAM and TIA registers by writing 0s.
; It also initalizes the stack pointer to $FF. This code comes
; from Andrew Davie's tutorial. Note just clearing the RAM did
; not work well on the original 2600 hardware.
; ****************************************************************
Reset
ldx #0 ; load the X register with a 0
txa ; transfer X to accumulator
Clear
dex ; decrement X register
txs ; transfer X to stack pointer
pha ; push accumulator onto stack
bne Clear ; branch to Clear if accumulator != 0
; ****************************************************************
; This initializes the random seed for the GetRandom subroutine
; Using two different random numbers to prevent dependencies
; between powerups and mutations.
; ****************************************************************
SetRandomSeed
lda INTIM ; this is an unknown value
eor #$FF ; XOR to flip bits so seed is not 0
sta RandomPU ; Seed for GetRandomPU
lda INTIM ; this is an unknown value
eor #$FF ; XOR to flip bits so seed is not 0
sta RandomMut ; Seed for GetRandomMut
; ****************************************************************
; This initializes the game launch flag for drawing start screen
; when game is first booted.
; ****************************************************************
lda #1 ; load accumulator with a 1
sta GameLaunchFlag ; set game launch flag to draw start
; screen first time through until
; reset switch is pressed then flag=0
jmp PlayfieldInit ; bypass the next bit of code on boot
; ****************************************************************
; This is marks the place in the code to jump to when starting a
; game after fire button is pressed from start screen.
; ****************************************************************
StartHere
lda #0 ; load accumulator with a 0
sta GameLaunchFlag ; reset game launch flag so start
; screen isn't drawn anymore after
; reset switch is pressed
; ****************************************************************
; This initializes the player and playfield color registers
; ****************************************************************
PlayfieldInit
lda #$80 ; load accumulator with dark blue
sta COLUBK ; store in the background color memory
lda #1 ; load the accumulator with 1
sta CTRLPF ; storing a 1 here indicates a
; reflected playfield
lda #$1E ; Load the player 1 color
sta COLUP1 ; set player 1 color register
; ****************************************************************
; This initializes the player X and Y position.
; ****************************************************************
lda #P0XSTART ; load accumulator with X position
sta P0X ; set P0 starting X position
lda #P0YSTART ; load accumulator with Y position
sta P0Y ; set P0 starting Y position
; ****************************************************************
; This initializes the patient (top) and money (bottom) scores
; that are presented to the player as colors. Brighter yellow for
; the patient score is an indication of health. Brighter yellow
; for the money score means more resources have been spent on the
; patient. The game is over when the max patient score (win), min
; patient score (loss), or max money score (loss) are reached.
; ****************************************************************
lda #1 ; load accumulator with 1
sta PScore ; set left score to 1
lda #1 ; load accumulator with 1
sta DScore ; set left score to 1
; ****************************************************************
; This initializes the game flags for tracking score and powerups.
; Most are set to 0 with only th P0 graphics flag set to 1.
; ****************************************************************
lda #1 ; load accumulator with flag = 1 to start
sta P0GfxFlag ; set player 0 graphic flag = 1
; this draws player without tricorder.
; tricorder comes on when P0 collides
; with M0 that is present when mutation
; flag > 0
lda #0 ; load accumulator with flag = 0 to start
sta UpFlag ; set joystick Up Flag = 0
sta DownFlag ; set joystick Down Flag = 0
sta EHRFlag ; set EHR Flag = 0
sta MoneyFlag ; set Money Bag Flag = 0
sta ResearchFlag ; set Research Flag = 0
sta ScreenFlag ; set Screen Flag = 0
sta EditFlag ; set Edit Flag = 0 to start, no edit
sta MBUsedFlag ; set Money Bag used Flag = 0 to start
sta WinFlag ; set the Win Flag = 0 to start, 1=win
sta LoseFlag ; set the Lose Flag = 0 to start, 1=lose
sta MutationFlag ; set Flag = 0, no mutation first screen
sta EndSoundFlag ; set Flag = 0 until end of game
sta NewLevelFlag ; time to draw new playfield? This gets
; set to 1 when player reaches right side
; of screen
; ****************************************************************
; The first part of a 2600 program is the vertical sync that
; tells the TV to get ready to draw a picture. The CPU needs
; to wait for three scanlines for the TV to get ready. This
; is done with VSYNC to start this process and WSYNC (x2) to
; wait for the scanlines to finish.
; ****************************************************************
VerticalSYNC ; The start of the new frame
; Main game loop starts here
lda #2 ; load the accumulator with a 2
sta VSYNC ; store 2 in VSYNC memory to start
sta WSYNC ; wait for scanline to finish
sta WSYNC ; second WSYNC (need at least 2)
lda #0 ; load the accumulator with a 0
sta VSYNC ; store 0 in VSYNC memory to end
; ****************************************************************
; The second part of a 2600 program is the vertical blank
; with an additional 37 scanlines of time before the screen
; is actually drawn on the TV. Can do some game computations
; here but must count the scanlines to add up to 37 total.
; 37 scanlines at 76 cycles each works out to be 43 ticks of
; the timer. Will set TIM64T to 43 to keep track of cycles.
; Will use this time for horizontal positioning of the sprites.
; Will also use this time for game calculations.
; ****************************************************************
lda #43 ; we have 43 ticks of the timer here
sta TIM64T ; start the timer for VBLANC
GameOverCheck1 ; check win & lose flags for game over
; if game over skip game calculations
; so no player movement during final screen
lda WinFlag ; load accumulator with Win Flag
beq GameOverCheck2 ; check lose flag if win flag = 0
jmp VerticalBLANK ; skip game calcs if flag = 1
GameOverCheck2
lda LoseFlag ; load accumulator with Lose Flag
beq NewLevelCheck ; move on to game calcs if lose flag = 0
jmp VerticalBLANK ; skip game calcs if flag = 1
NewLevelCheck ; time to draw new level with new random
; powerup and gameplay flags?
lda NewLevelFlag ; if flag = 1 then it is a new level
bne NewLevel ; if flag = 0 then do game calculations
jmp CheckWin ; but skip setting new gameplay flags
; using jmp here to avoid error due to
; a branch that is too long.
; the new level flag gets set to 1 when
; the player gets to far right side of PF
NewLevel ; start here when it is a new level
; and new flags need to be set
; e.g. new powerup flags get set here
SetEditFlag ; set this flag to 0 on new level
lda #0 ; load accumulator with a 0
sta EditFlag ; make sure Edit Flag = 0 on new screen
; this flag prevents multiple changes to
; score on same screen.
sta MBUsedFlag ; reset this flag that keeps track of
; whether the money bag powerup was used
sta WSYNC ; wait for finish of scanline
SetScreenFlag ; set the flag used to track which screen
; is drawn for cyto and nuc parts of PF
; this flag alternates between 0 and 1
lda ScreenFlag ; load accumulator with flag value
beq ScreenFlagSet1 ; flag is zero so need to change to 1
ScreenFlagSet0 ; flag = 1 so change to a 0
lda #0 ; load accumulator with a 0
sta ScreenFlag ; flag is 1 so need to change to 0
jmp SetPUFlags ; done setting helix flag
ScreenFlagSet1 ; change this flag to a 1
lda #1 ; load accumulator with a 1
sta ScreenFlag ; flag is 0 so need to change to 1
SetPUFlags ; set new flags for the powerups
lda #0 ; make sure all flags are reset to 0
; when a new level starts
sta EHRFlag ; set flag to 0
sta ResearchFlag ; set flag to 0
sta MoneyFlag ; set flag to 0
jsr GetRandomPU ; generate a random number from 0-255
; number is returned in accumulator
and #%00000011 ; limit number to 0-3
tax ; transfer accumulator to X register
sta WSYNC ; wait for finish of scanline
CheckEHR ; display EHR powerup?
cpx #0 ; if Random=0 then set EHR flag to 1
bne CheckRes ; if != 1 check research powerup
lda #1 ; load accumulator with a 1
sta EHRFlag ; set flag = 1
sta WSYNC ; wait for finish of scanline
CheckRes ; display research powerup?
cpx #1 ; if Random=1 then set research flag to 1
bne CheckMon ; now check money bag powerup
lda #1 ; load accumulator with a 1
sta ResearchFlag ; set flag = 1
sta WSYNC ; wait for finish of scanline
CheckMon ; display money bag powerup?
cpx #2 ; if Random=2 then set money bag flag to 1
bne CheckDone ; done setting powerup flags
lda #1 ; load accumulator with a 1
sta MoneyFlag ; set flag = 1
sta WSYNC ; wait for finish of scanline
CheckDone ; if Random=3 then no powerup
; on that screen and all powerup
; flags = 0
lda #0 ; load accumulator with a 0
sta NewLevelFlag ; set this flag = 0 to reset this flag
; no longer a new level
SetMutFlags ; set new flags for the mutation
lda #0 ; make sure flag is reset to 0
sta MutationFlag ; set flag to 0
jsr GetRandomMut ; generate a random number from 0-255
; number is returned in accumulator
and #%00000111 ; limit number to 0-7
sta MutationFlag ; store value in mutation flag
sta WSYNC ; wait for finish of scanline
CheckWin ; is the game over? winner?
ldx PScore ; load the X register with the patient score
cpx #9 ; is the patient healed?
bne CheckLose1 ; branch if PScore != 9, not healed
lda #1 ; load accumulator with a 1
sta WinFlag ; winner! Set WinFlag = 1
jmp VerticalBLANK ; move on to draw final screen
CheckLose1 ; check for loss based on patient score = 0
sta WSYNC ; wait for finish of scanline
lda PScore ; load accumulator with the patient score
bne CheckLose2 ; branch if PScore != 0
lda #1 ; load accumulator with a 1
sta LoseFlag ; loser! Set LoseFlag = 1
jmp VerticalBLANK ; move on to draw final screen
CheckLose2 ; check for loss based on dollar score = 9
ldx DScore ; load the X register with the patient score
cpx #9 ; is the player out of money?
bne MinDScore ; branch if DScore != 9
lda #1 ; load accumulator with a 1
sta LoseFlag ; loser! Set LoseFlag = 1
jmp VerticalBLANK ; move on to draw final screen
MinDScore ; this keep the DScore from falling below 1
lda DScore ; load X register with DScore value
bne SetM0X ; branch if X != 0
lda #1 ; load accumulator with a 1
sta DScore ; store the 1 in DScore to prevent 0
SetM0X ; setting the X position for missile 0
sleep M0X ; sleep this many cycles to set M0
; in middle of screen
sta RESM0 ; trigger X position for missile 0
; this never changes and is used as
; position marker for player 0
sta WSYNC ; wait for finish of scanline
; next section sets the horizontal
; position of player 1 with unique
; X positions for each powerup.
; no fine positioning needed here
; because P1 is static
SetP1XEHR ; set powerup X pos if EHR flag = 1
lda EHRFlag ; load the accumulator with EHR flag
beq SetP1XRes ; jump to next if flag = 0
sleep P1EHRX ; delay to horizontally position sprite
sta RESP1 ; set sprite X pos by writing to RESP1
sta WSYNC ; wait for finish of scanline
jmp StartHMoveP0 ; go to next section for P0 positioning
SetP1XRes ; set powerup X pos if research flag = 1
sta WSYNC ; wait for finish of scanline
lda ResearchFlag ; load the accumulator with research flag
beq SetP1XMon ; jump to next if flag = 0
sleep P1RESX ; delay to horizontally position sprite
sta RESP1 ; set sprite X pos by writing to RESP1
sta WSYNC ; wait for finish of scanline
jmp StartHMoveP0 ; go to next section for P0 positioning
SetP1XMon ; set powerup X pos if money bag flag = 1
sta WSYNC ; wait for finish of scanline
lda MoneyFlag ; load the accumulator with money bag flag
beq StartHMoveP0 ; jump to next if flag = 0
sleep P1MONX ; delay to horizontally position sprite
sta RESP1 ; set sprite X pos by writing to RESP1
StartHMoveP0 ; process P0 horizontal move here
; this bit of code from Battlezone game
; commonly used for horizontal positioning
sta WSYNC ; wait for finish of scanline
; timing of this WSYNC and next important
; for correct horizontal movement
lda P0X ; load accumulator with P0 X position
sec ; set the carry flag
DivideLoop ; divide by 15
sbc #15 ; subtract 15
bcs DivideLoop ; branch until negative
eor #7 ; calculate the fine offset
asl ; arithmetic shift left
asl
asl
asl
sta RESP0 ; fix the course position
sta WSYNC ; wait for finish of scanline
sta HMP0 ; set the fine offset
CheckMoneyBag ; is the money bag powerup activated?
lda MoneyFlag ; check if money bag powerup present
beq CheckP0M0Col1 ; if=0 then powerup not present
lda UpFlag ; is the jostick up?
beq CheckP0M0Col1 ; if=0 then joystick not up and skip
lda MBUsedFlag ; has the money bag powerul been used?
beq MBUse ; branch to use money bag if flag = 0
jmp CheckP0M0Col1 ; jump because powerup already used
MBUse
dec DScore ; powerup activated - decrease DScore
lda #1 ; load accumulator with a 1
sta MBUsedFlag ; store the 1 in MBUsedFlag so can't use
ldy #sfxPING ; select ping sound effect
jsr SFX_TRIGGER ; execute the sound
CheckP0M0Col1 ; This checks for a P0 - M0 collision
lda #1 ; load the accumulator with a 1
sta P0GfxFlag ; set the P0 graphics flag to
; tricorder off
lda #%01000000 ; load the D6 bit in accumulator
bit CXM0P ; bit compare to what is in col register
bne Collision1 ; branch if collision = 1
jmp VerticalBLANK ; skip to Vertical BLANK if no collision
; using jmp here because branch too long
Collision1
lda MutationFlag ; load accumulator with mutation flag
tax ; transfer accumulator to X register
cpx #7 ; compare X register to 7
bne Collision2 ; branch if != 7
ldx #5 ; change the 7 to a 5
; need to do something with the 7 since
; we only have 6 choices to map below
; this creates a slight excess of context
; dependent mutations that are helpful
; this is ok because I like these best
Collision2
lda DownFlag ; load accumulator with down joystick flag
tay ; transfer accumulator to Y register
cpx #0 ; is there a mutation? no mutation = 0
bne MutationYes ; if yes process type of mutation
jmp MutationDone ; if no jump to mutation done and VBLANK
MutationYes
sta WSYNC ; wait for finish of scanline
MutKnownPos ; known helpful mutation?
cpx #1 ; is mutation flag = 1?
bne MutKnownNeg ; branch if flag != 1
lda #2 ; load the accumulator with 2
sta P0GfxFlag ; set the P0 graphics flag
; to have right tricorder visible
lda EditFlag ; load edit flag to see if edit already made
beq MKPEdit ; branch if edit = 0
jmp MutationDone ; otherwise we are done
MKPEdit ; let's edit if joystick is down
cpy #1 ; is joystick down? Did a tay earlier
bne MutKnownNeg ; skip if not
inc PScore ; helpful editing has occured, inc score
inc DScore ; money used to edit, inc score
ldy #sfxHELP ; select sound effect for helpful edit
jsr SFX_TRIGGER ; execute the sound
lda #1 ; load accumulator with a 1
sta EditFlag ; reset edit flag = 1 to indicate no more
; edits this level
sta WSYNC ; wait for finish of scanline
jmp MutationDone ; all done - move on to VBLANK
MutKnownNeg ; known harmful mutation?
cpx #2 ; is mutation flag = 2?
bne MutUnknownPos ; branch if flag != 2
lda #3 ; load the accumulator with 3
sta P0GfxFlag ; set the P0 graphics flag
; to have right tricorder visible
lda EditFlag ; load edit flag to see if edit already made
beq MKNEdit ; branch if edit = 0
jmp MutationDone ; otherwise we are done
MKNEdit ; let's edit if joystick is down
cpy #1 ; is joystick down?
bne MutUnknownPos ; skip if not
dec PScore ; harmful editing has occured, dec score
inc DScore ; money used to edit, inc score
ldy #sfxHARM ; select sound effect for harmful edit
jsr SFX_TRIGGER ; execute the sound
lda #1 ; load accumulator with a 1
sta EditFlag ; reset edit flag = 1 to indicate no more
; edits this level
sta WSYNC ; wait for finish of scanline
jmp MutationDone ; all done - move on to VBLANK
MutUnknownPos ; unknown positive mutation - player must guess
; or use research powerup to reveal effect
cpx #3 ; is mutation flag = 3?
bne MutUnknownNeg ; branch if flag != 3
lda ResearchFlag ; now check if research powerup present
beq MUPP04 ; if=0 then powerup not present
; and player doesn't know type of mutation
lda UpFlag ; is the jostick up?
beq MUPP04 ; if=0 then not up and draw tricorder 4
; with symbol for unknown mutation
lda #2 ; load the accumulator with 2
sta P0GfxFlag ; set the P0 graphics flag
; to have right helpful tricorder visible
jmp MUPP02 ; skip the next section
MUPP04 ; draw the neutral tricorder since
; mutation unknown to player
lda #4 ; load the accumulator with 4
sta P0GfxFlag ; set the P0 graphics flag
; to have right tricorder visible
MUPP02
lda EditFlag ; load edit flag to see if edit already made
beq MUPEdit ; branch if edit = 0
jmp MutationDone ; otherwise we are done
MUPEdit ; let's edit if joystick is down
cpy #1 ; is joystick down?
bne MutUnknownNeg ; skip if not
inc PScore ; helpful editing has occured, inc score
inc DScore ; money used to edit, inc score
ldy #sfxHELP ; select sound effect for helpful edit
jsr SFX_TRIGGER ; execute the sound
lda #1 ; load accumulator with a 1
sta EditFlag ; reset edit flag = 1 to indicate no more
; edits this level
sta WSYNC ; wait for finish of scanline
jmp MutationDone ; all done - move on to VBLANK
MutUnknownNeg ; unknown positive mutation - player must guess
; or use research powerup to reveal effect
cpx #4 ; is mutation flag = 4?
bne MutContextPos ; branch if flag != 4
lda ResearchFlag ; now check if research powerup present
beq MUNP04 ; if=0 then powerup not present
; and player doesn't know type of mutation
lda UpFlag ; is the jostick up?
beq MUNP04 ; if=0 then not up and draw tricorder 4
; with symbol for context-dependent mutation
lda #3 ; load the accumulator with 3
sta P0GfxFlag ; set the P0 graphics flag
; to have right helpful tricorder visible
jmp MUNP03 ; skip the next section
MUNP04 ; draw the neutral tricorder since
; mutation unknown to player
lda #4 ; load the accumulator with 4
sta P0GfxFlag ; set the P0 graphics flag
; to have right tricorder visible
MUNP03
lda EditFlag ; load edit flag to see if edit already made
beq MUNEdit ; branch if edit = 0
jmp MutationDone ; otherwise we are done
MUNEdit ; let's edit if joystick is down
cpy #1 ; is joystick down?
bne MutContextPos ; skip if not
dec PScore ; harmful editing has occured, dec score
inc DScore ; money used to edit, inc score
ldy #sfxHARM ; select sound effect for harmful edit
jsr SFX_TRIGGER ; execute the sound
lda #1 ; load accumulator with a 1
sta EditFlag ; reset edit flag = 1 to indicate no more
; edits this level
sta WSYNC ; wait for finish of scanline
jmp MutationDone ; all done - move on to VBLANK
MutContextPos ; context-dependent positive mutation
; player must guess or use EHR powerup
cpx #5 ; is mutation flag = 5?
bne MutContextNeg ; branch if flag != 5
lda EHRFlag ; now check if EHR powerup present
beq MCPP05 ; if=0 then powerup not present
; and player doesn't know type of mutation
lda UpFlag ; is the jostick up?
beq MCPP05 ; if=0 then not up and draw tricorder 5
; with symbol for context-depentent mutation
lda #2 ; load the accumulator with 2
sta P0GfxFlag ; set the P0 graphics flag
; to have right helpful tricorder visible
jmp MCPP02 ; skip the next section
MCPP05 ; draw the neutral tricorder since
; mutation unknown to player
lda #5 ; load the accumulator with 5
sta P0GfxFlag ; set the P0 graphics flag
; to have right tricorder visible
MCPP02
lda EditFlag ; load edit flag to see if edit already made
beq MCPEdit ; branch if edit = 0
jmp MutationDone ; otherwise we are done
MCPEdit ; let's edit if joystick is down
cpy #1 ; is joystick down?
bne MutContextNeg ; skip if not
inc PScore ; helpful editing has occured, inc score
inc DScore ; money used to edit, inc score
ldy #sfxHELP ; select sound effect for helpful edit
jsr SFX_TRIGGER ; execute the sound
lda #1 ; load accumulator with a 1
sta EditFlag ; reset edit flag = 1 to indicate no more
; edits this level
sta WSYNC ; wait for finish of scanline
jmp MutationDone ; all done - move on to VBLANK
MutContextNeg ; context-dependent negative mutation
; player must guess or use EHR powerup
cpx #6 ; is mutation flag = 6?
bne MutationDone ; branch to done if flag != 6
; this allow nothing to happen when flag = 7
lda EHRFlag ; now check if EHR powerup present
beq MCNP05 ; if=0 then powerup not present
; and player doesn't know type of mutation
lda UpFlag ; is the jostick up?
beq MCNP05 ; if=0 then not up and draw tricorder 5
; with symbol for context-depentent mutation
lda #3 ; load the accumulator with 3
sta P0GfxFlag ; set the P0 graphics flag
; to have right helpful tricorder visible
jmp MCNP03 ; skip the next section
MCNP05 ; draw the neutral tricorder since
; mutation unknown to player
lda #5 ; load the accumulator with 5
sta P0GfxFlag ; set the P0 graphics flag
; to have right tricorder visible
MCNP03
lda EditFlag ; load edit flag to see if edit already made
beq MCNEdit ; branch if edit = 0
jmp MutationDone ; otherwise we are done
MCNEdit ; let's edit if joystick is down
cpy #1 ; is joystick down?
bne MutationDone ; skip if not
dec PScore ; harmful editing has occured, dec score
inc DScore ; money used to edit, inc score
ldy #sfxHARM ; select sound effect for harmful edit
jsr SFX_TRIGGER ; execute the sound
lda #1 ; load accumulator with a 1
sta EditFlag ; reset edit flag = 1 to indicate no more
; edits this level
sta WSYNC ; wait for finish of scanline
MutationDone ; all done checking mutations
sta CXCLR ; clear the collision registers
sta WSYNC ; wait for finish of scanline
VerticalBLANK ; The finish vertical blank loop
; This burns the scanlines left over
lda INTIM ; load timer value into accumulator
bne VerticalBLANK ; loop if timer not zero
sta WSYNC ; wait for scanline to finish
sta HMOVE ; finalize the horizontal move of P0
; this triggers execution of motion
; degree of move controlled by HMP0/1
; must be done at this point in code
sta WSYNC ; wait for scanline to finish
sta VBLANK ; store 0 in VBLANK memory to end
; and start drawing the picture by
; turning the TV beam on
; ****************************************************************
; The third part of a 2600 program is the screen drawing
; routine that is often called the kernel. This is where
; the players, missiles, and playfield are drawn. There
; are 192 scanlines to be drawn with 228 color clocks per line.
; There are 192 * 228 = 43,776 color clocks. There are 3
; machine cycles per color clock. This give 14,592 machine
; cycles. Dividing this by 64 gives 228 timer ticks. So, we store
; 228 in TIM64T to start the timer that is used to draw screen.
; We are using the Y register to keep track of 192 scanlines.
; We will draw the screen in chunks starting with the scores.
; ****************************************************************
ldy #192 ; load the Y register with 192
; this is the first scanline at top
; of the screen
lda #228 ; 228 timer ticks
sta TIM64T ; start the timer
lda #$00 ; load accumulator with PF color
sta COLUPF ; set the PF color to black
lda #%11111111 ; draw solid pattern for initial PF
sta PF0 ; set the PF0 register
sta PF1 ; set the PF1 register
sta PF2 ; set the PF2 register
DrawScreenTop ; Draw very top part of screen in black
dey ; decrement Y to burn a scanline
sta WSYNC ; wait for scanline to finish
cpy #188 ; Have 4 score scanlines passed?
bne DrawScreenTop ; loop until Y = 188 then set up the
; score part of PF
ldx PScore ; Load X with current patient score
lda PScoreColor,x ; use the score to get the PF color
sta COLUPF ; score-based color of PF for patient
dey ; burn scanline 187 to create border
sta WSYNC ; wait for scanline to finish
ldx (#SCHEIGHT-1) ; set up the score line counter
DrawPScore ; Draw score for patient health
lda PatientGfx,x ; Load patient graphics for this line
sta PF1 ; Draw the "P" for patient
SLEEP 21 ; kill time so this is only part of PF
; drawn
lda BlankGfx1,x ; Blank graphics to erase 2nd PF1
sta PF1 ; Draw the blank PF1 graphics
dex ; decrement our line counter
dey ; decrement the Y register
cpy #173 ; Have 14 score scanlines passed?
sta WSYNC ; wait for scanline to finish
bne DrawPScore ; loop until Y = 173 then set up the
; dollar score part of PF
dey ; burn scanline 172 to create border
sta WSYNC ; wait for scanline to finish
lda #$00 ; load accumulator with PF color
sta COLUPF ; set the PF color to black
DrawDivider1 ; Draw 4 scanline black divider between
; the patient and dollar scores
dey ; decrement the Y register
sta WSYNC ; wait for scanline to finish
cpy #168 ; Have 4 score scanlines passed?
bne DrawDivider1 ; loop until Y = 168 then draw the
; dollar score part of PF
ldx DScore ; Load X with current Left Score
lda DScoreColor,x ; use the score to get the PF color
sta COLUPF ; score-based color of PF for patient
sleep 16 ; kill some cycles here to keep the PF
; in sync
ldx (#SCHEIGHT-1) ; set up the score line counter
dey ; burn scanline 167 to create border
sta WSYNC ; wait for scanline to finish
DrawDScore ; Draw score for dollars
lda DollarGfx,x ; Load dollar graphics for this line
sta PF1 ; Draw the "D" for dollar
SLEEP 20 ; kill time so this is only part of PF
; drawn
lda BlankGfx1,x ; Blank graphics to erase 2nd PF1
sta PF1 ; Draw the blank PF1 graphics
dex ; decrement our line counter
dey ; decrement the Y register
cpy #153 ; Have 14 score scanlines passed?
sta WSYNC ; wait for scanline to finish
bne DrawDScore ; loop until Y = 153 then set up the
; dollar score part of PF
dey ; burn scanline 152 to create border
sta WSYNC ; wait for scanline to finish
lda #$00 ; load accumulator with PF color
sta COLUPF ; set the PF color to black
DrawDivider2 ; Draw 4 scanline black divider between
; scores and the rest of the playfield
dey ; decrement the Y register
sta WSYNC ; wait for scanline to finish
cpy #148 ; Have 4 score scanlines passed?
bne DrawDivider2 ; loop until Y = 148 then draw the
; dollar score part of PF
lda #$F0 ; load accumulator with powerup bar color
sta COLUPF ; set the PF color
dey ; burn scanline 147
sta WSYNC ; wait for scanline to finish
DrawPowerBar ; draw the next section that displays
; research, money bag, and EHR powerups
CheckResearch ; check the research powerup flag
ldx ResearchFlag ; load the accumulator with res flag
cpx #0 ; compare X to 0
beq CheckEHRFlag ; skip drawing this powerup if X=0
ldx (#PUHEIGHT-1) ; get line count to draw player 1
DrawResearch
lda #$0E ; set the research powerup color to white
sta COLUP1 ; write the color to the P1 color register
lda Research,x ; load player 1 (object) graphic
sta GRP1 ; set the graphic for this line
dex ; decrease the line counter
dey ; decrease the scanline count
sta WSYNC ; wait for scanline to finish
cpy #131 ; allow 16 scanlines to draw powerups
bne DrawResearch ; loop back to continue drawing powerup
; part of screen
jmp PowerDrawDone ; done drawing powerups
CheckEHRFlag ; check the EHR powerup flag
ldx EHRFlag ; load the accumulator with EHR flag
cpx #0 ; compare X to 0
beq CheckMoneyFlag ; skip drawing this powerup if X=0
ldx (#PUHEIGHT-1) ; get line count to draw player 1
DrawEHR
lda #$FC ; set the EHR powerup color to orange
sta COLUP1 ; write the color to the P1 color register
lda EHR,x ; load player 1 (object) graphic
sta GRP1 ; set the graphic for this line
dex ; decrease the line counter
dey ; decrease the scanline count
sta WSYNC ; wait for scanline to finish
cpy #131 ; allow 16 scanlines to draw powerups
bne DrawEHR ; loop back to continue drawing powerup
; part of screen
jmp PowerDrawDone ; done drawing powerups
CheckMoneyFlag ; check the money bag powerup flag
ldx MoneyFlag ; load accumulator with money bag flag
cpx #0 ; compare X to 0
beq PowerDrawDone ; skip drawing this powerup if X=0
ldx (#PUHEIGHT-1) ; get line count to draw player 1
DrawMoney
lda #$CA ; set the money powerup color to green
sta COLUP1 ; write the color to the P1 color register
lda MoneyBag,x ; load player 1 (object) graphic
sta GRP1 ; set the graphic for this line
dex ; decrease the line counter
dey ; decrease the scanline count
sta WSYNC ; wait for scanline to finish
cpy #131 ; allow 16 scanlines to draw powerups
bne DrawMoney ; loop back to continue drawing powerup
; part of screen
PowerDrawDone
lda #0 ; load accumulator with a 0
sta GRP1 ; zero out player 1 graphics
DrawDivider3 ; Draw 3 scanline divider between
; scores and the rest of the playfield
dey ; burn a scanline
sta WSYNC ; wait for scanline to finish
cpy #128 ; Have 3 score scanlines passed?
bne DrawDivider3 ; loop until Y = 128 then draw the
; cell membrane part of PF
lda #$02 ; load accumulator with cell mem color
sta COLUPF ; set the PF color
DrawCellMem ; Draw cell membrane loop
; PF registers already set from above
dey ; decrement the Y register
cpy #120 ; Have 8 scanlines passed?
sta WSYNC ; wait for scanline to finish
bne DrawCellMem ; loop until Y = 120 then set up the
; pattern for the cytoplasm part of PF
lda #$80 ; load accumulator with cyto color
sta COLUPF ; set the PF color
DrawCytTop ; draw the next section that represents
; the cytoplasm part of the cell PF
; draw top part first
dey ; decrement the Y register
cpy #110 ; Have 10 scanlines passed?
sta WSYNC ; wait for scanline to finish
bne DrawCytTop ; this is the first part of cyoplasm PF
ldx (#CYTHEIGHT-1) ; set up the cytoplasm line counter
dey ; decrement the Y register
sta WSYNC ; burn scanline 109 to save a few cycles
; so we can draw asymmetrical PF next
lda #$48 ; load accumulator with cyto color
sta COLUPF ; set the PF color
DrawCytOrgs ; Draw the cytoplasmic organelles next
lda ScreenFlag ; flag for which screen to draw
beq DrawOrgB ; if flag = 0 branch to organelles B
DrawOrgA ; draw organelles option A
lda BlankGfx2,x ; Load blank graphics for this line
sta PF0 ; Draw the blank line
lda MitoGfx,x ; Load mitochondria graphics for this line
sta PF1 ; Draw the mitochondria line
lda BlankGfx2,x ; Load blank graphics for this line
sta PF2 ; Draw the blank line
SLEEP 18 ; kill time for asymmetric playfield
; so we can draw both mito and ER
lda ERGfx,x ; Load ER graphics for this line
sta PF1 ; Draw the ER line
jmp DrawOrgDone ; skip the next section
DrawOrgB ; draw organelles option B
lda BlankGfx2,x ; Load blank graphics for this line
sta PF0 ; Draw the blank line
lda ERGfx,x ; Load mitochondria graphics for this line
sta PF1 ; Draw the mitochondria line
lda BlankGfx2,x ; Load blank graphics for this line
sta PF2 ; Draw the blank line
SLEEP 18 ; kill time for asymmetric playfield
; so we can draw both mito and ER
lda MitoGfx,x ; Load ER graphics for this line
sta PF1 ; Draw the ER line
DrawOrgDone ; done drawing the organelles
dex ; decrement our line counter
dey ; decrement the Y register
sta WSYNC ; wait for scanline to finish
cpy #95 ; Have 14 score scanlines passed?
bne DrawCytOrgs ; loop until Y = 96 then set up the
; dollar score part of PF
lda #$80 ; load accumulator with cyto color
sta COLUPF ; set the PF color
lda #%11111111 ; draw solid pattern for PF
sta PF0 ; set the PF0 register
sta PF1 ; set the PF1 register
sta PF2 ; set the PF2 register
DrawCytBot
dey ; decrement the Y register
cpy #85 ; Have 10 scanlines passed?
sta WSYNC ; wait for scanline to finish
bne DrawCytBot ; this is the bottom part of cyoplasm PF
lda #$02 ; load accum with nucleur membrane color
sta COLUPF ; set the PF color
DrawNucMem ; draw the next section that represents
; the nuclear membrane part of the cell PF
dey ; decrement the Y register
cpy #81 ; Have 4 scanlines passed?
sta WSYNC ; wait for scanline to finish
bne DrawNucMem ; loop until Y = 81 then set up the
; pattern for the nucleus part of PF
lda #$70 ; load accumulator with nucleus color
sta COLUPF ; set the PF color
ldx P0GfxFlag ; load accumulator with value for flag
StartScreenSwitch ; check the game launch flag to see if
; the start screen should be drawn
lda GameLaunchFlag ; load accumulator with game launch flag
beq WinSwitch ; if flag = 0 then don't draw new screen
ldx (#GHEIGHT-1) ; load X register with gene line count
jmp DrawNuc8 ; if flag = 1 then draw start screen
WinSwitch ; if win flag set draw final PF
lda WinFlag ; load accumulator with flag value
beq LoseSwitch ; if flag = 0 branch to next
ldx (#HHEIGHT-1) ; load X register with line count
jmp DrawNuc6 ; if flag = 1 then draw win screen
LoseSwitch ; if lose flag set draw final PF
lda LoseFlag ; load accumulator with flag value
beq P0GfxSwitch1 ; if flag = 0 branch to next
ldx (#HHEIGHT-1) ; load X register with line count
jmp DrawNuc7 ; if flag = 1 then draw win screen
P0GfxSwitch1 ; this set of switches determine what
; graphics to use for player 0
cpx #1 ; compare X register to 1
bne P0GfxSwitch2 ; if not = 1 then branch to next
ldx (#P0HEIGHT-1) ; load X register with P0 line count
dey ; burn scanline 80 to save some cycles
sta WSYNC ; for drawing first line of player 0
jmp DrawNuc1 ; go draw nucleus 1 with P0Graphic1
P0GfxSwitch2
cpx #2 ; compare X register to 2
bne P0GfxSwitch3 ; if not = 2 then branch to next
ldx (#P0HEIGHT-1) ; load X register with P0 line count
dey ; burn scanline 80 to save some cycles
sta WSYNC ; for drawing first line of player 0
jmp DrawNuc2 ; go draw nucleus 2 with P0Graphic2
P0GfxSwitch3
cpx #3 ; compare X register to 3
bne P0GfxSwitch4 ; if not = 3 then branch to next
ldx (#P0HEIGHT-1) ; load X register with P0 line count
dey ; burn scanline 80 to save some cycles
sta WSYNC ; for drawing first line of player 0
jmp DrawNuc3 ; go draw nucleus 3 with P0Graphic3
P0GfxSwitch4
cpx #4 ; compare X register to 4
bne P0GfxSwitch5 ; if not = 4 then branch to next
ldx (#P0HEIGHT-1) ; load X register with P0 line count
dey ; burn scanline 80 to save some cycles
sta WSYNC ; for drawing first line of player 0
jmp DrawNuc4 ; go draw nucleus 4 with P0Graphic4
P0GfxSwitch5
cpx #5 ; compare X register to 5
bne SwitchDone ; if not = 5 then branch to next
ldx (#P0HEIGHT-1) ; load X register with P0 line count
dey ; burn scanline 80 to save some cycles
sta WSYNC ; for drawing first line of player 0
jmp DrawNuc5 ; go draw nucleus 5 with P0Graphic5
SwitchDone
dey ; burn scanline 80 to save some cycles
sta WSYNC ; for drawing first line of player 0
DrawNuc1 ; Draw the nucleus screen loop with P0
; graphics 1 (no tricorder)
lda P0Graphic1,x ; load player 0 graphic 1
sta GRP0
lda P0Color,x ; grab the color of P0 for this line
sta COLUP0 ; set color for P0 each scanline
lda #0 ; load accumulator with 0
sta ENAM0 ; make sure missile 0 is off until later
MissileOn1 ; this bit turns on missile 0 at the
; right scanline. Used to check for
; collision with P0 for turning on
; tricorder for editing the DNA
cpy #47 ; check for scanline 47
bne SkipMissile1 ; skip if not right scanline
lda #2 ; load accumulator with 2
sta ENAM0 ; use the 2 to turn on missile 0
SkipMissile1 ; skip turning on the missile
dex ; decrease the line counter
dey ; decrease the scanline count
sta WSYNC ; wait for scanline to finish
cpy #25 ; time to draw the DNA PF?
bne DrawNuc1 ; loop back to continue drawing nucleus
; part of screen with PF and player
jmp DrawDNAPrep ; done drawing P0 - let's draw the DNA
; part of the playfield
DrawNuc2 ; Draw the nucleus screen loop with P0
; graphics 1 (no tricorder)
lda P0Graphic2,x ; load player 0 graphic 2
sta GRP0
lda P0Color,x ; grab the color of P0 for this line
sta COLUP0 ; set color for P0 each scanline
lda #0 ; load accumulator with 0
sta ENAM0 ; make sure missile 0 is off until later
MissileOn2 ; this bit turns on missile 0 at the
; right scanline. Used to check for
; collision with P0 for turning on
; tricorder for editing the DNA
cpy #47 ; check for scanline 47
bne SkipMissile2 ; skip if not right scanline
lda #2 ; load accumulator with 2
sta ENAM0 ; use the 2 to turn on missile 0
SkipMissile2
dex ; decrease the line counter
dey ; decrease the scanline count
sta WSYNC ; wait for scanline to finish
cpy #25 ; time to draw the DNA PF?
bne DrawNuc2 ; loop back to continue drawing nucleus
; part of screen with PF and player
jmp DrawDNAPrep ; done drawing P0 - let's draw the DNA
; part of the playfield
DrawNuc3 ; Draw the nucleus screen loop with P0
; graphics 3 (tricorder harm !)
lda P0Graphic3,x ; load player 0 graphic 3
sta GRP0
lda P0Color,x ; grab the color of P0 for this line
sta COLUP0 ; set color for P0 each scanline
lda #0 ; load accumulator with 0
sta ENAM0 ; make sure missile 0 is off until later
MissileOn3 ; this bit turns on missile 0 at the
; right scanline. Used to check for
; collision with P0 for turning on
; tricorder for editing the DNA
cpy #47 ; check for scanline 47
bne SkipMissile3 ; skip if not right scanline
lda #2 ; load accumulator with 2
sta ENAM0 ; use the 2 to turn on missile 0
SkipMissile3
dex ; decrease the line counter
dey ; decrease the scanline count
sta WSYNC ; wait for scanline to finish
cpy #25 ; time to draw the DNA PF?
bne DrawNuc3 ; loop back to continue drawing nucleus
; part of screen with PF and player
jmp DrawDNAPrep ; done drawing P0 - let's draw the DNA
; part of the playfield
DrawNuc4 ; Draw the nucleus screen loop with P0
; graphics 4 (unknown tricorder)
lda P0Graphic4,x ; load player 0 graphic 4
sta GRP0
lda P0Color,x ; grab the color of P0 for this line
sta COLUP0 ; set color for P0 each scanline
lda #0 ; load accumulator with 0
sta ENAM0 ; make sure missile 0 is off until later
MissileOn4 ; this bit turns on missile 0 at the
; right scanline. Used to check for
; collision with P0 for turning on
; tricorder for editing the DNA
cpy #47 ; check for scanline 47
bne SkipMissile4 ; skip if not right scanline
lda #2 ; load accumulator with 2
sta ENAM0 ; use the 2 to turn on missile 0
SkipMissile4
dex ; decrease the line counter
dey ; decrease the scanline count
sta WSYNC ; wait for scanline to finish
cpy #25 ; time to draw the DNA PF?
bne DrawNuc4 ; loop back to continue drawing nucleus
; part of screen with PF and player
jmp DrawDNAPrep ; done drawing P0 - let's draw the DNA
; part of the playfield
DrawNuc5 ; Draw the nucleus screen loop with P0
; graphics 5 (context-dependent tricorder)
lda P0Graphic5,x ; load player 0 graphic 5
sta GRP0
lda P0Color,x ; grab the color of P0 for this line
sta COLUP0 ; set color for P0 each scanline
lda #0 ; load accumulator with 0
sta ENAM0 ; make sure missile 0 is off until later
MissileOn5 ; this bit turns on missile 0 at the
; right scanline. Used to check for
; collision with P0 for turning on
; tricorder for editing the DNA
cpy #47 ; check for scanline 47
bne SkipMissile5 ; skip if not right scanline
lda #2 ; load accumulator with 2
sta ENAM0 ; use the 2 to turn on missile 0
SkipMissile5
dex ; decrease the line counter
dey ; decrease the scanline count
sta WSYNC ; wait for scanline to finish
cpy #25 ; time to draw the DNA PF?
bne DrawNuc5 ; loop back to continue drawing nucleus
; part of screen with PF and player
jmp DrawDNAPrep ; done drawing P0 - let's draw the DNA
; part of the playfield
DrawNuc6 ; this draws the final screen with
; a colored heart for healed patient (red)
lda #$80 ; load the accumulator with color number
sta COLUPF ; store in the playfield color memory
lda #%00000000 ; solid pattern for PF
sta PF0 ; set the PF0 register
sta PF1 ; set the PF1 register
sta PF2 ; set the PF2 register
dey ; decrement the Y register
cpy #71 ; Have 10 DNA scanlines passed?
sta WSYNC ; wait for scanline to finish
bne DrawNuc6 ; loop until Y = 71 then set up the
; pattern for heart PF
DrawHeartWin
lda #$30 ; load the accumulator with color number
sta COLUPF ; store in the playfield color memory
lda #%00000000 ; solid pattern for rest of PF
sta PF0 ; set the PF0 register
sta PF1 ; set the PF1 register
lda HeartGfx,x ; load the heart graphics for this line
sta PF2 ; set the PF2 register
dex ; decrement our line counter
dey ; decrement the Y register
cpy #35 ; Have 24 DNA scanlines passed?
sta WSYNC ; wait for scanline to finish
bne DrawHeartWin ; loop until Y = 47 then set up the
; pattern for last part of nucleaus PF
FinHeartWin
lda #$80 ; load the accumulator with color number
sta COLUPF ; store in the playfield color memory
lda #%00000000 ; solid pattern for PF
sta PF0 ; set the PF0 register
sta PF1 ; set the PF1 register
sta PF2 ; set the PF2 register
dey ; decrement the Y register
cpy #23 ; On scanline 23?
sta WSYNC ; wait for scanline to finish
bne FinHeartWin ; loop until Y = 23 then set up the
; DNA PF
jmp DrawDNAPrep ; done drawing P0 - let's draw the DNA
; part of the playfield
DrawNuc7 ; this draws the final screen with
; a colored heart for dead patient (grey)
lda #$80 ; load the accumulator with color number
sta COLUPF ; store in the playfield color memory
lda #%00000000 ; solid pattern for PF
sta PF0 ; set the PF0 register
sta PF1 ; set the PF1 register
sta PF2 ; set the PF2 register
dey ; decrement the Y register
cpy #71 ; Have 10 DNA scanlines passed?
sta WSYNC ; wait for scanline to finish
bne DrawNuc7 ; loop until Y = 71 then set up the
; pattern for heart PF
DrawHeartLose
lda #$06 ; load the accumulator with color number
sta COLUPF ; store in the playfield color memory
lda #%00000000 ; solid pattern for rest of PF
sta PF0 ; set the PF0 register
sta PF1 ; set the PF1 register
lda HeartGfx,x ; load the heart graphics for this line
sta PF2 ; set the PF2 register
dex ; decrement our line counter
dey ; decrement the Y register
cpy #35 ; Have 24 DNA scanlines passed?
sta WSYNC ; wait for scanline to finish
bne DrawHeartLose ; loop until Y = 47 then set up the
; pattern for last part of nucleaus PF
FinHeartLose
lda #$80 ; load the accumulator with color number
sta COLUPF ; store in the playfield color memory
lda #%00000000 ; solid pattern for PF
sta PF0 ; set the PF0 register
sta PF1 ; set the PF1 register
sta PF2 ; set the PF2 register
dey ; decrement the Y register
cpy #23 ; On scanline 23?
sta WSYNC ; wait for scanline to finish
bne FinHeartLose ; loop until Y = 23 then set up the
; DNA PF
jmp DrawDNAPrep ; get ready draw DNA helix
DrawNuc8 ; this draws the start screen with
; the name of the game on boot up
lda #$80 ; load the accumulator with color number
sta COLUPF ; store in the playfield color memory
lda #%00000000 ; solid pattern for PF
sta PF0 ; set the PF0 register
sta PF1 ; set the PF1 register
sta PF2 ; set the PF2 register
dey ; decrement the Y register
cpy #70 ; Have 10 DNA scanlines passed?
sta WSYNC ; wait for scanline to finish
bne DrawNuc8 ; loop until Y = 70 then set up the
; pattern for heart PF
;lda #$0E ; load the accumulator with color number
;sta COLUPF ; store in the playfield color memory
DrawGeneName ; draw "Gene" on playfield
lda GeneColor,x ; load the colors for the name
sta COLUPF ; write the colors to the register
lda Gene1Gfx,x ; load gene name graphics for this line
sta PF1 ; set the PF1 register
lda Gene2Gfx,x ; load gene name graphics for this line
sta PF2 ; set the PF2 register
sleep 10 ; burn cycles for asymmetrical playfield
lda BlankGfx2,x ; load gene name graphics for this line
sta PF1 ; set the PF1 register
lda BlankGfx2,x ; load gene name graphics for this line
sta PF2 ; set the PF2 register
dex ; decrement our line counter
dey ; decrement the Y register
cpy #56 ; Have 14 DNA scanlines passed?
sta WSYNC ; wait for scanline to finish
bne DrawGeneName ; loop until Y = 56 then set up the
; pattern for last part of nucleaus PF
DrawNameGap ; this draws some blank PF lines between
lda #$80 ; load the accumulator with color number
sta COLUPF ; store in the playfield color memory
lda #%00000000 ; solid pattern for PF
sta PF0 ; set the PF0 register
sta PF1 ; set the PF1 register
sta PF2 ; set the PF2 register
dey ; decrement the Y register
cpy #52 ; On scanline 52?
sta WSYNC ; wait for scanline to finish
bne DrawNameGap ; loop until Y = 52 then draw the
; jockey name part of playfield
ldx (#JHEIGHT-1) ; load X register with jockey line count
DrawMedicName ; draw "Medic" on playfield
lda MedicColor,x ; load the colors for the name
sta COLUPF ; write the colors to the register
lda Medic1Gfx,x ; load medic name graphics for this line
sta PF1 ; set the PF1 register
lda Medic2Gfx,x ; load medic name graphics for this line
sta PF2 ; set the PF2 register
sleep 11 ; burn cycles for asymmetrical playfield
lda BlankGfx2,x ; load gene name graphics for this line
sta PF1 ; set the PF1 register
lda BlankGfx2,x ; load gene name graphics for this line
sta PF2 ; set the PF2 register
dex
dey ; decrement the Y register
cpy #38 ; On scanline 38?
sta WSYNC ; wait for scanline to finish
bne DrawMedicName ; loop until Y = 38 then draw remaining
; blank lines of nucleus
FinDrawNames ; finish drawing the start screen nucleus
lda #$80 ; load the accumulator with color number
sta COLUPF ; store in the playfield color memory
lda #%00000000 ; solid pattern for PF
sta PF0 ; set the PF0 register
sta PF1 ; set the PF1 register
sta PF2 ; set the PF2 register
dey ; decrement the Y register
cpy #23 ; On scanline 23?
sta WSYNC ; wait for scanline to finish
bne FinDrawNames ; loop until Y = 23 then draw the
; DNA helix part of playfield
DrawDNAPrep ; Time to get ready to draw the DNA helix
ldx (#DNAHEIGHT-1) ; set up the DNA line counter
dey ; decrement the scanline count
sta WSYNC ; burn scanline 24 to save a few cycles
; for drawing the helices
lda #$1E ; load the accumulator with color number
sta COLUPF ; store in the playfield color memory
DrawDNA
lda ScreenFlag ; flag for which screen to draw
beq DrawHelixB ; if flag = 0 branch to draw helix B
DrawHelixA ; draw helix option A
lda HelixA0,x ; Load helix graphics for this line
sta PF0 ; Draw helix A
lda HelixA1,x ; Load helix graphics for this line
sta PF1 ; Draw helix A
lda HelixA2,x ; Load helix graphics for this line
sta PF2 ; Draw helix A
jmp HelixDone ; done loading helix graphics
DrawHelixB ; draw helix option B
lda HelixB0,x ; Load helix graphics for this line
sta PF0 ; Draw helix B
lda HelixB1,x ; Load helix graphics for this line
sta PF1 ; Draw helix B
lda HelixB2,x ; Load helix graphics for this line
sta PF2 ; Draw helix B
HelixDone ; done loading helix graphics
dex ; decrement our line counter
dey ; decrement the Y register
cpy #13 ; Have 11 DNA scanlines passed?
sta WSYNC ; wait for scanline to finish
bne DrawDNA ; loop until Y = 11 then set up the
; pattern for bottom strand of DNA PF
lda #%11111111 ; solid pattern for rest of PF
sta PF0 ; set the PF0 register
sta PF1 ; set the PF1 register
sta PF2 ; set the PF2 register
lda #$80 ; load the accumulator with color number
sta COLUPF ; store in the playfield color memory
DrawBot ; draw the very bottom part of screen
; until timer runs out
sta WSYNC ; wait for the scanline to finish
lda INTIM ; check timer count
bne DrawBot ; loop if timer not 0
lda #2 ; load accumulator with 2 binary 0010
sta VBLANK ; turn off screen for overscan
; ****************************************************************
; The final part of a 2600 program is the overscan that waits a
; final 30 scanlines to finish one complete frame on TV screen
; We will use this time to check for joystick movement. Note
; that this game is side to side only. No up or down movement.
; SWCHA data bits D7 to D4 = 1 for P0 right, left, down, up
; D3 to D0 are used for P1. Also checking for game reset switch
; and firebutton for starting a game. Also doing win/lose sound
; here.
; ****************************************************************
lda #35 ; load the X register with 35 ticks
sta TIM64T ; initialize the timer
GameResetCheck ; has the reset switch been pressed?
; if so branch up to Reset to clear
; memory and reset the whole game
lda SWCHB ; load accumulator with state of reset
; switch
and #%00000001 ; reset game? and returns a 1 if so
bne SoundUpdate ; move on if no reset switch
jmp Reset ; if reset switch pushed then reboot game
SoundUpdate
jsr SFX_UPDATE ; update sound effects
MoveLeft
lda #%01000000 ; D6 = 1 for P0 stick right
bit SWCHA ; bit compare to SWCHA register
bne MoveRight ; check move right if no left joystick
ldx P0X ; load X register with P0 X position
cpx #XMIN ; is P0 at left edge of screen?
beq MoveRight ; if so left move check is done
dec P0X ; otherwise decrease the P0 X position
lda #%00001000 ; a 1 in D3 of REFP0 says mirror
sta REFP0 ; reflection of P0 in mirror image
MoveRight
lda #%10000000 ; D7 = 1 for P0 stick right
bit SWCHA ; bit compare to SWCHA register
bne StickUp ; moving to StickUp if no right joystick
ldx P0X ; load X register with P0 X position
cpx #XMAX ; is P0 at right edge of screen?
beq AtEdge ; if so move to AtEdge to set flag
; and set P0X = XMIN
inc P0X ; otherwise increase the P0 X position
lda #%00000000 ; a 0 in D3 of REFP0 says no mirror
sta REFP0 ; no reflection of P0
jmp StickUp ; bypass the new level flag for stick up
AtEdge ; this gets executed if end of PF reached
lda #1 ; load accumulator with a 1
sta NewLevelFlag ; set flag = 1 to indicate new level
; needs to be drawn. Gets checked in
; vertical blank above.
lda #XMIN ; load accumulator with X min value
sta P0X ; set the P0 horizontal location to XMIN
StickUp
lda #0 ; load the accumulator with a 0
sta UpFlag ; zero out the stick up flag
lda #%00010000 ; D4 = 1 for P0 stick up
bit SWCHA ; bit compare to SWCHA register
bne StickDown ; moving to StickDown if no up joystick
lda #1 ; load the accumulator with a 1
sta UpFlag ; indicate with flag that stick up
StickDown
lda #0 ; load the accumulator with a 0
sta DownFlag ; zero out the stick down flag
lda #%00100000 ; D3 = 1 for P0 stick down
bit SWCHA ; bit compare to SWCHA register
bne MoveDone ; moving done if no down joystick
lda #1 ; load the accumulator with a 1
sta DownFlag ; indicate with flag that stick down
MoveDone ; done checking joysticks for movement
lda EndSoundFlag ; only play end sounds once if Flag=0
bne StartButton ; skip end game sounds if Flag=1
WinSound ; Game over? Play win sound effects
lda WinFlag ; load accumulator with win flag
beq LoseSound ; branch if win flag not set
ldy #sfxWIN1 ; sound effect 1 for win
jsr SFX_TRIGGER ; execute the sound
ldy #sfxWIN2 ; sound effect 2 for win
jsr SFX_TRIGGER ; execute the sound
ldy #sfxWIN3 ; sound effect 3 for win
jsr SFX_TRIGGER ; execute the sound
lda #1 ; load accumulator with a 1
sta EndSoundFlag ; set Flag=1 so sound done
jmp StartButton ; skip the lose sound check
LoseSound ; Game over? Play loss sound effects
lda LoseFlag ; load accumulator with lose flag
beq StartButton ; branch if lose flag not set
ldy #sfxLOSE1 ; sound effect 1 for loss
jsr SFX_TRIGGER ; execute the sound
ldy #sfxLOSE2 ; sound effect 2 for loss
jsr SFX_TRIGGER ; execute the sound
ldy #sfxLOSE3 ; sound effect 3 for loss
jsr SFX_TRIGGER ; execute the sound
lda #1 ; load accumulator with a 1
sta EndSoundFlag ; set Flag=1 so sound done
StartButton ; check if button pressed on startup screen
lda GameLaunchFlag ; load the game launch flag
beq OverScan ; branch to OverScan when flag = 0
lda INPT4 ; load accumulator with button value
bmi OverScan ; If INPT4 D7 = 1 then continue
jmp StartHere ; If INPT4 D7 = 0 then button pressed
; jumpt to Start Here to start new game
OverScan ; The overscan loop
lda INTIM ; Load the timer into the accumulator
bne OverScan ; Loop if timer not yet zero
jsr GetRandomPU ; ping the random number generators once
jsr GetRandomMut ; per frame to make the sequence less
; predictable when needed by the player
; crossing a mutation or new screen.
sta WSYNC ; wait for scanline to finish
jmp VerticalSYNC ; start the entire process over!
; ****************************************************************
; This is the 8-bit random number generator subroutine that uses
; the Galois linear feedback shift register (LFSR) approach.
; This generates and returns a number between 0-255.
; Have two of these to avoid dependencies between powerups and
; mutations.
; ****************************************************************
GetRandomPU ; this is for powerups
lda RandomPU ; load the accumulator with a seed
lsr ; logical shift right
bcc SkipEor1 ; branch on carry clear
eor #$D4 ; exclusive OR (i.e. XOR) function
SkipEor1
sta RandomPU ; store the random number in variable
rts ; return from subroutine
GetRandomMut ; this is for mutation flag
lda RandomMut ; load the accumulator with a seed
lsr ; logical shift right
bcc SkipEor2 ; branch on carry clear
eor #$A9 ; exclusive OR (i.e. XOR) function
; $A9 generates inverse sequence
SkipEor2
sta RandomMut ; store the random number in variable
rts ; return from subroutine
; ****************************************************************
; The graphics for player 0
; ****************************************************************
P0Graphic1 ; 55 scanlines total
; extra scanlines help position P0 without
; keeping track of scanlines. This buys some cycles
; during the player drawing routine.
; This is P0 without the tricorder
.byte %00000000 ; | |
.byte %10000000 ; |X |
.byte %10000000 ; |X |
.byte %11000011 ; |XX XX|
.byte %01100010 ; | XX X |
.byte %01100010 ; | XX X |
.byte %00110110 ; | XX XX |
.byte %00111110 ; | XXXXX |
.byte %00011100 ; | XXX |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %00111100 ; | XXXX |
.byte %00111110 ; | XXXXX |
.byte %00111010 ; | XXX X |
.byte %00111000 ; | XXX |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %00010000 ; | X |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %11111111 ; | | <- invisible blue line for missile here
.byte %00000000 ; | | for collision detection
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | | 42nd scanline here
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | | 55th scanline
P0Graphic2 ; 55 scanlines total
; This is P0 with a tricorder
; tricorder has health screen
.byte %00000000 ; | |
.byte %10000000 ; |X |
.byte %10000000 ; |X |
.byte %11000011 ; |XX XX|
.byte %01100010 ; | XX X |
.byte %01100010 ; | XX X |
.byte %00110110 ; | XX XX |
.byte %00111110 ; | XXXXX |
.byte %00011100 ; | XXX |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %00111100 ; | XXXX |
.byte %00111110 ; | XXXXX |
.byte %00111010 ; | XXX X |
.byte %00111000 ; | XXX |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %00010000 ; | X |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %11111111 ; | | <- invisible blue line here
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %01111110 ; | XXXXXX | tricorder
.byte %11111111 ; |XXXXXXXX| health screen
.byte %10000001 ; |X X|
.byte %10000001 ; |X X|
.byte %10011001 ; |X XX X|
.byte %10011001 ; |X XX X|
.byte %10011001 ; |X XX X|
.byte %10111101 ; |X XXXX X|
.byte %10111101 ; |X XXXX X|
.byte %10011001 ; |X XX X|
.byte %10011001 ; |X XX X|
.byte %10011001 ; |X XX X|
.byte %10000001 ; |X X|
.byte %11111111 ; |XXXXXXXX|
.byte %11000011 ; |XX XX|
.byte %01111110 ; | XXXXXX | 42nd scanline here
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
P0Graphic3 ; 55 scanlines total
; This is P0 with a tricorder
; tricorder has hurt screen
.byte %00000000 ; | | Doctor
.byte %10000000 ; |X |
.byte %10000000 ; |X |
.byte %11000011 ; |XX XX|
.byte %01100010 ; | XX X |
.byte %01100010 ; | XX X |
.byte %00110110 ; | XX XX |
.byte %00111110 ; | XXXXX |
.byte %00011100 ; | XXX |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %00111100 ; | XXXX |
.byte %00111110 ; | XXXXX |
.byte %00111010 ; | XXX X |
.byte %00111000 ; | XXX |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %00010000 ; | X |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %11111111 ; | | <- invisible blue line here
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | | 25th scanline here
.byte %00000000 ; | |
.byte %01111110 ; | XXXXXX | tricorder
.byte %11111111 ; |XXXXXXXX| hurt screen
.byte %10000001 ; |X X| exclamation point
.byte %10011001 ; |X XX X|
.byte %10000001 ; |X X|
.byte %10011001 ; |X XX X|
.byte %10011001 ; |X XX X|
.byte %10011001 ; |X XX X|
.byte %10011001 ; |X XX X|
.byte %10011001 ; |X XX X|
.byte %10011001 ; |X XX X|
.byte %10011001 ; |X XX X|
.byte %10000001 ; |X X|
.byte %11111111 ; |XXXXXXXX|
.byte %11000011 ; |XX XX|
.byte %01111110 ; | XXXXXX | 42nd scanline here
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
P0Graphic4 ; 55 scanlines total
; This is P0 with a tricorder
; tricorder has neutral screen
.byte %00000000 ; | |
.byte %10000000 ; |X |
.byte %10000000 ; |X |
.byte %11000011 ; |XX XX|
.byte %01100010 ; | XX X |
.byte %01100010 ; | XX X |
.byte %00110110 ; | XX XX |
.byte %00111110 ; | XXXXX |
.byte %00011100 ; | XXX |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %00111100 ; | XXXX |
.byte %00111110 ; | XXXXX |
.byte %00111010 ; | XXX X |
.byte %00111000 ; | XXX |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %00010000 ; | X |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %11111111 ; | | <- invisible blue line here
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %01111110 ; | XXXXXX | tricorder
.byte %11111111 ; |XXXXXXXX| neutral screen
.byte %10000001 ; |X X| hurt or heal?
.byte %10000001 ; |X X|
.byte %10000001 ; |X X|
.byte %10000001 ; |X X|
.byte %10000001 ; |X X|
.byte %10111101 ; |X XXXX X|
.byte %10111101 ; |X XXXX X|
.byte %10000001 ; |X X|
.byte %10000001 ; |X X|
.byte %10000001 ; |X X|
.byte %10000001 ; |X X|
.byte %11111111 ; |XXXXXXXX|
.byte %11000011 ; |XX XX|
.byte %01111110 ; | XXXXXX | 42nd scanline here
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
P0Graphic5 ; 55 scanlines total
; This is P0 with a tricorder
; tricorder has context-dependent screen
.byte %00000000 ; | |
.byte %10000000 ; |X |
.byte %10000000 ; |X |
.byte %11000011 ; |XX XX|
.byte %01100010 ; | XX X |
.byte %01100010 ; | XX X |
.byte %00110110 ; | XX XX |
.byte %00111110 ; | XXXXX |
.byte %00011100 ; | XXX |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %00111100 ; | XXXX |
.byte %00111110 ; | XXXXX |
.byte %00111010 ; | XXX X |
.byte %00111000 ; | XXX |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %00010000 ; | X |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %00011000 ; | XX |
.byte %11111111 ; | | <- invisible blue line here
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %01111110 ; | XXXXXX | tricorder
.byte %11111111 ; |XXXXXXXX| context-dependent screen
.byte %10000001 ; |X X| hurt or heal?
.byte %10000001 ; |X X|
.byte %10000001 ; |X X|
.byte %10100101 ; |X X X X|
.byte %10000001 ; |X X|
.byte %10000001 ; |X X|
.byte %10000001 ; |X X|
.byte %10100101 ; |X X X X|
.byte %10000001 ; |X X|
.byte %10000001 ; |X X|
.byte %10000001 ; |X X|
.byte %11111111 ; |XXXXXXXX|
.byte %11000011 ; |XX XX|
.byte %01111110 ; | XXXXXX | 42nd scanline here
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
P0Color ; one color for each of the 55 P0 scanlines
.byte $00
.byte $02
.byte $04
.byte $04
.byte $04
.byte $04
.byte $04
.byte $04
.byte $02
.byte $08
.byte $08
.byte $0A
.byte $0A
.byte $0C
.byte $0C
.byte $0E
.byte $0E
.byte $02
.byte $04
.byte $04
.byte $02
.byte $80 ; <- this color matches background
.byte $00 ; that is used as a marker of the
.byte $00 ; nucleotide to edit - this makes
.byte $00 ; missile 0 invisible but open to
.byte $00 ; collision detection
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A ; 42nd scanline
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
.byte $0A
; ****************************************************************
; The 8 colors for the patient and dollar score playfields
; ****************************************************************
PScoreColor
.byte $06
.byte $F0
.byte $F2
.byte $F4
.byte $F6
.byte $F8
.byte $FA
.byte $FC
.byte $FE
.byte $30
DScoreColor
.byte $00
.byte $F0
.byte $F2
.byte $F4
.byte $F6
.byte $F8
.byte $FA
.byte $FC
.byte $FE
.byte $06
; ****************************************************************
; The graphics for the score "P" and "$" - 14 scanlines each
; ****************************************************************
PatientGfx
.byte #%00111111 ; |XX |
.byte #%00111111 ; |XX |
.byte #%00111111 ; |XX |
.byte #%00111111 ; |XX |
.byte #%00111111 ; |XX |
.byte #%00111111 ; |XX |
.byte #%00111111 ; |XX |
.byte #%00000000 ; |XXXXXXXX|
.byte #%00000000 ; |XXXXXXXX|
.byte #%00111100 ; |XX XX|
.byte #%00111100 ; |XX XX|
.byte #%00111100 ; |XX XX|
.byte #%00000000 ; |XXXXXXXX|
.byte #%00000000 ; |XXXXXXXX|
DollarGfx
.byte #%11101111 ; | X |
.byte #%00000000 ; |XXXXXXXX|
.byte #%00000000 ; |XXXXXXXX|
.byte #%11111100 ; | XX|
.byte #%11111100 ; | XX|
.byte #%11111100 ; | XX|
.byte #%00000000 ; |XXXXXXXX|
.byte #%00000000 ; |XXXXXXXX|
.byte #%00111111 ; |XX |
.byte #%00111111 ; |XX |
.byte #%00111111 ; |XX |
.byte #%00000000 ; |XXXXXXXX|
.byte #%00000000 ; |XXXXXXXX|
.byte #%11101111 ; | X |
; ****************************************************************
; Blank graphics for use in the playfield - 14 scanlines
; ****************************************************************
BlankGfx1
.byte #%11111111 ; | |
.byte #%11111111 ; | |
.byte #%11111111 ; | |
.byte #%11111111 ; | |
.byte #%11111111 ; | |
.byte #%11111111 ; | |
.byte #%11111111 ; | |
.byte #%11111111 ; | |
.byte #%11111111 ; | |
.byte #%11111111 ; | |
.byte #%11111111 ; | |
.byte #%11111111 ; | |
.byte #%11111111 ; | |
.byte #%11111111 ; | |
BlankGfx2
.byte #%00000000 ; | |
.byte #%00000000 ; | |
.byte #%00000000 ; | |
.byte #%00000000 ; | |
.byte #%00000000 ; | |
.byte #%00000000 ; | |
.byte #%00000000 ; | |
.byte #%00000000 ; | |
.byte #%00000000 ; | |
.byte #%00000000 ; | |
.byte #%00000000 ; | |
.byte #%00000000 ; | |
.byte #%00000000 ; | |
.byte #%00000000 ; | |
; ****************************************************************
; The graphics for the "gene medic" name part of start screen
; ****************************************************************
Gene1Gfx ; graphic for "GE" part of "GENE"
.byte #%11101110 ; |XXX XXX |
.byte #%11101110 ; |XXX XXX |
.byte #%10101000 ; |X X X |
.byte #%10101000 ; |X X X |
.byte #%10101000 ; |X X X |
.byte #%10101000 ; |X X X |
.byte #%11101110 ; |XXX XXX |
.byte #%11101110 ; |XXX XXX |
.byte #%10001000 ; |X X |
.byte #%10001000 ; |X X |
.byte #%10001000 ; |X X |
.byte #%10001000 ; |X X |
.byte #%11101110 ; |XXX XXX |
.byte #%11101110 ; |XXX XXX |
Gene2Gfx ; graphic for "NE" part of "GENE"
.byte #%01110101 ; | XXX X X|
.byte #%01110101 ; | XXX X X|
.byte #%00010101 ; | X X X|
.byte #%00010101 ; | X X X|
.byte #%00010101 ; | X X X|
.byte #%00010101 ; | X X X|
.byte #%01110101 ; | XXX X X|
.byte #%01110101 ; | XXX X X|
.byte #%00010101 ; | X X X|
.byte #%00010101 ; | X X X|
.byte #%00010101 ; | X X X|
.byte #%00010101 ; | X X X|
.byte #%01110111 ; | XXX XXX|
.byte #%01110111 ; | XXX XXX|
Medic1Gfx ; graphic for "ME" part of "MEDIC"
.byte #%10101110 ; |XX XXX X|
.byte #%10101110 ; |XX XXX X|
.byte #%10101000 ; |XX X X X|
.byte #%10101000 ; |XX X X X|
.byte #%10101000 ; | X X X X|
.byte #%10101000 ; | X X X X|
.byte #%10101110 ; | X X X X|
.byte #%10101110 ; | X X X X|
.byte #%10101000 ; | X X X X|
.byte #%10101000 ; | X X X X|
.byte #%11101000 ; | X X X X|
.byte #%11101000 ; | X X X X|
.byte #%10101110 ; | X XXX X|
.byte #%10101110 ; | X XXX X|
Medic2Gfx ; graphic for "DIC" part of "MEDIC"
.byte #%11010011 ; | X X XX|
.byte #%11010111 ; | X X XX|
.byte #%01010101 ; | X X X |
.byte #%01010101 ; | XX X X |
.byte #%01010101 ; | XX X X |
.byte #%01010101 ; | XXX |
.byte #%01010101 ; | XX |
.byte #%01010101 ; | XX |
.byte #%01010101 ; | XXX |
.byte #%01010101 ; | XX X |
.byte #%01010101 ; | XX X |
.byte #%01010101 ; | X X |
.byte #%11010111 ; | X X XX|
.byte #%11010011 ; | X X XX|
GeneColor ; one color for each of the 14 scanlines
; for each of the start screen game names
; The colors for Gene in Gene Medic
.byte $02
.byte $02
.byte $04
.byte $04
.byte $06
.byte $06
.byte $08
.byte $08
.byte $0A
.byte $0A
.byte $0C
.byte $0C
.byte $0E
.byte $0E
MedicColor ; one color for each of the 14 scanlines
; for each of the start screen game names
; The colors for Medic in Gene Medic
.byte $12
.byte $12
.byte $14
.byte $14
.byte $16
.byte $16
.byte $18
.byte $18
.byte $1A
.byte $1A
.byte $1C
.byte $1C
.byte $1E
.byte $1E
; ****************************************************************
; The graphics for the mitochondria and endoplasmic reticulum
; cytoplasmic elements
; ****************************************************************
MitoGfx ; graphic for the mitochodria
.byte #%01111110 ; | XXXXXX |
.byte #%11111111 ; |XXXXXXXX|
.byte #%10100001 ; |X X X|
.byte #%10100001 ; |X X X|
.byte #%10100001 ; |X X X|
.byte #%10100001 ; |X X X|
.byte #%10100101 ; |X X X X|
.byte #%10100101 ; |X X X X|
.byte #%10100101 ; |X X X X|
.byte #%10000101 ; |X X X|
.byte #%10000101 ; |X X X|
.byte #%10000101 ; |X X X|
.byte #%11111111 ; |XXXXXXXX|
.byte #%01111110 ; | XXXXXX |
ERGfx ; graphic for the endoplasmic reticulum
.byte #%00000000 ; | |
.byte #%00011000 ; | XX |
.byte #%11111100 ; |XXXXXX |
.byte #%11111100 ; |XXXXXX |
.byte #%00011000 ; | XX |
.byte #%00111111 ; | XXXXXX|
.byte #%00111111 ; | XXXXXX|
.byte #%00011000 ; | XX |
.byte #%01111100 ; | XXXXX |
.byte #%01111100 ; | XXXXX |
.byte #%00110000 ; | XX |
.byte #%11111110 ; |XXXXXXX |
.byte #%11111110 ; |XXXXXXX |
.byte #%00110000 ; | XX |
; ****************************************************************
; The playfield graphics for the DNA double helix A
; Used on alternating screens to simulate movement through cell
; ****************************************************************
HelixA0 ; PF0 - only first four bits are read
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %10000000 ; |X |
.byte %01000000 ; | X |
.byte %00100000 ; | X |
.byte %00010000 ; | X |
.byte %00100000 ; | X |
.byte %01000000 ; | X |
.byte %10000000 ; |X |
.byte %00000000 ; | |
HelixA1 ; PF1
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %11000001 ; |XX X|
.byte %00100010 ; | X X |
.byte %00010100 ; | X X |
.byte %00001000 ; | X |
.byte %00010100 ; | X X |
.byte %00100010 ; | X X |
.byte %11000001 ; |XX X|
.byte %00000000 ; | |
HelixA2 ; PF2
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %10000011 ; |X XX|
.byte %01000100 ; | X X |
.byte %00101000 ; | X X |
.byte %00010000 ; | X |
.byte %00101000 ; | X X |
.byte %01000100 ; | X X |
.byte %10000011 ; |X XX|
.byte %00000000 ; | |
; ****************************************************************
; The playfield graphics for the DNA double helix B
; Used on alternating screens to simulate movement through cell
; ****************************************************************
HelixB0 ; PF0 - only first four bits are read
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %10000000 ; |X |
.byte %01000000 ; | X |
.byte %00100000 ; | X |
.byte %00010000 ; | X |
.byte %00100000 ; | X |
.byte %01000000 ; | X |
.byte %10000000 ; |X |
.byte %00000000 ; | |
HelixB1 ; PF1
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %10000011 ; |X XX|
.byte %01000100 ; | X X |
.byte %00101000 ; | X X |
.byte %00010000 ; | X |
.byte %00101000 ; | X X |
.byte %01000100 ; | X X |
.byte %10000011 ; |X XX|
.byte %00000000 ; | |
HelixB2 ; PF2
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %00000000 ; | |
.byte %11000001 ; |XX X|
.byte %00100010 ; | X X |
.byte %00010100 ; | X X |
.byte %00001000 ; | X |
.byte %00010100 ; | X X |
.byte %00100010 ; | X X |
.byte %11000001 ; |XX X|
.byte %00000000 ; | |
; ****************************************************************
; The graphics for the heart used to indicate win-loss at end
; ****************************************************************
HeartGfx ; graphics for the final heart
; only half is needed because it is
; refelected in symmatrical playfield
; need 36 scanlines to draw
.byte #%10000000 ; |X |
.byte #%10000000 ; |X |
.byte #%10000000 ; |X |
.byte #%11000000 ; |XX |
.byte #%11000000 ; |XX |
.byte #%11000000 ; |XX |
.byte #%11100000 ; |XXX |
.byte #%11100000 ; |XXX |
.byte #%11100000 ; |XXX |
.byte #%11110000 ; |XXXX | 10 scanline
.byte #%11110000 ; |XXXX |
.byte #%11110000 ; |XXXX |
.byte #%11111000 ; |XXXXX |
.byte #%11111000 ; |XXXXX |
.byte #%11111000 ; |XXXXX |
.byte #%11111100 ; |XXXXXX |
.byte #%11111100 ; |XXXXXX |
.byte #%11111100 ; |XXXXXX |
.byte #%11111110 ; |XXXXXXX |
.byte #%11111110 ; |XXXXXXX | 20 scanline
.byte #%11111110 ; |XXXXXXX |
.byte #%11111110 ; |XXXXXXX |
.byte #%11111110 ; |XXXXXXX |
.byte #%11111110 ; |XXXXXXX |
.byte #%11111110 ; |XXXXXXX |
.byte #%11111110 ; |XXXXXXX |
.byte #%11111100 ; |XXXXXX |
.byte #%11111100 ; |XXXXXX |
.byte #%11111100 ; |XXXXXX |
.byte #%11111100 ; |XXXXXX | 30 scanline
.byte #%11111000 ; |XXXXX |
.byte #%11111000 ; |XXXXX |
.byte #%01110000 ; | XXX |
.byte #%01110000 ; | XXX |
.byte #%00110000 ; | XX |
.byte #%00110000 ; | XX | 36 scanline
; ****************************************************************
; The graphics for the money bag and research powerups
; ****************************************************************
MoneyBag ; Money Bag powerup graphic
; 16 scanlines
.byte %00000000 ; | |
.byte %00111110 ; | XXXXX |
.byte %01110111 ; | XXX XXX|
.byte %01110111 ; | XXX XXX|
.byte %01100011 ; | XX XX|
.byte %01111011 ; | XXXX XX|
.byte %01100011 ; | XX XX|
.byte %01101111 ; | XX XXXX|
.byte %01100011 ; | XX XX|
.byte %00110110 ; | XX XX |
.byte %00110110 ; | XX XX |
.byte %00011100 ; | XXX |
.byte %00001000 ; | X |
.byte %00011100 ; | XXX |
.byte %00110110 ; | XX XX |
.byte %00000000 ; | |
Research ; Research powerup graphic
; 16 scanlines
.byte %00000000 ; | |
.byte %11111111 ; |XXXXXXXX|
.byte %10000001 ; |X X|
.byte %10111101 ; |X XXXX X|
.byte %10000001 ; |X X|
.byte %10111101 ; |X XXXX X|
.byte %10000001 ; |X X|
.byte %10111101 ; |X XXXX X|
.byte %10000001 ; |X X|
.byte %10111101 ; |X XXXX X|
.byte %10000001 ; |X X|
.byte %10001101 ; |X XX X|
.byte %10000001 ; |X X|
.byte %10100001 ; |X X X|
.byte %11111111 ; |XXXXXXXX|
.byte %00000000 ; | |
EHR ; EHR computer powerup graphic
; 16 scanlines
.byte %00000000 ; | |
.byte %11111111 ; |XXXXXXXX|
.byte %00000000 ; | |
.byte %11111111 ; |XXXXXXXX|
.byte %10000001 ; |X X|
.byte %10111101 ; |X XXXX X|
.byte %10111101 ; |X XXXX X|
.byte %10111101 ; |X XXXX X|
.byte %10111101 ; |X XXXX X|
.byte %10000001 ; |X X|
.byte %10111001 ; |X XXX X|
.byte %10111001 ; |X XXX X|
.byte %10111001 ; |X XXX X|
.byte %10000001 ; |X X|
.byte %11111111 ; |XXXXXXXX|
.byte %00000000 ; | |
; ****************************************************************
; The sound effect subroutines and data called from the main loop.
; These came directly from Darrell Spice's tutorial with some
; modifications. See sfx.asm file for more detailed comments.
; See https://is.gd/YDAPkE for information and credit. Thanks!
; ****************************************************************
SFX_F ; frequencies
.byte 0, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0 ; harmful edit
.byte 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3 ; helpful edit
.byte 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 ; ping
.byte 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 ; win1
.byte 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 ; win2
.byte 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9 ; win3
.byte 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 ; lose1
.byte 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25 ; lose2
.byte 0, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 ; lose3
SFX_CV ; channel and volume
.byte 0,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f ; harmful edit
sfxHARM = *-SFX_CV-1
.byte 0,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f,$6f ; helpful edit
sfxHELP = *-SFX_CV-1
.byte 0,$41,$42,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c ; ping
sfxPING = *-SFX_CV-1
.byte 0,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf ; win1
sfxWIN1 = *-SFX_CV-1
.byte 0,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf ; win2
sfxWIN2 = *-SFX_CV-1
.byte 0,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf ; win3
sfxWIN3 = *-SFX_CV-1
.byte 0,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf ; lose1
sfxLOSE1 = *-SFX_CV-1
.byte 0,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf ; lose2
sfxLOSE2 = *-SFX_CV-1
.byte 0,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf ; lose3
sfxLOSE3 = *-SFX_CV-1
SFX_OFF
ldx #0 ; silence sound output
stx SFX_LEFT ; reset to 0
stx SFX_RIGHT ; reset to 0
stx AUDV0 ; reset volume to 0
stx AUDV1 ; reset volume to 1
stx AUDC0 ; reset channel to 0
stx AUDC1 ; reset channel to 1
rts ; return
SFX_TRIGGER
ldx SFX_LEFT ; test left channel
lda SFX_CV,x ; CV value will be 0 if channel is idle
bne .leftnotfree ; if not 0 then skip ahead
sty SFX_LEFT ; channel is idle, use it
rts ; all done
.leftnotfree
ldx SFX_RIGHT ; test right channel
lda SFX_CV,x ; CV value will be 0 if channel is idle
bne .rightnotfree ; if not 0 then skip ahead
sty SFX_RIGHT ; channel is idle, use it
rts ; all done
.rightnotfree
cpy SFX_LEFT ; test sfx priority with left channel
bcc .leftnotlower ; skip ahead if new sfx has lower priority than active sfx
sty SFX_LEFT ; new sfx has higher priority so use left channel
rts ; all done
.leftnotlower
cpy SFX_RIGHT ; test sfx with right channel
bcc .rightnotlower ; skip ahead if new sfx has lower priority than active sfx
sty SFX_RIGHT ; new sfx has higher priority so use right channel
.rightnotlower
rts
SFX_UPDATE
ldx SFX_LEFT ; get the pointer for the left channel
lda SFX_F,x ; get the Frequency value
sta AUDF0 ; update the Frequency register
lda SFX_CV,x ; get the combined Control and Volume value
sta AUDV0 ; update the Volume register
lsr ; prep the Control value,
lsr ; it's stored in the upper nybble
lsr ; but must be in the lower nybble
lsr ; when Control is updated
sta AUDC0 ; update the Control register
beq .skipleftdec ; skip ahead if Control = 0
dec SFX_LEFT ; update pointer for left channel
.skipleftdec:
ldx SFX_RIGHT ; get the pointer for the right channel
lda SFX_F,x ; get the Frequency value
sta AUDF1 ; update the Frequency register
lda SFX_CV,x ; get the combined Control and Volume value
sta AUDV1 ; update the Volume register
lsr ; prep the Control value,
lsr ; it's stored in the upper nybble
lsr ; but must be in the lower nybble
lsr ; when Control is updated
sta AUDC1 ; update the Control register
beq .skiprightdec ; skip ahead if Control = 0
dec SFX_RIGHT ; update pointer for right channel
.skiprightdec
rts ; all done
; ****************************************************************
; The Interrupts are are used to help rescue the 6502 if it fails
; 2/3 are not used by 6507 chip.
; ****************************************************************
ORG $FFFA ; address for the interrupt vectors
InterruptVectors
.word Reset ; NMI
.word Reset ; RESET
.word Reset ; IRQ
END