It is currently Sun Dec 17, 2017 8:57 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Wed Jun 15, 2016 12:47 am 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1517
Why is it possible to collect only 255 rubies in "The Legend of Zelda" for the NES?

I mean, normally, I would know why this is the case: 0-255 = one byte.

But the rubies are displayed as a decimal number. So, didn't they store the value in a three byte array with one byte representing one decimal digit, like they did with the score in "Super Mario Bros."?

Does "Zelda" really have a "binary number to decimal output" function in the program code? Because if not, why don't the rubies max out at 999 instead?

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Wed Jun 15, 2016 1:53 am 
Offline

Joined: Thu Aug 12, 2010 3:43 am
Posts: 1589
I don't know what it's doing internally but 255 is usually a dead giveaway of using a byte to store the value regardless of how it's being output on screen =P


Top
 Profile  
 
PostPosted: Wed Jun 15, 2016 4:14 am 
Offline
User avatar

Joined: Mon Oct 06, 2014 12:37 am
Posts: 187
As this was Nintendo's first game with save files, keeping the game's stored state simplified would have been advantageous.

The linear usage of RAM, starting from $0656, supports this idea.


Top
 Profile  
 
PostPosted: Wed Jun 15, 2016 4:24 am 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 3192
Location: Mountain View, CA, USA
Rupie count is stored in a single byte (range 0-255) in RAM location $066d. The code turns the decimal value of that byte into a "3-tile sequence". The raw value is literal and not BCD (i.e. a value of $12 will show up as 18). Effectively it's something like this -- it's been a long, long time since I've looked at the code:

Tile/CHR offsets $00 through $09 are literally 0 through 9
Tile/CHR offset $21 is an X (indicating "number of" or "count of"); it's the same tile that's used for "X" in game text
Tile/CHR offset $24 is a blank/empty tile

If the rupie count is between 0 and 9 (1 digit), it displays X followed the 1-digit value, followed by an empty/blank tile. I.e. a rupie count of 3 would render as $210324.

If the rupie count is between 10 and 99 (2 digits), it displays X followed by the 2-digit value. I.e. a rupie count of 49 would render as $210409.

Otherwise, it prints the rupie count as a 3-digit number.

It might also optimise out needing to display tile $24 by simply filling that area of the nametable with $24 by default, then only drawing the ones it needs to. Same end result either way.

And honestly, that's exactly how I'd do it -- only "splitting" the number into individual digits in the routine that needed to draw the proper on-screen tiles to reflect the value/number. The X stuff is a nice cosmetic touch that players don't get hung up on when playing; I never really noticed it until now, actually.

Footnote: bombs (RAM $0658) and keys (RAM $066e) are printed the same way (yup -- you can have up to 255 of those too! The limiting factor is mainly in the game mechanics (how/when you find maximum bomb increases, etc.)). The one exception is the master key, where after you pick that up, the game sets some flag (not sure of RAM location) that essentially ignores the RAM $066e value and instead just prints XA{blank} (tiles $210a24).


Top
 Profile  
 
PostPosted: Wed Jun 15, 2016 11:29 am 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7319
Location: Chexbres, VD, Switzerland
Aligning numbers left is indeed very elegant, and this prohibes working data in BCD in memory. However, other games commonly uses a variant of BCD to store their data, for instance Castlevania uses entierely BCD, despite the NES lacking BCD mode. Other games simply stores digits in the low nybble and waste the high nybble. After all, the NES has 2k of RAM, one byte more or less will rarely make the difference.


Top
 Profile  
 
PostPosted: Wed Jun 15, 2016 11:38 am 
Online

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19353
Location: NE Indiana, USA (NTSC)
Bregalad wrote:
Aligning numbers left is indeed very elegant, and this prohibes working data in BCD in memory.

How does digit-by-digit storage prohibit left alignment? Just do the equivalent of this before you copy the text into the output buffer:
Code:
while (i < NUM_DIGITS - 1 && digits[i] == 0) {
  ++i;
}


Quote:
Other games simply stores digits in the low nybble and waste the high nybble. After all, the NES has 2k of RAM, one byte more or less will rarely make the difference.

Unless it's an RPG or strategy game that has a lot of stats that need to be tracked and displayed.

Among games I've programmed:
  • Concentration Room and "ZapPing" in Zap Ruder use 1-byte binary values for the players' scores and convert them to decimal on display
  • Thwaite stores the score as 3-byte "base 100", where each byte represents two digits from 0 to 99 ($00 to $63), and the ammo stocks are 1-byte binary, all converted to decimal on display
  • RHDE uses 2-byte values from 0 to 65535 for house scores and money amounts, converted to decimal on display
  • Haunted: Halloween '85 stores the kill count as a 2-byte value from 0 to (theoretically) 65535, converted to decimal on display


Top
 Profile  
 
PostPosted: Wed Jun 15, 2016 12:05 pm 
Offline
User avatar

Joined: Sat Sep 07, 2013 2:59 pm
Posts: 1517
Thanks for the replies. So, "Zelda" does convert the number for display. Interesting.

In my own game, there's only the score that needs to be displayed and a timer in certain situations.
Lives cannot go higher than 5. (Which is a design choice. I could increase them to 9 without altering the rest of the code.)
And energy is not shown as a number, but as individual units.

I save these values as one byte per digit since there's still plenty of room in RAM.
And each thing that gets you points can only have one non-zero digit. I.e. you can get 20 or 200 points for one thing, but not 250. Which makes the addition function quite simple.

