Stock market simulation with Cellular automaton

In my previous post I have created cellular automaton framework and implemented Conway’s game of life with it. In this post I will show you how you can use cellular automaton for one very interesting simulation – stock market. What I am going to do is a very simplified model of market micro structure. However it still can be fascinating to watch! And of course source code is provided and explained so you can try various things yourself! Even though it’s not necessary it could be helpful to read previous article first if you are going to really dive into the source code of this stock market simulation as I’m not going to explain application design here.

First of all you can download jar file and simply try out application by running this command (you will need java8 installed):

java -jar simulation.jar

Here is screenshot of how it looks like:

stockmarketautomaton

So how is it implemented? Firstly I have created 2d cellular world where each cell represents market participant. And as in the real stock market every participant affects other participants. To make it simple each cell in our stock market simulation has certain amount of money and number of shares on their balance. Before each iteration it also has action set – will it buy or sell shares next. As you can see and guess from the screenshot red cells are selling, green cells are buying and grey cells are neutral.

For market to function it must support two types of orders: limit and market. For example lets say you want to sell your phone. You put an ad that the price is 500$. That’s your offer and your limit order. Some other guy puts an ad that he is buying the phone but will only pay 450$. Now we have best bid 450$ and best offer 500$. Market order in this case would be simply to execute transaction at price offered or bid (in this case buying right away for 500$ or selling for 450$). For simplicity I made that 20% of participants are using limit orders to buy or sell and all neutral participants also put their bids and offers at much wider spreads. Other participants use market orders to buy/sell from those who did put their limit orders. The rules how market participant decides of his next action is simple: if he has 3 to 7 neighbors which are bullish – his next action is to buy. Same the other way around. However if all 8 of his neighbors are bullish or bearish – he takes contrarian view and does the opposite. Now once he has an open position he calculates his profit or loss and exit that position if loss exceeds 2% of the position size or profit is more than 6% of the position size. Participant with an open position also exits if neighbors become bullish and he is short or neighbors become bearish and he is long. And that’s it. As can you see rules are pretty simple. Now Lets walk you through the implementation and source code.

As in Conway’s game of life main class is very simple:

public class Main {
    public static void main(final String[] args) throws IOException, InterruptedException {
        final WorldGui<Action> gui = new SwingStockMarketGui();
        final World<Action> world = new StockMarketWorld(10, 10, new StockMarketAutomatonRule());
        gui.showWorld(world);
        for (int i = 0; i < 100; i++) {
            Thread.sleep(1000);
            world.nextState();
            gui.showWorld(world);
            System.out.println("Iteration " + i);
        }
    }
}

We iterate 100 times through world states and show results in GUI (which shows us final candlestick chart with each candle representing one iteration). Now obviously all the magic happens behind the scenes. First of all lets see how StockMarketWorld is implemented:

public class StockMarketWorld extends SimpleTwoDimensionalGrid<Action> {

    private static final int INITIAL_PRICE = 100;
    private final StockExchangeLevel2Book level2;
    private List<Candle> candles = new ArrayList<>(Arrays.asList(new Candle(INITIAL_PRICE, INITIAL_PRICE, INITIAL_PRICE, INITIAL_PRICE)));
    private Integer currentOpen = INITIAL_PRICE;
    private Integer currentHigh = INITIAL_PRICE;
    private Integer currentLow = INITIAL_PRICE;
    private Integer currentClose = INITIAL_PRICE;

    @Override
    protected Cell<Action> createNewCell(final XYCoordinates coordinates, final CellularAutomatonRule<Action> rule) {
        final Random random = new Random();
        // 33% chance to buy, sell, or neutral initial state
        int randomInt = random.nextInt(3);
        final Action action = randomInt == 0 ? Action.NEUTRAL : randomInt == 1 ? Action.BUY : Action.SELL;
        return new MarketParticipant(action, rule, coordinates, this);
    }

