Android tutorial part 2 - Conway's Game of Life

Thursday 17th of April 2014 06:50:19 PM


  Toggle Advanced Options




  • Day Five - The beginnings of both the grid and preferences activities
  • Day Six - The custom component - the Life grid
  • Day Seven - The 'game loop'

This tutorial has a first part: Android tutorial part 1 - Conway's Game of Life.
This tutorial has a third part: Android tutorial part 3 - Conway's Game of Life.

Furthermore, I have uploaded the application's Eclipse project directory.

Update on Saturday, February 18, 2012

Update on Monday, December 20, 2010

Update on Saturday, July 10, 2010

Update on Wednesday, July 07, 2010


NotesMappr - Android app

NotesMappr is PolishedCode's semantic note taking app for Google’s Android platform. In other words, NotesMappr (see screen shot, below) is note taking that fits your brain.


NotesMappr - Semantic note taking app for Android


Main benefits

  1. NotesMappr can be thought of as the textual equivalent of mind maps and/or concept maps. In a nutshell, NotesMappr's powerful underlying data structure, topic maps, makes it possible to link your notes in any way imaginable.
  2. Furthermore, NotesMappr's integration with Freebase makes it very easy to complement and enhance your own notes with comprehensive and reliable external articles and (associated) images.
  3. Finally, a set of notes in and of itself is a valuable thing. However, said set of notes becomes more valuable if the notes themselves are related in an appropriate manner. In addition, context enables both easy and quick discovery of information saving you time. Furthermore, context enables you to expand your knowledge without the risk of your knowledge becoming disjointed or fragmented.

Main features

  1. NotesMappr provides you with both the ability to establish meaningful relationships between notes and subsequently navigate said notes in a very straightforward manner.
  2. NotesMappr enables you to complement and enhance your own notes with articles and images from Freebase, an open Creative Commons licensed repository of structured data of almost 22 million entities.
  3. In addition to (formal) associations, NotesMappr also features "WikiWords" (words with multiple capital letters in the body of a note) to create and link notes within their context. NotesMappr automatically turns said WikiWords into links to other notes - just tap a link to create the new note.

Now, imagine studying with this app at your disposal. Or researching. Or generally just compiling notes on whatever topic that interests you. Just think for a moment how useful this app would be. What are you waiting for? Get NotesMappr for your Android device, now. It’s free.

Android app on Google Play


Tutorial

Day Five - The beginnings of both the grid and preferences activities

Today, I am going to implement the application's settings screen, enabling the user to interact with the game, on-the-fly, while the Life patterns are being generated.

The following settings will be available to the user:

  • 'minimum' or underpopulation variable
  • 'maximum' or overpopulation variable
  • 'hit' or spawn (new cell) variable
  • animation (or evolution) speed
  • colour coded tick box (mapping the rate of survival to colours, that is, number of iterations that a cell has survived to a distinct colour)

The first four items can be implemented as a ListPreference widget which is the preference equivalent of a spinner. Selecting the preference will display a dialog box containing a list of values from which to select.

The last item (the colour-coding tick box) will be implemented as a CheckboxPreference, a standard preference checkbox control, used to set preferences to either true or false.

The preferences screen will be accessible from the grid screen by means of a menu.

There is quite a lot of prep work and accompanying 'wiring up' to be done. For example:

  • Need to create the grid activity (GridActivity.java) and accompanying grid layout file (res/layout/grid.xml).
  • Need to create the preferences activity (PreferencesActivity.java) and accompanying settings file (res/xml/settings.xml). Take note that the settings.xml file is not located in the res/layout directory but in the res/xml directory.
  • Need to make the appropriate addition to the MainActivity.onCreate method with regards to setting a onClickListener for the New Game button.
  • Need to make the appropriate additions to the MainActivity.onClick method so that it is able to process both the click on the About button and the New Game button.
  • Need to create the res/menu/menu.xml file.
  • Need to add a couple of additional entries to the res/values/strings.xml file for the new UI elements (e.g., menu, preferences' widgets, etcetera).
  • Need to create the res/values/arrays.xml file, specifically to populate the ListPreference widgets in the settings screen.
  • Need to add the appropriate entries to the manifest file (AndroidManifest.xml) for the two new activities, GridActivity and PreferencesActivity.

Let's start with the MainActivity...

onCreate method

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // click-handlers for buttons
    View newButton = findViewById(R.id.new_button);
    newButton.setOnClickListener(this);
    View aboutButton = findViewById(R.id.about_button);
    aboutButton.setOnClickListener(this);
}

