@@loop:
        DECLE   $0010
        DECLE   $4000
        DECLE   NUTMARCH
        ENDP

ball    STRUCT  $320            ; The ball
@@x     EQU     $ + 0           ; X position of ball as 8.8 'fixed point'
@@y     EQU     $ + 1           ; Y position of ball as 8.8 'fixed point'
@@xv    EQU     $ + 2           ; X velocity of ball as 8.8 'fixed point'
@@yv    EQU     $ + 3           ; Y velocity of ball as 8.8 'fixed point'
        ENDS

pad0    STRUCT  $120            ; Controller pad #0
@@y     EQU     $ + 0           ; Y position of center of pad
@@ai    EQU     $ + 1           ; Whether or not to use built-in AI on this pad
        ENDS

pad1    STRUCT  $122            ; Controller pad #1
@@y     EQU     $ + 0           ; Y position of center of pad
@@ai    EQU     $ + 1           ; Whether or not to use built-in AI on this pad
        ENDS

rnd     STRUCT  $35E            ; Random number state
@@lo    EQU     $ + 0           ; lower 16 bits
@@hi    EQU     $ + 1           ; upper 16 bits
        ENDS

BIPT    EQU     $113            ; Bip timer

;------------------------------------------------------------------------------
;------------------------------------------------------------------------------

T1TLE:  BYTE    100, "4-TRIS", 0

pong    PROC
@@scor0 EQU     $110            ; Pad #0's score
@@scor1 EQU     $111            ; Pad #1's score
@@start EQU     $112            ; "Start of new game" flag
@@wait  EQU     $350            ; Pause/Wait timer

        DIS
        SUBI    #4,     R5
        SDBD
        MVI@    R5,     R2
        MVII    #$1FE,  R4
        SDBD
        CMP@    R4,     R2
        BEQ     @@ok
        J       TITLE + 8       ; not secret game.
@@ok:   SUBI    #2,     R4
        SDBD
        MVI@    R4,     R2
        COMR    R2              ; wait for pads to go up
        BNEQ    @@ok
        JSRD    R5,     ISRSPIN ; Set ISR to '@@init' and spin.

;------------------------------------------------------------------------------
; Initialization code
;------------------------------------------------------------------------------
@@init:
        DIS
        ; Make sure rand-number generator's non-zero
        MVO     PC,     rnd.lo
        MVO     PC,     rnd.hi

        ; Set up GRAM, playfield
        MVII    #$3C,   R0
        MVII    #$3800, R4
        MVII    #8,     R1
        CALL    FILLMEM                 ; Set up paddle graphic

        MVII    #$18,   R0      
        MVO@    R0,     R4              ; Ball graphic
        MVO@    R0,     R4
        MVII    #6,     R1
        CALL    FILLZERO

        ; Clear display, memory
        MVII    #$100,  R4
        MVII    #$1F0,  R1
        CALL    FILLZERO
        MVII    #$300,  R4
        MVII    #$05D,  R1
        CALL    FILLZERO

        ; Playfield
        MVII    #$2E7,   R3             ; White bar
        MVII    #$200 + 10, R4
        MVII    #12,    R2
