Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Recreate Conway's Game of Life on Android

DZone's Guide to

Recreate Conway's Game of Life on Android

In this tutorial, you will learn how to create an implementation of the automated Game of Life for Android using Android Studio.

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

The Game of Life is a famous cellular automaton created by the British John Horton Conway in 1970. The Game of Life is considered a zero-player game because the evolution is determined by its initial state, requiring no further input.

A player interacts with the Game of Life by creating an initial configuration. Then, you have just to observe the evolution of the cells. In this tutorial, you are going to learn how to create a Game of Life implementation on Android with Android Studio.

Representing a Cell

To start, we are going to design a Cell class to represent a cell in the Game of Life. A Cell will have  coordinates representing its position on the board of the game. Besides, a Cell will have an alive property indicating if the cell is alive or dead. We add also a die, a reborn, and an invert method to the Cell class.

package com.ssaurel.gameoflife;

public class Cell {

 public int x,y;
 public boolean alive;

 public Cell(int x, int y, boolean alive) {
  this.x = x;
  this.y = y;
  this.alive = alive;
 }

 public void die() {
  alive = false;
 }

 public void reborn() {
  alive = true;
 }

 public void invert() {
  alive = !alive;
 }

}


Representing the World

The next step is to represent the World of the Game of Life. A World will have dimensions (width and height) and also a 2D array of Cell objects. The World will be initialized randomly at the beginning thanks to an init method.

Conway's Game of Life is based on 4 rules:

  1. Any live cell with fewer than two live neighbors dies, as if caused by underpopulation.
  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 overpopulation.
  4. Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.

To apply these rules, we need a method to count the number of neighbors of a given cell. Note that each cell has 8 neighbors, which are the cells that are horizontally, vertically, or diagonally adjacent. 

Then, we implement a nextGeneration method, which will be used to generate the next generation of cells for our World. In this method, for each cell, we count the number of neighbors, and then we decide if the current cell must be dead or alive at the next generation by applying the four rules presented above.

package com.ssaurel.gameoflife;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class World {

 public static final Random RANDOM = new Random();
 public int width,height;
 private Cell[][] board;

 public World(int width, int height) {
  this.width = width;
  this.height = height;
  board = new Cell[width][height];
  init();
 }

 private void init() {
  for(int i = 0; i < width; i++) {
    for (int j = 0; j < height; j++) {
     board[i][j] = new Cell(i, j, RANDOM.nextBoolean());
    }
  }
 }

 public Cell get(int i, int j) {
  return board[i][j];
 }

 public int nbNeighboursOf(int i, int j) {
  int nb = 0;

  for (int k = i -1; k <= i +1; k++) {
   for (int l = j - 1; l <= j +1; l++) {
    if ((k != i || l != j) && k >= 0
         && k < width && l >=0 && l < height) {
         Cell cell = board[k][l];

         if (cell.alive) {
          nb++;
         }
    }
   }
  }

  return nb;
 }

 public void nextGeneration() {
  List<Cell> liveCells = new ArrayList<Cell>();
  List<Cell> deadCells = new ArrayList<Cell>();

  for (int i = 0; i < width; i++) {
   for (int j = 0; j < height; j++) {
    Cell cell = board[i][j];

    int nbNeighbours = nbNeighboursOf(cell.x, cell.y);

    // rule 1 & rule 3
    if (cell.alive &&
        (nbNeighbours < 2 || nbNeighbours > 3)) {
         deadCells.add(cell);
    }

    // rule 2 & rule 4
    if ((cell.alive && (nbNeighbours == 3 || nbNeighbours == 2))
         ||
        (!cell.alive && nbNeighbours == 3)) {
         liveCells.add(cell);
    }
   }
  }

  // update future live and dead cells
  for (Cell cell : liveCells) {
   cell.reborn();
  }

  for (Cell cell : deadCells) {
   cell.die();
  }
 }

}


Creating a Custom Game of Life View

Now, it's time to create a custom Game of Life View to graphically represent the cells of our World. Our custom View will extend the SurfaceView class and will implement the Runnable interface to let the World evolve. 

package com.ssaurel.gameoflife;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Display;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.WindowManager;

public class GameOfLifeView extends SurfaceView implements Runnable {

 // Default size of a cell
 public static final int DEFAULT_SIZE = 50;
 // Default color of an alive color (white in our case)
 public static final int DEFAULT_ALIVE_COLOR = Color.WHITE;
 // Default color of a dead color (black in our case)
 public static final int DEFAULT_DEAD_COLOR = Color.BLACK;
 // Thread which will be responsible to manage the evolution of the World
 private Thread thread;
 // Boolean indicating if the World is evolving or not
 private boolean isRunning = false;
 private int columnWidth = 1;
 private int rowHeight = 1;
 private int nbColumns = 1;
 private int nbRows = 1;
 private World world;
 // Utilitaries objects : a Rectangle instance and a Paint instance used to draw the elements
 private Rect r = new Rect();
 private Paint p = new Paint();