onClick method

public void onClick(View v) {
    switch (v.getId()) {
        case R.id.new_button:
            Intent gridIntent = new Intent(this, GridActivity.class);
            startActivity(gridIntent);
            break;
        case R.id.about_button:
            Intent aboutIntent = new Intent(this, AboutActivity.class);
            startActivity(aboutIntent);
            break;
    }
}

With regards to the onCreate method, the addition is the setting of the onClickListener for the New Game button. With regards to the onClick method, there is an additional case (R.id.new_button) within the switch statement to activate the GridActivity.

The Grid screen is the next step.

GridActivity.java

package com.quesucede.gameoflife;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

public class GridActivity extends Activity {

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.grid);
    }
}

Nothing of interest here, folks... let's move on ;-)

Obviously, the GridActivity needs to have an accompanying res/layout/grid.xml file. At the moment, said layout file is basically empty - later on, the orthogonal grid (for the actual Life patterns) will take up all of the available space within the screen.

grid.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content">
</LinearLayout>

Next, I need to add a couple of resource strings to the res/values/strings.xml.

strings.xml

<string name="grid_title">Game of Life Grid</string>
<string name="settings_label">Settings...</string>
<string name="settings_title">Game of Life Settings</string>
<string name="settings_shortcut">s</string>

And finally (for now), I need to add the appropriate entry for the GridActivity to the AndroidManifest.xml file.

AndroidManifest.xml

<activity android:name=".GridActivity" android:label="@string/grid_title" />

That should conclude the wiring up of the GridActivity and running the application and clicking on the New Game button will display the (still empty) grid screen.


Conway's Game of Life for Android - grid screen


Let's start wiring up the (settings) menu... first of all, I'll need to create res/menu/menu.xml and populate it appropriately.

menu.xml

<?xml version="1.0" encoding="UTF-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/settings" android:title="@string/settings_label"
        android:alphabeticShortcut="@string/settings_shortcut" 
        android:icon="@android:drawable/ic_menu_preferences" />
</menu>

Pending...

Next, some additions to the GridActivity.java code is necessary to create the menu (and bring it up when the user presses the physical Menu button on the device) and respond appropriately to the selection of the menu's button - in this case, the Settings... button.

GridActivity.java

package com.quesucede.gameoflife;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

public class GridActivity extends Activity {

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.grid);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.settings:
                startActivity(new Intent(this, PreferencesActivity.class));
                return true;
        }
        return false;
    }
}

With regards to GridActivity.java, take note of the following:

  • android.view.Menu, android.view.MenuInflater and android.view.MenuItem are imported.
  • Within the onCreateOptionsMenu method a MenuInflater object is created, that in turn reads the menu.xml file and 'inflates' said menu.
  • The onOptionsItemSelected call-back(?) method takes care of the menu selection and starts the appropriate activity; in this case, the PreferencesActivity.

Let's run the application, followed by clicking on the New Game button in the main screen and pressing the menu button on the device, respectively. It should display the Settings menu (button).


Conway's Game of Life for Android - Settings menu


Finally, for the Preferences screen, three additional files need to be created: PreferencesActivity.java, res/xml/settings.xml and finally, res/values/arrays.xml. Specifically, the last two files need some explanation. Android provides a nifty way to define application preferences with hardly any coding required. It does, however, require that you wire things up in a predefined manner. First, you need to create the actual activity.

PreferencesActivity.java

package com.quesucede.gameoflife;

import android.os.Bundle;
import android.preference.PreferenceActivity;

public class PreferencesActivity extends PreferenceActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.settings);
    }
}

Take note of the final line of code in the activity's onCreate method addPreferencesFromResource(R.xml.settings) and compare that to, for example, the GridActivity.onCreate method... in said method, the final line of code to inflate the view is setContentView(R.layout.grid). See the difference?

Now, let's take a look at res/xml/settings.xml.

settings.xml

