;  Tank Battle Game for the XGameStation Micro Edition
;  by Jeff Tranter
;  Copyright (c) 2006 Jeff Tranter 
;
;  This program is free software; you can redistribute it and/or modify
;  it under the terms of the GNU General Public License as published by
;  the Free Software Foundation; either version 2 of the License, or
;  (at your option) any later version.
; 
;  This program is distributed in the hope that it will be useful,
;  but WITHOUT ANY WARRANTY; without even the implied warranty of
;  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;  GNU General Public License for more details.
; 
;  You should have received a copy of the GNU General Public License
;  along with this program; if not, write to the Free Software
;  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
; 
; Tank Battle revision history:
;
; 3 Mar 2006 - version 1.0 - basic game is working


; XGS-ME TILE GRAPHICS ENGINE

; Version 1.3.1
; Alex Varanese

; 1.5.2005

; **** NOTES ******************************************************************

; ---- OVERVIEW ---------------------------------------------------------------

; This is a tile-based graphics system that makes it easy to create complex,
; graphically-detailed games on the XGameStation Micro Edition. Instead of
; directly generating the TV signal, client programs manipulate a framebuffer
; stored in SRAM.
;
; The framebuffer consists of 16x24 tiles. Each tile is 8x8 pixels and has its
; own foreground and background color. The system supports up to 64 unique
; tiles.

; ---- WRITING CLIENT PROGRAMS ------------------------------------------------

; Client programs can be written easily by filling in the empty Game_Init()
; and Game_Update() subroutines. Game_Init() is called once before the TV signal
; is generated, and Game_Update() is called once per frame.
;
; Game_Update() is given a timeslice of about 60,672 clocks, after which the TV
; signal will be corrupted. As long as the subroutine does not exceed this time
; limit, timing issues will be handled automatically and client programs need
; not worry about stalling for the remainder of the timeslice.
;
; The easiest way to draw graphics to the screen is with the following macros:
;
; M_SET_TILE x, y, tile_index, fg_color, bg_color
;
;	Sets a tile at any place on the screen. This macro allows control over
;	the tile's foreground and background colors as well.  specifies
;	the location of the tile to set.  can range from 0-15, and  can
;	range from 0-23. Clipping is automatically performed to the screen
; 	boundaries.
;
; M_FILL_ROW y, tile_index, fg_color, bg_color
;
; 	Fills an entire horizontal row of tiles to the specified tile graphic
; 	and colors.  specifies which screen row to fill (0-23).
;
; M_FILL_SCREEN tile_index, fg_color, bg_color
;
;	Fills the entire screen with the specified tile graphic and colors.
; 	Most useful for laying down a full-screen background pattern or color
;	at the start of the program.
;
; A better way is to create screen-sized maps in program memory which reference
; a TAT (tile attribute table) to save memory by using a limited palette of
; tiles:
;
; M_LOAD_MAP map, tat
;
;	Loads the map located at label , using the tile attribute table
;	located at label . The default TAT is already defined at label
;	. Passing the  label allows multiple TAT's to
; 	be created, allowing for diverse map designs.
;
; Collision detection is also made easy by reading the bitmap index of a given
; tile:
;
; M_GET_TILE_INDEX x, y
;
;	Returns the bitmap index of the tile found at  on the current
; 	screen in . Note that this is the actual index of the bitmap within
;	the tile bitmap definitions, not its index into a TAT.
;
; Lastly, the currently loaded screen can be copied to any other page boundary
; in SRAM. Use this technique to load maps into SRAM before starting your
; program. Note that in this version of the engine, all functions listed above
; operate on the visible screen only.
;
; M_COPY_SCREEN source_hi, source_lo, dest_hi, dest_lo
;
;	Copies a 96-page block (one full screen) from the page pointed to by
;	12-bit page address  to the page pointed to by
; 	12-bit page address . Good for copying screens into
;	other parts of memory as the are loaded at initialization time, then
; 	for copying into the visible buffer (located at page 0) during the
;	frame update.
;
; Once the background is on the screen, a good way to place foreground objects
; over while minimizing disruption of the background is Set_Tile_FG():
;
; M_SET_TILE_FG x, y, tile_index, fg_color
;
;	Works just like Set_Tile() but does not change the background color
;	of the original tile. This can be used to create a fairly convincing
;	transparency effect. Clipping is automatically performed to the screen
; 	boundaries.
;
; Tile bitmaps are stored uncompresed in program memory as 8-byte bitmaps with
; 1-bit per pixel, following the  label. These can be easily edited by
; hand. 64 unique tiles (512 program words) are available.
;
; NOTE: Only the tiles you actually use need program memory. If you only use,
;	for example, 13 out of the 64 tiles, the remaining 408 bytes (13 * 8)
;	can be used for program code or other resources.
;
; The location of the visible buffer can be moved by altering the value of
; . This value can be set easily with the
; SET_VISIBLE_PAGE macro by passing it the high and low bytes of the desired
; base address. This address is a 12-bit PAGE address, not a 16-bit BYTE
; address. Moving the pointer to the visible page enables graphic techniques
; such as page flipping and vertical scrolling.

; The region in which the tile drawing functions draw is called the active page,
; and can be set with the  registers. The
; SET_ACTIVE_PAGE macro can also be used to easily set this value by passing
; the high and low bytes of the address. Unlike the visible page, the active
; page is pointed to with a full 16-bit BYTE address, not a 12-bit PAGE address.

; The visible and active pages of course do not have to be the same, which
; allows one page to be displayed while another is drawn to.

; ---- SPECS SUMMARY ----------------------------------------------------------

; Frame Buffer Dimensions: 16x24
;         Tile Dimensions: 8x8 (1-bit per pixel)
;  Unique Tiles Available: 64
;   Bytes Stored Per Tile: 3 (Tile Index, Foreground & Background Colors)

; ---- SRAM ORGANIZATION ------------------------------------------------------

; TILE FRAMEBUFFER
; - Each row of 16 tiles requires 64 bytes:
;	- 16 tile indices
;	- 16 foreground color values
;	- 16 background color values
;	- 16 + 16 + 16 = 48
; - To make addressing as fast as possible, each page is aligned to 64 bytes.
; - The tile screen is 24 rows tall, meaning there are 16 * 24 = 384 tiles.
; - The total amount of framebuffer memory to represent the 16x24 tile screen
;   is 64 * 24 = 1536 bytes, or 96 pages of SRAM.
; - The base address of the framebuffer is stored in
;   . This the memory that will appear
;   onscreen when the next frame of video is generated.
;    specifies the area to which the tile drawing
;   functions draw. This means you can draw to one area of SRAM while displaying
;   another. 

; **** DIRECTIVES *************************************************************

DEVICE	SX52, OSCHS3, IFBD, XTLBUFD
RESET	Main
FREQ 	80_000_000
LIST    Q=65

; **** CONSTANTS **************************************************************

; **** VIDEO SIGNAL GENERATION

; Timing
CLK_SCALE 		EQU	8				; Clock scale, used for large delays (Clock Frequency / 10,000,000)

; Black/sync levels
BLACK_LEVEL		EQU	6				; Approx. .3V
SYNC			EQU	( 15 * 16 + 0 )
BLACK			EQU	( 15 * 16 + BLACK_LEVEL )
WHITE			EQU	( 15 * 16 + 15 )
OVERSCAN_COLOR		EQU	BLACK
HSYNC_COMP		EQU     0

; Color output
CBURST_ON		EQU	( 0  * 16 + 5 ) 
CBURST_OFF		EQU	( 15 * 16 + 5 )			; Equivalent to BLACK
COLOR_0			EQU	( 0  * 16 + BLACK_LEVEL ) 
COLOR_1			EQU	( 1  * 16 + BLACK_LEVEL ) 
COLOR_2			EQU	( 2  * 16 + BLACK_LEVEL ) 
COLOR_3			EQU	( 3  * 16 + BLACK_LEVEL ) 
COLOR_4			EQU	( 4  * 16 + BLACK_LEVEL ) 
COLOR_5			EQU	( 5  * 16 + BLACK_LEVEL ) 
COLOR_6			EQU	( 6  * 16 + BLACK_LEVEL ) 
COLOR_7			EQU	( 7  * 16 + BLACK_LEVEL ) 
COLOR_8			EQU	( 8  * 16 + BLACK_LEVEL ) 
COLOR_9			EQU	( 9  * 16 + BLACK_LEVEL ) 
COLOR_10		EQU	( 10 * 16 + BLACK_LEVEL ) 
COLOR_11		EQU	( 11 * 16 + BLACK_LEVEL ) 
COLOR_12		EQU	( 12 * 16 + BLACK_LEVEL ) 
COLOR_13		EQU	( 13 * 16 + BLACK_LEVEL ) 
COLOR_14		EQU	( 14 * 16 + BLACK_LEVEL ) 
COLOR_15		EQU	( 15 * 16 + 15 )		; Equivalent to CBURST_OFF and WHITE

; **** SRAM

; SRAM control lines (bit masks and bit indices)

MASK_SRAM_ADDR_CLOCK_ON		EQU	%00010000
MASK_SRAM_ADDR_CLOCK_OFF	EQU	%00000000
MASK_SRAM_ADDR_DATA_1		EQU	%00100000
MASK_SRAM_ADDR_DATA_0		EQU	%00000000
MASK_SRAM_ENABLE		EQU	%00000000
MASK_SRAM_DISABLE		EQU	%01000000
MASK_SRAM_DIR_READ		EQU	%10000000
MASK_SRAM_DIR_WRITE		EQU	%00000000

BIT_SRAM_ADDR_CLOCK		EQU	4
BIT_SRAM_ADDR_DATA		EQU	5
BIT_SRAM_nOE			EQU	6
BIT_SRAM_DIR_nWE		EQU	7

; **** RAM BANKS

BANK_VIDEO		EQU	$10
BANK_TILE_ROW_BUFF	EQU	$20
BANK_SRAM_COPY_BUFF	EQU	$20				; Use tile buffer 0 for SRAM transfers
BANK_STACK		EQU	$30				; Use tile buffer 1 for the 1-level stack
BANK_GAME_0		EQU	$50
BANK_GAME_1		EQU	$60
BANK_GAME_2		EQU	$70

; **** LOGIC

TRUE			EQU	1
FALSE			EQU	0

; **** DIRECTIONS

NORTH			EQU	0
SOUTH			EQU	1
WEST			EQU	2
EAST			EQU	3
NORTHEAST		EQU	4
SOUTHEAST		EQU	5
NORTHWEST		EQU	6
SOUTHWEST		EQU	7

UP  			EQU	0
DOWN			EQU	1
LEFT			EQU	2
RIGHT			EQU	3

; *** TANK GAME

GAME_MODE_INTRO		EQU	1				; game modes
GAME_MODE_PLAYING	EQU	2
GAME_MODE_OVER		EQU	3
TANK_1_COLOR		EQU	COLOR_0+2			; blue
TANK_2_COLOR		EQU	COLOR_8+3			; green
SHELL_COLOR		EQU	COLOR_14+6			; yellow

; **** VARIABLES **************************************************************

; **** Global bank

ORG	$0A

t0			DS 	1				; Temporary registers
t1			DS 	1
t2			DS 	1
t3			DS 	1
t4			DS 	1
t5			DS 	1

; **** Video signal generation

ORG	BANK_VIDEO

color_phase		DS	1				; Color burst phase reference
scan_count		DS 	1				; Scanline counter
fb_ptr_hi		DS	1				; High byte of framebuffer pointer
fb_ptr_lo		DS	1				; Low byte of framebuffer pointer
tile_row		DS	1				; Current row within the current tiles
tile_index		DS	1				; Tile index
fg_color		DS	1				; Foreground color
bg_color		DS	1				; Background color
visible_page_hi		DS	1				; Pointer to visible screen buffer in SRAM
visible_page_lo		DS	1
active_page_hi		DS	1				; Pointer to active page in SRAM
active_page_lo		DS	1

; Temporary video buffer registers
v0			DS	1
v1			DS	1
v2			DS	1
v3			DS	1

; **** Tile data buffers

ORG	BANK_TILE_ROW_BUFF					; Buffers a tile's row data in local RAM

