New ChoiceScript features: implicit control flow, gosub parameters

@dfabulich @kgold I was interested in understanding the difference between *gosub and *gosub_scene that supports the Fibonacci implementation. *gosub doesn’t push the variables to the stack and recursive calls would overwrite the same variables. The *gosub_scene implementation is closer to what I would expect from a function call in other languages, while *gosub maintains a simpler model and uses the global status.

May I ask why did you choose a different implementation? Was it for performance? Or did you want to maintain the model simpler for *gosub, as the recursive stack may be a complex concept for some developers?

Wasn’t it because *gosub is only for the scene it’s in, while gosub_scene can be entered from every scene, thus allowing for reoccurring operations?


I believe that’s because of what Dan mentioned; he only wanted to use preexisting features of the language in implementing *params. CS has only two levels of scope: 1) global scope, with *create; and 2) per-scene scope, with *temp. So it was a learning complexity concern.

As you mentioned, *gosub_scene provides local scope, pushing variables onto the stack, so the functionality is still there for programmers who want to use it.


Thanks @Chris_Conley

The global scope is risky. This example will swap the two variables a and b. If you reuse a name for your params, you may overwrite your data.

This is different with gosub_scene - I may decide to always use gosub_scene in my code.

*create a 100
*create b 10
${a} ${b}     # Prints 100 10
*gosub swap a b 
${a} ${b}     # Swapped, prints 10 100
gosub_scene startup swap a b 
${a} ${b}    # No second swap, vars unchanged, 10 100
*label swap
*params b a

Just be aware of this bug: New in ChoiceScript: Limits on infinite loops in Randomtest

More comments on this, after doing some experiments with recursion.

On complex projects, scoping is making things tricky. If you use *gosub, you may pollute the scene’s scope. With *gosub_scene instead, you don’t have any access to the local scope - all data you need there must be passed as params or global (in particular, simulated arrays).

On the other side, if you use CS as a target language (with tooling), you can use only local names (generated automatically) for *gosub and get a lot of stuff done. For instance, I’ve used strings to simulate growing data structures. This works in a very robust way (go CS engine!), but it’s very slow due to limited tools to manipulate strings – to modify a string you’d have to make a copy with a loop (which you must simulate with a goto). I’ve a version of the maze that can generate unbounded maps, but it’s at least 3x slower than the one using simulated arrays (map_0_0 map_1_1 …).

So, can we get scopes? Or at least, can we get better string manipulation? :slight_smile:


What do you mean by you have to make a copy with a loop?

@Chris_Conley I wrote a version of my maze (Advanced programming - randomized maze) where the map is stored with one long string variable per row (instead of one variable per cell). This way, I was able to create maps of unbounded size.

It’s very easy to read the data with the new setup.
Before (cell based): grid[ x][y] After (row based): grid[ x]#(y+1)

But setting the data is hard, there is no equivalent to:
*set grid[ x ][y] value

To update the string, you have to make a copy, character by character, and change what you need as you scan the original data.


Ok, I bit the bullet, dug a hole through the abstraction layer and started using native Javascript arrays in CS. Oh, my, it’s fast!! It’s like when, back in the days, you would write a small ASM function to speed up some data crunching in C or Python.

Now, in my preprocessor, I have a few new commands *js_array *js_set *js_get that help me do that. Back to the maze, I can have very large maps without a million variables grid_x_y, and it’s several times faster. Note that the result is still valid CS with some *script.

I think I’m now officially off topic. If anybody is interested we can discuss in a new topic.


I am not very good at reading code but to my understanding this “*create implicit_control_flow true” sets… all bugs hiding in choices invisible? I thought the point of quick test was to find bugs. Or is there another command where it just skips a specific choice? I know my choice works I’ve run it 1000 times but I can’t finagle it any more.

1 Like

No. What this does is makes *choice behave exactly like *fake_choice, and lets you write *if/*else and *if/*elseif/*else blocks that just fall through to the next line.

With or without ICF, the following is valid code:



*if met_bob
   This might be displayed.

This will always be displayed.

The following will only be valid if ICF is true:



*if met_bob
   This might be displayed.
   This might be displayed instead.

This will always be displayed.

Otherwise, without ICF, you’ll need to end every #option under a *choice, and each indented conditional block (that’s not just a standalone *if block), with a *goto or a *goto_scene or a *finish or an *ending.