<?xml version="1.0" encoding="UTF-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <ListPreference android:key="UNDERPOPULATION_VARIABLE"
        android:title="Minimum variable"
        android:summary="Any live cell with fewer than the minimum number of 
        live neighbours dies, as if caused by under-population"
        android:entries="@array/underpopulation_options" 
        android:entryValues="@array/underpopulation_values"
        android:dialogTitle="Minimum variable" android:defaultValue="2" />
    <CheckBoxPreference android:key="COLOR_CODING"
        android:title="@string/color_coding_title" 
        android:summary="@string/color_coding_summary"
        android:defaultValue="false" />
</PreferenceScreen>

Within the settings.xml file, two components have been defined (for the time-being), a ListPreference and a CheckBoxPreference. The ListPreference needs to be populated with two arrays to contain the display text and selection values and that is where res/values/arrays.xml comes into the picture.

arrays.xml

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <string-array name="underpopulation_options">
        <item>Zero live neighbours</item>
        <item>One live neighbour</item>
        <item>Two live neighbours</item>
        <item>Three live neighbours</item>
        <item>Four live neighbours</item>
        <item>Five live neighbours</item>
        <item>Six live neighbours</item>
    </string-array>
    <string-array name="underpopulation_values">
        <item>0</item>
        <item>1</item>
        <item>2</item>
        <item>3</item>
        <item>4</item>
        <item>5</item>
        <item>6</item>
    </string-array>
</resources>

Again, take a close look at the settings.xml file... the two array that have been defined in arrays.xml (underpopulation_options and underpopulation_values) are referenced with @array/underpopulation_options and @array/underpopulation_values, respectively.

Right, the last thing to do is to add the appropriate entry for the PreferencesActivity to the manifest file.

AndroidManifest.xml

<activity android:name=".PreferencesActivity" android:label="@string/settings_title" />

Running the application and selecting the Settings menu will display the preferences screen.


Conway's Game of Life for Android - Settings screen


Do me a favour and take a look again at both settings.xml and the screen shot above... you will be able to see how the UI defined in settings.xml 'maps' directly the preferences screen. Likewise, with the Minimum variable spinner and the two arrays defined in arrays.xml.


Conway's Game of Life for Android - ListPreference


All I have to do now is add the other ListPreference components to settings.xml and accompanying values to arrays.xml to finalize the settings screen.


Conway's Game of Life for Android - Finished Settings screen


In summary, with specific regards to Android UI programming, to a large degree, in addition to Android's concepts of activities, accompanying views, and intents, it really is all about the 'wiring up' - knowing what (type of) file goes where.

Furthermore, keep in mind that I have conveniently glossed over how to interact programmatically with the preferences, something I will leave until later.

Tomorrow, I will start with the orthogonal grid custom component... I think this is where things will really start to get interesting.


Day Six - The custom component - the Life grid

First things first... allow me to apologize for the long delay in getting around to this next step in the tutorial.

Right, the next step is about figuring out how to draw with Android. Normally, when I get to a phase in a software project that I do not know how to tackle in advance, my approach is to break the problem down into discrete parts which allows me to 'isolate' the specific issue that I need to focus on. Hence, for the first part of today's tutorial, I will be creating a new Android project to specifically figure out how to draw the Life patterns in the orthogonal grid (component).

The main activity for this project will be the GridActivity.

GridActivity.java

package com.quesucede.life;

import android.app.Activity;
import android.os.Bundle;

public class GridActivity extends Activity {
    
    private static final int CELL_SIZE = 8;
    private static final int WIDTH = 320 / CELL_SIZE;
    private static final int HEIGHT = 480 / CELL_SIZE;
    
    private static final int[][] gridArray = new int[HEIGHT][WIDTH];
    
    private GridView gridView;
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        initializeGrid();
        
        gridView = new GridView(this);
        
        setContentView(gridView);
        gridView.requestFocus();
    }
    
    @Override
    public void onDestroy() {
        // stop and remove animation thread
    }
    
    public static int[][] getGridArray() {
        return gridArray;
    }
    
    private void initializeGrid() {
        gridArray[10][(WIDTH / 2) -1] = 1;
        gridArray[11][(WIDTH / 2) -1] = 1;
        gridArray[12][(WIDTH / 2)] = 1;
        gridArray[10][(WIDTH / 2) +1] = 1;
        gridArray[11][(WIDTH / 2) +1] = 1;
    }
}

