; Vole.asm
; by Jason Tranter (2006)
; Please read the attached README.txt for instructions and a short
; description of the game.
;
; 5 Mar 2006 - version 1.0


; 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
LIST    Q=37

; **** 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		EQU	$50

; **** LOGIC

TRUE			EQU	1
FALSE			EQU	0

; **** DIRECTIONS

NORTH			EQU	0
SOUTH			EQU	1
WEST			EQU	2
EAST			EQU	3

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

; **** 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

playerx			DS	1
playery			DS	1
nextx			DS	1
nexty			DS	1
next2x			DS	1
next2y			DS	1
level			DS	1
voles			DS	1	
ticks			DS	1
levelstart		DS	1
					
; **** 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

; *****************************************************************************
; *
; *	_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)
	MOV	W, #tiles		; (1)
	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)
	MOV	W, #tiles		; (1)
	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

; *****************************************************************************
; *
; *	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 
		clr	level		; Set up the intro screen
		clr	ticks		; Clear game counter 
		clr	levelstart	; Set level to be re-initialized 
		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
	_BANK BANK_GAME
	inc ticks		; Increment the game counter
	mov t0, level
	cje t0, #0, level0	; Check what level we are currently on, then
	cje t0, #1, level1	; jump to that level. Level 0 is the intro
	cje t0, #2, level2	; screen, then 1-5 are Levels 1-5 respectively
	cje t0, #3, level3
	cje t0, #4, level4
	cje t0, #5, level5
	cje t0, #6, level6	; Level 6 is the Winning screen
	retp
		
level0	cje levelstart, #0, start0 ; Intro
level6	cje levelstart, #0, start6 ; End screen
	jmp @intro
level1	cje levelstart, #0, start1 ; LVL 1
level2	cje levelstart, #0, start2 ; LVL 2
level3	cje levelstart, #0, start3 ; LVL 3
level4	cje levelstart, #0, start4 ; LVL 4
level5	cje levelstart, #0, start5 ; LVL 5
	jmp @mainloop
	
start6	_BANK BANK_GAME
	mov levelstart, #1
	mov level, #0
	_BANK BANK_VIDEO
	M_LOAD_MAP screen_6, tile_attr_table ; Load the winning game display
	retp

start0	mov levelstart, #1 	; Set level to "initialized"
	_BANK	BANK_VIDEO
	M_LOAD_MAP screen_0, tile_attr_table ; Load the intro map
	retp
	
intro	_BANK BANK_GAME
	CALL @Call_Read_Joysticks ; Get joystick input
	jnb t0.4, pressed
	mov t0, ticks 
	and t0, #%00111111
	cje t0, #%00000010, draw
	cje t0, #%00100010, erase
	retp

draw	_BANK BANK_VIDEO
	M_SET_TILE_FG	#6,  #18, #19,	#WHITE	; S
	M_SET_TILE_FG	#7,  #18, #20,	#WHITE	; T
	M_SET_TILE_FG	#8,  #18,  #1,	#WHITE	; A
	M_SET_TILE_FG	#9,  #18, #18,  #WHITE	; R
	M_SET_TILE_FG	#10, #18, #20,  #WHITE	; T
	retp
	
erase	_BANK BANK_VIDEO
	M_SET_TILE_FG	#6,  #18, #0,	#WHITE ; erase the text
	M_SET_TILE_FG	#7,  #18, #0,	#WHITE
	M_SET_TILE_FG	#8,  #18, #0 ,  #WHITE
	M_SET_TILE_FG	#9,  #18, #0,   #WHITE
	M_SET_TILE_FG	#10, #18, #0,   #WHITE
	retp

start1	_BANK BANK_GAME				; This loads level one into memory
	mov levelstart, #1
	mov playerx, #2
	mov playery, #2
	mov voles, #9
	_BANK BANK_VIDEO
	M_LOAD_MAP screen_1, tile_attr_table
	retp
	
start2	_BANK BANK_GAME				; This loads level 2 into memory
	mov levelstart, #1
	mov playerx, #2
	mov playery, #3
	mov voles, #5
	_BANK BANK_VIDEO
	M_LOAD_MAP screen_2, tile_attr_table	
	retp

