Skip to content

Playing with list and function

theory-sma
Résultat final après l'execution de go plusieurs fois

Objectifs

Ce petit exemple pourrait être le début d'un jeu de société avec un déplacement de pions. Chaque tortues se téléporte sur une case au hasard, et si celle-ci est blanche, alors elle ajoute 1 pour chaque couleur présente dans un voisinnage de 8 à son inventaire stockée sous forme de liste.

Dans cette exercice, nous commençons par utiliser les concepts suivants :

  • l'écriture de fonctions
  • création et manipulation de listes pour stocker des éléments : ajout, modification
  • l'utilisation de fonctions pour parcourir les listes commes foreach
  • l'utilisation d'une fonction de voisinnage neighbors
  • l'utilisation de myself
  • la réécriture de code avec of

Algorithme

L'algorithme est le suivant :

  • (1) créer 10 tortues de position xy random, de couleurs noires
  • (2) créer un raster qui mélange 3 couleurs (vert, orange, bleu) et l'absence de couleurs (blanc)
  • (3) les tortues se téléporte sur une nouvelle case xy random
  • (4) pour chaque tortue qui est sur une case blanche, celle-ci
    • (4.1) récupère les couleurs des patchs dans son voisinage de 8
    • (4.2) classifier les couleurs dans un tableau avec la fonction classify-colors
    • (4.3) elle aggrege les couleurs classifiés renvoyées par (5) et les ajoute au décompte existant pour cette tortue
  • (5) une fois toutes les tortues blanches traitées revenir à l'étape (3)

Mise en oeuvre

Comme habituellement on définit deux grandes fonctions setup et go, la première servira à l'initialisation du monde, la deuxième à la mise en route du monde avec l'execution des règles à chaque pas de temps.

On définit deux attributs pour nos tortues :

  • on-white? sera un booléen qui indique si la tortue est sur une case blanche
  • count-colors contiendra une liste des couleurs qui récapitule les couleurs croisées par chaque tortue :
  • le premier item de cette liste représente les occurences de couleurs verte
  • le deuxième item de cette liste représente les occurences de couleur orange
  • le troisième item de cette liste représente les occurences de couleur bleu
  • le quatrième item de cette liste représente les occurences de couleur blanche
turtles-own [ 
    on-white? 
    count-colors 
]

Nous avons 4 procédures et 2 fonctions dans notre programme :

to setup 
   ...
end

to-report aggregate-colors [o-colors n-colors] ; (1)
   ...
end

to-report classify-colors [n-colors] ; (2)
   ...
end 

to count-on-patch ; (3)
   ...
end

to teleport 
   ...
end

to go 

end
  1. Cette fonction est appelé pour chaque tortue, prend en paramètre le tableau des couleurs précédentes o-colors et le tableau des nouvelles couleurs n-colors, elle renvoie le cumul des deux tableaux.
  2. Cette fonction est appelé pour chaque tortue, elle prend en paramètre le tableau des couleurs récupérée dans le désordre et renvoie décompté en suivant l'ordre [green orange blue white]. Si n-colors vaut [105 105 25 55 ] avec 105 (blue), 55 (green), 25 (orange) la fonction renverra [1 1 2 0]
  3. Cette procédure contient la partie (4) de l'algorithme, et c'est elle qui va apeller les fonctions (1) et (2) pour classifier puis agréger ces couleurs. Par exemple si o-colors vaut [4 2 2 0] et n-colors vaut [1 1 2 0] alors la fonction renverra [5 3 4 0]

La procédude setup initialise les couleurs de patches puis crée et initialise les 10 tortues sur le plateau de jeu.

A l'initialisation l'attribut count-colors est définit comme une liste vide de 4 items, pour les 4 couleurs que l'on doit compter au cours du jeu.

to setup

clear-all
reset-ticks

ask patches [
  set pcolor first shuffle [green orange blue white]
]

crt 10 [
    set xcor random-pxcor
    set ycor random-pycor
    set shape "dot"
    set size 2
    set color black
    set count-colors [ 0 0 0 0 ]
  ]

end
to teleport 
  ask turtles [
    set xcor random-pxcor
    set ycor random-pycor 
  ]
end

to go
  teleport
  count-on-patch
  tick
end

Dans la procédure go, on déplace les tortues de façon aléatoire teleport à chaque pas de temps tick, et surtout, le plus important on demande à compter les couleurs pour chaque tortues.

Afin de garder le code go relativement simple et lisible, on a choisi d'encapsuler le code relatif au décompte des couleurs dans la procédure count-on-patch.

Note

Il faut utiliser les fonctions et procédure pour rendre plus générique votre code, mais aussi pour l'organiser et le découper de façon logique. Il est plus intéressant pour le concepteur et relecteur du code d'avoir des corps de procédures et de fonctions de tailles raisonnables, et dont l'utilisation est claire et explicite du fait d'un nom bien choisi.

