La programmazione in Awk II: Life in a Shell

Il gioco Life fu inventato negli anni ’70 dal prolifico matematico John H. Conway (vedi [5] per la sua biografia) ed è diventato famoso dopo la pubblicazione di Martin Gardner nella sua rubrica di matematica amatoriale sulla rivista Scientific American [1,2]. Il gioco è basato sugli automi cellulari concepiti da Konrad Zuse e Stanislaw M. Ulam all’inizio degli anni ’50, e poi adottati da John von Neumann per il suo studio sugli automi auto-replicanti [2,3]. Un automa cellulare è composto da unità (celle) interagenti disposte in una griglia quadrataIl sistema si evolve in cicli di vita in cui ogni cella cambia stato e nuove celle possono nascere e altre possono sopravvivere o, eventualmente, morire. Lo stato di ogni cella nel ciclo successivo è definito dall’interazione con le celle adiacenti in base a delle regole. L’interazione avviene con i primi vicini di ciascuna cella. Come mostrato nella Figura 1, è possibile utilizzare due tipi di intorni (cerchi) della cella centrale. Il gioco Life usa il tipo di proposto da Moore. 

neumannmoore.jpg
Figura 1: Two tipi di intorni: a sinitra quello cosiddetto di Moore e a destra quello di Neumann.

Life in a Shell

Tempo fa, scrissi un programma di ennesima vita in linguaggio awk per visualizzare l’evoluzione di Life su uno schermo terminale (vedi Appendice). Questo programma è solo un esercizio per esplorare altre caratteristiche del linguaggio awk. È limitato nella grafica e nelle prestazioni rispetto ai molti programmi esistenti del gioco della vita, ma è ancora uno strumento utile per esplorare questo argomento affascinante. Inoltre, per coloro che hanno buoni ricordi della rivoluzione del microcomputing degli anni ’80, dà anche una sensazione di retro-computing!  

Il programma legge configurazioni del gioco in formato RLE (run-length encoding). Per eseguire il tipo di programma 

awk -f Life.awk RLEfile

Quindi ottiene la dimensione della finestra del terminale (linee X colonne) usando la funzione TermSize () dal comando della shell tput. La dimensione del terminale viene utilizzata per impostare le dimensioni delle matrici utilizzate dal gioco. La funzione GenFrame () prepara la cornice che si affaccia sull’arena del gioco. Il tipo di vicini utilizzati dal gioco è impostato usando la funzione Neighbor (1), il parametro 1 è per il tipo di Moore. La funzione RLEreader legge i contenuti del file RLE e aggiunge la configurazione iniziale della cella al centro della matrice di gioco. Il programma non legge il tipo di regola poiché utilizza solo le regole di Conway originali (B3 / S23). Pertanto, si prega di verificare il tipo di regola nel file RLE prima di utilizzarlo anche se non è difficile implementare anche altri tipi di regole leggendole dal file RLE e implementando nella funzione applyRule (). Il programma quindi stampa un menu che consente di scegliere di eseguire il gioco in modo continuo impostando il numero di generazioni o, in modo interattivo, fornendo manualmente il numero di passaggi da avanzare. Il menu viene impostato utilizzando il ciclo do while (implementato in gnu awk) e ottenendo l’input dalla tastiera utilizzando il comando getline ch <“-“. L’animazione viene eseguita stampando la matrice del gioco che viene aggiornata in base alle regole del gioco ad ogni ciclo. I colori e la cancellazione dello schermo sono ottenuti usando caratteri ESC. In Mac OSX, è possibile aumentare o ridurre la dimensione dei caratteri utilizzati nel terminale con i tasti Cmd +/- , rispettivamente. Riducendo la dimensione dei caratteri, si puo’ aumentare il numero di linee e colonne ingrandendo in questo modo la finestra del terminale. Questo produrrà un campo di gioco più ampio per una grande configurazione iniziale del gioco, ma si pagherà il prezzo di una riduzione della velocità del programma. Se la variabile PBC nel blocco BEGIN del programma è impostata su 1, il gioco viene eseguito con condizioni al contorno periodiche. In questa modalità, il gioco si svolge sulla superficie continua di un toroide. 

