Tutoriel LeekScript : Les Fonctions

De Leek Wars Wiki
Aller à : navigation, rechercher

Dans un programme, le plus important est de bien structurer son code. Pour cela vous avez appris l'utilisation des commentaires et c'est à peu près tout... Mais on va vite changer cela avec les fonctions, une des notions les plus importantes en programmation et vous allez vite comprendre. Vous en avez déjà croisées et même utilisées dans le chapitre des fonctions natives.

Qu'est-ce que c'est ?

Une fonction est une suite d'instructions, rien de plus. Ainsi plus de duplica de code, il suffit d’appeler la fonction qui va exécuter ces instructions. Afin de rendre les fonctions plus polyvalentes, on peut leur donner des paramètres comme par exemple le poireau que l'on veut frapper, le nombre de MP qrestants pour ce tour, etc. De plus la fonction peut retourner une valeur comme un tableau avec toutes les cases accessibles par notre poireau. Voici le prototype, i.e. la signature, d'une fonction :

NomDeLaFonction ( parametre1, parametre2, ..., parametreN ) : Retour

Le prototype, c'est la description de la fonction : son nom, son retour si il en y a un et ses paramètres qui sont aussi optionnels. La définition d'une fonction, c'est l'ensemble des instructions présentes dans celle-ci. Tous les prototypes des fonctions natives sont accessibles dans | la documentation. Sur la gauche, dans la liste des fonctions triées par thème, si on sélectionne "UseWeapon" dans la catégorie Armes, on trouve ceci :

UseWeapon-Mod.png
  • 1 -> Le prototype comme je vous l'ai expliqué : En premier le nom de la fonction, puis entre parenthèses les paramètres, puis le type du retour
  • 2 -> La description de la fonction : J'imagine que vous savez à quoi cela sert
  • 3 -> Les paramètres : Cette fonction prend un paramètre : "leek" qui doit être un nombre (l'ID du poireau ciblé)
  • 4 -> Le retour : La fonction retourne une valeur (on parle de constante ici), vous pouvez rechercher dans la doc les constantes
  • 5 -> Le nombre d'opérations de la fonction : (Rappel : vous avez 20 millions d'opérations par tour)

Que se passe-t-il lorsque l'on appelle une fonction?

Lors de l'appel d'une fonction, le programme 'principal' se met en pause pour exécuter le programme stocké dans la fonction. Puis à la fin de cette fonction, le programme 'principal' reprend là où il s'était arrêté.

La déclaration

On parle aussi de déclaration pour les fonctions. En effet le LS vous propose des fonctions natives prêtes à l'emploi tel que "getNearestEnemy / useWeapon / ...". Mais il est possible d'en créer par vous mêmes. Voici la syntaxe à adopter :

function NomDeLaFontion ( parametre1, parametre2, ..., parametreN ) {
    /* Les instructions */
}

Il n'est pas possible de tout faire dans une fonction : la déclaration d'une nouvelle fonction ainsi que la déclaration d'une variable globale sont interdites !

