Since P8 has a 32k limit on numbers, what's the best way (or a way) to have numbers in the 100 thousands or millions and so on?

I mean, I guess I could super fake it by just printing out zeros after a low score integer but is that really the best way to deal with it?

Well, you've got two ways...

- Fake it!

Example:

print(score.."00",0,0,7) |

- Use a combination of 2 variables that can preferably calculate to 9999.

Example:

print(score1..score2,0,0,7) |

This is from my Pico Racer game (though it actually uses tens of thousands):

function addscore(points_to_add) ones += points_to_add while ones >= 1000 do ones -= 1000 thousands += 1 end end |

It will of course overflow if you have e.g. 999 in ones and try to add 32000 points. If you want to avoid the overflow with huge scores you need to deal with the thousands as well when adding.

Then you have to print the thousands and ones so that the ones are zero padded (000, 001, 002 etc.). Didn't test the following but it should work:

local score_string = "00"..ones print(thousands..sub(score_string, #score_string - 2,#score_string)) |

EDIT: fixed a stupid typo in addscore()

In combo pool, I tried to simply store the score divided by 100, so using the fractionnal part to have more values. It's not totaly accurate but it was working good enough. I also added a 0 to make the score more impressive. Here is the code to display it, but there is probably a better way :

mulint = 100 -- when adding score : score += addscore/mulint function getscoretext(value) local fracscore = flr((value%1)*mulint+0.5) local textscore = (fracscore%mulint).."" local floorscore = flr(value) + flr(fracscore/mulint) if floorscore > 0 then if(#textscore<2) textscore = "0"..textscore textscore = floorscore..textscore end return textscore end -- then displaying the score : print(getscoretext(score)) |

In theory you could divide by 2^16 I think, but retrieving the number to display it may become hard, if someone as a simple code for that, it would be great.

Ok, I did some more tests with only one value to store the score, and I have something working prety well until about 300 millions. I check accuracy as well as I could. You must never add more than 32k to the score at a time obviously.

Here is the code :

mulint = 2^14 -- to add an integer value to the score: score += addscore/mulint -- convert a number to a string, padding with 0 to reach size n: function tostr(v,n) local s = ""..v local t = #s for i=1,n-t do s="0"..s end return s end -- get the text from a score value: function getscoretext(val) local low = (val * mulint)%mulint local high = flr(val) local begin = flr(high*1.6384) local last = flr(((high * 1.6384)%1)*10000+0.5) local nlow = low+last local rest = nlow%10000 local nhigh = begin+flr(nlow/10000) return tostr(nhigh,5)..tostr(rest,4) end -- print a score: print(getscoretext(score)) |

Tell me if you find any errors

@kometbomb: that's a really simple yet elegant solution, I like it! Might actually have to steal that one for my games :P

@kometbomb I didnt try that, but the precision displayed by a print of a fractionnal is not great. And asking a decimal version of a fixed point encoded value will always give an approximation.

I also quickly made a small "library" following your method, and it maybe easyer to use. Here it is :

function newscore() local s = {u=0,k=0} s.add = function (this,p) this.u += p%10000 this.k += flr(p/10000) while this.u>=10000 do this.u -= 10000 this.k += 1 end while this.u<0 do this.u += 10000 this.k -= 1 end end s.text = function (this) local vunit = this.u local kilo = ""..this.k if this.k<0 then kilo = "-"..(abs(this.k)-1) vunit = abs(((this.u)-10000)) end local units = "000"..vunit return ""..kilo..sub(units, #units-3,#units) end return s end -- here some sample use : score = newscore() score:add(1000) score:add(-30000) print(score:text()) |

@NuSan: Yeah, I did some testing and adding 0.01 for one point gets inaccurate really quick.

I was just thinking over the morning coffee, remembering programming in 6502 assembler that there could be a function that updates a number and returns the carry like this:

function adc(number,add) number += add local carry = 0 -- could probably get the carry -- with flr(number/1000) but I -- am not 100 % sure so let's -- be naive while number >= 1000 do carry += 1 number -= 1000 end return carry end |

And now you can cascade it. E.g.:

ones = 0 thousands = 0 millions = 0 billions = 0 adc(billions, adc(millions, adc(thousands, adc(ones, p)))) |

And you can similarly come up with a function that prints with zero padding (it's the same for the ones, thousands, millions etc.). This wastes a lot of space compared to your magic, might be relevant when saving e.g. a TOP 50 list in the cart memory.

Just use all 32 bits of the S15.16 fixed-point numbers. Under the hood, they're just integers with an implied decimal point between bits 15 and 16.

If you enjoy retro programming, you should definitely learn more about fixed-point numbers. I'll give a quick overview, but I'm a terrible teacher and I strongly suggest learning about fixed-point math on your own time.

I'll have a TL;DR at the end for those who just want to know how to solve this particular problem for now.

PICO Lua holds values where the integer portion is shifted left 16 so it can rest in the upper 16 bits of a native 32-bit integer on the real cpu. Let me give you an example...

Let's say you want to assign 10 to x:

`x = 10`

You're actually saying something like this to the Lua interpreter:

`x.internal = SHL(10,16)`

Or, if we write it directly in hex:

`x.internal = 0x000a0000`

The lower 16 bits are the fraction. For integer values in lua, it's always going to come out 0x????0000. For non-integers, you simply multiply the real fraction portion by 65536 (0x10000) and those are the bits to put in the lower 16.

For instance, if you want to encode 10.5, the fractional portion is .5, and .5 * 65536 is 32768, or 0x8000. So...

`x = 10.5`

...is...

`x.internal = 0x000a8000`

As it happens, multiplying by 65536 IS shifting left 16. You're not really doing two operations, one for the integer and one for the fraction. It's all just one shift by 16.

Helpfully, Lua lets us write non-integers with hexadecimal. So this:

`x = 10.5`

Could actually be written like this:

`x = 0x000a.8000`

If we simply take those bits and shift them left 16 bits, which is the same thing as shifting the decimal point right 16 bits, we can see how simple the underlying operation really is:

`x.internal = SHL(0x000a.8000,16) = 0x000a8000`

So if you want to store something in those bottom bits, you just have to do as a fraction. Hex fractions are easiest to deal with for literals, so I suggest you use them. If you don't know hex... well, you should learn hex. :)

**TL;DR:**

We need to store integer values in the bottom 16 bits, in the fraction. We can do this by (conceptually) shifting the integer portion right 16:

```
Think: need to add 100 to massive score
Do:
```

score += SHR(100,16) --or, using a hex literal directly score += 0x0000.0064 |

And...

```
Think: need to print score
Do: [b][i]EDIT: See better version four posts down[/i][/b]
```

local s = "" local v = score while (v!=0) do -- modulo by the underlying version of 10 local r = v % 0x0000.000a -- bring it up where lua will stringify it correctly r = SHL(r, 16) -- prefix the new digit onto the score string s = r..s -- move to the next digit v /= 10 end print(s) |

This math shouldn't lose any precision because it's all integers under the hood, no underflows or anything.

You guys were having issues with using tens and hundreds and thousands because they aren't powers of two, but shifting is all about powers of two and it can always be done cleanly. The only time we use tens is to convert the value to a human-readable decimal string.

@kometbomb : that's a prety simple way to extend the number as high as you want, wich can be usefull.

# Felice : thanks, I knew there was a clean way to extract the string. Once you think about extracting each digits separatly, it become way easyer.

I tried changing it to handle negative score, tell me if (or when ^^) you see a cleaner version :

function getscoretext(val) local s = "" local v = val while (v!=0) do -- modulo by the underlying version of 10 local r = v % 0x0000.000a -- bring it up where lua will stringify it correctly r = shl(r, 16) -- prefix the new digit onto the score string if val<0 then s = ((10-r)%10)..s else s = r..s end -- move to the next digit v /= 10 end if val<0 then s = "-"..s end return s end |

This is the smallest I can make it, for the sake of tokens+mem:

function getscoretext(val) local s = "" local v = abs(val) while (v!=0) do s = shl(v % 0x0.000a, 16)..s v /= 10 end if (val<0) s = "-"..s return s end |

Note that you could obviously just draw your score to screen during the loop, to avoid making half a dozen temporary strings, since Lua annoyingly insists strings must be immutable and constantly makes new copies when you manipulate them.

So I just got around to trying all of this out and I think it's working...thanks to everyone that contributed. Lord knows I would have *never* come up with such a elegant solution. All that math and Lua knowledge is beyond me.

Here's how I'm using it.

I use the shr() function to add to my score. So here, adding 15,000 points.

score += shr(15000,16) |

Then I use Felice's function to display it

print(getscoretext(score), 0,0, 7) |

The only change I made to the function was so that it would display a zero score. I just changed the val check to be val<=0 and instead of the result being the dash, it's just a 0 character. Seems to work fine.

The only thing I did notice is that if you add a number greater than 32,000 it gets wonky...which makes sense. As long as I don't give any single thing a point value greater than 32k, it's all good.

Thanks again, everyone. I know this will be the defacto thread for anyone looking to have nice, big, fat scores.

@matt

You're right that it needs a 0 condition. However, the loop ends when v==0, so you can't use v to check for 0 or sign. That's why it refers back to the original val to find the sign to prefix. It should also use val for the 0 condition.

Also, the fix would probably be to use a repeat..until loop instead of while..do. E.g.:

function getscoretext(val) local s = "" local v = abs(val) repeat s = (v % 0x0.000a / 0x.0001)..s v /= 10 until v==0 if (val<0) s = "-"..s return s end |

That way it always does one iteration and that covers the 0 case.

I've just realized that I basically failed to implement itoa() correctly due to missing an obvious edge case. I have brought shame on my CS104 professor.

Speaking of edge cases, this function will also fail if your player has -2147483648 points. So be aware of that. Just in case it happens.

Like this? https://www.lexaloffle.com/bbs/?tid=2601

Sure, I'm using an array to store the value of each digit [0-9], and an array to store the sprite index (it's mandatory).

digits={} sprites={} |

Next, i'm using a function that add a value to the score. It increment each digit until it reach (1)0, then increase the next digit, from the lowest to the greatest (right to left):

function add_score(h) local s,i,d=sprites,9,digits if(h<0)printh("ERR: add_score >32787")return while h>0 do local a=h%10 if(s[i]+a>9)h+=10 s[i]=(s[i]+a)%10 d[i]=64+s[i] h=flr(h/10) i-=1 end end |

Take care, if the added value is too big, it will be a negative value.

Hope it helps.

[Please log in to post a comment]