Adventure Game Studio

AGS Support => Beginners' Technical Questions => Topic started by: Lewis on Tue 24/01/2017 14:28:53

Title: Five games in one - variable/state logic (solved)
Post by: Lewis on Tue 24/01/2017 14:28:53
Right, try as I might, I can't wrap my head around the logic of this one, but I know it's possible, and apologies if I'm overlooking something really straight-forward.

I'm making a game that's actually five games in one.

At the beginning, four of them are locked off, and you can only play the first one. When you complete the first one, the second one unlocks... and so on. So far, so simple.

But the player should also be able to go back and play games they have completed, from the beginning. (Each game is only 20-30 mins long so I haven't yet decided if I'll implement a save anytime feature or not).

So what I'm trying to wrap my head around is how to ensure that, when the player clicks on a particular game to start it again, it resets the game to its original state.

Problems:

- I can't 'reset game' because that will reset the entire game to its initial start state, thus also resetting the variable that tells the main menu which games are unlocked.

- I can't simply set the appropriate 'start state' variables upon loading the first room of each game, because any DoOnceOnly scripts will have already triggered when the player plays through for the first time, and thus those DoOnceOnlies will not fire after the first time around.

***

It feels as though this is more than likely a very easy problem to solve. But I can't fathom it. So any help hugely appreciated. Thanks!
Title: Re: Five games in one - variable/state logic
Post by: Khris on Tue 24/01/2017 15:01:49
It's not that easy. I see two ways to do this:

- keep a file in the game folder that stores which games are unlocked (should use basic encryption / obfuscation).

- get rid of all DoOnceOnly()s, use only global/room variables.
Title: Re: Five games in one - variable/state logic
Post by: Mandle on Tue 24/01/2017 15:03:10
First problem I see is that for this kind of project you must remove all the DoOnceOnly parts of the code and replace them with variables that test whether the action has been done already once or not within that particular playthrough of that level...And then reset those variables every time the player restarts that level.

Do once only means exactly that and without a total game reset it's never going to work the way you want your game to.
Title: Re: Five games in one - variable/state logic
Post by: Mandle on Tue 24/01/2017 15:04:19
Hahaha Khris beat me to it by 2 seconds...
Title: Re: Five games in one - variable/state logic
Post by: Snarky on Tue 24/01/2017 15:04:51
There is not, as far as I'm aware, any way to "reset" a Game.DoOnceOnly() call, except presumably for RestartGame().

This leaves you with two options:

1. Use RestartGame() whenever you want to restart one of the sub-games, which means you have to keep track of which games are unlocked outside of the "game state", by writing it to and reading it from some custom file.
2. Replace the Game.DoOnceOnly() calls with flags that you can in fact reset. For this, it might make sense to write your own "DoOnce" module so that you can still keep all the same game logic.

The first option is probably the easiest.

(... aaaaand I got interrupted in the middle of posting this and Khris already said more or less the same thing.)
Title: Re: Five games in one - variable/state logic
Post by: dayowlron on Tue 24/01/2017 15:08:11
Also, I remember reading in the manual even though I haven't used it myself, but if each game was a different room then there is a "ResetRoom" function that can reset all the variables that are in that rooms script.
Title: Re: Five games in one - variable/state logic
Post by: Snarky on Tue 24/01/2017 15:29:19
Here's a quick version of a DoOnceOnly module:

Code (ags) Select
// DoOnceOnly module header
struct OnceOnly
{
   import static bool Do(String id);
   import static void Reset(String id);
   import static void ResetAll();
};


Code (ags) Select
// DoOnceOnly module script
#define DO_ONCE_ONLY_SIZE 24  // This is how many different calls you can have. Edit as appropriate
String DoOnceOnly_keys[DO_ONCE_ONLY_SIZE];
int DoOnceOnly_keyCount=0;

int DoOnceOnlyIndex(String key)
{
  if(key==null || key == "")
    return -1;

  int i=0;
  while(i < DoOnceOnly_keyCount)
  {
    if(key == DoOnceOnly_keys[i])
      return i;
    i++;
  }
  return -1;
}

bool DoOnceOnlyAdd(String key)
{
  if(DoOnceOnly_keyCount < DO_ONCE_ONLY_SIZE && key != null && key != "")
  {
    DoOnceOnly_keys[DoOnceOnly_keyCount] = key;
    DoOnceOnly_keyCount++;
    return true;
  }
  else return false;
}

bool DoOnceOnlyRemove(String key)
{
  int i = DoOnceOnlyIndex(key);
  if(i >= 0)
  {
    if(DoOnceOnly_keyCount > 0)
      DoOnceOnly_keys[i] = DoOnceOnly_keys[DoOnceOnly_keyCount];
    DoOnceOnly_keys[DoOnceOnly_keyCount] = null;
    keyCount--;
    return true;
  }
  else
    return false;
}

static bool OnceOnly::Do(String id)
{
  int i = DoOnceOnlyIndex(id);
  if(i < 0)
  {
    if(DoOnceOnlyAdd(id))
      return true;
    else
    {
      if(id == null || id == "")
        AbortGame("Error in OnceOnly.Do(): Illegal key. Cannot use null or empty string as ID.");
      else
        AbortGame("Error in OnceOnly.Do(): Buffer overflow with too many calls. Try increasing DO_ONCE_ONLY_SIZE.");
    }
  }
  else
    return false;
}

static void OnceOnly::Reset(String id)
{
  DoOnceOnlyRemove(id);
}

static void OnceOnly::ResetAll()
{
  int i=0;
  while(i < DO_ONCE_ONLY_SIZE)
  {
    OnceOnly_keys[i] = null;
    i++;
  }
  OnceOnly_keyCount = 0;
}