@@pfloop:
        MVO@    R3,     R4
        ADDI    #19,    R4
        DECR    R2
        BNEQ    @@pfloop

        ; Sprites
        CLRR    R4
        MVII    #$32,   R1
        CALL    FILLZERO                ; First, disable all MOBs

        ; Sprite attribute registers
                ;pfG??nnnnnnfff
        MVII    #00100000000010b, R0    ; paddles at either end
        MVII    #$10,   R4
        MVO@    R0,     R4              ; Paddle 0 in MOB 0
        DECR    R0
        MVO@    R0,     R4              ; Paddle 1 in MOB 1
        ADDI    #8+5,   R0
        MVO@    R0,     R4              ; Ball in MOB2

        ; Sprite position registers
        CLRR    R4
        MVII    #512 + 16,      R0      ; Paddle 0 at left
        MVII    #512 + 158,     R1      ; Paddle 1 at right
        MVO@    R0,     R4
        MVO@    R1,     R4

        MVII    #8,     R4
        MVII    #512 + 44, R0
        MVO@    R0,     R4              ; Paddle 0, centered, 2x height
        MVO@    R0,     R4              ; Paddle 1, centered, 2x height
        
        ; Set posn structures
        ANDI    #$FF,   R0
        MVO     R0,     pad0.y  
        MVO     R0,     pad1.y  

        ; Force color stack mode, all black
        MVI     $21,    R0
        CLRR    R0
        MVII    #$28,   R4
        MVO@    R0,     R4
        MVO@    R0,     R4
        MVO@    R0,     R4
        MVO@    R0,     R4
        MVO@    R0,     R4

        ; Make sound init'd properly
        MVII    #$38,   R0
        MVO     R0,     $1F8

        ; Set up an initial wait, and set the 'start of game' flag.
        MVII    #$80,   R0
        MVO     R0,     @@wait          ; Wait ~4 sec when game pops up
        MVO     R0,     @@start         ; Flag that we're starting up

        ; That's it.
        CALL    ISRSPIN                 ; Set game ISR, and spin.

;------------------------------------------------------------------------------
; Main Game ISR.  
; The entire game logic is implemented in the interrupt service routine.
; This is very reminiscent of how one would program for an Atari 2600.
;------------------------------------------------------------------------------
@@mainisr:
        PSHR    R5

        ; Do controller paddles
        MVII    #0,     R4
        CALL    @@padpos        ; Process controller 0

        MVII    #1,     R4
        CALL    @@padpos        ; Process controller 1

        ; If we're waiting because a ball was off-sides, skip ball update.
        MVI     @@wait, R0
        DECR    R0
        BMI     @@notwait       ; If expired, we're not waiting... just do it.
        MVO     R0,     @@wait
        BEQ     @@clearscore    ; When count transitions to 0, clear score disp
        B       @@done
@@clearscore:
        MVII    #$200 + 40, R4
        MVII    #10,    R1
        CALL    FILLZERO
        INCR    R4
        MVII    #10,    R1
        CALL    FILLZERO
@@notwait:

        ; Do ball position
        MVII    #ball.x,R4
        MVI@    R4,     R0      ; get x
        MVI@    R4,     R1      ; get y
        ADD@    R4,     R0      ; add xv
        MVI@    R4,     R3
        ADDR    R3,     R1      ; add yv
        SUBI    #4,     R4
        MVO@    R0,     R4      ; store x
        MVO@    R1,     R4      ; store y

        MVII    #$FF,   R2      ; handy constant

        SWAP    R0
        SWAP    R1
        ANDR    R2,     R0      ; integer portion of x pos
        ANDR    R2,     R1      ; integer portion of y pos

        CMPI    #1,     R0      ; Off left?
        BLE     @@badball       ; ...yes: bad ball!
        CMPI    #170,   R0      ; Off right?
        BGT     @@badball       ; ...yes: bad ball!

        TSTR    R3
        BPL     @@not_bounce_y_top ; If ball's not moving up, don't check top
        CMPI    #8,     R1      ; See if we hit the top of the screen.
        BLE     @@did_bounce_y  ; ... Yes, go bounce the ball.
        B       @@not_bounce_y  ; ... No, then don't test for bottom of screen.
        
@@not_bounce_y_top:
        CMPI    #101,   R1      ; We're moving down. Test for bottom of screen.
        BLE     @@not_bounce_y  ; ... No, then don't bounce.
@@did_bounce_y:
        NEGR    R3              ; Ok, bounce the ball by inverting velocity.
        MVO     R3,     ball.yv

        MVII    #$100,  R3      ; Make a mid-frequency 'BIP'
        CALL    _BIP

@@not_bounce_y:

        MVI     ball.xv, R5     ; Get our X velocity.
        NEGR    R5              ; Prepare to bounce off paddles.
        BPL     @@check_left    ; If going left, check left paddle.
        ; right paddle values
        MVII    #156,   R4      ; ... else check right paddle.
        MVI     pad1.y, R3
        B       @@check
@@check_left:
        ; left paddle values
        MVII    #19,    R4
        MVI     pad0.y, R3
