ID:98823
 
Keywords: programming
"Students of the Ichi school Way of strategy should train from the start with the sword and long sword in either hand. This is a truth: when you sacrifice your life, you must make fullest use of your weaponry. It is false not to do so, and to die with a weapon yet undrawn." Miyamoto Musashi (1584-1645)

I was browsing some source code for a Dragonball Z game recently, when I spotted a little bug, one of many. It was possible for your character's stamina to end up being negative. It wasn't really a big bug, everything carried on working as expected when you had negative stamina, and you could rest to recover to a sensible stamina again. But this got me thinking.

This little bug simply should not have come to be in the first place. Some bugs you can forgive, programming is a complicated thing at times and you can't expect to catch every case first time. But this one should simply never have happened, and yet it does. So Samurais, let me show you a very simple trick that means you never have to worry about this kind of bug.

First, let's look at what we're trying to do with stamina. In this game, it's a percentage. Perhaps in another game it's a value that can grow. But here's the important bit, it is always a number, it can go up and down, and it is never negative. So what we actually have here is not just a number in a variable, there's a little logic involved to make sure the stamina you have is sensible.

Here's a clever little trick I like to use a lot. We'll make a datum to represent this. What's a datum? Whatever you like! It's pretty vague, so hopefully you'll see in our case we're going to make a number that can be restricted. Datums are basically just objects that don't exist on the map, and can be used for these kinds of neat tricks. Here we go, we need a minimum possible value, a current value (our stamina) and a maximum possible value:

restricted_number
var/minimum
var/current
var/maximum

New(var/minimum = 0, var/current = 100, var/maximum = 100)
src.minimum = minimum
src.current = current
src.maximum = maximum

Very exciting, I'm sure. But what's the point? Well, we need to have some way to increase / reduce the current stamina in a way that it doesn't break the minimum / maximum:

    proc/set_current(var/number as num)
number = max(src.minimum, number)
number = min(src.maximum, number)
src.current = number

proc/reduce(var/number as num)
set_current(src.current - number)

proc/increase(var/number as num)
set_current(src.current + number)

That's a bit neater, now we can stop that weird negative stamina kind of bug. How do we check if we're at maximum / minimum stamina though? We could just compare the values, but lets add some procs to make our code easier to read:

    proc/at_min()
return src.current == src.minimum

proc/at_max()
return src.current == src.maximum

Now we're ready to rock. How do we use it? Here's an example, with a rest() proc:

mob/player
var/restricted_number/stamina = new()

verb/Rest()
while(!src.stamina.at_max())
src.stamina.increase(10)
sleep(50) // We increase stamina by 10 every 5 seconds.
src << "You stretch, feeling refreshed!"

verb/Whats_My_Stamina()
src << "You currently have [src.stamina.current] stamina."

verb/Tiring_Work()
while (!src.stamina_at_min())
src.stamina.decrease(10)
sleep(50)
src << "Pfff, you're all tired out."

So what is so special about this? Well, the original bug was basically caused by a problem with checking the allowed values of stamina and rounding off stamina when it's below 0. It probably doesn't even happen in all the different places we wrote it, because we're basically just copying bits of code and possibly had to change it. With the restricted_number, if we need to change it, we do it once and only once, where the datum is defined. It will change how it behaves for all uses, and in the same way. One less avoidable bug!

This doesn't just need to be used to stats, it can be used in calculations where you need to restrict some number to a range of values. Anything, damage calculations, power-up calculations, where-ever you want. Same goes, you can extend this to have different behaviour that suits your games. It's a small weapon, Samurai, but it has many uses. Don't leave it undrawn.

Thank you for reading. Full source code is below, feel free to use as you please:

restricted_number
var/minimum
var/current
var/maximum

New(var/minimum = 0, var/current = 100, var/maximum = 100)
src.minimum = minimum
src.current = current
src.maximum = maximum

proc/set_current(var/number as num)
number = max(src.minimum, number)
number = min(src.maximum, number)
src.current = number

proc/reduce(var/number as num)
set_current(src.current - number)

proc/increase(var/number as num)
set_current(src.current + number)

proc/at_min()
return src.current == src.minimum

proc/at_max()
return src.current == src.maximum

---

Got any neat little snippets like this that you want to share with people? Have a question on the code shown here, or the article in general? Talk to us and share here:
http://www.byond.com/members/BYONDAnime/forum?id=37140
Me like.
"I was browsing some source code for a Dragonball Z game recently"

Wait, what?
SuperAntx wrote:
"I was browsing some source code for a Dragonball Z game recently"

Wait, what?

It's more entertaining than SNL for me at times.
Static-GT wrote:
Me like.

I'm glad you like it! Are there other areas you'd like to see us cover in terms of programming?
Pixel projectiles, like shooting a bullet out of a gun or a beam out of your hand, etc.
BxS0ldi3R wrote:
Pixel projectiles, like shooting a bullet out of a gun or a beam out of your hand, etc.

^That and Pixel Movement.
Lol.
I noticed datums are not used by most people. Good to see someone putting it out there. :)
Stephen001 wrote:
Static-GT wrote:
Me like.

I'm glad you like it! Are there other areas you'd like to see us cover in terms of programming?

How about Bleach, spiritual pressure from the player slowing down the people around them. That be very nice and useful to me.
Static-GT wrote:
Stephen001 wrote:
Static-GT wrote:
Me like.

I'm glad you like it! Are there other areas you'd like to see us cover in terms of programming?

How about Bleach, spiritual pressure from the player slowing down the people around them. That be very nice and useful to me.

In normal terms, move delay within a given radius?
Useful tool, I may add in a few snippets of my own in the future.
Theironx wrote:
Static-GT wrote:
Stephen001 wrote:
Static-GT wrote:
Me like.

I'm glad you like it! Are there other areas you'd like to see us cover in terms of programming?

How about Bleach, spiritual pressure from the player slowing down the people around them. That be very nice and useful to me.

In normal terms, move delay within a given radius?


Yes.
Not to pry but, wouldn't using the built-in BYOND function's be much easier and cleaner? The min() and max() function's to be exact. Albeit, it is nice to have it self contained with the function's only being handled in one area. =)
Teh Governator wrote:
Not to pry but, wouldn't using the built-in BYOND function's be much easier and cleaner? The min() and max() function's to be exact. Albeit, it is nice to have it self contained with the function's only being handled in one area. =)

Well, I am using them. It's easier and cleaner not to have to care though, obviously. Hence the datum handles it for you.
http://pastebin.com/pq2McCC1
There are more possibilities than just those functions presented, though. I like my spin on it, being able to check percentages and whatnot, and probably more things in the future.

Not to revive an old post, but I use it for reference a lot, and felt I should share :3
This may be years old, but it's still a great post in my eyes, and so I thought I'd point out the little typo in the summarised code, just to help the Copy-and-pasters out there.

In set_current() you have "src.number = number" rather than "src.current = number"