 public GameOfLifeView(Context context) {
  super(context);
  initWorld();
 }

 public GameOfLifeView(Context context, AttributeSet attrs) {
  super(context, attrs);
  initWorld();
 }

 @Override
 public void run() {
  // while the world is evolving
  while (isRunning) {
   if (!getHolder().getSurface().isValid())
    continue;

   // Pause of 300 ms to better visualize the evolution of the world
   try {
    Thread.sleep(300);
   } catch (InterruptedException e) {
   }

   Canvas canvas = getHolder().lockCanvas();
   world.nextGeneration();
   drawCells(canvas);
   getHolder().unlockCanvasAndPost(canvas);
  }
 }

 public void start() {
  // World is evolving
  isRunning = true;
  thread = new Thread(this);
  // we start the Thread for the World's evolution
  thread.start();
 }

 public void stop() {
  isRunning = false;

  while (true) {
   try {
    thread.join();
   } catch (InterruptedException e) {
   }

   break;
  }
 }

 private void initWorld() {
  WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
  Display display = wm.getDefaultDisplay();
  Point point = new Point();
  display.getSize(point);

  // we calculate the number of columns and rows for our World
  nbColumns = point.x / DEFAULT_SIZE;
  nbRows = point.y / DEFAULT_SIZE;

  // we calculate the column width and row height
  columnWidth = point.x / nbColumns;
  rowHeight = point.y / nbRows;

  world = new World(nbColumns, nbRows);
 }

 // Method to draw each cell of the world on the canvas
 private void drawCells(Canvas canvas) {
   for (int i = 0; i < nbColumns; i++) {
    for (int j = 0; j < nbRows; j++) {
     Cell cell = world.get(i, j);
     r.set((cell.x * columnWidth) - 1, (cell.y * rowHeight) - 1,
           (cell.x * columnWidth + columnWidth) - 1, 
           (cell.y * rowHeight + rowHeight) - 1);
     // we change the color according the alive status of the cell
     p.setColor(cell.alive ? DEFAULT_ALIVE_COLOR : DEFAULT_DEAD_COLOR);
     canvas.drawRect(r, p);
    }
   }
 }

 // We let the user to interact with the Cells of the World
 @Override
 public boolean onTouchEvent(MotionEvent event) {
  if (event.getAction() == MotionEvent.ACTION_DOWN) {
   // we get the coordinates of the touch and we convert it in coordinates for the board
   int i = (int) (event.getX() / columnWidth);
   int j = (int) (event.getY() / rowHeight);

   // we get the cell associated to these positions
   Cell cell = world.get(i, j);
   // we call the invert method of the cell got to change its state
   cell.invert();

   invalidate();
  }

  return super.onTouchEvent(event);
 }
}


As you can see, we have added the possibility for the user to interact with the World. When a user will click on a specific Cell, it will invert its state. 

Creating the User Interface

With our GameOfLiveView created, we can use it to create the User Interface of our Game of Life implementation. As you can see, the UI is pretty simple:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 tools:context="com.ssaurel.gameoflife.MainActivity">

 <com.ssaurel.gameoflife.GameOfLifeView
  android:id="@+id/game_of_life"
  android:layout_width="match_parent"
  android:layout_height="match_parent"/>

</RelativeLayout>


Writing the Code of the Main Activity

The last step is to write the Java code of the Main Activity for our application. First, we get the reference of our GameOfLiveView in the onCreate method. Then, in the onResume method, we start the evolution of the World by calling the start method of the GameOfLifeView instance. Finally, we don't forget to stop the evolution of the World by calling the stop method of the GameOfLifeView object in the onPause method.

It gives us the following code:

package com.ssaurel.gameoflife;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

 private GameOfLifeView gameOfLifeView;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  gameOfLifeView = (GameOfLifeView) findViewById(R.id.game_of_life);
 }

 @Override
 protected void onResume() {
  super.onResume();
  gameOfLifeView.start();
 }

 @Override
 protected void onPause() {
  super.onPause();
  gameOfLifeView.stop();
 }
}


Testing our Conway's Game of Life Implementation

Now it's time to test our Conway's Game of Life implementation. You can see a screenshot of the World in evolution below:

Image title

To add more beauty to our application, we have also chosen to set the top window bar background in black. The final result looks great. To improve the implementation, you could let the users enter an initial configuration for the World rather than generating a random World at the beginning.

As an extra bonus, you can discover this tutorial in a video on YouTube if you prefer.

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:
java ,android ,mobile ,mobile app development

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}