Search code examples
javaconways-game-of-lifeagent-based-modelingmason-abm

Cellular Automaton Not Woorking


Update: Just to specify, depending on how I change the rules I can set it so within a couple of generations all cells are either permanently alive or dead. I have checked this by echoing statements to console. HOWEVER, this doesn't reflect in the GUI which shows all cells as always the same color.

I am trying to implement a simple Cellular Automaton to replicate the game of life. This uses the MASON library. My three classes:

Cell.java

package sim.app.gol;

import sim.engine.SimState;
import sim.engine.Steppable;
import sim.field.grid.IntGrid2D;
import sim.util.IntBag;

public class Cell implements Steppable {

    public IntGrid2D grid = new IntGrid2D(0,0);

    public void step(SimState state) {

        Matrix matrix = (Matrix) state;

        grid.setTo(matrix.matrix);

        for(int x = 0; x < grid.getWidth(); x++) {

            for(int y = 0; y < grid.getHeight(); y++) {

                IntBag nei = grid.getMooreNeighbors(x, y, 2, 0, false, new IntBag(), new IntBag(), new IntBag());

                int count = 0;

                for(int i = 0; i < nei.size(); i++) {

                    count += nei.get(i);

                }

                int currentState = grid.get(x, y);

                if(currentState == 0) {

                    if(count > 3)
                        matrix.matrix.set(x, y, 1);


                } else if(currentState == 1) {

                    matrix.matrix.set(x,y,0);


                }

            }


        }




    }

}

Matrix.java

package sim.app.gol;

import ec.util.MersenneTwisterFast;
import sim.engine.SimState;
import sim.field.grid.IntGrid2D;

public class Matrix extends SimState {

    public final int HEIGHT = 10;
    public final int WIDTH = 10;
    public IntGrid2D matrix = new IntGrid2D(HEIGHT, WIDTH);
    public final int NUM_CELLS = 80;

    public Matrix(long seed) {

        super(seed);

    }

    public void start() {

        super.start();

        // Utils for random number generator
        MersenneTwisterFast g = new MersenneTwisterFast();

        // We set everything to 0, no cells are active
        matrix.setTo(0);

        // Populating
        for(int i = 0; i < NUM_CELLS; i++) {

            int x = 0;
            int y = 0;

            // We don't want to mark as 'active' a cell that is already active
            do {
                x = g.nextInt(WIDTH);
                y = g.nextInt(HEIGHT);
            } while(matrix.get(x, y) == 1);

            matrix.set(x, y, 1);


        }

        schedule.scheduleRepeating(new Cell());


    }

    public static void main(String[] args) {


        doLoop(Matrix.class, args);
        System.exit(0);

    }



}

MatrixWithUI.java

package sim.app.gol;

import java.awt.Color;

import javax.swing.JFrame;

import sim.app.students.Students;
import sim.display.Console;
import sim.display.Controller;
import sim.display.Display2D;
import sim.display.GUIState;
import sim.engine.SimState;
import sim.portrayal.continuous.ContinuousPortrayal2D;
import sim.portrayal.grid.ObjectGridPortrayal2D;
import sim.portrayal.grid.ValueGridPortrayal2D;
import sim.portrayal.simple.OvalPortrayal2D;

public class MatrixWithUI extends GUIState {

    public Display2D display;
    public JFrame displayFrame;
    public ValueGridPortrayal2D matrixPortrayal = new ValueGridPortrayal2D();

    public static void main(String[] args) {

        MatrixWithUI mwu = new MatrixWithUI();
        Console c = new Console(mwu);
        c.setVisible(true);

    }

    public void start() {

        super.start();
        setupPortrayals();

    }

    public void load(SimState state) {

        super.load(state);
        setupPortrayals();

    }

    public void setupPortrayals() {

        Matrix matrix = (Matrix) state;
        matrixPortrayal.setField(matrix.matrix);
        matrixPortrayal.setPortrayalForAll(new OvalPortrayal2D());

        display.reset();
        display.setBackdrop(Color.white);

        display.repaint();

    }

    public void init(Controller c) {

        super.init(c);
        display = new Display2D(600,600,this);
        display.setClipping(true);

        displayFrame = display.createFrame();
        displayFrame.setTitle("Schoolyard Display");
        c.registerFrame(displayFrame);
        displayFrame.setVisible(true);
        display.attach(matrixPortrayal, "Yard");

    }

    public void quit() {

        super.quit();
        if (displayFrame != null) displayFrame.dispose();
        displayFrame = null;
        display = null;

    }

    public MatrixWithUI() { 

        super(new Matrix (System.currentTimeMillis())); 

    }

    public MatrixWithUI(SimState state) {

        super(state);

    }

    public static String getName() {

        return "Student Schoolyard Cliques";

    }

}

However, for some reason all cells are continuously set to 0 (or off). Any thoughts?


Solution

  • Note: this is a tentative answer as I have no way of verifying it at the moment.

    First, let's look at the documentation of ValueGridPortrayal2D. It says:

    Like other FieldPortrayal2Ds, this class uses an underlying SimplePortrayal2D to draw each separate element in the grid. A default SimplePortrayal2D is provided which draws squares. In the default, the color for the square is determined by looking up the value of the square in a user-provided color-table, or if there is none, by interpolating it between two user-provided colors. See the setColorTable() and setLevels() methods.

    So, if you settle for squares rather than ovals, you can drop this line:

        matrixPortrayal.setPortrayalForAll(new OvalPortrayal2D());
    

    And instead, add:

        java.awt.Color[] colorTable = new java.awt.Color[2];
        colorTable[0] = new java.awt.Color(1.0F,0.0F,0.0F,0.0F);
        colorTable[1] = new java.awt.Color(1.0F,0.0F,0.0F,1.0F);
        matrixPortrayal.setMap( new SimpleColorMap(colorTable) );
    

    This should give you white squares (transparent on a white backdrop) for 0, and red squares for 1.

    If you want to draw ovals, this default implementation of a SimplePortrayal2D that uses a map is not available. The documentation goes further to say:

    You can also provide your own custom SimplePortrayal2D (use setPortrayalForAll(...) ) to draw elements as you see fit rather than as rectangles. Your SimplePortrayal2D should expect objects passed to its draw method to be of type MutableDouble.

    So we need to override the draw() method and treat the passed object - the cell value - as a MutableDouble (by which I assume they mean the one from org.apache.commons.lang):

        matrixPortrayal.setPortrayalForAll(new OvalPortrayal2D() {
            public void draw(Object object, Graphics2D graphics, DrawInfo2D info) {
                MutableDouble valueObj = (MutableDouble)object;
                if ( valueObj.intValue() == 0 ) {
                    paint = new java.awt.Color(1.0F,0.0F,0.0F,0.0F);
                } else {
                    paint = new java.awt.Color(1.0F,0.0F,0.0F,1.0F);
                }
                filled = true;
                super.draw(object, graphics, info);
            }
    
        });
    

    So we have created an anonymous subclass of OvalPortrayal2D. It inherits the fields paint, filled and scale from AbstractShapePortrayal2D. So we override paint (java.awt.Paint, which java.awt.Color extends) with the color we need for the particular value, and make sure the oval is filled.