    @Override
    public void nextState() {
        currentOpen = getLastCandle().getClose();
        currentHigh = currentOpen;
        currentLow = currentOpen;
        currentClose = currentOpen;
        
        List<MarketParticipant> participants = new ArrayList<>();
        
        for (final Cell<Action>[] cellArray : worldGrid) {
            for (final Cell<Action> cell : cellArray) {
                final MarketParticipant participant = (MarketParticipant) cell;
                participants.add(participant);
            }
        }
        
        // make participants to act in random order during each iteration
        Collections.shuffle(participants);
        boolean useLimitOrders = true;
        int i = 0;
        for (MarketParticipant participant : participants) {
            participant.act(useLimitOrders);
            if (useLimitOrders && (i++ > participants.size() / 5)) { // 20% participants use limit orders
                useLimitOrders = false;
            }
        }
        
        candles.add(new Candle(currentOpen, currentHigh, currentLow, currentClose));
        
        // CellularAutomatonRule sets new values for next round
        super.nextState();
    }
}

Method createNewCell() is used to initialize world and create new cells which in this case is of MarketParticipant type. NextState() method randomly shuffle market participants and invoke their act() methods (this way they are acting in random order). First 20% of them uses limit orders for their actions. I left out other methods like placing orders on the market and returning candles or order book as I am concentrating on the behaviour of the market participants and want to save some space. You can check out full source from github.

Now lets see how Cell implementation is done. I called it MarketParticipant:

public class MarketParticipant extends SimpleCell<Action> {

    private int totalCapital = 1000000;
    private int sharesHold = 0;
    private int positionSize = 0;
    
    public MarketParticipant(final Action value, final CellularAutomatonRule<Action> rule, final Coordinates cellCoordinates,
            final World<Action> world) {
        super(value, rule, cellCoordinates, world);
    }

    public void act(boolean withLimitOrder) {
        final StockMarketWorld stockMarket = (StockMarketWorld) world;
        final int lastPrice = stockMarket.getLastCandle().getClose();
        final Random random = new Random(System.nanoTime());
        
        // buy or sell random amount from 0 to 400 shares
        final int randomNumberOfShares = (random.nextInt(4) + 1) * 100;
        final int numberOfShares = sharesHold != 0 ? Math.abs(sharesHold) : randomNumberOfShares;
        
        if (getValue() == Action.NEUTRAL) {
            int spread = random.nextInt(10) + 4;
            // if market maker undecided he simply bids and offers with big spread
            stockMarket.placeBid(this, randomNumberOfShares, lastPrice - spread);
            stockMarket.placeOffer(this, randomNumberOfShares, lastPrice + spread);
        } else if (withLimitOrder && (getValue() == Action.BUY || getValue() == Action.SELL)) {
            if (getValue() == Action.BUY) {
                int spread = random.nextInt(3) + 1; // first offer @ random spread no more than 4
                stockMarket.placeBid(this, numberOfShares, lastPrice - spread);
            } else if (getValue() == Action.SELL) {
                int spread = random.nextInt(3) + 1; // first offer @ random spread no more than 4
                stockMarket.placeOffer(this, numberOfShares, lastPrice + spread);
            }
        } else if (!withLimitOrder && (getValue() == Action.BUY || getValue() == Action.SELL)) {
            // if there is open position then size is of current position size otherwise its random number of shares
            if (getValue() == Action.BUY) {
                stockMarket.buyMarket(this, numberOfShares);
            } else if (getValue() == Action.SELL) {
                stockMarket.sellMarket(this, numberOfShares);
            }
        }
    }
}

public enum Action {
    BUY,
    SELL, 
    NEUTRAL
}

Market participant action depends on the cell value which in this case is of type Action. It is set by StockMarketAutomatonRule after the iteration for the next iteration. As you can see from the code if action is set to be NEUTRAL, market participant puts limit orders with wide spread, otherwise depending on withLimitOrder flag it puts limit or market orders to the market. And that’s about it.

The last important class is StockMarketAutomatonRule which decides what each cell is going to do during next iteration. In this case – buy, sell or stay neutral.

public class StockMarketAutomatonRule implements CellularAutomatonRule<Action> {

    @Override
    public Action calculateNewValue(final Action currentValue, final World<Action> world, final Coordinates coordinates) {
        final XYCoordinates gridCoordinates = (XYCoordinates) coordinates;
        final StockMarketWorld marketWorld = (StockMarketWorld) world;

        final MarketParticipant marketParticipant = (MarketParticipant) world.getCell(new XYCoordinates(gridCoordinates.getX(),
                gridCoordinates.getY()));
        
        final int bullishNeighbors = getNumberOfBulishNeighbors(marketWorld, gridCoordinates);
        final int bearishNeighbors = getNumberOfBearishNeighbors(marketWorld, gridCoordinates);

        if (marketParticipant.getSharesHold() == 0) {
            return calculateActionFromNeighbors(marketWorld, gridCoordinates, bullishNeighbors, bearishNeighbors);
        } else {
            int currentPositionValue = marketParticipant.getSharesHold() * marketWorld.getLastCandle().getClose();
            int profitLoss = marketParticipant.getPositionSize() - currentPositionValue;
            double profitLossPrc = (double)(profitLoss * 100)/(double)currentPositionValue;
            boolean sameCloseTwice = marketWorld.getLastCandle().getClose() == marketWorld.getCandles().get(marketWorld.getCandles().size() - 2).getClose();
            if (marketParticipant.getSharesHold() > 0) {  // if already long
                if (sameCloseTwice) {
                    return Action.SELL; // if closed same twice in a row - exit position
                } else if (bearishNeighbors >= 4) {
                    return Action.SELL;
                } else if (profitLossPrc > 6) {
                    return Action.SELL;
                } else if (profitLossPrc < -2) {
                    return Action.SELL;
                }
                    
            } else { // if already short
                if (sameCloseTwice) {
                    return Action.BUY; // if closed same twice in a row - exit position
                } else if (bullishNeighbors >= 4) {
                    return Action.BUY;
                } else if (profitLossPrc > 6) {
                    return Action.BUY;
                } else if (profitLossPrc < -2) {
                    return Action.BUY;
                }
            }
        }
        
        return Action.NEUTRAL;
    }
    
    private Action calculateActionFromNeighbors(StockMarketWorld marketWorld, XYCoordinates gridCoordinates, int bullishNeighbors, int bearishNeighbors) {
        
        boolean between3and7Bulish = 3 <= bullishNeighbors && bullishNeighbors <= 7;
        boolean between3and7Bearish = 3 <= bearishNeighbors && bearishNeighbors <= 7;
        
        if (between3and7Bearish && between3and7Bulish) {// if have both 4 bullish and 4 bearish neighbors then be neutral
            return Action.NEUTRAL;
        } else if (between3and7Bulish || bearishNeighbors > 7) {// if more than 7 bearish - take contrarian view and buy
            return Action.BUY;
        } else if (between3and7Bearish || bullishNeighbors > 7) {// if more than 7 bullish - take contrarian view and sell
            return Action.SELL;
        } else {
            return Action.NEUTRAL;
        }
    }
}

Rules are pretty simple. If participant does not have open position then it checks how many of his neighbors are bullish and how many bearish. If 3 to 7 are bullish his next action is to buy and if 3 to 7 is bearish his next action is to sell. If all 8 neighbors are bullish he takes opposite view and sells. Same if all 8 neighbors are bearish – he takes opposite view and buys. If he has open position he takes profits if it’s more than 6% of his open position and cut losses if it exceeds 2% of open position. Also exit position if last 2 candles closes at the same price or neighbors become too bearish/bullish to the opposite side. And that’s it.

Summary

As you can see it is extremely simplified simulation. It can be a lot of fun to play with different parameters and add additional features. You can check out source code from github and do it yourself. Maybe it would make sense to separate market participants into two types like market makers who use only limit orders and speculators who use market orders. It would be interesting to add participants with more different and longer term strategies. For example some moving average or stochastic (or other indicator) strategies. Each cell could look not only at their own neighbors but market sentiment as a whole. Some market “news” feature could be added that changes perception of participants and it could be interesting to watch how they react to it. If you read good book about market microstructure I am sure there will be no shortage for ideas.

Can it be useful for real world trading? Well I am not sure. One possible use case could be to identify all types of real world market participants and build realistic market microstructure model based on that. Then by running a lot of simulations try to guess current market composition and then run simulation further which could provide a peek into the future under various conditions. Could it work? I don’t know but I hope to find out ūüôā

