gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Coding > Okay, I want to program sprites that show any number, like

#14729 - LOst? - Mon Jan 12, 2004 1:45 am

Okay, I want to program sprites that show any number, like an extra life counter, or a debug output.

I want one function to show decimal numbers, and one other function that can show hexadecimal numbers.

I have the numbers and the sprites set up already, so what should I do to output a integer number on screen?

I could have done this easy if the Gameboy had faster modulo calculation. But it hasn't, so anyone's willing to help me out?

And please, I can't program inline assembler because of my easy compiler settings, so no dtoa, itoa with assembler in it :P

#14731 - yaustar - Mon Jan 12, 2004 1:54 am

How complex do you want it to be?

I have never done this before but am interested in how it turns out :)

For something like a lives counter, I would have an animated sprite that goes from 0-9. So if I wanted to increment it, I would go to the next animation frame.

this may help as well
http://www.gbadev.org/download.php?section=demos&filename=bgcounter.zip
_________________
[Blog] [Portfolio]

#14733 - sajiimori - Mon Jan 12, 2004 2:28 am

Is the problem just figuring out what the digits are?

For relatively small numbers (i.e. health counters but not memory addresses), it's reasonable to use repeated subtraction.
Code:

get_digits(int num, int* hundreds, int* tens, int* ones)
{
    int h = 0, t = 0;

    while(num >= 100)
        ++h, num -= 100;
    while(num >= 10)
        ++t, num -= 10;

    *hundreds = h;
    *tens = t;
    *ones = num;
}

main()
{
    int first, second, third;
    int health = 548;
    get_digits(health, &first, &second, &third);
    printf("%d%d%d <-- should say 548",
        first, second, third);
}

Is speed really so important for debug output? If not, just use sprintf.

#14740 - DekuTree64 - Mon Jan 12, 2004 4:17 am

Here's another possibility, reciprocal multiplication. I know you said you don't want ASM, but for anyone else who does, here's my int-to-string function:
Code:
/*
converts int to string
args: int
return: char*
Writes digits to global array tempStr, but could easily be modified to take a string as a second argument
*/

.global itoa
.arm
.align 2
itoa:
stmfd sp!, {r4}
movs r4, r0
rsbmi r0, r0, #0  @make sure we're working with a positive number
ldr r12, =tempStr @global array
add r12, r12, #15 @start at the end of the string and work our way back
mov r1, #0
strb r1, [r12]
ldr r1, =429496729 @.1 << 32

