
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;											;
;	The Great Race							;
;	A car game based on the pic16f648a uC	;
;											;
;	by brad slattery 2008					;
;											;
;	email:	brad@retrobrad.com				;
;	web:	www.retrobrad.com/electronics	;
;											;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


	LIST p=16f648a 				;	tell assembler what chip we are using
	include "P16f648a.inc"		;	include the defaults for the chip
	__config 0x3f18				;	sets the configuration settings (oscillator type etc.)

PC equ 0x02					;	The program counter will be refered to as PC

	cblock 0x20				;	start of general purpose registers - this is where we start defining variable names
		counta				;	used in the delay routines
		countb 				;	used in the delay routines
		pc_track			;	this is our track data program counter
		pc_end_graphics		;	this is our end graphics program counter
		vram_1				;	a video ram location
		vram_2				;	a video ram location
		vram_3				;	a video ram location
		vram_4				;	a video ram location
		vram_5				;	a video ram location
		vram_6				;	a video ram location
		vram_7				;	a video ram location
		vram_8				;	a video ram location
		vtemp_1				;	a temporary video ram location (used when tranfering vram data to the next vram location)
		vtemp_2				;	a temporary video ram location (used when tranfering vram data to the next vram location)
		vtemp_3				;	a temporary video ram location (used when tranfering vram data to the next vram location)
		vtemp_4				;	a temporary video ram location (used when tranfering vram data to the next vram location)
		vtemp_5				;	a temporary video ram location (used when tranfering vram data to the next vram location)
		vtemp_6				;	a temporary video ram location (used when tranfering vram data to the next vram location)
		vtemp_7				;	a temporary video ram location (used when tranfering vram data to the next vram location)
		repeat_frame		;	used to determine how many times we will be repeating each 'frame'
		car_data			;	we need to know where the car is on the screen, this variable holds that data
		button_timer		;	used to help in slowing down the button repeat rate (it prevents the car from moving many times for one button press)
		button_adjust		;	used to help in slowing down the button repeat rate (it prevents the car from moving many times for one button press)
		stop_repeat			;	used to help in slowing down the button repeat rate (it prevents the car from moving many times for one button press)
		level				;	a variable to hold the level data (so we know what level we are on)
		speed				;	we set the initial speed in the setup routine (this determines how fast the car is going)
		end_hold			;	used to prevent us from pushing a button at the end for a specified amount of time
	endc					;	end of general purpose registers

	org 0x0000					;	org sets the origin, 0x0000 for the 16F628,

	movlw h'07'					;	Turn comparators off	
	movwf CMCON 				;	

	bsf STATUS, RP0 			;	select bank 1
	movlw b'00000000' 			;	set PORTB all outputs
	movwf TRISB					;
	movlw b'11110000' 			;	set PORTA first four bits outputs and last four inputs
	movwf TRISA 				;
	bcf STATUS, RP0 			;	select bank 0

	goto setup		; go straight to the setup routine