Cellular automaton mini framework and Conway’s Game of Life implementation

In this article I am going to create mini cellular automaton framework and then implement Conway’s game of life with it.¬† I will try to put attention to proper coding techniques, API design,¬† OOP and code documentation so its not only about cellular automaton but also about my thought process when I design new application.

Lets get started. First of all what is cellular automaton? Let’s look at wikipedia:

A cellular automaton (pl. cellular automata, abbrev. CA) is a discrete model studied in computability theory, mathematics, physics, complexity science, theoretical biology and microstructure modeling. Cellular automata are also called cellular spaces, tessellation automata, homogeneous structures, cellular structures, tessellation structures, and iterative arrays.

A cellular automaton consists of a regular grid of cells, each in one of a finite number of states, such as on and off (in contrast to a coupled map lattice). The grid can be in any finite number of dimensions. For each cell, a set of cells called its neighborhood is defined relative to the specified cell. An initial state (time t=0) is selected by assigning a state for each cell. A new generation is created (advancing t by 1), according to some fixed rule (generally, a mathematical function) that determines the new state of each cell in terms of the current state of the cell and the states of the cells in its neighborhood. Typically, the rule for updating the state of cells is the same for each cell and does not change over time, and is applied to the whole grid simultaneously, though exceptions are known, such as the stochastic cellular automaton and asynchronous cellular automaton.

In other words it is a grid of cells and each cell changes its state during time iteration according to certain rules.

I want API to be as simple as possible and my goal is that it should work like this for any automaton at the end of the day:

World<Boolean> world = // create world implementation

for (int i = 0; i < 1000; i++) {
            Thread.sleep(100);
            world.nextState();
}

I don’t know if¬†World is the best name for grid of cells abstraction but decided to use it anyway.

So the main classes will be¬† World and Cell .¬† Lets create them. Because I promised mini framework¬†in the title I’m looking for a flexible solution and will create interfaces first which later can be implemented according to our needs.


public interface Cell<T> {
    
    T getValue();
    
    void revaluateCell();

    void commitRevaluation();
    
}

public interface World<T> {

    public void nextState();
    
    public void addPattern(Pattern pattern, Coordinates coordinates);
    
    public Cell<T> getCell(Coordinates coordinates);

}

World of course has method nextState() which is used for iterating through states and also provide additional methods for cell retrieval based on its coordinates and addition of new pattern. Cell can return its value with getValue() method. Also it is capable of reevaluating itself and then commit new reevaluated value. The reason for such design is that I want each cell to be “smart” and handle its own value independently. The 2 step process of reevaluation and commit is required because in most cases cell reevaluates itself depending on other cells in the world. That means that cell cannot commit its value before all cells reevaluate theirs first otherwise cell’s states would be inconsistent.¬† Lets see how such cell’s¬† implementation looks like:

public class SimpleCell<T> implements Cell<T> {

    private T value;

    private T revaluatedValue;

    private CellularAutomatonRule<T> rule;

    private Coordinates cellCoordinates;

    private World<T> world;

    public SimpleCell(T value, CellularAutomatonRule<T> rule,
            Coordinates cellCoordinates, World<T> world) {
        super();
        this.value = value;
        this.rule = rule;
        this.cellCoordinates = cellCoordinates;
        this.world = world;
    }

    @Override
    public T getValue() {
        return value;
    }

    @Override
    public void revaluateCell() {
        revaluatedValue = rule.calculateNewValue(value, world, cellCoordinates);
    }

    @Override
    public void commitRevaluation() {
        value = revaluatedValue;
        revaluatedValue = null;
    }

}

public interface CellularAutomatonRule<T> {
    T calculateNewValue(T currentValue, World<T> grid, Coordinates coordinates);
}
public interface Coordinates {
    int[] getCoordinates();
}

This SimpleCell implementation should be enough for most of the simple use cases. Here we have added two new abstractions РCoordinates and CellularAutomatonRule. Coordinates tells us where the Cell is in the world. Even though most often cellular automatons are in 2D grid world we want the design which allows any dimension and any type of Cell.  CellularAutomatonRule implementations defines rules how cell changes its value from one step to the next. In perfect world and perfect framework this should be the only class that has to be implemented when creating a new custom automaton.

