New ChoiceScript features: implicit control flow, gosub parameters

I was talking crap, sorry. I actually get a ‘couldn’t find scene ghost.txt’ error with your example, if I use *gosub_scene (*goto_scene works fine). And then a “TypeError: Cannot read property ‘length’ of undefined”, regardless of if *params is left empty or not. This is only in QuickTest, the game itself runs fine.

Are you on a fresh copy of CS?

Oh yeah, I had the ‘couldn’t find scene’ error, too, but listing ghost in startup fixed it. I’m using CSIDE 1.1.2 (development version, I believe – I’m on a mac and had the deletion bug)

All scenes have to be listed in startup.txt for compiling to work properly anyway, right? I seem to remember it being required when the game goes to CoG/HG…


Update on the quicktest problem:
It turns out, apparently, that telling quicktest to navigate around the *params command still results in an error:

*if not (choice_quicktest)

is not valid.
However, it seems the problem is only with params used with *gosub_scene, as @CJW’s example above within a scene worked fine. So, I tried putting a completely useless subroutine in the malfunctioning scene around the code including the *params command, and now it works… :face_with_raised_eyebrow:

*label printamessage
*if (choice_quicktest)
	*gosub sillyquicktest 1
*label sillyquicktest
*temp thingtodisplay "default message should never be seen"
*if (param_1 = 1)
	*set thingtodisplay "This message displays the first time."
*if (param_1 = 2)
	*set thingtodisplay "This message displays the second time"

I recommend you always add *params after the label, so that they are loaded already, even if you do not use them (this won’t result in an error, the params will just be loaded but not used)

I was having problems with CSIDE trying to use *params, but it was because I was using an old version. After updating to latest or dev version it worked fine in quicktest.

My other *params attempts worked succesfully, either with *gosub or *gosub_scene.

I got an error when you add *params on a *label if you don’t give any value for those parameters.

Here’s my sample, on my startup.txt

*label skrinrid
*params as df gh
@{repeat So, d|D}o you wish to activate screenreader mode?
@{repeat [n/][[i]Cannot be changed until game over or restart[/i]]|}
	#Option 1
	#Option 2

But then, why would you put *params if you’re not going to use it? ¯\_(ツ)_/¯

Well I didn’t try renaming the *params though. In one instance I had 2 calculations in this *gosub, and one requires 3 params, another requires just 2. I was able to use either just fine, giving the required number of *params depending on which I wanted to use.

*label misc_calcs_index
*gotoref param_1

*label dollar_to_pound_calc
*set dollar_pound 0
*set dollar_pound round(param_2 * 1.33)

*label accCalculate
*set finalAcc round(param_2 + ((2*param_3)/1.5))

So I use param_1 to select what calculation I need. If its the dollar_pound I just need param_2, for the other one I need param_2 and param_3. So when I use this *gosub I just need to include either 2 or 3 params when I require it. So far it all worked fine in quicktest.

Ooh, I see. You had the value ready to be assigned to those parameters.

I thought what you meant with

is to literally add *params to every *label you can find, which surely tingles my rage nerve :"


I think I found a minor bug with *params and *gosub_scene

This works:

*gosub stat- "silly" 2 "${var} blabla" 2

But this doesn’t:

*gosub_scene code stat- "silly" 2 "${var} blabla" 2

The error is: Invalid expression at char 10, expected NUMBER, STRING, VAR or PARENTHETICAL, was: OPERATOR

So obviously that’s from the label name “stat-”. Removing the minus sign from the label makes it work, but apparently including a ${var} or [b][/b] in one of the parameters breaks it, because this works:

*gosub_scene code stat- "silly" 2 "blabla" 2

I can rename the labels, I just prefer to use + or -.

But since it works with normal *gosub I don’t see why it can’t work with *gosub_scene.

Operators in label names is not supported. The “bug” is that it works with *gosub in the first place.


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