Community

SCI Programming => SCI Syntax Help => Topic started by: Doan Sephim on June 11, 2019, 11:35:48 AM

Title: Can someone explain Flags to me?
Post by: Doan Sephim on June 11, 2019, 11:35:48 AM
In the past, I've used Global variables for TRUE/FALSE situations like "the first time you enter a room." While it works, it does produce a hefty main.sc which limits what I can do in other rooms/regions. Since then, I've heard a bit about using Flags in the place of global variables for those kinds of uses. The direction given was to look through the QfG1 decompiles, but after having spent some time looking through the forums and scripts, I'm not sure I understand how to implement this.

I'm not a native programmer, so things often go over my head. Can someone point this out to me?
Title: Re: Can someone explain Flags to me?
Post by: EricOakford on June 11, 2019, 11:54:11 AM
Here's how event flags work, using code from my SCI01 Template:
Code: [Select]
(procedure (Btst flagEnum)
;Test a boolean game flag
(& [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16)))
)

(procedure (Bset flagEnum  &tmp oldState)
;Set a boolean game flag
(= oldState (Btst flagEnum))
(|= [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16)))
oldState
)

(procedure (Bclr flagEnum  &tmp oldState)
;Clear a boolean game flag
(= oldState (Btst flagEnum))
(&= [gameFlags (/ flagEnum 16)] (~ (>> $8000 (mod flagEnum 16))))
oldState
)

(procedure (SolvePuzzle flag points)
;Adds an amount to the player's current score. A flag (one used with
;Bset, Bclr, and Btst) is used so that a score is only added once.
(if (not (Btst flag))
(theGame changeScore: points)
(Bset flag)
)
)

They refer to a global variable array:
Code: [Select]
[gameFlags 10] ;each global can have 16 flags. 10 globals * 16 flags = 160 flags. If you need more flags, just increase the array!
The SolvePuzzle procedure checks if a flag is set, and if it's not, it awards the specified points to your score.

The flags are enumerated and defined in your GAME.SH file.

This is much more efficient than having one global per TRUE/FALSE situation, so that you only need separate global variables for situations with multiple states.
Title: Re: Can someone explain Flags to me?
Post by: NilG on June 11, 2019, 12:16:13 PM
Awesome!  I just counted and I've got 70 global binary variables in what's probably a little less than a quarter of the full game.  I can shift quite a few local variables over this way as well, which could help trim the necessary logic from some of the main rooms into the specific room/input scripts.  My heap's shifting further from nightmare to comfortable with each trick you all pass on.