Because each class should be responsible for only one thing lets add last abstraction – WorldGui.

public interface WorldGui<T> {
    public void showWorld(World<T> world);
}

It has only one method called showWorld() which must be invoked after each iteration to display World in a new state for the user. Today I will implement very simple text GUI just to show Conway’s game of life but because we code to abstraction it is very easy to replace it with other types of more interesting GUIs for example Swing.

So with current design our framework is used like this: user implements CellularAutomatonRule which defines what his automaton is doing and how each Cell changes its values. Then he constructs World which contains Cells of certain value type. Ideally all required World and Cell implementations are already provided by framework and can be simply reused. Then he simply iterate and invoke World.nextState() and WorldGui.showWorld() methods. And that is it.

OK lets implement interfaces we have left. Here is implementation of simple 2D coordinates which is really simple and self explanatory:

public class XYCoordinates implements Coordinates {

    private int x;
    private int y;

    public XYCoordinates(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }

    @Override
    public int[] getCoordinates() {
        return new int[] { x, y };
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

}

Now World implementation for 2d grid world. As you can see here we have 2 classes SimpleTwoDimensionalGrid which is abstract class and SimpleBooleanGridWorld which extends SimpleTwoDimensionalGrid. The reason for this is to encapsulate all logic for 2d worlds in abstract class and reuse it later with various types of cells. In this example we create 2d world with cells with boolean values.

public abstract class SimpleTwoDimensionalGrid<T> implements World<T> {

    private Cell<T>[][] worldGrid = null;
    
    private int height;

    private int lenght;
    
    private CellularAutomatonRule<T> rule;

    @SuppressWarnings("unchecked")
    public SimpleTwoDimensionalGrid(int height, int length, CellularAutomatonRule<T> rule) {
        super();
        this.height = height;
        this.lenght = length;
        this.rule = rule;
        this.worldGrid = new Cell[height][lenght];
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < lenght; x++) {
                worldGrid[y][x] = createNewCell(new XYCoordinates(x, y), rule);
            }
        }
    }

    @Override
    public void nextState() {
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < lenght; j++) {
                worldGrid[i][j].revaluateCell();
            }
        }
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < lenght; j++) {
                worldGrid[i][j].commitRevaluation();
            }
        }
    }

    @Override
    public Cell<T> getCell(Coordinates coordinates) {
        XYCoordinates gridCoordinates = (XYCoordinates) coordinates;
        return worldGrid[gridCoordinates.getY()][gridCoordinates.getX()];
    }
    
    @Override
    public void addPattern(Pattern pattern, Coordinates coordinates) {
        @SuppressWarnings("unchecked")
        T[][] patternValues = ((GridPattern<T>)pattern).get();
        XYCoordinates gridCoordinates = ((XYCoordinates)coordinates);
        for (int y = gridCoordinates.getY(); y < getHeight() && y < gridCoordinates.getY() + patternValues.length; y++) {
            for (int x = gridCoordinates.getX(); x < getLenght() && x < gridCoordinates.getX() + patternValues[0].length; x++) {
                worldGrid[y][x] = new SimpleCell<T>(patternValues[y - gridCoordinates.getY()][x - gridCoordinates.getX()], rule, new XYCoordinates(x, y), this);
            }
        }
    }

    public int getHeight() {
        return height;
    }

    public int getLenght() {
        return lenght;
    }
    
    protected abstract Cell<T> createNewCell(XYCoordinates coordinates, CellularAutomatonRule<T> rule);
}

public class SimpleBooleanGridWorld extends SimpleTwoDimensionalGrid<Boolean> {
    public SimpleBooleanGridWorld(int height, int length, CellularAutomatonRule<Boolean> rule) {
        super(height, length, rule);
    }

    @Override
    protected Cell<Boolean> createNewCell(XYCoordinates coordinates, CellularAutomatonRule<Boolean> rule) {
        return new SimpleCell<Boolean>(false, rule, coordinates, this);
    }
}

public interface Pattern {
    Object get();
}
public abstract class GridPattern<T> implements Pattern {
    public abstract T[][] get();
}
public class ToadPattern extends GridPattern<Boolean> {
    @Override
    public Boolean[][] get() {
        return new Boolean[][] {{false, true, true, true}, {true, true, true, false}};
    }
}

