SwapScreens

The first subroutine here called SwapScreens will check to see which screen is active at a given time (Screen1 or Screen2). Two screens are constantly being switched back and forth to enable the game to run more smoothly. The call to LoadPointer will load the screen contents from both variables (SCREEN2_MEM) and (SCREEN1_MEM) and save the results in the variables (CURRENT_SCREEN) and (CURRENT_BUFFER). These are used to below to obtain the screen address of each individual screen.

;------------------------------------------------------------------------------- 
; Exchange the front and backbuffer screens 
;------------------------------------------------------------------------------- 
lda CURRENT_SCREEN + 1 ; load hi byte of current screen 
cmp #>SCREEN2_MEM 
beq @screen2 
loadPointer CURRENT_SCREEN, SCREEN2_MEM 
loadPointer CURRENT_BUFFER, SCREEN1_MEM 
rts
@screen2
loadPointer CURRENT_SCREEN, SCREEN1_MEM
loadPointer CURRENT_BUFFER, SCREEN2_MEM
rts

In this section within the subroutine FetchPlayfieldAddress we read from the variable (CURRENT_SCREEN + 1), check it against (SCREEN1_MEM) to see if there is a match. This is what looks up the screen address for Screen1 or Screen2. Then we get the screen address of SCREEN2_LINE_OFFSET_TABLE_LO/HI) or (SCREEN1_LINE_OFFSET_TABLE_LO/HI) and place them in ZEROPAGE_POINTER_1 (low and high bytes).

Note: This call is necessary when we want to check the area around our game Sprite. By reading the address close to our sprite (up/down/left/right or below them) we can block that direction from movement.

;=================================================================================================== 
; FETCH PLAYFIELD LINE ADDRESS 
;=================================================================================================== 
; A helper routine to return the line address for the current front screen only. A cut back version 
; of FetchLineAddress for faster use with sprite/character collisions it also uses the Y register 
; instead of the X as that is tied up in our collision routines to hold the sprite number 
; 
; Y = line number 
; Returns : ZEROPAGE_POINTER_1 = screen line address ; Modifies A 
;--------------------------------------------------------------------------------------------------- 
FetchPlayfieldLineAddress
lda CURRENT_SCREEN + 1 ; load HI byte of current screen address 
cmp #>SCREEN1_MEM ; compare it to the HI byte of SCREEN1_MEM 
beq @screen1 ; if it's equal - it's screen1  
; otherwise it's screen2 
lda SCREEN2_LINE_OFFSET_TABLE_LO,y ; Use Y to lookup the address and save it in 
sta ZEROPAGE_POINTER_1 ; ZEROPAGE_POINTER_1 
lda SCREEN2_LINE_OFFSET_TABLE_HI,y 
sta ZEROPAGE_POINTER_1 + 1 
rts

@screen1

lda SCREEN1_LINE_OFFSET_TABLE_LO,y 
; Use Y to lookup the address and save it in 
sta ZEROPAGE_POINTER_1 ; ZEROPAGE_POINTER_1 
lda SCREEN1_LINE_OFFSET_TABLE_HI,y 
sta ZEROPAGE_POINTER_1 + 1 
rts

The subroutine FetchLineAddress is used to detect which screen is being displayed (similar to the above routine). It is also used to get the screen address of our Score Display area (below the map playfield). The data is loaded into the variable (SCORE_LINE_OFFSET_TABLE_LO/HI) bytes and saved into ZEROPAGE_POINTER_1 (low and high bytes).

;-------------------------------------------------------------------------------
; FETCH LINE ADDRESS 
;------------------------------------------------------------------------------- 
; A helper routine to return the line address for the correct screen to draw to 
; Given the screen base in WPARAM1, and the line in X (Y coord) we test 
; the high byte in WPARAM1 and use the correct lookup table to get the line 
; address, returning it in ZEROPAGE_POINTER_1 
; An additional 'jump in' point "FetchScreenLineAddress" can be used that will 
; only consider the CURRENT_SCREEN pointer, likewise "FetchBufferLineAddress" 
; will jump in and substitute the current buffer. 
; 
; WPARAM1 - BASE ADDRESS OF SCREEN (1,2,Score) 
; X - Line required ; 
; returns ZEROPAGE_POINTER_1 
; 
; Modifies A 
; 
;------------------------------------------------------------------------------- 
FetchLineAddress 
lda WPARAM1 + 1 
jmp detectScreen
FetchScreenLineAddress
lda CURRENT_SCREEN + 1 
jmp detectScreen
FetchBufferLineAddress
lda CURRENT_BUFFER + 1

