Commit fa5ef830 authored by twanvl's avatar twanvl

Finished documenting scripting system.

This means the documentation is DONE (yay!)
parent 7201fac0
Function: primary_choice
--Usage--
> primary_choice(some_choice_value)
Returns the name of the 'top level' choice selected.
For example if a [[type:field|choice field]] is declared with the [[type:choice]]s:
>choice:
> name: large
> choice: red
> choice: blue
>choice:
> name: small
> group choice: just small
> choice: green
> choice: yellow
Then @primary_choice("large red") == "large"@.
This function is used to keep [[type:statiscs_dimension|statistics]] cleaner by not having columns for all possible sub choices.
--Parameters--
! Parameter Type Description
| @input@ [[type:string]] Choice value to look at.
--Examples--
> primary_choice("large red") == "large"
> primary_choice("large") == "large"
> primary_choice("small green") == "small"
......@@ -19,6 +19,7 @@ Parts
| @"<wxy>"@ @"xy"@ Selects the characters from the input, but only once.
@"<xy>"@ is the same as @"<x><y>"@.
| @"[wxy]"@ @"yyyxx"@ Selects the given characters, but keep them in the same order as in the input.
| @"(xwz)"@ @"zzxx"@ The same as @"cycle(xyz)"@.
| @"ordered(wxy)"@ @"xxyyy"@ The same as just @"wxy"@.
| @"once(wxy)"@ @"xy"@ The same as @"<wxy>"@.
| @"mixed(wxy)"@ @"yyyxx"@ The same as @"[wxy]"@.
......
Best practices
Here are some best practices and tricks for using scripts, try to follow this as much as possible.
--Use comments--
Put comments before function definitions to describe what the function does:
> # Transmogrifies a flubble value for in grunk costs
> flubble_script := { ... }
--Use short scripts--
Try to put as little as possible in [[type:field]] scripts.
Instead define a function in the init script.
'''bad''':
>field:
> name: flubble
> script:
> some very complicted expression
'''good''':
>init script:
> ...
> flubble_script := { some very complicted expression }
>field:
> script: flubble_script()
--Use rule form--
If a function is available in [[script:rule form]] use it where possible.
Many rules can be chained together using the @+@ operator.
Have a look at @text_filter@ in the magic game file for an example.
--Don't be afraid to nest--
Don't be afraid to nest complex things like @if@ expressions inside other expressions.
For example the blend scripts for magic use:
> colors := sort_text( order: "(wubrg)"
> , (if chosen(choice:"white") then "w")
> + (if chosen(choice:"blue") then "u")
> + (if chosen(choice:"black") then "b")
> + (if chosen(choice:"red") then "r")
> + (if chosen(choice:"green") then "g"))
and:
> font_color_positions
> [ if chosen(choice:"horizontal") then "horizontal"
> else if chosen(choice:"vertical") then "vertical"
> else if chosen(choice:"overlay") then "overlay"
> else "radial"
> ][number_of_items(in: colors)]
--Re-use functions--
If two scripts or functions are very similair put the common part into another function.
The differences can be passed in parameters.
A good example is in the @something 2@ fields used for Magic split and flip cards.
They behave exactly the same as the non-2 variants.
Don't use:
>init script:
> automatic_card_color_based_on_casting_cost := { ... card.casting_cost ... }
> automatic_card_color_based_on_casting_cost_2 := { ... card.casting_cost_2 ... }
>field:
> name: card color
> default: automatic_card_color_based_on_casting_cost()
>field:
> name: card color
> default: automatic_card_color_based_on_casting_cost_2()
Instead use:
>init script:
> automatic_card_color := { ... input ... }
>field:
> name: card color
> default: automatic_card_color(card.casting_cost)
>field:
> name: card color
> default: automatic_card_color(card.casting_cost_2)
--Trick: hooks--
Use this trick when defining a large function that might be overridden by stylesheets.
For example things like the blend scripts for Magic cards are very large, and they are shared by all stylesheets.
Some stylesheets need to do something special, or they don't support a particular template.
This can be handled using hooks.
What this means is that you don't do:
> blend := { large and complicated function bla bla, use "template.png" bla bla }
But instead
> template := "template.png"
> blend := { large and complicated function bla bla, use template bla bla }
Now a stylesheet can override this template by just changing that variable.
Another option is splitting up the template
> blend := { large and complicated function bla bla, continue_blend() }
> continue_blend := { use template bla bla, etc. }
A stylesheet can override just a part of this, or even put something in between:
> really_continue_blend := continue_blend
> continue_blend := { do_this_first; really_continue_blend() }
--Trick: lookup tables--
Sometimes the behaviour of a function depends on a particular value.
A good example of this are (again) the magic blend scripts.
What way cards are blended depends on the number of colors and on the 'style' of blending.
Instead of using a lot of @if@ statements it can be cleaner to use a [[type:map]] as a lookup table.
So, instead of:
> if style == "horizontal" then lots of code
> else if style == "vertical" then lots of code
> # etc.
use:
> blends := [
> horizontal: { lots of code }
> vertical: { lots of code }
> ]
> ...
> blends(style)() # lookup and call the function
Control structures
MSE Script has two types of control structures.
--If-Then-Else--
To switch between two options use:
> if condition then a else b
If the condition evaluates to @true@,
then the expression evaluates to @a@, otherwise it evaluates to @b@.
The else part is optional.
For example:
> if 1 + 1 == 2 then "yes sir!" else "something is wrong"
Will evaluate to @"yes sir!"@.
Note that if-then-else is an ''expression'', it can be used almost everywhere:
> 1 + (if card.color == "red" then 1 else 2)
> color := (if card.color == "red" then "r") +
> (if card.color == "green" then "g")
The @then@ and @else@ parts can also contain assignments and other control structures.
> if card.color == "red" then
> filter := filter + "r"
To use multiple statements in the then or else branches you must use parentheses:
> # WRONG
> if 1 + 1 == 2 then
> x := y
> y := z
> # RIGHT
> if 1 + 1 == 2 then (
> x := y
> y := z
> )
--For-Each--
To iterate over all elements in a [[type:list]] the @for each@ construct
> for each variable in list do expression
If list is a list of items, for example set.cards, the expression is evaluated for each item in that list.
The variable becomes set to that each item in succession.
The results of the expression are combined using the @+@ [[script:operators|operator]].
It is also possible to iterate over a range of values
> for variable from begin to end do expression
The expression is evaluated for each number from begin to end (including begin, not including end). The variable becomes set to that each number in succession. The results of the expression are combined using +.
--Summary--
! Syntax Description
| @if a then b else c@ If @a@ is @true@ evaluates to @b@, otherwise evaluates to @c@
| @for each x in list do something@ Does something for each element in a list
| @for x from 1 to 100 do something@ Does something for all numbers from 1 to 100
Scripting language
MSE uses a custom scripting language to add complicated behaviour to fields and styles.
MSE uses a custom scripting language to add complicated behaviour to [[type:field]]s and [[type:style]]s.
--Topics--
* [[script:introduction|Introduction to scripting]]
* [[script:Operators]]
* [[script:variables|Variables and functions]]
* [[script:Control structures]]
* [[script:Predefined variables]]
* [[script:Best practices]]
See also:
* [[type:index|Data types used]]
* [[fun:index|Built in functions]]
Scripting introduction
MSE uses a custom scripting language to add complicated behaviour to [[type:field]]s and [[type:style]]s.
This documentation assumes that the reader has some programming experience.
In particular it is assumed that you know what 'functions', 'variables' and 'operators' are.
Scripts in MSE are usually small, just a few lines.
A piece of script code is also called an ''expression''.
Expressions can be ''evaluated'' to give a result.
For example the expression @1 + 1@ will evaluate to @2@.
An important idea behind MSE is that it tries to be ''pure'',
there are no global variables and there is no way to change properties of objects.
This means that when you evaluate an expression the result is always the same (unless it contains variable assignments).
Basic elements of scripts are ''constants''.
A constant is something with a fixed value.
Possible constants are:
! Syntax Type
| @nil@ Nothing
| @true@, @false@ [[type:boolean]]
| @123@ [[type:int]]
| @123.5@ [[type:double]]
| @"abc"@ [[type:string]]
| @rgb(255,0,0)@ [[type:color]]
| @[a,b,c]@ [[type:list]]
Note: Strings, list and colors don't have to be constants, they can contain other code.
The @nil@ value has a special meaning, it is 'nothing'.
Adding @nil@ to something has no effect (it returns 'something'), calling @nil@ as a function just returns the input, etc.
<div style="text-align:right;">next: <a href="operators">Operators &rarr;</a></div>
Operators
To get more complicated expressions you combine them using operators.
--Basic mathematics--
MSE script supports most basic mathamatical operators:
! Operator Example Description
| @a + b@ @3 + 2 == 5@<br/> Add two numbers,
@"3" + "2" == "32"@ concatenate two strings or compose two functions (see below)
| @a - b@ @3 - 2 == 1@ Substract two numbers
| @a * b@ @3 * 2 == 6@ Multiply two numbers
| @a / b@ @3 / 2 == 1@<br/> Divide two numbers.<br/> Rounds towards zero for [[type:int]]s.<br/>
@3 / 2.0 == 1.5@ Does not round for [[type:double]]s.
| @a mod b@ @3 mod 2 == 1@ Take the remainder after integer division (modulo)
| @-a@ @-(3 + 2) == -5@ Negate a number (make it negative if positive and vice versa)
===The + operator===
The @+@ operator has three functions
* It adds [[type:int]]s (also [[type:double]]s), @1+1 == 2@
* It concatenates strings, @"1" + "1" == "11"@
* It composes [[type:function]]s @(f + g) () == g( input: f() )@
--Comparison--
It is also possible to compare values. All comparisons evaluate to either @true@ or @false@.
! Operator Example Description
| @a == b@ @1 + 1 == 2@<br/>
@"x" == "x"@<br/>
@1 + 1 == "2"@ Are two numbers or strings the same? @=@ can also be used instead.
| @a != b@ @1 + 1 != 3@<br/>
@"x" != "y"@ Are two numbers or strings different?
| @a < b@ @1 < 2@<br/>
@"x" < "y"@ Is a less than b? Uses [[http://en.wikipedia.org/wiki/Lexicographical_order|lexicographic order]] for strings.
| @a > b@ @2 > 1@<br/>
@"y" > "x"@ Is a greater than b?
| @a <= b@ @1 <= 1@<br/>
@"x" <= "y"@ Is a less than b or are they equal?
| @a >= b@ @2 >= 1@<br/>
@"x" >= "x"@ Is a greater than b or are they equal?
--Booleans--
[[type:Boolean]]s (for example from comparisons) can be combined using:
| @a and b@ Are both @a@ and @b@ true?
| @a or b@ Is at least one of @a@ and @b@ true?
| @a xor b@ Is exactly one of @a@ and @b@ true?
| @not a@ Is @a@ false?
In a table:
! @a@ @b@ <tt>a or b</tt> <tt>a and b</tt> <tt>a xor b</tt>
| @false@ @false@ @false@ @false@ @false@
| @false@ @true@ @true@ @false@ @true@
| @true@ @false@ @true@ @false@ @true@
| @true@ @true@ @true@ @true@ @false@
--Grouping and order--
Operators are ordered as usual, so
> 1 + 2 * 3 == 1 + (2 * 3) == 7
Operators can be grouped differently using parentheses.
> (1 + 2) * 3 == 3 * 3 == 9
The exact order of precedence is given in the following table,
higher in the table means that this operator binds tighter to its arguments, @*@ binds tighter then @+@.
| @a(...)@, @a.b@, @a[b]@ Function calls, property access, see below
| @-a@, @not@ Unary operators
| @*@, @/@, @mod@ Multiplication and division
| @+@, @-@ Addition and substraction
| @==@, @!=@, @<@, @>@, @<=@, @>=@ Comparisons
| @and@, @or@, @xor@ Boolean operators
| @:=@ Assignement, see below
| @;@ Sequence, see below
--Properties--
Properties of types, as described in the [[type:index|data type section]] of the documentation, can be accessed using the @.@ operator:
> set.cards # retrieve the 'cards' property of a set
The @[]@ operator has a similair purpose, only the property retrieved is determined by a string, so it can be changed:
> set["cards"] # same as above
> c := "cards"
> set[c] # again, the same
Multiple uses of these operators can be combined, for example:
> set.cards[0].card_color # the card color of the first card in the set
Note that a property named @card color@ is refered to as @card_color@ when using the @.@ operator, all spaces become underscores.
> style.padding left # syntax error
> style.padding_left # use this instead
--Assignment and sequence--
Values can be assigned to [[script:variables]] using the @:=@ operator:
> variable := 1 + 1
The result of this expression is the value assigned, this can be used to assign to multiple variables:
> var1 := var2 := 1 + 1
> # now var1 == 2 and var2 == 2
To combine multiple assignments into a single expression the ''sequencing operator'', @;@ can be used.
This first executes an expression, discards the result and then evaluates another one:
> var1 := 1 + 1 # assign
> ; # discard the result (i.e. 2)
> var1 * 2 # retrieve the value again, returns 4
Semicolons at the end of a line can be omitted, so the above can also be written simply as:
> var1 := 1 + 1
> var1 * 2
<div style="text-align:right;">next: <a href="variables">Variables and functions &rarr;</a></div>
Predefined variables
When evaluating scripts several variables are predefined containing useful information.
Aside from the [[fun:index|built in functions]] the following variables are provided:
! Variable Type When available? Description
| @game@ [[type:game]] always The current game, the same as @set.game@
| @set@ [[type:set]] always The current set.
| @stylesheet@ [[type:stylesheet]] not in the @init script@ of the game
The current stylesheet
| @card@ [[type:card]] not in @init script@s or when exporting
The current card.
| @card_style@ [[type:indexmap]] of [[type:style]]s where @card@ is available Style properties for the current card, the same as @stylesheet.card_style@.
| @styling@ [[type:indexmap]] of [[type:value]]s where @card@ is available Styling options for the stylesheet/card.
| @value@ [[type:value]] when evaluating a [[type:field]]'s @script@ or @default@ script Current value in the field.
| @options@ [[type:indexmap]] of [[type:value]]s when exporting Options of the [[type:export template]].
Variables are only available where they make sense.
Rule form
Some functions are available in ''rule form''.
These rule form functions are functions that create a new [[type:function]].
That new function, the rule, applies some transformation to the input and returns the result.
A rule is like a normal function with all parameters given, except for the @input@.
Rules are often combined using the + operator, for example:
> # First all "a"s are replaced, then all "b"s.
> remove_as_and_bs := replace_rule(match: "a", replace: "") +
> replace_rule(match: "b", replace: "")
>
> text_with_as_and_bs := "bla bla bla"
> text_without_as_and_bs := remove_as_and_bs(text_with_as_and_bs)
Variables and functions
--Variables--
MSE script has the notion of ''variables''.
A variable is a name holding a value, assigned using the @:=@ operator:
> variable := 1 + 1
From now on (until another value is assigned) @variable@ evaluates to @2@ in the rest of the script.
--Functions--
It is possible to define your own [[type:function]]s in MSE script.
The syntax for this is very simple, code in curly braces defines a function:
> { code goes here }
To be able to refer to the function it is usually assigned to a variable:
> function := { code goes here }
Calling a function is done using parentheses:
> { code }()
> function()
this has the effect of evaluating the code inside the curly braces.
--Scope--
Assignments to variables are ''local'' to the current function.
Consider:
> function := {
> x := "something else"
> # here x is something else
> }
> x := 1
> function()
> # here x is still 1
Unlike most programming languages MSE script uses [[http://en.wikipedia.org/wiki/Dynamic_scoping#Dynamic_scoping|dynamic scoping]].
This means that assignments done in the calling function are visible in the called function:
> fun := { "xyz is {xyz}" }
> one := {
> xyz := 1
> fun()
> }
> two := {
> xyz := "two"
> fun()
> }
> one() == "xyz is 1"
> two() == "xyz is two"
--Parameters--
The scoping can be used to pass parameters to functions as shown above.
To make this easier, parameters can be specified inside the parentheses of the function call:
> fun(xyz: "a parameter") == "xyz is a parameter"
These assignments are ''only'' visible to the called function.
> xyz := "outside"
> fun(xyz: "a parameter")
> # xyz is still "outside"
The syntax for parameters is @name: value@.
Multiple parameters are separated by commas.
The special syntax @value@ (without a name) means the variable @input@ is used:
> fun := { input + var2 }
> fun(var2: "yes", "no") == "noyes"
--Overriding functions--
Like custom functions, the [[fun:index|built in functions]] are also stored in variables.
It is possible to overwrite them:
> to_upper := { input }
> to_upper("xyz") == "xyz" # Not what it used to do
A neat trick is adding 'extra' behaviour to functions.
This can be done by first making a copy, and calling that:
> real_to_upper := to_upper
> to_upper := { "upper case: " + real_to_upper() }
> to_upper("xyz") == "upper case: XYZ"
Note that @real_to_upper@ is called without extra parameters, the @input@ variable is still set from the outer call to the new @to_upper@ itself.
<div style="text-align:right;">next: <a href="control_structures">Control structures &rarr;</a></div>
......@@ -56,16 +56,20 @@ The @type@ determines what values of this field contain:
Additional properties are available, depending on the type of field:
! Type Property Type Default Description
| @"text"@ @script@ [[type:script]] Script to apply to values of this field after each change.
| @"text"@ @script@ [[type:script]] Script to apply to values of this field after each change.<br/>
If the script evaluates to a constant (i.e. doesn't use @value@) then values in this field can effectively not be edited.
| ^^^ @default@ [[type:script]] Script to determine the value when it is in the default state (not edited).
| ^^^ @default name@ [[type:string]] @"Default"@ Name of the default state, currently not used.
| ^^^ @multi line@ [[type:boolean]] @false@ Can values of this field contain line breaks?
| @"choice"@ @script@ [[type:script]] Script to apply to values of this field after each change.
| @"choice"@ @script@ [[type:script]] Script to apply to values of this field after each change.<br/>
If the script evaluates to a constant (i.e. doesn't use @value@) then values in this field can effectively not be edited.
| ^^^ @default@ [[type:script]] Script to determine the value when it is in the default state (not edited).
| ^^^ @initial@ [[type:string]] Initial value for new values for this field.
| ^^^ @default name@ [[type:string]] @"Default"@ Name of the default state.
| ^^^ @choices@ [[type:list]] of [[type:choice]]s Possible values for this field.
| ^^^ @choice colors@ [[type:map]] of [[type:color]]s Colors of the choices for statistics graphs.
| ^^^ @choice colors cardlist@ [[type:map]] of [[type:color]]s Colors of the choices for lines in the card list,<br/> see also the @card list color script@ property of [[type:game]]s.
| @"multiple choice"@ <<< <<< <<<
'' Multiple choice fields have the same attributes as normal choice fields.''<br/>
......@@ -74,7 +78,8 @@ Additional properties are available, depending on the type of field:
| @"boolean"@ ''A boolean field is a choice field with the choices @"yes"@ and @"no"@.'' <<< <<< <<<
| @"color"@ @script@ [[type:script]] Script to apply to values of this field after each change.
| @"color"@ @script@ [[type:script]] Script to apply to values of this field after each change.<br/>
If the script evaluates to a constant (i.e. doesn't use @value@) then values in this field can effectively not be edited.
| ^^^ @default@ [[type:script]] Script to determine the value when it is in the default state (not edited).
| ^^^ @initial@ [[type:string]] Initial color for new values for this field.
| ^^^ @default name@ [[type:string]] @"Default"@ Name of the default state.
......
......@@ -22,8 +22,8 @@ The first element of a list is numbered 0, the next 1, etc.
> list[0] # The same thing
> list[0+0] # The same thing
It is possible to iterate over lists using the @for@ construct:
> for x in [1,2,3] do "x = {x}. "
It is possible to iterate over lists using the @for each@ construct:
> for each x in [1,2,3] do "x = {x}. "
evaluates to:
> "x = 1. x = 2. x = 3. "
......
......@@ -21,6 +21,6 @@ Like lists, maps can be accessed using either the bracket operator, or the dot o
> map["k"+"ey"] # The same thing
It is possible to iterate over the values maps using the @for@ construct:
> for x in [one: 1, two: 2] do "x = {x}. "
> for each x in [one: 1, two: 2] do "x = {x}. "
evaluates to:
> "x = 1. x = 2. "
......@@ -26,3 +26,9 @@ Sections between curly braces are interpreted as script code, that is concatenta
This can be nested arbitrarily.
The @+@ operator concatenates strings. Numbers and most other values are automatically converted to strings when needed.
Using the @[]@ or @.@ operator characters in a string can be selected. 0 is the first character:
> "xyz"[0] == "x"
> "xyz".0 == "x" # same thing
It is an error to select characters outside the string
> "xyz".10 # error
\ No newline at end of file
......@@ -108,12 +108,12 @@ The rest of the properties depend on the type of [[type:field]] this style is fo
! <<< <<< <<< <<<
| @"image"@ @mask@ [[type:scriptable]] [[type:filename]] ''none'' A mask to apply to the image, black areas in the mask become transparent, similair to [[fun:set_mask]].
@default@ [[type:image|scriptable image]] ''none'' A default image to use when the card has none.
| ^^^ @default@ [[type:image|scriptable image]] ''none'' A default image to use when the card has none.
! <<< <<< <<< <<<
| @"symbol"@ @variations@ [[type:list]] of [[type:symbol variation]]s Available variations of the symbol, a variation describes color and border size.
| @min aspec ratio@ [[type:double]] @1@ Bounds for the aspect ratio, @width/height@ symbols can take. This can be used to make non-squary symbols.
| @max aspec ratio@ [[type:double]] @1@ ^^^
| ^^^ @min aspec ratio@ [[type:double]] @1@ Bounds for the aspect ratio, @width/height@ symbols can take. This can be used to make non-squary symbols.
| ^^^ @max aspec ratio@ [[type:double]] @1@ ^^^
! <<< <<< <<< <<<
| @"info"@ @font@ [[type:font]] ''Required'' Font to render the text.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment