9

I have a matrix that implements John Conway's life simulator in which every cell represents either life or lack of it.

Every life cycle follows these rules:

  1. Any live cell with fewer than two live neighbors dies, as if caused by under-population.

  2. Any live cell with two or three live neighbors lives on to the next generation.

  3. Any live cell with more than three live neighbors dies, as if by overcrowding.

  4. Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.

Every cell will have a thread that will perform the changes by the rules listed above.

I have implemented these classes:

import java.util.Random;

public class LifeMatrix {
    Cell[][] mat;
    public Action currentAction = Action.WAIT_FOR_COMMAND;
    public Action changeAction;

    public enum Action {
        CHECK_NEIGHBORS_STATE,
        CHANGE_LIFE_STATE,
        WAIT_FOR_COMMAND
    }

    // creates a life matrix with all cells alive or dead or random between dead or alive
    public LifeMatrix(int length, int width) {
        mat = new Cell[length][width];

        for (int i = 0; i < length; i++) { // populate the matrix with cells randomly alive or dead
            for (int j = 0; j < width; j++) {
                mat[i][j] = new Cell(this, i, j, (new Random()).nextBoolean());
                mat[i][j].start();
            }

        }
    }

    public boolean isValidMatrixAddress(int x, int y) {
        return x >= 0 && x < mat.length && y >= 0 && y < mat[x].length;
    }

    public int getAliveNeighborsOf(int x, int y) {
        return mat[x][y].getAliveNeighbors();
    }

    public String toString() {
        String res = "";
        for (int i = 0; i < mat.length; i++) { // populate the matrix with cells randomly alive or
                                               // dead
            for (int j = 0; j < mat[i].length; j++) {
                res += (mat[i][j].getAlive() ? "+" : "-") + "  ";
            }
            res += "\n";
        }
        return res;
    }


    public void changeAction(Action a) {
        // TODO Auto-generated method stub
        currentAction=a;
        notifyAll();                 //NOTIFY WHO??
    }
}

/**
 * Class Cell represents one cell in a life matrix
 */
public class Cell extends Thread {
    private LifeMatrix ownerLifeMat; // the matrix owner of the cell
    private boolean alive;
    private int xCoordinate, yCoordinate;

    public void run() {
        boolean newAlive;

        while (true) {
            while (! (ownerLifeMat.currentAction==Action.CHECK_NEIGHBORS_STATE)){
                synchronized (this) {//TODO to check if correct


                try {
                    wait();
                } catch (InterruptedException e) {
                    System.out.println("Interrupted while waiting to check neighbors");
                }}
            }
            // now check neighbors
            newAlive = decideNewLifeState();

            // wait for all threads to finish checking their neighbors
            while (! (ownerLifeMat.currentAction == Action.CHANGE_LIFE_STATE)) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    System.out.println("Interrupted while waiting to change life state");
                };
            }