Il programma è fornito così com’è senza garanzie di funzionamento corretto. Siete pertanto invitati a provarlo e se trovate dei bachi o lo migliorate, inviatemi la nuova versione!

Nelle figure che seguono ho riportato alcuni esempi di configurazioni iniziali del gioco molto note. Gli esempi sono generati usando il file RLE ottenuto dal sito web Conwaylife sopra citato. Buon divertimento! 

GosperGlider.mov
Esempio 1: Gospel glider gun.
Tumbler.mov
Esempio 2: Tumbler.
Harvester.mov
Esempio 3: harvester.
CheshireCat.mov
Esempio 4: Cheshire cat.
Period2osc.mov
Esempio 5: Period 2 oscillators.
Period5osc.mov
Esempio 6: Period 5 oscillators.
Pentadecathon_Synth.mov.gif
Esempio 7: Pentadechalon Synthesizer.

BIBLIOGRAFIA

  1. M. Gardner. The fantastic combination of John Conway’s new solitaire game “life”.”. Scientific American, October 1970.
  2. M. Gardner. On cellular automata, self-reproduction, the Garden of Eden and the game of “life”. Scientific American, Feb. 1971.
  3. M. Schroeder. Fractal, Chaos, Power Laws: minutes from an infinite paradise., Dover Ed., 1991.
  4. S. Wolfram. A new kind of Science. Wolfram Media Inc.
  5. S. Roberts. Genius at Play. Bloomsbury Publishing, 2015.

APPENDICE

In questa appendice viene fornito il programm usato per generare le figure di questo articolo. Il programma è stato sviluppato e provato su Mac OSX usando gawk.  Per usarlo, copiatelo in un file di testo chiamato, ad esempio, Life.awk  e poi fatelo girate usando il comando: gawk -f Life.awk.

#
# this subroutine read a RLE file and
# assign the rule of the game
#
#
# (c) 2014 Roccatano
#

BEGIN {
    PBC = 0
    TermSize()
    GenFrame()
    Neighbor(1)
    print "AWK LIFE" 
    fname = ARGV[1]
    print "Read the initial configuration from", fname 
    RLEreader(fname)
    sx = int(maxcols/2 - x/2)
    sy = int(maxlines/2 - y/2)
    for (i=0;i<y;i++) {
        for (j=0;j<x;j++) {
            cmt[sy+i,sx+j]=mat[i,j]
        }
    }
#
# Run the game
#
    sx = maxcols
    sy = maxlines
    do {
        system("clear")
        print red("Configuration name:"),Name
        green("=======================================================\n")
        print blue("1) Step-by-step")
        print blue("2) Continuous")
        print blue("3) Exit     ")
        green("=======================================================\n")
        red("Select a visualization method  (0 to exit):  \n")
        getline choice < "-"
        if (choice == 1) {
            cy=1
            nsteps =1
            system("clear")
            do {
                for (n=1;n<=nsteps;n++) {
                    cGeneration(sx,sy)
                    PrintGame()
                    cy++
                }
                red(" |Insert the number of steps to advance or Enter for one (0 to Menu)")
                getline ch < "-"
                if (ch == ""  ) {ch=1}
                nsteps= ch
            } while (ch !=0) 
        } else if (choice ==2)  {
            red("input the number of steps:  \n")
            getline nsteps < "-"
            system("clear")
            for (cy=1;cy<nsteps;cy++) {
                cGeneration(sx,sy)
                PrintGame()
            }
        }
    } while (choice >0 && choice  <3)

}