itoa_loop:
umull r2, r3, r0, r1 @r3 = num/10
mov r2, r3, LSL #3
add r2, r2, r3, LSL #1 @x*8+x*2=x*10
sub r0, r0, r2 @subtract (num/10)*10 from num to get remainder
add r0, r0, #'0'
strb r0, [r12, #-1]!
movs r0, r3
bne itoa_loop

cmp r4, #0
bpl itoa_return
mov r0, #'-'
strb r0, [r12, #-1]! @don't forget to add a - sign if the number was originally negative

itoa_return:
ldmfd sp!, {r4}
mov r0, r12
bx lr


I'll see if I can whip up a C version real quick. It will only be able to deal with 16-bit numbers since I can't use 64-bit multiplication, but that's usually enough anyway.

Code:
char *itoa(int num)
{
   int tmp, tmp2, sign;
   char *str = tempStr + 15;
   *str = 0;
   if(num < 0)
   {
      sign = 1;
      num = -num;
   }
   else
      sign = 0;
   do
   {
      tmp = num * 6553;
      tmp >>= 16;
      tmp2 = tmp << 3;
      tmp2 += tmp << 1;
      num -= tmp2;
      num += '0';
      *--str = num;
      num = tmp;
   } while(num != 0); //so we run through at least once, incase num==0 to begin with
   if(sign)
      *--str = '-';
   return str;
}

Can't guarantee that will work, but you get the idea.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#14741 - dagamer34 - Mon Jan 12, 2004 4:22 am

You could also use sprintf so that the number becomes characters.
It also makes it easier for decimal places.

Just because its a number doesn't mean it has to act like one.
_________________
Little kids and Playstation 2's don't mix. :(

#14743 - poslundc - Mon Jan 12, 2004 5:35 am

If you want the functionality of sprintf on numbers but without the overhead of the library routine, you can download my posprintf library. Runs in Thumb mode from the ROM, so it won't be as quick as DekuTree's algorithm, but it lets you print to any string without having to "print backwards" and you can format your numbers with leading zeros/spaces.

Plus it's a kick-ass algorithm someone in these forums pointed me to. Everything is shifts and adds for numbers up to +/-65535.

Dan.

#14746 - LOst? - Mon Jan 12, 2004 7:20 am

dagamer34 wrote:
You could also use sprintf so that the number becomes characters.
It also makes it easier for decimal places.

Just because its a number doesn't mean it has to act like one.


I can't use sprintf because of my GCC telling me dtoa doesn't work.

I will try out all of your codes. Thanks :)

#14747 - Lord Graga - Mon Jan 12, 2004 8:12 am

I have used this writing method with success :)

The problem is that there's only 128 sprites :B

Code:
u8 used_sprites;
const char font_name[43] = {
   ' ','_','-','a','b',
   'c','d','e','f','g',
   'h','i','j','k','l',
   'm','n','o','p','q',
   'r','s','t','u','v',
   'w','x','y','z','#',
   '0','1','2','3','4',
   '5','6','7','8','9',
   '!','.',','
};
const u8 font_width[43] = {
   4,6,4,5,6,
   6,6,6,5,5,
   5,2,5,6,5,
   7,6,6,5,7,
   5,5,6,5,5,
   7,5,5,6,8,
   6,5,5,5,5,
   5,5,5,6,5,
   2,2,2
};

void Write(char *text, u16 y){
   int i,letter,textlength;
   int writetext[100];

   letter = 0;
   while(text[letter]!=NULL)   {
      for(i=0;i<43;i++) if(text[letter] == font_name[i]) writetext[letter] = i;
      letter++;
   }
   for(i=0;i<letter;i++) textlength += font_width[writetext[i]]+1;
   textlength = 120 - textlength/2;
   for(i=0;i<letter;i++)   {
      if(writetext[i])      {
         sprites[used_sprites].attribute0 = COLOR_256|y;
         sprites[used_sprites].attribute1 = textlength;
         sprites[used_sprites].attribute2 = 512+writetext[i]*2;
         used_sprites ++;
      }
      textlength += font_width[writetext[i]]+1;
   }
   CopyOAM();
}

#14756 - poslundc - Mon Jan 12, 2004 3:54 pm

Lord Graga wrote:
I have used this writing method with success :)

The problem is that there's only 128 sprites :B


Check it out.

Dan.

#14770 - LOst? - Mon Jan 12, 2004 6:22 pm

Lord Graga wrote:
I have used this writing method with success :)

The problem is that there's only 128 sprites :B

Code:
u8 used_sprites;
const char font_name[43] = {
   ' ','_','-','a','b',
   'c','d','e','f','g',
   'h','i','j','k','l',
   'm','n','o','p','q',
   'r','s','t','u','v',
   'w','x','y','z','#',
   '0','1','2','3','4',
   '5','6','7','8','9',
   '!','.',','
};
const u8 font_width[43] = {
   4,6,4,5,6,
   6,6,6,5,5,
   5,2,5,6,5,
   7,6,6,5,7,
   5,5,6,5,5,
   7,5,5,6,8,
   6,5,5,5,5,
   5,5,5,6,5,
   2,2,2
};

void Write(char *text, u16 y){
   int i,letter,textlength;
   int writetext[100];

   letter = 0;
   while(text[letter]!=NULL)   {
      for(i=0;i<43;i++) if(text[letter] == font_name[i]) writetext[letter] = i;
      letter++;
   }
   for(i=0;i<letter;i++) textlength += font_width[writetext[i]]+1;
   textlength = 120 - textlength/2;
   for(i=0;i<letter;i++)   {
      if(writetext[i])      {
         sprites[used_sprites].attribute0 = COLOR_256|y;
         sprites[used_sprites].attribute1 = textlength;
         sprites[used_sprites].attribute2 = 512+writetext[i]*2;
         used_sprites ++;
      }
      textlength += font_width[writetext[i]]+1;
   }
   CopyOAM();
}


Thank you, but still I'm not after making a font with sprites. All I want is to have some statistic over the game, like a score and a life counter. I will use a group of different sized sprites for use with the menus, such as Option, and Start Game, Press Start, etc...

#14833 - LOst? - Tue Jan 13, 2004 3:32 am