Might not be bad to compile some article on the Wiki for heap management tricks like these for some of us more amateur programmer types.  Perhaps I'll take some time to at least start on something like that; it'd be nice to contribute for sure in content if not coding and ideas.
Title: Re: Can someone explain Flags to me?
Post by: Doan Sephim on June 11, 2019, 03:05:38 PM
This is quite helpful, thank you.
Follow up: I'm working in an older project, and it is not yet in Sierra script. It'll be translated over in time, but when I run the auto-convert I end up with about 140 or so errors (I'll get around to them eventually ;)
In the meantime, I'm working on the scripts with studio scripting. In the code you provided, there is the mathematical operation of "mod."
I've never seen that operation in studio script and was wondering if anyone knew the symbol for that operation in Studio.
Thanks again for your help with flags. I always suspected there was a better way to do these type of things, but never knew about it until now

Edit: Mod's symbol appears to be % in studio script.
When I'm defining in the game.sc script, does that take up heap space?
Title: Re: Can someone explain Flags to me?
Post by: troflip on June 11, 2019, 05:59:46 PM
When I'm defining in the game.sc script, does that take up heap space?

Define's are a compile time thing, they don't take up any extra space.
Title: Re: Can someone explain Flags to me?
Post by: MusicallyInspired on June 11, 2019, 11:14:19 PM
Edit: Mod's symbol appears to be % in studio script.

Yeah, "MOD" is short for "modulus" and returns a remainder from a division operation...right?
Title: Re: Can someone explain Flags to me?
Post by: Kawa on June 12, 2019, 04:00:35 AM
Define's are a compile time thing, they don't take up any extra space.
I don't think you're talking about the same kind of defining that Doan Sephim is.
Yeah, "MOD" is short for "modulus" and returns a remainder from a division operation...right?
Correct.
Title: Re: Can someone explain Flags to me?
Post by: NilG on June 12, 2019, 05:07:44 AM
Is $8000 hex?  I know how to convert hex, but I'm a little fuzzy on how it applies here, if so?  I've got no real experience with bitwise operation; I feel like I get the most basic of concepts, but I'm definitely lacking in the applicability department, so I'm kind of slow at translating when it comes up.
Title: Re: Can someone explain Flags to me?
Post by: lskovlun on June 12, 2019, 06:03:24 AM
Is $8000 hex?  I know how to convert hex, but I'm a little fuzzy on how it applies here, if so?  I've got no real experience with bitwise operation; I feel like I get the most basic of concepts, but I'm definitely lacking in the applicability department, so I'm kind of slow at translating when it comes up.
Yup, 215, that is to say, the value you AND something with to isolate the 15th bit (OR to set it).
By shifting it right as here, you get decreasing powers of two which can be used similarly to isolate/set the 14th, 13th, etc. bit.
Title: Re: Can someone explain Flags to me?
Post by: Kawa on June 12, 2019, 06:20:44 AM
What's important is that those functions do the intricate math for you so you don't have to worry. I for one get confused easily when dealing with sub-byte data so I for one am thankful.
Title: Re: Can someone explain Flags to me?
Post by: Charles on June 12, 2019, 11:06:55 AM
It can be fun to learn about bit operations and binary/hex and all of that, but like Kawa said, that's all irrelevant knowledge for the Btst, Bset and Bclr procedures.  They do all the heavy lifting for you.

All you need to remember is that a single variable can hold 16 flags.  So the template game has 10 variables assigned to the gameFlags array. Ergo, 160 flags.  The first flag is flag #0, the last is flag #159. If you need more flags, extend the array; you get 16 more flags for each variable you extend the flag array.

You tell the Btst, Bset and Bclr procedures which flag number you want to test/set/clear.  And you should use an enum to track your flags, just to make things easier to read/follow. The enum's won't use any memory since the compiler will hardcode whatever number the flag is when compiling.

Here's how Btst, Bset, and Bclr compare to similar statements using individual global variables, assuming your global variable is named gVar1 and you want to replace it with a flag defined as FLAG1:
Code: [Select]
(Btst FLAG1)
(== gVar1 TRUE)

(Bset FLAG1)
(= gVar1 TRUE)

(Bclr FLAG1)
(= gVar1 FALSE)
Title: Re: Can someone explain Flags to me?
Post by: Doan Sephim on June 12, 2019, 11:27:38 AM
The flags are enumerated and defined in your GAME.SH file.
Do you define them in the Game.sh file or enumerate them (or does it even matter which?)
I apologize for the hand-holding needed with this, but what does it look like in the subheader?

This is what I see in the Template, but I'm confused on how it relates to the gameFlags:
Code: [Select]
;Event flags
;Example: fBabaFrog (original Sierra naming)
Title: Re: Can someone explain Flags to me?
Post by: EricOakford on June 12, 2019, 12:11:38 PM
The flags are enumerated and defined in your GAME.SH file.
Do you define them in the Game.sh file or enumerate them (or does it even matter which?)
I apologize for the hand-holding needed with this, but what does it look like in the subheader?

This is what I see in the Template, but I'm confused on how it relates to the gameFlags:
Code: [Select]
;Event flags
;Example: fBabaFrog (original Sierra naming)

It doesn't really matter; using "enum" is just a simpler way to list defines in numerical order. The compiler recognizes them in the exact same way.

As an example, here are the event flags from R44QSCI:
Code: [Select]
; Event flags
(enum
fFoyerLightOn
fMetJohan
fJohanHasTie
fJohanAtReception
fMetNiels
fNielsSleeping
fTookJar
fTookGarbage
fOpenedStoveHood
fTookFilter
fTookTie
fFellDownStairs
fTookShowerGel
fSavedJinwo
fMetRinze
fGaveRinzeBeer
fTookPen
fTookNote
fWroteNote
fJosineDumped
fJosineGotNote
fCanEnterErikRoom
fJinwoHasKey
fCanEnterJosineRoom
fErikWantsFood
fEnteredJosineRoom
fTookRyeBread
fWearingFilter
fSawErikDead
)

If you want to start an enum at a specific number, just type that number after "enum".

Oh yeah, I forgot to mention a bit of trivia earlier - KQ4 and LSL2, the first two SCI games, didn't use the "Btst/Bset/Bclr" trio at all - they just used global variables for every event. Since that's a waste of heap space, PQ2 implemented the much more efficient bit-based event flag procedures. These became a standard part of SCI games ever since.
Title: Re: Can someone explain Flags to me?
Post by: Charles on June 12, 2019, 12:14:07 PM
You can put them wherever. You can define then, or use enums. Enums are actually a bit easier to manage.  I mean, heck, if you wanted you could just use numbers for everything and keep it in your head.  Defines and Enums make no difference to the compiled code. They're only for making your code easier to read.  When they get compiled, the compiler will basically just do a find/replace with the defined or enumerated value.  They both have their place.  Defines are great for one-off values. Enums are great if you're defining a bunch of sequential values, like flags.

You could put enums in a seperate .sh file and include that in game.sh (although if you do that, the tooltip may not properly show up in SCICompanion when you hover over it).

I'll show you the more complicated example, and let you pare down as necessary. So if you have 160 flags to play with (as in the template game default), you can make those flags be whatever you want. Let's say your game has 40 rooms, and you want to know if ego has visited any room before. Then you have another 120 flags for game states or puzzle points.  You can break those up into multiple enums for easier readability.

in Game.sh:
Code: [Select]
;;; Sierra Script 1.0 - (do not remove this comment)
;put the Sierra Script comment at the top, so we can use sierra script in the header file
(include gameEnumVisited.sh)

create a new file called gameEnumVisited.sh:
Code: [Select]
;;; Sierra Script 1.0 - (do not remove this comment)
(enum    ;the default starting point is 0, although you can specify another)
   FLAG_VISITED_ROOM1    ;this is given a value of 0
   fVisitedRoom2                 ;this is given a value of 1
   VISITED_RM3                 ;this is given a value of 2
   ;... continue for as many as needed
   ;NOTE, flags can be called whatever you want, but being consistent with your naming will make your code easier to read.
)