detectScreen

cmp #>SCREEN1_MEM 
beq @screen1 
cmp #>SCREEN2_MEM 
beq @screen2 
cmp #>SCORE_SCREEN 
beq @score 
; if none of the above, it will default to Screen1
@screen1
lda SCREEN1_LINE_OFFSET_TABLE_LO,x 
sta ZEROPAGE_POINTER_1 
lda SCREEN1_LINE_OFFSET_TABLE_HI,x 
sta ZEROPAGE_POINTER_1 + 1 
rts
@screen2
lda SCREEN2_LINE_OFFSET_TABLE_LO,x 
sta ZEROPAGE_POINTER_1 
lda SCREEN2_LINE_OFFSET_TABLE_HI,x 
sta ZEROPAGE_POINTER_1 + 1 
rts
@score
lda SCORE_LINE_OFFSET_TABLE_LO,x 
sta ZEROPAGE_POINTER_1 
lda SCORE_LINE_OFFSET_TABLE_HI,x 
sta ZEROPAGE_POINTER_1 + 1 
rts

DisplayByte

The subroutine (DisplayByte) reads data from the variable (PARAM4). Then a call is made to FetchLineAddress to find the screen address position. After this color ram memory for the screen line is loaded from (COLOR_LINE_OFFSET_TABLE_LO/HI) bytes and saved in the variables of ZEROPAGE_POINTER_3  (low and high bytes).

Then we check the individual bytes one by one, mask out 15 digits, add 48 to reach the beginning of our numerical data in the Commodore 64 PET ASCII character set (i.e. 48 starts at zero). After this a check is made to see if the value is less than 58, which would then be reading as the ASCII (“9”) character. Finally we subtract 57 from this value to point back to the alphabet (starting with “A”). This allows it to read from 0-9 or A-F.

;--------------------------------------------------------------------------------------------------- 
; DISPLAY BYTE DATA 
;--------------------------------------------------------------------------------------------------- 
; Displays the data stored in a given byte on the screen as readable text in hex format (0-F) 
; X = screen line - Yes, this is a little arse-backwards (X and Y) but I don't think 
; Y = screen column addressing modes allow me to swap them around 
; A = byte to display 
; MODIFIES : ZEROPAGE_POINTER_1, ZEROPAGE_POINTER_3, PARAM4 
;--------------------------------------------------------------------------------------------------- 
sta PARAM4 ; store the byte to display in PARAM4 
jsr FetchLineAddress 
lda COLOR_LINE_OFFSET_TABLE_LO,x ; fetch line address for color 
sta ZEROPAGE_POINTER_3 
lda COLOR_LINE_OFFSET_TABLE_HI,x 
sta ZEROPAGE_POINTER_3 + 1 
lda PARAM4 ; load the byte to be displayed 
and #$0F 
clc ; mask for the lower half (0-F) 
adc #$30 ; add $30 (48) to display character set 
; numbers 
clc ; clear carry flag 
cmp #$3A ; less than the code for A (10)? 
bcc @writeDigit ; Go to the next digit 
sec 
sbc #$39 ; if so we set the character code back to 
; display A-F ($01 - $0A)

For the last part of our subroutine, we continue to read each byte (value) and write them to the variable ZEROPAGE_POINTER_1. Then we set the text color to yellow and save this in the variable ZEROPAGE_POINTER_3. The next step is to mask off the top 4 bits of our byte (seen in binary as “11110000”) which blanks out bits 0-3. The byte is then shifted 4 times to set it to a value of (“0-F”). Then we add 48 to it to be sure it’s set between A-F. From this byte check we subtract 57 from it again to point it back to the beginning alphabet PET ASCII characters.

Finally we read in the variable ZEROPAGE_POINTER_1 to get the character stored earlier from the subroutine FetchLineAddress and write it to color ram that was saved earlier.

