; 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