tile_row_buff		DS	48

; **** 1-Level "Stack"

ORG	BANK_STACK

s0			DS 	1				; Stack registers
s1			DS 	1
s2			DS 	1
s3			DS 	1
s4			DS 	1
s5			DS 	1
s6			DS 	1
s7			DS 	1
s8			DS 	1
s9			DS 	1
s10			DS 	1
s11			DS 	1
s12			DS 	1
s13			DS 	1
s14			DS 	1
s15			DS 	1

ORG	BANK_GAME_0

ticks			DS	1			; Tick count (increments once per frame)
game_mode		DS	1			; Mode game is in
player_bank		DS	1			; Holds bank address for current player

ORG	BANK_GAME_1

tank_x			DS	1			; Tank column (0..15)
tank_y			DS	1			; Tank row(0..24)
tank_dir		DS	1			; Tank direction
new_x			DS	1			; Holds temporary new position info
new_y			DS	1
new_dir			DS	1
shell_x			DS	1			; Shell column
shell_y			DS	1			; Shell row
shell_dir		DS	1			; Shell direction (0 if not in flight)
tank_score		DS	1			; Player score, incremented with each kill
tank_exploding		DS	1			; 0=tank is normal, 1 or more=tank exploding (lasts several frames)
temp			DS	1			; general purpose temp
tank_color		DS	1			; color to draw tank
	
ORG	BANK_GAME_2

tank_x			DS	1			; Tank column (0..15)
tank_y			DS	1			; Tank row(0..24)
tank_dir		DS	1			; Tank direction
new_x			DS	1			; Holds temporary new position info
new_y			DS	1
new_dir			DS	1
shell_x			DS	1			; Shell column
shell_y			DS	1			; Shell row
shell_dir		DS	1			; Shell direction (0 if not in flight)
tank_score		DS	1			; Player score, incremented with each kill
tank_exploding		DS	1			; 0=tank is normal, 1 or more=tank exploding (lasts several frames)
temp			DS	1			; general purpose temp
tank_color		DS	1			; color to draw tank

; **** DEBUGGER WATCHES *******************************************************

WATCH t0, 8, UDEC
WATCH t1, 8, UDEC
WATCH t2, 8, UDEC
WATCH t3, 8, UDEC
WATCH t4, 8, UDEC
WATCH t5, 8, UDEC

WATCH v0, 8, UDEC
WATCH v1, 8, UDEC
WATCH v2, 8, UDEC
WATCH v3, 8, UDEC

; **** MACROS *****************************************************************

; *****************************************************************************
; *
; *	NOP Multiples
; *
; *	Wrappers for various "hacks" to simulate multi-clock NOPs in a single
; *	program word (except NOP2).

NOP2	MACRO		; Just two NOPs
	NOP
	NOP
ENDM

NOP3	MACRO		; WARNING: Cannot use on page boundaries
	JMP	$ + 1
ENDM

NOP4	MACRO		; WARNING: Cannot use when W and M are in use
	IREAD
ENDM

; *****************************************************************************
; *
; *	_BANK
; *
; *	Wraps the BANK instruction to ensure bit 7 of FSR is set properly.

_BANK	MACRO	b

	; Set the bank
	BANK	b

	; Set FSR bit 7 if necessary
	IF ( b & %10000000 )
		SETB	FSR.7
	ELSE
		CLRB	FSR.7
	ENDIF

ENDM ; _BANK

; Switch to appropriate bank based on current value of player_bank variable
BANK_PLAYER	MACRO
	_BANK	BANK_GAME_0
	cje	player_bank, #BANK_GAME_1, :pl1
	_BANK	BANK_GAME_2
	jmp	:done
:pl1	_BANK	BANK_GAME_1
:done
ENDM ; BANK_PLAYER

; *****************************************************************************
; *
; *	_MODE
; *
; *	SX52-safe version of the MODE instruction that sets the bit 4 of M in
; *	addition to bits 0-3.

_MODE	MACRO	md

	MOV	W, md
	MOV	M, W

ENDM

; *****************************************************************************
; *
; *	DELAY
; *
; *	Delays by the specified number of clocks. Originally by Andre' LaMothe.

counter		EQU	t0
counter2	EQU	t1

DELAY MACRO clocks