But I agree that an RPG definitely profits from a conversion function. If you have dozens of statuses that can go from 0 to 50000, it definitely makes a difference whether you have a two bytes unsigned int or a five bytes array per status.

_________________
Available now: My game "City Trouble".
Website: https://megacatstudios.com/products/city-trouble
Trailer: https://youtu.be/IYXpP59qSxA
Gameplay: https://youtu.be/Eee0yurkIW4
German Retro Gamer article: http://i67.tinypic.com/345o108.jpg


Top
 Profile  
 
PostPosted: Wed Jun 15, 2016 5:57 pm 
Offline
User avatar

Joined: Fri Nov 19, 2004 7:35 pm
Posts: 3969
Converting small binary numbers to decimal is not hard, the simplest way to do it is repeated subtraction.
Let's say you have a 16-bit unsigned int (0-65535).
Count how many times you can subtract 10000, 1000, 100, 10, 1. That's your decimal number right there.

_________________
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!


Top
 Profile  
 
PostPosted: Thu Jun 16, 2016 5:56 am 
Offline

Joined: Thu Aug 12, 2010 3:43 am
Posts: 1589
The problem isn't that, the problem is doing it quickly.


Top
 Profile  
 
PostPosted: Thu Jun 16, 2016 7:06 am 
Online

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19353
Location: NE Indiana, USA (NTSC)
How quickly does it need to be? You only need to convert values during those frames when you're updating the status bar. The routine I used in Concentration Room, Thwaite, and Zap Ruder converts 8 bits to 3 digits in 80 cycles, and the routine in RHDE and HH85 converts 16 bits to 5 digits in about 670 cycles. Other faster routines have been posted.


Top
 Profile  
 
PostPosted: Thu Jun 16, 2016 8:52 am 
Offline

Joined: Thu Aug 12, 2010 3:43 am
Posts: 1589
Yeah usually you update like once every many frames =/ I imagine that most games actually just do some rather dumb decimal-to-BCD conversion and move on.


Top
 Profile  
 
PostPosted: Thu Jun 16, 2016 10:09 am 
Offline

Joined: Thu Aug 28, 2008 1:17 am
Posts: 591
Sik wrote:
The problem isn't that, the problem is doing it quickly.


What's the fast method for doing this on the NES?

I think having a 256 LUT, which outputs 0 to 99. And for the last digit in the 100s place, simply check of the original byte is greater than 200 (populate a 2) or greater than 100 (populate a 1). Since NES doesn't have decimal mode like other 65x, you can't easily cascade two more bytes in this process unfortunately.

_________________
__________________________
http://pcedev.wordpress.com


Top
 Profile  
 
PostPosted: Thu Jun 16, 2016 10:17 am 
Online

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19353
Location: NE Indiana, USA (NTSC)
This is what Thwaite and Concentration Room use. For 69, it produces $0000 = $06 and A = $09. For 246, it produces $0000 = $24 and A = $06.
Code:
; 8-bit binary to decimal converter
; copyright 2010 Damian Yerrick
; License: WTFPL http://www.wtfpl.net/

.macro bcd8bit_iter value
  .local skip
  cmp value
  bcc skip
  sbc value
skip:
  rol highDigits
.endmacro

;;
; Converts a decimal number to two or three BCD digits
; in no more than 84 cycles.
; @param A the number to change
; @return A: low digit; $0000: upper digits as nibbles
.proc bcd8bit
highDigits = 0
  asl highDigits
  asl highDigits

  ; Each iteration takes 11 if subtraction occurs or 10 if not.
  ; But if 80 is subtracted, 40 and 20 aren't, and if 200 is
  ; subtracted, 80 is not, and at least one of 40 and 20 is not.
  ; So this part takes up to 6*11-2 cycles.
  bcd8bit_iter #200
  bcd8bit_iter #100
  bcd8bit_iter #80
  bcd8bit_iter #40
  bcd8bit_iter #20
  bcd8bit_iter #10
  rts
.endproc


And it uses the "base 100" trick because of this lack of cascading.
Code:
;;
; Adds between 1 and 255 points to the score.
; X, Y, and memory (apart from score) are unchanged.
.proc addScore
  clc
  adc score1s
  bcc notOver256
  inc score100s
  inc score100s
  adc #55
notOver256:
  cmp #100
  bcc notOver100
  sbc #100
  inc score100s
  bcs notOver256
notOver100:
  sta score1s
  lda bgDirty
  ora #BG_DIRTY_STATUS
  sta bgDirty
  rts
.endproc


Top
 Profile  
 
PostPosted: Thu Jun 16, 2016 1:10 pm 
Offline

Joined: Thu Aug 12, 2010 3:43 am
Posts: 1589
tomaitheous wrote:
I think having a 256 LUT, which outputs 0 to 99.

You only need 100 entries for that ;P

But yeah that's fast and feasible for homebrew, back in the day it wasn't that feasible with tiny PRG-ROMs though where every byte mattered, hence the slower methods. (well, and not every programmer being clever enough or having enough time to come up with a good mehtod)


Top
 Profile  
 
PostPosted: Thu Jun 16, 2016 1:37 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10169
Location: Rio de Janeiro - Brazil
Sik wrote:
You only need 100 entries for that ;P

Yes, just handle the hundreds digit first and whatever's left is guaranteed to be 99 or less.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 21 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 6 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group