You call it just like Game.DoOnceOnly(): OnceOnly.Do("blah blah blah");
You can reset a key with OnceOnly.Reset("blah blah blah");
Or reset all of them with OnceOnly.ResetAll();

I haven't tested any of this, so there could be typos/bugs.

Edit: Another thought â€" for cases such as yours it might be better to have multiple instances of this struct. That way you could easily handle all the OnceOnly() state for each of the sub-games separately (e.g. reset the state for only one of the games). I'd have to change the code a little...

Edit 2: Fixed the errors that have been pointed out.
Title: Re: Five games in one - variable/state logic
Post by: Lewis on Tue 24/01/2017 15:34:30
Thanks all - and oh, incredible Snarky! I will give this a whirl and see what happens, as otherwise I could end up with fourteen million global variables.

Edit: I'm getting a "missing semicolon after first struct declaration" error in the header, but it doesn't look to be missing anything- any ideas?
Title: Re: Five games in one - variable/state logic
Post by: Crimson Wizard on Tue 24/01/2017 15:38:35
Quote from: Lewis on Tue 24/01/2017 15:34:30
Edit: I'm getting a "missing semicolon after first struct declaration" error in the header, but it doesn't look to be missing anything- any ideas?
Add semicolon after first struct declaration?
It should end with "};"


Snarky, instead of
Code (ags) Select

return 1/0; // Crash! Illegal key or too many different DoOnceOnly calls (if so, increase DO_ONCE_ONLY_SIZE)


You could do "AbortGame" supplying human-friendly message :).
Title: Re: Five games in one - variable/state logic
Post by: Lewis on Tue 24/01/2017 15:41:16
Yep, just spotted it!

Now getting "Unknown preprocessor directive 'DO_ONCE_ONLY_SIZE'... will keep on digging, thanks so much so far!
Title: Re: Five games in one - variable/state logic
Post by: Crimson Wizard on Tue 24/01/2017 15:47:25
Quote from: Lewis on Tue 24/01/2017 15:41:16
Now getting "Unknown preprocessor directive 'DO_ONCE_ONLY_SIZE'... will keep on digging, thanks so much so far!

It should be "#define DO_ONCE_ONLY_SIZE..."
Title: Re: Five games in one - variable/state logic
Post by: Lewis on Tue 24/01/2017 15:49:53
Thanks!

Next: line 12, 'undefined symbol 'KeyCount''

I wish there were a way to parse for all errors at once, and that I were less useless a coder. :(
Title: Re: Five games in one - variable/state logic
Post by: Lewis on Tue 24/01/2017 15:56:32
OK, got it compiling now with these edits. Now testing logic.


// DoOnceOnly module script
#define DO_ONCE_ONLY_SIZE 24  // This is how many different calls you can have. Edit as appropriate
String DoOnceOnly_keys[DO_ONCE_ONLY_SIZE];
int DoOnceOnly_keyCount=0;

int DoOnceOnlyIndex(String key)
{
  if(key==null || key == "")
    return -1;

  int i=0;
  while(i<DoOnceOnly_keyCount)
  {
    if(key == DoOnceOnly_keys[i])
      return i;
    i++;
  }
  return -1;
}

bool DoOnceOnlyAdd(String key)
{
  if(DoOnceOnly_keyCount < DO_ONCE_ONLY_SIZE && key != null && key != "")
  {
    DoOnceOnly_keys[DoOnceOnly_keyCount] = key;
    DoOnceOnly_keyCount++;
    return true;
  }
  else return false;
}

bool DoOnceOnlyRemove(String key)
{
  int i = DoOnceOnlyIndex(key);
  if(i >= 0)
  {
    if(DoOnceOnly_keyCount > 0)
      DoOnceOnly_keys[i] = DoOnceOnly_keys[DoOnceOnly_keyCount];
    DoOnceOnly_keys[DoOnceOnly_keyCount] = null;
    DoOnceOnly_keyCount--;
    return true;
  }
  else
    return false;
}

static bool OnceOnly::Do(String id)
{
  int i = DoOnceOnlyIndex(id);
  if(i < 0)
  {
    if(DoOnceOnlyAdd(id))
      return true;
    else
      return 1/0; // Crash! Illegal key or too many different DoOnceOnly calls (if so, increase DO_ONCE_ONLY_SIZE)
  }
  else
    return false;
}

static void OnceOnly::Reset(String id)
{
  DoOnceOnlyRemove(id);
}

static void OnceOnly::ResetAll()
{
  int i=0;
  while(i < DO_ONCE_ONLY_SIZE)
  {
    DoOnceOnly_keys[i] = null;
    i++;
  }
  DoOnceOnly_keyCount = 0;
}
Title: Re: Five games in one - variable/state logic
Post by: Lewis on Tue 24/01/2017 16:03:49
HUGE SUCCESS!

This is all working now and functioning exactly as intended. And because players will only go through one game at once, I can just ResetAll each time a player starts any of the new games, define character start rooms and positions for that particular game, and reset all the game's global variables.

Thanks so much, this is supremely useful.

EDIT: It is worth nothing that room_firstload of course doesn't get reset by this, which is hardly the biggest of deals when one can just create a OnceOnly.Do within room_AfterFadeIn.
Title: Re: Five games in one - variable/state logic
Post by: Snarky on Tue 24/01/2017 17:33:45
Ah yes, a few mistakes but no more than usual. :P Glad you got it working!

Quote from: Crimson Wizard on Tue 24/01/2017 15:38:35
Snarky, instead of
Code (ags) Select

return 1/0; // Crash! Illegal key or too many different DoOnceOnly calls (if so, increase DO_ONCE_ONLY_SIZE)


You could do "AbortGame" supplying human-friendly message :).

Now where's the fun in that?