@@check:
        ADDI    #7,     R3      ; center ball with respect to pad

        SUBR    R0,     R4      ; Compare ball's X position with paddle's
        BEQ     @@maybe_hit     ; If equal, maybe we've hit.
        DECR    R4              ; Add 1 unit of fuzz in there...
        BNEQ    @@not_hit       ; Still no match?  Forget it.

@@maybe_hit:
        SUBR    R1,     R3      ; We might have a hit, so check our Y values.
        BMI     @@upper_half    ; If ball's Y is less than paddle's, 
                                ; ...then we're in the upper half of the paddle

        CMPI    #$8,    R3      ; See if we're in 8 pixel range, lower half.
        BGT     @@not_hit       ; Greater?  We went under the paddle.
        B       @@hit           ; Else, we hit!  Go to the 'hit' stuff.

@@upper_half:                   ; We're in the upper half of the paddle.
        NEGR    R3              ; Negate the difference, so it's positive.
        CMPI    #$8,    R3      ; Are we in 8 pixel range on upper half?
        BGT     @@not_hit       ; Greater?  We went over the paddle.
        NEGR    R3              ; Else, we hit!  Fix our difference.

@@hit:
        NEGR    R3              ; First, negate our difference, and then
        SLL     R3,     2       ; multiply by 32.
        SLL     R3,     2
        SLL     R3,     1
        ADD     ball.yv, R3     ; Add this to our ball's Y velocity to put
                                ; some 'english' on the ball.

        MVO     R3,     ball.yv ; Store our updated Y velocity.
        MVO     R5,     ball.xv ; Store our negated X velocity.

        MVII    #$200,  R3      ; Default to low 'BIP' tone for left paddle.
        CMPI    #80,    R0
        BLT     @@bip_lo        ; If we're on right side, do high BIP tone.
        SLR     R3,     2       ; Set period to high BIP tone for right paddle
@@bip_lo
        CALL    _BIP            ; Bip!

@@not_hit:
        XORI    #512,   R0      ; Set the visibility bit on the ball
        XORI    #256,   R1      ; Set the Y Size 2x flag on the ball
        MVO     R0,     $2      ; Set X coord + Vis
        MVO     R1,     $A      ; Set Y coord + YSize2

@@done:
        ; Count down the 'bip' counter
        MVI     BIPT,   R0      ; Get the BIP counter.
        DECR    R0              ; Count it down
        BMI     @@bip_skip      ; Negative?  Already expired.
        MVO     R0,     BIPT    ; Store updated count.
        BNEQ    @@bip_skip      ; Not zero?  Don't shut off BIP.
        MVO     R0,     $1FB    ; Else, shut off the BIP by zeroing volume.
@@bip_skip:
        
        ; STIC 'handshake'
        MVO     R0,     $20     ; Tell the STIC to enable video.

        ; Return.
        PULR    PC              ; End of ISR!

;------------------------------------------------------------------------------
; Ball went off-sides -- OR -- start of a game.
;------------------------------------------------------------------------------
@@badball:
        MVII    #88,    R0
        MVII    #48,    R1
        MVO     R0,     @@wait
        SWAP    R0
        SWAP    R1

        MVO     R0,     ball.x
        MVO     R1,     ball.y
        CLRR    R0
        CMP     @@start, R0
        BEQ     @@doscore
        MVII    #@@scor0, R4
        MVO@    R0,     R4
        MVO@    R0,     R4
        MVO@    R0,     R4
        B       @@fix
@@doscore
        MVI     ball.xv, R0
        MVII    #@@scor0,R3
        TSTR    R0
        BPL     @@p1score
        INCR    R3
@@p1score
        MVI@    R3,     R1
        INCR    R1
        MVO@    R1,     R3

        ; show scores
        MVII    #$200 + 40 + 3, R4
        MVI     @@scor0,R0
        CALL    _SCORE  
        MVII    #$200 + 60 - 4, R4
        MVI     @@scor1,R0
        CALL    _SCORE  

