«[Figlio dell'uomo] Porgi l'orecchio e ascolta le parole di KGB
e applica la tua mente alla SUA istruzione
» Pv. 22,17

Qui si straparla di vari argomenti:
1. Il genere dei pezzi è segnalato da varie immagini, vedi Legenda
2. Istruzioni per i nuovi lettori (occasionali e non) qui
3. L'ultimo corto è questo

sabato 16 aprile 2011

Margherite vs. erbacce

Avendo un grande giardino mi capita spesso, soprattutto in primavera, di essere impegnato a tagliare l'erba. Tutto sommato è un'attività che mi piace perché richiede quel giusto impegno che permette di distrarsi e lasciare vagare i pensieri.
Un poeta che taglia l'erba sarà ispirato a comporre sonetti ma io, più prosaicamente, tendo a perdermi in riflessioni più banali.

In particolare non posso fare a meno di notare che, semplificando al massimo, ci sono due tipi d'erba in giardino: quella che rimane bassa, aderente al terreno (tipo le margherite) e quella che cresce in verticale (tipo l'erbacce).
Ovviamente io, per fare meno fatica, vorrei che il mio giardino fosse ricoperto di margherite perché così non avrei da tagliare spesso l'erba. Però, se non curo (taglio) il prato, è invece propria l'erbaccia che prende il sopravvento.
Ciò è logico: l'erbaccia, crescendo verso l'alto, “ruba” la luce alle margherite, le ricopre e lentamente le rimpiazza.
Ma se l'erba che cresce verso l'alto ha questo grande vantaggio allora perché esistono in natura erbe che rimangono radenti al terreno?
La colpa è degli erbivori: essi preferiscono mangiarsi le erbe “alte” piuttosto che quelle "basse" e così facendo riequilibrano la situazione.

Fatte queste considerazioni ho deciso di fare una simulazione al computer. Così ho creato un modello che rispecchiasse la situazione reale ma ovviamente con gigantesche semplificazioni.

Ho creato un prato virtuale diviso in quadretti. Ogni quadretto può essere coperto da margherite, da erbacce oppure essere spoglio.
Ad ogni iterazione ogni quadretto di margherite o erbacce ha una certa probabilità di propagarsi: in tal caso viene selezionato casualmente uno dei quattro quadretti adiacenti. Se il quadretto selezionato è spoglio allora le margherite o le erbacce lo colonizzeranno automaticamente; se invece tale quadretto contiene già un'erba allora c'è solo una certa probabilità che venga colonizzato dall'erba avversaria. In questo frangente l'erbaccia ha più probabilità di avere successo rispetto alle margherite.
Infine ho aggiunto una pecora!
Essa parte da una posizione casuale nel campo e, sempre casualmente, si sposta in uno dei quattro quadretti adiacenti. La pecora preferirà trasferirsi dove c'è l'erbaccia, in subordine dove ci sono margherite e, solo in mancanza di meglio, in un quadretto di terreno brullo. La mia pecora è particolarmente vorace e il quadrato da dove proviene diventa spoglio!

Con questa idea in mente mi sono guardato per un paio di giorni JavaFX e oggi ho scritto il programma per fare questa simulazione.

Se c'è interesse scriverò un post di commento dettagliato al codice ma per ora mi limito a dire poche cose. La simulazione è composta da due file: uno in Java con tutta la logica e uno in JavaFX per la parte grafica. Il codice in JavaFX probabilmente non è ben scritto (come detto me lo sono studiato in poco più di 24 ore!) quindi qualunque consiglio per migliorarlo sarà ben apprezzato.

Una parolina la meritano anche i parametri di configurazione in Engine.java: SIZE è il lato del quadrato; GFACTOR è il fattore di crescita: più è alto e minore sarà la probabilità di riproduzione dell'erbacce e delle margherite (5, ad esempio, significa 1 probabilità su 5); WEEDSTRENGHT è la probabilità che l'erbaccia riesca a colonizzare un quadrato di margherite (6 equivale al 60%, 4 al 40%, etc); DAISYSTRENGHT è l'analogo per le margherite.

Con i parametri indicati la simulazione è piuttosto equilibrata: ovviamente aumentando la dimensione del campo, il fattore di crescità oppure con WEEDSTRENGHT >> DAISYSTRENGTH l'importanza della pecora viene ridotta e l'erbaccia ha facilmente il sopravvento.

Ecco il codice:
Engine.java con la logica
package base;

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;

/**
 * Apr 14, 2011
 * @author KGB
 */
