----------------------------------- Mike Werewolf Lundi 20 Novembre 2006 20:51 [Tuto] Cas pratique d'utilisation des opérateurs booléens ----------------------------------- Attention, ce tutorial est destiné aux programmeurs aguerris ; il comporte des notions plus ou moins techniques. OPERATEURS BOOLEENS : CAS PRATIQUE D'UTILISATION Les opérateurs booléens sont extrêmement peu utilisés dans la programmation Mugen. Il faut dire que leur intérêt peut sembler très marginal. Pourtant, il est des cas où ils peuvent se révéler extrêmement utiles : - Ils permettent des situations complexes - Ils permettent de simplifier le code et son utilisation Les opérateurs booléens fonctionnent avec des valeurs binaires, c'est-à-dire des valeurs qui ne peuvent valoir que 0 ou 1. Petit rappel sur les bases : Nous fonctionnons habituellement en système décimal, ou "base 10", c'est-à-dire un système reposant sur 10 chiffres (allant de 0 à 9). Mais il existe d'autres systèmes, notamment : - Le système hexadécimal ou "base 16" comprenant 16 chiffres (de 0 à 9 puis de A à F) ; on l'utilise pour la codification des couleurs en HTML, par exemple. - Le système octal, ou "base 8", comprenant 8 chiffres (de 0 à 7), - Et donc le fameux système binaire ou "base 2", ne comprenant que 2 chiffres (0 et 1). Pour chacun de ces systèmes, lorsqu'on arrive à la dernière unité et qu'on ajoute 1, les unités reviennent à 0 et on augmente d'un les dizaines (et idem pour les centaines, milliers, etc). Donc en base 10, on passe de 9 à 10 (puis de 19 à 20) ; en hexadécimal, on passe de F à 10, puis de 1F à 20 ; en octal, on passe de 7 à 10, puis de 17 à 20 ; et en binaire, on passe de 1 à 10, puis de 11 à 100. Tous les nombres d'une base ont leur équivalence dans une autre base. Par exemple, si vous faites : 1+1+1+1+1+1+1+1+1+1+1+1 = -> En base 10, vous obtiendrez 12 -> En base 16, vous obtiendrez C -> En base 8, vous obtiendrez 14 -> En base 2, vous obtiendez 1100 -> Et chacun de ces nombres est équivalent. Le système binaire présente une caractéristique très intéressante, à savoir tout nombre ne contenant qu'un seul "1" (ex : 1, 10, 100, 1000, 10000, etc.) a pour équivalent en décimal une puissance de 2 (j'utiliserai "**" pour symboliser la puissance dans ce qui suit) : -> 1 binaire = 1 décimal = 2**0 -> 10 binaire = 2 décimal = 2**1 -> 100 binaire = 4 décimal = 2**2 -> 1000 binaire = 8 décimal = 2**3 -> 10000 binaire = 16 décimal = 2**4 -> etc. Exposition du cas pratique : Le cas pratique, c'est Link. Comme vous le savez sans doute, avec ce personnage, dans sa version finale, vous pourrez acheter des objets au fur et à mesure de votre progression dans le mode arcade, afin de les utiliser en combat. Donc pour pouvoir utiliser ces objets, il va falloir stocker l'information : "Link a (ou n'a pas) tel objet", et pour cela, on fait bien sûr appel aux variables. La question est : comment utiliser ces variables au mieux ? Il y a plusieurs solutions. La plus simple consiste à utiliser une variable par objet, et à vérifier la valeur de la variable en question quand on veut utiliser l'objet concerné. Dans ce cas, la variable vaut 1 si on a l'objet et 0 si on ne l'a pas (je ne traiterai pas ici des cas particuliers des flèches, bombes et bouteilles). Oui, mais problème : Link peut acheter près d'une vingtaine d'objets différents : ça fait beaucoup de variable monopolisées pour peu de choses... Sans compter que Link utilise déjà un nombre assez conséquent de variables, et qu'il n'en reste pas suffisamment de libre pour un tel usage. La seconde se rapproche du raisonnement final. Il consiste à dire qu'on peut certainement regrouper plusieurs objets en une seule variable. Eh oui, car en fait, ce qui nous importe, c'est de savoir si on a ou non l'objet, donc si notre variable vaut soit 1 soit 0. On peut ainsi se dire qu'il serait possible d'utiliser un nombre à "plusieurs rangs" (les "rangs" désignant les unités, les dizaines, les centaines, etc. ; le nombre 465 a 3 rangs, par exemple). Ainsi, les unités correspondraient à un objet, les dizaines à un autre, les centaines à un troisième, etc. Ainsi, si la variable vaut 1101, on en déduirait qu'on a le premier objet (unités), le troisième (centaines) et le quatrième (milliers), mais pas le second (dizaines). Mais d'autres problèmes se posent, même si la situation est mieux : -> Déjà, on a un problème de "grandeur" ; Link utilisant 18 objets, il nous faut un nombre ayant 18 rangs, donc par exemple : var(0) = 1000000000000000000 -> Or Elecbyte stipule dans sa doc sur les expressions que les valeurs entières acceptées vont environ de -2 milliards à +2 milliards, soit donc une valeur maximale de : var(0) = 2000000000 -> Soit 10 rangs au maximum ; il nous en manque donc pas mal, ce qui nous obligerait à utiliser une seconde variable. -> Ensuite, le travail sur la variable elle-même serait loin d'être aisé. Par exemple, pour récupérer la valeur du 5ème rang, il nous faudrait une expression de ce style : Floor(var(0)/10000)-(Floor(var(0)/100000)*10) Ex : avec var(0) = 1000010000 : Floor(1000010000/10000)-(Floor(1000010000/100000)*10) = Floor(100001)-(Floor(10000.1)*10) = 100001-(10000*10) = 100001-100000 = 1 Et il faudrait s'amuser ainsi à trouver la formule idéale pour tous les rangs ! On arrive donc à la troisième et dernière solution, celle qui consiste à utiliser les chiffres binaires. De ce qu'on a vu précédemment, on peut retirer plusieurs choses : -> la valeur de la variable qu'on voulait utiliser pour la solution 2 est un nombre binaire (composé uniquement de 0 et de 1) -> la correspondance décimale des nombres binaires est bien plus courte (en terme de rangs). Par exemple, la correspondance décimale du binaire 1000 (4 rangs) est 8 (1 rang). Et pour 1000000000 (10 rangs), c'est 512 (3 rangs). A partir de là, on entrevoit la solution : utiliser les valeurs binaires pour savoir si on a ou non un objet (comme on se proposait de le faire en solution 1 et 2) mais stocker la valeur en "équivalent décimal" pour limiter la valeur de la variable. Reste donc juste à savoir si on peut travailler en binaire sur des valeurs décimales : la réponse est oui ! Les opérateurs booléens : La toute première chose à savoir sur les opérateurs booléens dans Mugen, c'est que les arguments (càd les nombres ou expressions utilisés par les opérateurs) sont : - des nombres de base 10 (donc décimaux, non binaires), - des entiers uniquement (c'est une erreur de travailler avec des flottants), - interversibles (donc a {opérateur} b équivaut à b {opérateur} a). Ces arguments décimaux sont convertis en binaire avant l'opération. Le résultat obtenu est de type binaire, forcément, mais il est lui-même converti en décimal lorsque Mugen retourne ce résultat. => 4 | 3 = 7 : les deux arguments et le résultat sont des décimaux, bien que l'opération se fassent sur leurs équivalents binaires. Les arguments, une fois convertis en binaire, possèdent chacun un nombre de rang. Si les deux arguments ont un nombre de rang différent, on complète celui qui en a le moins en rajoutant des 0 sur sa gauche (ce qui ne change pas sa valeur : 1 = 01 = 001) de façon à ce qu'ils aient le même nombre de rangs. Opération de départ => 4 | 3 Conversion en binaire => 100 | 11 100 = 3 rangs ; 11 = 2 rangs. On doit donc rajouter un 0 sur la gauche du second argument : 100 | 011 Ensuite, les rangs des arguments sont comparés entre eux pour donner la valeur de ce rang dans le résultat binaire. Tout ceci ressemble peut-être à du chinois, mais en pratique, c'est extrêmement simple. Avant de vous montrer quelques exemples, je dois vous présenter rapidement les opérateurs : NOT : il n'utilise qu'un seul argument, et retourne normalement l'inverse binaire de cet argument (retourne 0 quand il y a 1, et inversement). Je ne m'attarde pas dessus car il fait apparemment planter Mugen. Les 3 autres opérateurs utilisent tous 2 arguments OR ("OU") : pour chaque rang de chaque argument, si au moins un des deux argument vaut 1, alors le résultat vaut 1. AND ("ET") : pour chaque rang de chaque argument, si et seulement si les deux arguments valent 1, alors le résultat vaut 1. XOR ("OU EXCLUSIF") : pour chaque rang de chaque argument, si un et un seul des deux arguments vaut 1, alors le résultat vaut 1. Dans tous les cas, si le résultat ne vaut pas 1, il vaut 0. Quelques exemples : * OR : opérateur : | rang : 987654321 ---------------------- argument 1 : 100101101 argument 2 : 101000100 ---------------------- résultat : 101101101 -> rang 9 : argument 1 = 1, argument 2 = 1 ; au moins un des deux vaut 1 donc le rang 9 du résultat vaut 1 -> rang 8 : argument 1 = 0, argument 2 = 0 ; aucun des deux ne vaut 1, donc le rang 8 du résultat vaut 0 -> rang 7 : argument 1 = 0, argument 2 = 1 ; au moins un des deux vaut 1 donc le rang 7 du résultat vaut 1 -> rang 6 : argument 1 = 1, argument 2 = 0 ; au moins un des deux vaut 1 donc le rang 6 du résultat vaut 1 -> etc. NB : l'équivalent décimal de l'argument 1 est 301 l'équivalent décimal de l'argument 2 est 324 l'équivalent décimal du résultat est 365 Donc dans Mugen, pour effectuer un tel calcul, on écrirait : 301|324 (ou 324|301), et Mugen retournerait 365. Exemple : [State 1000] type = VarSet trigger1 = Time = 0 var(0) = 301 | 324 Avec ceci, var(0) vaut 365. * AND : opérateur : & rang : 987654321 ---------------------- argument 1 : 100101101 argument 2 : 101000100 ---------------------- résultat : 100000100 -> rang 9 : argument 1 = 1, argument 2 = 1 ; les deux valent 1 donc le rang 9 du résultat vaut 1 -> rang 8 : argument 1 = 0, argument 2 = 0 ; les deux ne valent pas 1 donc le rang 8 du résultat vaut 0 -> rang 7 : argument 1 = 0, argument 2 = 1 ; les deux ne valent pas 1 donc le rang 7 du résultat vaut 0 -> rang 6 : argument 1 = 1, argument 2 = 0 ; les deux ne valent pas 1 donc le rang 6 du résultat vaut 0 -> etc. NB : l'équivalent décimal de l'argument 1 est 301 l'équivalent décimal de l'argument 2 est 324 l'équivalent décimal du résultat est 260 Donc dans Mugen, pour effectuer un tel calcul, on écrirait : 301 & 324 (ou 324 & 301), et Mugen retournerait 260. Exemple : [State 1000] type = VarSet trigger1 = Time = 0 var(0) = 301 & 324 Avec ceci, var(0) vaut 260. * XOR : opérateur : ^ rang : 987654321 ---------------------- argument 1 : 100101101 argument 2 : 101000100 ---------------------- résultat : 001101001 -> rang 9 : argument 1 = 1, argument 2 = 1 ; les deux ont la même valeur donc le rang 9 du résultat vaut 0 -> rang 8 : argument 1 = 0, argument 2 = 0 ; les deux ont la même valeur donc le rang 8 du résultat vaut 0 -> rang 7 : argument 1 = 0, argument 2 = 1 ; les deux n'ont pas la même valeur donc le rang 7 du résultat vaut 1 -> rang 6 : argument 1 = 1, argument 2 = 0 ; les deux n'ont pas la même valeur donc le rang 6 du résultat vaut 1 -> etc. NB : l'équivalent décimal de l'argument 1 est 301 l'équivalent décimal de l'argument 2 est 324 l'équivalent décimal du résultat est 105 Donc dans Mugen, pour effectuer un tel calcul, on écrirait : 301^324 (ou 324^301), et Mugen retournerait 105. Exemple : [State 1000] type = VarSet trigger1 = Time = 0 var(0) = 301^324 Avec ceci, var(0) vaut 105. ____________ On notera également que (301 | 324)+(301^324)=(301 & 324) = 260 + 105 = 365 Les opérateurs binaires "implicites" : On a vu précédemment que les binaires avaient leur équivalent dans les différentes bases (8, 10, 16...). Grâce à cela, les opérations standard (addition, soustraction...) effectuées sur des décimaux s'effectuent également, de façon implicite, pour leur équivalent binaire ! Par exemple, si on fait 3 + 4, en décimal, on obtient 7. Et en binaire : * Equivalent binaire de 3 = 011 * Equivalent binaire de 4 = 100 --- * Résultat d'addition = 111 * Equivalent décimal de 111 = 7 Mise en pratique : Pour ce qui suit, voici les éléments importants à retenir de tout ce qu'on a vu : Les décimaux et les binaires ont leurs équivalents dans l'autre base, et les opérations effectuées dans une base donnent le même résultat (après conversion, évidemment), dans l'autre base. Le "&" ne peut être vrai que si les deux rangs comparés valent 1. Les équivalents décimaux des binaires ne contenant qu'un seul "1" sont des puissances de 2. Fort de tout ceci, nous avons deux choses principales à faire avec notre variable : Vérifier que l'on a ou non tel objet (lors de l'achat ou de l'utilisation) Changer la valeur de la variable lorsqu'on achète l'objet. Pour chacune de ces deux opéations, on va devoir vérifier/modifier un rang et un seul, sans tenir compte des autres. Le tout est donc de savoir lequel ! Pour cela, on va donc utiliser ces fameuses puissances de deux, où le "1" du chiffre binaire correspondant sera placé sur le rang qu'on veut vérifier/modifier. Par exemple, si on veut vérifier ou modifier le contenu du 4ème rang, on va devoir travailler avec le nombre 2**3, c'est à dire 8 (équivalent binaire : 1000). On pourrait passer tout simplement par les nombres décimaux, sans chercher à utiliser les puissances. Il suffit de savoir qu'au rang binaire : 1 correspond le nombre décimal 1 2 correspond le nombre décimal 2 3 correspond le nombre décimal 4 4 correspond le nombre décimal 8 5 correspond le nombre décimal 16 6 correspond le nombre décimal 32 7 correspond le nombre décimal 64 8 correspond le nombre décimal 128 etc. Cependant, les puissances de 2 nous simplifient la vie, car on peut utiliser une formule valable pour tous les rangs : * au rang binaire N correspond le nombre décimal 2**(N-1). Exemples : Binaire Puiss. 2 : Calcul : 1 0 2**0 = 1 2 1 2**1 = 2 3 2 2**2 = 4 4 3 2**3 = 8 5 4 2**4 = 16 6 5 2**5 = 32 7 6 2**6 = 64 8 7 2**7 = 128 ... 18 17 2**17 = 131072 S'il est plus simple d'utiliser 1, 2, 4, ou 8 au début, pour les rangs plus avancés en revanche, il est plus simple de faire appel à ces puissances de 2. A partir de là : * pour vérifier si on a un objet ou non (donc si le rang correspondant à cet objet vaut 0 ou 1), il suffit de faire un "&" sur ce rang avec le nombre décimal correspondant : tous les autres rangs retourneront 0, car dans ce chiffre, seul le rang concerné vaut 1. Et pour ce rang à vérifier, si le résultat du "&" vaut 0, alors c'est qu'on n'a pas l'objet (sinon, c'est que le rang vaut 1 et donc, qu'on a l'objet). Exemple : si notre var(0) vaut 55. Son équivalent binaire est donc : 110111. * Je veux vérifier le rang 4 : je pose donc "var(0) & 8" (ou "var(0) & 2**(4-1)" si vous préférez). Résultat : > var(0) = 110111 > valeur = 001000 > résult = 000000 donc en décimal = 0 => On n'a pas l'objet, ce qui est vrai puisque le rang 4 vaut 0. * Je veux vérifier le rang 5 : je pose donc "var(0) & 16" (ou "var(0) & 2**4" si vous préférez). Résultat : > var(0) = 110111 > valeur = 010000 > résult = 010000 donc en décimal = 16 et donc différent de 0 => On a l'objet, ce qui est vrai puisque le rang 5 vaut 1. Bref, c'est terriblement plus efficace que ce qu'on avait pour la deuxième solution évoquée au début de ce message. Pour rappel : Floor(var(0)/10000)-(Floor(var(0)/100000)*10) * pour acquérir un objet (ce qui implique que son rang vaut 0), c'est encore plus simple : compte tenu de l'équivalence entre les bases binaire et décimale, il suffit d'ajouter la valeur décimale correspondante au rang pour que celui-ci passe de 0 à 1 : * Je repars de ma var(0) qui vaut 55 (binaire = 110111), et j'achète l'objet correspondant au rang 4. Je pose donc : var(0) + 2**3 (ou var(0)+8). Ma var(0) passe donc à 55+16=63. Vérifions ce que ça donne pour notre équivalence binaire : > var(0) = 110111 > valeur = 001000 > résult = 111111 donc en décimal = 63 => on a changé le rang concerné (et uniquement celui-ci), et on possède donc l'objet, désormais ! PETIT SOUCI AVEC MUGEN... Il se trouve que Mugen présente un "léger" bug avec les puissances de 2. En effet, si certains calculs sont correctes, d'autres en revanche sont complètement faux (en fait, certaines puissances de 2 sont inversées). Voici un tableau représentant les 24 premières puissances de 2 (donc de 2^0 à 2^23), où sont notés avec une astérisque les valeurs qui posent problème : Rang : Binaire : Retourne : Au lieu de : 1 000000000000000000000001 1 1 2 000000000000000000000010 2 2 3 000000000000000000000100 4 4 4 000000000000000000001000 8 8 5 000000000000000000010000 16 16 6 000000000000000000100000 * 64 32 7 000000000000000001000000 * 32 64 8 000000000000000010000000 128 128 9 000000000000000100000000 256 256 10 000000000000001000000000 * 4096 512 11 000000000000010000000000 1024 1024 12 000000000000100000000000 * 16384 2048 13 000000000001000000000000 * 512 4096 14 000000000010000000000000 8192 8192 15 000000000100000000000000 * 2048 16384 16 000000001000000000000000 32768 32768 17 000000010000000000000000 65536 65536 18 000000100000000000000000 * 16777216 131072 19 000001000000000000000000 262144 262144 20 000010000000000000000000 524288 524288 21 000100000000000000000000 1048576 1048576 22 001000000000000000000000 2097152 2097152 23 010000000000000000000000 4194304 4194304 24 100000000000000000000000 8388608 8388608 Veillez donc à utiliser les équivalents "Mugen" et non les valeurs "réelles" (style : utilisez 64 et non 32 pour 2 puissance 5...). Voilà, c'était un cas concret d'utilisation de ces opérateurs booléens, qui se révèlent d'une efficacité redoutable pour la situation exposée ici. Je suppose qu'il y a d'autres cas où ils peuvent être très utiles, mais on n'a que rarement le réflexe de penser à cette solution, ou même de voir comment elle pourrait être utile. Notez quand même qu'on ne travaille qu'avec des valeurs binaires, donc sur des cas "stricts" (on a l'objet ou on ne l'a pas), ce qui m'a obligé à exclure les bouteilles, du fait qu'on peut en avoir plusieurs, et que chacune peut avoir un contenu différent. Idem pour les bombes et les flèches qu'on peut avoir en plusieurs exemplaires. Mike Werewolf.