Tiredness

Started by Vincent, Mon 10/11/2014 21:08:00

Previous topic - Next topic

Vincent

Good evening to all AGSer.

I was building a function that is used to calculate the tiredness of the main character when he tries to perform a run.
And to calculate the opposite, when he is not moving (so that can recover his energy).
I can get everything working except for one step.
gRunToolbar change his graphic so quickly.
I tried out different ways but i can't make it work as i wish (slowly)
Someone could help me out here ? I have something like this.

Code: ags


bool ModeWalk, ModeRun;
int tiredness, time = -1; // time that detain the player is not moving

function Update_Runtoolbar()
{
  if (ModeRun && player.Moving) // running
  {
    tiredness += 1;
    if (tiredness >= 30 && gRunToolbar.BackgroundGraphic == 56) gRunGraphic.BackgroundGraphic = 57; // start growing
    // etc etc
  }
  else 
  {
    if (!player.Moving && tiredness >= 10) // should mean that he was running before...
    {
      time += 1;
      if (time >= 40) 
      {
        int time_recover = 0; 
        while(time_recover <= 30)
        {
          time_recover ++; // start recover energy
          // here is the problem, too much quickly change graphic. 
          // i tried lately here to put a SetTimer... but with no lucky at all
          if (gRunToolbar.BackgroundGraphic == 57 && IsTimerExpired(1)) // i though a timer was the ideal here
          // but doesn't work...
            { gRunToolbar.BackgroundGraphic = 56; } // go back the graphic for recover
          // etc etc
          tiredness -= 1;
        }
          if (tiredness < 29) time = 0; time_recover = 0; // send everything to back
      }
    } 
  }
} 



Why the SetTimer does not work for what I'm trying to do ? :-\
It's very likely that I'm doing it in worst way possible.
I am aware of...

monkey0506

#1
I've added some comments throughout your code, and I think that the problem doesn't really lie in the built-in timers, but your own logic. If the player is running and moving, you increase their "tiredness" until they stop moving for at least 1 second. But then, unless their tiredness was at least 60, you're completely resetting their tiredness all at once, in a single game loop. There isn't even a single frame in-between for you to update graphics, because it happens all at once. Note that if you want to create a blocking interaction for recovery, then you could call Wait(1) inside of your while loop, but that would be a terrible idea, because then the player would lose control of the game to a blocking event. Instead, you should strip out the while loop altogether, and extend the scope of the time_recover variable to the entire script (e.g., place it at the top of the script with your other variables). Then you can check and/or update it appropriately in the same way you're updating your other variables. You shouldn't need to use SetTimer at all (even though you haven't shown any code where you actually called it anyway).

Code: ags
bool ModeWalk, ModeRun;
int tiredness, time = -1; // time that detain the player is not moving
 
function Update_Runtoolbar()
{
  if (ModeRun && player.Moving) // running
  {
    tiredness += 1;
    if (tiredness >= 30 && gRunToolbar.BackgroundGraphic == 56) gRunGraphic.BackgroundGraphic = 57; // start growing
    // etc etc
  }
  else // !ModeRun || !player.Moving
  {
    if (!player.Moving && tiredness >= 10) // should mean that he was running before...
    {
      time += 1;
      if (time >= 40) // presumably this is called once per game loop, default 40 loops per second, this will happen after 1 second of player not moving while being tired
      {
        int time_recover = 0; 
        while (time_recover <= 30) // this is a tight loop, should run in under 1 game loop
        {
          time_recover ++; // start recover energy
          // here is the problem, too much quickly change graphic. 
          // i tried lately here to put a SetTimer... but with no lucky at all
          // note that the check occurs 30 times, but within a single game loop
          // also note that there is no SetTimer call in this code snippet
          if (gRunToolbar.BackgroundGraphic == 57 && IsTimerExpired(1)) // i though a timer was the ideal here
          // but doesn't work...
            { gRunToolbar.BackgroundGraphic = 56; } // go back the graphic for recover
          // etc etc
          tiredness -= 1; // tiredness is always decreased by 31 each game loop (0..30, inclusive)
        }
        // note that unless tiredness was at least 60, this will always be true
        if (tiredness < 29) time = 0; // note lack of braces means that only resetting the time variable depends on tiredness variable
        // note that resetting the local variable time_recover is redundant -- it only exists within this scope which is ending
        time_recover = 0; // send everything to back
        // also note that tiredness is not reset; if player's tiredness was only 10, they may actually gain an energy boost just by stopping for 1 second
      }
    } 
  }
}

Vincent

Thank You Very Much monkey_05_06.

I have to focus firstly on a better logic, that has a meaning at least :-D
I'm reading and reading your comments again. They are vital help.
Thanks also for having responded me.
I'm going to fix this "logic", then I'd love to let you see.
Still Big Thanks :)

