> LeekScript Tutorial
In a program, the most important thing is to structure your code well. For that you have learned how to use comments and that's about it... But we're going to change that quickly with functions, one of the most important concepts in programming, and you'll understand quickly. You have already come across some, and even used them, in the Native Functions chapter.
A function is a sequence of instructions, nothing more. So no more code duplication: you just call the function, which will run those instructions. To make functions more versatile, you can give them parameters such as the leek you want to hit, the number of MP remaining for this turn, etc. In addition, a function can return a value such as an array with all the cells reachable by our leek. Here is the prototype, i.e. the signature, of a function:
FunctionName ( parameter1, parameter2, ..., parameterN ) : Return
The prototype is the description of the function: its name, its return value if it has one, and its parameters, which are also optional. The definition of a function is the set of instructions it contains. All the prototypes of the native functions are available in the documentation. On the left, in the list of functions sorted by theme, if we select "UseWeapon" in the Weapons category, we find this: !UseWeapon-Mod
When a function is called, the 'main' program pauses to run the program stored in the function. Then at the end of that function, the 'main' program resumes where it left off.
We also speak of declaration for functions. Indeed, LS offers you ready-to-use native functions such as "getNearestEnemy / useWeapon / ...". But it is possible to create your own. Here is the syntax to use:
function FunctionName ( parameter1, parameter2, ..., parameterN ) { /* The instructions */ }
You can't do everything inside a function: declaring a new function as well as declaring a global variable are forbidden!
To make it more concrete, we are going to create a very simple function; its goal will be to move closer to the centre of the map, then cast a shield chip and a healing chip. Here is its prototype: (Note that the prototype is not meant to be put in the code, except maybe as a comment to explain your function)
ArmorAndCare()
Nothing simpler indeed: our function takes no parameter and returns nothing!
It is still missing a definition, here it is:
function ArmorAndCare(){ moveTowardCell(306); useChip(CHIP_ARMOR, getEntity()); useChip(CHIP_CURE, getEntity()); }
To call the function, it's like the others:
ArmorAndCare(); // Moves closer to the centre of the map then casts ARMOR and CURE
Important note: a function cannot access outside variables except globals. You have to imagine that when the function is called, it "lives" in its own world and the only things it has access to are the global variables and the other functions.
To illustrate the system of parameters and function return values, we'll take this example:
// Prototype: getCellDistanceTo(leek) : (Number) Distance between my leek and the one given as parameter function getCellDistanceTo(leek){ return getCellDistance(getCell(), getCell(leek)); }
According to the prototype, the function takes a leek (its ID) as a parameter and returns the distance between that leek and its own leek. The list of parameters is (almost) infinite; you just separate each parameter with a comma. A function returns with the keyword 'return'. In this example the function 'getCellDistance' returns a number, which we return directly. When the function reaches a 'return', it stops and resumes where the function was called. For those interested, the principle of reducing the number of parameters of a function is called 'currying'. If you want to stop the function early without returning a value, you just put the 'return' keyword without a value.
In the examples above, the data sent when a function is called are copied. Here is what happens:
function getCellDistanceTo(leek){ return getCellDistance(getCell(), getCell(leek)); }
var enemy = getNearestEnemy();
getCellDistanceTo(enemy );
// 1 - Call to getNearestEnemy, which returns a leek ID (example: 2) // 2 - enemy is now '2' // 3 - Call to getCellDistanceTo, which takes a parameter (in the example: enemy, i.e. 2) // 4 - Inside the function getCellDistanceTo: 'leek = 2;' runs, then the function definition // 5 - Call to getCellDistance: copy of 'getCell()' (its return) and of 'getCell(leek)' (its return too) // 6 - Returns the distance between the two leeks
What does this copy mean? If we modify 'leek' inside 'getCellDistanceTo', our variable 'enemy' will not be modified.
Let's take an example that will better illustrate passing by reference:
// Prototype // canDivide (result, (Number) a, (Number) b) : (Bool) True if we can divide 'a' by 'b', and stores the result in 'result'
// Definition function canDivide (result, a, b) { result = 0; if (b === 0) return false; // Division by zero is not possible // No need for 'else' because if b === 0 the function will have stopped result = a / b; return true; }
For now we speak of "passing by value" because, when this function is called, all the parameters are copied. To do this passing by reference, you have to use an '@' before the parameter (in the definition):
function canDivide2 (@result, a, b) { result = 0; if (b === 0) return false; // Division by zero is not possible // No need for 'else' because if b === 0 the function will have stopped result = a / b; return true; }
Thanks to this, if we modify result, the variable used to call the function will also be modified. Let's test the two functions:
var result;
if (canDivide(result, 5, 2)) // Function with passing by value debug("Division is possible between 5 and 2, the result is: " + result); else debug("Dividing 5 by 2 is not possible!");
if (canDivide2(result, 5, 2)) // Function with passing by reference debug("Division is possible between 5 and 2, the result is: " + result); else debug("Dividing 5 by 2 is not possible!"); /* If we test, we get: "Division is possible between 5 and 2, the result is: null" then "Division is possible between 5 and 2, the result is: 2.5" */
If you want more explanation about the behaviour of @, which isn't only useful in parameters, I recommend this page: Behavior of @
In Leekscript, functions are first-class objects. We can therefore consider the function declaration of the form function name([parameters]) {[code]} as a special syntax. This is noticeable in particular in terms of scope. The language considers that functions declared this way are separate, and only the objects in the global scope (including themselves) are accessible inside their own scope. Manipulating functions like any other first-class object lets us consider them in a much more interesting way. Remember what is possible to do with numbers/strings/arrays:
We learn by doing. For now, we'll just reproduce the arrayX functions that take a "callback" (it's just a function, let's avoid the term callback from now on). The function given as a parameter will only expect one or two arguments (we won't deal with associative arrays), and we'll place the parameters in a different order in anticipation of the section on functions as return values. It is recommended to start with iter.
filter : (elem -> Bool, [elem]) -> [elem]: Here we want to rebuild an array that only contains the objects for which the function returns true when we apply it to an element (f(xs[i])); for example, if we pass isSummon and getAliveAllies() as arguments (filter(isSummon, getAliveAllies())), we'll get a list of the living allied bulbs. You can now move on to partition.foldl : ((acc, elem) -> acc, acc, [elem]) -> acc: Here we want to consume an array by going through it from start to end. We start by applying the function to the accumulator and the first element (f(base, xs[0])), then we apply the function to the result and the second element, and so on until we've gone through the whole array. At the end we return the final result.foldr : ((elem, acc) -> acc, acc, [elem]) -> acc: Here we want to consume an array by going through it from end to start. We start by applying the function to the last element and the accumulator (f(xs[n], base)), then we apply the function to the second-to-last element and the result, and so on until we've gone through the whole array. At the end we return the final result.iter : (elem -> (), [elem]) -> (): Here we simply want to apply the function to each element of the array. Just like the function given as a parameter, we won't return anything (or null). It's the simplest function to reproduce, don't hesitate to start with this one, then do map.map : (elem -> ret, [elem]) -> [ret]: Here we want to rebuild an array that contains the objects transformed by the supplied function. If we pass getLife and getAliveAllies() as arguments, we'll get the list of the hit points of our team. We can then feed it to sum if we want the total hit points of our team, or to average to know the average health of our team, or to arrayMin if we want to know whether someone urgently needs healing. Once you've reproduced this function, you're advised to code filter.: Here we do the same work as with filter, but instead of dropping the elements that don't satisfy the predicate, we'll put them in a second array. We'll then return the two arrays inside one array. The first being the list of elements satisfying the predicate, the second the list of the other elements. With the same example as filter`, we'll have the list of allied bulbs at index 0, and the list of allied leeks at index 1.sort : (elem -> Ord, [elem]) -> [elem]: Sorting an array is too complex for the scope of this article. A link will be inserted if an article is ever written on the subject.Impossible de charger les données du jeu.
Vérifiez votre connexion et réessayez.