Let's take a look at GridActivity's source:

  • A couple of constants are defined, obviously CELL_SIZE is what it is, the height and width (in pixels) for each individual cell.
  • WIDTH and HEIGHT define the size of the grid. As you can see, I am using some magic numbers (320 and 480 pixels, the Android baseline HVGA screen size) to calculate the width and height of the orthogonal grid - this would be a shooting offense in production code, but in this case, I need to focus on bigger and better things ;-)
  • The creation of the two-dimensional (static) grid variable: gridArray.
  • The declaration of the GridView component (view). This is really where all the action takes place and I will be covering this component, shortly.
  • The usual (overridden) onCreate method:
    • A call to the initializeGrid method to define an arbitrary Life pattern.
    • The creation of the actual GridView component.
    • A call to the setContentView method with the GridView component that was created in the previous line of code passed as a parameter. What does this mean? Well, up until now, we would normally have created the accompanying (XML) view in res/layout and passed the appropriate reference to said view to the setContentView method for Android to 'inflate' it... in this case, the view is defined in (procedural) Java code instead of a (declarative) XML view. This last point is important - I absolutely have to do it this way (as far as I know) because I need to (after having extended Android's View class) implement my custom draw code in the overridden onDraw method of the GridView class.

GridView.java

package com.quesucede.life;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;

public class GridView extends View {
    
    private static final int CELL_SIZE = 8;
    private static final int WIDTH = 320 / CELL_SIZE;
    private static final int HEIGHT = 480 / CELL_SIZE;
    
    public GridView(Context context) {
        super(context);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Paint background = new Paint();
        background.setColor(getResources().getColor(R.color.background));
        
        Paint cell = new Paint();
        cell.setColor(getResources().getColor(R.color.cell));
        
        // draw background
        canvas.drawRect(0, 0, getWidth(), getHeight(), background);
        
        // draw cells
        for (int h = 0; h < HEIGHT; h++) {
            for (int w = 0; w < WIDTH; w++) {
                if (GridActivity.getGridArray()[h][w] != 0) {
                    canvas.drawRect(
                        w * CELL_SIZE, 
                        h * CELL_SIZE, 
                        (w * CELL_SIZE) +CELL_SIZE,
                        (h * CELL_SIZE) +CELL_SIZE,
                        cell);
                }
            }
        }
    }
}

Right, what's going on here?

  • After having imported the appropriate classes, the class GridView extends (inherits from) the View class.
  • The same constants that were defined in the GridActivity.java file are declared again (I know, I know... just remember that I'm focusing on bigger and better things :-))
  • Definition of the onDraw (overridden) method:
    • Creation of a Paint object (background) whose colour is set by referencing the background ID (defined in res/values/colors.xml).
    • Creation of another Paint object (cell).
    • The background is drawn by calling canvas.drawRect method with the appropriate parameters passed to it (i.e., left and top coordinates with accompanying width and height followed by the previously defined background paint object).
    • Looping over the two-dimensional grid (the normal pattern with the outer for statement looping vertically and the inner for statement looping horizontally).
    • An if statement to check if the current cell (defined by its combined vertical and horizontal position) is 'alive' (!= 0); if it is alive, then the canvas.drawRect method with the appropriate parameters (x/y-coordinates, width and height size, and paint object, respectively) is called.

Conway's Game of Life for Android - orthogonal grid component


Day Seven - The 'game loop'

Well, let me try and finish the second part of this tutorial so that I can finally get on to the third and final part of polishing the application and publishing it on the Android Market.

Furthermore, today, I will show how I have integrated the (drawing) code from Day Six of the tutorial into the GameOfLife project.

Relevant URLs

I'm using one of Android's convenience classes to synchronize with the GUI thread before updating the UI, in this case the Handler class for processing messages, that is, running tasks on the message thread. I chose to use Handler because it seemed the easiest way to implement the game loop. It's important to keep in mind that it is the game loop that drives the game's sub-systems, in this case, the next generation algorithm and the accompanying animation of each successive generation.

Well, let's start. I've made changes to the following (already existing) source files:

  • GridActivity.java
  • GridView.java
  • grid.xml
  • PreferencesActivity.java
  • settings.xml
  • arrays.xml

Furthermore, I have created a new source file, Life.java in which I have encapsulated all of the methods for the next generation algorithm. But let's start looking at the above-mentioned files, one by one.

GridActivity.java

package com.quesucede.gameoflife;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

public class GridActivity extends Activity {

    private GridView _gridView;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.grid);
        
        _gridView = (GridView)findViewById(R.id.grid_view); 
        _gridView.setMode(GridView.RUNNING);
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.settings:
                startActivity(new Intent(this, PreferencesActivity.class));
                return true;
        }
        return false;
    }
    
    @Override
    protected void onPause() {
        super.onPause();
        _gridView.setMode(GridView.PAUSE);
    }
}

