;;; ---------- COLOR TEST ---------- ;;; Michael Martin, 2014 ;;; ;;; This is a sample program for the Atari 2600 VCS that lets you ;;; explore the 128-color palette the system provides. This is ;;; presented mainly as a more sophisticated example program to ;;; supplement "hi_stella". ;;; ;;; It makes use of every graphical element but the Ball, and also ;;; makes use of multicolor asymmetric playfields. .require "../../platform/stella.oph" .outfile "colortest.bin" .data .org $0080 .space startcol 1 ; Starting color for the striped playfield .space subrow 1 ; Counting lines per "tall pixel" .space curcol 1 ; The color number we are focusing on at the moment .space high_nybble 2 ; Pointer to graphic data for 16s hexit .space low_nybble 2 ; Pointer to graphic data for ones hexit .space input_allowed 1 ; Flag for whether or not to ignore input. .text ;; Start at $f800 - 2KB ROMs are the smallest size available. .org $f800 reset: `clean'start ;; We offer 1c as the initial color. It's a nice yellow shade. lda #$1c sta curcol frame: `vertical'sync ; Beginning of the frame. Set up the timer lda #43 ; to count out the length of VBLANK while sta TIM64T ; we do the processing for the display. ;; Place the player and missile graphics appropriately. We ;; count cycles and write the missile and player reset registers ;; at the closest times we can manage. Due to the way the TIA ;; timing works, the formula for the pixel they will show up at ;; is N*3-55+P, where N is the number of cycles from the end of ;; the latest STA WSYNC and the end of the STA RES* instruction, ;; and P is 1 for player sprites and 0 for missiles and the ball. ;; ;; The line after that we can strobe HMOVE to adjust them the ;; rest of the way into place. ;; ;; We will be using the missiles to draw the left and right ;; sides of a largish square and the player sprites to display a ;; byte value (the current color) as two hex digits (one per ;; player). All the rest of our graphics will be done via the ;; playfield registers. ;; ;; The missile graphics are being targeted to pixels 40 and 116 ;; and will be 4 pixels wide each. The player graphics will be ;; 8 pixels wide and are targeting pixels 72 and 80. sta WSYNC sta WSYNC ; = 0 ldy #$06 ; +2 = 2 * dey ; +2 = 4- 9-14-19-24-29 bne - ; +3 = 7-12-17-22-27-31 sta RESM0 ; +3 = 34 (31*3-55 = 38. Needs to move 2 pixels right.) lda #$E0 ; +2 = 36 sta HMCLR ; +3 = 39 - reset the fine-move registers sta HMM0 ; +3 = 42 - set M0 to move 2 right sta RESP0 ; +3 = 45 (42*3-54 = 72. Placed perfectly.) sta RESP1 ; +3 = 48 (45*3-54 = 81. Needs to move 1 pixel left.) lda #$10 ; +2 = 50 sta HMP1 ; +3 = 53 - and set P1 to move 1 left. nop ; +2 = 55 nop ; +2 = 57 sta RESM1 ; +3 = 60 (57*3-55 = 116. Placed perfectly.) sta WSYNC sta HMOVE ; Next scanline, execute the fine moves. lda #$20 sta NUSIZ0 ; Quad-size missiles, single copy of single player sta NUSIZ1 ; M1 and P1 are the same ;; Read the input lda #$00 sta SWACNT lda SWCHA bit input_allowed bmi true_input_read ;; Wait for neutral stick so we can re-enable input. and #$f0 cmp #$f0 bne input_done ;; Bits are set if the direction isn't active, so we only get ;; here if the stick was neutral. this also means the accumulator ;; has #$f0 in it now, which means we can store it directly and ;; the BIT/BMI above will start succeeding next frame. sta input_allowed beq input_done true_input_read: ;; Now we rotate it through the carry bit to see what ;; direction was pushed. We advance the color 2 or 16 at a time, ;; depending. (The least significant bit in the color register is ;; the one ignored, so we are not missing anything here.) ror ; Skip P2 input ror ror ror ror ; Carry clear if up bcs + inc curcol ; If up, increase color by 2 inc curcol jmp input_found * ror ; Carry clear if down bcs + dec curcol ; If down, decrease color by 2 dec curcol jmp input_found * ror ; Carry clear if left bcs + lda curcol sec sbc #$10 ; Left decreases color by 16 sta curcol jmp input_found * ror ; Carry clear if right bcs input_done lda curcol adc #$10 ; Right increases color by 16 sta curcol input_found: lda #$00 sta input_allowed input_done: ;; Clear the playfield while we wait, and make it asymmetric lda #$00 sta PF0 sta PF1 sta PF2 sta CTRLPF ;; alter playfield color so we get a rotating effect dec startcol ;; prepare numeric sprite values lda curcol lsr lsr lsr lsr tay lda digits_low, y sta high_nybble lda curcol and #$0f tay lda digits_low, y sta low_nybble lda #$ff sta low_nybble+1 sta high_nybble+1 ;; Wait for VBLANK to finish, then turn off the VBLANK signal. * lda INTIM bne - sta WSYNC sta VBLANK ;; Display kernel. ;; Top blank: 4 lines ldx #4 stx subrow * sta WSYNC dex bne - ;; Header graphics: 20 lines ldy #5 ldx startcol header_loop: sta WSYNC stx COLUPF ; +3 = 3 lda pf0_left-1,y ; +4 = 7 sta PF0 ; +3 = 10 lda pf1_left-1,y ; +4 = 14 sta PF1 ; +3 = 17 lda pf2_left-1,y ; +4 = 21 sta PF2 ; +3 = 24 cmp $80 ; +3 = 27 (3-cycle no-op) lda pf0_right-1,y ; +4 = 31 sta PF0 ; +3 = 34 lda pf1_right-1,y ; +4 = 38 sta PF1 ; +3 = 41 lda pf2_right-1,y ; +4 = 45 sta PF2 ; +3 = 48 ** MUST STORE PF2 2ND TIME ON EXACTLY CYCLE 48 ** inx ; +2 = 50 inx ; +2 = 52 dec subrow ; +5 = 57 bne header_loop ; +2 = 59 dey ; +2 = 61 beq header_done ; +2 = 63 lda #4 ; +2 = 65 sta subrow ; +3 = 68 bne header_loop ; +3 = 71 ;; We've cut it very fine here! We only have 76 cycles per ;; scanline and we use nearly all of them. header_done: ;; Ruled split between title and data (8 lines) ldy #$00 ; Clear playfield now that we're done (+2 = 72) ldx #$0c ; Default status color is light grey (+2 = 74) sta WSYNC ; Rest of previous line sty PF0 sty PF1 sty PF2 stx COLUPF stx COLUP0 stx COLUP1 dey ldx #$f0 sta WSYNC sta WSYNC sta WSYNC stx PF0 ; Fill playfield completely sty PF1 sty PF2 ldy #$01 sty CTRLPF ; Symmetric PF sta WSYNC sta WSYNC sta WSYNC sta WSYNC dey sty PF0 ; Clear playfield again sty PF1 sty PF2 ldy #$08 ; 32 lines (for letters; 8, 16, 8) * sta WSYNC dey bne - ldy #$06 * lda (high_nybble), y sta GRP0 lda (low_nybble), y sta GRP1 sta WSYNC sta WSYNC dey bpl - iny sty GRP0 sty GRP1 ldy #$0A * sta WSYNC dey bne - ;; Top border (12 lines) lda #$03 sta PF1 lda #$ff sta PF2 sta WSYNC sta WSYNC sta WSYNC sta WSYNC sta WSYNC sta WSYNC ldx #$00 stx PF1 stx PF2 ldx #$02 ; Turn on walls (the missiles) stx ENAM0 stx ENAM1 sta WSYNC sta WSYNC sta WSYNC sta WSYNC sta WSYNC sta WSYNC sta PF2 lda curcol sta COLUPF ;; Color blob (96 lines) ldx #96 * sta WSYNC dex bne - ;; Bottom border (12 lines) stx PF2 lda #$0c sta COLUPF sta WSYNC sta WSYNC sta WSYNC sta WSYNC sta WSYNC sta WSYNC stx ENAM0 ; Turn off walls (the missles) stx ENAM1 lda #$03 sta PF1 lda #$ff sta PF2 sta WSYNC sta WSYNC sta WSYNC sta WSYNC sta WSYNC sta WSYNC stx PF1 stx PF2 ldx #$08 * sta WSYNC dex bne - ; Turn on VBLANK, do 30 lines of Overscan lda #$02 sta VBLANK ldy #30 * sta WSYNC dey bne - jmp frame ; And the frame is done, back to VSYNC. ;;; Graphical data. Notice that we have to start not on a page ;;; boundary, but with all graphics in each group on one page. .advance $FF01 pf0_left: .byte $e0,$20,$20,$20,$e0 pf1_left: .byte $77,$54,$54,$54,$74 pf2_left: .byte $ae,$6a,$ea,$aa,$ee pf0_right: .byte $00,$00,$00,$00,$00 pf1_right: .byte $4e,$48,$4c,$48,$ee pf2_right: .byte $27,$24,$27,$21,$77 ;; We don't need a digits_high. It's always $FF! digits_low: .byte