(define FLAG_ROOM4  3)  ;we've explicitly given this a value of 3. We could have also made this the next line in the enum

;game states are called out here
(enum 40  ;we're starting this enum at 40, for the game state flags.
    fEgoIsJumping  ; has a value of 40. For when ego's jumping off a massive cliff.
    FLAG_BABAFROG ; has a value of 41. For when ego has turned Baba Yaga into a frog.
)

;puzzle points are defined here, so the user doesn't double-dip on points.
(enum 120
    POINTS_JUMPED ;120.  User jumped. Yay. Give yourself 5 points.
)

Then in each room's script, in the dispose code you would add the following
Code: [Select]
(method (dispose)
(Bset FLAG_VISITED_ROOM1) ; sets flag#0 to 1 (i.e. turns it on, or sets as TRUE)
                ;we'll also give the user some points, because the had to jump to leave the room
                (AddToScore POINTS_JUMPED 5) ;the AddToScore procedure will only add points if the POINTS_JUMPED flag is 0. 
                ;So no other checking is required. The user will only get 5 points once.
(super dispose:)
)

Keep in mind that you can still use Btst to check if points have been awarded. It's still just a flag.
Title: Re: Can someone explain Flags to me?
Post by: Doan Sephim on June 12, 2019, 01:34:00 PM
Thanks Eric and Charles! I only have one question remaining (I hope!) - the thing I don't understand is how the enums in the subheader relate to the variable and the procedure that references that variable. The enumerated list seems to be unconnected to the variable and procedures. I'm guessing they're connected by something in the procedure, but I don't get it, and it makes me feel like I'm missing something.
Title: Re: Can someone explain Flags to me?
Post by: troflip on June 12, 2019, 02:07:52 PM
Code: [Select]
(procedure (Btst flagEnum)
;Test a boolean game flag
(& [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16)))
)

If the flag you pass in is 35, then this essentially becomes:

(procedure (Btst flagEnum)
   ;Test a boolean game flag
   (& [gameFlags 2] (>> $8000 3)) ; since 35 /16 is 2, and 35 mod 16 is 3
)