public class Engine
{
//CONSTANTS (Used just in this class)
//==========================
public final static int SIZE=10;
public final static int DAISY=1;
public final static int WEED=2;
public final static int DIRT=0;
public final static int GFACTOR=5;
public final static int WEEDSTRENGHT=6; //1 to 10
public final static int DAISYSTRENGHT=4;  //1 to 10

//VARIABLES
//=========
int[][] map=new int[SIZE][SIZE];
int pecX;
int pecY;
int pecDir;

private SecureRandom ran = null;

//CONSTRUCTORS
//============
public Engine()
{
    try
    {
        ran = SecureRandom.getInstance("SHA1PRNG");
    }
    catch (Exception e) { }
    initMap();
}

//PUBLIC: OTHERS
public void doStep()
{
        for(int x=0;x<SIZE;x++)
            for(int y=0;y<SIZE;y++)
            {
                if(quicknum(GFACTOR)!=1 || map[x][y]==DIRT)
                    continue;
                ArrayList<Square> vicini=getNeighbours(x,y);
                Collections.shuffle(vicini);
                Square s=vicini.get(0);
                if(s.val!=map[x][y])
                {
                    switch(s.val)
                    {
                        case DIRT:
                            map[s.x][s.y]=map[x][y];
                            break;
                        case DAISY:
                            if(quicknum(10)<=WEEDSTRENGHT)
                                map[s.x][s.y]=WEED;
                            break;
                        case WEED:
                            if(quicknum(10)<=DAISYSTRENGHT)
                                map[s.x][s.y]=DAISY;
                            break;
                    }
                }
            }
        ArrayList<Square> vicini=getNeighbours(pecX,pecY);
        Collections.shuffle(vicini);
        Collections.sort(vicini);
        map[pecX][pecY]=DIRT;
        Square dest=vicini.get(0);
        pecDir=findNewDir(pecX,pecY,dest.x,dest.y);
        pecX=dest.x;
        pecY=dest.y;
        //outputMap();
}

//PROTECTED
//============

//PRIVATE
//=======

private int quicknum(int nMax) { return genera(nMax,1); }

private int genera(int facce, int volte)
{
        int result = 0;
        if(facce<=0)
            return 0;
        for (int i=0; i<volte; i++)
                result += ran.nextInt(facce) + 1;
        return result;
}

private void outputMap()
{
    System.out.println("-----");
    for(int y=0;y<SIZE;y++)
    {
        for(int x=0;x<SIZE;x++)
            System.out.print(map[x][y]+" ");
        System.out.println("");
     }
    System.out.println("Pec. X,Y=("+pecX+","+pecY+") pecDir: "+pecDir);
}

private int findNewDir(int oldX, int oldY, int newX, int newY)
{
    int prod=(oldX-newX)+(oldY-newY)*2;
    int res=0;
    switch(prod)
    {
        case -2:
            res=1;
            break;
        case -1:
            res=2;
            break;
        case 1:
            res=0;
            break;
        case 2:
            res=3;
            break;
    }
    return res;
}

    private void initMap()
    {
        for(int y=0;y<SIZE;y++)
            for(int x=0;x<SIZE;x++)            
                map[x][y]=quicknum(2);            
        pecX=quicknum(SIZE)-1;
        pecY=quicknum(SIZE)-1;
        pecDir=quicknum(4)-1;
        //outputMap();
    }

    private ArrayList<Square> getNeighbours(int x, int y)
    {
        ArrayList<Square> lst=new ArrayList<Square>(4);
        if(x>0)
            lst.add(new Square(map[x-1][y],x-1,y));
        if(x<SIZE-1)
            lst.add(new Square(map[x+1][y],x+1,y));
        if(y>0)
            lst.add(new Square(map[x][y-1],x,y-1));
        if(y<SIZE-1)
            lst.add(new Square(map[x][y+1],x,y+1));
        return lst;
    }

//GETTERS
//============
    public int[][] getMap()
    {
        return map;
    }

    public int getPecX()
    {
        return pecX;
    }

    public int getPecY()
    {
        return pecY;
    }

    public int getPecDir()
    {
        return pecDir;
    }

//INNER CLASSES
//=============
class Square implements Comparable<Square>
{
    int val;
    int x;
    int y;

    @Override public int compareTo(Square s2)
    {
        return s2.val-this.val;
    }

    private Square(int v)
    {
        val=v;
    }

    private Square(int pv, int px, int py)
    {
        val=pv;
        x=px;
        y=py;
    }
}
}

e Simulator.fx per la grafica...
package base;

import javafx.stage.Stage;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.layout.Tile;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.animation.transition.TranslateTransition;
import javafx.animation.transition.RotateTransition;
import javafx.animation.transition.ParallelTransition;

/**
 * @author KGB
 */

def imgWeed = Image{url: "{__DIR__}resources/weed.png"};
def imgDaisy = Image{url: "{__DIR__}resources/daisy.png"};
def imgRabbit = Image{url: "{__DIR__}resources/wererabbit.png"};
def imgDirt = Image{url: "{__DIR__}resources/dirt.png"};

var engine = new Engine();
var staticField = engine.getMap();

