Back to School – Arithmetic in Sheet Workers

There’s a lot you can do with JavaScript, but you are usually concerned with one thing: updating an attribute value. For that, you need to be able to do two things: perform arithmetic and work with strings. That’s mostly what this post is about. But we’ll touch on a few more advanced things too.

In the last post, we covered the basics of variables, how they are created and how to avoid common errors. Nearly all of your sheet workers will be calculating attributes and for that you need numbers. That’s what this post is about – we’ll come back to strings later.

Grabbing Attribute values

The last post described how to grab attribute values from a character sheet. That looks something like this:

on('change:dexterity change:agility', () => { getAttrs(['dexterity', 'agility'], values => { const dex = int(v.dexterity); const agl = int(v.agility);
Code language: PHP (php)

This worker watches for changes in the dexterity and agility attributes defined on the sheet, and every time they change, this sheet worker fires, and through the getAttrs line, their values are grabbed from the sheet and stored in the values object.

The last two lines use the int function described in the last post, and grabs the scores from the values object and stores them in the dex and agl variables.

As described last post, the last two lines could have been written like this:

const dex = parseInt(v.dexterity) || 0; const agl = parseInt(v.agility) || 0;
Code language: JavaScript (javascript)

Using the int function leads to a lot less typing (and there are other advantages).

Changing Attributes

Now we have some attribute values stored in variables, what can we do with them?

Arithmetic

The simplest thing is to perform arithmetic of various sorts. Here are some different calculations:

const move = dex + agl; const move = dex *2 + agl; const move = (dex + agl) *2; const move = dex/10 + agl/10;
Code language: JavaScript (javascript)

Simplifying Arithmetic

You can add (+), subtract (-), multiply (*), divide (/), and more. But addition and division are usually all you need. But you also can do this:

dex += 5; agl -= dex;

The +-, -=, *=, and /= syntaxes are shorthand. Instead of typing this:

dex = dex +5;

you can simply type:

dex += 5;

Whenever you find yourself altering a value, you can use syntax like += or *=. Javascript knows that you are modifying the original value by the modifier.

There is a difference between += and =+, but its every subtle and pretty much always irrelevant in sheet workers, so you can ignore it.

Rounding and the Math Object

You might also need to round an attribute (to the nearest, rounding down, or rounding up). This is where the math object comes in…

Here are two lines that do exactly the same thing, but are written slightly differently.

const move = Math.round(dex/10 + agl/10); const move = Math.round((dex + agl)/10);
Code language: JavaScript (javascript)

In sheet workers (and programming generally), there are often a lot of different ways to get the same result. Often it doesn’t matter which you use – the important thing is to find an approach you like.

The above examples round to the nearest whole number. But what if you want to round down? The stat bonus in D&D, is based on rounding down. Stat 10-11 gives +0, stat 12-13 gives +1, and so on. This is the same as Stat/2 -5, as long as you round down. That can be written as:

const dex_bonus = Math.floor(dex/2) -5; const agl_bonus = Math.floor((agl-10)/2) -5;
Code language: JavaScript (javascript)

Again they are two different ways of calculating the same result. You can use Math.ceil() to round up.

Here’s another trick you can do with rounding. Let’s say you have a calculation that adds the weights of things together, where you can easily end up with thousandths and lower, but want to display the value in hundredths without changing the actual values.

const weight = float(v.weight); const display = Math.round(weight *100)/100;
Code language: JavaScript (javascript)

So your weight might be something like 17.2465. You want the full value, so use float in place of int.

Then you multiply by 100 and round. it becomes 1725 and is then divided by 100 without being rounded. The final total is 17.25. This is perfect as a neat display value.

You can use rounding to change the values, or to create values perfect for display. But the Math object isn’t just for rounding. It has a lot of useless functions buried in it (and some that are useless for character sheets).

Other Math functions

You can find a complete list of Math functions online, like here, but I’ll describe a few that are most useful for Character Sheets.

The Max and Min functions are very useful, and I use them maybe more often than I use the rounding functions. Each works the same way – supply a group of numbers separated by commas and it’ll tell you the largest or smallest.

const largest_stat_value = Math.max(dex, agl); const smallest_stat_value = Math.min(dex, agl)
Code language: JavaScript (javascript)

Using max is great for limiting values. Say you want to add dex and agl, but the minimum score must be 0:

const sum_of_dex_and_agl = Math.max(0, dex + agl);
Code language: JavaScript (javascript)

Each value to evaluate must be separated by a comma, but you can include calculations in each ‘value’.

You can also nest max and min (and any other function). Say the sum must be at least 0, but no higher than 30, you could do:

const sum_of_dex_and_agl = Math.min(30, Math.max(0, dex + agl));
Code language: JavaScript (javascript)

Here Math.max has been nested inside Math.min. When doing this kind of thing, make sure you keep the number of brackets right, and remember to use min and max correctly. Remember min gives you the smallest number of the set, and max gives you the largest number. I frequently use the wrong one, and have to correct myself!

When you want to create a random number, you have a special ability on roll20 – you can use the dice function. But that’s a bit complex (and will be covered later). In the meantime, if you want to create a random number, you can use the Math.random() function.

This function generates a number from 0-0.999999999 (recurring), so you need to multiply by a value to get a dice value, then add 1 otherwise it’ll start at 0. For example, a d6 would be

const d6 = Math.floor(Math.random() *6) +1;
Code language: JavaScript (javascript)

You need to round off because it generates numbers like 0.453267, and you need to round down, and then add 1.

You can use this to create a dice roll function, and we’ll come back to that in the post on loops.

Math.sign gives you the sign (whether a number is positive or negative). This is handy when constructing strings. By default a number will be shown like 17 if positive, and -17 if negative. But you might want positive numbers to be shown like +17.

To understand this needs if statements, and ternary operators, which are covered in the next chapter, so this is shown as an example without much explanation for now.

This is easily done with Math.sign. Math.sigh gives a value of -1 for negative values, 1 for positive values, and 0 for 0 or -0. So think about what you want to happen, and construct an if statement that works for that:

const score = int(v.score); let with_sign = score; if (Math.sign(score) >= 0) { with_sign = "+" + score; }
Code language: JavaScript (javascript)

This example uses let in place of const because the value of the variable might change.

It also uses + to add two elements in a string, so if the score has a value of 17, it will be shown as +17.

And that is of course wrapped in an if statement. You’ll learn more about those in the next post.

Here’s a list of Math functions I have found useful, some of which have already been mentioned:

  • Math.round: Round to nearest whole value.
  • Math.ceil: Round up.
  • Math.floor: round down.
  • Math.max: pick the largest number out of a group.
  • Math.min: pick the smallest number.
  • Math.random: Generate a random number. handy for creating your own dice functions.
  • Math.sign: report the sign of a number. positive = 1, negative = -1, 0 = 0.
  • Math.abs: Convert a number to its absolute value, removing its sign.
  • Math.pow: calculate the power of a number. Math.pow(number, 2) gives the square of a number, and Math.pow(number, 0.5) gives the square root.

Saving Attributes

Now that you have a result, you’ll want to update the character sheet with the new value. Chapter 9 covers saving attributes in much detail, but for now, this is how you do it.

Let’s say you have created a variable called move, and want to save it to an attribute called speed:

setAttrs({ speed: move });
Code language: CSS (css)

You might want to save multiple attributes, in which case you separate them with commas, like so:

setAttrs({ speed: move, dexterity_bonus: dex_bonus, agility_bonus: agl_bonus });
Code language: CSS (css)

You can use the same name for the variable as the attribute you are being saved to, like so:

setAttrs({ speed: move, dex_bonus: dex_bonus, agl_bonus: agl_bonus });
Code language: CSS (css)

In fact, there’s an advantage to doing it that way – you don’t have to include the name twice. You can do this:

setAttrs({ speed: move, dex_bonus, agl_bonus });
Code language: CSS (css)

When the variable is the same name as the attribute, you only need to include one of them.

Conclusion

In this post, we described how to perform simple arithmetic, and save the attributes back to the character sheet. In the next post, we’ll cover conditional calculations – using if statements. You’ll find these are very useful.

Series Navigation<< Variables in JavaScript and Grabbing Attributes

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.