start3	_BANK BANK_GAME
	mov levelstart, #1
	mov playerx, #7
	mov playery, #10
	mov voles, #6
	_BANK BANK_VIDEO
	M_LOAD_MAP screen_3, tile_attr_table	; This loads level 3 into memory
	retp

start4	_BANK BANK_GAME
	mov levelstart, #1
	mov playerx, #7
	mov playery, #9
	mov voles, #5
	_BANK BANK_VIDEO
	M_LOAD_MAP screen_4, tile_attr_table	; This loads level 4 into memory
	retp

start5	_BANK BANK_GAME
	mov levelstart, #1
	mov playerx, #5
	mov playery, #8
	mov voles, #1
	_BANK BANK_VIDEO
	M_LOAD_MAP screen_5, tile_attr_table	; This loads level 5 into memory
	retp
	
pressed	_BANK BANK_GAME
	inc level				; Go to next level and create it
initlvl	clr levelstart
	retp
	
mainloop	; *********************************** MAIN GAME LOOP ***************************

	_BANK BANK_GAME
	mov t0, voles
	cje t0, #0, pressed 	; If all the voles are rescued, jump to next lvl
	mov t0, ticks
	and t0, #%00000111
	cjne t0, #0, donemov1
	mov nextx, playerx
	mov nexty, playery
	mov next2x, playerx
	mov next2y, playery
	CALL @Call_Read_Joysticks ; Get joystick input 
	and t0, #%00011111
	cje t0, #%00001111, initlvl	; Re-initialize the level if player presses the button
	cje t0, #%00011110, up2		; up pressed
	cje t0, #%00011101, down2	; down pressed
	cje t0, #%00011011, left2	; left pressed
	cje t0, #%00010111, right2	; right pressed