to count-on-patch

  ask turtles [
    let new-colors [] ; (1)
    ask patch-here [ ; (2)
      (ifelse 
        pcolor = white [
          ask myself [ ; (3)
            set on-white? true
            set new-colors (classify-colors [pcolor] of neighbors) ; (4)
          ]
        ][
          ask myself [ ; (5)
            set on-white? false
          ]
        ]
      )
    ]

    if on-white? [ 
      set count-colors aggregate-colors count-colors new-colors ; (6)
    ]

]
end
  1. La variable locale new-colors est réinitialisé pour chaque tortue, elle permet de stocker au niveau du contexte de la tortue le résultat de la classification qui a lieu dans le contexte imbriqué du patch-here
  2. On ouvre un contexte questionant le patch sous jacent à la tortue
  3. Au sein du contexte du patch, on a besoin de mettre à jour la variable locale new-colors avec le résultat de la classification. On a donc le droit d'utiliser le mot clef myself qui se rapport ici à l'agent tortue.
  4. On apelle la fonction classify-colors avec comme paramètre la liste des 8 couleurs renvoyés par [pcolor] of neighbors
  5. même chose que pour 3, si la couleur du patch n'est pas white il est important de remettre la valeur de on-white? à false.
  6. On apelle la fonction aggregate-colors avec la liste de couleurs rencontrés stockées dans la tortue dans la variable (count-colors) et les valeurs classifiés précédemment dans (4)

Cette fonction pourrait être écrite autrement, et de façon plus simple, en s'appuyant sur d'autres primitives netlogo, à vous d'essayer.

La fonction de classification utilise foreach, un type de boucle dédié au parcours de listes.

Note

Pour rappel la différence entre les Listes et les AgentSet est lié à l'ordonnancement des éléments. A chaque appel de fonction qui retourne un AgentSet on a la garantie que le tirage se fait de façon aléatoire. Mais parfois, comme dans cet exercice, nous avons besoin de garder les éléments ordonnés, c'est pourquoi on utilise une liste et les fonctions associées aux manipulation de listes.

La fonction classify-colors est la suivante :

to-report classify-colors [n-colors]
  ; g o b w
  let n-colors-classified [ 0 0 0 0 ] ; (1)
  foreach n-colors [ c -> ; (2)
   (ifelse c = green
      [
        set n-colors-classified replace-item 0 n-colors-classified (item 0 n-colors-classified + 1) ; (3)
       ]
   (c = orange) 
        [
        set n-colors-classified replace-item 1 n-colors-classified (item 1 n-colors-classified + 1)

        ]
   (c = blue)
      [set n-colors-classified replace-item 2 n-colors-classified (item 2 n-colors-classified + 1)]

   (c = white)
    [set n-colors-classified replace-item 3 n-colors-classified (item 3 n-colors-classified + 1)]
  )]
  report n-colors-classified ; (4)
end
  1. On initialiste une variable locale qui contiendra le résultat renvoyé par (4)
  2. Pour chaque élément c (couleur) du tableau, on execute la condition ifelse qui détermine de quel couleur il s'agit exactement.
  3. Si par exemple, la couleur c est verte, alors on va remplacer l'item 0 (replace-item) de la liste n-colors-classified par (la valeur précédente + 1) c'est à dire ((item 0 n-colors-classified)+ 1)
  4. Une fois arrivée à la fin de la boucle, on a forcément classé tous les éléments, donc on peut renvoyer le tableau classifié n-colors-classified

La fonction aggregate-colors est la suivante :

to-report aggregate-colors [o-colors n-colors]
  let i 0
  let ag-colors [ 0 0 0 0 ] ; (1)
  foreach n-colors [ elem -> ; (2)
    set ag-colors replace-item i ag-colors (elem + item i o-colors) ; (3)
    set i (i + 1) ; (4)
  ]
  report ag-colors ; (5)
end
  1. On créé une variable locate pour stocker le résultat de la somme entre le tableau de couleur stocké dans l'attribut de la tortue et le nouveau tableau de couleurs issue de la collecte une fois classifié.
  2. On parcourt chaque élément du tableau, peu importe lequel d'ailleurs vu qu'ils on les deux 4 valeurs. Il nous faut juste un compteur i qui va permettre d'identifier ou l'on se situe dans le tableau.
  3. On va mettre à jour le tableau ag-colors en se basant sur le cumul des valeurs du nouveau tableau (ici elem) et celui du tableau existant pour la tortue (item i o-colors)
  4. On met à jour le compteur i explicitement, foreach ne gérant pas çà.
  5. On renvoie le tableau final après la fusion des deux tableau, celui ci va écraser la liste existante (cad count-colors) déjà pour la tortue ayant fait l'appel à cette fonction.

