

-----------------------------------

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.


-----------------------------------

Anji

Mardi 21 Novembre 2006 9:10



-----------------------------------

301 & 324 = 260
Je dois avouer que ça choque au début ! Je ne connaissais pas du tout l'utilisation de ces opérateurs booléens et c'est une excellente découverte car j'utilise beaucoup de variables booléennes et j'étais naïvement parti sur la solution 2 ( :oops: ...).

Il ne reste donc plus qu'à préparer un petit fichier Excel pour passer rapidement d'une base à une autre (bah oui parce que connaître la conversion de 301 n'est pas si évident et intuitif que ça !).


Merci beaucoup pour ce tuto aussi utile qu'intéressant, Professeur Mike !


-----------------------------------

Mike Werewolf

Mardi 21 Novembre 2006 16:08



-----------------------------------

j'étais naïvement parti sur la solution 2
Je pense que c'est une "erreur" qu'on fait souvent (je l'ai faite aussi !). Seulement, Link et ses objets ont démontré les limites de cette méthode, ce qui m'a poussé à en chercher une autre. C'est en relisant la doc sur les opérateurs que j'ai pensé à ça. ;)

Il ne reste donc plus qu'à préparer un petit fichier Excel pour passer rapidement d'une base à une autre (bah oui parce que connaître la conversion de 301 n'est pas si évident et intuitif que ça !).
En général, une connaissance basique des puissances de 2 peut suffire (1, 2, 4, 8, 16, 32, 64, 128, etc.). Perso, j'utilise ma vieille TI-85 pour des conversions plus poussées ! :mrgreen:

Merci beaucoup pour ce tuto aussi utile qu'intéressant, Professeur Mike !
:lol: Merci ! Je pense que c'est une technique trop peu connue et qui offre d'énormes possibilités. Ca peut être un peu complexe au début, mais une fois qu'on a compris le système, c'est tout simple et bien pratique ! :D

Mike Werewolf.


-----------------------------------

R.o.q.u.e.

Jeudi 23 Novembre 2006 18:15



-----------------------------------

Intéressant, mais il faut en effet s'accrocher un peu pour tout saisir  ^^!