With regards to GridActivity.java, take note of the following:

  • The CELL_SIZE, WIDTH, and HEIGHT constants have been removed, as well as the two-dimensional grid array (they have all been moved to Life.java). Furthermore, within the next day or two, the hard-coding within the definition of the WIDTH (320) and HEIGHT (480) constants will be removed.
  • Changed back to inflating the grid.xml (declarative) layout file (setContentView(R.layout.grid)) instead of using the grid component directly (setContentView(gridView)).
  • A reference to the gridView component is obtained by means of its ID (gridView = (GridView)findViewById(R.id.grid_view)); that way, the gridView's setMode method can be called to set the game's current state (note: there will be a couple of changes with regards to the state handling for the game).
  • Finally, the overridden onPause method has been added.

The next file, GridView.java has probably become the most complex file within the whole project and warrants examining in depth:

GridView.java

package com.quesucede.gameoflife;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.View;

public class GridView extends View {
    
    public static final int PAUSE = 0;
    public static final int RUNNING = 1;
    
    private Life _life;
    
    private long _moveDelay = 250;
    
    private RefreshHandler _redrawHandler = new RefreshHandler();

    class RefreshHandler extends Handler {

        @Override
        public void handleMessage(Message message) {
            GridView.this.update();
            GridView.this.invalidate();
        }

        public void sleep(long delayMillis) {
            this.removeMessages(0);
            sendMessageDelayed(obtainMessage(0), delayMillis);
        }
    };

    public GridView(Context context, AttributeSet attrs) {
        super(context, attrs);
        _life = new Life(context);
        initGridView();
    }
    
    public void setMode(int mode) {
        if (mode == RUNNING) {
            update();
            return;
        }
        if (mode == PAUSE) {
            // TODO: implement.
        }
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        Paint background = new Paint();
        background.setColor(getResources().getColor(R.color.background));

        Paint cell = new Paint();
        cell.setColor(getResources().getColor(R.color.cell));

        // draw background
        canvas.drawRect(0, 0, getWidth(), getHeight(), background);

        // draw cells
        for (int h = 0; h < Life.HEIGHT; h++) {
            for (int w = 0; w < Life.WIDTH; w++) {
                if (Life.getGrid()[h][w] != 0) {
                    canvas.drawRect(
                        w * Life.CELL_SIZE, 
                        h * Life.CELL_SIZE, 
                        (w * Life.CELL_SIZE) + (Life.CELL_SIZE -1),
                        (h * Life.CELL_SIZE) + (Life.CELL_SIZE -1),
                        cell);
                }
            }
        }
    }
    
    private void update() {
        _life.generateNextGeneration();
        _redrawHandler.sleep(_moveDelay);
    }
    
    private void initGridView() {
        setFocusable(true);
    }
}
Relevant URLs

If you take a close look at the code above, you will see a pattern that I have copied from one of the Android samples ('Use the Source, Luke') that you can download as part of the Android SDK, namely, Snake. The pattern I am referring to is the creation of a handler that can be used to cause animation to happen: setting the view as a target and using the sleep() method to bring about an update/invalidate to occur at regular intervals. This is all happening in the following code snippet:

private long _moveDelay = 250;
    
private RefreshHandler _redrawHandler = new RefreshHandler();

class RefreshHandler extends Handler {

    @Override
    public void handleMessage(Message message) {
        GridView.this.update();
        GridView.this.invalidate();
    }

    public void sleep(long delayMillis) {
        removeMessages(0);
        sendMessageDelayed(obtainMessage(0), delayMillis);
    }
};