function cGeneration(sx,sy) {
#
# This function calculates apply
# the automata rules to generate a new state
# The functio use borders to deal with the 
# period conditions. The frames around the 
# central square are exchanged according to 
# the type of neighborhood adopted.
#
# sx,sy = x,y sizes of the matrix
#
# (c) Roccatano 2014
######################################################

    for (i=1;i<sy;i++) {
        for (j=1;j<sx;j++) {
            applyRule()      
        }
    }
    swapMatrix()
}

function applyRule() {
#
# It applies the automata rules to the single cell
# i,j: current cell position
# px,py: calculate position of the neighbour cells.
# cmt[] : current matrix
# nmt[] : new matrix
# (c) 2014 Roccatano
##################################################


# Calculate the status of the current cell
    val =0
    for (k=0;k<8;k++) {
        px = j + nx[k]
        py = i + ny[k]
        val+=cmt[py,px]+0
    }
    cv = cmt[i,j]
    ncv = cv

#
# Apply rules
#
    if (cv == 0 && val == 3) {ncv =1}
    if (cv ==1 && (val < 2 || val >3)) { ncv =0 }



# update matrix    
    nmt[i,j]=ncv

# Store the status of the current cell for assigning a colour
    if (ncv> 0) {nei[i,j]=val}

}

function swapMatrix(){
#
# Swap the new matrix with the current one
#
# (c) 2014 Roccatano
##################################################

    for (i=1;i<sy;i++){
        for (j=1;j<sx;j++){
            cmt[i,j]=nmt[i,j]
        }
    }
    if (PBC == 1) {
#    
# copy the borders
#
        for (i=1;i<sx;i++){
            cmt[0,i]=nmt[sy-1,i]
            cmt[sy,i]=nmt[1,i]
        }
        for (i=1;i<sy;i++){
            cmt[i,0]=nmt[i,sx-1]
            cmt[i,sx]=nmt[i,1]
        }
#
# copy corners
#
        cmt[0,0]=nmt[sy-1,sx-1]
        cmt[sy,sx]=nmt[1,1]
        cmt[0,sx]=nmt[1,sx-1]
        cmt[sy,0]=nmt[sy-1,1]
    }
}

function Neighbor(ineig) {
# 
#  Two types of boundary      
#  ineig = 0 : von Neumann
#          1 : Moore
#  The central cell (x) with a 
#  Moore   (M) boundaries has 8 neighborhood cells   MNM  012
#  Neumann (N) boundaries has 4 neighborhood cells   NxN  3 4
#                                                    MNM  567
#
# (c) Roccatano 2014
#############################################################
#

    if (ineig ==1) {
        nx[0]=-1; ny[0]=-1
        nx[1]=0;  ny[1]=-1
        nx[2]=1;  ny[2]=-1 
        nx[3]=-1; ny[3]=0  
        nx[4]=1;  ny[4]=0  
        nx[5]=-1; ny[5]=1  
        nx[6]=0;  ny[6]=1  
        nx[7]=1;  ny[7]=1  
    } else {
        nx[0]=0;  ny[0]=-1
        nx[1]=-1; ny[1]=0  
        nx[2]=1;  ny[2]=0  
        nx[3]=0;  ny[3]=1  

    }
}

function GenFrame() {
########################################################
# Generate the frame of the game
#
# (c) 2014 Roccatano
#
########################################################

    lines -=2 
    cols-=10
    maxlines=lines-lines%4
    maxcols=cols-cols%4
    print maxlines,maxcols
    for (k=0;k<=maxlines;k++) {
        for (i=0;i<=maxcols;i++) {
            if (k!=0 && k!=maxlines && k%4!=0) {
                matgrid[k,i]=" "
                if (i==0 || i==maxcols && i%4==0) {
                    matgrid[k,i]="│"
                }
            }
            else {
                if (k==0 || k==maxlines) {
                    if (i%4!=0 ) {
                        matgrid[k,i]="─"
                    }
                    else {
                        if (k==0 ) {
                            matgrid[k,i]="┬"
                        }else{
                            matgrid[k,i]="┴"
                        }
                    }
                }
                else {
                    if (i%4!=0 ) {
                        matgrid[k,i]=" "
                    }
                    else {
                        matgrid[k,i]=" "
                        if (i==0 || i==maxcols) {
                            if (i==0 ) {
                                matgrid[k,i]="├"
                            }else{
                                matgrid[k,i]="┤"
                            }
                        }

                    }

                }
            }
        }
    }
    matgrid[0,0]="┌"
    matgrid[0,maxcols]="┐"
    matgrid[maxlines,0]="└"
    matgrid[maxlines,maxcols]="��"
}

