-
Notifications
You must be signed in to change notification settings - Fork 0
FormulaDataTypes
- summary A summary of the data types that a variable can be in FFL.
This page attempts to cover the basics of all the data types you will encounter in the FrogattoFormulaLanguage. See StupidCodingTricks for some fun examples. (However, it is usually best to look at real code in the .cfg files.) See ObjectFunctions for use cases.
In FFL, a variable is a textual string which evaluates to _something_. (See FormulaDataTypes for what can be stored in a variable.) They are usually created in an object, and stored in the objects .vars or .tmp category. Variables in .vars are saved, and variables in .tmp are not. Values in .consts may not vary, and have to be defined in an object's .cfg file.
In addition to being defined as a vars.name, variables can be defined by functions such as map(list, 'name', name.data). Here, name.data will be run once for each element in list. If name was already used in this context, you'll need to use a different name. _This will fail silently._ Another way to define a variable is to use where. If you have, for example, a function, you can specify a variable to use by saying: function(name) where name = 10. Now, function will evaluate with 10 as the augment. This is especially handy when you want to do two things to a function's return value. Consider the case where we want to create an object, but we also want to save it to a variable. While we could mess around with spawn(...) and have the object created set it's parent's variable, it is much easier to create and save the object by using [set(vars.obj, obj), add_object(obj)] where obj = object(...). If we were to simply [set(vars.obj, object(...)), add_object(object(...))], we would have a different object in vars.obj than we had actually spawned.
The map data type is created with the syntax {'thing_one' -> 1, 'thing_two' -> ~two~, ...} and so on. Now, if we set the variable var to that map, var.thing_one evaluates to 1, and var.thing_two would evaluate to the translatable string 'two'. This is one of the rarer structures, as most of it's functionality has been absorbed by FormulaList. You can access string types via var['think_one'].
A list in FFL is created using the syntax [thing1, thing2, thing3, ...] and so on. Let's set the variable var to [a, b, c, d]. If we want to access a, we would check var[0]. Similarly, if we wanted to check b, we would look in var[1]. A slice of the list may be obtained using the syntax list[e1:e2]. For example, the statement debug(var[1:3]) would print [b, c]. debug(var[2:4]) would print [c, d]. If you want to add to a list, you must append another list. For example, var + [e, f] would be [a,b,c,d,e,f]. It is important to remember that you can only assign a variable once per FML event. (See FrogattoMarkupLanguage for more details.)
To iterate over a list, you can use map(list, 'thing', [*code goes here*]). The code in the bit where code goes is executed once for every item in the list, and the item is accessed via the variable thing. eg, set(thing.x, thing.x+1) would set each of the list's elemets's x value to be one higher. It is useful to note that you can iterate through a list by index by saying map(range(size(var)), 'index', var[index]).
To make a list shorter, you can use filter(list, 'thing', *condition*). This works much the same as map(...), but instead of returning a list of commands it returns the list you passed in less the elements where the condition did not evaluate to true. For example, filter(var, 'letter', 1d2 = 1) would randomly select half the list.
An integer is a type of variable which contains one whole number. This number may be positive or negative.
A decimal number is like an integer, but it has five decimal places. Fixed-point numbers are compatible with fixed-point numbers in operations. Note: Decimal numbering only came to Frogatto with version 1.4, there is the occasional place where an integer is still implicitly converted to a decimal.
A string~ is translatable. A 'string' is not. You can put a variable into a string by saying 'bla bla {variable} bla.'. For example, if variable was equal to 5, we'd have 'bla bla 5 bla' as the string. (Note that a variable can contain code, in addition to a simple value. FFL makes no distinction.)
The most common object is an actor, such as Frogatto, an ant, a shooting flower, and so on. These objects all have common variables, listed in ObjectProperties. For more on working with objects, see the GameObjectProgrammingGuide. Objects such as these may be created with ObjectFunctions such as object(...) or spawn(...). Objects are passed by pointer, so setting a variable to an object makes the variable that object too. In addition to the common objects, there is also a level object. This is accessed via the variable level. level.camera returns a list saying where the screen is, and level.chars returns all the objects actually in the level. (The name is a holdover from the very early days of the engine, when we used a _chars_ system instead of the current _objects_ system.)
A command is anything like set(x, 10) or debug('test'). It tells the Frogatto engine to do something. A command is run when it's _exposed_, lost to the ether -- that is to say, it's not being handled by something, or stored, it's just being left unassigned. Almost every command you ever see will be this type. Commands can, of course, be stored in lists. Since ObjectEvents can only have one thing inside them, a list has to be used to store multiple events to be run. This is important, as it means that the entire list is first evaluated to commands and _then_ the commands are run. This is why, if `x` is currently `5`, [set(x, x+10), debug(x)] will actually print `5`! When the list was _exposed_, it didn't consist of instructions like `set x to itself + 10, then print the value of x`. Each element had already been evaluated, so the list was now [Set x to 15. Print 5.] Now that we have our commands evaluated, we actually run them.
Let's look at this common source of baldness.
on_find_targets="
map(chars, 'char',
set(list, list + [[char.x, char.y]]))
where chars = filter(level.chars, 'char', char.type = 'target')"Let's say that `list` is set to `10,5` before this event is fired, and our level has targets at `1,2`, `3,20`, and `0,6`. If we were to `debug(list)` in another event, we would find that `list` contained
[[10,5],[0,6]]
What's going on? You screwed up your `filter()`? Your `map()` isn't iterating? You _clearly_ set the list to be added to correctly.
Well, not quite.
As we know, a [FormulaMap map(...)] command returns a list. Since `set()` returns a command, and the command is evaluated before it is actually run, what we've executed is a list of commands that looks like: [Set list to 10,5],[1,2. Set list to 10,5],[3,20. Set list to 10,5],[0,6. Each time we iterated over our `set(list, ...)` function, we got a fully independent command. Since all the commands were determined before any were ran, we set `list` three times but we could only see what we set it to the final time. The correct way to set our list would be:
on_find_targets="
set(list,
map(chars, 'char', [char.x, char.y])
) where chars = filter(level.chars, 'char', char.type = 'target')"A property is like a [Functions function], but it can access all the variables an event can. Instead of being declared with a functions="...", a property is described like this:
[properties] fun = "def(var_in), *your code here using var_in*" prop = "*your code here*" [/properties]
In the first case, `fun`, we have some code which returns some [FormulaDataTypes value] and takes a value. You would call it by saying `fun(x)`. The second case, `prop`, just returns a value when it's name comes up. It is used, simply, `prop`.
Properties can be accessed by using the dot operator on an object. If we have the variable `obj` set to be some object with a property `prop`, we can simply access that object's property by saying `obj.prop`. It is important to remember that properties are executed in the context of the object calling them, so if we want to set a variable in the object where we defined the property and we are calling the property from another object, we need to use the syntax `set(me.variable, ...)` instead of just `set(variable, ...)`.
Properties can also recurse.
A property with an ALL_CAPS name is treated as a static constant by the engine.
A boolean statement evaluates to either true or false. Operators that produce a boolean value include =, !=, <, and so on. An integer value of 0 is false, while everything else is true. null() is numerically 0, and as such evaluates to false as well. Objects are always true. An `if()` function takes a boolean value for it's first augment, and a `filter()` function takes one as the last augment.
Boolean functions may be combined with `and`, and can also be prefaced with `not` for negation. You can't say '`and not`', however, so enclose the boolean to be negated in brackets.
A lambda function which subtracts one could be written as def(x) x-1 . This may be used with set() as the second arg, or just used in place if needed. Otherwise, functions /may/ be defined in a functions= string, or as a property. The difference is that the property can access any variable of the current object, not merely the ones passed in as args. A function may only operate on the passed-in variables. You can recurs by simply calling the function by name (you can't do this for a lambda function, because it by nature has no name) or you can use the recursion syntax. This example searches for an element in a list.
def my_index(ls, item, n)
base ls = []: -1 #case where we've got to the end of the list and nothing found#
base ls[0] = item: n #case where we've found a match#
recursive: my_index(ls[1:], item, n+1) #recursive case: no match -- search next item#