Vincent

#3
Ciao monkey_05_06.

I tried so far a better logic to implement on this function.
On reflection, I thought it is not necessary a while expression.
Your help was very useful. Please, feel free to correct me if you see something wrong.
And of course, if you want.


Code: ags

#define RUN_THRESHOLD 240
#define RECOVER_DELAY 240

int run_counter = 0, recover_counter = 0;
bool ModeWalk, ModeRun;

function Update_Runtoolbar()
{
  if (ModeRun && player.Moving) // Lose energy if player move in ModeRun
  {
    run_counter ++;
  }
  else if (run_counter > 0) run_counter --;
  
  if (run_counter > RUN_THRESHOLD)
  {
    run_counter = 0;
    if (run_counter <= 5)
    {
      if (gRunToolbar.BackgroundGraphic == 56) { gRunToolbar.BackgroundGraphic = 57; } 
      else if (gRunToolbar.BackgroundGraphic == 57) { gRunToolbar.BackgroundGraphic = 58; } 
      // ETC ETC
    }
   }
  // Recover energy if player stands still
  if (!player.Moving || ModeWalk) { recover_counter ++; }
  else if (recover_counter > 0) recover_counter --;

  if (recover_counter > RECOVER_DELAY)
  {
    recover_counter = 0;
    if (recover_counter <= 5)
    {
      if (gRunToolbar.BackgroundGraphic == 57) 
      { gRunToolbar.BackgroundGraphic = 56; player.AnimationSpeed = 3;} 
      else if (gRunToolbar.BackgroundGraphic == 58) 
      { gRunToolbar.BackgroundGraphic = 57; player.AnimationSpeed = 5; }
      // ETC ETC
    }
  }
}


I managed to make it work as i wished.:-D HAPPY !
But it was because of you that have made me think further.
I can go to sleep now, :-D 02:48 am here

Anpiel

This is awesome! My girlfriend and I were talking about something stamina related and now we have something like that!
Angels they fell first but I'm still here

Vincent

#5
Nice, thank you

monkey0506

#6
I was going to comment on this before, but I forgot.

The logic in this code is simply broken. What you are doing is this:

* If player is in run mode and moving, increase run_counter
* If player is not in run mode or is not moving or both, decrease run_counter
* If run_counter is greater than 240 (player has been actively running for at least 240 game loops, e.g., 6 seconds without stopping):
|-- * reset run_counter immediately to 0
|-- * then check if run_counter is less than or equal to 5 (always true, since you know that it's 0)
|-- |-- * if it is (it always is), then update gRunToolbar.BackgroundGraphic
* If player is not moving or is in walk mode or both, increase recover_counter
* If player is moving or is not in walk mode or both, decrease recover_counter
* If recover_counter is greater than 240 (player has been standing still or in walk mode for at least 240 game loops, e.g., 6 seconds):
|-- * reset recover_counter immediately to 0
|-- * then check if recover_counter is less than or equal to 5 (always true, since you know that it's 0)
|-- |-- * if it is (it always is), then update gRunToolbar.BackgroundGraphic and player.AnimationSpeed

The problem (as I see it) is that you're treating the loss of energy and the recovery of energy as two separate, totally unrelated things. Here's what I would do instead:

* Use a single variable for player's energy, say "playerStamina" (initialize to 6 seconds).
* Use a separate variable to delay energy recovery, say "playerRecoverDelay" (initialize to 1 second).
* If player is in run mode and moving and stamina is not completely depleted:
|-- * Decrease playerStamina.
|-- * Reset playerRecoverDelay to 1 second.
|-- * Set toolbar graphic to 57.
|-- * Set player.AnimationSpeed to 5.
* Otherwise (player is not in run mode or player is not moving or player stamina is depleted):
|-- * Reset player.AnimationSpeed to 3.
|-- * If playerStamina is 0 (energy is completely depleted), set toolbar graphic to 58.
|-- * If playerRecoverDelay is greater than 0, decrease playerRecoverDelay.
|-- * Otherwise (playerRecoverDelay is less than or equal to 0):
|-- |-- * If player has not fully recovered their stamina (playerStamina is less than 6 seconds):
|-- |-- |-- * Increase playerStamina.
|-- |-- |-- * Make sure toolbar graphic is set to 57 (in case energy was totally depleted).
|-- |-- * Otherwise (player has fully recovered their stamina):
|-- |-- |-- * Reset playerRecoverDelay to 1 second.
|-- |-- |-- * Set toolbar graphic to 56.

Planning out what you're going to do before you start typing code can help you see and prevent logic errors like you currently have in your code (e.g., checking the value of a variable you just set!).

Here's what it would look like in code:

Code: ags
#define PLAYER_STAMINA_MAX (GetGameSpeed() * 6) // 6 seconds, regardless of game speed
#define PLAYER_RECOVER_DELAY GetGameSpeed() // 1 second, regardless of game speed
#define PLAYER_WALK_SPEED 3 // player's walking animation speed
#define PLAYER_RUN_SPEED 5 // player's running animation speed

int playerStamina; // player's remaining energy
int playerRecoverDelay; // time remaining (in game loops) until player begins recovering energy
bool ModeRun; // NOTE: You haven't shown where this is updated

function game_start() // NOTE: I use game_start to initialize my variables since I'm calling GetGameSpeed()
{
  playerStamina = PLAYER_STAMINA_MAX;
  playerRecoverDelay = PLAYER_RECOVER_DELAY;
}

function UpdateStamina() // renamed to clarify what this does
{
  if ((ModeRun) && (player.Moving) && (playerStamina > 0)) // player is actively running
  {
    playerStamina--; // lose energy
    playerRecoverDelay = PLAYER_RECOVER_DELAY; // reset this if player starts running after recovery has started
    gRunToolbar.BackgroundGraphic = 57; // update toolbar graphic
    player.AnimationSpeed = 5; // update animation speed
  }
  else // player is NOT running OR player's stamina is totally depleted
  {
    player.AnimationSpeed = 3;
    if (playerStamina == 0) gRunToolbar.BackgroundGraphic = 58; // energy is completely depleted, update toolbar graphic
    if (playerRecoverDelay > 0) playerRecoverDelay--; // player is waiting to recover energy
    else if (playerStamina < PLAYER_STAMINA_MAX) // energy recovery has begun
    {
      playerStamina++; // increase energy
      gRunToolbar.BackgroundGraphic = 57; // make sure toolbar graphic is updated (in case player energy WAS depleted)
    }
    else // energy is fully recovered
    {
      playerRecoverDelay = PLAYER_RECOVER_DELAY; // reset delay for next recovery
      gRunToolbar.BackgroundGraphic = 56; // update toolbar graphic
    }
  }
}


This might require some tweaking to get this working exactly the way you want, but this actually keeps the loss and recovery of energy tied to the same variable, and the "delay" variable is just used as an additional timer to force the player to wait a bit before they start gaining energy back. I'll also mention that using a name like "Update_Runtoolbar" when you're changing other things like the player's animation speed is a very bad idea, which is why I renamed the function.

Vincent

Ciao monkey_05_06

Thanks so much for answering back, I appreciate it.
Well, the second time I tried to write the function is definitely less illogical than the first one ;-D, I assume.
Because at least it was working as I bear in mind (broken logic (or not)) but people do not work like that.


I added the function that you build for me in my block notes.
I tested and adapted it to my requests into the testing room, and it work just like a charm !
Also needless to say, but just to confirm it.


I'm still a beginner AGSer and I learn every day.
Thanks for showing me the right way.


ps:
the only thing that I still have to figure out in your post is as follow:
Quote from: monkey_05_06 on Sat 15/11/2014 10:14:01
I'll also mention that using a name like "Update_Runtoolbar" when you're changing other things like the player's animation speed is a very bad idea, which is why I renamed the function.

I am a lover of perfectionism too, but i could not call this current function abcdefg() and to check if the player HasExplicitTint ?

monkey0506

Glad you got it working! :)

Quote from: Vincent on Sat 15/11/2014 14:33:51the only thing that I still have to figure out in your post is as follow:
Quote from: monkey_05_06 on Sat 15/11/2014 10:14:01
I'll also mention that using a name like "Update_Runtoolbar" when you're changing other things like the player's animation speed is a very bad idea, which is why I renamed the function.

I am a lover of perfectionism too, but i could not call this current function abcdefg() and to check if the player HasExplicitTint ?

You can, of course, name functions whatever you want, but this really comes down the the programming idiom that your function names should represent some type of contract with the user (whether it's yourself (now), yourself (a year from now), or someone else entirely). With a name like "Update_Runtoolbar" this implies that the only thing the function is doing is updating the graphics on the toolbar. This could lead to serious confusion or cause "bugs" which could be difficult to track down. The way you have it set up, the toolbar's graphic is directly tied to the player's stamina, which is fine and it makes sense to do it this way, but the logical connection is that the toolbar reflects the stamina, not the other way around.

Since the toolbar reflects the stamina, then the user is far more likely to expect the toolbar to change when the stamina changes than to expect the stamina to change because the toolbar changed. So if you tell the user "I'm going to update the stamina" and the toolbar graphic is also updated, there's no breech of contract here because those things bear this logical relationship. However, if you tell the user "I'm going to update the toolbar graphic" and you start changing the player's stamina variable, player's animation speed, and/or any other side effects, then you have essentially broken the contract that the user thought they were making.

This is the core of what I was saying about the function name: If your function has side effects other than what the function name says it is going to do, they must be obvious (or at least have an obvious correlation). If you have a function with no side effects (e.g., the function just checks things, it doesn't change anything) then it is less important to explain the nuances of the function (e.g., naming a function "abcdefg" and checking player.HasExplicitTint doesn't change anything, so it's less dangerous than a function named "abcdefg" that changes the player's tint, scaling, and view). If in doubt, don't hesitate to make your function names long to explain what the function is for -- a function called "UpdateStaminaAndRunToolbar" is even more explicit in what it's doing and makes it totally clear to the user).

At the end of the day, it's up to you to decide what you name your functions, but as someone with more than a decade of programming experience, I can assure you that when you come back to this code in a year or two, your future self will greatly appreciate the few extra seconds you spent typing out the more elaborate/explicit names. 8-)

Vincent

Quote from: monkey_05_06 on Sat 15/11/2014 18:01:58
Glad you got it working! :)
Yeah, me too.