sajiimori wrote:
Is the problem just figuring out what the digits are?

For relatively small numbers (i.e. health counters but not memory addresses), it's reasonable to use repeated subtraction.
Code:

get_digits(int num, int* hundreds, int* tens, int* ones)
{
    int h = 0, t = 0;

    while(num >= 100)
        ++h, num -= 100;
    while(num >= 10)
        ++t, num -= 10;

    *hundreds = h;
    *tens = t;
    *ones = num;
}

main()
{
    int first, second, third;
    int health = 548;
    get_digits(health, &first, &second, &third);
    printf("%d%d%d <-- should say 548",
        first, second, third);
}

Is speed really so important for debug output? If not, just use sprintf.


Very good indeed! I really liked this one because it isn't using any multiply, and it's really small.

Now I finally have everything I need to make a GBA game. This is ganna be fun! Thank you

#14871 - haduken - Wed Jan 14, 2004 12:09 am

So, cxutting through the code, you're going to want a seperate variable for each digit of the display (one for ones, tens, hundreds, et cetera). When each variable gets to a certain number (9 for a 0-9 display, F for 0-F hex), the next digit will be incremented. I've never tried this (not on a GBA, anyway), but that's what everyone ELSE seems to be doing, so...
_________________
There were worms, lots of worms, worms that made me crazy,..

#14874 - LOst? - Wed Jan 14, 2004 3:44 am

haduken wrote:
So, cxutting through the code, you're going to want a seperate variable for each digit of the display (one for ones, tens, hundreds, et cetera). When each variable gets to a certain number (9 for a 0-9 display, F for 0-F hex), the next digit will be incremented. I've never tried this (not on a GBA, anyway), but that's what everyone ELSE seems to be doing, so...


That's what I'm been doing. And it works great!

#14877 - poslundc - Wed Jan 14, 2004 5:40 am

That's fine for small numbers, but it gets prohibitively slow very fast (that was fun to say). Trust me, a multiply is a LOT cheaper than looping over a subtraction.

Use DekuTree's method or my posprintf (or even just implement the algorithm it is based on yourself if you'd like) to deal with larger numbers or larger volumes of numbers. Repeated subtraction works and is intuitive, but it is a very naive approach to a problem that is easily solved by only slightly more sophisticated methods.

Dan.

#14924 - tepples - Wed Jan 14, 2004 8:29 pm

Here's a less naive version of repeated subtraction that's admittedly not exactly as fast as multiplication by the reciprocal, but it works up to UINT_MAX even in the absence of 32x32=64 bit multiplies (such as in a self-contained Thumb module). It also adapts readily to 8-bit microcontrollers without hardware multiply such as the 6502 processor used in the NES.
Code:
static const unsigned long utoa_bases[39] =
{
  4000000000U,
  2000000000,
  1000000000,  /* 1G */
   800000000,
   400000000,
   200000000,
   100000000,  /* 100M */
    80000000,
    40000000,
    20000000,
    10000000,  /* 10M */
     8000000,
     4000000,
     2000000,
     1000000,  /* 1M */
      800000,
      400000,
      200000,
      100000,  /* 100K */
       80000,
       40000,
       20000,
       10000,  /* 10K */
        8000,
        4000,
        2000,
        1000,  /* 1K */
         800,
         400,
         200,
         100,  /* 100 */
          80,
          40,
          20,
          10,  /* 10 */
           8,
           4,
           2,
           1
};

/* utoa() ******************************
   Convert an unsigned integer to an ascii string.
*/
size_t utoa(char *dst, unsigned long src)
{
  unsigned int i;
  unsigned int pval = 4;
  unsigned int digit = 0;
  unsigned int len = 0;

  for(i = 0; i < 39; i++)
  {
    if(src >= utoa_bases[i])
    {
      src -= utoa_bases[i];
      digit += pval;
    }
    pval >>= 1;
    if(pval == 0)
    {
      pval = 8;
      if(digit || len)
        len++;
      if(len)
        *dst++ = digit + '0';
      digit = 0;
    }
  }
  *dst = 0;  /* zero-terminate the string */
  return len;
}

size_t stoa(char *dst, signed long src)
{
  if(src < 0)
  {
    *dst++ = '-';
    return 1 + utoa(dst, -src);
  }
  else
  {
    return utoa(dst, src);
  }
}

_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.