donemov1
	jmp @donemov			; nothing pressed (don't move)

up2	dec nexty		;  UP
	dec next2y
	dec next2y
	jmp move2
			
down2	inc nexty		;  DOWN
	inc next2y
	inc next2y
	jmp move2
	
left2	dec nextx		;  LEFT
	dec next2x
	dec next2x
	jmp move2
	
right2	inc nextx		;  RIGHT
	inc next2x
	inc next2x
	jmp move2

move2
	M_PAGE 800		; @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
	
	mov t4, next2x
	mov t5, next2y
	_BANK BANK_VIDEO
	M_GET_TILE_INDEX t4, t5 ; Check spot 2 spaces from player
	_BANK BANK_GAME 
	cje t0, #32, groundy	; if spot 2 spaces in front of player is ground
	jmp @groundn
	
groundy mov t4, nextx
	mov t5, nexty
	_BANK BANK_VIDEO
	M_GET_TILE_INDEX t4, t5
	_BANK BANK_GAME
	cje t0, #32, pground	; The spot 1 space in front is ground
	cje t0, #27, pvole	; The spot 1 space in front is a vole
	cje t0, #29, pblock	; The spot 1 space in front is a moveable block
	cje t0, #28, ptele	; The spot 1 space in front is a telepad
	jmp @donemov	

groundn	cje t0, #28, tele	; If spot 2 away is a teleporter, we can push some stuff into it
	mov t4, nextx
	mov t5, nexty
	_BANK BANK_VIDEO
	M_GET_TILE_INDEX t4, t5
	_BANK BANK_GAME
	cje t0, #32, walk	; if spot 1 away is ground then move
	cje t0, #28, pusht	; if spot 1 away is tele, we can push it into some stuff
	_BANK BANK_VIDEO
	jmp @donemov

pusht
	_BANK BANK_GAME
	mov t4, next2x
	mov t5, next2y
	_BANK BANK_VIDEO
	M_GET_TILE_INDEX t4, t5
	_BANK BANK_GAME
	cje t0, #27, tvole
	cje t0, #29, tblock
	jmp @donemov

tvole	dec voles		; Pushing a vole into the telepad
tblock 	_BANK BANK_GAME
	mov t1, next2x
	mov t2, next2y
	_BANK BANK_VIDEO
	M_SET_TILE_FG	t1,  t2, #28 , #WHITE ;  spot 2 away becomes telepad
	_BANK BANK_GAME
	mov t1, nextx
	mov t2, nexty
	_BANK BANK_VIDEO
	M_SET_TILE_FG	t1,  t2, #31, #WHITE ; next spot becomes player
	_BANK BANK_GAME
	mov t1, playerx
	mov t2, playery
	mov playerx, nextx
	mov playery, nexty
	_BANK BANK_VIDEO
	M_SET_TILE_FG	t1,  t2, #32, #BLACK+1 ; spot player was in becomes ground
	jmp @donemov
	
walk	mov t1, nextx
	mov t2, nexty
	_BANK BANK_VIDEO
	M_SET_TILE_FG	t1,  t2, #31, #WHITE ; next spot becomes player
	_BANK BANK_GAME
	mov t1, playerx
	mov t2, playery
	mov playerx, nextx
	mov playery, nexty
	_BANK BANK_VIDEO
	M_SET_TILE_FG	t1,  t2, #32, #BLACK+1 ; spot player was in becomes ground
	jmp @donemov
	
pground	mov t3, #BLACK+1 	; Moving on ground (takes 0 energy)
	jmp push
pblock	mov t3, #COLOR_8+5	; Pushing a block (takes 1 energy)
	jmp push
ptele	mov t3, #WHITE		; Pushing a telepad (takes 1 energy)
	jmp push
pvole	mov t3, #COLOR_7+3	; Pushing a vole (takes 1 energy)
	jmp push
tele	mov t4, nextx
	mov t5, nexty
	_BANK BANK_VIDEO
	M_GET_TILE_INDEX t4, t5
	_BANK BANK_GAME
	cje t0, #27, volep
	cje t0, #29, blockp
	cje t0, #32, groundp
	jmp @donemov
volep	dec voles		; pushing a vole into the teleporter (1)
blockp	nop			; pushing a block into the teleporter (1)
groundp	nop			; moving on ground (0)
	mov t3, #WHITE		; Pushing something into the teleporter
	mov t0, #28
	
push	_BANK BANK_GAME 
	mov t1, next2x
	mov t2, next2y
	_BANK BANK_VIDEO
	M_SET_TILE_FG	t1,  t2, t0 , t3 ;  spot 2 away becomes whatever the player pushed
	_BANK BANK_GAME
	mov t1, nextx
	mov t2, nexty
	_BANK BANK_VIDEO
	M_SET_TILE_FG	t1,  t2, #31, #WHITE ; next spot becomes player
	_BANK BANK_GAME
	mov t1, playerx
	mov t2, playery
	mov playerx, nextx
	mov playery, nexty
	_BANK BANK_VIDEO
	M_SET_TILE_FG	t1,  t2, #32, #BLACK+1 ; spot player was in becomes ground

donemov
	_BANK BANK_GAME
	mov t0, level
	add t0, #33
	_BANK BANK_VIDEO
	M_SET_TILE_FG	#6,  #23, t0, #WHITE ; level
	
	_BANK BANK_GAME
	mov t0, voles
	add t0, #33
	_BANK BANK_VIDEO
	M_SET_TILE_FG	#15,  #23, t0, #WHITE ; voles left
	retp
	
End_Game_Update

; **** 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

	DW	$511, $000, $000, $000, $011, $500 ; Intro Screen
	DW	$100, $000, $000, $000, $000, $100
	DW	$100, $000, $000, $000, $000, $100
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$060, $600, $600, $600, $666, $000
	DW	$060, $606, $060, $600, $600, $000
	DW	$060, $606, $060, $600, $666, $000
	DW	$060, $606, $060, $600, $600, $000
	DW	$006, $000, $600, $660, $666, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$100, $000, $000, $000, $000, $100
	DW	$100, $000, $000, $000, $000, $100
	DW	$511, $000, $000, $000, $011, $500

screen_1
	
	DW	$111, $111, $111, $111, $111, $100 ; Level:	 1
	DW	$122, $222, $222, $222, $224, $100 ; Starting Location:	2,2
	DW	$127, $223, $223, $322, $625, $100 ; Voles:	9
	DW	$122, $222, $222, $222, $226, $100
	DW	$144, $444, $442, $222, $222, $100
	DW	$164, $222, $244, $442, $444, $100
	DW	$124, $222, $243, $222, $222, $100
	DW	$154, $222, $222, $222, $222, $100
	DW	$122, $222, $342, $222, $225, $100
	DW	$122, $222, $242, $226, $222, $100
	DW	$144, $244, $444, $444, $444, $100
	DW	$122, $222, $242, $223, $226, $100
	DW	$144, $622, $242, $632, $222, $100
	DW	$153, $222, $242, $232, $222, $100
	DW	$144, $222, $242, $232, $252, $100
	DW	$122, $262, $244, $444, $443, $100
	DW	$122, $222, $242, $222, $222, $100
	DW	$122, $222, $242, $222, $422, $100
	DW	$122, $522, $222, $332, $622, $100
	DW	$111, $111, $111, $111, $111, $100
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$AB8, $BA0, $000, $89A, $BC0, $000
	
screen_2

	DW	$111, $111, $111, $111, $111, $100 ;	Level: 2
	DW	$122, $222, $222, $222, $222, $100 ;	Starting location: 2,3
	DW	$122, $224, $444, $444, $222, $100 ;	Voles: 5
	DW	$127, $222, $666, $664, $222, $100
	DW	$122, $224, $444, $444, $222, $100
	DW	$122, $222, $222, $222, $222, $100
	DW	$122, $222, $222, $222, $222, $100	
	DW	$122, $222, $222, $222, $222, $100
	DW	$122, $222, $444, $444, $444, $100
	DW	$122, $222, $423, $222, $222, $100
	DW	$122, $222, $224, $225, $222, $100
	DW	$143, $444, $444, $224, $322, $100
	DW	$123, $222, $242, $224, $342, $100
	DW	$122, $222, $244, $444, $223, $100
	DW	$122, $222, $432, $232, $322, $100
	DW	$144, $233, $222, $342, $223, $100
	DW	$122, $222, $444, $344, $422, $100
	DW	$122, $222, $222, $222, $223, $100
	DW	$124, $422, $222, $222, $232, $100
	DW	$111, $111, $111, $111, $111, $100
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$AB8, $BA0, $000, $89A, $BC0, $000

screen_3
	
	DW	$111, $111, $111, $111, $111, $100
	DW	$122, $322, $222, $223, $222, $100
	DW	$122, $322, $252, $223, $222, $100 ;6 voles
	DW	$122, $322, $424, $223, $222, $100 ;7,10
	DW	$122, $324, $242, $423, $222, $100
	DW	$122, $222, $222, $222, $222, $100
	DW	$122, $222, $222, $222, $222, $100
	DW	$122, $444, $444, $444, $444, $100
	DW	$122, $223, $222, $322, $222, $100
	DW	$122, $422, $232, $222, $422, $100
	DW	$122, $432, $373, $232, $422, $100
	DW	$122, $422, $232, $222, $422, $100
	DW	$122, $223, $222, $322, $222, $100
	DW	$144, $444, $444, $444, $422, $100
	DW	$122, $222, $222, $222, $222, $100
	DW	$122, $426, $262, $624, $222, $100
	DW	$122, $422, $626, $224, $222, $100
	DW	$122, $422, $262, $224, $222, $100
	DW	$122, $422, $222, $224, $222, $100
	DW	$111, $111, $111, $111, $111, $100
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$AB8, $BA0, $000, $89A, $BC0, $000
			
screen_4

	DW	$111, $111, $111, $111, $111, $100 ;  Level 4
	DW	$123, $335, $242, $224, $322, $100 ;  5 voles
	DW	$132, $422, $244, $423, $223, $100 ;  Player X = 7
	DW	$122, $424, $422, $222, $223, $100 ;  Player Y = 9
	DW	$143, $422, $224, $223, $422, $100
	DW	$122, $342, $242, $342, $242, $100
	DW	$126, $422, $432, $232, $462, $100
	DW	$142, $222, $224, $232, $422, $100
	DW	$122, $222, $432, $422, $422, $100
	DW	$132, $232, $375, $342, $422, $100
	DW	$123, $224, $323, $232, $332, $100
	DW	$123, $222, $234, $223, $234, $100
	DW	$122, $622, $422, $424, $222, $100
	DW	$123, $222, $234, $222, $232, $100
	DW	$122, $224, $442, $222, $322, $100
	DW	$123, $233, $222, $224, $322, $100
	DW	$122, $222, $244, $423, $232, $100
	DW	$126, $422, $262, $434, $243, $100
	DW	$122, $422, $242, $222, $222, $100
	DW	$111, $111, $111, $111, $111, $100
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$AB8, $BA0, $000, $89A, $BC0, $000
	
screen_5

	DW	$111, $111, $111, $111, $111, $100 ;  Level 5
	DW	$122, $242, $222, $224, $222, $100 ;  1 Vole
	DW	$142, $222, $222, $332, $222, $100 ;  Player: 5,8	
	DW	$122, $324, $334, $442, $222, $100
	DW	$143, $222, $442, $222, $222, $100
	DW	$122, $232, $242, $222, $422, $100
	DW	$122, $243, $224, $322, $222, $100
	DW	$143, $224, $224, $322, $222, $100
	DW	$122, $237, $464, $522, $444, $100
	DW	$122, $444, $224, $224, $222, $100
	DW	$122, $222, $422, $422, $242, $100
	DW	$123, $423, $324, $244, $422, $100
	DW	$123, $242, $232, $223, $222, $100
	DW	$142, $222, $422, $224, $222, $100
	DW	$142, $244, $222, $424, $244, $100
	DW	$123, $323, $222, $422, $222, $100
	DW	$123, $442, $444, $422, $222, $100
	DW	$122, $222, $232, $322, $222, $100
	DW	$122, $342, $232, $222, $222, $100
	DW	$111, $111, $111, $111, $111, $100
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$AB8, $BA0, $000, $89A, $BC0, $000
	
screen_6

	DW	$000, $000, $000, $000, $000, $000 ; Winning Screen!!
	DW	$606, $006, $600, $060, $600, $000
	DW	$060, $060, $060, $060, $600, $000
	DW	$060, $060, $060, $060, $600, $000
	DW	$060, $006, $600, $066, $600, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$600, $060, $060, $060, $060, $060
	DW	$600, $060, $060, $066, $060, $060
	DW	$606, $060, $060, $060, $660, $000
	DW	$060, $600, $060, $060, $060, $060
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $007, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	DW	$000, $000, $000, $000, $000, $000
	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,  BLACK, 	BLACK, 0	; Blank
	DW	0,  BLACK, 	COLOR_1+1, 0	; Border
	DW	32, BLACK+1, 	BLACK, 0	; Ground
	DW	29, COLOR_8+5, 	BLACK, 0	; Movable Block
	DW	30, COLOR_3+4,  BLACK, 0	; Immovable Block
	DW	28, WHITE, 	BLACK, 0	; Telepad
	DW	27, COLOR_7+3, 	BLACK, 0	; Vole
	DW	31, WHITE, 	BLACK, 0	; Player
	DW	22, WHITE, 	BLACK, 0	; v
	DW	15, WHITE, 	BLACK, 0	; o
	DW	12, WHITE,	BLACK, 0	; l
	DW	5,  WHITE, 	BLACK, 0	; e
	DW	19, WHITE, 	BLACK, 0	; s
	DW	0,  WHITE, 	BLACK, 0	; - not used
	DW	0,  WHITE,	BLACK, 0	; - not used
	DW	0,  WHITE, 	BLACK, 0	; - not used

; **** 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

	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

	DW	%01111100		; Vole
	DW	%11111110
	DW	%11010110
	DW	%11111110
	DW	%11000110
	DW	%11111110
	DW	%01111100
	DW	%00000000
	
	DW	%11111110		; Telepad
	DW	%00000010
	DW	%11111010
	DW	%10001010
	DW	%10111010
	DW	%10000010
	DW	%11111110
	DW	%00000000
	
	DW	%11111110		; Block
	DW	%11111110
	DW	%11111110
	DW	%11111110
	DW	%11111110
	DW	%11111110
	DW	%11111110
	DW	%00000000


	DW	%11111110		; Immovable Block
	DW	%11000110
	DW	%10101010
	DW	%10010010
	DW	%10101010
	DW	%11000110
	DW	%11111110
	DW	%00000000

	DW	%01110000		;  Player
	DW	%01110000
	DW	%00100000
	DW	%11111000
	DW	%00100000
	DW	%01010000
	DW	%01010000
	DW	%00000000

	DW	%11111110   		;  ground
	DW	%11111110
	DW	%11111110
	DW	%11111110
	DW	%11111110
	DW	%11111110
	DW	%11111110
	DW	%00000000

	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

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

               ( geocities.com/jefftranter@rogers.com)