- Anatomy of a Sheet Worker
- Events, and watching Attributes
- Variables – How to Name Things
- Arithmetic in Sheet Workers
- What If? in Sheet Workers
- JavaScript Objects
- Getting Loopy With JavaScript
- Logging in the Browser Console
- Strings, Arrays, and Loops
- Asynchronicity and Things to Avoid With Loops
- Changes and the eventInfo Object
- Action Buttons
- setAttrs and Saving Attributes
- Castle Falkenstein Design – Sheet Workers
- The Perils of Sheet Worker Functions
- The Script Block and Identifying Characters
- Arrays and Dropdowns
- Undefined and Other Error Values
- The Ternary Operator – The One-Line If
- Template Literals
- Functions and the Fat Arrow
- Strings in Sheet Workers
- A Sheet Worker Reprise
Probably the most fundamental operation in programming is the logical comparison – the humble if statement. Does something equal something else, if something is bigger or smaller, and so on.
Let’s say your character has a Move attribute that equals your Dexterity or Agility, whichever is highest. You could do that with the Math.max function (see previous post), but you can also use an if statement:
const dex = parseInt(values.dexterity);
const agl = parseInt(values.agility);
let move = 0;
if(dex > agl) {
move = dex;
} else {
move = agl;
};
Code language: JavaScript (javascript)
dex and agl are defined using const because those values don’t change, while move is defined with let. because its value needs to change. If in doubt, just use let everywhere.
We use parseInt to make sure those values are numbers, and set an initial value of move. Then we check if dex is higher than agl – if so, use that value and if not, use the other value.
There are several ways to write this more compactly, but this serves our purposes for now.
Those Weird Squiggles
You’ll often be tempted to do this:
if(DEX = 5) {
Code language: JavaScript (javascript)
The problem is, a single equality has just one meaning in JavaScript: it sets that value. The above line does not check if DEX equals 5. It sets DEX to a value of 5.
To do a comparison, you need to use a double or triple equality, like so:
if(DEX == 5) {
// or
if(DEX === 5) {
Code language: JavaScript (javascript)
Don’t feel bad if you use the single quality by mistake – I do this a lot! Just keep an eye out for it, and fix it when you find it.
As mentioned in Variables, data can be String, Number, Objects, or more. When comparing values, JavaScript will convert data if it can to make the comparison work. So this is true:
if(5 = "5") { // calculates as true
Code language: JavaScript (javascript)
In this example, the “5” is a string, but JavaScript converts it to the number 5 to make this test.
This is usually bad! It’s convenient but sloppy. There are many times you care about the date type and its value. This is where the triple quality comes in:
if(5 === "5") { // calculates as false
Code language: JavaScript (javascript)
This compares values but does not change the type. 5 is a number, and “5” is a string, so they are not equal. You should always use triple equalities over double equalities. It helps to avoid subtle errors you would miss otherwise.
There are other comparisons you can do. For example:
- >: bigger than
- <: smaller than
- >=: bigger than or equal to
- <=: smaller than or equal to
- >==: bigger than or equal to, and the same type
- <==: smaller than or equal to, and the same type
- !: NOT (this is described in the next tab, but essentially true becomes false and vice-versa.)
- !=: NOT equal to
- !==: NOT equal to, and the same type
In sheet workers, you can usually ensure data is of the correct type when you create your variables so may not need the same type comparisons. But it’s just good practice to use them when you can.
Here’s where things get a bit mind-bending. When doing a logical comparison, JS is not testing if something equals something else, it tests if something is truthy or falsy.
Recall from variables#boolean, there are six possible falsy values. Otherwise, the comparison is truthy. This means you can do this:
if(dex) {
Code language: JavaScript (javascript)
If dex exists, and if it’s a number that doesn’t equal 0, it is truthy. There’s an implied equals there.
if(!dex) {
Code language: JavaScript (javascript)
! means NOT, so do the test without the ! and then reverse it. True becomes false, and vice-versa. This is a good way to test if something does NOT exist, or its value is not equal to some desired value.
You can include multiple comparisons, using || (OR) or && (AND) to break them up.
if(dex >== 10 || agl >== 10 || str >== 10) {
Code language: JavaScript (javascript)
JS will test in the order you have listed, and if it finds a truthy value, it stops – it does not perform any remaining tests.
if( (dex >== 10 && agl >== 10) || str >== 10) {
Code language: JavaScript (javascript)
This tests if DEX and AGL are both 10 or higher, then tests if STR is 10 or higher.
Using OR and AND you can construct very complex tests.
You can exploit the habit of JS to stop at the first truthy value to set default values.
const int = parseInt(values.int);
Code language: JavaScript (javascript)
This grabs int from values, but what if int doesnt exist, or can’t be parsed into a number? That produces an error, which will make the code after this fail. But you can do this:
const int = parseInt(values.int) || 0;
Code language: JavaScript (javascript)
This is effectively saying set int to the parseInt operation, OR set it to 0. So if the parseInt results in failure, JS will proceed to the next value, which is zero.
You might set something else as default valeu – it depends on the situation. So could do this:
const int = parseInt(values.int) || "N/A";
Code language: JavaScript (javascript)
You can set anything there. It’s a powerful technique, and should be used a lot.
Remember that JS looks for falsy values, and one falsy value is 0. This means if you are looking for 0, you can have some unpredictable errors. I’ve seen this error a lot:
<imput type="checkbox" name="attr_example" value="0">
Code language: HTML, XML (xml)
Recall when you create a checkbox, the value is what the checkbox is set to when checked. When unchecked it always has a value of 0.
If you set the value to 0, and then test if(checkbox === 0), it will always report as true, because there is no difference between checked and unchecked.
While this can happen with checkboxes, it doesn’t just happen with checkboxes. You have to be careful when setting anything to 0.
A very useful concept in programming is the toggle. When something has exactly two values it is easy to swap between them. It’s easiest if they have a value of exactly 0 or 1. Imagine you have a Checkbox named carried with value=”1″, and an action button called carry. Then you can do this:
on('clicked:carry', () => {
const carried = 1 - int(values.carried);
setAttrs({carried});
});
Code language: JavaScript (javascript)
Action buttons will be described fully soon, but when you click them, they run some code. Here, you click the action button and it toggles carried between a value of 1 and 0. If it started with 0, it becomes 1; if it started as 1, it becomes 0.
Toggles like this are useful in lots of different situations. I always (always!) add value=”1″ to checkboxes – it makes their values easier to use in sheet workers for many reasons, but it makes them perfect for toggles.
The Else Statement
The else statement is handy for handling “what if the test isn’t true”. You can chain multiple else statements, and they can each have their own if statements.
if (int >15) {
/* do something when int is over 15 */
} else if (int > 10 {
/* do smething when over 10 but below 15 */
} else {
/* do something if none of above if statements are true, so if int is 9- */
}
Code language: JavaScript (javascript)
Remember that in javascript, the code stops at the first truthy statement. So in the above case, if int was 16, the second and their lines would not run. This can be very handy.
There’s a weird resistance from some sheet authors for using the else statement, so you end up with code like
if (int >= 20) { /* some code*/ }
if (int >=15 && <20 } { /* some code*/ }
if (int >=10 && <15 } { /* some code*/ }
if (int >=5 && <10 } { /* some code*/ }
if (int <5 } { /* some code*/ }
Code language: JavaScript (javascript)
But using an else statement is more efficient, and you can take advantage of the fact that JS stops at the first true statement, so you can rewrite the above code to this:
if (int >= 20) { /* some code*/ }
else if (int >=15} { /* some code*/ }
else if (int >=10} { /* some code*/ }
else if (int >=5} { /* some code*/ }
else { /* some code*/ }
Code language: JavaScript (javascript)
Using else is always better than not using it.
Analysing Your Problem
The most important part of building a sheet worker is figuring out what you are trying to do, and analysing what you need to do. Often you can produce much simpler code if you look at what it’s doing. For instance, I’ve seen code like this:
const dex = int(values.dex);
let dex_mod = 0;
if (dex >= 18) {
dex_mod = 4;
else if (dex >= 16) {
dex_mod = 3;
else if (dex >= 14) {
dex_mod = 2;
else if (dex >= 12) {
dex_mod = 1;
else if (dex >= 10) {
dex_mod = 0;
else if (dex >= 8) {
dex_mod = -1;
else if (dex >= 6) {
dex_mod = -2;
else if (dex >= 4) {
dex_mod = -3;
else if (dex >= 2) {
dex_mod = -4;
else {
dex_mod = -5;
}
Code language: JavaScript (javascript)
This looks at every possible result and works out the result for them. But what about special cases above 18 (like, what if dex is 23, or 35?)?
A much more compact method is to analyse the structure of the attributes and figure out how the modifiers are calculated. We can see they give +1 for every 2 points of difference in the stat, which suggests a very simple calculation:
const dex_mod = Math.floor(Dex/2) -5;
Code language: JavaScript (javascript)
This handles any score in dex. You might want to limit it so the penalty never gets below -5 (an attribute score of 0), and limit the bonus to a maximum of +10. That’s where Math.min and max come in, like so:
const dex_mod = Math.min(10, Math.max(-5, Math.floor(Dex/2) -5));
Code language: JavaScript (javascript)
That looks complicated, but you can break it up to make it easier to read like so:
let dex_mod = Math.floor(Dex/2) -5;
dex_mod = Math.max(-5, dex_mod);
dex_mod = Math.min(10, dex_mod);
Code language: JavaScript (javascript)
You should always write code you can read, and consider how it will be read by people who come after you. All of these code examples work, and if you can’t see a way to simplify the expression, it’s fine to use a big elaborate if statement. But if you can see a way to simplify it, your code will be much easier to write.
The point here is: if you find yourself building a complicated if statement, analyse the process and see if you can see any rules to make it simpler.
Branching and Scope
This post has concentrated on using if statements for calculations, but you can also use if statements to decide which code to run.
let carry = 0; speed = 0; spell_power = 0;
if (str > 0) {
/* calculate carry capacity */
}
if (dex > 0) {
/* calculate movement speed */
}
if (pow > 0) {
/* calculate magical power */
}
Code language: JavaScript (javascript)
In this example, three different branches of code are run, but each is only run if the stat is higher enough.
However, this runs into the problem of scope. Each { } contains its own scope, and variables created inside a scope exist only inside that score.So if carry was created inside the str >0 scope, any value set there would stop existing when the code moved on to the dex >0 test. This is why some variables are created before those scopes exist.
If the variable exists before the scope states, you can set its value – and that value will persist after the scope ends. So you have to be careful about this.
This also means that you can create a variable before a sheet worker, and it will exist inside that sheet worker. Such global variables can be useful. You’ll see some uses for them later.
Learn more about Scope, if so inclined.
Summary
So you’ve learned the basics of using if statements, including the rules of logic comparisons, using else statements, the basics of analysis and scope.
There are two main alternatives to using if statements that will get their own posts:
- The Ternary Operator
- Switch