cg-Cad

Lisp »Tips 'n Tricks »Automi »1 »2 »3

Life

Il gioco ("Game of Life", modello di J.H. Conway, 1970) simula l’evoluzione di una popolazione, in termini di nascita e morte di singole entità (nel nostro caso punti bidimensionali).
Le condizioni che incidono circa la nascita e la morte sono la quantità di elementi adiacenti che generano uno stato di solitudine o di sovraffollamento. Ogni elemento vive e muore all'interno di una cella quadrata; l'insieme delle celle formano una mappa di N righe e M colonne (nel lisp N=M).

Le Regole del Gioco
Ogni cella è un automa a due stati, accesa o spenta, viva o morta, e risente dello stato di ogni cella del proprio intorno in modo tale che:

  • se una cella ospita un automa vivo, questo continuerà a vivere anche nella generazione successiva solo se 2 o 3 delle otto celle ospitano automi vivi;
  • se una cella vuota ha tre automi adiacenti vivi, allora ospiterà un nuovo automa;
  • se un automa ha meno di 2 automi adiacenti vivi o più di 3 vivi, esso morirà per inedia o sovrappopolazione.

Le regole del Gioco sono tradotte nel seguente algoritmo:

  • un punto V che ha due o tre vicini V con l'iterazione successiva resta V;
  • un punto F diventa V con l'iterazione successiva se ha tre vicini V;
  • un punto V che non ha nessun vicino V o ne ha al massimo solo uno con l'iterazione successiva diventa F;
  • un punto V che ha quattro o più vicini V con l'iterazione successiva diventa F;
  • tutte le V e le F prendono posto esattamente nello stesso istante: i punti prossimi-F non possono partecipare alla prossima-V di altri punti e i punti prossimi-V non possono partecipare alla prossima-F di altri punti.

Il punto 5 dell'algoritmo vuol dire che le liste devono essere 2.