track_data					;	Here's our track data!
	incf pc_track, f		;	increment pc_track by one and then
	movf pc_track, w		;	move it into our working register THEN
	addwf PC				;	add this number to our program counter
	nop						;	skip one step...
	retlw b'10000001'		;	now each time this routine is called, we will grab the
	retlw b'10000001'		;	next successive byte of data!
	retlw b'11000001'		;	this means the screen will scroll from top to bottom
	retlw b'11000001'
	retlw b'11100001'
	retlw b'11100001'
	retlw b'11110001'
	retlw b'11110001'
	retlw b'11111001'
	retlw b'11110001'
	retlw b'11100001'
	retlw b'11000011'
	retlw b'10000111'
	retlw b'10001111'
	retlw b'10000111'
	retlw b'10000011'
	retlw b'10110001'
	retlw b'10111001'
	retlw b'10111001'
	retlw b'10111001'
	retlw b'10010001'
	retlw b'10000001'
	retlw b'10000001'
	retlw b'11000011'
	retlw b'11100111'
	retlw b'11000011'
	retlw b'10000001'
	retlw b'10011001'
	retlw b'10011001'
	retlw b'10111101'
	retlw b'10111101'
	retlw b'10011001'
	retlw b'10011001'
	retlw b'10000001'
	retlw b'11000011'
	retlw b'11100111'
	retlw b'11000011'
	retlw b'10000001'
	retlw b'10000001'
	retlw b'10010001'
	retlw b'10110001'
	retlw b'10111001'
	retlw b'10111001'
	retlw b'10011001'
	retlw b'10000001'
	retlw b'10000011'
	retlw b'10000111'
	retlw b'10001111'
	retlw b'10000111'
	retlw b'11000001'
	retlw b'11100001'
	retlw b'11110001'
	retlw b'11111001'
	retlw b'11111101'
	retlw b'11111001'
	retlw b'11110001'
	retlw b'10000001'
	retlw b'10000001'
	retlw b'10110001'
	retlw b'10110001'
	retlw b'10110001'
	rlf level, 1			;	now we have made it to the next level
	btfsc level, 7			;	have we reached the end of the game yet? if yes then:
	goto collided			;	goto collided routine (this will end up displaying the smiley face)
	decf speed, 1			;	if not, then decrement our speed variable by one (this speeds up the game)
	movlw h'01'				;	and now reset our pc_track variable so that we can
	movwf pc_track			;	draw the track again from the start
	retlw b'10000001'		;	and return to where we came from with the first part of our track.

end_data				
	incf pc_end_graphics, f			;	increment pc_end_graphics by one and then
	movf pc_end_graphics, w			;	move it into our working register THEN
	addwf PC						;	add this number to our program counter
	nop								;	skip one step...
; one									depending on what we have in our program counter
	retlw b'00011000'				;	will determine which number (or graphic) we
	retlw b'00111000'				;	draw on the screen
	retlw b'01111000'
	retlw b'00011000'
	retlw b'00011000'
	retlw b'00011000'
	retlw b'01111110'
	retlw b'01111110'
; two
	retlw b'00111000'				;	this draws the number 1
	retlw b'01111100'
	retlw b'11000110'
	retlw b'00001100'
	retlw b'00011000'
	retlw b'00110000'
	retlw b'01111110'
	retlw b'11111110'
; three
	retlw b'00111100'				;	this draws the number 3
	retlw b'01111110'
	retlw b'00000110'
	retlw b'00111110'
	retlw b'00111110'
	retlw b'00000110'
	retlw b'01111110'
	retlw b'00111100'
; four
	retlw b'01100110'				;	this draws the number 4
	retlw b'01100110'
	retlw b'01100110'
	retlw b'01100110'
	retlw b'01111110'
	retlw b'00111110'
	retlw b'00000110'
	retlw b'00000110'
; five
	retlw b'01111110'				;	this draws the number 5
	retlw b'01111110'
	retlw b'01100000'
	retlw b'01111100'
	retlw b'01111110'
	retlw b'00000110'
	retlw b'01111110'
	retlw b'01111100'
; six
	retlw b'00111110'				;	this draws the number 6
	retlw b'01111110'
	retlw b'01100000'
	retlw b'01111100'
	retlw b'01111110'
	retlw b'01100110'
	retlw b'01111110'
	retlw b'00111100'
; seven
	retlw b'01111111'				;	this draws the number 7
	retlw b'01111111'
	retlw b'00000011'
	retlw b'00000110'
	retlw b'00001100'
	retlw b'00011000'
	retlw b'00110000'
	retlw b'01100000'
; end
	retlw b'00111100'				; this draws a smiley face to say we have finished!
	retlw b'01000010'
	retlw b'10100101'
	retlw b'10000001'
	retlw b'10100101'
	retlw b'10011001'
	retlw b'01000010'
	retlw b'00111100'