@writeDigit
iny ; increment the position on the line  
sta (ZEROPAGE_POINTER_1),y 
; write the character code 
lda #COLOR_YELLOW ; set the color to white 
sta (ZEROPAGE_POINTER_3),y 
; write the color to color ram dey 
; decrement the position on the line 
lda PARAM4 ; fetch the byte to DisplayText 
and #$F0 ; mask for the top 4 bits (00 - F0) - 11110000 
lsr ; shift it right to a value of 0-F 
lsr 
lsr 
lsr 
adc #$30 ; from here, it's the same 
clc 
cmp #$3A ; check for A-F 
bcc @lastDigit 
sbc #$39
@lastDigit
sta (ZEROPAGE_POINTER_1),y ; write character and color 
lda #COLOR_YELLOW 
sta (ZEROPAGE_POINTER_3),y 
rts

DisplayText

For this subroutine DisplayText we begin by grabbing the first byte from the variable (PARAM2). The variable (SCORE_LINE_OFFSET_TABLE_LO/HI) bytes are saved in ZEROPAGE_POINTER_2 (low and high bytes).

The variable (COLOR_LINE_OFFSET_TABLE_LO/HI) bytes are placed in the variable ZEROPAGE_POINTER_2 (low and high bytes).

;------------------------------------------------------------------------------- ; DISPLAY TEXT 
;------------------------------------------------------------------------------- 
; Display a line of text '@' ($00) is the end of text characters 
; '/' ($2f) is the line break character 
; 
; ZEROPAGE_POINTER_1 = pointer to text data 
; PARAM1 = X ; PARAM2 = Y ; PARAM3 = Color 
; 
; Modifies ZEROPAGE_POINTER_2 and ZEROPAGE_POINTER_3 
; 
; Note all text should be in lower case : byte 'hello world@' or byte 'hello world',0 
;------------------------------------------------------------------------------- 
ldx PARAM2 
lda SCORE_LINE_OFFSET_TABLE_LO,x 
sta ZEROPAGE_POINTER_2 
lda SCORE_LINE_OFFSET_TABLE_HI,x 
sta ZEROPAGE_POINTER_2 + 1  
lda COLOR_LINE_OFFSET_TABLE_LO,x ; Fetch the address for the line in color ram  
sta ZEROPAGE_POINTER_3 
lda COLOR_LINE_OFFSET_TABLE_HI,x 
sta ZEROPAGE_POINTER_3 + 1 ; add the X offset to the destination address 