@@again:
        MVI     ball.xv, R0
        TSTR    R0
        BEQ     @@fix
        MVI     ball.yv, R0
        SARC    R0,     1
        MVO     R0,     ball.yv
        BNEQ    @@done
@@fix:
        MVII    #32,    R2
        CALL    NEXTRAND  
        SARC    R0,     1
        MVII    #$FF,   R0
        ANDR    R0,     R1
        MVO     R1,     ball.yv
        ADCR    PC              ; skip negr if C set
        NEGR    R0
        MVO     R0,     ball.xv
        B       @@done

;------------------------------------------------------------------------------
; Process paddle's position, and update based on controller inputs.
;------------------------------------------------------------------------------
@@padpos:
        MOVR    R4,     R3
        ADDR    R4,     R3
        ADDI    #pad0.y, R3     ; Point to pad 0 or 1 structure
        MOVR    R4,     R2
        XORI    #$1FF,  R2      ; Point to controller input 0 or 1
        MVI@    R2,     R0      ; Load value from controller input.
        MVI@    R3,     R2      ; Load Y value from pad's structure.

        CMPI    #$FF,   R0      ; See if controller is pressed at all
        BEQ     @@nopadpress

        INCR    R3
        MVO@    R3,     R3      ; Set 'no AI' flag
        DECR    R3

        MOVR    R0,     R1
        ANDI    #1,     R0      ; See if player pressed down
        BNEQ    @@notpaddown
@@domoveup
        INCR    R2              ; Yes?  Move down.
        CMPI    #89,    R2
        BLE     @@padok
@@badpadpos:
@@padposdone:
        JR      R5
        
@@notpaddown
        ANDI    #4,     R1      ; See if player pressed up.
        BNEQ    @@nopadpress
@@domovedown
        DECR    R2              ; Yes?  Move up.
        CMPI    #8,     R2
        BLT     @@badpadpos
@@padok:
        MVO@    R2,     R3      ; Store new Y position
        XORI    #512,   R2      ; Set 'YSize2' bit.
        ADDI    #8,     R4      ; Point to correct pad's Y position register
        MVO@    R2,     R4
        JR      R5

@@nopadpress:
        ; If wait timer is active, don't do AI.
        MVI     @@wait, R0
        TSTR    R0
        BNEQ    @@padposdone

        ; Do 'AI' for paddles which haven't been moved yet.
        INCR    R3
        MVI@    R3,     R0
        DECR    R3
        TSTR    R0
        BNEQ    @@padposdone

        MOVR    R4,     R1
        DECR    R1              ; -1 if pad 0, 0 if pad 1
        XOR     ball.xv, R1     ; Want to compare sign bits
        SLLC    R1,     1       ; If carry cleared, ball coming our way.
        BC      @@padposdone

        MVI     ball.y, R1
        SWAP    R1
        ANDI    #$FF,   R1
        SUBI    #7,     R1
        SUBR    R2,     R1
        BGT     @@moveup        ; Ball above paddle, move up.
        BLT     @@movedown      ; Ball below paddle, move down.
        B       @@padposdone    ; Level with each other, don't move

        ; Allow some slop in AI so that the AI puts some 'english' on the ball
@@moveup:
        CMPI    #4,     R1      ; Are we within acceptible range on paddle?
        BLT     @@padposdone    ; Yes... don't move.
        B       @@domoveup
@@movedown:
        NEGR    R1
        CMPI    #4,     R1      ; Are we within acceptible range on paddle?
        BLT     @@padposdone    ; Yes... don't move.
        B       @@domovedown
        
        

        ENDP

;;==========================================================================;;
;; _BIP                                                                     ;;
;; Play a bip on the PSG, with period in R3.                                ;;
;;                                                                          ;;
;; INPUTS:                                                                  ;;
;; R3 -- Period of BIP sound effect.                                        ;;
;; R5 -- Return address.                                                    ;;
;;                                                                          ;;
;; OUTPUTS:                                                                 ;;
;; R3 -- trashed.                                                           ;;
;;==========================================================================;;
_BIP:    PROC

        MVO     R3,     $1F0    ; Store low half of period to Channel A lo
        SWAP    R3
        MVO     R3,     $1F4    ; Store high half of period to Channel A hi
        MVII    #$F,    R3      ; Volume == 15
        MVO     R3,     $1FB    ; Store volume to Channel A's volume reg.
        MVII    #4,     R3
        MVO     R3,     BIPT    ; Set our bip duration to 3 ticks.
        JR      R5
        ENDP