Let's take a look at the above snippet:

  • A 250 milliseconds delay variable is declared (side note: in the next stage of the tutorial, this variable will be used to control the varying speed of the animation of the Life patterns).
  • A (redraw) handler object is created and subsequently defined as an inner class. Said inner class extends (inherits from) Handler:
    • the overriden handleMessage is defined which in turn calls its outer class' update and (inherited) invalidate methods.
    • The sleep method is defined with an accompanying delay in milliseconds (delayMillis) parameter; removeMessages does exactly what it says on the tin: it removes a message that is currently in the message queue. Likewise, sendMessageDelayed enqueues a message into the message queue which will be received into the handleMessage method, in the thread attached to the handler.

The messages that are enqueued into the message queue are, to all intents and purposes, 'dummy' messages. That is, there is no conditional processing based on the content of the messages - what is important, is the actual setting up of the appropriate mechanism of updating the UI in response to the (periodic) sending (and receiving) of messages.

The next bit of code to examine is the view's constructor.

public GridView(Context context, AttributeSet attrs) {
    super(context, attrs);
    _life = new Life(context);
    initGridView();
}

Take note of the parameters that are being passed, specifically the second parameter: AttributeSet attrs. This parameter is required if you declare the view (component) in a layout file. In this instance, the GridView component (com.quesucede.gameoflife.GridView) is declared in the res/layout/grid.xml (layout) file.

grid.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent" android:layout_height="fill_parent">
    <com.quesucede.gameoflife.GridView
        android:id="@+id/grid_view" 
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
</FrameLayout>

Going back to the view's constructor, the next line of code instantiates the _life object and passes the current context to it (a bit later on, you'll see why). Remember, the Life class encapsulates the Game of Life's algorithm and accompanying helper methods.

Next, if you compare the onPaint method to the original code developed in Day Six you will see that there are no significant changes.

Finally, let's take a look at the update method.

private void update() {
    _life.generateNextGeneration();
    _redrawHandler.sleep(_moveDelay);
}

As small as it is, the update method is crucial for two reasons: the game's next generation is calculated here (_life.generateNextGeneration()) and it calls the redrawHandler's sleep method. It is this final call to the sleep method that keeps the pendulum swinging, that is, the following sequence of events is taking place:

  1. The GridActivity.onCreate method has the following call to the view's setMode method: _gridView.setMode(GridView.RUNNING).
  2. Within said setMode method, if the mode parameter equals RUNNING, it calls the view's update method which, as we have just seen, calculates the next generation's Life pattern and subsequently calls the the sleep method.
  3. The sleep method removes the pending message (removeMessages(0)) and enqueues a new message into the message queue with a delay (sendMessageDelayed(obtainMessage(0), delayMillis)) that will be (automatically) handled by the overridden handleMessage method.
  4. The handleMessage method calls the update method and invalidates the view signaling to Android to redraw the the view by calling the (overridden) onDraw method.

After the initial call to update the application basically 'cycles' between the GridView.update, RefreshHandler.sleep and RefreshHandler.handleMessage methods.

In terms of application logic, that's it... we have a 'game loop' and in said game loop the next generation is calculated and the grid is redrawn.

Let's move on. The next class to look at is PreferencesActivity. Specifically, let's see how we can interact with the (user) preferences.

PreferencesActivity.java

package com.quesucede.gameoflife;

import android.content.Context;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;

public class PreferencesActivity extends PreferenceActivity {

    private static final String OPTION_MINIMUM = "UNDERPOPULATION_VARIABLE";
    private static final String OPTION_MINIMUM_DEFAULT = "2";
    private static final String OPTION_MAXIMUM = "OVERPOPULATION_VARIABLE";
    private static final String OPTION_MAXIMUM_DEFAULT = "3";
    private static final String OPTION_SPAWN = "SPAWN_VARIABLE";
    private static final String OPTION_SPAWN_DEFAULT = "3";
    private static final String OPTION_ANIMATION_SPEED = "ANIMATION_SPEED_VARIABLE";
    private static final String OPTION_ANIMATION_SPEED_DEFAULT = "3";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.settings);
    }
    
    public static String getMinimumVariable(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context).
            getString(OPTION_MINIMUM, OPTION_MINIMUM_DEFAULT);
    }
    
    public static String getMaximumVariable(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context).
            getString(OPTION_MAXIMUM, OPTION_MAXIMUM_DEFAULT);
    }
    
    public static String getSpawnVariable(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context).
            getString(OPTION_SPAWN, OPTION_SPAWN_DEFAULT);
    }
    
    public static String getAnimationSpeed(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context).
            getString(OPTION_ANIMATION_SPEED, OPTION_ANIMATION_SPEED_DEFAULT);
    }
}
Relevant URLs

