STACK 'n' SMILE
(c) Nicholas Campbell 2003
--------------------------
Stack 'n' Smile is my entry for the 2003 1K Minigame Competition. I actually
started work on it back in mid-August, but left it alone for a long time when I
believed that it was going to be impossible to fit the game into 1K. With the
competition deadline closing in, though, I worked on the game again, sadly
having to sacrifice certain things (namely a rather nice sprite generation
routine, and the sprites themselves). But I did manage to fit it into 1K, and
overall, I'm very happy with the finished game. I hope you enjoy it as well.
Loading and Playing Stack 'n' Smile
-----------------------------------
So, how do you play Stack 'n' Smile? To load the game, insert the DSK file
containing the game into your Amstrad CPC emulator. (Obviously, if you're using
a real CPC, you won't need to do this.) Then type RUN"STACK" and press the
RETURN key. This will load the game, which will start immediately after it has
loaded; there was no room to include a "press a key" message or anything like
that, unfortunately.
Stack 'n' Smile is a very simplified version of UBI Soft's excellent game Pick
'n' Pile, which is one of my favourite games for the Amstrad CPC. You must swap
bricks around so that they form columns at least three bricks high, all of
which are the same colour. (Originally, I intended the game to use balls
instead of bricks, and for the balls to roll down the screen, but a lack of
space meant that I couldn't do that, so I changed them to bricks because bricks
will stack on top of each other nicely.) When you do this, the column(s) will
be removed.
When you remove a column, more bricks will fall from the top of the screen, so
it's impossible to remove all of the bricks and complete the game! There is
also a time limit of five minutes; when your time runs out, the border turns
red and the message "OUT OF TIME!" appears on the screen. To have another go,
press any key. You score 10 points for each brick you remove from a column.
Unlike Pick 'n' Pile, there are no special multiplier or bonus bricks to give
you extra points and collect gems; it's a 1K game, remember?
Other features missing from Stack 'n' Smile that were present in Pick 'n' Pile
are a jolly title tune, sound effects, multi-coloured sprites, monsters that
decrease your time fast, bombs, levels, and speed - because it doesn't use
proper sprites and relies on the standard text routines provided by the Amstrad
CPC's firmware, it's a bit slow, especially at the start of the game when lots
of bricks are falling down the screen. Despite this, I think Stack 'n' Smile is
quite a nice, playable game, when one considers that it occupies only 1
kilobyte of memory.
The keys are:
Q - move cursor up
A - move cursor down
I - move cursor left
O - move cursor right
SPACE - select and swap bricks
I hope you enjoy the game. I didn't call it Stack 'n' Smile for no reason,
after all!
Assembling the Game
-------------------
The source code for Stack 'n' Smile is intended for use with Exodus' Turbo-Ass,
which is my assembler of choice. It's heavily commented and documented to allow
Z80 beginners and experts alike to understand it easily. If you want to try
assembling the source code yourself, get Turbo-Ass from NVG's Amstrad CPC
archive (< ftp://ftp.nvg.ntnu.no/pub/cpc/ > - it's in the utils/cpc directory),
then do the following:
* Load Turbo-Ass by inserting the relevant DSK file or disc.
* Type RUN"TASS128K" and press RETURN. You should see a black screen with some
text at the bottom.
* Press ESC, and then L, to load a file.
* Insert the DSK file or disc containing the Stack 'n' Smile source code.
* Type STACK.ASM and press RETURN. You should see the source code on the screen
after it has finished loading.
* Press ESC, and then 3, to assemble the source code. This will save the file
STACK to the disc. This file will actually be longer than 1K, because there
are 25 bytes used to store various things by the game. However, they may be
left out, and if the file is saved again, it will be exactly 1K in size,
including the header.
* Press ESC, and then 1, to quit Turbo-Ass.
If you need any more information on how to use Turbo-Ass, load the help file
TURBOASS.ENG which is stored on the Turbo-Ass disc.
Copying
-------
You are allowed to copy Stack 'n' Smile, with or without the source code,
although I'd prefer it if you did include it. You should also include this text
file with the game.
Nicholas Campbell, 28th September 2003, 10:17pm
e-mail : nich < AT > durge < DOT > org
website: < http://users.durge.org/~nich/ >
NOTICE TEXTE n° 2 (28.47 Ko)
;STACK 'n' SMILE - an entry for the 2003 1K Minigame Competition
;by Nich Campbell 11th August to 28th September 2003
;The assembler may claim that the game is more than 896 ($380) bytes long (the
;maximum allowed under the rules of the competition), but if you exclude the
;zeroes at the end, the game is exactly 896 bytes long
;You may distribute this game, with or without the source code
*= $A000
.OBJECT "stack.",$A000
;================
; INITIALISATION
;================
game_table = *+$0400 ;Address to store a 20 x 12 table for the game; this
;must be page-aligned (i.e. the low byte must be 0),
;otherwise all sorts of bugs will occur!
character_table = game_table+20*12 ;A table to store the defined characters
start_of_program
CALL $BC02 ;Reset colours to their default values
LD DE,254 ;Define table to store some defined characters, from
LD HL,character_table ;characters 254 to 255
CALL $BBAB
LD A,75 ;Number of bricks to release initially (must be less
;than 240)
LD (bricks_to_release),A
LD HL,0 ;Reset score to zero
LD (score),HL
LD HL,game_table ;Clear game table
LD (HL),0
LD D,H
LD E,L
INC DE
LD BC,20*12-1
LDIR
;Print the status bar at the top of the screen; this also sets the mode and
;border colour
LD HL,status_bar
CALL print_string
;Set the graphics pen colour
LD A,2
CALL $BBDE
;Set an initial random number seed using the R register
LD A,R
LD (seed),A
;Set the initial cursor coordinates
LD HL,$0B
LD (cursor_ycoord),HL
;Set the clock in seconds (which should be less than 1000)
LD HL,300
LD (clock),HL
;Enable the event block and the clock
LD HL,event_block
LD DE,decrease_clock
LD B,$80 ;$80 = normal, asynchronous event
CALL $BCEF
LD HL,ticker_event_block
LD DE,50 ;DE = initial value of counter (50ths of a second)
LD BC,50 ;BC = value to set counter after event has occurred
CALL $BCE9
;==============
; MAIN PROGRAM
;==============
main_loop
;Check the clock marker which indicates if the clock has changed; if so, print
;the new value of the clock
;
;This cannot be done when decreasing the clock itself, because the main program
;interferes with the printing of the clock and corrupts the screen
LD A,(clock_marker)
OR A ;Has clock changed?
JR Z,check_bricks_to_release ;No, so don't print new value
LD BC,clock_string+5 ;No, so print clock and set address of clock
;string, skipping first few bytes
LD HL,(clock) ;HL = clock
;Calculate the number of each unit - 10000s, 1000s, 100s, 10s and units
CALL calculate_3digits
;Print the clock and reset the clock marker
LD HL,clock_string
CALL print_string
XOR A
LD (clock_marker),A
LD HL,(clock)
LD A,H ;Has the clock reached zero?
OR L
JP Z,game_over ;Yes, so game is over
check_bricks_to_release
;Check if any bricks still need to be released, and if so, select a random brick
;(red, yellow or blue)
LD A,(bricks_to_release)
OR A ;Are there any bricks to release?
JR Z,main_loop2 ;No, so don't select a brick
select_random_brick
CALL get_random_number
AND 3 ;Ensure number is between 0 and 3
OR A ;Is it 0 (background sprite)?
JR Z,select_random_brick ;Yes, so select another number
LD B,A ;B = sprite number
PUSH BC ;Store sprite number of brick
;Select a random column to place the brick
select_random_column
CALL get_random_number
AND 31 ;Ensure number is between 0 and 31
CP 20 ;Is it greater than 19?
JR NC,select_random_column ;Yes, so select another number
LD D,A ;DE = coordinates to place brick
LD E,0 ;D = column, E = row
PUSH DE ;Store coordinates
CALL get_table_address ;Get table address of these coordinates (stored
;in HL)
POP DE ;Get coordinates
POP BC ;Get sprite number of brick
LD A,(HL) ;Get sprite number stored at top of column
OR A ;Is it empty?
JP NZ,check_left_key ;No, so don't place a brick in this column
LD (HL),B ;Yes, so store brick at these coordinates
EX DE,HL ;Now HL = coordinates
CALL print_sprite
LD HL,bricks_to_release ;Decrease number of bricks to release
DEC (HL)
main_loop2
;Check all the columns to see if all the bricks in each column are of the same
;colour; if so, remove the bricks in that column
check_columns
LD D,0 ;Start at leftmost column
LD HL,game_table+(11*20) ;HL = address of bottom left column
;First, get the colour of the brick at the bottom of the current column
check_columns_loop
PUSH HL ;Store address of bottom of column
LD A,(HL) ;A = brick colour at bottom
OR A ;Is column empty?
JR Z,go_to_next_column ;Yes, so go to next column
LD C,A ;No, so now C = brick colour at bottom
LD B,1 ;B = counter for number of bricks in column of same
;colour
;Next, check all the bricks above it to see if they are the same colour; if any
;brick is encountered which is a different colour, go to the next column
check_columns_loop2
LD A,L
SUB 20 ;Set address for row above
LD L,A
LD A,(HL) ;Get brick colour at these coordinates
OR A ;Are there any more bricks in this column?
JR Z,remove_bricks_from_column ;No, and we know all bricks in column
;are same colour, so remove them
CP C ;Is brick same as colour at bottom of column?
JR NZ,go_to_next_column ;No, so go to next column
INC B ;Yes, so increase counter
LD A,B
CP 12 ;Is column full (no space above brick at top)?
JR NZ,check_columns_loop2 ;No, so check next brick in column
;Remove all the bricks in a column
remove_bricks_from_column
POP HL ;Get address of bottom of column
PUSH HL ;Store it again
LD A,B
CP 3 ;There must be at least three bricks in column for it
;to be removed
JR C,go_to_next_column
LD E,11 ;Set y-coordinate for bottom of column
remove_bricks_loop
LD (HL),0 ;Remove brick
LD A,L
SUB 20 ;Set address for row above
LD L,A
PUSH HL ;Store table address of brick
PUSH BC ;Store number of bricks to remove
PUSH DE ;Store coordinates
CALL get_table_address ;Get table address of these coordinates (stored
;in HL)
POP DE ;Get coordinates
LD B,0 ;Replace brick with background sprite
PUSH DE ;Store coordinates
EX DE,HL ;Now HL = coordinates
CALL print_sprite
LD HL,(score)
LD DE,10
ADD HL,DE ;Increase score by 10 for each brick removed
LD (score),HL
LD HL,bricks_to_release
INC (HL)
POP DE ;Get coordinates
POP BC ;Get number of bricks to remove
POP HL ;Get table address of brick
DEC E ;Set y-coordinate for next brick
DJNZ remove_bricks_loop
PUSH DE ;Store coordinates
;Print the score at the top right of the screen
LD BC,score_string+5 ;Address of score string, skipping first few
;bytes
LD HL,(score) ;HL = score
;Calculate the number of each unit - 10000s, 1000s, 100s, 10s and units
LD DE,10000
CALL store_digit
LD DE,1000
CALL store_digit
CALL calculate_3digits
;Print the score
LD HL,score_string
CALL print_string
POP DE ;Get coordinates
;Go to the next column
go_to_next_column
POP HL ;Get address of bottom of column
INC HL ;Increase it for the next column
INC D ;Go to next column
LD A,D
CP 20 ;Have we gone past rightmost column of table?
JR NZ,check_columns_loop ;No, so go to next column
;Check the entire table and move bricks downwards if they are able to do so
move_bricks
LD DE,10 ;DE = initial table coordinates
;D = column, E = row
LD HL,game_table+(10*20) ;HL = address of these coordinates
move_bricks_loop
;Get the sprite number at the current coordinates, and the sprite number
;directly below it; the first is stored in B, while the second is stored in A
PUSH HL ;Store address of these coordinates
LD B,(HL) ;B = sprite number stored at these coordinates
LD A,L
ADD 20 ;Go to next row (each row is 20 bytes long)
LD L,A
LD A,(HL) ;A = sprite number stored directly below coordinates
POP HL ;Get address of these coordinates
;Check if there is a brick directly below the current coordinates, and/or if the
;current coordinates are empty
OR A ;Is there anything below these coordinates?
JR NZ,move_bricks_nextcolumn ;Yes, so go to the next column
LD A,B
OR A ;Is there anything at the current coordinates?
JR Z,move_bricks_nextcolumn ;No, so go to the next column
;If there isn't a brick below, then move whatever is at the current coordinates
;downwards
PUSH HL ;Store address of these coordinates
LD A,L
ADD 20 ;Go to next row (each row is 20 bytes long)
LD L,A
LD (HL),B ;Store brick directly below these coordinates
PUSH DE ;Store coordinates again
EX DE,HL ;Now HL = coordinates
INC L ;Set y-coordinate for row below
CALL print_sprite
POP DE ;Get coordinates
POP HL ;Get address of these coordinates
LD B,0 ;Clear cell at these coordinates
LD (HL),B
PUSH DE ;Store coordinates
PUSH HL ;Store address of these coordinates
EX DE,HL ;Now HL = coordinates
CALL print_sprite
POP HL ;Get address of these coordinates
POP DE ;Get coordinates
;Go to the next column of the table
move_bricks_nextcolumn
INC D ;Go to next column
INC HL ;Go to address of next column
LD A,D
CP 20 ;Have we reached rightmost column of table?
JR NZ,move_bricks_loop ;No, so repeat for remaining columns
LD D,0 ;Yes, so reset x-coordinate
LD A,L
SUB 40 ;Set address for row above
LD L,A
DEC E ;Go to next row (the one above)
LD A,E
CP $FF ;Have we reached top row of table?
JR NZ,move_bricks_loop ;No, so repeat for remaining rows
CALL wait ;Slow cursor down
;Check for keypresses and move the cursor accordingly
;
;The keys are: Q = up, A = down, I = left, O = right, SPACE = select brick
check_left_key
LD A,35 ;Check I key (35)
CALL $BB1E
JR Z,check_right_key
CALL print_sprite_under_cursor
LD A,(cursor_xcoord) ;Get cursor x-coordinate
OR A ;Is cursor at leftmost column of table?
JR Z,check_right_key ;Yes, so don't move cursor
DEC A ;No, so move cursor left
LD (cursor_xcoord),A ;Store new x-coordinate
check_right_key
LD A,34 ;Check O key (34)
CALL $BB1E
JR Z,check_up_key
CALL print_sprite_under_cursor
LD A,(cursor_xcoord) ;Get cursor x-coordinate
CP 19 ;Is cursor at rightmost column of table?
JR Z,check_up_key ;Yes, so don't move cursor
INC A ;No, so move cursor right
LD (cursor_xcoord),A ;Store new x-coordinate
check_up_key
LD A,67 ;Check Q key (67)
CALL $BB1E
JR Z,check_down_key
CALL print_sprite_under_cursor
LD A,(cursor_ycoord) ;Get cursor y-coordinate
OR A ;Is cursor at top row of table?
JR Z,check_down_key ;Yes, so don't move cursor
DEC A ;No, so move cursor up
LD (cursor_ycoord),A ;Store new y-coordinate
check_down_key
LD A,69 ;Check A key (69)
CALL $BB1E
JR Z,check_select_key
CALL print_sprite_under_cursor
LD A,(cursor_ycoord) ;Get cursor y-coordinate
CP 11 ;Is cursor at bottom row of table?
JR Z,check_select_key ;Yes, so don't move cursor
INC A ;No, so move cursor down
LD (cursor_ycoord),A ;Store new y-coordinate
check_select_key
LD A,47 ;Check SPACE key (47)
CALL $BB1E
CALL NZ,select_cell
;Draw the cursor, and the selected cursor if a brick has been selected, at their
;new positions
LD HL,(cursor_ycoord) ;Get new coordinates of cursor
CALL draw_cursor
LD A,(selected_marker) ;Check if a brick is currently selected
OR A
JP Z,main_loop ;No, so don't draw a cursor over this brick
LD HL,(selected_ycoord) ;Get selected coordinates
CALL draw_cursor
JP main_loop
;Select the cell at the current coordinates of the cursor, if there is a brick
;at these coordinates (you're not allowed to select a blank area)
select_cell
CALL wait ;Prevent cursor from being unselected easily
LD HL,(cursor_ycoord) ;Get coordinates of cursor
LD A,(selected_marker) ;Get marker to indicate if a cell has already
;been selected
OR A ;Has a cell been selected?
JR NZ,swap_cells ;Yes, so swap selected cell with cell at current
;cursor position
LD (selected_ycoord),HL ;No, so store new selected coordinates
EX DE,HL ;Now DE = cursor coordinates (storing coordinates in
;HL above saves one byte overall)
CALL get_table_address ;Get table address of these coordinates (stored
;in HL)
LD A,(HL)
OR A ;Is there anything below cursor?
RET Z ;No, so don't select cell
LD HL,selected_marker ;Yes, so set marker to indicate a brick has
LD (HL),$FF ;been selected
RET
;If a cell has already been selected, then swap the contents of the selected
;cell and the cell at the current cursor position (even if it is empty)
swap_cells
EX DE,HL ;Now DE = coordinates of cursor
CALL get_table_address ;Get table address of cursor (stored in HL)
LD C,(HL) ;C = brick at cursor
EX DE,HL ;Now DE = table address of cursor
PUSH DE ;Store table address of cursor
LD DE,(selected_ycoord) ;Get selected coordinates
CALL get_table_address ;Get table address of selected cursor (stored
;in HL)
LD A,(HL) ;A = brick at selected cursor
POP DE ;Get table address of cursor
LD (HL),C ;Store brick at cursor at selected cursor position
LD (DE),A ;Store brick at selected cursor in current cursor
;position
LD DE,(selected_ycoord) ;Print sprite under selected cursor, which
CALL print_sprite_under_cursor2 ;removes it from screen
CALL print_sprite_under_cursor
LD HL,selected_marker ;Set marker to indicate no brick is now
LD (HL),0 ;selected
RET
;Print the sprite under the cursor
print_sprite_under_cursor
LD DE,(cursor_ycoord) ;Get coordinates of cursor
print_sprite_under_cursor2
PUSH DE ;Store coordinates
CALL get_table_address ;Get table address of these coordinates
LD B,(HL) ;Get sprite number at these coordinates
POP HL ;Get coordinates
CALL print_sprite
RET
;Game over routine
game_over
LD HL,ticker_event_block
CALL $BCEC ;Disable and delete clock event
;Print the "game over" message
LD HL,game_over_string
CALL print_string
;Wait for about three seconds to prevent returning to the game immediately
LD B,25
game_over_loop
PUSH BC
CALL wait
POP BC
DJNZ game_over_loop
;Clear the keyboard buffer, and wait for a key to be pressed
clear_keybuffer_loop
CALL $BB1B
JR C,clear_keybuffer_loop
CALL $BB18
;Start a new game
JP start_of_program
;=============
; SUBROUTINES
;=============
;Generate a random number between 0 and 255
;
;The random number generator used here is known as a linear congruential
;generator, which takes the form of the equation x(n+1) = (ax(n)+b) mod m. The
;quality of the numbers depends on what values are used for a and b. m is set to
;256 and cannot be changed in this routine, and a and b are defined randomly
;using the R (refresh) register
;
;Why not just use LD A,R on its own? Answer: The results just aren't random
;enough!
;
;Entry conditions:
;None
;
;Exit conditions:
;A = random number
;BC, HL corrupted
get_random_number
LD A,R ;Get random number for a
OR $81 ;Set bits 7 and 0, so a is at least 128 (the bigger
;the value of a, the better) and odd (if a is even,
;then ax(n) is always even, which is not good)
LD B,A ;B = value of a
LD A,R ;Get random number for b
OR $80 ;Set bit 7, so b is at least 128; this seems to
;produce better quality random numbers
LD C,A ;C = value of b
LD HL,seed ;Address of current random number seed
LD A,(HL) ;Get current seed (i.e. x(n))
random_loop
ADC A ;Add x(n) to itself
DJNZ random_loop ;Multiply x(n) by a
ADC C ;Add b to result
LD (HL),A ;Store new seed (i.e. x(n+1))
RET
;Print a sprite to the screen
;
;Entry conditions:
;B = number of sprite to print
;H = x-coordinate of table to print sprite (0 - 19)
;L = y-coordinate of table to print sprite (0 - 11)
;
;Exit conditions:
;A, DE, HL corrupted
;B = 0
print_sprite
INC H ;Increase x-coordinate by 1
SLA L ;Multiply y-coordinate by 2
INC L ;Increase y-coordinate by 2
INC L
CALL $BB75 ;Set coordinates for printing sprite
;Calculate the address where the sprite is stored
INC B ;Increase sprite number by 1, otherwise the routine
;below doesn't work properly
LD HL,sprite_array-7
LD DE,7 ;Each sprite is seven bytes long
sprite_loop
ADD HL,DE ;Go to next sprite
DJNZ sprite_loop
;Print a string (which must end with the byte $E5, or 229 in decimal)
;
;Entry conditions:
;HL = start address of string
;
;Exit conditions:
;A, HL corrupted
print_string
LD A,(HL) ;Get character
CP $E5 ;Is it end of string marker?
RET Z ;Yes, so return
CALL $BB5A ;No, so print character
INC HL ;Go to next character
JR print_string
;Get the address in the game table which corresponds to the specified x- and y-
;coordinates
;
;Entry conditions:
;D = x-coordinate in table (0 - 19)
;E = y-coordinate in table (0 - 11)
;
;Exit conditions:
;A, B, DE corrupted
;HL = address of coordinates
get_table_address
LD A,D ;A = x-coordinate
LD B,E ;B = y-coordinate
INC B ;Increase y-coordinate, otherwise routine below
;doesn't work properly
LD DE,20 ;Each row is 20 bytes long
LD HL,game_table-20 ;HL = start address of table
address_loop
ADD HL,DE ;Go to next row
DJNZ address_loop
LD E,A
ADD HL,DE ;Go to appropriate column
RET
;Draw a cursor on the screen at the specified coordinates
;
;Entry conditions:
;H = x-coordinate of table to draw cursor (0 - 19)
;L = y-coordinate of table to draw cursor (0 - 11)
;
;Exit conditions:
;A, BC, DE, HL corrupted
draw_cursor
;Convert the coordinates to the appropriate coordinates for the firmware's
;graphics routines
;First convert the x-coordinate, and store the result in DE
;To convert the x-coordinate, multiply it by 32 (i.e. xnew = x*32)
PUSH HL ;Store coordinates
LD L,H ;HL = x-coordinate
LD H,0
ADD HL,HL ;Multiply HL by 32
ADD HL,HL
ADD HL,HL
ADD HL,HL
ADD HL,HL
EX DE,HL ;Now DE = converted x-coordinate
POP HL ;Get coordinates
;Then convert the y-coordinate, and store the result in HL
;To convert the y-coordinate, multiply it by 32, then subtract the result from
;382 (i.e. ynew = 382-y*32)
LD H,0 ;HL = y-coordinate
ADD HL,HL ;Multiply HL by 32
ADD HL,HL
ADD HL,HL
ADD HL,HL
ADD HL,HL
PUSH DE ;Store converted x-coordinate
EX DE,HL ;Now DE = y-coordinate * 32
LD HL,382
SBC HL,DE ;Subtract multiplied y-coordinate from 382 to give
;converted y-coordinate (stored in HL)
POP DE ;Get converted x-coordinate
;DE and HL now contain the x- and y-coordinates respectively for the top left
;corner of the table cell
CALL $BBC0 ;Move to top left corner of cell
;Draw the cursor
;Lots of PUSHing and POPping is used here because it saves a few bytes; the
;stack can be used to assign 0 to DE or HL
LD DE,30
LD HL,0
PUSH HL
CALL $BBF9 ;Draw top line
POP DE
PUSH DE
LD HL,-30
CALL $BBF9 ;Draw right line
LD DE,-30
POP HL
PUSH HL
CALL $BBF9 ;Draw bottom line
POP DE
LD HL,30
JP $BBF9 ;Draw left line
;Wait for a short period of time
;
;Entry conditions:
;None
;
;Exit conditions:
;A corrupted
;BC = 0
wait
LD BC,10000 ;BC = length of delay (the larger the value, the
;longer the delay)
wait_loop
DEC BC
LD A,B ;Does BC = 0?
OR C
JR NZ,wait_loop ;No, so continue delay
RET ;Yes, so return
;Part of a routine to calculate digits from a 16-bit number, used to print both
;the score and the clock
calculate_3digits
LD DE,100
CALL store_digit
LD DE,10
CALL store_digit
LD DE,1
;Calculate a digit and store it in a string
;
;Entry conditions:
;BC = address to store digit
;DE = units for calculating digit (10000, 1000, 100, 10 or 1)
;HL = number
;
;Exit conditions:
;A = ASCII character of digit (0-9)
;BC = address to store next digit
;HL = remainder from calculating digit; use it to calculate next digit
store_digit
XOR A
store_digit_loop
SBC HL,DE ;Subtract 10000, 1000, 100, 10 or 1 from number
JR C,store_digit2 ;Is number negative after subtraction?
INC A ;No, so increase digit and repeat
JR store_digit_loop
store_digit2
ADD HL,DE ;Undo last subtraction to make score positive again
ADD 48
LD (BC),A
INC BC
RET
;Decrease the clock every second
;
;Entry conditions:
;None
;
;Exit conditions:
;A, HL corrupted
decrease_clock
LD HL,(clock)
DEC HL ;Decrease clock by 1
LD (clock),HL
LD A,$FF ;Set clock marker to indicate that clock has changed
LD (clock_marker),A
RET
;==============
; PROGRAM DATA
;==============
;Text strings
;The status bar message; this uses control codes to set the mode, the border and
;ink colours, the paper and pen inks to write the message in, and defines the
;characters for the bricks
status_bar
.BYTE 4,0,29,1,1 ;set mode and border colour
.BYTE 28,0,0,0,28,2,26,26,28,4,2,2 ;set ink colours
.BYTE 25,254,126,122,253,253,255,255,255,255 ;define upper half of brick
.BYTE 25,255,255,255,255,255,255,255,126,126 ;define lower half of brick
.BYTE 15,1 ;set paper and pen inks
.TEXT "SCORE "
.BYTE 15,2
.TEXT "00000"
.BYTE 15,1
.TEXT " TIME "
.BYTE 15,2
.TEXT "300"
.BYTE $E5
;The score and clock strings
score_string
.BYTE 31,7,1,15,2
.TEXT "00000"
.BYTE $E5
clock_string
.BYTE 31,18,1,15,2
.TEXT "000"
.BYTE $E5
;The "game over" message
game_over_string
.BYTE 31,5,13,14,3,15,2,29,6,6
.TEXT "OUT OF TIME!"
.BYTE 14,0
.BYTE $E5
;Sprite data
;The character sequences for printing each sprite; each one must be seven bytes
;long (including the end of string marker, $E5)
sprite_array
.BYTE 15,1,32,8,10,32,$E5 ;background
.BYTE 15,3,254,8,10,255,$E5 ;red brick
.BYTE 15,1,254,8,10,255,$E5 ;yellow brick
.BYTE 15,4,254,8,10,255,$E5 ;blue brick
;A silly message to increase the file size to exactly 896 ($380) bytes,
;excluding the zeroes that follow it
.BYTE 164
.TEXT " NC 2003"
;The random number seed
seed
.BYTE 0
;The number of bricks that still need to be released
bricks_to_release
.BYTE 0
;The x- and y-coordinates of the cursor; the y-coordinate should be stored first
cursor_ycoord
.BYTE 0
cursor_xcoord
.BYTE 0
;The x- and y-coordinates of the selected table cell
selected_ycoord
.BYTE 0
selected_xcoord
.BYTE 0
;A marker which indicates whether there is a currently selected cell or not
selected_marker
.BYTE 0
;The current score and clock
score
.WORD 0
clock
.WORD 0
;A marker which indicates whether the clock has changed or not
clock_marker
.BYTE 0
;The ticker event block for the timer, which is used by the firmware
ticker_event_block
.FILL 6,0
event_block
.FILL 7,0