setup						;	Okay, lets get everything setup, ready to play the game!
	movlw b'00001000'		;	setup PORTA so that the 7442 will have its' first ouput enabled
	movwf PORTA				;	and also send out a logic 1 to the red column of led cathodes (so they wont turn on)
	clrf PORTB				;	we dont want any data on the screen just yet - so clear PORTB
	clrf vram_1				;	these next lines clear our video ram.
	clrf vram_2				;	if we didnt clear them, then we would get random
	clrf vram_3				;	dots on our screen at startup and would more than likely crash
	clrf vram_4				;	as soon as we started playing! (experiment with not
	clrf vram_5				;	clearing all these vrams and see what happens!)
	clrf vram_6				;	....
	clrf vram_7				;	....
	clrf vram_8				;	....
	clrf stop_repeat		;	clear all 8 bits in stop repeat (even though we only use two.)
	clrf pc_track			;	clear pc_track (so we start from the top of this data)
	clrf pc_end_graphics	;	clear pc_end_graphics (so we start from the top of this data)
	movlw b'00010000'		;	set the start position of our car
	movwf car_data			;	(almost in the middle of the screen)
	movlw d'08'				;	set our button response delay (the higher the number,
	movwf button_adjust		;	the longer you have to wait to be able to push the button again)
	movlw d'01'				;	setup our level counter (start from level
	movwf level				;	one of course...)
	movlw d'10'				;	setup our game scrolling speed.
	movwf speed				;	(the higher the number, the slower the scroll speed)
	movlw d'50'				;	this prevents us from pushing a button to reset the game straight
	movwf end_hold			;	away - which allows you to see your score beforehand

begin							;	Our main program!
	call display				;	call the display routine
	call fill_vram				;	call the fill video ram routine
	goto begin					;	do it all again!

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;																	;
;	This next routine basically takes care of drawing the actual	;
;	graphics on the screen. You will notice that there are eight 	;
;	"sections" here and they basically look the same except for		;
;	minor differences. Thats because they are all doing the same	;
;	thing - activating a certain row of cathodes and then grabbing	;
;	the anode data in order to turn the leds on. The only			;
;	difference between them is that each section activates a		;
;	different row of cathodes and also grabs it's data to be		;
;	displayed from a different vram location.						;
;																	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

display	
				movf speed, w					;	We need to set the speed that our game is running at,
				movwf repeat_frame				;	so we grab that data from speed and copy it to our frame_rate