; first compute fractional remainder of 10 and delay
IF (((clocks) // 10) > 0)

; first 3 clock chunks
	REPT (((clocks) // 10)/3)
	JMP $ + 1
	ENDR

; now the remainder if any

	REPT (((clocks) // 10)//3)
	NOP
	ENDR
ENDIF

; next multiples of 100
IF (((clocks) / 100) >= 1)

; delay 100*(clocks/100), loop equals 100, therefore 1*(clocks/100) iterations
	mov counter, #((clocks)/100) 	; (2)
:Loop					

	mov counter2, #24	 	; (2)
:Loop100					
	djnz counter2, :Loop100		; (4/2)

	djnz counter, :Loop		; (4/2)

ENDIF

; last compute whole multiples of 10, and delay
IF (( ((clocks) // 100) / 10) >= 1)

; delay 10*(clocks/10), loop equals 10, therefore (clocks/10) iterations
	mov counter, #( ((clocks) // 100) / 10) 	; (2)
:Loop2					
	jmp $ + 1			; (3)
	jmp $ + 1			; (3)
	djnz counter, :Loop2		; (4/2)

ENDIF

ENDM ; DELAY

; *****************************************************************************
; *
; *	PUSH_GLOBALS
; *
; *	Pushes all globals onto the 1-level stack.

PUSH_GLOBALS MACRO

	_BANK	BANK_STACK
	MOV	s0, t0
	MOV	s1, t1
	MOV	s2, t2
	MOV	s3, t3
	MOV	s4, t4
	MOV	s5, t5

ENDM ; PUSH_GLOBALS

; *****************************************************************************
; *
; *	POP_GLOBALS
; *
; *	Pops all globals off the 1-level stack.

POP_GLOBALS MACRO

	_BANK	BANK_STACK
	MOV	t0, s0
	MOV	t1, s1
	MOV	t2, s2
	MOV	t3, s3
	MOV	t4, s4
	MOV	t5, s5

ENDM ; POP_GLOBALS

; *****************************************************************************
; *
; *	SRAM_OFF
; *
; *	Disables the SRAM.

SRAM_OFF MACRO

	SETB	RC.BIT_SRAM_nOE

ENDM	; SRAM_OFF

; *****************************************************************************
; *
; *	Set & Scroll Visible & Active Pages

SET_VISIBLE_PAGE MACRO p_hi, p_lo

	MOV	visible_page_hi, p_hi
	MOV	visible_page_lo, p_lo

ENDM	; SET_VISIBLE_PAGE

SET_ACTIVE_PAGE MACRO p_hi, p_lo

	MOV	active_page_hi, p_hi
	MOV	active_page_lo, p_lo
	CLC
	RL	active_page_hi
	RL	active_page_lo
	RL	active_page_hi
	RL	active_page_lo
	RL	active_page_hi
	RL	active_page_lo
	RL	active_page_hi
	RL	active_page_lo

ENDM	; SET_ACTIVE_PAGE

INCR_VISIBLE_PAGE MACRO incr

	ADD	visible_page_lo, incr
	ADDB	visible_page_hi, C

ENDM	; INCR_VISIBLE_PAGE

INCR_ACTIVE_PAGE MACRO incr

	ADD	active_page_lo, incr
	ADDB	active_page_hi, C

ENDM	; INCR_ACTIVE_PAGE

; *****************************************************************************
; *
; *	WRAPPER MACROS
; *
; *	Wrap commonly-used subroutines to make calling and argument passing
; *	easier.

M_FILL_SCREEN MACRO _tile_index, _fg_color, _bg_color

	MOV	tile_index, _tile_index
	MOV	fg_color,   _fg_color
	MOV	bg_color,   _bg_color
	CALL	Call_Fill_Screen

ENDM ; M_FILL_SCREEN

; *****************************************************************************

M_FILL_ROW MACRO _y, _tile_index, _fg_color, _bg_color

	MOV	t5, _y
	MOV	tile_index, _tile_index
	MOV	fg_color,   _fg_color
	MOV	bg_color,   _bg_color
	CALL	@Call_Fill_Row

ENDM ; M_FILL_ROW

; *****************************************************************************

M_SET_TILE MACRO _x, _y, _tile_index, _fg_color, _bg_color

	MOV	t4, _x
	MOV	t5, _y
	MOV	tile_index, _tile_index
	MOV	fg_color,   _fg_color
	MOV	bg_color,   _bg_color
	CALL	@Call_Set_Tile

ENDM ; M_SET_TILE

; *****************************************************************************

M_SET_TILE_FG MACRO _x, _y, _tile_index, _fg_color

	MOV	t4, _x
	MOV	t5, _y
	MOV	tile_index, _tile_index
	MOV	fg_color,   _fg_color
	CALL	@Call_Set_Tile_FG

ENDM ; M_SET_TILE_FG

; *****************************************************************************

M_GET_TILE_INDEX MACRO _x, _y

	MOV	t4, _x
	MOV	t5, _y
	CALL	@Call_Get_Tile_Index

ENDM ; M_GET_TILE_INDEX

; *****************************************************************************

M_LOAD_MAP MACRO _map, _tat

	; Set up the map pointer
	MOV	t1, #_map >> 8
	MOV	t0, #_map

	; If the tile attribute table argument was not zero, break it up and
	; pass it. Otherwise, pass the default table.
	MOV	t3, #_tat >> 8
	MOV	t2, #_tat
	
	CALL	@Call_Load_Map

ENDM ; M_LOAD_MAP

; *****************************************************************************

M_COPY_SCREEN MACRO _source_hi, _source_lo, _dest_hi, _dest_lo

	MOV	t1, _source_hi
	MOV	t0, _source_lo
	MOV	t3, _dest_hi
	MOV	t2, _dest_lo
	CALL	@Call_Copy_Screen

ENDM ; M_COPY_SCREEN

	
; *****************************************************************************

; This trick is to prevent involuntary jumps between a page boundary.

M_PAGE MACRO _page

	jmp @page_??_page
IF $ > $??_page
ERROR "Page Spillage!"
ENDIF
	org $??_page
page_??_page

ENDM ; M_PAGE

; **** SUBROUTINE JUMP TABLE **************************************************

ORG	$0

; Video
Call_Init_Scanline	JMP	@Init_Scanline

; SRAM
Call_SRAM_Write		JMP	@SRAM_Write
Call_SRAM_Read		JMP	@SRAM_Read
Call_SRAM_Load_Page_W	JMP	@SRAM_Load_Page_W
Call_SRAM_Load_Page_R	JMP	@SRAM_Load_Page_R

; Joysticks
Call_Read_Joysticks	JMP	@Read_Joysticks

; Tile Engine
Call_Set_Tile		JMP	@Set_Tile
Call_Set_Tile_FG	JMP	@Set_Tile_FG
Call_Get_Tile_Index	JMP	@Get_Tile_Index
Call_Fill_Row		JMP	@Fill_Row
Call_Fill_Screen	JMP	@Fill_Screen
Call_Load_Map		JMP	@Load_Map
Call_Copy_Screen	JMP	@Copy_Screen

; Game
Call_Game_Init		JMP	@Game_Init
Call_Game_Update	JMP	@Game_Update

; **** MAIN *******************************************************************

Main

	; **** INITIALIZATION *************************************************

	; Set RA ports to output
 	_MODE	#$1F
	MOV	RA,  #%00000000
	MOV	!RA, #%00000000
	MOV	RC,  #( MASK_SRAM_ADDR_CLOCK_OFF | MASK_SRAM_ADDR_DATA_0 | MASK_SRAM_DISABLE | MASK_SRAM_DIR_WRITE )
	MOV	!RC, #%00000000

	; Set RE ports to output
	MOV	RE,  #%00000000
	MOV	!RE, #%00000000

	_BANK	BANK_VIDEO

	; Set the default color phase
	MOV	color_phase, #COLOR_14 - 1

	; Set the visible & active pages to the bottom of SRAM
	SET_VISIBLE_PAGE #0, #0
	SET_ACTIVE_PAGE	 #0, #0

	; Initialize the game
	CALL	@Call_Game_Init

	; **** DRAW SCREEN ****************************************************

	_BANK	BANK_VIDEO

:draw_screen

	; Reset the tile row
	CLR	tile_row

	; Reset the row page pointer
	MOV	fb_ptr_lo, visible_page_lo
	MOV	fb_ptr_hi, visible_page_hi

	; **** DRAW ACTIVE SCANLINES ******************************************

	MOV	scan_count, #192
:draw_active_scanline

	; **** INITIALIZE SCANLINE AND READ DATA ******************************

	MOV	RE, #BLACK		; Front porch - 1.5us
	DELAY	( CLK_SCALE * 15 - 2 )

	; **** H-SYNC (376 CLOCKS) ********************************************

	MOV	RE, #SYNC		; (2) Emit sync for H-sync period

	MOV	t0, fb_ptr_lo		; (1)   Copy the framebuffer pointer into 
	MOV	t1, fb_ptr_hi		; (1)
	CALL	@Call_SRAM_Load_Page_R	; (139) Load the proper SRAM page

	; **** Read first half of tile row data

	MOV	t0, #8			; (2) Read 8 tiles
	MOV	t1, tile_row		; (2) The current tile row to read
	CLR	FSR			; (1)
	_BANK	BANK_TILE_ROW_BUFF	; (2) Switch to the tile row data bank	
:read_tile

	; Read the next tile index from SRAM
	MOV	t2, RD			; (2)
	CLC				; (1) Multiply the tile index by 8
	RL	t2			; (1)
	RL	t2			; (1)
	RL	t2			; (1)

	; Read the next 8-bit tile row data
	MOV 	t3, #tiles >> 8		; (2) Build the high nibble of the address in 
	ADDB	t3, RD.5		; (2) Add the high bit of the index for tiles 32-63
	MOV	M, t3			; (1)
	LIST    Q=37
	MOV	W, #tiles		; (1)
	LIST    Q=-37
	ADD	W, t1			; (1)
	ADD	W, t2			; (1) Add the tile index
	IREAD				; (4)
	MOV	IND, W			; (1) Write the row data
	ADD	FSR, #3			; (2) Move to the next character

	INC	RC			; (1) Move to the next SRAM byte

	; Move to the next byte in the SRAM page and loop	
	DJNZ	t0, :read_tile		; (2/4)

	_BANK	BANK_VIDEO		; (2) Restore the video bank

	; Read Loop: 220 Clocks
	
	DELAY	( 376 - 220 - 142 - 2 - 11 ) ; **** Fudge factor (fix later)

	; **** FINISH SCANLINE INITIALIZATION *********************************

	MOV	RE, #BLACK		; Pre-burst - .6us
	DELAY	( CLK_SCALE * 6 - 2 )
	MOV	RE, color_phase		; Color-burst reference - 2.5us
	DELAY	( CLK_SCALE * 25 - 2 )
	MOV	RE, #BLACK		; Post-burst - 1.6us
	DELAY	( CLK_SCALE * 16 - 2 )

	; **** DRAW SCREEN (4208 CLOCKS) **************************************

	; **** DRAW LEFT-HAND BORDER ******************************************

	MOV	RE, #BLACK		; (2)

	; **** Read second half of tile row data

	; Get the current tile row to read
	_BANK	BANK_VIDEO		; (2)
	MOV	t1, tile_row		; (2)

	; Set up the loop
	MOV	t0, #8			; (2) Read 8 tiles
	CLR	FSR			; (1)
	_BANK	BANK_TILE_ROW_BUFF	; (2) Switch to the tile row data bank
	ADD	FSR, #24		; (2) Start on the 8th 3-byte tile structure
:read_tile_2

	; Read the next tile index from SRAM
	MOV	t2, RD			; (2)
	CLC				; (1) Multiply the tile index by 8
	RL	t2			; (1)
	RL	t2			; (1)
	RL	t2			; (1)

	; Read the next 8-bit tile row data
	MOV 	t3, #tiles >> 8		; (2) Build the high nibble of the address in 
	ADDB	t3, RD.5		; (2) Add the high bit of the index for tiles 32-63
	MOV	M, t3			; (1)
	LIST    Q=37
	MOV	W, #tiles		; (1)
	LIST    Q=-37
	ADD	W, t1			; (1)
	ADD	W, t2			; (1) Add the tile index
	IREAD				; (4)
	MOV	IND, W			; (1) Write the row data
	ADD	FSR, #3			; (1) Move to the next character
	INC	RC			; (1) Move to the next byte

	; Move to the next byte in the SRAM page and loop	
	DJNZ	t0, :read_tile_2	; (2/4)
	; Loop total: 208 clocks

	; Read tiles total: 217 clocks

	; **** Read the row's foreground and background colors

	; Load the foreground color page

	; Load the SRAM Page
	_BANK	BANK_VIDEO		; (2)
	MOV	t0, fb_ptr_lo		; (1)   Copy the framebuffer pointer into 
	MOV	t1, fb_ptr_hi		; (1)
	ADD	t0, #1			; (2)   Move to the second page
	ADDB	t1, C			; (2)
	CALL	@Call_SRAM_Load_Page_R	; (139)
	; 146 clocks

	; Copy the page into RAM
	MOV	t0, #4			; (2)
	CLR	FSR			; (1)
	_BANK	BANK_TILE_ROW_BUFF	; (2) Switch to the tile row data bank
	INC	FSR			; (1) Offset by 1 the for foreground color field
:read_fg_color
	REPT	4
	MOV	IND, RD			; (2)
	INC	RC			; (1) Move to the next tile
	ADD	FSR, #3			; (2)
	ENDR
	DJNZ	t0, :read_fg_color	; (2/4)
	; Read FG colors: 100 + 146 = 246 clocks

	; Load the background color page

	; Load the SRAM Page
	_BANK	BANK_VIDEO		; (2)
	MOV	t0, fb_ptr_lo		; (1) Copy the framebuffer pointer into 
	MOV	t1, fb_ptr_hi		; (1)
	ADD	t0, #2			; (2) Move to the third page
	ADDB	t1, C			; (2)
	CALL	@Call_SRAM_Load_Page_R	; (138)

	; Copy the page into RAM
	MOV	t0, #4			; (2)
	CLR	FSR			; (1)
	_BANK	BANK_TILE_ROW_BUFF	; (2) Switch to the tile row data bank
	ADD	FSR, #2			; (2) Offset by 2 the for background color field
:read_bg_color
	REPT	4
	MOV	IND, RD			; (2)
	INC	RC			; (1) Move to the next tile
	ADD	FSR, #3			; (2)
	ENDR
	DJNZ	t0, :read_bg_color	; (2/4)
	; Read FG colors: 101 + 146 = 247 clocks

	SRAM_OFF			; (1) Disable SRAM
	_BANK	BANK_VIDEO		; (2) Restore the video bank

	; Left-hand border processing total: 710 + 3 = 713 clocks

	; **** DRAW TILES *****************************************************

	;  Switch to the tile row buffer bank
	CLR	FSR			; (1)
	_BANK	BANK_TILE_ROW_BUFF	; (2)

	; Draw the next tile row (8 pixels)
	MOV	t2, #16			; (2) Draw scanline in tile row increments (16 tiles, 8 pixels each)
:draw_tile_row

	; **** READ TILE ROW **************************************************
	
	MOV	t5, IND			; (2) Read the tile data
	INC	FSR			; (1) Point to the color value
	MOV	t3, IND			; (2) Read the color data
	INC	FSR			; (1) Point to the next tile/color pair
	MOV	t4, IND			; (2) Read the color data
	INC	FSR			; (1) Point to the next tile/color pair
	; Total: 9 clocks

	; **** DRAW FIRST PIXEL ***********************************************

	; Draw the first pixel outside of the row loop to absorb the timing
	; offset created above when reading the tile row data
	
	; Read the first pixel (bit 7)
	MOV	W, t4			; (1)   Draw with the foreground color
	RL	t5			; (1)   Shift out bit 7
	SNC				; (1/2)
	MOV	W, t3			; (1)   Draw with the background color
	MOV	RE, W			; (1)   Set the output color

	NOP
	NOP
	NOP
	NOP
	NOP
	; Total: 10 clocks

	; **** DRAW REMAINING PIXELS ******************************************

	REPT	7

	NOP4 				; (4)   Leading pixel padding
	NOP4 				; (4)
	NOP				; (1)

	MOV	W, t4			; (1)   Draw with the foreground color
	RL	t5			; (1)   Shift out bit 7
	SNC				; (1/2)
	MOV	W, t3			; (1)   Draw with the background color
	MOV	RE, W			; (1)   Set the output color

	NOP4				; (4)   Trailing pixel padding.
	NOP4 				; (4)
	NOP				; (1)

	ENDR
	; Total: 23 * 7 = 161 clocks

	DJNZ	t2, :draw_tile_row	; (2/4) Draw the next tile row
	; Loop total: 9 + 10 + 161 + 4 = 184 * 16 = 2944 clocks

	; **** DRAW RIGHT-HAND BORDER *****************************************

	MOV	RE, #BLACK
	DELAY	( 588 - 18 - 2 - 18 - 40 )	; Subtract tile update code time below
						; **** Fudge factor (fix later)
						; v1.1 (12.6.2004) -40 clocks for 64 tile access

	; **** UPDATE TILE POINTERS *******************************************

	_BANK	BANK_VIDEO		; (2) Switch back to the video bank

	; Increment the tile base row address
	INC	tile_row		; (1)
	AND	tile_row, #7		; (2)

	; Increment the framebuffer pointer if the tile is complete
	CJNE	tile_row, #0, :skip_new_row	; (4/6)
	NOP2					; (2)
	ADD	fb_ptr_lo, #4			; (2) Move ahead 4 pages
	ADDB	fb_ptr_hi, C			; (2)
	JMP	:new_row_done			; (3)
:skip_new_row
	NOP2					; (2)
	NOP2					; (2)
	JMP	$ + 1				; (3)
:new_row_done

	; Total: 18 clocks

	; **** END OF RASTER, NEXT SCANLINE ***********************************

	_BANK	BANK_VIDEO
	DJNZ	scan_count, :draw_active_scanline

	; **** BOTTOM SCREEN OVERSCAN *****************************************

	MOV	scan_count, #24
:draw_bottom_overscan
	CALL	@Call_Init_Scanline
	MOV 	RE, #OVERSCAN_COLOR
	DELAY	( CLK_SCALE * 526 - 2 - 4 - 2 )
	_BANK	BANK_VIDEO
	DJNZ	scan_count, :draw_bottom_overscan

	; **** VERTICAL SYNC (~60,690 CLOCKS) *********************************

	; Emit sync during V-sync
	MOV	RE, #SYNC

	; Reset RTCC and set prescaler to 1:256, allowing the 8-bit counter to
	; keep [very] approximate time over 60,690 clocks.
	MOV	!OPTION, #%11000111		; Turn off interrupts and set prescaler
	CLR	RTCC

	; Handle game logic
	CALL	@Call_Game_Update
	_BANK	BANK_VIDEO			; Make sure the proper bank is selected upon return

	; Delay for remainder of V-Sync
	CJB	RTCC, #237, $			; Closest multiple of 256 to 60,690 is
						; 60,672 (256 * 237). Loop until the RTCC
						; Reaches this point.
	DELAY	( 60_690 - 60_672 )		; Pad difference between actual V-sync
						; length and prescaler approximation

	; **** TOP SCREEN OVERSCAN ********************************************

	MOV	scan_count, #34
:draw_top_overscan
	CALL	@Call_Init_Scanline
	MOV	RE, #OVERSCAN_COLOR
	DELAY	( CLK_SCALE * 526 - 2 - 4 - 2 )
	_BANK	BANK_VIDEO
	DJNZ	scan_count, :draw_top_overscan

	; **** DRAW NEXT FRAME ************************************************

	JMP	:draw_screen

End_Main

; **** SUBROUTINES ************************************************************

ORG	$200

; *****************************************************************************
; *
; *	Init_Scanline ()
; *
; *	Sets up scanline rasterization.

Init_Scanline

	MOV	RE, #BLACK		; Front porch - 1.5us
	DELAY	( CLK_SCALE * 15 - 2 )
	MOV	RE, #SYNC		; H-sync - 4.7us
	DELAY	( CLK_SCALE * 47 - 2 )
	MOV	RE, #BLACK		; Pre-burst - .6us
	DELAY	( CLK_SCALE * 6 - 2 )
	MOV	RE, color_phase		; Color-burst reference - 2.5us
	DELAY	( CLK_SCALE * 25 - 2 )
	MOV	RE, #BLACK		; Post-burst - 1.6us
	DELAY	( CLK_SCALE * 16 - 2 )
	RETP

End_Init_Scanline

; *****************************************************************************
; *
; *	SRAM_Write ()
; *
; *	Writes a value to a random address in SRAM.
; *
; *	INPUTS
; *		t1:t0 - 16-bit address
; *		t2    - Data

SRAM_Write

	; Work in the low page
	CLRB	RA.7

	; Set lower nibble of  with 4-bit page offset, upper nibble of  with
	; SRAM control lines
	MOV	W, t0
	AND	W, #%00001111
	OR	W, #( MASK_SRAM_ADDR_CLOCK_OFF | MASK_SRAM_ADDR_DATA_0 | MASK_SRAM_DISABLE | MASK_SRAM_DIR_WRITE )
	MOV	RC, W

	; Set data bus to output
	_MODE	#$1F
	MOV	!RD, #%00000000
	MOV	RD, t2			; Put data on bus

	; Shift 12-bit page base address in
	MOV	t2, #3			; Shift 12 bits (loop unrolled 4x)
:shift_addr

	REPT	4
	CLRB	RC.BIT_SRAM_ADDR_CLOCK	; Turn the clock low
	NOP
	CLRB	RC.BIT_SRAM_ADDR_DATA	; Assume address bit is low
	SNB	t1.7			; Set address data bit high if address bit 11 is high
	SETB	RC.BIT_SRAM_ADDR_DATA
	NOP				; Delay for setup time
	SETB	RC.BIT_SRAM_ADDR_CLOCK	; Turn the clock hi
	RL	t0			; Shift the 16-bit address left for the next address bit
	RL	t1
	ENDR

	DJNZ	t2, :shift_addr

	; Write the data
	CLRB	RC.BIT_SRAM_ADDR_CLOCK	; Clear the SRAM clock line
	CLRB	RC.BIT_SRAM_DIR_nWE	; Set SRAM write mode
	CLRB	RC.BIT_SRAM_nOE		; Enable SRAM

	NOP2				; Hold

	SRAM_OFF			; Disable SRAM

	RETP

End_SRAM_Write

; *****************************************************************************
; *
; *	SRAM_Read ()
; *
; *	Reads a value from a random address in SRAM.
; *
; *	INPUTS
; *		t1:t0 - 16-bit address
; *
; *	OUTPUTS
; *		t0    - Data

SRAM_Read

	; Work in the low page
	CLRB	RA.7

	; Set lower nibble of  with 4-bit page offset, upper nibble of  with
	; SRAM control lines
	MOV	W, t0
	AND	W, #%00001111
	OR	W, #( MASK_SRAM_ADDR_CLOCK_OFF | MASK_SRAM_ADDR_DATA_0 | MASK_SRAM_DISABLE | MASK_SRAM_DIR_READ )
	MOV	RC, W

	; Set data bus to input
	_MODE	#$1F
	CLR	RD
	MOV	!RD, #%11111111

	; Shift 12-bit page base address in
	MOV	t2, #3			; Shift 12 bits (unrolled 4x)
:shift_addr

	REPT	4
	CLRB	RC.BIT_SRAM_ADDR_CLOCK	; Turn the clock low
	NOP
	CLRB	RC.BIT_SRAM_ADDR_DATA	; Assume address bit is low
	SNB	t1.7			; Set address data bit high if address bit 11 is high
	SETB	RC.BIT_SRAM_ADDR_DATA
	NOP				; Delay for setup time
	SETB	RC.BIT_SRAM_ADDR_CLOCK	; Turn the clock hi
	RL	t0			; Shift the 16-bit address left for the next address bit
	RL	t1
	ENDR
	
	DJNZ	t2, :shift_addr

	; Read the data
	CLRB	RC.BIT_SRAM_ADDR_CLOCK	; Clear the SRAM clock line
	SETB	RC.BIT_SRAM_DIR_nWE	; Set SRAM read mode
	CLRB	RC.BIT_SRAM_nOE		; Enable SRAM
	NOP2				; Hold

	MOV	t0, RD			; Read the data from the data bus
	NOP2				; Hold

	SRAM_OFF			; Disable SRAM

	RETP

End_SRAM_Read

; *****************************************************************************
; *
; *	SRAM_Load_Page_W ()
; *
; *	Prepares an SRAM page for writing.
; *
; *	INPUTS
; *		t1:t0 - Page base address (lower nibble of  used)

SRAM_Load_Page_W

	; Work in the low page
	CLRB	RA.7			; (1)

	; Set data bus to output
	_MODE	#$1F			; (2)
	MOV	!RD, #%00000000		; (2)

	; Set lower nibble of  with 4-bit page offset, upper nibble of  with
	; SRAM control lines
	CLR	W												   ; (1)
	OR	W, #( MASK_SRAM_ADDR_CLOCK_OFF | MASK_SRAM_ADDR_DATA_0 | MASK_SRAM_DISABLE | MASK_SRAM_DIR_WRITE ) ; (1)
	MOV	RC, W												   ; (1)

	; Shift 12-bit page base address in
	MOV	t2, #2			; (2) Shift 12 bits (loop unrolled 6x)
:shift_addr
	REPT	6
	CLRB	RC.BIT_SRAM_ADDR_CLOCK	; (1)   Turn the clock low
	NOP				; (1)
	CLRB	RC.BIT_SRAM_ADDR_DATA	; (1)   Assume address bit is low
	SNB	t1.3			; (1/2) Set address data bit high if address bit 11 is high
	SETB	RC.BIT_SRAM_ADDR_DATA	; (1)
	NOP				; (1)   Delay for setup time
	SETB	RC.BIT_SRAM_ADDR_CLOCK	; (1)   Turn the clock hi
	RL	t0			; (1)   Shift the 16-bit address left for the next address bit
	RL	t1			; (1)
	ENDR
	DJNZ	t2, :shift_addr		; (2/4)
	; Address shift loop total = ( 36 + 4 ) * 3 = 120

	; Prepare the SRAM for writing
	CLRB	RC.BIT_SRAM_ADDR_CLOCK	; (1) Clear the SRAM clock line
	CLRB	RC.BIT_SRAM_DIR_nWE	; (1) Set SRAM write mode
	CLRB	RC.BIT_SRAM_nOE		; (1) Enable SRAM

	; SRAM Setup total = 120 + 10 = 130 clocks (Plus CALL @ & RETP = 130 + 7 = 137)
	RETP

End_SRAM_Load_Page_W

; *****************************************************************************
; *
; *	SRAM_Load_Page_R ()
; *
; *	Prepares an SRAM page for reading.
; *
; *	INPUTS
; *		t1:t0 - Page base address (lower nibble of  used)

SRAM_Load_Page_R

	; Work in the low page
	CLRB	RA.7			; (1)

	; Set lower nibble of  with 4-bit page offset, upper nibble of  with
	; SRAM control lines
	CLR	W												  ; (1)
	OR	W, #( MASK_SRAM_ADDR_CLOCK_OFF | MASK_SRAM_ADDR_DATA_0 | MASK_SRAM_DISABLE | MASK_SRAM_DIR_READ ) ; (1)
	MOV	RC, W												  ; (1)

	; Set data bus to input
	_MODE	#$1F			; (2)
	CLR	RD			; (1)
	MOV	!RD, #%11111111		; (2)

	; Shift 12-bit page base address in
	MOV	t2, #3			; (2) Shift 12 bits (loop unrolled 4x)
:shift_addr
	REPT	4
	CLRB	RC.BIT_SRAM_ADDR_CLOCK	; (1)   Turn the clock low
	NOP				; (1)
	CLRB	RC.BIT_SRAM_ADDR_DATA	; (1)   Assume address bit is low
	SNB	t1.3			; (1/2) Set address data bit high if address bit 11 is high
	SETB	RC.BIT_SRAM_ADDR_DATA	; (1)
	NOP				; (1)   Delay for setup time
	SETB	RC.BIT_SRAM_ADDR_CLOCK	; (1)   Turn the clock hi
	RL	t0			; (1)   Shift the 16-bit address left for the next address bit
	RL	t1			; (1)
	ENDR
	DJNZ	t2, :shift_addr		; (2/4)
	; Loop total: 120 clocks

	; Prepare the SRAM for reading
	CLRB	RC.BIT_SRAM_ADDR_CLOCK	; (1) Clear the SRAM clock line
	SETB	RC.BIT_SRAM_DIR_nWE	; (1) Set SRAM read mode
	CLRB	RC.BIT_SRAM_nOE		; (1) Enable SRAM

	; SRAM Setup total = 132 clocks (Plus CALL @ & RETP = 132 + 7 = 139)
	RETP

End_SRAM_Load_Page_R

; *****************************************************************************
; *
; *	Read_Joysticks ()
; *
; *	Reads in the status of both joysticks.
; *
; *	INPUTS
; *		t0 - Joystick 0 status
; *		t1 - Joystick 1 status

Read_Joysticks

	JOY_PORT_MASK		equ	%00000111 ; mask for bits used by joystick interface from SX52

	; Set the RA port's pin directions for joystick interfacing
 	_MODE	#$1F
	MOV	RA,  #%00000000
	MOV	!RA, #%11111100

	; step 1: read in port and mask control bits
	mov	W, RA
	and	W, #JOY_PORT_MASK

	; step 2: prepare for read

	mov	RA, W

	; step 3: latch joysticks into shift registers

	clrb	RA.1		; JOY_SH/LD = (0), set parallel load mode

	DELAY(1)

	setb	RA.0		; JOY_CLK   = (1), clock

	DELAY(1)

	clrb	RA.0		; JOY_CLK   = (0), clock

	DELAY(1)

	; step 4: shift data into system, 16-bits

	setb	RA.1		; JOY_SH/LD = (1), set serial shift mode

	; shift 16-bits of address into latch
	mov 	t2, #16	; 16 bits per joystick read

:Read_Joy_Bit_Loop

	rl	t0		; rotate results right thru carry
	rl	t1		; rotate upper results from from including carry

	; read joy in data on port bit first

	sb 	RA.2		; jump over if set
	jmp 	:Joy_Bit_Zero

	; bit set, write 1 to joystick results packet

	setb 	t0.0	; data8[8] = (1)
	jmp	:Joy_Clock_Next_Bit

:Joy_Bit_Zero

	; bit clear, write 0 to joystick results packet

	clrb 	t0.0	; data8[7] = (0)

:Joy_Clock_Next_Bit

	; clock next data bit

	DELAY(1)

	setb	RA.0		; JOY_CLK = (1), clock

	DELAY(1)

	clrb	RA.0		; JOY_CLK = (0), clock

	DELAY(1)
		
	djnz 	t2, :Read_Joy_Bit_Loop

	; reset all joystick control bits
	mov	W, RA
	and	W, #JOY_PORT_MASK
	mov	RA, W

	; Restore the pin directions on RA
 	_MODE	#$1F
	MOV	RA,  #%00000000
	MOV	!RA, #%00000000

	RETP

End_Read_Joysticks

; *****************************************************************************
; *
; *	Set_Tile ()
; *
; *	Sets a specific tile's index and color attributes.
; *
; *	INPUTS
; *		t4	   - X
; *		t5	   - Y
; *		tile_index - Tile index
; *		fg_color   - Foreground color
; *		bg_color   - Background color

Set_Tile

	; **** BOUNDS CHECK ***************************************************

	; Make sure tile is within 0-15, 0-23
	CJA	t4, #15, :no_draw
	CJA	t5, #23, :no_draw

	; **** SET TILE INDEX *************************************************

	; Set  to the value of , then shift left 2 bits to multiply
	; by 4 to get the base address of the page. Finally, shift left 4 more
	; bits to place the value in the page nibble, allowing the page offset
	; nibble to be set next
	MOV	t0, t5
	CLR	t1
	CLC
	REPT	2 + 4
	RL	t0
	RL	t1
	ENDR

	; Add the X location stored in  to the address in 
	ADD	t0, t4
	ADDB	t1, C

	_BANK	BANK_VIDEO

	; Add the active page offset
	ADD	t0, active_page_lo
	ADDB	t1, C
	ADD	t1, active_page_hi

	; Copy the completed address into  for later use
	MOV	t4, t0
	MOV	t5, t1

	; Write the tile index
	MOV	t2, tile_index
	CALL	@Call_SRAM_Write

	; **** SET FOREGROUND COLOR *******************************************

	; Restore the address in 
	MOV	t0, t4
	MOV	t1, t5

	; Shift the address over to the next page
	ADD	t0, #16
	ADDB	t1, C

	; Write the foreground color
	_BANK	BANK_VIDEO
	MOV	t2, fg_color
	CALL	@Call_SRAM_Write

	; **** SET BACKGROUND COLOR *******************************************

	; Restore the address in 
	MOV	t0, t4
	MOV	t1, t5

	; Shift the address over to the next page
	ADD	t0, #32
	ADDB	t1, C

	; Write the foreground color
	_BANK	BANK_VIDEO
	MOV	t2, bg_color
	CALL	@Call_SRAM_Write

:no_draw
	RETP

End_Set_Tile

; *****************************************************************************
; *
; *	Set_Tile_FG ()
; *
; *	Sets a specific tile's index and foreground color, leaving the
; *	background color intact.
; *
; *	INPUTS
; *		t4	   - X
; *		t5	   - Y
; *		tile_index - Tile index
; *		fg_color   - Foreground color

Set_Tile_FG

	; **** BOUNDS CHECK ***************************************************

	; Make sure tile is within 0-15, 0-23
	CJA	t4, #15, :no_draw
	CJA	t5, #23, :no_draw

	; **** SET TILE INDEX *************************************************

	; Set  to the value of , then shift left 2 bits to multiply
	; by 4 to get the base address of the page. Finally, shift left 4 more
	; bits to place the value in the page nibble, allowing the page offset
	; nibble to be set next
	MOV	t0, t5
	CLR	t1
	CLC
	REPT	2 + 4
	RL	t0
	RL	t1
	ENDR

	; Add the X location stored in  to the address in 
	ADD	t0, t4
	ADDB	t1, C

	_BANK	BANK_VIDEO

	; Add the active page offset
	ADD	t0, active_page_lo
	ADDB	t1, C
	ADD	t1, active_page_hi

	; Copy the completed address into  for later use
	MOV	t4, t0
	MOV	t5, t1

	; Write the tile index
	MOV	t2, tile_index
	CALL	@Call_SRAM_Write

	; **** SET FOREGROUND COLOR *******************************************

	; Restore the address in 
	MOV	t0, t4
	MOV	t1, t5

	; Shift the address over to the next page
	ADD	t0, #16
	ADDB	t1, C

	; Write the foreground color
	_BANK	BANK_VIDEO
	MOV	t2, fg_color
	CALL	@Call_SRAM_Write

:no_draw
	RETP

End_Set_Tile_FG

ORG	$400

; *****************************************************************************
; *
; *	Get_Tile_Index ()
; *
; *	Returns the bitmap index of the specified tile.
; *
; *	INPUTS
; *		t4 - X
; *		t5 - Y
; *
; *	OUTPUTS
; *		t0 - Tile index

Get_Tile_Index

	; **** SET TILE INDEX *************************************************

	; Set  to the value of , then shift left 2 bits to multiply
	; by 4 to get the base address of the page. Finally, shift left 4 more
	; bits to place the value in the page nibble, allowing the page offset
	; nibble to be set next
	MOV	t0, t5
	CLR	t1
	CLC
	REPT	2 + 4
	RL	t0
	RL	t1
	ENDR

	; Add the X location stored in  to the address in 
	ADD	t0, t4
	ADDB	t1, C

	; Add the active page offset
	ADD	t0, active_page_lo
	ADDB	t1, C
	ADD	t1, active_page_hi

	; Read the tile index
	CALL	@Call_SRAM_Read

	RETP

End_Get_Tile_Index

; *****************************************************************************
; *
; *	Fill_Row ()
; *
; *	Fills a row with the specified index and foreground/background colors.
; *
; *	INPUTS
; *		t5	   - Row
; *		tile_index - Tile index
; *		fg_color   - Foreground color
; *		bg_color   - Background color

Fill_Row

	; **** Calculate the 12-bit page address of the row in  ********

	; Set  to the value of , then shift left 2 bits to multiply
	; by 4 (each row occupies 4 pages)
	MOV	t3, t5
	CLR	t4
	CLC
	REPT	2
	RL	t3
	RL	t4
	ENDR

	; Copy the active page into  and divide it by 16 to convert it to
	; a page offset
	MOV	t1, active_page_hi
	MOV	t0, active_page_lo
	CLC
	REPT	4
	RR	t1
	RR	t0
	ENDR

	; Add the active page offset to the address in 
	ADD	t3, t0
	ADDB	t4, C
	ADD	t4, t1

	; **** Fill the row's tile index field ********************************
	
	; Load the SRAM page base address
	MOV	t0, t3
	MOV	t1, t4
	CALL	@Call_SRAM_Load_Page_W

	; Get the tile index from the video buffer
	_BANK	BANK_VIDEO
	MOV	t1, tile_index
	
	; Write to each tile
	MOV	t0, #15
:fill_row_tile
	MOV	RD, t1		; Set the tile index
	INC	RC		; Move to the next tile
	DJNZ	t0, :fill_row_tile
	MOV	RD, t1		; Set the tile index

	; **** Fill the row's foreground color field **************************
	
	; Load the SRAM page base address, then move ahead one page
	MOV	t0, t3
	MOV	t1, t4
	ADD	t0, #1
	ADDB	t1, C
	CALL	@Call_SRAM_Load_Page_W

	; Get the foreground color from the video buffer
	MOV	t1, fg_color
	
	; Write to each tile
	MOV	t0, #15
:fill_row_fg
	MOV	RD, t1		; Set the tile index
	INC	RC		; Move to the next tile
	DJNZ	t0, :fill_row_fg
	MOV	RD, t1		; Set the tile index

	; **** Fill the row's background color field **************************
	
	; Load the SRAM page base address, then move ahead two pages
	MOV	t0, t3
	MOV	t1, t4
	ADD	t0, #2
	ADDB	t1, C
	CALL	@Call_SRAM_Load_Page_W

	; Get the background color from the video buffer
	MOV	t1, bg_color
	
	; Write to each tile
	MOV	t0, #15
:fill_row_bg
	MOV	RD, t1		; Set the tile index
	INC	RC		; Move to the next tile
	DJNZ	t0, :fill_row_bg
	MOV	RD, t1		; Set the tile index
	
	SRAM_OFF		; Disable SRAM

	RETP

End_Fill_Row

; *****************************************************************************
; *
; *	Fill_Screen ()
; *
; *	Sets each tile to the specified index and foreground/background colors.
; *
; *	INPUTS
; *		tile_index - Tile index
; *		fg_color   - Foreground color
; *		bg_color   - Background color

Fill_Screen

	; Fill 24 rows
	MOV	t5, #23
:fill_row
	CALL	@Call_Fill_Row 	; Fill the row
	DJNZ	t5, :fill_row
	CALL	@Call_Fill_Row 	; Fill the last row

	RETP

End_Fill_Screen

; *****************************************************************************
; *
; *	Load_Map ()
; *
; *	Decodes and loads an encoded, 16x24 tile map into SRAM from program
; *	memory.
; *
; *	INPUTS
; *		t1:t0 - Pointer to source map in program memory
; *		t3:t2 - Pointer to tile attribute table

Load_Map

	; Save the tile attribute table pointer on the stack
	_BANK	BANK_STACK
	MOV	s7, t3
	MOV	s6, t2

	; Decode 24 rows
	CLR	t5			; Row counter
:decode_row

	; Decode 16 tiles (6 program words)
	CLR	t4			; Tile counter
	CLR	t2			; Nibble counter
:decode_tile

	; **** DECODE THE TILE BASED ON THE NIBBLE COUNTER ********************

	; Read nibble 0?
	CJNE	t2, #0, :read_nibble_1
	MOV	M, t1			; Read the program word
	MOV	W, t0
	IREAD
	MOV	t3, M			; Tile is in 
	JMP	:tile_decoded

	; Read nibble 1?
:read_nibble_1
	CJNE	t2, #1, :read_nibble_2	; Read the program word
	MOV	M, t1
	MOV	W, t0
	IREAD
	MOV	t3, W			; Tile is in high nibble of 
	RR	t3
	RR	t3
	RR	t3
	RR	t3
	AND	t3, #%00001111
	JMP	:tile_decoded

	; Read nibble 2?
:read_nibble_2
	MOV	M, t1
	MOV	W, t0
	IREAD
	MOV	t3, W			; Tile is in low nibble of 
	AND	t3, #%00001111
	ADD	t0, #1			; Read next program word
	ADDB	t1, C

	; Tile decoding finished
:tile_decoded

	; Increment the nibble counter
	INC	t2
	CJB	t2, #3, :skip_wrap_nibble
	CLR	t2
:skip_wrap_nibble

	; **** READ THE TILE'S ATTRIBUTES *************************************

	; Save the globals
	PUSH_GLOBALS

	; Multiply the tile index by 4 to get the tile attribute table offset
	_BANK	BANK_STACK
	CLC
	RL	t3
	RL	t3
	ADD	t3, s6			; Add base address of table

	; Load the tile's index
	MOV	M, s7
	MOV	W, t3			; Tile offset + 0
	IREAD
	MOV	t1, W
	INC	t3			; Move to next field

	; Load the tile's foreground color
	MOV	M, s7
	MOV	W, t3			; Tile offset + 1
	IREAD
	MOV	t2, W
	INC	t3			; Move to next field

	; Load the tile's background color
	MOV	M, s7
	MOV	W, t3			; Tile offset + 2
	IREAD
	MOV	t0, W

	; **** WRITE THE TILE TO THE FRAMEBUFFER ******************************

	_BANK	BANK_VIDEO
	M_SET_TILE	t4, t5, t1, t2, t0

	; Restore the globals
	POP_GLOBALS

	; Decode the next tile
	INC	t4
	CJB	t4, #16, :decode_tile

	; Skip the alignment word
	ADD	t0, #1
	ADDB	t1, C

	; Decode the next row
	INC	t5
	CJB	t5, #24, :decode_row

	RETP

End_Load_Map

; *****************************************************************************
; *
; *	Copy_Screen ()
; *
; *	Copies a 16x24 framebuffer from one location in SRAM to another. Can
; *	be used to quickly restore backgrounds while moving on-screen objects,
; *	or to change backgrounds (to implement screen scrolling, for example).
; *
; *	INPUTS
; *		t1:t0 - 12-bit base page address of source screen
; *		t3:t2 - 12-bit base page address of destination screen

Copy_Screen

	; Copy the screen base addresses onto the stack
	_BANK	BANK_STACK
	MOV	s7, t1
	MOV	s6, t0
	MOV	s9, t3
	MOV	s8, t2

	; Copy 24 rows
	MOV	t0, #24
:copy_row
	
	; Save the row counter (stack bank already selected)
	MOV	s0, t0

	; Copy 3 pages
	MOV	t5, #3
:copy_page
	
	; Load the next source SRAM page to read
	_BANK	BANK_STACK
	MOV	t1, s7			; t1:t0 points to the base of the page
	MOV	t0, s6
	CALL	@Call_SRAM_Load_Page_R

	; Copy the SRAM page to a RAM buffer
	MOV	t0, #4			; Unroll loop 4x
	CLR	FSR
	_BANK	BANK_SRAM_COPY_BUFF
:sram_to_ram

	MOV	IND, RD			; 0
	INC	FSR
	INC	RC
	NOP
	NOP
	MOV	IND, RD			; 1
	INC	FSR
	INC	RC
	NOP
	NOP
	MOV	IND, RD			; 2
	INC	FSR
	INC	RC
	NOP
	NOP
	MOV	IND, RD			; 3
	INC	FSR
	INC	RC

	DJNZ	t0, :sram_to_ram

	SRAM_OFF			; Disable the SRAM
	
	; Load the next destination SRAM page to write
	_BANK	BANK_STACK
	MOV	t1, s9			; t1:t0 points to the base of the page
	MOV	t0, s8
	CALL	@Call_SRAM_Load_Page_W

	; Copy the RAM buffer to the SRAM page
	MOV	t0, #5			; Unroll loop 3x, copy 15 bytes
	CLR	FSR
	_BANK	BANK_SRAM_COPY_BUFF
:ram_to_sram

	MOV	RD, IND			; 0
	INC	FSR
	INC	RC
	NOP
	NOP
	MOV	RD, IND			; 1
	INC	FSR
	INC	RC
	NOP
	NOP
	MOV	RD, IND			; 2
	INC	FSR
	INC	RC

	DJNZ	t0, :ram_to_sram

	; Copy the last byte outside of the loop
	MOV	RD, IND
	NOP
	NOP

	SRAM_OFF			; Disable the SRAM

	; Increment the source and destination addresses
	_BANK	BANK_STACK
	ADD	s6, #1
	ADDB	s7, C
	ADD	s8, #1
	ADDB	s9, C

	; Copy next page
	DJNZ	t5, :copy_page

	; Restore the row counter
	MOV	t0, s0

	; Skip past the alignment page
	_BANK	BANK_STACK
	ADD	s6, #1
	ADDB	s7, C
	ADD	s8, #1
	ADDB	s9, C

	; Copy next row
	DJNZ	t0, :copy_row

	RETP

End_Copy_Screen

ORG	$600

;  Jump table
Call_Initialize_New_Game	JMP	@Initialize_New_Game
Call_Is_Tile_Solid		JMP	@Is_Tile_Solid
Call_Update_X_Y			JMP	@Update_X_Y

; *****************************************************************************
; *
; *	Game_Init ()
; *
; *	Initialize the game. This function is called before TV signal generation
; *	begins, so it has no time restrictions.

Game_Init
	_BANK	BANK_GAME_0
	clr	ticks				; Initialize tick count to zero
	call	@Call_Initialize_New_Game	; Call other routine to do all the work
	_BANK	BANK_VIDEO
	RETP
End_Game_Init

; *****************************************************************************
; *
; *	Game_Update ()
; *
; *	Update the game logic. This function is called once per frame during
; *	the V-blank, allowing for roughly 60,600 clocks of processing. Exceeding
; *	this period by any significant margin will compromise TV signal output.

Game_Update

	
;	MOV	color_phase, #COLOR_0		; uncomment this line if you want different colors

; Increment tick counter	
	_BANK	BANK_GAME_0
	inc	ticks

; Delay game to make it playable by only running every 4 frames
	mov	w, ticks			; get ticks
	and	w, #%00000011			; mask out 2 lsb bits
	jz	:play				; if zero, play game
	_BANK	BANK_VIDEO
	retp					; else return

; Jump to appropriate code based on game mode
:play	_BANK	BANK_GAME_0
	cje	game_mode,#GAME_MODE_INTRO,	@game_intro
	cje	game_mode,#GAME_MODE_PLAYING,	@game_playing
	cje	game_mode,#GAME_MODE_OVER,	@game_over

; Intro
; Animate a shell moving from one tank to another (4,16 to 11,16).
; Get joysticks
; If either fire pressed:
;   draw main screen to remove intro text
;   place tanks
;   display score
;   set game_mode = playing
; return
game_intro
	_BANK	BANK_GAME_0
	mov	t0, ticks			; get time
	_BANK	BANK_GAME_1
	mov	new_x, t0			; take the time value
	rr	new_x				; shift out the 2 bits that we know are zero
	rr	new_x
	and	new_x, #%0000111		; look at 3 low order bits
	add	new_x, #4			; convert from 0-7 to 4-11 (x coord of shell)
	cjne	new_x, #4, :not_4		; first time is special case
	mov	shell_x, #11			; x coord of previous shell to erase
	jmp	:draw
:not_4	mov	shell_x, new_x			; calculate x coord of previous shell to erase
	dec	shell_x				; one less than position of shell
:draw
	mov	t0, new_x			; draw shell
	_BANK	BANK_VIDEO
	M_SET_TILE_FG	t0, #16, #57,  #SHELL_COLOR
	_BANK	BANK_GAME_1
	mov	t0, shell_x			; draw blank to erase old shell
	_BANK	BANK_VIDEO
	M_SET_TILE_FG	t0, #16, #0, #WHITE

:done	call	@Call_Read_Joysticks		; see if either fire button pressed (active low)
	jnb	t0.4, :fire_pressed
	jnb	t1.4, :fire_pressed
	jmp	:leave
:fire_pressed
	_BANK	BANK_VIDEO			; draw map to erase into text
	LIST    Q=37
	M_LOAD_MAP	screen_0, tile_attr_table
	LIST    Q=-37
	_BANK	BANK_GAME_0
	mov	player_bank, #BANK_GAME_1
	BANK_PLAYER
	mov	t0, tank_x			; draw tanks
	mov	t1, tank_y
	_BANK	BANK_VIDEO
	M_SET_TILE_FG	t0 , t1, #39,  #TANK_1_COLOR
	_BANK	BANK_GAME_0
	mov	player_bank, #BANK_GAME_2
	BANK_PLAYER
	mov	t0, tank_x
	mov	t1, tank_y
	_BANK	BANK_VIDEO
	M_SET_TILE_FG	t0, t1,  #43,   #TANK_2_COLOR

	M_SET_TILE_FG	#1,  #23, #26,  #WHITE ; 'P'	; display player scores
	M_SET_TILE_FG	#2,  #23, #2,   #WHITE ; '1'
	M_SET_TILE_FG	#3,  #23, #58,  #WHITE ; ': '
	M_SET_TILE_FG	#4,  #23, #10,  #WHITE ; '9'
	M_SET_TILE_FG	#10, #23, #26,  #WHITE ; 'P'
	M_SET_TILE_FG	#11, #23, #3,   #WHITE ; '2'
	M_SET_TILE_FG	#12, #23, #58,  #WHITE ; ': '
	M_SET_TILE_FG	#13, #23, #10,  #WHITE ; '9'
	_BANK	BANK_GAME_0	;  set game mode to playing
	mov	game_mode, #GAME_MODE_PLAYING
:leave	_BANK	BANK_VIDEO
	retp

game_playing

; Figure out active player (alternates each frame)
	_BANK	BANK_GAME_0
	jb	ticks.2, :pl2			; look at bit 2 of ticks
	mov	player_bank, #BANK_GAME_1	; bank is for player 1
	jmp	:plbnk
:pl2	mov	player_bank, #BANK_GAME_2	; else bank is for player 1
:plbnk	BANK_PLAYER				; set the bank

; Handle exploding tank - lasts 4 frames (1 through 5)
; if tank_exploding != 0
;   increment tank_exploding
;   if tank_exploding == 6
;     tank_exploding = 0
;     draw original tank tile
;   else
;     draw tile for next exploding animation
;   return
	cje	tank_exploding, #FALSE, not_exploding
	mov	t0, tank_x
	mov	t1, tank_y
	mov	t2, #44
	add	t2, tank_exploding
	mov	t3, tank_color
	_BANK	BANK_VIDEO
	M_SET_TILE_FG t0, t1, t2, t3
	BANK_PLAYER
	inc	tank_exploding
	cje	tank_exploding, #6, last_explosion
	retp

last_explosion
	clr	tank_exploding
	mov	t0, tank_x
	mov	t1, tank_y
	mov	t2, tank_dir
	mov	t3, tank_color
	_BANK	BANK_VIDEO
	M_SET_TILE_FG t0, t1, t2, t3
	BANK_PLAYER
	retp

; Handle moving shell and possible hit
; if shell_dir != 0
;   new_x = shell_x
;   new_y = shell_y
;   if shell_dir = north
;     new_y -= 1 
;   if shell_dir = south
;     new_y += 1 
;   etc.. for all 8 directions
not_exploding
	cje	shell_dir, #0, @no_shell
	mov	new_x, shell_x
	mov	new_y, shell_y
	mov	t0, shell_dir
	call	@Call_Update_X_Y
	mov	t4, new_x
	mov	t5, new_y
	_BANK	BANK_VIDEO
	call	@Call_Is_Tile_Solid			; see if shell can be placed there
	BANK_PLAYER
	cje	t0, #TRUE, @hit_something		; jump if not
	mov	t0, shell_x				; erase the old shell
	mov	t1, shell_y
	_BANK	BANK_VIDEO
	M_SET_TILE_FG	t0 , t1, #0,  #WHITE
	BANK_PLAYER
	mov	shell_x, new_x				; update shell variables
	mov	shell_y, new_y
	mov	t0, shell_x				; draw the new shell
	mov	t1, shell_y
	_BANK	BANK_VIDEO
	M_SET_TILE_FG	t0 , t1, #57,  #SHELL_COLOR
	jmp	@no_shell

	M_PAGE 800
		
; If shell went out of bounds, shell is now dead
; If shell hit tank, tank is now dead
; If shell hit another shell, both shells are now dead.
hit_something
	cjb	new_x, #1,  out_of_bounds	; are we out of bounds?
	cja	new_x, #14, out_of_bounds
	cjb	new_y, #1,  out_of_bounds
	cja	new_y, #21, out_of_bounds

	BANK_PLAYER
	mov	t0, new_x
	mov	t1, new_y
	_BANK	BANK_GAME_1	;  did we hit tank 1 ?
	cjne	t0, tank_x, not_tank1
	cjne	t1, tank_y, not_tank1
; Yes, we hit tank 1. Decrement the score for tank 1. Mark tank 1 as exploding. Erase the shell.
	dec	tank_score
	mov	tank_exploding, #TRUE
	jmp	out_of_bounds
	
not_tank1
	BANK_PLAYER
	mov	t0, new_x
	mov	t1, new_y
	_BANK	BANK_GAME_2	;  did we hit tank 2 ?
	LIST    Q=48
	cjne	t0, tank_x, not_tank2
	cjne	t1, tank_y, not_tank2
; Yes, we hit tank 2. Decrement the score for tank 2. Mark tank 2 as exploding. Erase the shell.
	dec	tank_score
	mov	tank_exploding, #TRUE
	LIST    Q=-48

out_of_bounds
	BANK_PLAYER
	mov	t0, shell_x				; erase the old shell
	mov	t1, shell_y
	_BANK	BANK_VIDEO
	M_SET_TILE_FG	t0, t1, #0,  #WHITE
	BANK_PLAYER
	clr	shell_dir				; reset shell variables

not_tank2:

;  did we hit a shell? If so, kill both shells
	BANK_PLAYER
	mov	t0, new_x
	mov	t1, new_y
	_BANK	BANK_VIDEO
	M_GET_TILE_INDEX t0, t1				; see if tile is a shell
	cjne	t0, #57, no_shell
	_BANK	BANK_GAME_1
	clr	shell_dir
	mov	t0, shell_x
	mov	t1, shell_y
	_BANK	BANK_VIDEO
	M_SET_TILE_FG t0, t1, #0,  #WHITE
	_BANK	BANK_GAME_2
	LIST	Q=48
	clr	shell_dir
	mov	t0, shell_x
	mov	t1, shell_y
	LIST	Q=-48
	_BANK	BANK_VIDEO
	M_SET_TILE_FG t0, t1, #0,  #WHITE

no_shell
	call	@Call_Read_Joysticks
	_BANK	BANK_GAME_0
	cje	player_bank, #BANK_GAME_1, :j1	; if player 2, use joystick 2 in t1
	mov	t0, t1
:j1	BANK_PLAYER
	mov	temp, t0			; keep for later
	and	t0, #%00001111			; mask out all except direction bits
	cje	t0, #0, no_update		; no buttons pressed
	mov	new_x, tank_x			; save original position (so we can erase it)
	mov	new_y,	tank_y
	cje	t0, #%00000101, :move_se
	cje	t0, #%00000110, :move_ne
	cje	t0, #%00000111, :move_right
	cje	t0, #%00001001, :move_sw
	cje	t0, #%00001010, :move_nw
	cje	t0, #%00001011, :move_left
	cje	t0, #%00001101, :move_down
	cje	t0, #%00001110, :move_up
	jmp	no_update
:move_se
	inc	new_x
	inc	new_y
	mov	new_dir, #40
	jmp	:update
:move_ne
	inc	new_x
	dec	new_y
	mov	new_dir, #38
	jmp	:update
:move_nw
	dec	new_x
	dec	new_y
	mov	new_dir, #44
	jmp	:update
:move_sw
	dec	new_x
	inc	new_y
	mov	new_dir, #42
	jmp	:update
:move_up
	dec	new_y
	mov	new_dir, #37
	jmp	:update
:move_down
	inc	new_y
	mov	new_dir, #41
	jmp	:update
:move_left
	dec	new_x
	mov	new_dir, #43
	jmp	:update
:move_right
	inc	new_x
	mov	new_dir, #39
	jmp	:update

:update
	mov	t4, new_x
	mov	t5, new_y
	_BANK	BANK_VIDEO
	call	@Call_Is_Tile_Solid
	BANK_PLAYER
	cje	t0, #TRUE, no_update
	mov	t0, tank_x	
	mov	t1, tank_y
	_BANK	BANK_VIDEO
	M_SET_TILE_FG t0, t1, #0, #WHITE
	BANK_PLAYER
	mov	tank_x, new_x
	mov	tank_y, new_y
	mov	tank_dir, new_dir
	mov	t0, tank_x	
	mov	t1, tank_y
	mov	t2, tank_dir
	mov	t3, tank_color
	_BANK	BANK_VIDEO
	M_SET_TILE_FG	t0, t1, t2, t3

no_update

; Handle fire button
; if fire button pressed
;   if shell already moving, do nothing
;   else if shell can move
;     set shell x, y, direction, and set shell moving
;     draw the initial shell
	BANK_PLAYER
	jb	temp.4, no_fire
	cjne	shell_dir, #0, no_fire
	mov	new_x, tank_x
	mov	new_y, tank_y
	mov	t0, tank_dir
	call	@Call_Update_X_Y
	mov	t4, new_x
	mov	t5, new_y
	_BANK	BANK_VIDEO
	call	@Call_Is_Tile_Solid			; see if shell can be placed there
	BANK_PLAYER
	cje	t0, #TRUE, no_fire
	mov	shell_x, new_x				; if so, update shell variables
	mov	shell_y, new_y
	mov	shell_dir, tank_dir
	mov	t0, shell_x				; draw the shell
	mov	t1, shell_y
	_BANK	BANK_VIDEO
	M_SET_TILE_FG	t0 , t1, #57,  #SHELL_COLOR
	BANK_PLAYER

no_fire

; update scores on screen
; if high score reached
;   game_mode = game over
;   return
	_BANK	BANK_GAME_1
	mov	t0, tank_score	;  get the score
	inc	t0		;  tile number is one more than actual number
	_BANK	BANK_VIDEO
	M_SET_TILE_FG	#4,  #23, t0,  #WHITE ; P1 score
	_BANK	BANK_GAME_1
	cje	tank_score, #0, score_zero

	_BANK	BANK_GAME_2
	LIST    Q=48
	mov	t0, tank_score	;  get the score
	LIST    Q=-48
	inc	t0		;  tile number is one more than actual number
	_BANK	BANK_VIDEO
	M_SET_TILE_FG	#13, #23, t0,  #WHITE ; P2 score
	_BANK	BANK_GAME_2
	LIST    Q=48
	cje	tank_score, #0, score_zero
	LIST    Q=-48
	jmp	leave

score_zero
	_BANK	BANK_GAME_0
	mov	game_mode, #GAME_MODE_OVER
leave	_BANK	BANK_VIDEO
	retp

game_over

	M_PAGE a00
		
; if game_mode == game_over
;   display animated destroyed tank
;   Display message:
;     GAME OVER
;   TANK DESTROYED
;     PRESS FIRE
;   get joystick
;   if either fire button pressed
;   game_mode = game_intro
;   return
	_BANK	BANK_VIDEO
	M_SET_TILE_FG	#3,  #7,  #17,  #WHITE	; G
	M_SET_TILE_FG	#4,  #7,  #11,  #WHITE	; A
	M_SET_TILE_FG	#5,  #7,  #23,  #WHITE	; M
	M_SET_TILE_FG	#6,  #7,  #15,  #WHITE	; E

	M_SET_TILE_FG	#8,  #7,  #25,  #WHITE	; O
	M_SET_TILE_FG	#9 , #7,  #32,  #WHITE	; V
	M_SET_TILE_FG	#10, #7,  #15,  #WHITE	; E
	M_SET_TILE_FG	#11, #7,  #28,  #WHITE	; R

; below commented out because game is too big!
;	M_SET_TILE_FG	#1,  #11, #30,  #WHITE	; T
;	M_SET_TILE_FG	#2,  #11, #11,  #WHITE	; A
;	M_SET_TILE_FG	#3,  #11, #24,  #WHITE	; N
;	M_SET_TILE_FG	#4,  #11, #21,  #WHITE	; K

;	M_SET_TILE_FG	#6,  #11, #14,  #WHITE	; D
;	M_SET_TILE_FG	#7,  #11, #15,  #WHITE	; E
;	M_SET_TILE_FG	#8,  #11, #29,  #WHITE	; S
;	M_SET_TILE_FG	#9,  #11, #30,  #WHITE	; T
;	M_SET_TILE_FG	#10, #11, #28,  #WHITE	; R
;	M_SET_TILE_FG	#11, #11, #25,  #WHITE	; O
;	M_SET_TILE_FG	#12, #11, #35,  #WHITE	; Y
;	M_SET_TILE_FG	#13, #11, #15,  #WHITE	; E
;	M_SET_TILE_FG	#14, #11, #14,  #WHITE	; D

	M_SET_TILE_FG	#3,  #20, #26,  #WHITE	; P
	M_SET_TILE_FG	#4,  #20, #28,  #WHITE	; R
	M_SET_TILE_FG	#5,  #20, #15,  #WHITE	; E
	M_SET_TILE_FG	#6,  #20, #29,  #WHITE	; S
	M_SET_TILE_FG	#7,  #20, #29,  #WHITE	; S

	M_SET_TILE_FG	#9,  #20, #16,  #WHITE	; F
	M_SET_TILE_FG	#10, #20, #19,  #WHITE	; I
	M_SET_TILE_FG	#11, #20, #28,  #WHITE	; R
	M_SET_TILE_FG	#12, #20, #15,  #WHITE	; E

	call	@Call_Read_Joysticks		; see if either fire button pressed (active low)
	jnb	t0.4, :fire_pressed1
	jnb	t1.4, :fire_pressed1
	jmp	:leave1
:fire_pressed1
	_BANK BANK_GAME_0
	mov	game_mode, #GAME_MODE_INTRO
	call	@Call_Initialize_New_Game
:leave1	_BANK BANK_VIDEO
	retp
	
End_Game_Update


; *****************************************************************************
; *
; *	Initialize_New_Game()
; *
; *	Initialize variables for a new game and display intro screen.

Initialize_New_Game
	_BANK	BANK_GAME_0
	mov	player_bank, #BANK_GAME_1
	BANK_PLAYER
	mov	tank_x, #2
	mov	tank_y, #11
	mov	tank_dir, #39
	clr	shell_x
	clr	shell_y
	clr	shell_dir
	mov	tank_score, #9
	clr	tank_exploding
	mov	tank_color, #TANK_1_COLOR

	_BANK	BANK_GAME_0
	mov	player_bank, #BANK_GAME_2
	BANK_PLAYER
	mov	tank_x, #12
	mov	tank_y, #13
	mov	tank_dir, #43
	clr	shell_x
	clr	shell_y
	clr	shell_dir
	mov	tank_score, #9
	clr	tank_exploding
	mov	tank_color, #TANK_2_COLOR

; Load the initial map
	_BANK	BANK_VIDEO
	LIST    Q=37
	M_LOAD_MAP screen_0, tile_attr_table
	LIST    Q=-37

; Draw text
	_BANK	BANK_VIDEO
	M_SET_TILE_FG	#2,  #4,  #30,  #WHITE	; T
	M_SET_TILE_FG	#3,  #4,  #11,  #WHITE	; A
	M_SET_TILE_FG	#4,  #4,  #24,  #WHITE	; N
	M_SET_TILE_FG	#5,  #4,  #21,  #WHITE	; K

	M_SET_TILE_FG	#7,  #4,  #12,  #WHITE	; B
	M_SET_TILE_FG	#8,  #4,  #11,  #WHITE	; A
	M_SET_TILE_FG	#9,  #4,  #30,  #WHITE	; T
	M_SET_TILE_FG	#10, #4,  #30,  #WHITE	; T
	M_SET_TILE_FG	#11, #4,  #22,  #WHITE	; L
	M_SET_TILE_FG	#12, #4,  #15,  #WHITE	; E

	M_SET_TILE_FG	#7,  #7,  #12,  #WHITE	; B
	M_SET_TILE_FG	#8,  #7,  #35,  #WHITE	; Y
	
	M_SET_TILE_FG	#2,  #10, #20,  #WHITE	; J
	M_SET_TILE_FG	#3,  #10, #15,  #WHITE	; E
	M_SET_TILE_FG	#4,  #10, #16,  #WHITE	; F
	M_SET_TILE_FG	#5,  #10, #16,  #WHITE	; F

	M_SET_TILE_FG	#7,  #10, #30,  #WHITE	; T
	M_SET_TILE_FG	#8,  #10, #28,  #WHITE	; R
	M_SET_TILE_FG	#9,  #10, #11,  #WHITE	; A
	M_SET_TILE_FG	#10, #10, #24,  #WHITE	; N
	M_SET_TILE_FG	#11, #10, #30,  #WHITE	; T
	M_SET_TILE_FG	#12, #10, #15,  #WHITE	; E
	M_SET_TILE_FG	#13, #10, #28,  #WHITE	; R

	M_SET_TILE_FG	#3 , #16, #39,  #TANK_1_COLOR	; tank
	M_SET_TILE_FG	#12, #16, #43,  #TANK_2_COLOR	; tank

	M_SET_TILE_FG	#3,  #20,  #26, #WHITE	; P
	M_SET_TILE_FG	#4,  #20, #28,  #WHITE	; R
	M_SET_TILE_FG	#5,  #20, #15,  #WHITE	; E
	M_SET_TILE_FG	#6,  #20, #29,  #WHITE	; S
	M_SET_TILE_FG	#7,  #20, #29,  #WHITE	; S

	M_SET_TILE_FG	#9,  #20, #16,  #WHITE	; F	
	M_SET_TILE_FG	#10, #20, #19,  #WHITE	; I
	M_SET_TILE_FG	#11, #20, #28,  #WHITE	; R
	M_SET_TILE_FG	#12, #20, #15,  #WHITE	; E

	_BANK	BANK_GAME_0
	mov	game_mode, #GAME_MODE_INTRO

	_BANK	BANK_VIDEO
	RETP

; *****************************************************************************
; *
; *	Is_Tile_Solid()
; *
; *	Accept x,y in t4,t5
; *     Return TRUE in t0 if wall is solid, FALSE if vacant.

Is_Tile_Solid
	cjb	t4, #1,		:out_of_bounds
	cja	t4, #14,	:out_of_bounds
	cjb	t5, #1,		:out_of_bounds
	cja	t5, #21,	:out_of_bounds
	M_GET_TILE_INDEX t4, t5
	cje	t0, #0, :is_empty
:out_of_bounds
	mov	t0, #TRUE
	retp
:is_empty
	mov	t0, #FALSE
	retp

; *****************************************************************************
; *
; *	Update_X_Y
; *
; *	Accept tank tile number in t0
; *     Return new x and y values based on direction of tile in new_x, new_y
; *	e.g. if tile is 40 (tank facing southeast) then X and Y are incremented by 1.
; *     Does not change values if direction is not valid.
; *	Assumes the bank is set appropriately
; *
Update_X_Y
	cje	t0, #37, u_north
	cje	t0, #38, u_northeast
	cje	t0, #39, u_east
	cje	t0, #40, u_southeast
	cje	t0, #41, u_south
	cje	t0, #42, u_southwest
	cje	t0, #43, u_west
	cje	t0, #44, u_northwest
	retp

u_north	
	dec	new_y
	retp
u_east
	inc	new_x
	retp
u_west
	dec	new_x
	retp
u_south
	inc	new_y
	retp
u_northeast
	inc	new_x
	jmp	u_north
u_southeast
	inc	new_x
	jmp	u_south
u_southwest
	dec	new_x
	jmp	u_south
u_northwest
	dec	new_x
	jmp	u_north
	
; **** SCREEN TILEMAPS ********************************************************

; These maps describe entire 16x24 screens, stored in memory as 6x24 words.
; Since each program word is 12 bits, it can store three 4-bit indices into the
; tile attribute table. The last two nibbles in the last word of each scanline
; (word 5) are not used.

screen_0

; Game border with one blank row at bottom for score

	DW	$155,$555,$555,$555,$555,$200
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$700,$000,$000,$000,$000,$800
	DW	$366,$666,$666,$666,$666,$400
	DW	$000,$000,$000,$000,$000,$000

; **** TILE ATTRIBUTE TABLE ***************************************************

; This table can hold the attributes for 16 tiles and is referenced by the
; Load_Map() subroutine. Each entry is 4 words and follows this structure:
; 
;	WORD	DESCRIPTION
;	---------------------------------
;	0	Tile Index
; 	1	Foreground Color
;	2	Background Color
;	3	Alignment Byte (Reserved)

ORG	$E00 - 64

tile_attr_table

	DW	 0, WHITE, BLACK, 0	; 0 space
	DW	49, WHITE, BLACK, 0	; 1 upper left border
	DW	51, WHITE, BLACK, 0	; 2 upper right border
	DW	54, WHITE, BLACK, 0	; 3 bottom left border
	DW	56, WHITE, BLACK, 0	; 4 bottom right border
	DW	50, WHITE, BLACK, 0	; 5 top border
	DW	55, WHITE, BLACK, 0	; 6 bottom border
	DW	52, WHITE, BLACK, 0	; 7 left border
	DW	53, WHITE, BLACK, 0	; 8 right border
	DW	 0, WHITE, BLACK, 0	; 9
	DW	 0, WHITE, BLACK, 0	; A
	DW	 0, WHITE, BLACK, 0	; B
	DW	 0, WHITE, BLACK, 0	; C
	DW	 0, WHITE, BLACK, 0	; D
	DW	 0, WHITE, BLACK, 0	; E
	DW	 0, WHITE, BLACK, 0	; F

; **** TILE BITMAPS ***********************************************************

; Each tile is 8x8 1-bit pixels and stored in an 8-word block. Each word
; corresponds to a row of 8 pixels, and the upper 4 bits are not used.

ORG	$E00

tiles

; Blank/Space

	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000

; Numbers

	DW	%00111110		; 0
	DW	%01100011
	DW	%01110011
	DW	%01101011
	DW	%01100111
	DW	%01100011
	DW	%00111110
	DW	%00000000

	DW	%00011100		; 1
	DW	%00111100
	DW	%00011100
	DW	%00011100
	DW	%00011100
	DW	%00011100
	DW	%01111111
	DW	%00000000

	DW	%00111110		; 2
	DW	%01100011
	DW	%00000011
	DW	%00001110
	DW	%00111000
	DW	%01100000
	DW	%01111111
	DW	%00000000

	DW	%00111110		; 3
	DW	%01100011
	DW	%00000011
	DW	%00001110
	DW	%00000011
	DW	%01100011
	DW	%00111110
	DW	%00000000

	DW	%00000110		; 4
	DW	%00001110
	DW	%00010110
	DW	%00100110
	DW	%01111111
	DW	%00000110
	DW	%00000110
	DW	%00000000

	DW	%01111111		; 5
	DW	%01100000
	DW	%01100000
	DW	%01111110
	DW	%00000011
	DW	%01100011
	DW	%00111110
	DW	%00000000

	DW	%00111110		; 6
	DW	%01100011
	DW	%01100000
	DW	%01111110
	DW	%01100011
	DW	%01100011
	DW	%00111110
	DW	%00000000

	DW	%01111111		; 7
	DW	%00000011
	DW	%00000110
	DW	%00001100
	DW	%00011000
	DW	%00011000
	DW	%00011000
	DW	%00000000

	DW	%00111110		; 8
	DW	%01100011
	DW	%01100011
	DW	%00111110
	DW	%01100011
	DW	%01100011
	DW	%00111110
	DW	%00000000

	DW	%00111110		; 9
	DW	%01100011
	DW	%01100011
	DW	%00111111
	DW	%00000011
	DW	%01100011
	DW	%00111110
	DW	%00000000

; Letters A though Z

	DW	%00001000		; A
	DW	%00011100
	DW	%00110110
	DW	%01100011
	DW	%01111111
	DW	%01100011
	DW	%01100011
	DW	%00000000

	DW	%01111110		; B
	DW	%01100011
	DW	%01100011
	DW	%01111110
	DW	%01100011
	DW	%01100011
	DW	%01111110
	DW	%00000000

	DW	%00111110		; C
	DW	%01100011
	DW	%01100000
	DW	%01100000
	DW	%01100000
	DW	%01100011
	DW	%00111110
	DW	%00000000

	DW	%01111110		; D
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%01111110
	DW	%00000000

	DW	%01111111		; E
	DW	%01100000
	DW	%01100000
	DW	%01111100
	DW	%01100000
	DW	%01100000
	DW	%01111111
	DW	%00000000

	DW	%01111111		; F
	DW	%01100000
	DW	%01100000
	DW	%01111100
	DW	%01100000
	DW	%01100000
	DW	%01100000
	DW	%00000000

	DW	%00111110		; G
	DW	%01100011
	DW	%01100000
	DW	%01101111
	DW	%01100011
	DW	%01100011
	DW	%00111111
	DW	%00000000

	DW	%01100011		; H
	DW	%01100011
	DW	%01100011
	DW	%01111111
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%00000000

	DW	%01111111		; I
	DW	%00011100
	DW	%00011100
	DW	%00011100
	DW	%00011100
	DW	%00011100
	DW	%01111111
	DW	%00000000

	DW	%00000111		; J
	DW	%00000011
	DW	%00000011
	DW	%00000011
	DW	%00000011
	DW	%01100011
	DW	%00111110
	DW	%00000000

	DW	%01100011		; K
	DW	%01100011
	DW	%01100110
	DW	%01111100
	DW	%01100110
	DW	%01100011
	DW	%01100011
	DW	%00000000

	DW	%01100000		; L
	DW	%01100000
	DW	%01100000
	DW	%01100000
	DW	%01100000
	DW	%01100000
	DW	%01111111
	DW	%00000000

	DW	%01100011		; M
	DW	%01110111
	DW	%01101011
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%00000000

	DW	%01100011		; N
	DW	%01110011
	DW	%01101011
	DW	%01100111
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%00000000

	DW	%00111110		; O
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%00111110
	DW	%00000000

	DW	%01111110		; P
	DW	%01100011
	DW	%01100011
	DW	%01111110
	DW	%01100000
	DW	%01100000
	DW	%01100000
	DW	%00000000

	DW	%00111110		; Q
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%01101011
	DW	%00111110
	DW	%00000111

	DW	%01111110		; R
	DW	%01100011
	DW	%01100011
	DW	%01111110
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%00000000

	DW	%00111110		; S
	DW	%01100011
	DW	%01100000
	DW	%00111110
	DW	%00000011
	DW	%01100011
	DW	%00111110
	DW	%00000000

	DW	%01111111		; T
	DW	%00011100
	DW	%00011100
	DW	%00011100
	DW	%00011100
	DW	%00011100
	DW	%00011100
	DW	%00000000

	DW	%01100011		; U
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%00111110
	DW	%00000000

	DW	%01100011		; V
	DW	%01100011
	DW	%01100011
	DW	%00110110
	DW	%00110110
	DW	%00011100
	DW	%00011100
	DW	%00000000

	DW	%01100011		; W
	DW	%01100011
	DW	%01100011
	DW	%01100011
	DW	%01101011
	DW	%01110111
	DW	%01100011
	DW	%00000000

	DW	%01100011		; X
	DW	%01100011
	DW	%00110110
	DW	%00011100
	DW	%00110110
	DW	%01100011
	DW	%01100011
	DW	%00000000

	DW	%01100011		; Y
	DW	%01100011
	DW	%00110110
	DW	%00011100
	DW	%00011100
	DW	%00011100
	DW	%00011100
	DW	%00000000

	DW	%01111111		; Z
	DW	%00000111
	DW	%00001110
	DW	%00011100
	DW	%00111000
	DW	%01110000
	DW	%01111111
	DW	%00000000

; Tanks - 8 directions

	DW	%00010000
	DW	%00010000
	DW	%00010000
	DW	%01111100
	DW	%01111100
	DW	%01111100
	DW	%01111100
	DW	%01111100

	DW	%00000001
	DW	%00010010
	DW	%00111100
	DW	%01111100
	DW	%11111110
	DW	%01111100
	DW	%00111000
	DW	%00010000

	DW	%00000000
	DW	%11111000
	DW	%11111000
	DW	%11111111
	DW	%11111000
	DW	%11111000
	DW	%00000000
	DW	%00000000

	DW	%00010000
	DW	%00111000
	DW	%01111100
	DW	%11111110
	DW	%01111100
	DW	%00111100
	DW	%00010010
	DW	%00000001

	DW	%01111100
	DW	%01111100
	DW	%01111100
	DW	%01111100
	DW	%01111100
	DW	%00010000
	DW	%00010000
	DW	%00010000

	DW	%00001000
	DW	%00011100
	DW	%00111110
	DW	%01111111
	DW	%01111110
	DW	%00111100
	DW	%01001000
	DW	%10000000

	DW	%00000000
	DW	%00011111
	DW	%00011111
	DW	%11111111
	DW	%00011111
	DW	%00011111
	DW	%00000000
	DW	%00000000

	DW	%10000000
	DW	%01001000
	DW	%00111100
	DW	%01111110
	DW	%01111111
	DW	%00111110
	DW	%00011100
	DW	%00001000

; Exploding tank (4 frames)

	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00011000
	DW	%00011000
	DW	%00000000
	DW	%00000000
	DW	%00000000

	DW	%00000000
	DW	%00000000
	DW	%00111100
	DW	%00111100
	DW	%00111100
	DW	%00111100
	DW	%00000000
	DW	%00000000

	DW	%00000000
	DW	%01111110
	DW	%01111110
	DW	%01111110
	DW	%01111110
	DW	%01111110
	DW	%01111110
	DW	%00000000	

	DW	%11111111
	DW	%11111111
	DW	%11111111
	DW	%11111111
	DW	%11111111
	DW	%11111111
	DW	%11111111
	DW	%11111111
		
; Borders for game frame (4 corners plus 2 vertical and 2 horizontal bars)

	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000001

	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%11111111

	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%10000000

	DW	%00000001
	DW	%00000001
	DW	%00000001
	DW	%00000001
	DW	%00000001
	DW	%00000001
	DW	%00000001
	DW	%00000001

	DW	%10000000
	DW	%10000000
	DW	%10000000
	DW	%10000000
	DW	%10000000
	DW	%10000000
	DW	%10000000
	DW	%10000000

	DW	%00000001
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000

	DW	%11111111
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000

	DW	%10000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000
	DW	%00000000

; Shell/bullet

	DW	%00000000	
	DW	%00000000	
	DW	%00000000	
	DW	%00011000	
	DW	%00011000	
	DW	%00000000	
	DW	%00000000	
	DW	%00000000

;  missing font characters

	DW	%00000000	;  colon
	DW	%00011000	
	DW	%00011000	
	DW	%00000000	
	DW	%00011000	
	DW	%00011000	
	DW	%00000000	
	DW	%00000000	

    Source: geocities.com/jefftranter@rogers.com/xgs

               ( geocities.com/jefftranter@rogers.com)