In comparison to Day Five's PreferencesActivity, the only thing of interest is the addition of four static methods: getMinimumVariable, getMaximumVariable, getSpawnVariable, and finally, getAnimationSpeed (acting on the animation speed preference has not been implemented, yet). Really, calling the above mentioned static methods is all it takes to (programmatically) access the application's preferences (an example of accessing the preferences can been seen in Life.java, below).

For the sake of completeness, both settings.xml and arrays.xml are provided below.

settings.xml

<?xml version="1.0" encoding="UTF-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <ListPreference android:key="UNDERPOPULATION_VARIABLE"
        android:title="Minimum variable"
        android:summary="Any live cell with fewer than the minimum number of 
        live neighbours dies, as if by under-population"
        android:entries="@array/population_options" 
        android:entryValues="@array/population_values"
        android:dialogTitle="Minimum variable" 
        android:defaultValue="2" />
    <ListPreference android:key="OVERPOPULATION_VARIABLE"
        android:title="Maximum variable"
        android:summary="Any live cell with more than the maximum number of 
        live neighbours dies, as if by overcrowding"
        android:entries="@array/population_options" 
        android:entryValues="@array/population_values"
        android:dialogTitle="Maximum variable" 
        android:defaultValue="3" />
    <ListPreference android:key="SPAWN_VARIABLE"
        android:title="Spawn variable"
        android:summary="Any dead cell with exactly the spawn number of live 
        neighbours becomes a live cell"
        android:entries="@array/population_options" 
        android:entryValues="@array/population_values"
        android:dialogTitle="Spawn variable" 
        android:defaultValue="3" />
    <ListPreference android:key="ANIMATION_SPEED_VARIABLE"
        android:title="Animation speed"
        android:entries="@array/animation_speed_options" 
        android:entryValues="@array/animation_speed_values"
        android:dialogTitle="Animation speed" 
        android:defaultValue="3" />
    <CheckBoxPreference android:key="COLOR_CODING"
        android:title="@string/color_coding_title" 
        android:summary="@string/color_coding_summary"
        android:defaultValue="false" />
</PreferenceScreen>

arrays.xml

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <string-array name="population_options">
        <item>Zero live neighbours</item>
        <item>One live neighbour</item>
        <item>Two live neighbours</item>
        <item>Three live neighbours</item>
        <item>Four live neighbours</item>
        <item>Five live neighbours</item>
        <item>Six live neighbours</item>
    </string-array>
    <string-array name="population_values">
        <item>0</item>
        <item>1</item>
        <item>2</item>
        <item>3</item>
        <item>4</item>
        <item>5</item>
        <item>6</item>
    </string-array>
    <string-array name="animation_speed_options">
       <item>Very slow</item>
       <item>Slow</item>
       <item>Normal</item>
       <item>Fast</item>
       <item>Very fast</item>
    </string-array>
    <string-array name="animation_speed_values">
       <item>5</item>
       <item>4</item>
       <item>3</item>
       <item>2</item>
       <item>1</item>
    </string-array>
</resources>

Finally, let's take a (somewhat superficial) look at Life.java (with the application's next generation algorithm and helper methods).

Life.java

package com.quesucede.gameoflife;

import android.content.Context;

public class Life {

    public static final int CELL_SIZE = 8;
    public static final int WIDTH = 320 / CELL_SIZE;
    public static final int HEIGHT = 480 / CELL_SIZE;

    private static final int[][] _lifeGrid = new int[HEIGHT][WIDTH];

    private Context _context;

    public Life(Context context) {
        this._context = context;
        initializeGrid();
    }

    public static int[][] getGrid() {
        return _lifeGrid;
    }