Now we begin to read from the variable (ZEROPAGE_POINTER_2 to get the screen address for our score panel display. Then we add the Y value to it and save it back in ZEROPAGE_POINTER_2 to continue down the line (in screen memory). This also continues for the high byte.

The next step is to access the variable ZEROPAGE_POINTER_3 which contains the color ram screen address. The data is written to the low and high bytes to follow the color ram address line by line.

lda ZEROPAGE_POINTER_2 
clc  
adc PARAM1 
sta ZEROPAGE_POINTER_2 
lda ZEROPAGE_POINTER_2 + 1  
adc #0  
sta ZEROPAGE_POINTER_2 + 1 ; Same for color ram  
lda ZEROPAGE_POINTER_3 
clc  
adc PARAM1 
sta ZEROPAGE_POINTER_3 
lda ZEROPAGE_POINTER_3 + 1  
adc #0  
sta ZEROPAGE_POINTER_3 + 1 ; Start the write for this line 

A new loop is started here that first reads from the variable ZEROPAGE_POINTER_1 which contains the screen address. We continue reading it checking for a line break (PET ASCII character “47”). This data is then stored in ZEROPAGE_POINTER_2. Then we read from the variable (PARAM3) which stored the color and save it in ZEROPAGE_POINTER_3. Continue until the loop is complete.

ldy #0 
@inlineLoop 
lda (ZEROPAGE_POINTER_1),y 
cmp #00 
beq @endMarkerReached 
cmp #$2F 
beq @lineBreak 
sta (ZEROPAGE_POINTER_2),y 
lda PARAM3 
sta (ZEROPAGE_POINTER_3),y 
iny 
jmp @inLineLoop

Finally we access the lineBreak area where we continue reading the bytes and append them to the variable ZEROPAGE_POINTER_1 (low and high bytes), and then increment the variable (PARAM2) which continues to read through the Y value to keep the loop going. The loop won’t exit until it has the found end of the marker.

@lineBreak
iny 
tya 
clc 
adc ZEROPAGE_POINTER_1 
sta ZEROPAGE_POINTER_1 
lda #0 
adc ZEROPAGE_POINTER_1 + 1 
sta ZEROPAGE_POINTER_1 + 1 
inc PARAM2 
jmp DisplayText

@endMarkerReached

rts

Clear the contents of the variable (SCREEN1_MEM) and (SCREEN2_MEM) which will clear the two swapping screens we have in memory.

ClearScreen1

;------------------------------------------------------------------------------- 
; CLEAR SCREEN 
;-------------------------------------------------------------------------------  
; Clears the screen using a chosen character. 
; A = Character/Color to clear the screen with 
; 
; Modifies X 
;------------------------------------------------------------------------------- 
ldx #$00
@clearLoop
sta SCREEN1_MEM,x 
sta SCREEN1_MEM + 250,x 
sta SCREEN1_MEM + 500,x ; Game screen only goes to 720 
sta SCREEN1_MEM + 750,x 
inx 
cpx #250 
bne @clearLoop 
rts 

ClearScreen2

ldx #$00 
@clearLoop 
sta SCREEN2_MEM,x 
sta SCREEN2_MEM + 250,x 
sta SCREEN2_MEM + 500,x ; Game screen only goes to 720 
sta SCREEN2_MEM + 750,x 
inx 
cpx #250 
bne @clearloop 
rts 

Clear the contents of the variable (SCORE_SCREEN) and (COLOR_MEM) which will clear the score display (below our game map) and the color ram area that exists on the screen.

ClearScoreScreen

ldx #$00 
@clearLoop 
sta SCORE_SCREEN,x 
sta SCORE_SCREEN + 250,x 
sta SCORE_SCREEN + 500,x 
sta SCORE_SCREEN + 750,x 
inx 
cpx #250 
bne @clearloop 
rts 

ClearColorRam

ldx #$00 
@clearLoop 
sta COLOR_MEM,x 
sta COLOR_MEM + 250,x 
sta COLOR_MEM + 500,x 
sta COLOR_MEM + 750,x 
inx 
cpx #250 
bne @clearLoop 
rts

This next area for subroutine CopyToBuffer will transfer data cast into variable memory (SCREEN1_MEM) and store it onto (SCREEN2_MEM). So it continues down the screen transferring data into a backbuffer area.

CopyToBuffer

;------------------------------------------------------------------------------- 
; COPY TO BUFFER (SLOW) 
;------------------------------------------------------------------------------- 
; NOTE : Don't use this for scrolling. Use the unrolled version in scrolling.asm 
; this is just for setup purposes. It takes the current front screen and copys 
; it to the buffer 
;--------------------------------------------------------------------------------  
@copy_screen1
ldx #$00 
@loop1 
lda SCREEN1_MEM,x 
sta SCREEN2_MEM,x 
lda SCREEN1_MEM + 250,x 
sta SCREEN2_MEM + 250,x 
lda SCREEN1_MEM + 500,x 
sta SCREEN2_MEM + 500,x 
lda SCREEN1_MEM + 750,x ; Game screen only goes to 720 
sta SCREEN2_MEM + 750,x 
inx 
cpx #250 
bne @loop1 
rts
; Screen Line Offset Tables 
; Query a line with lda (POINTER TO TABLE),x (where x holds the line number) 
; and it will return the screen address for that line 
; C64 PRG STUDIO has a lack of expression support that makes creating some tables very problematic 
; Be aware that you can only use ONE expression after a defined constant, no braces, and be sure to 
; account for order of precedence. 
; For these tables you MUST have the Operator Calc directive set at the top of your main file 
; or have it checked in options or BAD THINGS WILL HAPPEN!! It basically means that calculations 
; will be performed BEFORE giving back the hi/lo byte with '>' rather than the default of 
; hi/lo byte THEN the calculation

SCREEN_LINE_OFFSET_TABLE_LO
SCREEN1_LINE_OFFSET_TABLE_LO

byte <SCREEN_MEM  
byte <SCREEN_MEM + 40  
byte <SCREEN_MEM + 80 
byte <SCREEN_MEM + 120 
byte <SCREEN_MEM + 160 
byte <SCREEN_MEM + 200 
byte <SCREEN_MEM + 240 
byte <SCREEN_MEM + 280 
byte <SCREEN_MEM + 320 
byte <SCREEN_MEM + 360 
byte <SCREEN_MEM + 400 
byte <SCREEN_MEM + 440 
byte <SCREEN_MEM + 480 
byte <SCREEN_MEM + 520 
byte <SCREEN_MEM + 560 
byte <SCREEN_MEM + 600 
byte <SCREEN_MEM + 640 
byte <SCREEN_MEM + 680 
byte <SCREEN_MEM + 720 
byte <SCREEN_MEM + 760 
byte <SCREEN_MEM + 800 
byte <SCREEN_MEM + 840 
byte <SCREEN_MEM + 880 
byte <SCREEN_MEM + 920 
byte <SCREEN_MEM + 960

SCREEN_LINE_OFFSET_TABLE_HI
SCREEN1_LINE_OFFSET_TABLE_HI

byte >SCREEN_MEM 
byte >SCREEN_MEM + 40 
byte >SCREEN_MEM + 80 
byte >SCREEN_MEM + 120 
byte >SCREEN_MEM + 160 
byte >SCREEN_MEM + 200 
byte >SCREEN_MEM + 240 
byte >SCREEN_MEM + 280 
byte >SCREEN_MEM + 320 
byte >SCREEN_MEM + 360 
byte >SCREEN_MEM + 400 
byte >SCREEN_MEM + 440 
byte >SCREEN_MEM + 480 
byte >SCREEN_MEM + 520 
byte >SCREEN_MEM + 560 
byte >SCREEN_MEM + 600 
byte >SCREEN_MEM + 640 
byte >SCREEN_MEM + 680 
byte >SCREEN_MEM + 720 
byte >SCREEN_MEM + 760 
byte >SCREEN_MEM + 800 
byte >SCREEN_MEM + 840 
byte >SCREEN_MEM + 880 
byte >SCREEN_MEM + 920 
byte >SCREEN_MEM + 960
SCREEN2_LINE_OFFSET_TABLE_LO 
byte <SCREEN2_MEM  
byte <SCREEN2_MEM + 40  
byte <SCREEN2_MEM + 80 
byte <SCREEN2_MEM + 120 
byte <SCREEN2_MEM + 160 
byte <SCREEN2_MEM + 200 
byte <SCREEN2_MEM + 240 
byte <SCREEN2_MEM + 280 
byte <SCREEN2_MEM + 320 
byte <SCREEN2_MEM + 360 
byte <SCREEN2_MEM + 400 
byte <SCREEN2_MEM + 440 
byte <SCREEN2_MEM + 480 
byte <SCREEN2_MEM + 520 
byte <SCREEN2_MEM + 560 
byte <SCREEN2_MEM + 600 
byte <SCREEN2_MEM + 640 
byte <SCREEN2_MEM + 680 
byte <SCREEN2_MEM + 720 
byte <SCREEN2_MEM + 760 
byte <SCREEN2_MEM + 800 
byte <SCREEN2_MEM + 840 
byte <SCREEN2_MEM + 880 
byte <SCREEN2_MEM + 920 
byte <SCREEN2_MEM + 960

SCREEN2_LINE_OFFSET_TABLE_HI

byte >SCREEN2_MEM 
byte >SCREEN2_MEM + 40 
byte >SCREEN2_MEM + 80 
byte >SCREEN2_MEM + 120 
byte >SCREEN2_MEM + 160 
byte >SCREEN2_MEM + 200 
byte >SCREEN2_MEM + 240 
byte >SCREEN2_MEM + 280 
byte >SCREEN2_MEM + 320 
byte >SCREEN2_MEM + 360 
byte >SCREEN2_MEM + 400 
byte >SCREEN2_MEM + 440 
byte >SCREEN2_MEM + 480 
byte >SCREEN2_MEM + 520 
byte >SCREEN2_MEM + 560 
byte >SCREEN2_MEM + 600 
byte >SCREEN2_MEM + 640 
byte >SCREEN2_MEM + 680 
byte >SCREEN2_MEM + 720 
byte >SCREEN2_MEM + 760 
byte >SCREEN2_MEM + 800 
byte >SCREEN2_MEM + 840 
byte >SCREEN2_MEM + 880 
byte >SCREEN2_MEM + 920 
byte >SCREEN2_MEM + 960
SCORE_LINE_OFFSET_TABLE_LO 
byte <SCORE_SCREEN  
byte <SCORE_SCREEN + 40  
byte <SCORE_SCREEN + 80 
byte <SCORE_SCREEN + 120 
byte <SCORE_SCREEN + 160 
byte <SCORE_SCREEN + 200 
byte <SCORE_SCREEN + 240 
byte <SCORE_SCREEN + 280 
byte <SCORE_SCREEN + 320 
byte <SCORE_SCREEN + 360 
byte <SCORE_SCREEN + 400 
byte <SCORE_SCREEN + 440 
byte <SCORE_SCREEN + 480 
byte <SCORE_SCREEN + 520 
byte <SCORE_SCREEN + 560 
byte <SCORE_SCREEN + 600 
byte <SCORE_SCREEN + 640 
byte <SCORE_SCREEN + 680 
byte <SCORE_SCREEN + 720 
byte <SCORE_SCREEN + 760 
byte <SCORE_SCREEN + 800 
byte <SCORE_SCREEN + 840 
byte <SCORE_SCREEN + 880 
byte <SCORE_SCREEN + 920 
byte <SCORE_SCREEN + 960

SCORE_LINE_OFFSET_TABLE_HI

byte >SCORE_SCREEN 
byte >SCORE_SCREEN + 40 
byte >SCORE_SCREEN + 80 
byte >SCORE_SCREEN + 120 
byte >SCORE_SCREEN + 160 
byte >SCORE_SCREEN + 200 
byte >SCORE_SCREEN + 240 
byte >SCORE_SCREEN + 280 
byte >SCORE_SCREEN + 320 
byte >SCORE_SCREEN + 360 
byte >SCORE_SCREEN + 400 
byte >SCORE_SCREEN + 440 
byte >SCORE_SCREEN + 480 
byte >SCORE_SCREEN + 520 
byte >SCORE_SCREEN + 560 
byte >SCORE_SCREEN + 600 
byte >SCORE_SCREEN + 640 
byte >SCORE_SCREEN + 680 
byte >SCORE_SCREEN + 720 
byte >SCORE_SCREEN + 760 
byte >SCORE_SCREEN + 800 
byte >SCORE_SCREEN + 840 
byte >SCORE_SCREEN + 880 
byte >SCORE_SCREEN + 920 
byte >SCORE_SCREEN + 960
COLOR_LINE_OFFSET_TABLE_LO 
byte <COLOR_MEM  
byte <COLOR_MEM + 40  
byte <COLOR_MEM + 80 
byte <COLOR_MEM + 120 
byte <COLOR_MEM + 160 
byte <COLOR_MEM + 200 
byte <COLOR_MEM + 240 
byte <COLOR_MEM + 280 
byte <COLOR_MEM + 320 
byte <COLOR_MEM + 360 
byte <COLOR_MEM + 400 
byte <COLOR_MEM + 440 
byte <COLOR_MEM + 480 
byte <COLOR_MEM + 520 
byte <COLOR_MEM + 560 
byte <COLOR_MEM + 600 
byte <COLOR_MEM + 640 
byte <COLOR_MEM + 680 
byte <COLOR_MEM + 720 
byte <COLOR_MEM + 760 
byte <COLOR_MEM + 800 
byte <COLOR_MEM + 840 
byte <COLOR_MEM + 880 
byte <COLOR_MEM + 920 
byte <COLOR_MEM + 960

COLOR_LINE_OFFSET_TABLE_HI

byte >COLOR_MEM 
byte >COLOR_MEM + 40 
byte >COLOR_MEM + 80 
byte >COLOR_MEM + 120 
byte >COLOR_MEM + 160 
byte >COLOR_MEM + 200 
byte >COLOR_MEM + 240 
byte >COLOR_MEM + 280 
byte >COLOR_MEM + 320 
byte >COLOR_MEM + 360 
byte >COLOR_MEM + 400 
byte >COLOR_MEM + 440 
byte >COLOR_MEM + 480 
byte >COLOR_MEM + 520 
byte >COLOR_MEM + 560 
byte >COLOR_MEM + 600 
byte >COLOR_MEM + 640 
byte >COLOR_MEM + 680 
byte >COLOR_MEM + 720 
byte >COLOR_MEM + 760 
byte >COLOR_MEM + 800 
byte >COLOR_MEM + 840 
byte >COLOR_MEM + 880 
byte >COLOR_MEM + 920 
byte >COLOR_MEM + 960