So it's selecting the 3rd bit in the 2nd gameFlags array index. It's basically just doing integer division and modulo to turn the flag number into an array index (0 to however big the array is) and bit position (0-15).


Title: Re: Can someone explain Flags to me?
Post by: Doan Sephim on June 12, 2019, 02:16:34 PM
Thanks Troflip et al. I think I finally grasp what going on with this script and I'm very thankful for everyone for bearing with me. I kinda feel like the dumb kid in class asking all these questions, but everyone has been quite patient and accommodating, and for that I'm grateful.
Title: Re: Can someone explain Flags to me?
Post by: troflip on June 12, 2019, 02:17:50 PM
Interestingly enough, if you're doing it this way in SCI0, this could end up eating up more memory, since the script code takes up heap space (I forget if that's true for SCI01 or not, but it's no longer the case for SCI1+ since script code isn't in the heap).

BTest, bSet, BClear functions themselves eat up 101 bytes. So that's a fixed cost right off the bat.
160 regular globals eat up 320 bytes, whereas [gameFlags 10] only eats up 20. So you're saving 300 bytes at a cost of 101 bytes. Still coming out ahead, except...

Except it takes 6 bytes of compiled code to call BTest or BSet.
But only 4 bytes (or possibly 5) to directly assign a global TRUE or FALSE.
And only 2 bytes (or possibly 3) to read from it.

So, read a flag 75 times in currently loaded script code and you've already lost your bytes advantage.

Title: Re: Can someone explain Flags to me?
Post by: Doan Sephim on June 12, 2019, 02:26:51 PM
So, read a flag 75 times in currently loaded script code and you've already lost your bytes advantage.
When you say 75 times in currently loaded script, i assume that that means (from a general gameplay perspective) in a single room, but after each room, it's all disposed, correct? So generally speaking, it's a heap saver, but if I were to read the flag(s) 75 times in a single room, it would be heap-negative compared to normal.

Or does it mean 75 times at all?
Title: Re: Can someone explain Flags to me?
Post by: troflip on June 12, 2019, 02:33:10 PM
Yes correct, but you need to include all the places in Main.sc or Game.sc or whatever too (scripts that are always loaded).
Title: Re: Can someone explain Flags to me?
Post by: Doan Sephim on June 12, 2019, 02:39:06 PM
Yes correct, but you need to include all the places in Main.sc or Game.sc or whatever too (scripts that are always loaded).
I see, so if I have any of those procedure already called in the default scripts, those will always be adding towards that 75-time limit.

Even so, it seems likely to me, that 75 is a pretty high number of calls for average use, so I think it will ultimately save.
Title: Re: Can someone explain Flags to me?
Post by: Charles on June 12, 2019, 02:46:40 PM
Also considering the AddToScore procedure, which relies on the Btst/Bset/Bclr trio, let's round down to a hypothetical 50 flags being read in a single scene ( i.e. the current script, main.sc, game.sc, and whatever locales and regions are also included with the current script).  Is that a practical consideration?  50 flags read in a single game scene sounds more than sufficient, but I haven't done compared many games.

The only code I've really looked through is QFG1, and I'm struggling to recall if it ever did more than 5 flag read/writes in a single scene.
Title: Re: Can someone explain Flags to me?
Post by: NilG on June 12, 2019, 05:59:25 PM
One other question from me, too.  I implemented this for one of my globals and it seems to be working great after a couple of initial test runs.  However, when I compile, I get a bunch of "No effect on code" warnings for the new stuff, as in the attached.

The flag and procedures must be working, since the game's responding to events related to the flag.  I've edited the code and commented out the global for the time being while testing.  From the player standpoint, so far so good, but am I missing something?
Title: Re: Can someone explain Flags to me?
Post by: troflip on June 12, 2019, 06:55:23 PM
Enclose the code in BTest in a return statement.

Title: Re: Can someone explain Flags to me?
Post by: NilG on June 12, 2019, 07:51:54 PM
That did it, except for the "oldState" lines in Bset and Bclr.

I found that enclosing "oldState" in a return statement in Bset made that one disappear; doing the same in Bclr did not.

I noticed that oldState isn't even in parentheses in either line, which seems unusual.  Are these what's getting returned?  Seems like it to me, they're the new values getting assigned(?), but making sure.  Any thoughts why Bclr's oldState would still give the "no effect on code" warning?

