TLDR Can we please have better array support? Would you take code contributions?
Hi all. I am amazed by how vibrant this community is. Kudos to @dfabulich and the team for creating a tool that got so much creative energy going. It’s really interesting to read the discussions, and see the several ways people have pushed features like the array syntax, or multi-replace, to the limits.
An issue I find often discussed is the lack of support for arrays. I wanted to summarize here what I have learned so far by reading the forum and hacking around CS. I hope this is helpful to everybody struggling, like me, with arrays, and I’ll be happy to share code with all those that are interested.
I’ll write the story of my quest in second person, like many of the games we like.
You are writing your epic RPG saga, and decide to create a log of all the amazing
quests your hero will complete. On this log, you will run stats and show them to the player.
Or you decide to record all the choices taken by the player, so you can replay, test
or debug your game (Wouldn’t that be a great tool? I believe it’s doable with some *script).
Awesome! What’s the log? Just a long list of entries you can scan, append to, and possibly modify. Let’s implement that in ChoiceScript. You will:
*choice #1 - do it all by hand
At the top of your startup.txt, you manually add a bunch of entries
*create log_1
*create log_2
…
*create log_100
Well, let’s hope 100 is enough. Luckily, we can use the convenient array syntax to loop (*) over the log and do what we need.
On day ${i} you did: ${log[i]}
(*) Oh, yes, you have to simulate the loop with goto and labels.
PROS: pure ChoiceScript.
CONS: what happens on day 101?
*choice #2 - use a code generator
You write up some Python / C++ / other language to generate the code for you. This tool will write the sequence of *create commands you need. It may also implement the loop for you. Nice!
Hey, you could even use ChoiceScript to write ChoiceScript for you! [Tool] ChoiceScript Helper (Currently Unreleased) - #2 by RETowers @RETowers
PROS: less tedious, the tool can be reused in multiple occasions.
CONS: you maintain your own tool and nobody else can take advantage of it.
*choice #3 - use strings
It turns out Choicescript already has an infinite data structure: strings! You can append the daily log to a large log string, and it can grow as much as your computer memory will allow. If you want to parse it back and breaking it up, you’ll need a separator among entries and you will have to write a little parsing routine.
Also, strings can’t be modified in place. If you want to change some data, you’ll have to copy the data over to a new variable and adjust the data while you do that. (You could implement that with *script for performance.)
Still, strings can be a useful tool if the log entry is simple and you don’t have to modify it - eg. you can log your travels with one character: N W S E.
PROS: unlimited growth.
CONS: slow performance, complex code for the parser, very hard to modify the data.
*choice #4 - use *script to create variables
You decide the go all out and understand what happens when you create a variable. Now, in scene.js, Scene.prototype.create simply does this.stats[varname] = value (also updates this.created). Ok, maybe with Javascript we can create variables as needed.
You need some trick as you can’t directly pass a variable to *script, but that’s easy to solve if you read from this.stats. Once the new variable is created, you can use it easily in ChoiceScript.
*create day 101
*create entry “You mastered a new spell”
*script this.stats[“log_”+this.stats.day] = this.stats.entryYour log for day ${day}: ${log[day]}
*set log[101] “Sorry, my bad, you didn’t master the spell yet”
This is rather simple and readable. Now you can programmatically create variables anywhere in your code (not just at top - this would be great for scoping or recursion). Still, you remember that unfortunately this is not a real array where you can get the length, insert, remove, append or sort.
PROS: very simple, allows for unlimited growth, no need to pre-create variables.
CONS: *script is a little like cheating, and your game won’t be hosted on COG.
*choice #5 - Use JS arrays directly
Now that you have discovered Javascript, you decide to try to interface Choicescript to a real Javascript array. Again, you’ll need some workaround to pass data forth and back between the two layers but, wow, this looks like fun.
-
You need to define JS functions to create, read, and write the array. You need a persistent namespace to store the functions, so you use document (you guessed that this.stats would also work, but this doesn’t seem to be the case). These functions will read their arguments from shared variables via this.stats, like you did at choice #4.
-
Every time you create / read / write, you’ll have to use *script to invoke the functions.
-
If you built some preprocessor at choice #2, you can extend it with some shortcuts for this.
-
Oh, yes, you need to pass or bind ‘this’, because… Javascript.
You try to read data from an array.
*script document.js_array_read = function(x){
x.stats.js_value = x.stats[x.stats.js_varname][parseInt(x.stats.js_pos)];
}*comment This will read log_array_js[day] into entry
*js_read log_array_js day entry
Your tool will expand the last line to:
*set js_varname “log_array_js”
*set js_pos day
*script document.js_array_read(this);
*set entry js_value
Your log for day ${day}: ${entry}
It’s a little tricky to set up but, with some tooling, the code (before expansion) is very easy to understand. Also, in your experiments, it runs REALLY fast, faster than a collection of pre-created variables. And, it’s a real array that you can potentially sort, map, filter etc. using some *script.
PROS: unbounded data structure, dynamic creation, and IT’S FAST.
CONS: hard to setup, and again it’s using *script.
*choice #6 - Not selectable - Modify Choicescript
You decide to just hack Choicescript, add your array commands in scene.js and tweak them the way you like. Now in your new CS+, you can *create_array *slice_array *array_replace and all the magic that other languages have. As you are on it, you also change strings so they are 0-based, instead of 1-based, for expressions like string#pos.
PROS: the sky is the limit.
CONS: well, now your game can’t be hosted anywhere.
Thanks all for reading this far. What choice did you take?
In my experiments, #5 seemed to be very powerful, but probably #4 strikes the best balance: it requires only one simple *script command to get the job done.
Now, if *create got extended to accept an expression for the variable name (a limited change in code, it would reuse a lot of code from Scene.prototype.set), this would make a lot of interesting tricks possible. The often requested *create_array could be emulated with a loop of *create varname_{id}.
Oh, I didn’t speak about matrices and objects, but the problems and solutions are similar to these.
So, do you have similar tricks to share? Let me have your thoughts!