Obviously, this thread title is tongue-in-cheek. But there’s certain features that are lacking compared to other languages that I’d like to see, things which would help prevent bugs and improve the authoring experience.
First, the goal of any ChoiceScript changes would be to make the language easier to use, and to encourage good practices that will keep code more consistent and easier to maintain and help prevent bugs. Most importantly, I’d say, any changes like this need to not break any existing code.
So, here are three issues, or three parts of one big issue:
1. There are no constant values.
A constant is like a variable that never changes. They’re great to use in a narrative game context to set, for example, target values that skills and stats will be compared against to determine the results of #option
s. That way, you can test the gameplay and see if these values are too easy or hard, and they can be immediately adjusted all in one place, at the top of the scene file.
For instance, I’ve started defining and using values as if they were constants in my current project:
*comment integer-stat-testing target values
*temp TEST_EASY 2
*temp TEST_MED 3
*temp TEST_TOUGH 4
*comment secondary value testing points
*temp THRESHOLD_MINOR 10
*temp THRESHOLD_MAJOR 20
(By convention, constants are typically named in ALLCAPS, but this isn’t strictly necessary.)
Then, whenever I test a skill, I can test *if
skill
>
TEST_EASY
, or *if
secondary_value
>
THRESHOLD_MINOR
, and so on.
This provides a compact, easy place to keep all these important numbers, to ensure they’re all consistent. If I want to adjust one for balancing purposes, I know I can’t accidentally overlook a single instance hidden somewhere.
Defining these as *temp
s also lets me adjust difficulty every scene; later scenes should have more difficult skill tests than earlier ones, for instance.
But since these are variables, not constants, this is a bit dangerous. For instance, I might accidentally
*set TEST_EASY successes
when I meant to do the reverse, and then not realize my mistake until I noticed that my skill checks were easier or harder than they were supposed to be in this chapter. Depending on how diligent I am about testing manually or how closely I read randomtest transcripts, that bug might take months to recognize! Whereas if TEST_EASY
were a true constant, that line would immediately throw an error.
2. There’s no dedicated enumerated types system.
This is closely related to the last issue; enumerated values in other languages are typically constants, and I feel they should be here, as well. Multireplace and arrays may have made this concept a bit less necessary than it had been a few years ago, but it’s still not really the ideal situation.
Basically, the idea with enumerations, aka “enums”, is there is a set of certain things which are all related, and one (or more than one) variable whose values are limited only to them.
Gender is the classic example. Say the player (or another important character) can be one of female, male, or nonbinary.
If you implement those three as strings, that’s dangerous; what if you misspell one of the words? CS has no way of knowing what a valid “gender” string is supposed to be. It doesn’t even know the concept exists. If you’re not careful, the game will just fail silently, not displaying any word when it always should display one of them. The only way you’ll even get an error message is if you don’t use an *else
in your gender-related code.
Alternately, you could make it a number. You could even define those various gender (or whatever) possibilities as unique numbers in startup.txt
:
*create FEMALE 1
*create MALE 2
*create NONBINARY 3
etc.
Then you can just say *set
sam_sex MALE
wherever relevant in your scene, which makes your code easy to read. You can also use multireplace on any enum variable:
She rolls her eyes. "@{sam_sex Girls|Guys|People} like Sam over there…
Well, they're hard to understand."
One problem, though, is these are treated as simple numbers internally by CS; the language doesn’t know that 1
and 2
and 3
are valid sexes, but 0
and -2
and 17
and 0.625
are not. If you somehow accidentally set someone’s sex to an invalid number, the only way you’ll realize it is later on, some lines or paragraphs or even whole scenes later, because an error message will only pop up whenever you next try to test the value. This makes tracking down where the problem actually occurred incredibly difficult.
And, because they’re all numbers, you can do weird stuff like *set
FEMALE
((
MALE
*
MALE
) -
FEMALE
)
. You shouldn’t do that, obviously, but it is valid code, and it will run.
If enums instead got their own dedicated type system, you couldn’t set a “gender” variable to anything but FEMALE
or MALE
or NONBINARY
. Any attempt to perform mathematical operations on it would throw an error message. And any attempt to set the variable to anything else, “male” or “MALE”, 0
or -5
, or even 1
or 2
, would as well. Better to fail immediately with a clear explanation than to misbehave, or fail silently, or throw an error message ten minutes later pointing to the wrong part of your code.
I do use fake enums like this in my own latest project. The best way to demonstrate the concept might be in an example:
startup.txt
*comment Sexes
*create FEMALE 1
*create MALE 2
*create THEMS 3
*create PLURAL 4
*create OBJECT 5
*comment Pre-populate the pronouns
*create they_1 "she"
*create they_2 "he"
*create they_3 "they"
*create they_4 "they"
*create they_5 "it"
*create them_1 "her"
*create them_2 "him"
*create them_3 "them"
*create them_4 "them"
*create them_5 "it"
…etc., for all the pronouns.
Then, in a scene.txt file
Defining these pseudo-enums in startup
lets me use them dynamically in paragraphs, thanks to multireplace and arrays:
*temp sam_sex MALE
*if pc_sex = MALE
*set sam_sex FEMALE
Sam walks up. You shoot ${them[sam_sex]} a meaningful look.
"Well," ${they[sam_sex]} says. "Perhaps."
This will automatically display as
if Sam is male, and
if female.
3. There’s not enough type safety. (Or, maybe not enough types.)
Not quite as major as the other two, but it’s all more or less interrelated. The way ChoiceScript checks values means a lot of user errors can slip through, becoming very difficult to track down.
For example: ChoiceScript lets you add a fairmath number to a fairmath number, even if that would bring its value above 100; or subtract the one from the other, even if that would bring its value below 0; or perform any other operation that would bring its value out of the 0-100 bound. This is almost always a bug, but CS lets you do it, because it doesn’t recognize that a number for use in fairmath is supposed to differ in any way from a “money” or “number of troops” or “acres of land owned” sort of number.
Again, the only way you’ll realize you’ve accidentally set a fairmath number too high or low is later on, possibly even in a different scene than where you set it, whenever you use that variable in another fairmath calculation. The culprit is usually some +
or -
that was accidentally written when the author meant to write %+
or %-
. If there was a fairmath-number type, CS could throw an error message immediately whenever its value was set outside the valid fairmath range.
If the author could explicitly define variable types from the outset—or maybe there was a use_strict_typing
flag, akin to implicit_control_flow
, which required the author to explicitly declare the type for every *create
and *temp
variable—this would prevent such hard-to-track-down bugs.
Even if such a flag wasn’t used, CS could dynamically determine variable types, as it already does right now. Number, string, and boolean types would be determined as they currently are; enums would be recognized when they’re set or initialized to an enumerated value like MALE
or FEMALE
or BLOND
or BLUE
; fairmath values whenever a *set
A
%+
B
or *set
A
%-
B
was done to them.
Thoughts?
I have some ideas about potential syntax for these proposed features, as well, but this post is long enough already. First, I’d like to hear what you think about them.
And, as I mentioned before, I want to emphasize that none of these changes should break your code. Well, OK, maybe if you’re doing some kind of complicated math on a fairmath value, without using some intermediate *temp
variables, while still managing to constrain the result within 0-100 at the very end. Or, if you’re assigning numbers to string variables and vice versa. But, uh, don’t do that.