########################################################### 
# Print the the game on the terminal 
#
# (c) Roccatano 2014
###########################################################
function PrintGame() {

    print "\033[H"
    for (k=0;k<=maxlines;k++) {
        for (i=0;i<=maxcols;i++) {
            val =matgrid[k,i]
            if (k>0 && k<maxlines && i>0 && i< maxcols) {
                val =cmt[k,i]
            }
            if (val=="" || val == "0") {val = " "}
            if (val=="1" ) {val = "o"}
            if (val == "o") {
                if (nei[k,i]==2) {printf "\033[1;31m%s\033[0m",val}
                    if (nei[k,i]==3) {printf "\033[1;32m%s\033[0m",val}
                    }
                    else {
                        printf "%s",val
                    }
                }
                printf "\n"
            }
            printf "\033[1;32m Name: %-30s  \033[0m Size:(%d,%d)   Generation: %d",Name,lines,cols,cy 
}

function TermSize() {
            cmd="tput lines"
            cmd | getline lines
            lines=lines-2
            cmd="tput cols"
            cmd | getline cols
}

###########################################################
# this subrouting read a RLE file and
# assign the rule of the game
#
#
# (c) 2014 Roccatano
###########################################################
#

function RLEreader(fname) {
    syv["o"]=1
    syv["b"]=0
    nva=""
    row=0
    col=0
    while (getline < fname > 0) {
        if (NF > 0) {
            if ( substr($1,1,1) != "#") {
                if ($1 == "x") {
                    x= substr($3,1,length($3)-1)+0
                    y= substr($6,1,length($6)-1)+0
                    print x,y
# Rule
                    split($9,tt,"/")
                    print tt[1], tt[2]
                } else {
                    count = 0 
                    for (i=1;i<=length($0);i++) {
                        sy=substr($1,i,1)

                        if (sy ~  /o|b/ ) { 
                            va = syv[sy] 
                            # print sy,count,nva,va
                            if (count !=0) {
                                for (j=1;j<=count;j++) {
                                    mat[row,col]=va
                                    col+=1
                                }
                                count =0
                                nva=""
                            }else {
                                mat[row,col]=va
                                col++
                            }
                        }
                        if (sy ~/[0-9]/){
                            nva = nva sy
                            count = nva*1  
                            #              print count,nva
                        }
                        if (sy == "$" || sy == "!") { 
                            #                 print row, substr($1,1,i)
                            if (col<y-1) {
                                diff=y-col               
                                for (k=1;k<diff;k++) {
                                    mat[row,col]=0
                                    col++
                                }
                            }
                            row++
                            count =0
                            nva=""
                            col=0
                        }
                    }
                }
            }else {
                if ($1=="#N") {Name = substr($0,3)}
            }
        }
    }
    close (fname)
    if (row<x-1) {
        diff=x-row
        for (j=0;j<diff;j+=1) {
            col=0
            for (k=0;k<y;k++) {
                col++
                mat[row,col]=0
            }
            row++
        }
    }

}
function red(s) {
    printf "\033[1;31m" s "\033[0m"
}

function green(s) {
    printf "\033[1;32m" s "\033[0m"
}

function blue(s) {
    printf "\033[1;34m" s "\033[0m"
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.