Pour être plus parlant, nous allons créer une fonction très simple, son but va être de se rapprocher du centre de la carte, de lancer une puce de bouclier et une de soin. Voici son prototype : (A noter que le prototype n'est pas à mettre dans le code, sauf à la rigueur en commentaire pour expliquer votre fonction)

ArmorAndCare()

Rien de plus simple en effet, notre fonction ne prend pas de paramètre et ne retourne rien !

Il lui manque tout de même une définition que voilà :

function ArmorAndCare(){
	moveTowardCell(306);
	useChip(CHIP_ARMOR, getLeek());
	useChip(CHIP_CURE, getLeek());
}

Pour appeler la fonction, c'est comme les autres :

ArmorAndCare(); // Se rapproche du centre de la carte puis lance ARMOR et CURE

Remarque importante, une fonction ne peut pas accéder aux variables extérieurs sauf les globales. Il faut imaginer que lorsque l'on appelle la fonction, celle-ci "vit" dans son propre monde et que la seule chose à laquelle elle a l'accès sont les variables globales et les autres fonctions.

Afin d'illustrer le système des paramètres et des retours de fonctions nous allons prendre cet exemple :

// Prototype : getCellDistanceTo(leek) : (Nombre) Distance entre mon poireau et celui en paramètre
function getCellDistanceTo(leek){
	return getCellDistance(getCell(), getCell(leek));
}

D'après le prototype, la fonction prend en paramètre un poireau (son ID), et retourne la distance entre celui-ci et son propre poireau. La liste des paramètres est (presque) infinie, il suffit de séparer chaque paramètre par une virgule. Le retour de la fonction s'effectue avec le mot clé 'return'. Dans cet exemple la fonction 'getCellDistance' renvoie un nombre que l'on renvoie directement. Lorsque la fonction rencontre un 'return', elle s’arrête et reprend à l'endroit de l'appel de la fonction. Pour les intéressés, le principe de réduire le nombre de paramètre d'une fonction s'appelle la 'currification'. Si vous voulez arrêter prématurément la fonction sans renvoyer une valeur, il suffit de placer le mot-clé 'return' sans valeur.

Passage par référence

Dans les exemples ci-dessus, les données envoyées lors de l'appel d'une fonction sont copiés. Voici ce qu'il se passe :

function getCellDistanceTo(leek){
	return getCellDistance(getCell(), getCell(leek));
}

var enemy = getNearestEnemy();

getCellDistanceTo(enemy );

// 1 - Appel de getNearestEnemy qui renvoie un ID de poireau (exemple : 2)
// 2 - enemy vaut maintenant '2'
// 3 - Appel de getCellDistanceTo qui prend un paramètre (dans l'exemple : enemy soit 2)
// 4 - Dans la fonction getCellDistanceTo : 'leek = 2;' s'execute puis la définition de la fonction
// 5 - Appel de getCellDistance : copie de 'getCell()' (le retour) et de 'getCell(leek)' (le retour aussi)
// 6 - Retour de la distance entre les deux poireaux

Que signifie cette copie ? Si l'on modifie 'leek' dans 'getCellDistanceTo', notre variable 'enemy' ne sera pas modifie.

Prenons un exemple qui illustrera mieux le passage par référence :

// Prototype
// canDivide (result, (Nombre) a, (Nombre) b) : (Bool) True si l'on peut diviser 'a' par 'b' et stocke le resultat dans 'result'

// Définition
function canDivide (result, a, b) {
    result = 0;
    if (b === 0)
        return false; // La division par zéro n'est pas possible
    // Pas besoins de 'else' car si b === 0 la fonction sera arrete
    result = a / b;
    return true;
}

Pour l'instant on parle de "passage par valeur" car lors de l'appel à cette fonction, tous les paramètres sont copiés. Pour effectuer ce passage par référence, il faut utliser un '@' avant le paramètre (dans la définition) :

function canDivide2 (@result, a, b) {
    result = 0;
    if (b === 0)
        return false; // La division par zéro n'est pas possible
    // Pas besoins de 'else' car si b === 0 la fonction sera arrêtée
    result = a / b;
    return true;
}

Grâce à ceci, si l'on modifie result, la variable qui a servi à l'appel de la fonction sera aussi modifié. Testons les deux fonctions :

var result;

if (canDivide(result, 5, 2)) // Fonction avec passage par valeur
    debug("La division est possible entre 5 et 2, le resultat est : " + result);
else
    debug("La division de 5 par 2 n'est pas possible !");

if (canDivide2(result, 5, 2)) // Fonction avec passage par référence
    debug("La division est possible entre 5 et 2, le resultat est : " + result);
else
    debug("La division de 5 par 2 n'est pas possible !");
/*
Si on test, on obtient :
"La division est possible entre 5 et 2, le resultat est : null"
puis
"La division est possible entre 5 et 2, le resultat est : 2.5"
*/

Si vous voulez plus d'explication sur le comportement du '@' qui ne sert pas que dans les paramètres, je vous conseil cette page : Comportement du '@'

Fonction en tant qu'objet de première classe

En Leekscript, les fonctions sont des objets de première classe. On peut donc considérer la déclaration de fonction de la forme function name([parameters]) {[code]} comme étant une syntaxe spécial. Cela se remarque notamment en terme de périmètre. Le langage considère que les fonctions déclarées de la sorte sont à part, et seul les objets dans le périmètre global (dont elles-mêmes) sont accessibles à l'intérieur de leur propre périmètre.
Manipuler les fonctions comme n'importe quel objet de première classe nous permet de les considérer de manière bien plus intéressante. Rappelez vous ce qu'il est possible de faire avec les nombres/chaînes de caractères/tableaux:

  • les créer anonymement sous forme de littéral:
0;
"";
[];
function(x) { return x; };
  • les affecter à des variables ou structures de données:
var int = 0;
var string = "";
var array = [];
var func = function(x) { return x; };

var all = [0, "", [], function(x) { return x; }];
  • les passer en paramètre à une fonction:
acceptAnything(0);
acceptAnything("");
acceptAnything([]);
acceptAnything(function(x) { return x; });
var id = function(x) { return x; };
acceptAnything(id);
acceptAnything(getNearestEnemy); // Il est tout à fait possible de passer une fonction préexistante.
  • les retourner à partir d'une fonction:
function() { return 0; };
function() { return ""; };
function() { return []; };
function() { return function(x) { return x; }; };
function() { return getNearestEnemy; };

Fonction en paramètre

Reproduire la Lib

C'est en faisant qu'on apprends. Pour l'instant, nous allons nous contenter de reproduire les fonctions arrayX qui prennent une "callback" (c'est juste une fonction, évitons le terme callback à l'avenir). La fonction en paramètre n'attendras qu'un ou deux arguments, (nous ne manipulerons pas les tableau associatifs), et nous placerons les paramètres dans un ordre différent en prévision de la section sur les fonctions en retour. Il est recommandé de commencer par iter.

  • filter : (elem -> Bool, [elem]) -> [elem]: Ici nous voulons reconstruire un tableau qui ne contient que les objets dont la fonction retourne true lorsque nous l'appliquons à un élément (f(xs[i])), par exemple, si nous passons isSummon et getAliveAllies() en argument (filter(isSummon, getAliveAllies())), nous obtiendrons une liste des bulbes alliés vivant. Vous pouvez maintenant passer à partition.
  • foldl : ((acc, elem) -> acc, acc, [elem]) -> acc: Ici, nous voulons consommer un tableau en le parcourant du début à la fin. Nous commencons par appliquer la fonction à l'accumulateur et au premier élément (f(base, xs[0])), ensuite nous appliquons la fonction au résultat et au deuxième élément, ainsi de suite jusqu'à avoir parcouru tout le tableau. Nous retournons à la fin le résultat final.
  • foldr : ((elem, acc) -> acc, acc, [elem]) -> acc: Ici, nous voulons consommer un tableau en le parcourant de la fin au début. Nous commencons par appliquer la fonction au dernier élément et à l'accumulateur (f(xs[n], base)), ensuite nous appliquons la fonction à l'avant dernier élément et au résultat, ainsi de suite jusqu'à avoir parcouru tout le tableau. Nous retournons à la fin le résultat final.
  • iter : (elem -> (), [elem]) -> (): Ici, nous voulons simplement appliquer la fonction à chaque élément du tableau. Tout comme la fonction prise en paramètre, nous ne retournerons rien (ou null). C'est la fonction la plus simple à reproduire, n'hésitez pas à commencer par celle-ci, puis ensuite à faire map.
  • map : (elem -> ret, [elem]) -> [ret]: Ici, nous voulons reconstruire un tableau qui contient les objets transformés par la fonction fournie. Si nous passons getLife et getAliveAllies() en argument, nous obtiendrons la liste des points de vie de notre équipe. Nous pouvons ensuite la fournir à sum si nous voulons le total de points de vie de notre équipe, ou bien à average pour connaitre l'état de santé moyen de notre équipe, ou encore à arrayMin si nous voulons savoir si quelqu'un à besoin d'être soigné d'urgence. Une fois cette fonction reproduite, il vous est conseillé de coder filter.
  • partition : (elem -> Bool, [elem]) -> [[elem], [elem]]: Ici on fait le même travail qu'avec filter, mais au lieu d'abandonner les éléments qui de satisfont pas le prédicat, nous allons les placer dans un deuxième tableau. Nous retournerons ensuite les deux tableaux avec un tableau. Le premier étant la liste des éléments satisfaisant le prédicat, le deuxième la liste des autres éléments. Avec le même exemple que filter, nous aurons la liste des bulbes alliés en index 0, et la liste des poireaux alliés en index 1.
  • sort : (elem -> Ord, [elem]) -> [elem]: Trier un tableau est trop complexe pour le cadre de cette article. Un lien sera inséré si jamais un article est réalisé à ce sujet.

Ajouter des Fonctions Utiles

Fonction en retour