;;==========================================================================;;
;; RAND                                                                     ;;
;; Advance random number generator state by some number of bits.            ;;
;;                                                                          ;;
;; INPUTS:                                                                  ;;
;; R2 -- Number of bits to advance state by.                                ;;
;;                                                                          ;;
;; OUTPUTS:                                                                 ;;
;; R0 -- Lower 16 bits of random number state                               ;;
;; R1 -- Upper 16 bits of random number state                               ;;
;; R2 -- Zeroed                                                             ;;
;; R4 -- Clobbered                                                          ;;
;; R5 -- Intact                                                             ;;
;;==========================================================================;;
;RAND    PROC
;        MVII    #rnd.lo, R4
;        MVI@    R4,     R0
;        MVI@    R4,     R1
;
;@@loop:
;        SLLC    R0,     1
;        RLC     R1,     1
;        BNC     @@nocarry
;        XORI    #$A,    R0      ; period==(2**31 - 1) polynomial
;@@nocarry:
;        DECR    R2
;        BNEQ    @@loop
;
;        SUBI    #2,     R4
;        MVO@    R0,     R4
;        MVO@    R1,     R4
;        JR      R5
;        ENDP

;;==========================================================================;;
;; _SCORE                                                                   ;;
;; Display score for a given player.  Also test for game over.              ;;
;;                                                                          ;;
;; INPUTS:                                                                  ;;
;; R0 -- Score  (0..15)                                                     ;;
;; R4 -- Display pointer                                                    ;;
;; R5 -- Return address                                                     ;;
;;                                                                          ;;
;; OUTPUTS:                                                                 ;;
;; R0, R1 -- Trashed.                                                       ;;
;; R4 -- Points to right of score.                                          ;;
;;==========================================================================;;
_SCORE: PROC
        CMPI    #15,    R0
        BLT     @@notgameover           ; Game Over at 15 points.

        CLRR    R1
        DECR    R1
        SLR     R1,     1       
        MVO     R1,     pong.start      ; Reset our 'Start' and 'Wait' flags
        MVO     R1,     pong.wait       ; to a really large number ($7FFF)

@@notgameover:  
        CLRR    R1                      ; Start out leading digit as blank.
        CMPI    #10,    R0              ; See if we have a leading 1 digit.
        BLT     @@notten                ; If R0 < 10, we do not have leading 1.
        SUBI    #10,    R0              ; ... otherwise, subtract 10
        MVII    #$8F,   R1              ; ... and set the leading digit to '1'

@@notten:
        MVO@    R1,     R4              ; Write the first digit of the score.
        SLL     R0,     2               ; Move the second digit into place
        SLL     R0,     1               ; ... by left shifting 3.
        ADDI    #$87,   R0              ; Add card offset and #7 for 'white'
        MVO@    R0,     R4              ; Show second digit.
        JR      R5                      ; Return.
        ENDP

;;==========================================================================;;
;; ISRSPIN                                                                  ;;
;; Sets ISR to the value in R5, and spins waiting for interrupts.           ;;
;; Note:  Call this with interrupts disabled! (eg. JSRD)                    ;;
;;                                                                          ;;
;; INPUTS:                                                                  ;;
;; R5 -- ISR function address                                               ;;
;;                                                                          ;;
;; OUTPUTS:                                                                 ;;
;; ISR vector set to value in R5.                                           ;;
;; Does not return.                                                         ;;
;;==========================================================================;;
ISRSPIN PROC
        MVII    #$2F0,  R6
        MOVR    R5,     R0
        MVO     R0,     $100
        SWAP    R0
        MVO     R0,     $101

        EIS
        MOVR    PC,     R5
@@spinloop:
        DECR    R2
        B       NEXTRAND                ; loops to @@spinloop



