[stella] Death Derby: First code submission

Subject: [stella] Death Derby: First code submission
From: Glenn Saunders <cybpunks2@xxxxxxxxxxxxx>
Date: Mon, 10 Sep 2001 10:12:00 -0700
Here it is.


Glenn Saunders - Producer - Cyberpunks Entertainment Personal homepage: http://www.geocities.com/Hollywood/1698 Cyberpunks Entertainment: http://cyberpunks.uni.cc
;Death Derby 2001
;version .0001 alpha
;By Glenn Saunders

;Built opon the foundation of:
; How to Draw A Playfield.
; by Nick Bensema  9:23PM  3/2/97
	

;
; Atari 2600 programming is different from any other kind of programming
; in many ways.  Just one of these ways is the flow of the program.
;
; Since the CPU must hold tha TIA's hand through all graphical operations,
; the flow ought to go like this:
;
; Clear memory and registers
; Set up variables
; Loop:
;    Do the vertical blank
;    Do game calculations
;    Draw screen
;    Do more calculations during overscan
;    Wait for next vertical blank
; End Loop.
;
; What I will do is create an outline, and explain everything I can.
; This program will display "HELLO" and scroll it down the screen.
;
; In writing this program, I will take the opportunity to show you
; how a few simple modifications can completely change a program's
; appearance or behavior.  I will invite you to comment out a few
; lines of code, and alter others, so that you can observe the results.
;



  processor 6502
  include vcs.h


; CUSTOM EQUATES

;FINE CONTROL FOR MISSILES

Right_8 = %10000000
Right_7 = %10010000
Right_6 = %10100000
Right_5 = %10110000
Right_4 = %11000000
Right_3 = %11010000
Right_2 = %11100000
Right_1 = %11110000
Right_0 = %00000000


; CONSTANTS      
ScoreHeight = #32
ScreenHeight = #160

; RAM POINTERS
    SEG.U vars
    org $80

;PLAYFIELD LAYOUT:
;32 tombstone bits per row    (X resolution)
;20 tombstone bits per column (Y resolution)
; each tombstone is 8 scanlines high


;640 bits used for the entire bitmap
; 80 bytes of RAM TOTAL

LeftPF1DataRAM 	ds 20
LeftPF2DataRAM	ds 20
RightPF1DataRAM ds 20
RightPF2DataRAM ds 20

PlayfieldY 	ds 1 ; scanline counter
RowCounter 	ds 1


; THESE ARE TEMP VARIABLES AND SHOULD BE MERGED LATER
; USING OVERLAYS
FoundPFRam 	ds 1
NewTombstone 	ds 1
M0_CollisionRow ds 1
M1_CollisionRow ds 1
; /TEMPS

P0Score ds 2
P1Score ds 2


;76543210
;xxx      SPEED (7 speeds plus stop)
;   x     Gear 1 = forward, 0 = reverse
;    xxxx 16 degrees rotation
P0State ds 1
P1State ds 1

P0_X ds 1
P1_X ds 1

P0_Y ds 1
P1_Y ds 1


;76543210
;xx       SPEED (3 speeds plus stop)
;      xx Current Direction (UP DOWN LEFT RIGHT)
;    xx   Frame of animation (0-3)
;  xx     Type (0-3)
M0State ds 1
M1State ds 1

;MISSILE X is a base value.  All subsequent horizontal positioning
;adds variable positive (+1-7 pixels to the right) fine control adjustments
M0_X ds 1
M1_X ds 1

M0_Y ds 1
M1_Y ds 1


CURRENT_VARIATION ds 1; encode all game variations bits here

GAME_TIMER ds 1;BCD timer for traditional arcade mode



;ROM AREA

        SEG code
        org $F000


Start
;
; The 2600 powers up in a completely random state, except for the PC which
; is set to the location at $FFC.  Therefore the first thing we must do is
; to set everything up inside the 6502.
;
        SEI  ; Disable interrupts, if there are any.
        CLD  ; Clear BCD math bit.
;
; You may feel the need to use the stack, in which case:
;
        LDX  #$FF
        TXS  ; Set stack to beginning.
;
; Now the memory and TIA registers must be cleared.  You may feel the
; need to write a subroutine that only clears out a certain section of
; memory, but for now a simple loop will suffice.
;
; Since X is already loaded to 0xFF, our task becomes simply to ocunt
; everything off.
;
        LDA #0
