Programming:Display and update Scores

From CPCWiki - THE Amstrad CPC encyclopedia!
Jump to: navigation, search

Methods for Storing, Displaying and Updating Scores

There are a number of methods for storing a Score in memory, here are a few examples:

A Single Word

This is the simplest and smallest method for storing the score in memory, but it has a couple of disadvantages over some other methods. It can only hold a score up to 65535 (although you can add an extra '0' or two on the display to make that 655350 or 6553500), converting it to digits for display requires a relatively slow algorithm, and adding extra lives every 10000 points for example can take a complex routine. The advantage of using a straight Word is that the maths required to add points to the score is very simple and fast.

To define the score:

.score dw 0

To add some points (in BC):

.add_score_bc
ld hl,(score)
add hl,bc
ld (score),hl
ret

To display the score requires converting to a decimal:

.show_score
ld hl,score
ld bc,-10000
call show_digit
ld bc,-1000
call show_digit
ld bc,-100
call show_digit
ld bc,-10
call show_digit
ld a,l
add '0'
jp show_char

.show_digit
ld a,'0' - 1
.inc_digit
inc a
add hl,bc
jr nc,inc_digit
or a
sbc hl,bc
add '0'
jp show_char	; This assumes the show_char routine doesn't corrupt HL

Checking for every 10000 points is complicated, this would normally done in the add_score routine, and I'm not going to write the code. It would however, be quite simple to add an extra life every multiple of 256 units (eg. 10240 points) by checking the most significant byte. eg:

.add_score_bc
ld hl,(score)
ld a,h
add hl,bc
ld (score),hl
sub h
cp 2
ret c
ld hl,lives
inc (hl)
ret

A Double Word

Like the above method, this provides a simple and efficient way to store and add points, but the routine required to convert the number to decimal for display is even more difficult, as is the extra lives every 10000 points.

To define the score:

.score ds 4

To add BC points to the score:

.add_score_bc
ld hl,(score)
add hl,bc
ld (score),hl
ret nc
ld hl,(score + 2)
inc hl
ld (score + 2),hl
ret

Displaying the score is much more tricky since it involves a lot of DWord subtractions etc. Most Z80 assemblers (including WinAPE) can't handle DWord values, so you need to get your calculator out to figure out what the values are to subtract for successive digits. The score can go as high as 4294967295, but I wouldn't recommend doing the maths for all possible digits. The code to display a DWord value is too complicated for this document.

A Single BCD Word

This is similar to the above method, but the score is treated as Binary Coded Decimal. It's main disadvantage is that it can hold even less values than the single Word, restricting the score to 10000 possible combinations (one again with extra zeroes if required). Depending on the type of game and difficulty, this could be a good method to use. It's easy to store in memory and pass around, displaying and adding points are all relatively easy.

To define the score is exactly the same as for a Single Word:

.score dw 0

Adding BC points is also fairly straightforward (here with the extra life check every 1000 units):

.add_score_BC
ld hl,(score)
ld d,h
ld a,l
add c
daa
ld l,a
ld a,h
adc b
daa
ld h,a
ld (score),hl
sub d
cp #10
ret c
ld hl,lives
inc (hl)
ret

And the routine to display the score:

.show_score
ld hl,(score)
.bcd_hl
ld a,h
call bcd_a
ld a,l
.bcd_a
ld h,a
rra:rra:rra:rra
and #0f
add '0'
call show_char
ld a,h
and #0f
add '0'
jp show_char

A Double BCD Word

This is actually one of the best methods of storing and displaying a score since the maths and display methods remain fairly simple, and your score can now hold up to 8 digits. Luckily the Z80 has just enough fast 16 bit registers (ie. Non-index registers) to be able to handle all the things you require.

To define the score:

.score ds 4

To add BC points to the score (here with the extra life check every 10000 units):

ld hl,(score)
ld a,l
add c
daa
ld l,a
ld a,h
adc b
daa
ld h,a
ld (score),hl
ret nc
ld hl,lives
inc (hl)
ld hl,(score + 2)
ld a,l
add 1
daa
ld l,a
ld a,h
adc 0
daa
ld h,a
ld (score + 2),hl
ret

Displaying the score is relatively simple too, using the same routine as above:

.show_score
ld hl,(score + 2)
call bcd_hl
ld hl,(score)
.bcd_hl
.
.
.

Multiple BCD Bytes

This is very similar to the above method, but the values are stored and manipulated in a slightly different way. With this method, you can determine exactly how many digits you require. Maths is pretty straightforward, as is extra lives and display. One advantage this method has is that if you want to add 100 to the score, you don't have to add the two zeroes since you can start at the second BCD digit. With this method, you can store the score either little-endian (least significant bytes first) or big-endian. Big-endian has the advantage that you can read the score in a memory editor left-to-right. eg. 1234500 points is displayed as 01 23 45 00. This can make debugging easier.

To define the score (6 digits):

.score ds 3

To add C points to the score:

.add_score_c
ld b,2		; This is the number of score bytes - 1
ld hl,score + 2
.add_part
ld a,(hl)
add c
daa
ld (hl),a
ret nc
.add_loop
dec hl
ld a,(hl)
adc 0
daa
ld (hl),a
djnz add_loop
ret

Displaying the score is relatively simple too, using the same routine as above:

.show_score
ld hl,score
ld b,3
.show_loop
ld a,(hl)
inc hl
call bcd_a
djnz show_loop
ret

One Byte per Digit

This is one of the simplest methods of storing and displaying scores. Its main disadvantages are that it uses slightly more memory and can't usually be held in registers, so must be copied around (eg. for high score tables). Its main advantages are that the scores are easy to display, update and can be easily read in a memory editor.

To define the score (6 digits):

.score ds 6

To add a value to the score, you usually decide which digit you wish to add to, then start at that digit, in this routine, B holds the value to add, C is the digit number.

.add_score_b_c
ld a,b
ld hl,score - 2560
ld b,10
add hl,bc
add (hl)
ld (hl),a
sub b
ret c
ld (hl),a
inc c
.add_loop
dec c
ret z
dec hl		;; You can test the value in C here to determine when to increment lives (eg. ld a,c:cp 2:call z,inc_lives)
inc (hl)
ld a,(hl)
sub b
ret nz
ld (hl),a
jr add_loop

Displaying the score is really easy. All you have to do is add the value of the '0' character to each digit:

.show_score
ld hl,score
ld b,6
.show_loop
ld a,(hl)
inc hl
add '0'
call show_char
djnz show_loop
ret

One character per digit

This method is identical to the one above, but you actually store the character representation of each digit, meaning you don't have to add the '0' value to each digit for display. In this case, you could use a generic string display routine to show the score. In this example I'm using a zero terminated string.

To define the score:

.score db '000000',0

This add routine can only increment a particular digit, simplifying it somewhat (eg. Add 1, 10, 100, 1000 to the score). L holds the offset. I've also used another trick here, by assuming the score is page-aligned.

.inc_score
ld h,score / 256
.next_digit
inc (hl)
ld a,(hl)
cp '9' + 1
ret nz
ld (hl),'0'
dec l		; You can test the value in L here to determine when to increment lives (eg. ld a,l:cp 2:call z,inc_lives)
jp p,next_digit
ret

And the routine to display the score is a simple zero terminated string printing routine, just call it with HL pointing to score:

.print_hl
ld a,(hl)
inc hl
or a
ret z
call show_char
jr print_hl

Some More Information

Each of these routines has its advantages and disadvantages, and it usually depends on the type of game you're writing or your own preferences which one you pick to use. A few questions you should consider before deciding which routines to use are:

  • What's the maximum score in my game, or how big would you like the score before it clocks over?
  • How many times in a cycle (eg. frame) does the score get incremented?
  • Will the score be redisplayed each time it's updated, once every cycle, or only rarely (eg. in between turns)?
  • How fast is the character printing routine?
  • Is there going to be a high score shown or a high score table in the game?

    Depending on the answers to these questions you should be able to pick the best routine. For example, it's almost impossible in Frogger to get past 20,000 points, and the score only updates in multiples of 10 points so for this the Single BCD Word method was used with an extra '0' appended to the end. The score would clock after 99,990 points. It's also easy with this method to compare it with the high score each time points are added. Frogger displays both the current player's score and the high score every frame using a very fast character printing routine. Executioner 06:18, 21 September 2007 (CEST)