            // all threads finished checking neighbors now change life state
            alive = newAlive;
        }
    }

    // checking the state of neighbors and
    // returns true if next life state will be alive
    // returns false if next life state will be dead
    private boolean decideNewLifeState() {
        if (alive == false && getAliveNeighbors() == 3)
            return true; // birth
        else if (alive
                && (getAliveNeighbors() == 0 || getAliveNeighbors() == 1)
                || getAliveNeighbors() >= 4)
            return false; // death
        else
            return alive; // same state remains

    }

    public Cell(LifeMatrix matLifeOwner, int xCoordinate, int yCoordinate, boolean alive) {
        this.ownerLifeMat = matLifeOwner;
        this.xCoordinate = xCoordinate;
        this.yCoordinate = yCoordinate;
        this.alive = alive;
    }

    // copy constructor
    public Cell(Cell c, LifeMatrix matOwner) {
        this.ownerLifeMat = matOwner;
        this.xCoordinate = c.xCoordinate;
        this.yCoordinate = c.yCoordinate;
        this.alive = c.alive;
    }

    public boolean getAlive() {
        return alive;
    }

    public void setAlive(boolean alive) {
        this.alive = alive;
    }

    public int getAliveNeighbors() { // returns number of alive neighbors the cell has
        int res = 0;
        if (ownerLifeMat.isValidMatrixAddress(xCoordinate - 1, yCoordinate - 1) && ownerLifeMat.mat[xCoordinate - 1][yCoordinate - 1].alive)
            res++;
        if (ownerLifeMat.isValidMatrixAddress(xCoordinate - 1, yCoordinate) && ownerLifeMat.mat[xCoordinate - 1][yCoordinate].alive)
            res++;
        if (ownerLifeMat.isValidMatrixAddress(xCoordinate - 1, yCoordinate + 1) && ownerLifeMat.mat[xCoordinate - 1][yCoordinate + 1].alive)
            res++;
        if (ownerLifeMat.isValidMatrixAddress(xCoordinate, yCoordinate - 1) && ownerLifeMat.mat[xCoordinate][yCoordinate - 1].alive)
            res++;
        if (ownerLifeMat.isValidMatrixAddress(xCoordinate, yCoordinate + 1) && ownerLifeMat.mat[xCoordinate][yCoordinate + 1].alive)
            res++;
        if (ownerLifeMat.isValidMatrixAddress(xCoordinate + 1, yCoordinate - 1) && ownerLifeMat.mat[xCoordinate + 1][yCoordinate - 1].alive)
            res++;
        if (ownerLifeMat.isValidMatrixAddress(xCoordinate + 1, yCoordinate) && ownerLifeMat.mat[xCoordinate + 1][yCoordinate].alive)
            res++;
        if (ownerLifeMat.isValidMatrixAddress(xCoordinate + 1, yCoordinate + 1) && ownerLifeMat.mat[xCoordinate + 1][yCoordinate + 1].alive)
            res++;
        return res;
    }

}

public class LifeGameLaunch {

    public static void main(String[] args) {
        LifeMatrix lifeMat;
        int width, length, populate, usersResponse;
        boolean userWantsNewGame = true;
        while (userWantsNewGame) {
            userWantsNewGame = false; // in order to finish the program if user presses
                                      // "No" and not "Cancel"
            width = Integer.parseInt(JOptionPane.showInputDialog(
                    "Welcome to John Conway's life simulator! \n"
                            + "Please enter WIDTH of the matrix:"));
            length = Integer.parseInt(JOptionPane.showInputDialog(
                    "Welcome to John Conway's life simulator! \n"
                            + "Please enter LENGTH of the matrix:"));


            lifeMat = new LifeMatrix(length, width);

            usersResponse = JOptionPane.showConfirmDialog(null, lifeMat + "\nNext cycle?");
            while (usersResponse == JOptionPane.YES_OPTION) {
                if (usersResponse == JOptionPane.YES_OPTION) {
                    lifeMat.changeAction(Action.CHECK_NEIGHBORS_STATE);
                } 
                else if (usersResponse == JOptionPane.NO_OPTION) {
                    return;
                }
                // TODO leave only yes and cancel options
                usersResponse = JOptionPane.showConfirmDialog(null, lifeMat + "\nNext cycle?");
            }
            if (usersResponse == JOptionPane.CANCEL_OPTION) {
                userWantsNewGame = true;
            }
        }
    }
}

My trouble is to synchronize the threads: Every cell(a thread) must change its life/death state only after all threads have checked their neighbors. The user will invoke every next life cycle by clicking a button.

My logic, as can be understood from the run() method is to let every cell(thread) run and wait for the right action state that is represented by the variable currentAction in LifeMatrix class and go ahead and execute the needed action.

What I struggle with is how do I pass these messages to the threads to know when to wait and when execute next action?

Any suggestions to change the design of the program are very welcome as long as every cell will be implemented with a separate thread!

Max Segal
  • 1,955
  • 1
  • 24
  • 53
  • 3
    If the goal is to learn how to manage threads, this is a good exercise. If the goal is to make a *good* working application, then you'll be better off using a single thread to update the cells. – MadConan Jun 17 '15 at 13:58
  • this is indeed a university exercise for practicing thread synchronization... any rough directions are also good... how do I make all threads wait untill a variable currentAction in LifeMatrix object changes? – Max Segal Jun 17 '15 at 14:04

2 Answers2

1

I would solve this using two Phasers.

