Five games in one - variable/state logic (solved)

Started by Lewis, Tue 24/01/2017 14:28:53

Previous topic - Next topic

Lewis

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!
Returning to AGS after a hiatus. Co-director of Richard & Alice and The Charnel House Trilogy.

Khris

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.

Mandle

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.

Mandle

Hahaha Khris beat me to it by 2 seconds...

Snarky

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.)

dayowlron

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.
Pro is the opposite of Con                       Kids of today are so much different
This fact can clearly be seen,                  Don't you know?
If progress means to move forward         Just ask them where they are from
Then what does congress mean?             And they tell you where you can go.  --Nipsey Russell

Snarky

#6
Here's a quick version of a DoOnceOnly module:

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


Code: ags
// 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.

Lewis

#7
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?
Returning to AGS after a hiatus. Co-director of Richard & Alice and The Charnel House Trilogy.

Crimson Wizard

#8
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

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 :).

Lewis

Yep, just spotted it!

Now getting "Unknown preprocessor directive 'DO_ONCE_ONLY_SIZE'... will keep on digging, thanks so much so far!
Returning to AGS after a hiatus. Co-director of Richard & Alice and The Charnel House Trilogy.

Crimson Wizard

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..."

Lewis

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. :(
Returning to AGS after a hiatus. Co-director of Richard & Alice and The Charnel House Trilogy.

Lewis

OK, got it compiling now with these edits. Now testing logic.

Code: ags

// 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;
}
Returning to AGS after a hiatus. Co-director of Richard & Alice and The Charnel House Trilogy.

Lewis

#13
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.
Returning to AGS after a hiatus. Co-director of Richard & Alice and The Charnel House Trilogy.

Snarky

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

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?

SMF spam blocked by CleanTalk