Code is pretty much self explanatory.  In constructor we create a new 2 dimensional array and fill it by invoking createNewCell() method for each position.  SimpleBooleanGridWorld returns SimpleCell with Boolean type value.  And in nextState() method we simply iterate through each cell two times Рfirst to reevaluate and then commit new value.  Also World has the method to add some custom patterns to it so I have added some sample Pattern interface implementations.

For GUI I am a bit lazy and will create very simple text GUI with simple System.out.println().  It is printing grid world and then it is printing empty lines. If everything is correct this creates textual animation.

public class TextBooleanGridWorldGui implements WorldGui<Boolean> {

    @Override
    public void showWorld(World<Boolean> world) {
        SimpleBooleanGridWorld booleanGridWorld = (SimpleBooleanGridWorld)world;
        for (int y = 0; y < booleanGridWorld.getHeight(); y++) {
            for (int x = 0; x < booleanGridWorld.getLenght(); x++) {
                System.out.print(booleanGridWorld.getCell(new XYCoordinates(x, y)).getValue() ? "@" : "-");
            }
            System.out.println();
        }
        for (int y = 0; y < booleanGridWorld.getHeight(); y++) { // print empty lines to separate different states (if its large enough it will create animation for changing states)
            System.out.println();
        }
    }

}

And last interface to implement before running our example is of course CellularAutomatonRule:

public class ConwaysGameOfLifeRule implements CellularAutomatonRule<Boolean> {
    
    private static Cell<Boolean> EMPTY_CELL = new SimpleCell<Boolean>(false, null, null, null);

    @Override
    public Boolean calculateNewValue(Boolean currentValue, World<Boolean> world, Coordinates coordinates) {
        XYCoordinates gridCoordinates = (XYCoordinates)coordinates;
        SimpleTwoDimensionalGrid<Boolean> gridWorld = (SimpleBooleanGridWorld)world;
        int sum = (getUpperNeighbor(gridWorld, gridCoordinates).getValue() ? 1 : 0) + (getLowerNeighbor(gridWorld, gridCoordinates).getValue() ? 1 : 0)
                + (getLeftNeighbor(gridWorld, gridCoordinates).getValue() ? 1 : 0) + (getRightNeighbor(gridWorld, gridCoordinates).getValue() ? 1 : 0)
                + (getUpperLeftNeighbor(gridWorld, gridCoordinates).getValue() ? 1 : 0) + (getUpperRightNeighbor(gridWorld, gridCoordinates).getValue() ? 1 : 0)
                + (getLowerLeftNeigbhor(gridWorld, gridCoordinates).getValue() ? 1 : 0) + (getLowerRightNeigbhor(gridWorld, gridCoordinates).getValue() ? 1 : 0);
        return currentValue ? sum == 2 || sum == 3 : sum == 3;
    }
    
    private Cell<Boolean> getUpperNeighbor(SimpleTwoDimensionalGrid<Boolean> world, XYCoordinates coordinates) {
        int neighborX = coordinates.getX();
        int neighborY = coordinates.getY() - 1;
        return (neighborY >= 0) ? world.getCell(new XYCoordinates(neighborX, neighborY)) : EMPTY_CELL;
    }

    private Cell<Boolean> getLowerNeighbor(SimpleTwoDimensionalGrid<Boolean> world, XYCoordinates coordinates) {
        int neighborX = coordinates.getX();
        int neighborY = coordinates.getY() + 1;
        return (neighborY < world.getHeight()) ? world.getCell(new XYCoordinates(neighborX, neighborY)) : EMPTY_CELL;
    }

    private Cell<Boolean> getLeftNeighbor(SimpleTwoDimensionalGrid<Boolean> world, XYCoordinates coordinates) {
        int neighborX = coordinates.getX() - 1;
        int neighborY = coordinates.getY();
        return (neighborX >= 0) ? world.getCell(new XYCoordinates(neighborX, neighborY)) : EMPTY_CELL;
    }