You use one Phaser to control cycles and one one to synchronize the cells when they determine if they are alive or not.

public class Cell extends Thread {
    private LifeMatrix ownerLifeMat; // the matrix owner of the cell
    private boolean alive;
    private int xCoordinate, yCoordinate;

    // Phaser that controls the cycles
    private Phaser cyclePhaser;

    // Phaser for cell synchronisation
    private Phaser cellPhaser;

    public Cell(LifeMatrix matLifeOwner, Phaser cyclePhaser, Phaser cellPhaser,
            int xCoordinate, int yCoordinate, boolean alive) {
        this.ownerLifeMat = matLifeOwner;
        this.cyclePhaser = cyclePhaser;
        this.cellPhaser = cellPhaser;
        this.xCoordinate = xCoordinate;
        this.yCoordinate = yCoordinate;
        this.alive = alive;

        // Register with the phasers
        this.cyclePhaser.register();
        this.cellPhaser.register();
    }

    public void run() {
        boolean newAlive;

        while (true) {
            // Await the next cycle
            cyclePhaser.arriveAndAwaitAdvance();

            // now check neighbors
            newAlive = decideNewLifeState();

            // Wait until all cells have checked their state
            cellPhaser.arriveAndAwaitAdvance();

            // all threads finished checking neighbors now change life state
            alive = newAlive;
        }
    }

    // Other methods redacted
}

You control the cycles by having the main thread register on the cyclePhaser and have it arrive on it to start the next cycle.

Raniz
  • 10,882
  • 1
  • 32
  • 64
  • Just curious, seems using a Barrier is going to be simpler in this case (and in fact we just need one Phaser/Barrier), any reason to choose Phaser (and 2 Phasers) in your solution? – Adrian Shum Jun 18 '15 at 02:06
  • The reason for using 2 is that the main thread needs to arrive when advancing to the next cycle but has no business doing so when the cells check state. The reason for using `Phaser` over `CyclicBarrier` is because I like it more (static number of participants vs `(de)register`) and that it has the `arrive` method which doesn't wait for the other threads. `Phaser` is just more versatile and I consider `CyclicBarrier` pretty much obsolete. – Raniz Jun 18 '15 at 02:13
1

Using a CyclicBarrier should be easy to understand:

(updated to use 2 Barriers, and make use of inner class to make cell looks shorter and cleaner)

psuedo code:

public class LifeMatrix {
    private CyclicBarrier cycleBarrier;
    private CyclicBarrier cellUpdateBarrier;
    //.....

    public LifeMatrix(int length, int width) {
        cycleBarrier = new CyclicBarrier(length * width + 1);
        cellUpdateBarrier = new CyclicBarrier(length * width);

        // follow logic of old constructor
    }

    public void changeAction(Action a) {
        //....
        cycleBarrier.await()
    }

    // inner class for cell
    public class Cell implements Runnable {
        // ....

        @Override
        public void run() {
             while (...) {
                 cycleBarrier.await();  // wait until start of cycle
                 boolean isAlive = decideNewLifeState();
                 cellUpdateBarrier.await();  // wait until everyone completed
                 this.alive = isAlive;
             }
        }
    }
}
Adrian Shum
  • 38,812
  • 10
  • 83
  • 131
  • This will make the cells run continously and won't pause for the user to initiate the next cycle – Raniz Jun 18 '15 at 02:15
  • Good catch :P I forgot about that when I write this. But it can be easily done by changing to `CyclicBarrier barrier = new CyclicBarrier(noOfCells + 1);` and when user initiate each cycle, performs `barrier.await(); barrier.await();` (Or split to 2 barrier if OP wants, which makes it clearer in such aspect) – Adrian Shum Jun 18 '15 at 02:20
  • Yeah, that's the reason I use two `Phaser`s in my answer - I don't like the double call to `await()`. It works fine in this case, but if the cells were doing something more advanced that takes longer time the main thread is locked while waiting for the cells - in a real world app that means the UI would be frozen. – Raniz Jun 18 '15 at 02:23
  • Yup, I can update my answer to use 2 Barriers :) which should solve the problem – Adrian Shum Jun 18 '15 at 02:40