B1      STA 0,X
        DEX
        BNE B1
;
; The above routine does not clear location 0, which is VSYNC.  We will
; take care of that later.
;
; At this point in the code we would set up things like the data
; direction registers for the joysticks and such.
;
        ;JSR  GameInit
        
        
        
        ; This should only need to be called once when the game first boots up
        ; so it is no longer a subroutine
	;GameInit
	;0 is already in accumulator
	       STA PlayfieldY ; initialize scanline counter
	       LDA PF_Reflect
	       STA CTRLPF; set playfield to reflect
	       
	       ;initialize this test variable
	       ;LDA #159
	       ;STA TestMult8

	       
; load initial tombstone graphics from ROM to RAM
; this won't be necessary in the final game
	       LDX #20 ; start at the end and cycle back to zero
LoadLeftPF1
	       LDA LeftPF1DataROM,X
	       STA LeftPF1DataRAM,X
	       DEX
	       BNE LoadLeftPF1
	       
	       LDX #20 ; start at the end and cycle back to zero
LoadLeftPF2
	       LDA LeftPF2DataROM,X
	       STA LeftPF2DataRAM,X
	       DEX
	       BNE LoadLeftPF2
	       
	       LDX #20
LoadRightPF1
	       LDA RightPF1DataROM,X
	       STA RightPF1DataRAM,X
	       DEX
	       BNE LoadRightPF1
	       
	       LDX #20 ; start at the end and cycle back to zero
LoadRightPF2
	       LDA RightPF2DataROM,X
	       STA RightPF2DataRAM,X
	       DEX
	       BNE LoadRightPF2
	       
	       
	       JSR SetColors
        
;
; Here is a representation of our program flow.
;
MainLoop
        JSR  VerticalBlank ;Execute the vertical blank.
        JSR  CheckSwitches ;Check console switches.
        JSR  GameCalc      ;Do calculations during Vblank
        JSR  DrawScreen    ;Draw the screen
        JSR  OverScan      ;Do more calculations during overscan
        JMP  MainLoop      ;Continue forever.
;
; It is important to maintain a stable screen, and this routine
; does some important and mysterious things.  Actually, the only
; mysterious part is VSYNC.  All VBLANK does is blank the TIA's
; output so that no graphics are drawn; otherwise the screen
; scans normally.  It is VSYNC which tells the TV to pack its
; bags and move to the other corner of the screen.
;
; Fortunately, my program sets VBLANK at the beginning of the
; overscan period, which usually precedes this subroutine, so
; it is not changed here.
;
VerticalBlank
        LDX  #0
        LDA  #2
        STA  WSYNC
        STA  WSYNC
        STA  WSYNC
        
        STA  VSYNC ; Begin vertical sync.
        
        STA  WSYNC ; First line of VSYNC
        STA  WSYNC ; Second line of VSYNC.
;
; But before we finish off the third line of VSYNC, why don't we
; use this time to set the timer?  This will save us a few cycles
; which would be more useful in the overscan area.
;
; To insure that we begin to draw the screen at the proper time,
; we must set the timer to go off just slightly before the end of
; the vertical blank space, so that we can WSYNC up to the ACTUAL
; end of the vertical blank space.  Of course, the scanline we're
; going to omit is the same scanline we were about to waste VSYNCing,
; so it all evens out.
;
; Atari says we have to have 37 scanlines of VBLANK time.  Since
; each scanline uses 76 cycles, that makes 37*76=2888 cycles.
; We must also subtract the five cycles it will take to set the
; timer, and the three cycles it will take to STA WSYNC to the next
; line.  Plus the checking loop is only accurate to six cycles, making
; a total of fourteen cycles we have to waste.  2888-14=2876.
;
; We almost always use TIM64T for this, since the math just won't
; work out with the other intervals.  2880/64=44.something.  It
; doesn't matter what that something is, we have to round DOWN.
;
        LDA  #44
        STA  TIM64T
;
; And now's as good a time as any to clear the collision latches.
;
        LDA #0
        STA CXCLR
;
; Now we can end the VSYNC period.
;
        STA  WSYNC ; Third line of VSYNC.
        
        STA  VSYNC ; (0)
;
; At this point in time the screen is scanning normally, but
; the TIA's output is suppressed.  It will begin again once
; 0 is written back into VBLANK.
;
        RTS