Come primo passo si inizializza una lista di n elementi; ogni elemento della lista è una lista che contiene una chiave di ricerca, due numeri (le coordinate x e y del punto, l'entità che vive nella cella) e un valore di verità (F per falso, V per vero).

Come secondo passo si deve cambiare il valore di verità di alcune liste della mappa, da F (falso) a V (vero) in modo casuale; cioè alla domanda "c'è vita?" in alcuni casi si risponde sì e si scrive V per vero.

Con gli array l'implementazione dell'algoritmo è facile, ma visto che AutoLISP non gli usa come si contano i vicini in una lista?

Consideriamo una lista L di 9 elementi e ogni elemento di L è una lista e (numero numero lettera), quindi scriviamo la lista L come se fosse una matrice:
(
(0 0 "F")(0 1 "F")(0 2 "V")
(1 0 "V")(1 1 "F")(1 2 "F")
(2 0 "F")(2 1 "V")(2 2 "F")
)
Con la funzione (nth i L) e con l'aiuto di (while) possiamo scorrere tutta la lista e leggere tutti gli elementi e, e con le funzioni (car) estrarre le coordinate del punto (x,y) e il valore di verità (F,V).
Ogni elemento e si guarda intorno e se l'elemento, come ad esempio (0 0 "F"), è sul confine del campo la ricerca dei vicini può causare errori e di questo si deve tenere conto.

(-? -? ?)(-? ? ?)(-? +? ?)
(? -? ?)(0 0 "F")(0 1 "F")
(+? -? ?)(1 0 "V")(1 1 "F")

Questo è lo schema della ricerca di V,F:

colonna-1colonnacolonna+1
riga-1***
riga*(x,y,c)*
riga+1***

Cercare di individuare le liste in base alle coordinate x,y del punto non è pratico; la funzione (assoc e L) permette di individuare una lista (contenuta in una lista) con precisione, è necessario però inserire all'interno di ogni e una chiave di ricerca (che può essere un numero o una parola o una combinazione alfanumerica), nel nostro caso sarà un valore numerico (incrementabile in modo automatico) sufficentemente alto, per non confonderlo con le coordinate del punto, ad esempio 1000.

Una soluzione per i punti di confine è contare i valori di verità (F,V), salvare il risultato in una variabile contatore (conta_fv) e ignorare i punti con conta_fv<8. Nell'esempio il punto (1 1) è circondato da 8 punti di confine che non cambiano di stato ma contribuiscono al cambiamento di stato di (1 1).

1000
F
(x,y)=(0 0)
1001
F
(x,y)=(0 1)
1002
V
(x,y)=(0 2)
1003
V
(x,y)=(1 0)
1004
F
(x,y)=(1 1)
1005
F
(x,y)=(1 2)
1006
F
(x,y)=(2 0)
1007
V
(x,y)=(2 1)
1008
F
(x,y)=(2 2)
  • Test: stampa il numero di vicini V per ogni punto di una mappa 8x8 - esclusi i punti di confine a Nord e a Sud.

Il terzo passo consiste nello scrivere il codice delle regole, facendo attenzione al punto 5.

  • Test: mostra l'evoluzione del campo di gioco dalla generazione 0 alla generazione 1.

E infine l'ultimo passo consiste nello scrivere una procedura per disegnare i punti in autocad, ad esempio come centri di cerchi di raggio=0.5 e inserire un'istruzione while che chiede al giocatore se vuole continuare a giocare.

;|
  LF.lsp (C) 2004 by Claudio Piccini.
  27 Aprile 2004 (versione 1.0)
  www.cg-cad.com

  Il Gioco della Vita
  (The Game of Life di J.H. Conway)
|;

;|
  estrae un numero casuale 
  da 0 a 63
  Il seme e' inizializzato con la variabile DATE
  in questo modo la serie di numeri casuali
  e' diversa in ogni sessione di gioco.
|; 
(defun rn ( / m b c) 
 (if (not sd) (setq sd (getvar "DATE")))
 (setq m 65521 b 15937 c 33503 sd 
  (rem (+ (* b sd) c) m)
 )
 (setq sd (* (/ sd m) 64))
)
;|
  inizializza la mappa
  come una griglia di punti MxN = 8x8
  memorizzati in liste di associazioni:
  (chiave x y valore_verita'="F")
|; 
(defun go_life (mXn / key) 
 (setq key 1000)
 (while (<= M mXn)
  (setq N 0)
  (while (<= N mXn)
   (setq mappa (append mappa 
                (list (list key M N "F"))
               )
   )
   (setq key (1+ key))
   (setq N (1+ N))
  )
  (setq M (1+ M))
 )
 (setq mappa mappa)
)
;|
  inserisce punti "V" nella mappa
  in modo casuale
|;
(defun random_life (mappa mXn / 
                    z i k key x y 
                    cellaF cellaV
 )
 (setq z 25) ; numero punti "V"
 (setq i 0)
 (while (< i z)
  (setq k (fix (rn)))
  (setq key (+ 1000 k))
  (setq cellaF (assoc key mappa))
  (setq x (cadr cellaF))
  (setq y (caddr cellaF))
  (setq cellaV (list key x y "V"))
  (setq mappa (subst cellaV cellaF mappa)) 
  (setq i (1+ i))
 )
 (setq mappa mappa)
)
;|
  contaVicini conta i "V"
  esclude dalla conta i punti di confine
  della mappa a Nord e a Sud:
  la mappa puo' essere vista come un
  cilindro aperto sul piano.
|;
(defun contaVicini (e L / key conta_fv conta_V)
 (setq key (car e))
 (setq conta_fv 0)
 (setq conta_V 0) ;conta i "V"
 (if (assoc (- key 9) L)
  (progn
   (if (= (cadddr (assoc (- key 9) L)) "V")
    (setq conta_V (1+ conta_V))
   )
   (setq conta_fv (1+ conta_fv))
  )
 )
 (if (assoc (- key 8) L)
  (progn
   (if (= (cadddr (assoc (- key 8) L)) "V")
    (setq conta_V (1+ conta_V))
   )
   (setq conta_fv (1+ conta_fv))
  )
 )  
 (if (assoc (- key 7) L)
  (progn
   (if (= (cadddr (assoc (- key 7) L)) "V")
    (setq conta_V (1+ conta_V))
   )
   (setq conta_fv (1+ conta_fv))
  )
 )  
 (if (assoc (- key 1) L)
  (progn
   (if (= (cadddr (assoc (- key 1) L)) "V")
    (setq conta_V (1+ conta_V))
   )
   (setq conta_fv (1+ conta_fv))
  )
 )  
 (if (assoc (+ key 1) L)
  (progn
   (if (= (cadddr (assoc (+ key 1) L)) "V")
    (setq conta_V (1+ conta_V))
   )
   (setq conta_fv (1+ conta_fv))
  )
 )  
 (if (assoc (+ key 7) L)
  (progn
   (if (= (cadddr (assoc (+ key 7) L)) "V")
    (setq conta_V (1+ conta_V))
   )
   (setq conta_fv (1+ conta_fv))
  )
 )  
 (if (assoc (+ key 8) L)
  (progn
   (if (= (cadddr (assoc (+ key 8) L)) "V")
    (setq conta_V (1+ conta_V))
   )
   (setq conta_fv (1+ conta_fv))
  )
 )  
 (if (assoc (+ key 9) L)
  (progn
   (if (= (cadddr (assoc (+ key 9) L)) "V")
    (setq conta_V (1+ conta_V))
   )
   (setq conta_fv (1+ conta_fv))
  )
 )  
 (if (= conta_fv 8)
  (setq conta_V conta_V)
  (setq conta_V -1)
 )
) 
;|
  Disegna n cerchi
  con centro in (chiave M N "V")
|;
(defun disegna_mappa (mappa / i x y valore)
 (setq i 0)
 (while (< i (length mappa))
  (setq x (cadr (nth i mappa)))  ; coordinata X del punto
  (setq y (caddr (nth i mappa))) ; coordinata Y del punto
  (setq valore (cadddr (nth i mappa))) ; valore V,F
  (if (= valore "V")
   (progn
    (command "_circle" (list x y) 0.5)
    (setq ent (append ent (list (entlast))))
   )
  )
  (setq i (1+ i))
 )
 (setq ent ent)
)

(defun c:lf ( / snapp sd M N mXn risp ent
                mappa mappa2 i j conta 
                key x y valore mappa2 lista
 )
 (setvar "cmdecho" 0)
 (setq snapp (getvar "osmode"))
 (command "_osnap" "_non")
 (setq M 0 N 0 mXn 7)
 ; inizializza la mappa
 (setq mappa (go_life mXn))
 ; inserisce casualmente punti V
 (setq mappa (random_life mappa mXn))
 ;|
   legge la lista mappa e salva la nuova
   configurazione in mappa2
 |;
 (while
  (progn
   (setq i 0)
   (while (< i (length mappa))
    (setq conta 
     (contaVicini (nth i mappa) mappa)
    )
    (setq key (car (nth i mappa))) ; chiave di ricerca
    (setq x (cadr (nth i mappa)))  ; coordinata X del punto
    (setq y (caddr (nth i mappa))) ; coordinata Y del punto
    (setq valore (cadddr (nth i mappa))) ; valore V,F
    (cond
     ((= conta -1) ; punto di confine
      (setq lista (list key x y valore))
      (setq mappa2 (append mappa2 (list lista)))
     )
     ((= conta 0) ; se V allora diventa F
      (setq lista (list key x y "F"))
      (setq mappa2 (append mappa2 (list lista)))
     )
     ((= conta 1) ; se V allora diventa F
      (setq lista (list key x y "F"))
      (setq mappa2 (append mappa2 (list lista)))
     )
     ((= conta 2) ; il valore resta immutato
      (setq lista (list key x y valore))
      (setq mappa2 (append mappa2 (list lista)))
     )
     ((= conta 3) ; se V resta V, se F diventa V
      (setq lista (list key x y "V"))
      (setq mappa2 (append mappa2 (list lista)))
     )
     ((>= conta 4) ; se V diventa F
      (setq lista (list key x y "F"))
      (setq mappa2 (append mappa2 (list lista)))
     )
    )
    (setq i (1+ i))
   )
   (setq mappa mappa2)
   (setq mappa2 nil)
   (setq ent (disegna_mappa mappa))
   (initget "S s N n")
   (setq risp 
    (getkword "\nNuova generazione? (S/N) [s]: ")
   )
   (cond   
    ((or (= risp "N")(= risp "n")) 
     nil
    )
    ((or (= risp "S")(= risp "s")(= risp nil))
     (if (/= ent nil)
      (progn
       (setq j 0)
       (while (< j (length ent))
        (entdel (nth j ent))
        (setq j (1+ j))
       )
       (setq ent nil)
      )
     )
     T
    )
   )
  )
 )
 (setvar "osmode" snapp)
 (command "_redraw")
 (setvar "cmdecho" 1)
 (princ)
)
;;;eof

01234

Con questa modifica al lisp la vita diventa più difficile:

...
(cond
 ((= conta -1) ; se punto di confine allora F
  (setq lista (list key x y "F"))
  (setq mappa2 (append mappa2 (list lista)))
 )
 ...

012345

Per approfondire:
Life... il gioco della vita di Conway
Il gioco della vita: algoritmo

Lisp »Tips 'n Tricks

Ultimo Aggiornamento_Last Update: 27 Aprile 2004