    private Cell<Boolean> getRightNeighbor(SimpleTwoDimensionalGrid<Boolean> world, XYCoordinates coordinates) {
        int neighborX = coordinates.getX() + 1;
        int neighborY = coordinates.getY();
        return (neighborX < world.getLenght()) ? world.getCell(new XYCoordinates(neighborX, neighborY)) : EMPTY_CELL;
    }

    private Cell<Boolean> getUpperLeftNeighbor(SimpleTwoDimensionalGrid<Boolean> world, XYCoordinates coordinates) {
        int neighborX = coordinates.getX() - 1;
        int neighborY = coordinates.getY() - 1;
        return (neighborX >= 0 && neighborY >= 0) ? world.getCell(new XYCoordinates(neighborX, neighborY)) : EMPTY_CELL;
    }

    private Cell<Boolean> getUpperRightNeighbor(SimpleTwoDimensionalGrid<Boolean> world, XYCoordinates coordinates) {
        int neighborX = coordinates.getX() + 1;
        int neighborY = coordinates.getY() - 1;
        return (neighborX < world.getLenght() && neighborY >= 0) ? world.getCell(new XYCoordinates(neighborX, neighborY)) : EMPTY_CELL;
    }

    private Cell<Boolean> getLowerLeftNeigbhor(SimpleTwoDimensionalGrid<Boolean> world, XYCoordinates coordinates) {
        int neighborX = coordinates.getX() - 1;
        int neighborY = coordinates.getY() + 1;
        return (neighborX >= 0 && neighborY < world.getHeight()) ? world.getCell(new XYCoordinates(neighborX, neighborY)) : EMPTY_CELL;
    }

    private Cell<Boolean> getLowerRightNeigbhor(SimpleTwoDimensionalGrid<Boolean> world, XYCoordinates coordinates) {
        int neighborX = coordinates.getX() + 1;
        int neighborY = coordinates.getY() + 1;
        return (neighborX < world.getLenght() && neighborY < world.getHeight()) ? world.getCell(new XYCoordinates(neighborX, neighborY)) : EMPTY_CELL;
    }

}

Implementation of¬†ConwaysGameOfLifeRule provides some getters to retrieve cell’s neighbors. In¬†calculateNewValue() method it returns new value of the cell according to the Conway’s game of life rules which are following:
1. Any live cell with fewer than two live neighbours dies, as if caused by under-population.
2. Any live cell with two or three live neighbours lives on to the next generation.
3. Any live cell with more than three live neighbours dies, as if by overcrowding.
4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

And that’s it. Lets create main method and see Conway’s game of life in action:

public class Main {
    
    public static void main(String[] args) throws IOException, InterruptedException {
        WorldGui<Boolean> gui = new TextBooleanGridWorldGui();
        World<Boolean> world = new SimpleBooleanGridWorld(30, 100, new ConwaysGameOfLifeRule());
 world.addPattern(new ToadPattern(), new XYCoordinates(15, 15));
        gui.showWorld(world);
        for (int i = 0; i < 1000; i++) {
            Thread.sleep(100);
            world.nextState();
            gui.showWorld(world);
        }
    }

}

Pretty much as I wanted. If you run it from your eclipse you might see your toad pattern  similar to this (if you have not implemented your own GUIclass) :

toad

You can read more about game of life and its other patterns in wikipedia.

So what if you want one dimensional or 3d automaton. Well you will need implement all classes e.g create 3dCoordinates, 3D World, 3DGui etc. Same goes for different values of the cell. It can be as simple as boolean or as complex as you want when creating value class. And the good news is after you do this, its easy just to change rule class and experiment with different cellular automatons. However in most cases 2d grid automatons should be enough ūüôā

So this article was not only about cellular automaton but also about OOP and my thought process when designing new application. I hope you liked it and learned something from it. I welcome any criticism, remarks, ways to improve, suggestions, bugfixes etc. If you implement different kinds of cellular automatons on top of it let me know! All code can be downloaded from Github!

You might wonder how cellular automatons can be used in practice. It is useful for doing various kinds of simulations. For example traffic simulation or electronic circuits simulation or some particles movement simulation etc. You could even try to play with cellular automatons and try to create some music!

In the next article I will try to implement something more interesting with automaton – simplified stock market simulation. Cheers!