Add a Plot

Pour construire un plot sur l'interface, il faut faire un clic-droit puis choisir Plot.

theory-sma
Configuration du plot avec la somme des couleurs par case pour l'ensemble des tortues

Vous pouvez ensuite définir les valeurs qui vont être utilisé par chaque Pen (Stylo) au sein du plot, sachant que ces valeurs seront forcément recalculé à chaque pas de temps (tick)

New rules

Nous allons ajouter un peu de piment à ce jeu avec une nouvelle règle.

Chaque pion/tortue possède donc un tableau de couleurs qui est le cumul des couleurs qu'il a rencontré dans son voisinnage pendant la partie.

Nous allons ajouter un tableau des scores pour stocker les scores de chacune des tortue, celui-ci est un attribut my-score de la tortue et il est initialisé à [0 0 0 0].

Lorsque deux pions se croisent dans un certains radius, par exemple 3, alors ils s'affrontent selon les règles suivantes :

  • si il y a plus d'un pion dans le voisinnage, on en prend un au hasard, on reste dans le cadre d'un duel
  • on compare les couleurs count-colors des deux pions paire à paire entre les 2 combattants.
  • si un pion est plus fort sur une couleur alors il gagne le combat pour cette couleur, ce qui revient à ajouter +1 à my-score à la position correspondante à la couleur. Le perdant lui perd 1 point, sachant que le score ne peux pas descendre en dessous de zéro...

Par exemple :

  • Le pion A possède un count-colors = [2 3 4 1]
  • Le pion B possède un count-colors = [1 4 4 2]

Ce qui donnera à la fin du combat :

  • Le pion A ajoutera a son tableau des score existants, les valeurs : [ +1 -1 0 -1 ]
  • Le pion B ajoutera a son tableau des score existants, les valeurs : [ -1 +1 0 +1 ]

Nous allons ajouter deux nouvelles variables à nos tortues :

  • already-battle? est un booléen qui détermine si la tortue a déjà combattu ou pas
  • count-battle contiendra le résultat aggrégé des combats

On initialise ces valeurs dans le create des tortues.

turtles-own [ on-white? count-colors already-battle? count-battle]

to setup

...

crt 10 [
    set xcor random-pxcor
    set ycor random-pycor
    set shape "dot"
    set size 2
    set color black
    set count-colors [ 0 0 0 0 ]
    set count-battle [ 0 0 0 0 ]
    set already-battle? false
  ]

  count-on-patch

end

La partie combat se situera juste après l'opération de déplacement et le décompte des couleurs par les tortues. Nous allons concentrer le code correspondant dans la procédure battle

   ask turtles [
    let my-ennemy one-of other turtles in-radius 3 ; (1)
    let count-this-battle [0 0 0 0]

    if is-turtle? my-ennemy and already-battle? = false [ ; (2)
     show (word "battle with => " my-ennemy)

      let my-count-colors count-colors ; (3)
      let ennemy-count-colors [count-colors] of my-ennemy 
      let i 0

     ; Comparaison des tableaux entre protagnonistes      

     ; Mise à jour des valeurs de tableau des protagonistes

    ]


  ] 
end

Processus de comparaison entre les tableaux des protagonistes :

foreach my-count-colors [ mc -> 
        let result-battle (battle-between (item i count-colors) (item i ennemy-count-colors)) ; (4)

        ;; remplace à la position i dans count-this-battle
        set count-this-battle replace-item i count-this-battle result-battle ; (5)

        set i i + 1
      ]

Ici on utilise une nouvelle fonction qui permet simplement de rapporter pour chaque duel de couleurs la valeur correspondante.

to-report battle-between [c1 c2]
(ifelse
    c1 > c2 [ report 1 ] ; c1 gagne c2
    c1 = c2 [ report 0 ] ; match nul
    [report -1] ; c1 perd c2
    )
end

La mise à jour des valeurs chez les protagonistes :

      ;; les deux sont marqués comme s'étant déjà battus
      set count-battle (aggregate-battle count-battle count-this-battle) ; (6)

      ask my-ennemy [ ; (7)

        set already-battle? true
        set count-battle (aggregate-battle count-battle count-this-battle)

      ]

La fonction aggregate-battle utilisé ici est strictement identique à la fonction déjà existante aggregate-colors. Le plus logique serait de les remplacer toutes deux par une seule fonction aggregate-list-by-colum plus générique.

to-report aggregate-list-by-column [o-list n-list]
  let i 0
  let ag-list [ 0 0 0 0 ] ;
  foreach n-list [ elem -> ;
    set ag-list replace-item i ag-list (max (list 0 (elem + item i o-list)))
    set i (i + 1)
  ]
  report ag-list
end