LIST OFF ; *** N I G H T D R I V E R *** ; Copyright 1980 Atari, Inc ; Programmer: Rob Fulop ; Analyzed, labeled and commented ; by Dennis Debro ; Last Update: June 14, 2004 processor 6502 ; ; NOTE: You must compile this with vcs.h version 105 or greater. ; TIA_BASE_READ_ADDRESS = $30 ; set the read address base so this runs on ; the real VCS and compiles to the exact ; ROM image include vcs.h include macro.h LIST ON ;=============================================================================== ; A S S E M B L E R - S W I T C H E S ;=============================================================================== NTSC = 0 PAL = 1 COMPILE_VERSION = NTSC ; change this to compile for different ; regions ;============================================================================ ; T I A - C O N S T A N T S ;============================================================================ HMOVE_L7 = $70 HMOVE_L6 = $60 HMOVE_L5 = $50 HMOVE_L4 = $40 HMOVE_L3 = $30 HMOVE_L2 = $20 HMOVE_L1 = $10 HMOVE_0 = $00 HMOVE_R1 = $F0 HMOVE_R2 = $E0 HMOVE_R3 = $D0 HMOVE_R4 = $C0 HMOVE_R5 = $B0 HMOVE_R6 = $A0 HMOVE_R7 = $90 HMOVE_R8 = $80 ; values for ENAMx and ENABL DISABLE_BM = %00 ENABLE_BM = %10 ; values for NUSIZx: ONE_COPY = %000 TWO_COPIES = %001 TWO_WIDE_COPIES = %010 THREE_COPIES = %011 DOUBLE_SIZE = %101 THREE_MED_COPIES = %110 QUAD_SIZE = %111 MSBL_SIZE1 = %000000 MSBL_SIZE2 = %010000 MSBL_SIZE4 = %100000 MSBL_SIZE8 = %110000 VERTICAL_DELAY = 1 ; values for REFPx: NO_REFLECT = %0000 REFLECT = %1000 ; mask for SWCHB BW_MASK = %1000 ; black and white bit SELECT_MASK = %10 RESET_MASK = %01 ;============================================================================ ; U S E R - C O N S T A N T S ;============================================================================ ROMTOP = $F000 VBLANK_TIME = $30 OVERSCAN_TIME = $1C ; color constants BLACK = $00 WHITE = $0E ; NTSC color constants YELLOW = $10 ORANGE = $20 BRICK_RED = $30 RED = $40 BLUE_PURPLE = $60 BLUE = $80 BRIGHT_GREEN = $C0 GREEN = $D0 GREEN_BROWN = $E0 BROWN = $F0 H_FONT = 5 H_OBSTACLE = 8 SELECT_DELAY = 18 INIT_PYLON_DISTANCE = 4 ; initial distance between left/right pylon XMIN = 0 XMAX = 158 START_LEFT_PYLON_XPOS = 78 START_RIGHT_PLYON_XPOS = START_LEFT_PYLON_XPOS + INIT_PYLON_DISTANCE MIN_VELOCITY = 0 MAX_VELOCITY = 6 MAX_PYLON_GROUPS = 8 MAX_GAME_SELECTION = 9 STARTING_GAME_TIME = $90 ; BCD ; track difficulty values RANDOM_TRACK = 0 NOVICE_TRACK = 1 PRO_TRACK = 2 EXPERT_TRACK = 3 ;============================================================================ ; Z P - V A R I A B L E S ;============================================================================ temp = $81 ;-------------------------------------- tempObstacleHeight = temp ; used in kernel to draw the obstacle ;-------------------------------------- pylonVertSpace = temp ; used in kernel -- space between pylons pylonHeight = $82 ; used in kernel to vary height of pylons scoreGraphic1 = $83 ;-------------------------------------- tempPylonGroupNumber = scoreGraphic1 scoreGraphic2 = $84 ;-------------------------------------- tempKernelScanline = scoreGraphic2 obstacleStatus = $86 ; status flags for obstacle scanlineHolder = $87 ; scan line number before drawing obstacle obstacleDataPointer = $88 ; $88 - $89 trackSettingDataPointer = $8A ; $8A - $8B trackDifficulty = $8C ; see track difficulty values carColor = $8D player0HorizPos = $90 obstacleVertSize = $91 ; used in kernel for repeating scan lines randomSeed = $93 carVelocity = $94 ; velocity of player's car initLeftPylonXPos = $95 ; top pylon XPos frameCount = $99 ; updated each frame sound0Volume = $9C selectDelayRate = $9D ; controls game selections if SELECT held fireButtonValue = $9F colorMode = $A0 ; D7 = 1 for COLOR D7 = 0 for B/W trackCheckPoint = $A2 ; score increases when checkpoint passed trackDiffTableIndex = $A3 ; used to read the track difficulty table pylonColor = $AA maximumVelocity = $AB ; velocity ceiling (controlled by right ; difficulty switch) sound1Volume = $AE gameSelection = $B0 ; game selection in BCD selectDebounce = $B1 gameState = $B2 gameTimer = $B3 playerScore = $B4 paddleValue = $B5 gameTimerMask = $B6 gameBCDValues = $B8 ; $B8 - $B9 ;-------------------------------------- playerScoreBCD = gameBCDValues gameTimerBCD = playerScoreBCD+1 playerScoreLSBOffset = $BA playerTimerLSBOffset = $BB playerScoreMSBOffset = $BC playerTimerMSBOffset = $BD leftPylonHorizPos = $BE ; $BE - $C5 rightPylonHorizPos = $C7 ; $C7 - $CE ;============================================================================ ; R O M - C O D E ;============================================================================ SEG Bank0 org ROMTOP Start ; ; Set up everything so the power up state is known. ; sei ; disable interrupts cld ; clear decimal mode ldx #$FF txs ; point stack to the beginning inx ; x = 0 txa .clearLoop sta VSYNC,x inx bne .clearLoop jsr SystemPowerup MainLoop VerticalSync lda #%00000010 sta WSYNC ; wait for next scan line sta VBLANK ; disable TIA (D1 = 1) sta WSYNC ; wait 3 scan lines before starting new sta WSYNC ; frame sta WSYNC sta VSYNC ; start vertical sync (D1 = 1) sta WSYNC ; first line of VSYNC sta WSYNC ; second line of VSYNC lda #VBLANK_TIME sta WSYNC ; third line of VSYNC sta VSYNC ; end vertical sync (D1 = 0) sta TIM64T ; set timer for vertical blanking period inc $96 bne .incrementFrameCount inc $9E bne .incrementFrameCount stx gameState .incrementFrameCount inc frameCount ; increment frame count each new frame lda $AF sec sbc carVelocity sta $AF lda $96 lsr bcc LF045 jmp ReadConsoleSwitches LF045: ldx #0 lda $A1 bmi LF04C txa LF04C: and #$07 tay lda LF712,y sta $AC tya asl asl asl tay lda $9B bne LF0A2 stx $A7 stx $A8 lda #1 sta $A4 LF065: jsr DeterminePylonXOffset clc adc leftPylonHorizPos,x sta leftPylonHorizPos,x sty $AD ldy $AC jsr DeterminePylonXOffset eor #$FF ; make the value negative clc adc #1 clc adc rightPylonHorizPos,x sta rightPylonHorizPos,x lda leftPylonHorizPos,x cmp #XMAX bcc LF08A lda $A7 ora $A4 sta $A7 LF08A: lda rightPylonHorizPos,x cmp #XMAX bcc LF096 lda $A8 ora $A4 sta $A8 LF096: asl $A4 inx inc $AC ldy $AD iny cpx #MAX_PYLON_GROUPS bne LF065 LF0A2: jmp LF2A4 ReadConsoleSwitches lda SWCHB ; read console switches ror ; RESET value now in carry bcs .readColorAndSelectButtons jsr InitPylonVariables sty gameState sty $A5 sty $92 iny ; y = 0 sty playerScore ; clear the player's score sty $B7 sty trackDiffTableIndex ; reset the track difficulty table index sty COLUBK ; set background color to BLACK sty carVelocity ; reset car velocity (car at rest) iny ; y = 1 sty $A1 lda gameSelection ; get current game selection tay ; move to y for later and #3 ; get the track difficulty setting sta trackDifficulty ; and set it for the current game lda #$0F ; assume this is a timed game selection cpy #5 ; determine if this is a timed game bcs .skipSettingGameTimerMask sta gameTimerMask ; set game timer mask to show timer .skipSettingGameTimerMask sta trackCheckPoint lda #STARTING_GAME_TIME sta gameTimer lda #BLUE+3 sta carColor sta obstacleStatus lda #MAX_VELOCITY ; assume EXPERT velocity setting bit SWCHB ; read the right difficulty switch bmi .setMaximumVelocity lda #MAX_VELOCITY-2 ; set to AMATUEUR velocity setting .setMaximumVelocity sta maximumVelocity .readColorAndSelectButtons ldx #0 lda SWCHB ; read the console switch value and #BW_MASK ; get the B/W switch value bne .setColorMode ; set color mode to B/W ldx #128 ; set color mode to COLOR .setColorMode stx colorMode lda SWCHB ; read the console switches and #SELECT_MASK ; mask to find SELECT value beq .selectSwitchPressed lda #0 sta selectDebounce ; show SELECT not pressed this frame sta selectDelayRate ; reset select button delay rate beq LF137 ; unconditional branch .selectSwitchPressed lda selectDebounce ; get the select debounce flag bne .delayGameSelectionIncrement ; branch if SELECT held from last frame IncrementGameSelection sed ; set to decimal mode lda gameSelection ; get current game selection clc adc #1 ; increment game selection by 1 sta gameSelection cld ; clear decimal mode cmp #MAX_GAME_SELECTION ; make sure it doesn't go over the max bne .skipGameSelectionWrap lda #1 ; wrap game selection back around to 1 sta gameSelection .skipGameSelectionWrap lda #$FF sta selectDebounce ; show SELECT button was held this frame lda gameState ; get the current game state beq LF124 jsr InitPylonVariables LF124: lda #0 sta selectDelayRate ; reset select button delay rate sta gameState jsr ResetGameVariables bne LF137 ; unconditional branch .delayGameSelectionIncrement inc selectDelayRate ; increment select delay rate lda selectDelayRate ; get select delay rate to see if cmp #SELECT_DELAY ; game selection should be incremented beq IncrementGameSelection LF137: lda $AF cmp #158 bcc LF140 jmp LF1C4 LF140: inc $92 ldx #MAX_PYLON_GROUPS-1 .movePylonsDownLoop lda leftPylonHorizPos,x ldy rightPylonHorizPos,x sta leftPylonHorizPos+1,x sty rightPylonHorizPos+1,x dex bpl .movePylonsDownLoop lda initLeftPylonXPos ; get the initial pylon's horiz position sta leftPylonHorizPos ; set top left pylon's horiz position clc adc #INIT_PYLON_DISTANCE sta rightPylonHorizPos ; set top right pylon's horiz position lda #176 sta $AF dec trackCheckPoint bpl LF1B5 bit gameState bpl SetGameTrackVariables lda trackDifficulty bne SetGameTrackVariables lda randomSeed bpl LF170 ldx #1 bne LF177 ; unconditional branch LF170: ldx #$81 ror bcc LF177 ldx #$C2 LF177: stx $A1 lda randomSeed ; get the random number and #$0F ; make sure value is 0 <= a <= 15 ora #$08 ; make sure the value is 8 <= a <= 15 sta trackCheckPoint bne IncrementScore ; unconditional branch SetGameTrackVariables lda trackDifficulty ; get the track difficulty asl ; multiply by 2 for table read tay lda TrackDifficultyDataPointers,y sta trackSettingDataPointer lda TrackDifficultyDataPointers+1,y sta trackSettingDataPointer+1 ldy trackDiffTableIndex LF193: lda (trackSettingDataPointer),y bne LF19A tay ; reset the track difficulty table index beq LF193 ; unconditional branch LF19A: sta $A1 iny ; increment index to read check point lda (trackSettingDataPointer),y sta trackCheckPoint ; set new check point value iny ; increment for next table read sty trackDiffTableIndex ; save for next table read bit gameState bpl LF1B1 IncrementScore sed lda playerScore ; get the player's score clc adc #1 ; increment player's score by 1 sta playerScore cld LF1B1: lda #0 sta $A5 LF1B5: lda $A1 bpl LF1C4 and #$07 tax lda initLeftPylonXPos clc adc LF6F5,x sta initLeftPylonXPos LF1C4: ldy obstacleStatus bpl LF1CC ldx $92 bpl LF1CF LF1CC: jmp LF26A LF1CF: tya ; move obstacle status to the accumulator and #$0F ; keep only obstacle type tay lda obstacleStatus and #%00010000 beq LF1E1 lda rightPylonHorizPos,x ; get the right pylon's horiz position clc adc #16 jmp .setObstaclesHorizPos LF1E1: lda leftPylonHorizPos,x ; get the left pylon's horiz position cpy #0 ; check to see if obstacle is a car bne .positionObstacleOffTrack ; if not then place obstacle off the track clc adc #2 jmp .setObstaclesHorizPos .positionObstacleOffTrack sec sbc #24 .setObstaclesHorizPos sta player0HorizPos txa cmp #6 bne LF23C lda initLeftPylonXPos cmp #START_LEFT_PYLON_XPOS+2 bcc LF204 lda obstacleStatus and #%11101111 jmp LF208 LF204: lda obstacleStatus ora #%00010000 LF208: sta obstacleStatus lda #172 sta $8F lda #$FF sta $92 lda randomSeed ; get the random number and #$03 ; make number 0 <= a <= 3 bne LF230 lda leftPylonHorizPos clc adc #2 sta player0HorizPos bit SWCHB bvs LF228 lda #$40 sta sound1Volume LF228: lda obstacleStatus and #%11101111 sta obstacleStatus lda #0 LF230: sta $81 lda obstacleStatus and #%11110000 ora $81 sta obstacleStatus bne LF26A LF23C: lsr sta $84 tax lda #16 sta $81 lda obstacleStatus and #%00001111 bne LF24C sta $81 LF24C: lda obstacleStatus and #%00010000 eor $81 beq LF25C lda player0HorizPos sec sbc LF75D,x sta player0HorizPos LF25C: lda player0HorizPos bpl LF273 clc adc LF75D,x bcs LF26A cmp #160 bcc LF273 LF26A: lda obstacleStatus and #%10111111 sta obstacleStatus jmp LF2A4 LF273: lda obstacleStatus ora #%01000000 sta obstacleStatus lda obstacleStatus bpl LF2A4 and #$0F ; make sure value is 15 <= a <= 0 asl ; multiply by 2 -- table has word address tax lda ObstacleDataPointers,x sta obstacleDataPointer lda ObstacleDataPointers+1,x sta obstacleDataPointer+1 ldx $84 lda ObstacleVertSizeTable,x sta obstacleVertSize lda ObstacleSizeTable,x sta NUSIZ0 ldx #0 lda player0HorizPos ; get player0's horizontal position jsr CalculateHorizPosition ; coarse move player and sta HMP0 ; calculate fine motion sta WSYNC ; wait for next scan line sta HMOVE ; horizontally more sprites LF2A4: lda playerScore sta playerScoreBCD bit gameState bpl LF2E3 bit $A5 bmi LF2D8 lda frameCount cmp #60 bne LF2D8 sed ; set to decimal mode lda gameTimer ; get the game timer sec sbc #1 ; reduce game timer by 1 sta gameTimer cld cmp #0 bne LF2D4 lda gameTimerMask beq LF2D4 jsr ResetGameVariables lda #$40 sta gameState jsr InitPylonVariables jmp DisplayKernel LF2D4: lda #0 sta frameCount LF2D8: lda gameTimerMask beq .setGameTimerBCDValue lda gameTimer ; get the game timer .setGameTimerBCDValue sta gameTimerBCD jmp CalculateDigitOffsets LF2E3: lda $96 bne LF2F7 lda $A9 and #$07 tay lda LF7ED,y sta carColor ; set the car color ora #$08 sta pylonColor ; set the pylon colors inc $A9 LF2F7: bvs LF2D8 lda gameSelection sta playerScoreBCD CalculateDigitOffsets ldx #1 .digitOffsetLoop lda gameBCDValues,x ; get the game BCD value (score or timer) and #$0F ; mask off the upper nybbles sta temp ; save the value for later asl ; shift the value left to multiply by 4 asl clc ; add in original so it's multiplied by 5 adc temp ; [i.e. x * 5 = (x * 4) + x] sta playerScoreLSBOffset,x lda gameBCDValues,x and #$F0 ; mask off the lower nybbles lsr ; divide the value by 4 lsr sta temp ; save the value for later lsr ; divide the value by 16 lsr clc ; add in original so it's multiplied by adc temp ; 5/16 [i.e. 5x/16 = (x / 16) + (x / 4)] sta playerScoreMSBOffset,x dex bpl .digitOffsetLoop DisplayKernel SUBROUTINE .waitTime ldx INTIM bne .waitTime stx WSYNC ; wait for next scan line stx HMP0 stx VBLANK ; enable TIA (D1 = 0) lda #$E5 sta TIM64T stx CTRLPF stx scoreGraphic1 ; clear score graphics for score kernel stx scoreGraphic2 inx ; x = 1 stx $A4 ldx #H_FONT+1 ScoreKernel sta WSYNC ;-------------------------------------- lda scoreGraphic1 ; 3 get the score graphic for display sta PF1 ; 3 = @06 ldy playerScoreMSBOffset ; 3 lda NumberFonts,y ; 4 read the number fonts and #$F0 ; 2 mask the lower nybble sta scoreGraphic1 ; 3 save it in the score graphic ldy playerScoreLSBOffset ; 3 lda NumberFonts,y ; 4 read the number fonts and #$0F ; 2 mask the upper nybble ora scoreGraphic1 ; 3 or with score graphic to get LSB sta scoreGraphic1 ; 3 value lda scoreGraphic2 ; 3 get the score graphic for display sta PF1 ; 3 = @39 ldy playerTimerMSBOffset ; 3 lda NumberFonts,y ; 4 read the number fonts and #$F0 ; 2 mask the lower nybble sta scoreGraphic2 ; 3 save it in the score graphic ldy playerTimerLSBOffset ; 3 lda NumberFonts,y ; 4 read the number fonts and gameTimerMask ; 3 gameTimerMask turns on/off right digits sta WSYNC ; 3 = @64 ;-------------------------------------- ora scoreGraphic2 ; 3 or with score graphic to get LSB sta scoreGraphic2 ; 3 value lda scoreGraphic1 ; 3 sta PF1 ; 3 = @12 dex ; 2 beq LF383 ; 2³ inc playerScoreLSBOffset ; 5 inc playerScoreMSBOffset ; 5 inc playerTimerLSBOffset ; 5 inc playerTimerMSBOffset ; 5 lda scoreGraphic2 ; 3 sta PF1 ; 3 jmp ScoreKernel ; 3 LF383: stx PF1 ; 3 = @20 clear PF1 register (x = 0) ldy #$B0 ; 2 lda #%00000001 ; 2 sta CTRLPF ; 3 = @27 lda $96 ; 3 lsr ; 2 bcs LF393 ; 2³ jmp PylonKernel ; 3 LF393: sta WSYNC ;-------------------------------------- LF395: sta WSYNC ;-------------------------------------- cpy $8F ; 3 beq LF3A4 ; 2³ bit INPT0 ; 3 read the paddle controller bmi LF3A1 ; 2³ check if capacitor charged inc paddleValue ; 5 LF3A1: dey ; 2 bne LF395 ; 2³ LF3A4: sty scanlineHolder ; 3 save scan line until done with ; obstacle lda #$29 ; 2 sta TIM64T ; 4 ldy #0 ; 2 DrawObstacleKernel lda obstacleVertSize ; 3 sta tempObstacleHeight ; 3 cpy #H_OBSTACLE*2 ; 2 doubled to include color data too beq .doneObstacleKernel ; 2³ sta WSYNC ;-------------------------------------- lda (obstacleDataPointer),y; 5 bit obstacleStatus ; 3 bvc .skipObstacleDraw ; 2³ sta GRP0 ; 3 = @13 draw the obstacle .skipObstacleDraw iny ; 2 lda (obstacleDataPointer),y; 5 bit colorMode ; 3 bpl .colorObstacle ; 2³ and #$0F ; 2 mask the color hue for B/W .colorObstacle sta COLUP0 ; 3 color the obstacle bit INPT0 ; 3 read the paddle controller bmi .incrementObstacleIndex; 2³ check if capacitor charged inc paddleValue ; 5 .incrementObstacleIndex iny ; 2 dec tempObstacleHeight ; 5 beq DrawObstacleKernel ; 2³ .drawObstacleKernelLoop sta WSYNC ;-------------------------------------- bit INPT0 ; 3 read the paddle controller bmi .skipPaddleValue ; 2³ check if capacitor charged inc paddleValue ; 5 .skipPaddleValue dec tempObstacleHeight ; 5 bne .drawObstacleKernelLoop; 2³ continue until height is 0 beq DrawObstacleKernel ; 3 unconditional branch .doneObstacleKernel sta WSYNC ;-------------------------------------- lda #0 ; 2 sta GRP0 ; 3 = @05 clear obstacle player register lda scanlineHolder ; 3 get the scan line number before sec ; 2 drawing obstacle sbc #H_OBSTACLE*4 ; 2 decrement by 32 tay ; 2 set to new scan line number LF3EF: sta WSYNC ;-------------------------------------- bit INPT0 ; 3 read the paddle controller bmi LF3F7 ; 2³ check if capacitor charged inc paddleValue ; 5 LF3F7: lda INTIM ; 4 bne LF3EF ; 2³ LF3FC: sta WSYNC ;-------------------------------------- bit INPT0 ; 3 read the paddle controller bmi LF404 ; 2³ check if capacitor charged inc paddleValue ; 5 LF404: dey ; 2 cpy #31 ; 2 bne LF3FC ; 2³+1 lda carColor ; 3 bit colorMode ; 3 bpl .colorPlayerCar ; 2³ and #$0F ; 2 .colorPlayerCar sta COLUPF ; 3 CarKernel sta WSYNC ;-------------------------------------- tya ; 2 lsr ; 2 divide value by 8 whic makes the car lsr ; 2 32 scan lines high lsr ; 2 tax ; 2 lda CarGraphics,x ; 4 sta PF2 ; 3 = @17 dey ; 2 bne CarKernel ; 2³ jmp Overscan ; 3 PylonKernel lda pylonColor ; 3 bit colorMode ; 3 bpl .colorPylons ; 2³ and #$0F ; 2 mask the color hue for B/W .colorPylons sta COLUP0 ; 3 color the pylons by setting the sta COLUP1 ; 3 player colors (pylons are missiles) LF431: sta WSYNC ;-------------------------------------- cpy $AF ; 3 beq .nextPylonGroup ; 2³ dey ; 2 bne LF431 ; 2³ .nextPylonGroup inx ; 2 txa ; 2 dex ; 2 asl ; 2 multiply by 2 sta pylonHeight ; 3 set the pylon height for the section stx tempPylonGroupNumber ; 3 save pylon group number for later sty tempKernelScanline ; 3 save scan line number for later lda leftPylonHorizPos,x ; 4 ldx #2 ; 2 set index to coarse move missile 0 jsr CalculateHorizPosition ; 6 sta HMM0 ; 3 set fine motion for missile 0 ldx tempPylonGroupNumber ; 3 retrieve pylon group number lda rightPylonHorizPos,x ; 4 ldx #3 ; 2 set index to coarse move missile 1 jsr CalculateHorizPosition ; 6 sta HMM1 ; 3 set fine motion for missile 1 ldx tempPylonGroupNumber ; 3 retrieve pylon group number lda tempKernelScanline ; 3 retrieve scan line number sec ; 2 sbc #5 ; 2 took 5 lines to compute position cpx $92 ; 3 bne LF469 ; 2³ bit gameState ; 3 bpl LF469 ; 2³ sta $8F ; 3 LF469: tay ; 2 cpy #16 ; 2 bcc EndDisplayKernel ; 2³ sta WSYNC ;-------------------------------------- sta HMOVE ; 3 lda $A7 ; 3 and $A4 ; 3 bne LF47C ; 2³ lda #ENABLE_BM ; 2 sta ENAM0 ; 3 = @16 LF47C: lda $A8 ; 3 and $A4 ; 3 bne LF486 ; 2³ lda #ENABLE_BM ; 2 sta ENAM1 ; 3 LF486: dey ; 2 .drawPylonLoop sta WSYNC ;-------------------------------------- dey ; 2 dec pylonHeight ; 5 bne .drawPylonLoop ; 2³ sta WSYNC ;-------------------------------------- lda #DISABLE_BM ; 2 sta ENAM1 ; 3 = @05 sta ENAM0 ; 3 = @08 dey ; 2 asl $A4 ; 5 inx ; 2 cpx #MAX_PYLON_GROUPS ; 2 beq EndDisplayKernel ; 2³ lda PylonVertOffsetTable,x ; 4 sta pylonVertSpace ; 3 .pylonSpaceLoop sta WSYNC ;-------------------------------------- dey ; 2 dec pylonVertSpace ; 5 bne .pylonSpaceLoop ; 2³ jmp .nextPylonGroup ; 3 EndDisplayKernel sta WSYNC ;-------------------------------------- .kernelWaitTime ldx INTIM bne .kernelWaitTime Overscan SUBROUTINE lda #OVERSCAN_TIME sta TIM64T stx PF2 ; clear PF2 register (x = 0) ; ; random number generator...also used in Cosmic Ark ; lda randomSeed asl eor randomSeed asl asl rol randomSeed lda $96 lsr bit gameState bmi LF4CE jmp .waitTime LF4CE: bcs LF4D3 LF4D0: jmp LF555 LF4D3: lda $9B cmp #16 bcs LF4D0 lda paddleValue ldy #0 sty paddleValue LF4DF: sec sbc #10 bcc LF4E8 iny jmp LF4DF LF4E8: ldx #0 cpy #11 bcc LF4F0 ldy #10 LF4F0: sty $98 bit $A5 bmi LF51F lda $9B bne LF500 lda carVelocity ; get the player's car velocity beq LF51F ; branch if not moving bne LF502 ; unconditional branch LF500: ldy $9A LF502: lda leftPylonHorizPos,x clc adc LF7B0,y sta leftPylonHorizPos,x lda rightPylonHorizPos,x clc adc LF7B0,y sta rightPylonHorizPos,x inx cpx #MAX_PYLON_GROUPS bne LF502 lda initLeftPylonXPos clc adc LF7B0,y sta initLeftPylonXPos LF51F: lda $9B bne LF555 inc $B7 lda fireButtonValue eor SWCHA bpl LF532 lda #0 sta $B7 sta $9E LF532: lda SWCHA ; read paddle button (accelerator) sta fireButtonValue ; save for later (D7 = 0 when pressed) lda $B7 cmp #$05 bne LF555 lda carVelocity ; get the player's car velocity bit fireButtonValue ; check if player accelerating car bpl .checkToIncrementVelocity ; branch if car accelerating cmp #MIN_VELOCITY beq LF551 ; branch if player car at rest dec carVelocity ; slow player's car (accelerator not pressed) bpl LF551 ; unconditional branch .checkToIncrementVelocity cmp maximumVelocity ; compare current velocity to maximum beq LF551 ; branch if maximum velocity reached inc carVelocity ; increase player's velocity LF551: lda #0 sta $B7 LF555: ldx $9B bne LF587 lda $C5 bmi LF565 cmp #72 bcc LF565 stx $9A bcs LF573 LF565: lda $CE cmp #88 bcs LF5B1 cmp #32 bcc LF5B1 lda #10 sta $9A LF573: lda #$81 sta $9B lda #$0F sta sound0Volume lda #$15 sta AUDF0 lda #$08 sta AUDC0 stx carVelocity stx $A9 LF587: DEC $9B ;5 LDA $9B ;3 BEQ LF5A4 ;2 AND #$07 ;2 BNE LF5D5 ;2 LDY $A9 ;3 LDA LF7ED,Y ;4 ORA #$08 ;2 BIT colorMode ;3 BPL LF59E ;2 AND #$0F ;2 LF59E: STA COLUBK ;3 INC $A9 ;5 BNE LF5D5 ;2 LF5A4: STA $9B ;3 STA $B7 ;3 STA COLUBK ;3 LDX #$0E ;2 STX AUDF0 ;3 INX ;2 STX AUDC0 ;3 LF5B1: lda obstacleStatus ; get obstacle status flags and #$07 ; keep the obstacle id bne LF5D5 ; branch if not a car lda $8F CMP #$50 ;2 BCS LF5D5 ;2 lda player0HorizPos ; get the car's horizontal position bmi LF5D5 cmp #56 bcc LF5D5 lda #0 sta $9A lda obstacleStatus ora #%00000100 sta obstacleStatus lda #4 sta $92 bne LF573 ; unconditional branch LF5D5: lda #%10000000 sta VBLANK ; enable TIA and discharge paddles sta WSYNC sta WSYNC sta WSYNC ldy #0 sty VBLANK lda $9B bne LF5FC lda $96 and #$03 bne LF5F4 ldx carVelocity lda LF7F5,x sta $8E LF5F4: LSR $8E ;5 BCC LF5FA ;2 LDY #$0C ;2 LF5FA: STY AUDV0 ;3 LF5FC: LDA $96 ;3 AND #$07 ;2 BNE LF60D ;2 LDA sound0Volume ;3 BEQ LF60D ;2 sec sbc #1 sta AUDV0 sta sound0Volume LF60D: lda sound1Volume beq LF625 dec sound1Volume and #%00011000 ; make sure value is 0, 8, 16, or 24 beq LF619 lda #$0F LF619: sta AUDV1 LDA #$0F ;2 STA AUDF1 ;3 LDA #$0C ;2 STA AUDC1 ;3 BNE .waitTime ;2 LF625: LDA #$08 ;2 STA AUDF1 ;3 STA AUDC1 ;3 LDA $9B ;3 BNE LF641 ;2 lda carVelocity cmp #MIN_VELOCITY+2 bcc LF641 LDA $98 ;3 BEQ LF63D ;2 CMP #$0A ;2 BNE LF641 ;2 LF63D: LDA #$08 ;2 BNE LF643 ;2 LF641: LDA #$00 ;2 LF643: STA AUDV1 ;3 .waitTime ldx INTIM bne .waitTime jmp MainLoop SystemPowerup lda #MSBL_SIZE2 ; set the size of the pylons and obstacles sta NUSIZ0 sta NUSIZ1 sta randomSeed ; initialize randomSeed lda #$0E sta AUDF0 ldx #1 stx gameSelection ; initialize game selection stx obstacleVertSize stx $98 lda #MAX_VELOCITY-1 sta carVelocity ; set car's velocity lda #BLUE+7 sta carColor InitPylonVariables lda #RED+8 sta pylonColor lda #176 sta $AF lda #172 sta $8F lda #START_LEFT_PYLON_XPOS sta initLeftPylonXPos sta leftPylonHorizPos lda #START_RIGHT_PLYON_XPOS sta rightPylonHorizPos ldx #1 lda #XMAX+1 sta AUDC0 ldy #XMIN-1 .initPylonHorizPosLoop sta rightPylonHorizPos,x sty leftPylonHorizPos,x inx cpx #MAX_PYLON_GROUPS bne .initPylonHorizPosLoop rts DeterminePylonXOffset lda carVelocity sta $81 lda #0 LF697: dec $81 bmi LF6A2 clc adc LF6FA,y jmp LF697 LF6A2: rts ResetGameVariables lda #0 sta obstacleStatus sta COLUBK ; set background color to BLACK sta trackDiffTableIndex ; reset the track difficulty table index sta trackCheckPoint ; reset track check point sta $9B sta trackDifficulty sta AUDV0 ; turn off game sounds by setting volume sta AUDV1 ; registers to 0 sta gameTimerBCD sta gameTimerMask lda #MAX_VELOCITY-1 sta carVelocity ; set car's velocity sta $A1 rts CalculateHorizPosition clc adc #55 pha ; push value to stack for later lsr ; shift top nybble to lower nybble lsr lsr lsr tay ; save the value pla ; get the object's x position and #$0F ; mask upper nybble sty temp ; save coarse value for later clc adc temp ; add in coarse value (A = C + F) cmp #15 bcc .skipSubtractions sbc #15 ; subtract 15 iny ; and increment coarse value .skipSubtractions cmp #8 ; make sure hasn't gone pass min x value eor #$0F bcs .skipFineIncrement adc #1 ; increment fine motion value dey ; reduce coarse value .skipFineIncrement asl ; move fine motion value to upper nybble asl asl asl sty WSYNC ; wait for next scan line .coarseMoveLoop dey bpl .coarseMoveLoop sta RESP0,x ; set object's coarse position rts PylonVertOffsetTable .byte 0, 1, 1, 1, 2, 4, 8, 10 LF6F5: .byte 0, -4, 4, -7, 7 LF6FA: .byte -1,-1,-1,-1,-1,-1,-1,-1,1,1,1,0,-1,-1,-1,-1 .byte -2,-2,-2,-1,-1,-1,-1,0 LF712: .byte $00,$10,$08 TrackDifficultyDataPointers .word RandomDifficultySettings ; these values are never read .word NoviceDifficultySettings .word ProDifficultySettings .word ExpertDifficultySettings RandomDifficultySettings .byte $01, 20 .byte $81, 2 .byte $C2, 4 .byte $81, 1, 0 NoviceDifficultySettings .byte $01, 20 .byte $81, 18 .byte $01, 24 .byte $C2, 10 .byte $01, 10 .byte $81, 16, 0 ProDifficultySettings .byte $01, 10 .byte $C2, 10 .byte $01, 6 .byte $81, 8 .byte $01, 8 .byte $81, 18 .byte $C2, 10 .byte $01, 8 .byte $81, 10, 0 ExpertDifficultySettings .byte $01, 10 .byte $81, 8 .byte $C2, 15 .byte $01, 8 .byte $C2, 10 .byte $81, 10 .byte $01, 10 .byte $81, 20 .byte $C2, 12, 0 CarGraphics .byte $A0 ; |X.X.....| .byte $E0 ; |XXX.....| .byte $A0 ; |X.X.....| .byte $80 ; |X.......| LF75D: .byte $08 ; | X | $F75D .byte $10 ; | X | $F75E .byte $20 ; | X | $F75F ObstacleVertSizeTable .byte 1, 2, 4 ObstacleSizeTable .byte MSBL_SIZE2 | ONE_COPY .byte MSBL_SIZE2 | DOUBLE_SIZE .byte MSBL_SIZE2 | QUAD_SIZE ObstacleDataPointers .word CarData,TreeData,HouseData,TreeData,CrashedCarData ObstacleSpriteData TreeData .byte $18 ; |...XX...| .byte GREEN+4 .byte $3C ; |..XXXX..| .byte GREEN+4 .byte $7E ; |.XXXXXX.| .byte GREEN+4 .byte $FF ; |XXXXXXXX| .byte GREEN+4 .byte $FF ; |XXXXXXXX| .byte GREEN+4 .byte $18 ; |...XX...| .byte ORANGE+4 .byte $18 ; |...XX...| .byte ORANGE+4 .byte $18 ; |...XX...| .byte ORANGE+4 HouseData .byte $18 ; |...XX...| .byte RED+6 .byte $3C ; |..XXXX..| .byte RED+6 .byte $7E ; |.XXXXXX.| .byte RED+6 .byte $FF ; |XXXXXXXX| .byte RED+6 .byte $7A ; |.XXXX.X.| .byte BLACK+12 .byte $6A ; |.XX.X.X.| .byte BLACK+12 .byte $6E ; |.XX.XXX.| .byte BLACK+12 .byte $6E ; |.XX.XXX.| .byte BLACK+12 CrashedCarData .byte $78 ; |.XXXX...| .byte BLUE+6 .byte $FC ; |XXXXXX..| .byte BLUE+4 .byte $96 ; |X..X.XX.| .byte BLUE+4 .byte $7A ; |.XXXX.X.| .byte BLUE+2 .byte $3E ; |..XXXXX.| .byte BLUE+2 .byte $33 ; |..XX..XX| .byte ORANGE+12 .byte $1F ; |...XXXXX| .byte BLUE+2 .byte $12 ; |...X..X.| .byte BLUE+2 CarData .byte $3C ; |..XXXX..| .byte BLUE+8 .byte $7E ; |.XXXXXX.| .byte BLUE+6 .byte $CB ; |XX..X.XX| .byte BLUE+4 .byte $9D ; |X..XXX.X| .byte BLUE+2 .byte $FF ; |XXXXXXXX| .byte BLUE+2 .byte $C3 ; |XX....XX| .byte ORANGE+12 .byte $FF ; |XXXXXXXX| .byte BLUE+2 .byte $42 ; |.X....X.| .byte BLUE+2 LF7B0: .byte -4 .byte -3 .byte -2 .byte -1 .byte 0 .byte 0 .byte 0 .byte 1 .byte 2 .byte 3 .byte 4 NumberFonts zero .byte $0E ; |....XXX.| .byte $0A ; |....X.X.| .byte $0A ; |....X.X.| .byte $0A ; |....X.X.| .byte $0E ; |....XXX.| one .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| two .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $EE ; |XXX.XXX.| .byte $88 ; |X...X...| .byte $EE ; |XXX.XXX.| three .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $66 ; |.XX..XX.| .byte $22 ; |..X...X.| .byte $EE ; |XXX.XXX.| four .byte $AA ; |X.X.X.X.| .byte $AA ; |X.X.X.X.| .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| five .byte $EE ; |XXX.XXX.| .byte $88 ; |X...X...| .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $EE ; |XXX.XXX.| six .byte $EE ; |XXX.XXX.| .byte $88 ; |X...X...| .byte $EE ; |XXX XXX.| .byte $AA ; |X.X.X.X.| .byte $EE ; |XXX.XXX.| seven .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| .byte $22 ; |..X...X.| eight .byte $EE ; |XXX.XXX.| .byte $AA ; |X.X.X.X.| .byte $EE ; |XXX.XXX.| .byte $AA ; |X.X.X.X.| .byte $EE ; |XXX.XXX.| nine .byte $EE ; |XXX.XXX.| .byte $AA ; |X.X.X.X.| .byte $EE ; |XXX.XXX.| .byte $22 ; |..X...X.| .byte $EE ; |XXX.XXX.| LF7ED .byte BROWN+2 .byte ORANGE+4 .byte BLUE_PURPLE+6 .byte YELLOW+2 .byte BRIGHT_GREEN+4 .byte GREEN_BROWN+6 .byte RED+2 .byte BLUE+4 LF7F5: .byte $00 ; | | $F7F5 .byte $0C ; | XX | $F7F6 .byte $0C ; | XX | $F7F7 .byte $0A ; | X X | $F7F8 .byte $0E ; | XXX | $F7F9 .byte $0F ; | XXXX| $F7FA .byte $0F ; | XXXX| $F7FB .org ROMTOP+2048-4, 0 ; 2K ROM .word Start .word 0