Difference between revisions of "Programming:Display and update Scores"
Executioner (Talk | contribs) (→Multiple BCD Bytes) |
|||
(One intermediate revision by one other user not shown) | |||
Line 1: | Line 1: | ||
− | == Methods for Storing Scores | + | == Methods for Storing, Displaying and Updating Scores == |
There are a number of methods for storing a Score in memory, here are a few examples:<br> | There are a number of methods for storing a Score in memory, here are a few examples:<br> | ||
+ | |||
=== A Single Word === | === 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.<br> | 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.<br> | ||
Line 214: | Line 215: | ||
</pre> | </pre> | ||
− | More to | + | === 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.<br> | ||
+ | <br> | ||
+ | To define the score (6 digits): | ||
+ | <pre> | ||
+ | .score ds 6 | ||
+ | </pre> | ||
+ | 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. | ||
+ | <pre> | ||
+ | .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 | ||
+ | </pre> | ||
+ | Displaying the score is really easy. All you have to do is add the value of the '0' character to each digit: | ||
+ | <pre> | ||
+ | .show_score | ||
+ | ld hl,score | ||
+ | ld b,6 | ||
+ | .show_loop | ||
+ | ld a,(hl) | ||
+ | inc hl | ||
+ | add '0' | ||
+ | call show_char | ||
+ | djnz show_loop | ||
+ | ret | ||
+ | </pre> | ||
+ | |||
+ | === 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.<br> | ||
+ | <br> | ||
+ | To define the score: | ||
+ | <pre> | ||
+ | .score db '000000',0 | ||
+ | </pre> | ||
+ | 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. | ||
+ | <pre> | ||
+ | .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 | ||
+ | </pre> | ||
+ | And the routine to display the score is a simple zero terminated string printing routine, just call it with HL pointing to <b>score</b>: | ||
+ | <pre> | ||
+ | .print_hl | ||
+ | ld a,(hl) | ||
+ | inc hl | ||
+ | or a | ||
+ | ret z | ||
+ | call show_char | ||
+ | jr print_hl | ||
+ | </pre> | ||
+ | |||
+ | == 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:<br> | ||
+ | <br> | ||
+ | <li>What's the maximum score in my game, or how big would you like the score before it clocks over? | ||
+ | <li>How many times in a cycle (eg. frame) does the score get incremented? | ||
+ | <li>Will the score be redisplayed each time it's updated, once every cycle, or only rarely (eg. in between turns)? | ||
+ | <li>How fast is the character printing routine?<br> | ||
+ | <li>Is there going to be a high score shown or a high score table in the game?<br> | ||
+ | <br> | ||
+ | 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. | ||
+ | |||
+ | [[User:Executioner|Executioner]] 06:18, 21 September 2007 (CEST) | ||
− | [[ | + | [[Category:Programming]] |
Latest revision as of 14:28, 8 October 2009
Contents
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:
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)