; this was broken off in order to allow for color cycling later on
; in which case we'll need to run a function which just restores the color
; registers when the game resets
SetColors
       LDA #$00
       STA COLUBK  ; Background will be black.
       LDA #$FF
       STA COLUPF  ; set playfield to white for now

       LDA #%10010000 ; border graphics
       STA PF0 ; store border graphics for whole screen for now

       RTS


;
; Checking the game switches is relatively simple.  Theoretically,
; some of it could be slipped between WSYNCs in the VBlank code.
; But we're going for clarity here.
;
; It just so happens that I'm not going to check any game switches
; here.  I'm just going to set up the colors, without even checking
; the B&W switch!  HA!
;
CheckSwitches
       RTS
;
; Minimal game calculations, just to get the ball rolling.
;
GameCalc
        RTS

;
; This is the scariest thing I've done all month.
;
DrawScreen
        LDA INTIM
        BNE DrawScreen ; Whew!
        STA WSYNC
        STA VBLANK  ;End the VBLANK period with a zero.
;
; Now we can do what we need to do.  What sort of playfield do
; we want to show?  A doubled playfield will work better than
; anything if we either want a side scroller (which involves some
; tricky bit shifting, usually) or an asymmetrical playfield (which
; we're not doing yet).  A mirrored playfield is usually best for
; vertical scrollers.  With some creativity, you can use both in your
; game.
;
; The "score" bit is useful for drawing scores with playfield graphics
; as Combat and other early games do.  It can also create a neat effect
; if you know how to be creative with it.  One useful application of
; score mode would be always having player 1 on the left side, and
; player 0 on the right side.  Each player would be surrounded in the
; opposite color, and the ball graphic could be used to stand out
; against either one.  On my 2600jr, color from the right side bleeds
; about one pixel into the left side, so don't think it's perfect.
; It's really far from perfect because PC Atari does not implement
; it at all; both sides appear as Player 0's color.  A26 does, though.
;
; To accomodate this, my routine puts color values into
; COLUP0 for the left side, and COLUP1 for the right side.  Change
; the LDA operand to 0 or 1 to use the normal system.  The code in
; the scanning loop accounts for both systems.
;

;
; Initialize some display variables.
;
        ;There aren't any display variables!
;
; I'm going to use the Y register to count scanlines this time.
; Realize that I am forfeiting the use of the Y register for this
; purpose, but DEC Zero Page takes five cycles as opposed to DEY's
; two, and LDA Zero Page takes three cycles as opposed to TYA's two.
;
; I'm using all 191 remaining scanlines after the WSYNC.  If you
; want less, or more, change the LDY line.
;
; This is a decremental loop, that is, Y starts at 191 and continues
; down to 0, as do all functions of Y such as memory locations, which
; is why the graphics at the end of this file are stored "bottom-up".
; In a way, one could say that's how the screen is drawn.  To turn this
; into an incremental loop, change the number to 255-191 (65) and change
; the DEY at the end ot the scanning loop to INY.
;
	LDA #20
	; initialize (or reinitialize) rowcounter
	STA RowCounter

;
;reserve 32 scanlines for the score and border graphic

        LDY #ScoreHeight
DrawScore
        STA WSYNC
        STY PF1; for debugging purposes
        STY PF2
        DEY
        BNE DrawScore

; border line
DrawTopBorder
	STA WSYNC
	LDA #%00000000
	STA PF0
	LDA #$FF
	STA PF1
	STA PF2

	LDA #ScreenHeight

ScanLoop

; Result of the following math is:
;  Y starts out holding the current scanline


	TYA ; transfer the scanline counter to A
	SBC #ScoreHeight
	LSR
	LSR
	LSR ; divide current scanline by 8
	TAX ; X is going to be used to index playfield RAM

;	LDA Sidewalk ; bit flip the sidewalk
;        EOR #128
;	STA Sidewalk
	
; WSYNC is placed BEFORE all of this action takes place.
        STA WSYNC ; start counting below

        NOP
        LDA LeftPF1DataRAM,X    
        STA PF1                 
        LDA LeftPF2DataRAM,X
        STA PF2                 
        
        ; this timing is imprecise.  I need help.

        NOP	;2
        NOP	;2
	NOP	;2
	NOP	;2
	NOP	;2
	NOP	;2
	CMP $80 ;3
		
	LDA RightPF2DataRAM,X
	TAX
	NOP 	;2
	CMP $80 ;3
	
	LDA RightPF1DataRAM,X
        STA PF2

	STX PF1                 ;[17]+3 = *20*  < 29

        
        
        DEY ; Y = Y - 1
        BNE ScanLoop; brance until Y = 0
;
; Clear all registers here to prevent any possible bleeding.
;
        LDA #2
        STA WSYNC  ;Finish this scanline.
        STA VBLANK ; Make TIA output invisible,
        ; Now we need to worry about it bleeding when we turn
        ; the TIA output back on.
        ; Y is still zero.
        ;STY PF0
        STY PF1
        STY PF2
        STY GRP0
        STY GRP1
        STY ENAM0
        STY ENAM1
        STY ENABL
        RTS

;
; For the Overscan routine, one might take the time to process such
; things as collisions.  I, however, would rather waste a bunch of
; scanlines, since I haven't drawn any players yet.
;

	
OverScan
	;Y is now at zero because it is a decremental scanline counter
	; insure that Sidewalk starts out as zero in the next frame
	;STY Sidewalk

;We've got 30 scanlines to kill.
        LDX #30
KillLines
         STA WSYNC
         DEX
         BNE KillLines
         RTS
ReverseBit
NOP ; insert code here
RTS

Add_Tombstone
; CALCULATE THE POINTER TO TOMBSTONE RAM ON COLLISION

; take the X position of the collision, divide by 4
; let's say X = 55.
; INT(55/4) = 13
; subtract by 4 because we will only be seeking beyond the safe-zone
; which is not part of the pseudobitmap

; we must turn on the 11th bit of the playfield

; divide by 8
; 11/8 = 1 remainder 3

;LDX 1
;LDA FindPF_RAM,X
;STA FoundPF_RAM
; we now have a ram pointer to the proper ram table

;NOW WE NEED TO POTENTIALLY FLIP THE BITS before writing to RAM
;LDA FindPF_RAM_BitOrder,X; load reversed bit order data
;BPL ReverseBit ; reverse bits if = $FF



; use the remainder
; turn on the 3rd bit of the first playfield ram table
; LDA 3
; STA NewTombstone ; temporarily save


; now calculate offset (row) to set

;subtract M0_Y by unused scanlines
;divide M0_Y by 8
; LDX (Y of collision) ;


; this combines the bit in the accumulator
; with the memory location starting at the contents of FindPF_RAM
; offset by the contents of X, the row counter
; ORA (FoundPF_RAM,X) 
RTS


;
; Graphics are placed so that the extra cycle in the PFData,X indexes
; is NEVER taken, by making sure it never has to index across a page
; boundary.  This way our cycle count holds true.
;

        org $FF00
;
; This is the tricky part of drawing a playfield: actually
; drawing it.  Well, the display routine and all that binary
; math was a bit tricky, too, but still, listen up.
;
; Playfield data isn't stored the way most bitmaps are, even
; one-dimensional bitmaps.  We will use the left side of the
; screen only, knowing that the right side is either repeated
; or reflected from it.
;
; In PF0 and PF2, the most significant bit (bit 7) is on the RIGHT
; side.  In PF1, the most significant bit is on the LEFT side.  This
; means that relative to PF0 and PF2, PF1 has a reversed bit order.
; It's just really weird.
;
;    PF0  |     PF1       |      PF2
;  4 5 6 7|7 6 5 4 3 2 1 0|0 1 2 3 4 5 6 7
;



;        DEATH RACE PLAYFIELD LAYOUT            
;   PF0|   PF1  |  PF2   |   PF2m |  PF1m  |PF0m
;  4567|76543210|01234567|76543210|01234567|7654



; these tables describe the playfield layout so that we can convert
; an X/Y collision into a bit to turn on in Playfield RAM



FindPF_RAM .byte #<LeftPF1DataRAM,#<LeftPF2DataRAM,#<RightPF2DataRAM,#<LeftPF1DataRAM

FindPF_RAM_BitOrder .byte #$FF,#$00,#$FF,#$00


;THIS ROM WON'T BE NECESSARY WHEN THE GAME BECOMES INTERACTIVE
	       ;REVERSED BIT ORDER
	       ;76543210
LeftPF1DataROM
        REPEAT 10 
        .byte %11111000
        .byte %01010101
        REPEND        
	

LeftPF2DataROM 
        REPEAT 10 
        .byte %01010101
        .byte %10101010
        REPEND 


;(REVERSE ALL BIT ORDERING)
	        ;76543210
RightPF2DataROM 
        REPEAT 10 
        .byte %11111111
        .byte %00000000
        REPEND	
        
	       ;REVERSED BIT ORDER
	       ;01234567
RightPF1DataROM
        REPEAT 10 
        .byte %11111111
        .byte %00000000
        REPEND	


;THE GRAPHICS        
CarRotation0   
  .byte %00000000; ........
  .byte %11101110; XXX.XXX.
  .byte %01100100; .XX..X..
  .byte %11011111; XX.XXXXX
  .byte %11011111; XX.XXXXX
  .byte %01100100; .XX..X..
  .byte %11101110; XXX.XXX.
  .byte %00000000; ........

CarRotation1   
  .byte %00000100; .....X..
  .byte %00011100; ...XXX..
  .byte %11000111; XX...XXX
  .byte %11111111; XXXXXXXX
  .byte %01011110; .X.XXXX.
  .byte %01100011; .XX...XX
  .byte %00110010; ..XX.X..
  .byte %00110000; ..XX....

CarRotation2
  .byte %00010000; ...X....
  .byte %00110110; ..XX.XX.
  .byte %00011110; ...XXXX.
  .byte %11011100; XX.XXX..
  .byte %11011011; XX.XX.XX
  .byte %01000010; .X....X.
  .byte %01111000; .XXXX...
  .byte %00011000; ...XX...

CarRotation3   
  .byte %00000000; ..X.XX..
  .byte %00000000; ..XXXX..
  .byte %00000000; .X.XXXXX
  .byte %00000000; ...XX.X.
  .byte %00000000; XX.XX.X.
  .byte %00000000; XXX.X...
  .byte %00000000; ..XXXX..
  .byte %00000000; ....XX..
 
CarRotation4
  .byte %00011000; ...XX...
  .byte %01011010; .X.XX.X.
  .byte %01111110; .XXXXXX.
  .byte %01011010; .X.XX.X.
  .byte %00011000; ...XX...
  .byte %01100110; .XX..XX.
  .byte %01111110; .XXXXXX.
  .byte %01011010; .X.XX.X.


;missiles are used as pseudoplayers
;data is encoded as width and pixel offsets.

;76543210
;XXXX    <- OFFSET (typically 0 through +8 pixels)
;    XX  <- NUSIZ data

;EXAMPLE:
;LDA (M0_ShapeFrame,X)
;STA HMM0
;LSL
;LSL
;STA NUSIZ0

Ped1_1;   OOOOWW__
   .byte %11110100 ; .XX.....
   .byte %11100000 ; ..X.....
   .byte %11011000 ; ...XXXX.
   .byte %11010100 ; ...XX...
   .byte %11111000 ; .XXXX...
   .byte %00000000 ; ........
   .byte %11010100 ; ...XX...
   .byte %11010100 ; ...XX...
   
Ped1_2;   OOOOWW__
   .byte %11010100 ; ...XX...
   .byte %11010100 ; ...XX...
   .byte %11010100 ; ...XX...
   .byte %11010100 ; ...XX...
   .byte %11010100 ; ...XX...
   .byte %00000000 ; ........
   .byte %11010100 ; ...XX...
   .byte %11010100 ; ...XX...

Ped1_3;   OOOOWW__
   .byte %11110100 ; .XX.....
   .byte %00000000 ; ..X.....
   .byte %11011100 ; ...XXXX.
   .byte %11100100 ; ..XX....
   .byte %11010100 ; ...XX...
   .byte %00000000 ; ........
   .byte %11010100 ; ...XX...
   .byte %11010100 ; ...XX...

Ped1_4;   OOOOWW__
   .byte %11011100 ; ...XXXX.
   .byte %11010000 ; ...X....
   .byte %11011100 ; ...XXXX.
   .byte %11010100 ; ...XX...
   .byte %00001100 ; XXXX....
   .byte %11101100 ; ..XXXX..
   .byte %11110100 ; .XX.....
   .byte %11110100 ; .XX.....

;add score graphics later


   
  
; this makes sure the program counter jumps to the program start
        org $FFFC
        .word Start
        .word Start
Current Thread