    public void initializeGrid() {
        resetGrid(_lifeGrid);

        _lifeGrid[8][(WIDTH / 2) - 1] = 1;
        _lifeGrid[8][(WIDTH / 2) + 1] = 1;
        _lifeGrid[9][(WIDTH / 2) - 1] = 1;
        _lifeGrid[9][(WIDTH / 2) + 1] = 1;
        _lifeGrid[10][(WIDTH / 2) - 1] = 1;
        _lifeGrid[10][(WIDTH / 2)] = 1;
        _lifeGrid[10][(WIDTH / 2) + 1] = 1;
    }

    public void generateNextGeneration() {
        int neighbours;
        int minimum = Integer.parseInt(PreferencesActivity
                .getMinimumVariable(this._context));
        int maximum = Integer.parseInt(PreferencesActivity
                .getMaximumVariable(this._context));
        int spawn = Integer.parseInt(PreferencesActivity
                .getSpawnVariable(this._context));

        int[][] nextGenerationLifeGrid = new int[HEIGHT][WIDTH];

        for (int h = 0; h < HEIGHT; h++) {
            for (int w = 0; w < WIDTH; w++) {
                neighbours = calculateNeighbours(h, w);

                if (_lifeGrid[h][w] != 0) {
                    if ((neighbours >= minimum) && (neighbours <= maximum)) {
                        nextGenerationLifeGrid[h][w] = neighbours;
                    }
                } else {
                    if (neighbours == spawn) {
                        nextGenerationLifeGrid[h][w] = spawn;
                    }
                }
            }
        }
        copyGrid(nextGenerationLifeGrid, _lifeGrid);
    }

    private void resetGrid(int[][] grid) {
        for (int h = 0; h < HEIGHT; h++) {
            for (int w = 0; w < WIDTH; w++) {
                grid[h][w] = 0;
            }
        }
    }

    private int calculateNeighbours(int y, int x) {
        int total = (_lifeGrid[y][x] != 0) ? -1 : 0;
        for (int h = -1; h <= +1; h++) {
            for (int w = -1; w <= +1; w++) {
                if (_lifeGrid[(HEIGHT + (y + h)) % HEIGHT][(WIDTH + (x + w))
                        % WIDTH] != 0) {
                    total++;
                }
            }
        }
        return total;
    }

    private void copyGrid(int[][] source, int[][] destination) {
        for (int h = 0; h < HEIGHT; h++) {
            for (int w = 0; w < WIDTH; w++) {
                destination[h][w] = source[h][w];
            }
        }
    }
}
Relevant URLs

With regards to the actual algorithm, it is very similar to my original Java applet Game of Life implemention. From an Android development point-of-view, the interaction with the applications preferences is interesting.

  • In the constructor, the private _context variable is set.
  • In the generateNextGeneration method, the minimum, maximum, and spawn variables are set by means of calling the static methods defined in the PreferencesActivity class. For example, int minimum = Integer.parseInt(PreferencesActivity.getMinimumVariable(this._context)); in this example you can see why it is necessary to have access to the context... the static getters (in the PreferencesActivity class) require that the context is passed to them.

And that's it. The second part of the tutorial has been concluded!


Conway's Game of Life for Android - Life pattern


There are still a couple of loose ends that definitely need tying up, for example, the hard-coding of the screen's dimensions and the animation speed preference that still needs implementing. Furthermore, I still need to implement the saving (and subsequent restoring) of the application's state (so that, for example, a game can be resumed). In addition, I would like to implement the possibility of having several initial game states (with interesting Life patterns) that the user can select from. Finally, I would like to implement a so-called Dashboard as outlined in the Twitter for Android: A closer look at Android’s evolving UI patterns Android Developers blog article.

All in all, the application is ninety percent done, but it is that last ten percent that is the hardest to do but makes all the difference. That is, wanting to develop something that is not standard but excellent, not ordinary but extraordinary.

I have uploaded the application's Eclipse project directory. I recommend downloading it to examine the project in its entirety.

This tutorial has a third part: Android tutorial part 3 - Conway's Game of Life.




Comments

Work in progress@10-06-06 21:50:21 by Brett Kromkamp

For anyone that stumbles onto this page... please keep in mind that this is a work in progress. Something that will be added to and updated over the course of the next few weeks starting from June 06, 2010.

Feedback@10-06-12 21:47:00 by Brett Kromkamp

If I have skipped anything or not explained something clearly enough, just let me know... leave a comment below and I will try to clarify.






Google