EDIT:  Must have done something wrong the first time, went back and added the return statement to Bclr again and now no warning.
Title: Re: Can someone explain Flags to me?
Post by: troflip on June 12, 2019, 08:26:42 PM
I should have said: enclose the *last* statement of the procedure in a return statement.
Title: Re: Can someone explain Flags to me?
Post by: NilG on June 12, 2019, 10:01:32 PM
Regarding heap, it seems like memory usage is going up each time I replace (== z TRUE/FALSE) with (Btst z) or = with Bset/Bclr.

It's not a huge difference, but seems counterintuitive to replacing the variables with flags.  Is it plugging in the whole procedure rather than a pointer (or whatever the procedure equivalent would be) each time I reference the call, or is this unexpected?  Logically, it's working fine.  I'm just not sure if the more flag testing/setting I do (as opposed to variable work), the heavier the price should be.  Some variables can pop up a dozen times in a script, so just looking for the best overall path.
Title: Re: Can someone explain Flags to me?
Post by: troflip on June 13, 2019, 12:09:08 AM
See my post a few posts up for an explanation of why this is. Basically, a procedure call takes up a few more bytes than assigning or testing a variable. You can disassemble the script to see exactly how much.



Code: [Select]
(== z TRUE/FALSE)

btw, why not use:
Code: [Select]
z  ; instead of (== z TRUE)

or

Code: [Select]
(not z)  ; instead of (== z FALSE)

Title: Re: Can someone explain Flags to me?
Post by: NilG on June 13, 2019, 05:06:14 AM
Thanks, troflip, not sure how I managed to miss that entire chunk of conversation, but makes sense.

Honestly, I just didn't realize the shorthand was accepted or think to try it out.  I'll def keep that in mind now, though.
Title: Re: Can someone explain Flags to me?
Post by: Kawa on June 13, 2019, 05:33:44 AM
Why would you just say z directly in a conditional expression instead of (== z TRUE)? For those playing along at home and don't know yet, this is because of a few things. First, TRUE and FALSE are just constants representing 1 and 0. Second, any non-zero value is "truthy". You'd think TRUE is the most truthy of all but it's just a number so it's equally truthy, this isn't Animal Farm. The expression in an if statement and such has to evaluate not to true but to truthy to execute, and that can be any expression whatsoever.
Code: [Select]
(if (AskConsent "Are you sure you want to shoot that?") ...) ; will execute if AskConsent returns non-zero
(if (Btst fShotTheThing) ...) ; will execute if the "shot the thing" flag is set
(if (== z TRUE) ...) ; will execute if z is exactly 1
(if z ...) ; will execute if z is 1 or higher

By that same token, (if mover (mover dispose:)) only executes if mover is non-zero and assumes it's a valid reference to some instance.

To elaborate on Phil's post about procedure calls and byte counts: bytecode-wise, (if z ...) turns into the following:
Code: [Select]
83 00      lal local0 //or 85 00 lat temp0, you get the point.
31 08      bnt justAfterThat
Load a local or temp into the accumulator, branch if the acc is not truthy. Four bytes, maybe one more if the code in the if block is large enough. The branch goes down eight bytes because that's how tiny the block itself is.
Compare that with a procedure call:
Code: [Select]
78         push1 // one parameter
39 2a      pushi 2a // flag #42
45 01 02   callb Btest 2 // call with two bytes worth of param data
31 08      bnt justAfterThat
That's six bytes to execute (if (Btest 42) ...). So if you test the same flag a bunch of times, consider testing it once and storing it to a temp.
And finally, checking your own properties as above is four bytes again:
Code: [Select]
63 12      pToa mover
31 08      bnt moverWasNull
Noting that mover is either null or something far greater than 1.
Now, by "returns non-zero" in the first half of this post above, we actually mean "sets the accumulator register to a non-zero value before popping back to the caller". You should be able to figure out what that means by now if you didn't already know.
Title: Re: Can someone explain Flags to me?
Post by: lskovlun on June 13, 2019, 03:49:22 PM
Code: [Select]
(if z ...) ; will execute if z is 1 or higher
You mean non-zero. Whether an integer is treated as signed or unsigned depends on the opcode. That is why we have > versus >u (or was that u>).