; FOO Racer by Ioannis Tsiompikas <nuclear@member.fsf.org>,
;              Georgios Bekiaris <geobekiaris@gmail.com>,
;              Nikolaos Noulas <nicknoulas@hotmail.com>
; designed for the QL200 PIC development board
;
; dipswitch settings:
;    S11/S12/S13 -> 00111110
;    S1 -> 00010001
;    S10 -> 0001
; 
; use RB0/RA0 buttons to control the car and avoid obstacles

#include <p16f877a.inc>
        __CONFIG _DEBUG_OFF&_CP_ALL&_WRT_HALF&_CPD_ON&_LVP_OFF&_BODEN_OFF&_PWRTE_ON&_WDT_OFF&_HS_OSC

tmp equ 0x20                    ; temporary register, used throughout
carpos equ 0x21                 ; car position [0, 2] (lane)
row_idx equ 0x22                ; input to clear_row
bit equ 0x23                    ; used by the set_wbit function
obs_lane equ 0x25               ; obstacle lane [0, 2]
obs_dist equ 0x26               ; obstacle distance from the lowest row
rnd_tbl_idx equ 0x29    ; used by the "random" number lookup routine
clear_color equ 0x2a    ; used by clear_screen

; delay loop variables and constants
count_ms equ 0x40
count_us equ 0x41
val_ms equ .100
val_us equ .249

rnd_tbl equ 0x70                ; random number lookup table base address
rnd_tbl_mask equ 0xf    ; used to mask the lookup index (table has 16 numbers)

; these values control the obstacle motion timer
inp_timer_val_high equ 60h
inp_timer_val_low equ 00h

; ---------------------------------
        
        org 00h
        nop                    ; examples seem to suggest that a nop is needed here
        goto begin     ; skip the interrupt vector and go to the main entry point
        
        ; interrupt vector, route all interrupts to the intr function
        org 04h
        call intr
        retfie
        
        ; entry point
begin
        call init      ; game state initialization

main_loop
        call handle_input      ; poll input and update carpos
        
        ; clear the display
        call clear_screen

        ; draw car and obstacle
        call draw_car
        call draw_obs
        goto main_loop


;********************************************
; ----- game initialization -----
init
        bsf STATUS, RP0                ; switch to bank 1
        ; set porta/b/c bits [1, 5] outputs, bit 0 input
        movlw 001h
        movwf TRISA
        movwf TRISB
        movwf TRISC

        ; set ADCON1 to all digital I/O
        movlw 6
        movwf ADCON1

        bsf PIE1, TMR1IE       ; enable timer1 interrupt
        bcf STATUS, RP0                ; return to bank 0

        bcf T1CON, TMR1CS      ; set Timer 1 mode
        ; set prescaler bits to 1:4
        bsf T1CON, T1CKPS1
        bcf T1CON, T1CKPS0
        ; set timer value
        movlw inp_timer_val_low
        movwf TMR1L
        movlw inp_timer_val_high
        movwf TMR1H
        ; enable timer1
        bsf T1CON, TMR1ON

        ; enable interrupts
        bsf INTCON, GIE
        bsf INTCON, PEIE

        ; set initial car position (center lane)
        movlw 1
        movwf carpos

        ; initialize the random (?!) number lookup table
        ; TODO find a better way to setup the table data
        movlw 2
        movwf rnd_tbl
        movlw 1
        movwf rnd_tbl + 1
        movlw 0
        movwf rnd_tbl + 2
        movlw 1
        movwf rnd_tbl + 3
        movlw 2
        movwf rnd_tbl + 4
        movlw 1
        movwf rnd_tbl + 5
        movlw 1
        movwf rnd_tbl + 6
        movlw 0
        movwf rnd_tbl + 7
        movlw 0
        movwf rnd_tbl + 8
        movlw 1
        movwf rnd_tbl + 9
        movlw 2
        movwf rnd_tbl + 0xa
        movlw 1
        movwf rnd_tbl + 0xb
        movlw 2
        movwf rnd_tbl + 0xc
        movlw 1
        movwf rnd_tbl + 0xd
        movlw 2
        movwf rnd_tbl + 0xe
        movlw 1
        movwf rnd_tbl + 0xf

        bcf clear_color, 0         ; set default clear color to black

        ; spawn the first obstacle
        call spawn_obstacle
        return

;********************************************
; ----- clears the "screen" ----
clear_screen
        movlw 1
        movwf row_idx
_clrscr_loop

        ; if(clear_color == 0) clear_row(); else set_row();
        btfsc clear_color, 0
        goto _clrscr_else
        call clear_row
        goto _clrscr_endif
_clrscr_else
        call set_row
_clrscr_endif

        incf row_idx, 1                ; increment the row counter
        movlw 6
        subwf row_idx, 0       ; if the row counter reaches 6
        btfss STATUS, Z                ; exit the loop
        goto _clrscr_loop
        return

;********************************************
; ----- clears a row of pixels indicated by row_idx -----
clear_row
        movf row_idx, 0               ; say for example that row_idx is 1
        call set_wbit          ; then W = 00000010
        call negw                      ; invert W -> 11111101
        movwf tmp                      ; tmp now contains the mask

        ; now AND all three ports with the mask to unset the requested row
        movf PORTA, 0
        andwf tmp, 0
        movwf PORTA

        movf PORTB, 0
        andwf tmp, 0
        movwf PORTB

        movf PORTC, 0
        andwf tmp, 0
        movwf PORTC
        return


