|
About Expressions
In Smash, an expression is some kind of mathematical phrase that evaluates to an integer. An expression can be as simple as an integer -- such as "3" or "5" -- or be as complex as a meticulously crafted mathematical formula.
Expressions can appear in several places in Smash. When you assign a value to a variable, the value takes the form of an expression. For example, everything right of the equals signs in each of these Smash commands is an expression:
- s var = 41
- v glob = var - 7
- v n = (var * 3) +/ (glob * 4)
- s check = e:coins >= apples
Expressions are also used in conditionals, for example:
- c a:box
- c d:ring
- c e:coins > 5
- c x
- c x**2 <= y/4
When an expression is used in a conditional, then the block of code following the conditional is executed only if the expression evaluates to something other than zero. Similary, a loop executes repeatedly, so long as the loop's conditional evaluates to something other than zero:
. You hug and kiss your beloved five times: v i = 0 L i < 5 , X O v i = i + 1
(The above loop sets the current area description text to "You hug and kiss your beloved five times: X O X O X O X O X O".)
Expression Syntax
Expressions consist of an integer, a variable, or one or more integers or variables modified by operators. Operators are those mathematical symbols between the integers and variables, such as + (plus), - (minus), < (less than), > (greater than), and many others.
If an expression consists of just an integer, it evaluates to itself. For example, the expression 3 evaluates to the integer 3. Simple, right?
A variable evaluates to its current value. If the variable "choice" is currently set to 7, then the expression choice evaluates to 7. Still simple, right?
When operators are included in an expression, the associated mathematical operation is performed. For example, the expression 3 + 5 evaluates to 8, because the + operator adds together its two operands. Complex expressions can be constructed using multiple operators: for example, 3 + 5 - 2 + 1 evaluates to 7.
The following is a categorized list of all the operators supported by Smash. In these charts, a and b refer to the operands of the operators. Except where noted below, these operands are expressions -- expressions, you see, are made up of other expressions. For example, in the expression 1 + 2 - 3 the minus sign is an operator whose operands are the expression 1 + 2 and the expression 3.
Mathematical Operators | |||
---|---|---|---|
Operator | Name | Usage | Meaning |
+ | Addition | a + b | Evaluates to the sum of a and b. |
- | Subtraction | a - b | Evaluates to the difference of a and b. |
* | Multiplication | a * b | Evaluates to the product of a and b. |
/ | Division | a / b | Evaluates to a divided by b. If b does not divide evenly into a, then the result is rounded towards zero, i.e., rounded down if the result is positive and up if the result is negative. |
-/ | Floor Division | a -/ b | Evaluates to a divided by b. If b does not divide evenly into a, then the result is rounded down. |
+/ | Ceiling Division | a +/ b | Evaluates to a divided by b. If b does not divide evenly into a, then the result is rounded up. |
% | Modulo | a % b | Evaluates to the remainder of a / b. |
-% | Floor Modulo | a -% b | Evaluates to the remainder of a -/ b. |
+% | Ceiling Modulo | a +% b | Evaluates to the remainder of a +/ b. |
** | Power | a ** b | Evaluates to a raised to the power of b. |
- | Negation | -a | Evaluates to the negation of a, that is, a with a flipped sign. |
A: | Absolute Value | A:a | Evaluates to the absolute value of a. |
s: | Sign | s:a | Evaluates to 1 if a is positive, -1 if a is negative, or 0 if a is 0. |
x: | Convert From Hexadecimal | x:a | Evaluates to the integer value of the hexadecimal string a. (For example, x:ff evaluates to 255.) |
Comparison Operators | |||
Operator | Name | Usage | Meaning |
= | Equality | a = b | Evaluates to 1 if a and b are equal; 0 otherwise. |
!= | Inequality | a != b | Evaluates to 1 if a and b are not equal; 0 otherwise. |
! | Inequality | a ! b | Same as != |
< | Less Than | a < b | Evaluates to 1 if a is less than b; 0 otherwise. |
> | Greater Than | a > b | Evaluates to 1 if a is greater than b; 0 otherwise. |
<= | Less Or Equal | a <= b | Evaluates to 1 if a is less than or equal to b; 0 otherwise. |
>= | Greater Or Equal | a >= b | Evaluates to 1 if a is greater than or equal to b; 0 otherwise. |
Logical Operators | |||
Operator | Name | Usage | Meaning |
, | And | a , b | Evaluates to 1 if a and b both evaluate to non-zero values; 0 otherwise. |
| | Or | a | b | Evaluates to 1 if a or b or both evaluate to non-zero values; 0 otherwise. |
! | Not | !a | Evaluates to 1 if a evaluates to 0; 0 otherwise. |
Bitwise Operators | |||
Operator | Name | Usage | Meaning |
.& | Bitwise And | a .& b | Evaluates to the bitwise intersection of a and b. |
.| | Bitwise Or | a .| b | Evaluates to the bitwise union of a and b. |
.^ | Bitwise Xor | a .^ b | Evaluates to the bitwise xor of a and b. |
~ | Bitwise Not | ~a | Evaluates to the bitwise complement of a. |
<< | Left Shift | a << b | Evaluates to a shifted left by b bits. |
>> | Right Shift | a >> b | Evaluates to a shifted right by b bits. |
++ | Concatenation | a ++ b | Evaluates to a shifted left by as many bytes as b uses, then bitwise or'ed to b. This effectively amounts to string concatenation. |
.+ | Set Bit | a .+ b | Evaluates to a with bit b set, if it isn't set already. This is equivalent to: a .| (1 << b). |
.- | Clear Bit | a .- b | Evaluates to a with bit b cleared, if it isn't cleared already. This is equivalent to: a .& ~(1 << b). |
.~ | Toggle Bit | a .~ b | Evaluates to a with bit b toggled. This is equivalent to: a .^ (1 << b). |
.? | Check Bit | a .? b | Evaluates to 1 if bit b in a is set; 0 otherwise. |
.! | Negate Check Bit | a .! b | Evaluates to 1 if bit b in a is not set; 0 otherwise. |
.: | set bit N | .:n | Evaluates to a value with only bit n set. This is equivalent to: 1 << b. |
Inventory Operators | |||
Operator | Name | Usage | Meaning |
a: | Has | a:t | Evaluates to 1 if the player has the object specified by the object tag t in his inventory; 0 otherwise. |
d: | Doesn't Have | d:t | Evaluates to 0 if the player has the object specified by the object tag t in his inventory; 1 otherwise. (Same as !a:t.) |
e: | Enum Has | e:t | Evaluates to the number of objects specified by the enumerated object tag t that the player has in his inventory. |
b: | Item Count | b:t | Evaluates to the number of objects carried in the inventory bag t. Note that "0" is the name of the default inventory bag. |
B: | Bag Check | B:t | Evaluates to 1 if the current inventory bag is t; 0 otherwise. |
Miscellaneous Operators | |||
Operator | Name | Usage | Meaning |
c: | Encode Characters | c:s | Encode the string s as a number. s can be at most eight characters long and contain only letters, numbers, and underscores. This is equivalent to the {"} grouping operator, except that {"} can accept other characters as well. |
C: | Encode Literal Character | C:c | Encode the character c as a number. c can be any character, including characters that the Smash language would otherwise parse, such as parentheses, braces, and spaces. If c is any white space character, or if the C: operator appears at the end of a line, then it evaluates to an encoded space character. |
f: | Function Call | f:t f:m.t |
Calls the function t in the module m (or the current module, if m is not supplied) and evaluates to its return value, or 0 if no return value is supplied by the function. |
l: | Convert To Lower Case | l:s | Convert the upper case characters in the encoded string s to lower case. |
L: | Location Check | L:t | Evaluates to 1 if the player's current location is t; 0 otherwise. |
p: | Plural Check | p:a | Evaluates to 0 if a is 1; 1 otherwise. |
P: | Pluralization Text | P:a | Evaluates to 0 if a is 1; {"s} otherwise. |
r: | Pseudo-Random | r:a | Evaluates to a pseudo-random integer between 0 and a-1, inclusive. [See note.] |
S: | System Info | S:s | Evaluates to the system value signified by the string s.
The human-readable text strings are preferred over the numerical
strings but both are supported.
|
u: | Convert To Upper Case | u:s | Convert the lower case characters in the encoded string s to upper case. |
U: | Capitalize Words | U:s | Adjust the case of the alphabetic characters in the encoded string s so that the first letter in each word (group of adjacent letters) is capitalized and the remaining letters are lower case. Note that the 0 (null) character is considered a dividing character between words even though it does not decode to any character at all. |
Note: Smash's pseudo-random number generator is intentionally designed to be repeatable. The random number generation algorithm is a good one, sufficiently indistinguishable from a true random number sequence, but the random number seed that is used is a hash of the move number and all prior moves made within the game. In other words, if you were to start a new Smash game and make a certain sequence of moves in it, you would get the same random numbers as if you played that same Smash game with the same moves a second time. This ensures that a given move list for a Smash game always has the same results, no matter how many times it is replayed. Vary that move list even a little, however, and the random numbers returned by the r: operator thereafter will almost certainly be wildly different.
Also note that Smash's "pseudo-random" number generator is based on a 32-bit integer; so if the value of its argument is larger than a 32-bit integer, the random number returned will still not be higher than 32 bits.
Precedence
Operator precedence refers to the order in which operators get evaluated. It is an important thing to consider when using different operators in conjunction with one another. For example, consider the expression "5 + 3 * 2". You might think that the + operator gets evaluated first, so the value of the expression would be 5 plus 3, totalling 8, times 2, which is 16. But it's not. The multiplication gets evaluated first. 3 times 2 is 6, plus 5 is 11. See how important it is to understand precedence? Evaluate the operators in the wrong order, and sometimes you can get the wrong result.
But what if you wanted to calculate the value of the sum of 5 and 3, multiplied by 2? You can use parentheses to force operators to be evaluated in a different order. The previous example could be rewritten as "(5 + 3) * 2", which, this time, evaluates to 16.
The following chart lists all the operators supported by Smash, in order of precedence, from highest (evaluated first) to lowest (evaluated last). When operators have equal precedence, they are evaluated in order from left to right -- except for unary operators (operators that only take one argument), which are evaluated right to left. (For the most part, you don't have to worry too much about left to right vs. right to left, because Smash does this in an intuitive manner.)
C: | Encode Literal Character |
{}, {'}, {"}, {,}, {$}, {#} | Expression Grouping Within a String |
() | Grouping |
A: a: b: B: d: e: c: f: l: L: r: p: P: s: S: u: U: x: .: - ! ~ | Absolute Value, Has, Item Count, Bag Check, Doesn't Have, Enum Has, Encode Characters, Function Call, Lower Case, Location Check, Pseudo-Random, Plural Check, Pluralization Text, Sign, System Info, Upper Case, Capitalize Words, Convert From Hexadecimal, Set Bit N, Negation, Not, Bitwise Not |
** | Power |
* | Multiply |
/ -/ +/ | Divide, Floor Divide, Ceiling Divide |
% -% +% | Modulo |
+ - | Add, Subtract |
++ | String Concatenation |
<< >> | Left Shift, Right Shift |
.+ .- .~ .? .! | Set Bit, Clear Bit, Toggle Bit, Check Bit, Negate Check Bit |
.& | Bitwise And |
.^ | Bitwise Xor |
.| | Bitwise Or |
= ! != < > <= >= | Equality, Inequality, Inequality, Less Than, Greater Than, Less Or Equal, Greater Or Equal |
, | And |
| | Or |
Grouping
As discussed earlier, parentheses can be used to force operators to be evaluated in a specific order rather than the default.
There is a second grouping operator, {}, which is referenced in the above chart. Using {}, you can insert the value of expressions into strings. Let's say you had three variables named var1, var2, and var3. Furthermore, let's say you had a variable x that is known to be set to either 1, 2, or 3. If x is 1, you want to set var1 to 1. If x is 2, you want to set var2 to 1. And if x is 3, you want to set var3 to 1. This could be accomplished with a series of conditionals, for example:
c x=1 v var1 = 1 C x=2 v var2 = 1 C v var3 = 1
This is reasonable, but what if you had a hundred such variables? Use of the {} grouping operator, you can condense all this into one line, regardless of how many different variables and values of x you might have:
v var{x} = 1
To illustrate how the above code works, let's say the current value of x is 2. The {x} part would evaluate to 2, and then the variable "var2" would be assigned the value of 1.
This is also how you can print out the value of expressions:
p Another coin falls from your pocket. You've only got {e:coins} left. Where are you going to find the {loan_amount - e:coins} coins you need to pay your father back?
Note, by the way, that within expressions, regular parentheses are equivalent to {}s:
v point_value = grid_(y)_(x)
...is equivalent to:
v point_value = grid_{y}_{x}
However, when evaluating a string, as in the p command above, {}s are evaluated, while ()s show up as literal parentheses characters in the string you are displaying. You can have a literal { character in a string by preceding it with a backslash. This does not need to be done with a literal } character. For example:
p You love the squiggly characters \{ and }.
For clarity, it is recommended that you always use {} for inserting the value of an expression into a string, while using () only for adjusting operator precedence.
String Evaluation
If an apostrophe character is the first character within {}s, it forces the expression inside to be evaluated to its string equivalent. It is the reverse of {"} and the c: and C: operators, all of which encode characters into numerical values. In this example, we can set the action description text to "Hi":
v message = c:Hi p {'message}
Most conventional games written in Smash will not have a need for string encoding. However, if string encoding is used, it is important to understand the distinction between {"} and the c: and C: operators. c: can take up to eight characters as an argument, but these characters must conform to Smash's usual rules for tags, that is, consisting only of letters, numbers, and/or underscores. The {"} grouping is more flexible, as it can accept most other types of characters. The C: operator only takes one character as an argument, but this character can be any character, including braces, which c: and {"} will not interpret. Strings can be concatenated together with the ++ operator, as in the following example, which sets the variable 'message' to "Hi, bro!":
v message = c:Hi ++ C:, ++ C: ++ c:bro ++ C:! p A weird stranger approaches you and says, "{'message}"
The simplest way to do this, however, is:
v message = {"Hi, bro!} p A weird stranger approaches you and says, "{'message}"
But when the string you want to encode includes braces, use of concatenation and the C: operator is necessary. The following example sets the variable "message" to "{x,y}":
v message = C:{ ++ {"x,y} ++ C:} p The teacher says, "Write down the coordinates in the form {'message}."
String evaluation provides a number of intriguing possibilities, such as defining an array of objects or location tags:
p Your eyes glaze over, and when you wake up, you are elsewhere.... v loc1 = c:kitchen v loc2 = c:parlor v loc3 = c:bathroom v loc4 = c:backyard g {'loc{r:4 + 1}}
All of the above, of course, are nonsensical examples. Even the last can be done more simply -- and without the eight character limit -- with a series of conditionals. But the tools are there for situations where string encoding may actually be called for.
Other Grouping Operators
If a comma is the first character within {}s, the expression inside is evaluated and formatted with commas delimiting every three decimal places. For example, "1,234,567" is produced when you evaluate {,1234567}.
If a dollar sign is the first character with {}s, the expression inside is evaluated as the number of cents and formatted as a dollar value. For example, "$12,345.67" is produced when you evaluate {$1234567}.
If a hash sign is the first character with {}s, the expression inside is evaluated as the number of cents and formatted as a pound value. For example, "£12,345.67" is produced when you evaluate {#1234567}.
Delayed Evaluation
In S commands only, square brackets -- that is, [] -- can be used as {}s to insert the value of an expression into the string. The difference is that {}s are evaluated immediately, upon execution of the command, whereas expressions embedded in []s are re-evaluated every time the status line is displayed. For example:
S 1 The value of var was {var} when this status line was set, but it's [var] now.
Within status lines, you can display a literal [ character by preceding it with a backslash, just as with a literal {. No special handling is needed to display a literal ] character.
All the special characters that work inside {}s also work inside []s:
S 1 You are in the ['room]. There is [$money] here. There are [,dustcount] dust mites on the table.
Scoping
"Scoping" is a term used to describe a means of differentiating between multiple variables with the same name. For example, it is perfectly legal to have a state variable in one location that has the same name as some other state variable in some other location.
Let's say you have two locations, named loc1 and loc2, and each location has a state variable defined in it called state. Context is usually enough for Smash to tell the difference. If you're in loc1 and you reference state, by default you're talking about loc1's state. If you're in loc2, by default you're talking about loc2's. But what if you're in loc1 and you want to reference loc2's state, or what if you're in loc3?
If you want to explicitly name the location whose state variables you want to access, you can "scope" the variable. In Smash, this is done by putting the location name before the variable name and separating them with a period:
c loc1.s = loc2.s p The state variables named "s" in loc1 and loc2 are the same! They are set to the value {loc1.s}.
This is the same notation (and the same principle) behind calling functions. If a function name is not scoped, it is assumed to reside in the same file; otherwise, the location (file) is given:
~ func p This is the local function! * 1 Call a local function. f func * 2 Call a remote function. f somewhere.func
But back to variables. Because Smash does not allow the creation of global variables that share a name with a preexisting state variable or timer, nor does it allow the creation of a new timer that shares a name with a preexisting state variable or global variable, it is never necessary to scope global variables or timers. However, there is an optional scoping syntax you can use with globals and timers that may help make the code more readable.
Global variables can be prefixed with a period to indicate that they are, in fact, global variables, rather than state variables or timers. Similarly, timers can be prefixed with three periods (an ellipsis, suggesting the passage of time).
* 1 Print out the value of the global variable "glob". p glob is set to {.glob} * 2 Print out the value of the timer "t". p t is set to {...t} * 3 Do some funky calculations with "glob" and "t". v result = .glob * 3 - ...t p The funky result is {.result}
Now, the above code would function identically without the scoping of the global variables and timer, but it wouldn't be as obvious, when reading the code, what kind of variables "glob" and "t" were. Moreover, there is a slight speed increase when using scoped variables. It's not worth scoping everything just for the extra speed, but if there are sections of code that are executed many times (code in repeatpre.sma and repeatpost.sma, for example, executes every move), it might be a good idea to scope the global variables and timers you use in those sections.
Note that scoping state variables is actually slightly less efficient, so it only makes sense to scope those when you have to -- that is, when you want to access a state variable that's in a location other than the one you're in.