Timeline {
    repeatCount: Timeline.INDEFINITE
    keyFrames: [KeyFrame{time: 1.5s action:function(){
        engine.doStep();
        staticField = engine.getMap();
        sequence = for (row in staticField)
                                for(square in row)
                                    square;
        campo = for (ele in sequence)
                                ImageView{
                                    image: if(ele==Engine.WEED) then imgWeed else if(ele==Engine.DAISY) then imgDaisy else imgDirt
                                }
        pecoraPosX = engine.getPecX();
        pecoraPosY = engine.getPecY();
        pecoraDir = engine.getPecDir();
        angle= if(pecoraDir-oldPecoraDir==-3) then 1 else if(pecoraDir-oldPecoraDir==3) then -1 else (pecoraDir-oldPecoraDir);
        pt.playFromStart();
        }},
        KeyFrame{time: 2s action:function(){
                pecora.translateX=10 + pecoraPosY*32;
                pecora.translateY=10 + pecoraPosX*32;
                pecora.rotate=90*pecoraDir;
                oldPecoraDir=pecoraDir;
            }}
        ]
}.play();

var sequence = for (row in staticField)
                                for(square in row)
                                    square;

 var campo = for (ele in sequence)
                                ImageView{
                                    image: if(ele==Engine.WEED) then imgWeed else if(ele==Engine.DAISY) then imgDaisy else imgDirt
                                }

var pecoraPosX = engine.getPecX();
var pecoraPosY = engine.getPecY();
var pecoraDir = engine.getPecDir();
var oldPecoraDir = pecoraDir;

var pecora = ImageView{
                         image: imgRabbit
                         translateY: 10 + pecoraPosX*32
                         translateX: 10 + pecoraPosY*32
                         rotate: 90*pecoraDir;
    };

    var angle = 0;

    var rotTrans = RotateTransition
    {
        duration: .5s
        node: pecora
        byAngle: bind angle*90;
        autoReverse: false
    }

    var movTrans = TranslateTransition {
 duration: .5s
 node: pecora
 toX: bind 10 + pecoraPosY*32
        toY: bind 10 + pecoraPosX*32
        autoReverse: false
}

var pt = ParallelTransition {content: bind [rotTrans, movTrans]}

Stage {
    title: "Margherite Vs. Erbacce"
    scene : Scene {
    width: Engine.SIZE*32+20
    height: Engine.SIZE*32+20
    fill: Color.CORAL
    content:  [Tile {
                          tileWidth: 40
                          nodeHPos: HPos.LEFT //Allinea gli elementi a sinistra
                          padding: Insets{top: 10 left: 10}
                          columns: engine.SIZE
                          rows: engine.SIZE
                          vgap: 0
                          hgap: 0
                          content: bind campo
                     }, //Tile
                     pecora
                     ]
           } // Scene
}//Stage

Ecco l'applet col programmino funzionante. Credo ci voglia Java 1.6. Su Firefox non mi riesce di configurare Java ma su Chrome non ci sono problemi. Non l'ho testato su IE...
<script src="http://dl.javafx.com/1.3/dtfx.js"></script>
<script>
    javafx(
        {
              archive: "http://www.greaterminds.net/blog/DaisyVsWeed/DaisyVsWeed.jar",
              draggable: true,
              width: 340,
              height: 340,
              code: "base.Simulator",
              name: "DaisyVsWeed"
        }
    );
</script>

Edited: Apparentemente ci sono molti problemi per visualizzare questo applet. Di sicuro richiede il runtime della versione 1.6 di java (e l'installazione del relativo plugin su FF di linux è tutt'altro che automatica...) poi suppongo di non aver configurato bene i relativi files col risultato che molti browser vengono confusi (ipotizzo io). Al momento l'applet risulta visibile e funzionante su Chrome+linux e Chrome+Mac se qualcuno ha altre informazioni mi faccia sapere!
Inoltre ho notato un piccolo bug nel codice: se qualcuno oltre me lo nota allora lo correggerò altrimenti no! (so di cosa si tratta ma non ho voglia di tornarci sopra...)
Edited 2: Ora mi funziona anche su FF con Linux e penso che sia importante che il plugin usato da FF non sia "IcedTea" ma quello chiamato "Java(TM) Plug-in 1.6.0_24" (ma forse questo è un problema specifico di ubuntu: vedi qui)
Edited 3:Funziona anche su FF 4 Beta e Windows 7 (plugin Java(TM) Platform SE 6 U24)
Edited 4:Funziona anche su FF 4 e XP (Java 1.6.0_24 e plugin java di oracle (non icedtea))
Edited 5:Funziona anche su Chrome + Vista. In realtà funziona praticamente sempre a condizione che si abbia l'ultima versione di Java e su FF non venga usato il plugin IcedTea...

Nessun commento:

Posta un commento