Quote from: Vincent on Mon 10/11/2014 21:08:00
I was building a function that is used to calculate the tiredness of the main character when he tries to perform a run.
And to calculate the opposite, when he is not moving (so that can recover his energy).
Yes, the intention was only to do that, the animation player part was a testing purpose


Quote from: Vincent on Tue 11/11/2014 01:51:08
I tried so far a better logic to implement on this function.
Ehm, looking at the script and then go back to the top of it, don't seems so.
Code: ags

int run_counter = 0, recover_counter = 0;


Quote from: monkey_05_06 on Sat 15/11/2014 10:14:01
|-- * then check if run_counter is less than or equal to 5 (always true, since you know that it's 0)

;-D


Quote from: monkey_05_06 on Sat 15/11/2014 10:14:01
The problem (as I see it) is that you're treating the loss of energy and the recovery of energy as two separate, totally unrelated things.
Quote from: monkey_05_06 on Sat 15/11/2014 10:14:01
Planning out what you're going to do before you start typing code can help you see and prevent logic errors like you currently have in your code (e.g., checking the value of a variable you just set!).

:)


Quote from: Vincent on Sat 15/11/2014 14:33:51
but i could not call this current function abcdefg() and to check if the player HasExplicitTint ?
Specifically, a bull_s_ hit.


Quote from: monkey_05_06 on Sat 15/11/2014 18:01:58
I can assure you that when you come back to this code in a year or two, your future self will greatly appreciate the few extra seconds you spent typing out the more elaborate/explicit names. 8-)
Ciao Monkey_05_06, Thanks so much for everything.


Quote from: Anpiel on Sat 15/11/2014 01:05:48
This is awesome! My girlfriend and I were talking about something stamina related and now we have something like that!
Quote from: Vincent on Sat 15/11/2014 07:00:16
Nice, thank you
But this is just a testing that i was doing for myself.
Absolutely nothing outstanding, rather.

SMF spam blocked by CleanTalk