loop
				call car						;	Call the car routine (this will draw our car on the screen)
				movlw b'00001000'
				movwf PORTA
				movf vram_1, w					;	Now we grab the first COLUMN of data from vram_1.
				movwf PORTB						;	then we copy it to PORTB
				call delay						;	Call the delay (to hold that one row ON for a split second)
				clrf PORTB
										
				movlw b'00001001'
				movwf PORTA
				movf vram_2, w					;	Now we grab the first COLUMN of data from vram_1.
				movwf PORTB						;	then we copy it to PORTB
				call delay						;	Call the delay (to hold that one row ON for a split second)
				clrf PORTB
										
				movlw b'00001010'
				movwf PORTA
				movf vram_3, w					;	Now we grab the first COLUMN of data from vram_1.
				movwf PORTB						;	then we copy it to PORTB
				call delay						;	Call the delay (to hold that one row ON for a split second)
				clrf PORTB
										
				movlw b'00001011'
				movwf PORTA
				movf vram_4, w					;	Now we grab the first COLUMN of data from vram_1.
				movwf PORTB						;	then we copy it to PORTB
				call delay						;	Call the delay (to hold that one row ON for a split second)
				clrf PORTB
										
				movlw b'00001100'
				movwf PORTA
				movf vram_5, w					;	Now we grab the first COLUMN of data from vram_1.
				movwf PORTB						;	then we copy it to PORTB
				call delay						;	Call the delay (to hold that one row ON for a split second)
				clrf PORTB
										
				movlw b'00001101'
				movwf PORTA
				movf vram_6, w					;	Now we grab the first COLUMN of data from vram_1.
				movwf PORTB						;	then we copy it to PORTB
				call delay						;	Call the delay (to hold that one row ON for a split second)
				clrf PORTB
										
				movlw b'00001110'
				movwf PORTA
				movf vram_7, w					;	Now we grab the first COLUMN of data from vram_1.
				movwf PORTB						;	then we copy it to PORTB
				call delay						;	Call the delay (to hold that one row ON for a split second)
				clrf PORTB
										
				movlw b'00001111'
				movwf PORTA
				movf vram_8, w					;	Now we grab the first COLUMN of data from vram_1.
				movwf PORTB						;	then we copy it to PORTB
				call delay						;	Call the delay (to hold that one row ON for a split second)
				clrf PORTB

					btfsc stop_repeat, 1		;	Check to see if the stop repeat bit 1 has been set,
					call button_time			;	if it has, then call the button_time routine (which will decrement the timer by one)
					call check_collision		;	if it has not been set, then keep going here and call the check_collision routine
	decfsz repeat_frame							;	then decrease repeat_frame by one, 
		goto loop								;	if its not zero then draw it all again!
	return										;	if it is zero, then go back to where we came from...


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;																	;
;	The key point to remember in this check_collision routine,		;
;	is that if we check the Z bit in STATUS, then we are			;
;	checking to see if our answer is ZERO or not. If the answer		;
;	IS zero, then the status bit Z will be set (it will be a 1)		;
;	this tells us 'YES THE ANSWER IS ZERO'. If the Z bit is a '0'	;
;	then it is telling us 'NO, THE ANSWER IS NOT ZERO'				;
;																	;
;	So, in this next bit, we are anding two 8-bit numbers. In this	;
;	case, the answer can either be '0' or a number above zero.		;
;	we don't care exactly what number it comes to, we just want to	;
;	know if the answer is zero or not. (A zero means we have not	;
;	hit anything, while anything above zero means we have hit a		;
;	wall.															;
;																	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

check_collision				;	Here we are going to check if we have hit a wall or not...
	movf car_data, w		;	Copy our car data into the w register,
	andwf vram_8, w			;	then and our car_data with vram_8
	btfss STATUS, Z			;	If the answer is NOT zero (status bit is NOT set) then -	
	goto collided			;	goto collided routine
	return					;	otherwise, just return from where we came from...

collided						;	We come here if we have hit a wall or we complete the game
	clrf PORTB					;	we dont want anything on the screen so clear PORTB
	movlw b'0001000'			;	setup the 7442 so the first column of leds is activated
	movwf PORTA					;	and the red led cathodes are disabled (send a 1 to them)	
	btfsc level, 0				;	check to see what level we are on, then we will add
	movlw d'00'					;	a specified number to the pc_end_graphics variable
	btfsc level, 1				;	then we will end up drawing that specified number
	movlw d'08'					;	on the screen.
	btfsc level, 2				;
	movlw d'16'					;
	btfsc level, 3				;
	movlw d'24'					;
	btfsc level, 4				;
	movlw d'32'					;
	btfsc level, 5				;
	movlw d'40'					;
	btfsc level, 6				;
	movlw d'48'					;
	btfsc level, 7				;
	movlw d'56'					;
	movwf pc_end_graphics		;	
	decfsz end_hold, f			;	check to see if the end_hold will allow us to reset the game
	goto collided_data			;	if not then go straight to collided_data
	movlw d'01'					;	if we can reset it then check both buttons
	movwf end_hold				;	if any button is pressed then reset the game
	btfss PORTA, 7				;	if no button is pressed then just continue on
	goto setup					;	to the collided_data routine
	btfss PORTA, 6				;
	goto setup					;

collided_data			;	this will draw our level number or smiley face!
	call end_data		;	grab 8-bits of data from the data table
	movwf PORTB			;	and then place that data on PORTB
	call delay			;	now hold that data there for a split second
	incf PORTA, f		;	activate the next column of led cathodes
	call end_data		;	grab 8-bits of data from the data table	
	movwf PORTB			;	and then place that data on PORTB
	call delay			;	now hold that data there for a split second
	incf PORTA, f		;	activate the next column of led cathodes
	call end_data		;	grab 8-bits of data from the data table
	movwf PORTB			;	and then place that data on PORTB
	call delay			;	now hold that data there for a split second
	incf PORTA, f		;	activate the next column of led cathodes
	call end_data		;	grab 8-bits of data from the data table
	movwf PORTB			;	and then place that data on PORTB
	call delay			;	now hold that data there for a split second
	incf PORTA, f		;	activate the next column of led cathodes
	call end_data		;	grab 8-bits of data from the data table
	movwf PORTB			;	and then place that data on PORTB
	call delay			;	now hold that data there for a split second
	incf PORTA, f		;	activate the next column of led cathodes
	call end_data		;	grab 8-bits of data from the data table		
	movwf PORTB			;	and then place that data on PORTB
	call delay			;	now hold that data there for a split second
	incf PORTA, f		;	activate the next column of led cathodes
	call end_data		;	grab 8-bits of data from the data table
	movwf PORTB			;	and then place that data on PORTB
	call delay			;	now hold that data there for a split second
	incf PORTA, f		;	activate the next column of led cathodes
	call end_data		;	grab 8-bits of data from the data table
	movwf PORTB			;	and then place that data on PORTB
	call delay			;	now hold that data there for a split second
	goto collided		;	and do it all again!

car							;	This routine draws the car on the screen
	call joystick			;	First of all, call the joystick to see if we're moving.
	bcf PORTA, 3			;	activate the bottom row of red led cathodes (send a 0 to it)
	movf car_data, w		;	Copy the contents of car_data to the w register,
	movwf PORTB				;	then copy this data from w to PORTB. (so we draw the car on the screen)
	call delay				;	call the delay - this will hold that info on the screen for a split second,
	bsf PORTA, 3			;	deactivate the bottom row of red led cathodes (send a 1 to it)
	return					;	now we're finished, return to the main program

joystick					;	this will check our buttons
	btfss PORTA, 7			;	has the left button been pressed? if yes then:
	call go_left			;	call the go_left routine, otherwise continue with the next line
	btfss PORTA, 6			;	has the right button been pressed? if yes then:
	call go_right			;	call the go_right routine, otherwise continue with the next line
	return					;	and finally, return back to the main program!

go_left
	btfsc stop_repeat, 0		;	First we check if the stop_repeat bit is set
	return						;	if it is set then we return to the main program
	rrf car_data, 1				;	If it is not set then the car will move across one space.
	bsf stop_repeat, 1			;	Now that we have moved one space we want to prevent our
	bsf stop_repeat, 0			;	car from moving another space straight away. so we set
	movf button_adjust, w		;	the stop_repeat bits which will remain set untill the
	movwf button_timer			;	specified time period is complete. Without this, the car
	return						;	would move way to fast for us to be able to play the game

go_right
	btfsc stop_repeat, 0		;	First we check if the stop_repeat bit is set
	return						;	if it is set then we return to the main program
	rlf car_data, 1				;	If it is not set then the car will move across one space.
	bsf stop_repeat, 1			;	Now that we have moved one space we want to prevent our
	bsf stop_repeat, 0			;	car from moving another space straight away. so we set
	movf button_adjust, w		;	the stop_repeat bits which will remain set untill the
	movwf button_timer			;	specified time period is complete. Without this, the car
	return						;	would move way to fast for us to be able to play the game

button_time						;	This routine decrements the button_timer by one
	decfsz button_timer, 1		;	(we can change the value of button_timer in the setup routine)
	return						;	If the timer has not yet reached zero, then it returns to the main program
	bcf stop_repeat ,0			;	If the timer has reached zero, then we clear the stop_repeat bits
	bcf stop_repeat, 1			;	this then allows us to press a button again!
	return						;	Now return to where we can from...

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;																	;
;	This next section basically 'fills' the	video ram locations		;
;	with the data from the previous video ram location - This is	;
;	how we get the screen to scroll. It also grabs a new byte of	;
;	data from the track data table to display in video ram 1.		;							
;																	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

fill_vram
		movf vram_1, 0		;	Copy vram_1 into vtemp_1 to use as a backup
		movwf vtemp_1		;	storage location. (just before we overwrite vram_1)
		movf vram_2, 0		;	Copy vram_1 into vtemp_2 to use as a backup
		movwf vtemp_2		;	storage location. (just before we overwrite vram_2)
		movf vram_3, 0		;	Copy vram_1 into vtemp_3 to use as a backup
		movwf vtemp_3		;	storage location. (just before we overwrite vram_3)
		movf vram_4, 0		;	Copy vram_1 into vtemp_4 to use as a backup
		movwf vtemp_4		;	storage location. (just before we overwrite vram_4)
		movf vram_5, 0		;	Copy vram_1 into vtemp_5 to use as a backup
		movwf vtemp_5		;	storage location. (just before we overwrite vram_5)
		movf vram_6, 0		;	Copy vram_1 into vtemp_6 to use as a backup
		movwf vtemp_6		;	storage location. (just before we overwrite vram_6)
		movf vram_7, 0		;	Copy vram_7 into vtemp_7 to use as a backup
		movwf vtemp_7		;	storage location. (just before we overwrite vram_7)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;																	;
;	Did you notice that we don't need to make a backup of vram_8?	;
;	That's because there are only 8 lines on the screen. So after 	;
;	we display the 8th line, it is then shifted out of the screen.	;
;																	;
;	Now that we have backed up our old vram data, we then need to	;
;	copy them to the next successive vram location - this basically	;
;	shifts everything on the screen down one space. It also grabs	;
;	a brand new byte of data and copies it into vram_1 (which		;
;	means it will be displayed at the top of the screen, before		;
;	It gets shifted again...										;
;																	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

		call track_data		;	Call track_data routine and return with a byte of data in the w register
		movwf vram_1		;	then copy the new byte of data to the first vram location
		movf vtemp_1, 0		;	copy what was in vram_1 into vram_2 (remember how we made
		movwf vram_2		;	a backup of vram_1 and called it vtemp_1.)
		movf vtemp_2, 0		;	copy what was in vram_2 into vram_3 (remember how we made
		movwf vram_3		;	a backup of vram_2 and called it vtemp_2.)
		movf vtemp_3, 0		;	copy what was in vram_3 into vram_4 (remember how we made
		movwf vram_4		;	a backup of vram_3 and called it vtemp_3.)
		movf vtemp_4, 0		;	copy what was in vram_4 into vram_5 (remember how we made
		movwf vram_5		;	a backup of vram_4 and called it vtemp_4.)
		movf vtemp_5, 0		;	copy what was in vram_5 into vram_6 (remember how we made
		movwf vram_6		;	a backup of vram_5 and called it vtemp_5.)
		movf vtemp_6, 0		;	copy what was in vram_6 into vram_7 (remember how we made
		movwf vram_7		;	a backup of vram_6 and called it vtemp_6.)
		movf vtemp_7, 0		;	copy what was in vram_7 into vram_8 (remember how we made
		movwf vram_8		;	a backup of vram_7 and called it vtemp_7.)
	return					;	Now that we're all done here, go back to our main program.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;																	;
;	Heres the delay routines!										;
;																	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

delay									;	This first delay is a rather fast one.
			movlw d'03'					;	You can make the delay longer by
			movwf counta				;	increasing the decimal value. Or you
			movwf countb				;	can make the delay shorter by decreasing
again	decfsz counta, 1				;	the decimal value.
			goto again					;
			decfsz countb, 1			;
			goto again					;	once counta and countb have reached zero
	return								;	it will return to the main program


	end									;	That's it!