GScript - A Javascript/ChoiceScript Framework Project

Hey there.

This is a framework to try to give new possibilities for game making, trying to simplify usage of JavaScript for someone making a CS Game.

As a TypeScript programmer I decided to make this little side project as a fun past time and I’m now sharing if others are interested in trying it out. It might be confusing at first but I tried to make the documentation easy to follow.

The project is over at my github, as is the documentation and installation instructions.

To download it, visit the link above and click on the Code button and then ‘Download as zip’. Or click the link below:

https://github.com/F-Ramos-B/gscript/archive/master.zip


Installation instructions

Simply extract the contents of the file to your dfabulich-choicescript-8456ed7\web\mygame folder and overwrite index.html when asked.

I’ve included a startup.txt file that uses some of the functions of the framework, but make sure to backup your startup.txt if you have one already!

At the start of your game, after you declare your variables, add this line so the framework can start:

*script startup()

I’ve added a sample startup.txt file as an example that uses some of the functions this framework provides.

Disclaimer

This project is has no involvement or support from Choice of Games LLC and is entirely personal. I may at any time decide to stop supporting this project and will not be held accountable if it screws up something on your game or if a ChoiceScript update breaks something.

I do not know if Choice of Games LLC will accept games published using this or any sort of games using JavaScript at all so use this on your game at your own peril.