;********************************************
; ----- sets a row of pixels indicated by row_idx -----
set_row
        movf row_idx, 0               ; say for example that row_idx is 1
        call set_wbit          ; then W = 00000010
        movwf tmp                      ; tmp contains that bit pattern ...

        ; ... which is ORed to all three ports in order to set the row bits
        movf PORTA, 0
        iorwf tmp, 0
        movwf PORTA

        movf PORTB, 0
        iorwf tmp, 0
        movwf PORTB

        movf PORTC, 0
        iorwf tmp, 0
        movwf PORTC
        return

;********************************************
; ----- draws the car (?!) -----
draw_car
        ; calculate the address of the corresponding port by adding the
        ; address of PORTA to carpos (PORTA, PORTB and PORTC are located
        ; in consecutve addresses).
        movlw PORTA
        addwf carpos, 0
        movwf FSR              ; move the address to FSR ...
        bsf INDF, 5            ; ... and set the appropriate bit using indirect addressing
        return

; *******************************************
; ----- draws the obstacle -----
draw_obs
        ; convert obstacle distance to row index (row_idx = 5 - obs_dist)
        movlw 5
        movwf row_idx
        movf obs_dist, 0
        subwf row_idx, 1
        
        ; set the 'row_idx'th bit of w
        movf row_idx, 0
        movwf bit
        call set_wbit
        movwf tmp              ; bit pattern with 1 at enemy position

        ; calculate the address of the corresponding port by adding the
        ; address of PORTA to carpos (PORTA, PORTB and PORTC are located
        ; in consecutve addresses).
        movlw PORTA
        addwf obs_lane, 0
        movwf FSR

        movf INDF, 0   ; read back port bits
        iorwf tmp, 0   ; OR with obstacle bit pattern
        movwf INDF             ; write back to the port
        return

;********************************************
; ----- places an obstacle at the top of a random lane -----
spawn_obstacle
        movlw 5                        ; maximum distance
        movwf obs_dist

        call rnd_lane  ; choose a lane at random [0, 2]
        movwf obs_lane
        return


; *******************************************
; ----- on crash do something fancy -----
crash
        ; progressively fill the screen from bottom to top
        movlw 5
        movwf row_idx
_crash_loop1
        call set_row
        call delay
        decfsz row_idx, 1
        goto _crash_loop1

        call delay     ; wait a bit

        ; progressively clear the screen from top to bottom
        movlw 1
        movwf row_idx
_crash_loop2
        call clear_row
        call delay
        incf row_idx, 1
        movlw 7
        subwf row_idx, 0
        btfss STATUS, Z
        goto _crash_loop2
        return


;********************************************
; ----- checks the two buttons and updates car position -----
handle_input
        ; test the left button bit
        btfss PORTA, 0
        goto _hinp_left_pressed

        ; test the right button bit
        btfss PORTB, 0
        goto _hinp_right_pressed

        ; no keypress, keep the car in the middle lane (1)
        movlw 1
        movwf carpos;
        return

        ; left key is pressed, move the car to lane 0
_hinp_left_pressed
        movlw 0
        movwf carpos;
        return

        ; right key is pressed, move the car to lane 2
_hinp_right_pressed
        movlw 2
        movwf carpos;
        return

;********************************************
; ----- sets the appropriate bit specified by W. result goes to W -----
; example: set_wbit(3) = 00001000
set_wbit
        movwf bit
        movlw 1
        movwf tmp
_set_wbit_loop
        rlf tmp, 1
        decfsz bit, 1
        goto _set_wbit_loop
        movf tmp, 0
        return

;********************************************
; ----- complements w (result -> w) ----
negw
        movwf tmp
        movlw 0ffh
        xorwf tmp, 0
        return

; *******************************************
; ---- return (in w) the next value of the random number sequence in rnd_tbl ----
rnd_lane
        ; mask with 0xf to make sure the index lies between [0, 15]
        movlw rnd_tbl_mask
        andwf rnd_tbl_idx, 0
        movwf tmp
        ; add the table base address
        movlw rnd_tbl
        addwf tmp, 0

        ; fetch the random number and increment rnd_tbl_idx
        movwf FSR
        incf rnd_tbl_idx, 1
        movf INDF, 0
        return

;********************************************
; ----- interrupt handler -----
intr
        ; we only handle timer interrupts, if it's not a timer interrupt skip.
        btfss PIR1, TMR1IF
        goto _intr_end

        ; we've got a timer1 interrupt
_intr_from_timer1
        ; if obs_dist is not 0, then we don't need to check for collisions
        ; so we jump to _intr_no_collision
        movf obs_dist, 0
        btfss STATUS, Z
        goto _intr_no_collision
        
        ; distance is zero, check if we're on the same lane
        movf obs_lane, 0
        subwf carpos, 0
        btfss STATUS, Z
        goto _intr_no_col_respawn      ; no collision, just respawn

        call crash             ; collision detected, blow it up!
_intr_no_col_respawn
        ; regardless of collision or no collision, the
        ; obstacle has gone off the screen and must be
        ; respawned
        call spawn_obstacle

_intr_no_collision
        decf obs_dist, 1       ; at each timer interrupt move the obstacle closer

_intr_tmr_reset
        ; reset timer 1 values
        movlw inp_timer_val_low
        movwf TMR1L
        movlw inp_timer_val_high
        movwf TMR1H

        ; clear timer 1 interrupt flag to re-enable timer interrupts
        bcf PIR1, TMR1IF

;       Shared interrupt code
_intr_end
        bcf INTCON, INTF       ; clear interrupt flag to re-enable interrupts
        return


;********************************************
; --------- delay (lifted from example programs) --------
delay
        movlw val_ms
        movwf count_ms
_delay_loop
        call delay_us
        decfsz count_ms, 1
        goto _delay_loop
        return

delay_us
        movlw val_us
        movwf count_us
_delay_us_loop
        nop
        decfsz count_us, 1
        goto _delay_us_loop
        return


        end