This project uses Lodash (https://lodash.com/) for internal operations and it comes bundled with the installation.

Documentation

Click the github project link above and scroll down for documentation and examples of things you can do and use.

10 Likes

Hi @GoldenSilver. This is very exciting. I like the fact that you used *script as an escape hatch. It means changes to the core of CS are limited and may require less effort to be integrated for publication. I have a few questions/suggestions:

  • Dynamic styles are very nice. From what I see, CS renders all the page in the same “text” div. How can you apply the style selectively? Does *applyStyle change the whole page? Would it be possible to style specific images or paragraphs in the page?

  • I love the object support. Can you explain in more details why you can’t simply do ${instance[field]}. If you keep it simple, you can *set instance_field value and get it done.

  • In “predicate” shouldn’t variables be referenced as this.stats.VARNAME? This is what works for me. I believe this.created doesn’t hold the value.

  • What’s the cost of sorting operation? Do you have to copy the simulated array into a JS array, sort it and copy it back? What about a syntax to just use native arrays directly (similar to your supplyInstance for objects)? I have some thoughts here: How to deal with arrays (choice #5, and use of tooling to help developers)

  • Can you resize or add elements to arrays? I think it would make them usable in a broader context. Eg. infinite growing log. In one of my demos The Dungeon of Lamurloq (first-person POV dungeon crawler) I use a trick to programmatically allocate an array of given size (from a variable).

  • Yes, CONSOLE. Thanks!

  • Yes, dynamic creation/removal of variables.

  • Functions. Did you look into supporting real, scoped functions? CS gosub can mess up your state if you are not careful.

Thanks for sharing this. I hope it grows into something widely adopted.

Hi, thanks for checking it out.

For now all style things are pointing to the document.body. In theory it should be possible to select any node in the page by id or the DOM node tree by accessing whats inside of document.body.

For example I used *script printer(document.body) and it gives me access to any node in the DOM, so it should be possible to style anything selectively.

The reason is that I tried to inject an entire object as a created or temp variable which does works, but CS cannot access its properties with dot notation or the object[property], trying to do so results in an error.

For example, I added a car object to test this, this is a print of my temp variables:

image

You can see that it’s saved correctly along with other temp variables. But trying to access name, color or year does not seem to be possible.

*script injectTempVar('object_test', {name: 'ford', year: 1996, color: 'red'})

Print object: ${object_test}

Print property: ${object_test.name}

Print property 2: ${object_test[name]}

Print property 3: ${object_test['name']}

*script printer(getAllTemps())

All of these except for the first (which just prints [object Object]) give me an error:

image

So because of this I came up with the HashMap instances implementation, since it doesn’t seem possible to access object properties directly from CS.

I actually made two shortcuts for getting created and temp variables, but I completly forgot to explain that in the documentation lol, I’ll update it later. You can use:

vars.created.VARNAME
vars.temps.VARNAME

To access created or temp variables (because when passing variables from CS to my JS functions you need to use that, just the variable name won’t work. Forgot to explain that. Give those two a try and see if it works for you. Also don’t use camelcase, keep variables lowercased since CS seems to transform them into lowercase.

*create arr_1 20
*create arr_2 10
*create arr_3 30
*create arr_4 80
*create arr_5 70

In CS any array you make is created like this:

image

So it isn’t actually a real array, just a bunch of variables with the index at the end, which in CS you can use the bracket syntax to retrieve a specific one. So to actually use array functions on this I had to:

  1. Get the array name and use it to filter either the created or temp variables for variables that have that name on them
  2. Count how far up the _x goes.
  3. Decrement all indexes by 1 (since on CS arrays start at 1 but they start at 0 on JS)
  4. Discard the variable names and then just use JS .map function to create an array out of it.

This process is done by the wrapArray function on wrappers.js file if you’re interested. I don’t know how costly this operation is since I don’t know much about algorithm processing cost.

wrapArray

Once whatever operation is concluded, the reverse process is created by unwrapping the array into these loose variables. This receives a name for the new variables and the array itself. The operation uses .reduce to create these variables which the key name and also returns the size at the end, this is then used by the injectors to create a bunch of variables. This process occurs on the runUnwrapper function on wrappers.js if I remember right.

During the unwrap, all indexes are incremented by 1 to make these arrays start at 1.

unwrapArray

image

Using something similar to how I implemented the instances is completely possible. I did it this way instead so that the creators could still use the normal CS ${arrayname[arrayindex]} syntax (since this one is accepted unlike the object one).

With the array wrapping and unwrapping it would be a bit more annoying I think. But using it similar to instances it should be easy to do, since I can simply use the .push or .splice functions to add or remove from arrays.

On second thought I should have gone for something like the instances as the wrapping and unwrapping was a bit of a pain to work with. With the way it is now I don’t think you could add or resize the arrays since they are recreated on every operation.

*script supports quite a lot of things as long as you keep it on one line. I added some misc functions that I didn’t detail on the documentation because I didn’t know if they would be useful or were working correctly as of yet.

For example I added the repeatableConsumer function which accepts a function, times to run and values as parameters. (There was a bug on this function, I just pushed an update to correct it, make sure to download the file again).

*script repeatableConsumer((param, index) => console.log(5 * (param || index)), 20, 40, 50, 60, 70, 80, 90)

# The function is the first parameter, the second (20) is how many times will run, all numbers past that are the values.

The function above for example will be run 20 times, and the function will receive each of the values and the current index as parameter. It will do a console.log of 5 * parameter (or index if parameter is falsy as it will occur after some iterations because I haven’t supplied 20 numbers).

Result

I only added these mostly for testing, since I didn’t thought of how useful they could be so that’s why I didn’t explain it on the documentation.


Feel free to post ideas for new things I could try to implement. Most of these were what I thought could be useful at the time.

Also feel free to open the goldenscript folder and take a look at the files inside. The functions of the framework are all there (apart from the lodash.min.js) which is an external utility library I use on the project. If you feel you can suggest modifications, improvements or new things let me know.

2 Likes

Hi @GoldenSilver, thanks for your message. I’m happy to read you have already thought about some of my questions.

  • Styles. Yes, you could walk the DOM and say “apply style to node #4”, is it what you mean?@Flurrywinde11 was working at adding new BbCodes (similar to [b] and [i]) to CS. I believe that approach can give you more granularity. You may want to talk to them.

  • Objects, dot notation. If you are modifying CS, maybe you can change the way CS interprets the square brackets. Right now it expands to underscore + string concatenation, but it could easily expand to actual array/object bracket notation. If you did that, a lot of your code would simplify. If you want to maintain compatibility, maybe you can have a setup flag (similar to implicit_control_flow). Would that work?

  • Array wrapping/unwrapping done this way wouldn’t work for intensive array operations. Again, a real bracket notation would help greatly. That way, you could use the JS array directly for sorting, extending, slicing etc.

  • Nice way to do the loop! The repeatableConsumer is a cute idea. I really miss a *for command in CS. The way I do loops is to run a preprocessor in front of my code. It maps my loops to a sequence of *goto *if *label etc. In general, I think of CS as a compilation target - like Assembler. I add the stuff I need to my tool, and it maps it down to CS. If you look at the source of my demo here, you will see a lot of it is auto-generated. A few small extensions in CS (eg. style control, bracket notation) could go a LONG way when managed by a tool. Which way could you expose the loop? @cup_half_empty was also interested.

  • Functions. Yes, you can use *script but the interaction with CS (variables) is a little clunky. I was looking into a CS extension to manage functions properly, with scoping, return values and recursion (without having to use *gosub_scene etc). This would probably require deeper changes in CS. What do you think?

2 Likes

That could be a little more complicated than it seems. Could add more selectors for the textReplacer but I use innerText for it. To add specific styles inside a tag wrap it would be needed to create a new node and the like so a style could be applied to it.

I have not modified CS’s files directly. The only thing I did was manipulate the current stuff available on the stats object once the game is being run.

Better stuff like you mentioned could be achieved by doing that sure but then you’d be making a new CS version pretty much. I haven’t looked at CS source code directly and I’m not that interested anyway because a new version might come out and break any changes and also this project was more of a side diversion to try out some lodash functions.

Even then there is the possibility that games would never be accepted while using this or a modified CS version so it could all be a lost cause or just be limited for WIP usage.

If you’re interested in taking a look, it seems most of the CS parsing stuff is made on mygamegenerator.js and web/scene.js files. Line 3736 of scene.js has the Scene.prototype.evaluateReference that seems to do the evaluation for array brackets so that’s a place to start I suppose. Maybe it could also be modified for accepting a dot for objects.

1 Like

Oh, I see, you simply injected some of your JS in index.html. Nice, very clean and clever.

Yes, if you don’t want to touch CS core you are somewhat limited. For instance, to do updated BbCodes, you’d need to heavily hack into the rendering.

In any case, your idea of pushing some JS next to CS is really cool. It is probably still an obstacle to publication (and I think you couldn’t use Dashingdon either) but the path you set is really interesting. I will look if it’s possible to hack into the Scene.prototype in a way similar to what you did.

1 Like

Yes, it works. With *script, you can modify CS’s functions themselves! You can start doing

*script Scene.prototype.evaluateReference = … new function all in one line…

evaluateReference is the first place to start - you could return a pair (var, field). But you’d have to fix all its uses, and there are many, unfortunately. You’d rewrite half CS. Maybe with some scripting, you can find what functions your mod changed and convert them to *script entries.

In other words, it’s possible to inject a CS mod just by using *script, so a game can still be hosted on Dashingdon, but probably not a great idea. :slight_smile:

Keep us posted on where your tool takes you!

Yes, please, not a great idea. I’ve had to remove two accounts using script ‘workarounds’ for nefarious purposes and if this sort of thing becomes more prevalent then I will have to shut down the site. I don’t have time to micromanage exploits, unfortunately.

Yes, I can imagine a bad actor exploiting the ability to run JS :frowning:
By the way, thanks again for all your effort to run the site, it’s a great gift to this community.

This is actually one of the exact same reasons I don’t currently allow *script in the ChoiceScript IDE. It’s all fun and games until someone realises they can take complete control of your PC through a ChoiceScript game… :grimacing:

Not to undersell what GoldenSilver has done here (it looks great!), but I’d much rather see us make concerted (polite) efforts to convince CoG to add/address missing features in ChoiceScript. Having half a community reliant on unofficial modifications isn’t really ideal. It’s great to use them as development stop gaps, but I think the end-goal should always be to contribute back to the source.

4 Likes

I have found a simple way to add style to a specific paragraph. It can be easily managed with a preprocessor macro, without injecting JS into the main CS code.


This will be normal.

This will have a fancy font.

*script     (ALL ON ONE LINE, BROKEN HERE FOR CLARITY)
  var pars = document.getElementsByTagName('p');
  var lastpar = pars[pars.length-1];
  lastpar.style.fontFamily="fantasy";

This will be normal again.

Yes because you are modifying the DOM directly. Everything that is on my addon can be done inside the *script commands without needing any extra js files at all, but since you have to do stuff in one line it would be very messed up.

I added a function for allowing any string passed (this string contains a bunch of css commands split by ; , like for example (“color: white; font-size: 16; font-weight: 600;”) so it was possible to use a *text_input and have the player type their own style to apply and use that string to apply everything to the current page style.

That function is commented but it was working somewhat ok, feel free to try it out if you wish, its on the styles.js file.

1 Like