Android Open Source - GameOfLife Game Of Life View






From Project

Back to project page GameOfLife.

License

The source code is released under:

GNU General Public License

If you think the Android project GameOfLife listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*******************************************************************************
 * Copyright (c) 2011-2013 Pieter Pareit.
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version./*  w w w .j  av a 2s .co m*/
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors:
 *     Pieter Pareit - initial API and implementation
 ******************************************************************************/
package be.ppareit.gameoflife;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.WindowManager;
import android.widget.Toast;
import be.ppareit.android.GameLoopView;

/**
 * The main view for the Game of Life.
 *
 * Responsible for drawing the data. Intercepts touch events. When the mode is set to
 * EDITING, it will add or remove data. When the mode is set to MOVING, panning and
 * zooming is done. When the mode is set to PLAYING, the game loop is started and the data
 * is updated to a new generation at each step.
 */
public class GameOfLifeView extends GameLoopView implements
        SharedPreferences.OnSharedPreferenceChangeListener {

    private static final String TAG = GameOfLifeView.class.getSimpleName();

    public enum State {
        RUNNING, EDITING, MOVING,
    }

    private State mState = State.MOVING;

    private static GameOfLife mGameOfLife = null;
    private static UndoManager mUndoManager = null;

    private Paint mCanvasPaint = null;
    private Paint mBackgroundPaint = null;
    private Drawable mLiveCell = null;
    private Drawable mDeadCell = null;

    // this object detects move and single tap events
    private final GestureDetector mMoveDetector;

    // this object detects move events, and continuously toggles cell state
    private final EditListner mEditListner;

    // this object can detect pinch to zoom touch events
    private final ScaleGestureDetector mScaleDetector;

    private Matrix mDrawMatrix = new Matrix();

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

        Settings settings = Settings.getSettings(context);

        if (mGameOfLife == null) {
            int rows = settings.getRows();
            int cols = settings.getCols();

            mGameOfLife = new GameOfLife(rows, cols);
            mGameOfLife.setUnderPopulation(settings.getMinimumVariable());
            mGameOfLife.setOverPopulation(settings.getMaximumVariable());
            mGameOfLife.setSpawn(settings.getSpawnVariable());
            mGameOfLife.loadGridFromFile(getResources().openRawResource(R.raw.android));

            mUndoManager = new UndoManager(mGameOfLife);
        }

        setTargetFps(settings.getAnimationSpeed());

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        prefs.registerOnSharedPreferenceChangeListener(this);

        mEditListner = new EditListner();
        mMoveDetector = new GestureDetector(context, new MoveListner());
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListner());

        initTheme(context);
    }

    private void initTheme(Context context) {
        Settings settings = Settings.getSettings(context);
        int theme = settings.getDisplayTheme();
        Resources res = getResources();
        TypedArray themeData = res.obtainTypedArray(theme);

        mCanvasPaint = new Paint();
        mCanvasPaint.setColor(Color.GRAY);

        mBackgroundPaint = new Paint();
        mBackgroundPaint.setColor(themeData.getColor(0, Color.BLACK));

        mDeadCell = themeData.getDrawable(1);
        mLiveCell = themeData.getDrawable(2);

        themeData.recycle();
    }

    public void setMode(State mode) {
        if (mode == State.RUNNING && mState != State.RUNNING) {
            mUndoManager.pushState();
            startGameLoop();
            mState = State.RUNNING;
        } else if (mode == State.EDITING && mState != State.EDITING) {
            pauseGameLoop();
            mState = State.EDITING;
        } else if (mode == State.MOVING && mState != State.MOVING) {
            pauseGameLoop();
            mState = State.MOVING;
        }
    }

    public State getGameState() {
        return mState;
    }

    @Override
    protected void onUpdate() {
        mGameOfLife.generateNextGeneration();
    }

    /**
     * @todo this can faster, just draw what needs to be drawn in case we are zoomed in
     */
    @Override
    protected void onDraw(Canvas canvas) {

        final int rows = mGameOfLife.getRows();
        final int cols = mGameOfLife.getCols();

        final int[][] grid = mGameOfLife.getGrid();

        canvas.drawRect(0, 0, getWidth(), getHeight(), mCanvasPaint);

        canvas.save();

        canvas.concat(mDrawMatrix);

        canvas.drawRect(0, 0, rows, cols, mBackgroundPaint);

        // addition is faster then multiplication combined with modulo,
        // so keep track of correct drawing position and update it every step
        int left = 0;
        int top = 0;
        // for all rows and all cols
        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                // draw the cell
                final Drawable cell = (grid[r][c] != 0) ? mLiveCell : mDeadCell;
                cell.setBounds(left, top, left + 1, top + 1);
                cell.draw(canvas);
                // check if at bound
                if (top + 1 > rows) {
                    // redraw cell, but at bottom
                    cell.setBounds(left, top - rows, left + 1, top - rows + 1);
                    cell.draw(canvas);
                }
                // reposition left
                left += 1;
            }
            left = 0;
            top += 1;
        }
        canvas.restore();
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        Settings settings = Settings.getSettings(getContext());

        mGameOfLife.setUnderPopulation(settings.getMinimumVariable());
        mGameOfLife.setOverPopulation(settings.getMaximumVariable());
        mGameOfLife.setSpawn(settings.getSpawnVariable());

        setTargetFps(settings.getAnimationSpeed());

        initTheme(getContext());
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mDrawMatrix.reset();

        // make center the left-top position
        final int cols = mGameOfLife.getCols();
        final int rows = mGameOfLife.getRows();
        mDrawMatrix.postTranslate(-cols / 2, -rows / 2);

        // scale to display metrics
        DisplayMetrics metrics = new DisplayMetrics();
        WindowManager wm = (WindowManager) getContext().getSystemService(
                Context.WINDOW_SERVICE);
        wm.getDefaultDisplay().getMetrics(metrics);

        final float scale = 20 * metrics.density;
        mDrawMatrix.postScale(scale, scale);
        Log.d(TAG, "Size changed, new scale: " + scale);

        // move left-top position to middle of screen
        mDrawMatrix.postTranslate(getWidth() / 2, getHeight() / 2);

    }

    private class MoveListner extends GestureDetector.SimpleOnGestureListener {

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
                float distanceY) {
            mDrawMatrix.postTranslate(-distanceX, -distanceY);
            return true;
        }

        /**
         * Even in the MOVING state we provide some editing, this by single tapping
         */
        @Override
        public boolean onSingleTapUp(MotionEvent e) {

            float[] pts = { e.getX(), e.getY() };
            Matrix inverse = new Matrix();
            mDrawMatrix.invert(inverse);
            inverse.mapPoints(pts);
            final int c = (int) pts[1];
            final int r = (int) pts[0];
            Log.d(TAG, "Tapped: " + c + "  " + r);
            final int cols = mGameOfLife.getCols();
            final int rows = mGameOfLife.getRows();

            if (0 <= c && c < cols && 0 <= r && r < rows) {
                mUndoManager.pushState();
                int[][] grid = mGameOfLife.getGrid();
                grid[c][r] = (grid[c][r] != 0 ? 0 : 1);
            }
            return true;
        }

    }

    private class EditListner {

        public void onTouchEvent(MotionEvent event) {
            mUndoManager.pushState();
            int historySize = event.getHistorySize();
            for (int h = 0; h < historySize; ++h) {
                doEdit((int) event.getHistoricalX(h), (int) event.getHistoricalY(h));
            }
            doEdit((int) event.getX(), (int) event.getY());
        }

        private int mPreviousFlippedRow = -1;
        private int mPreviousFlippedCol = -1;
        private long mPreviousFlippedTime = 0;

        private void doEdit(int x, int y) {

            float[] pts = { x, y };
            Matrix inverse = new Matrix();
            mDrawMatrix.invert(inverse);
            inverse.mapPoints(pts);
            final int c = (int) pts[0];
            final int r = (int) pts[1];
            final int cols = mGameOfLife.getCols();
            final int rows = mGameOfLife.getRows();

            if (0 <= c && c < cols && 0 <= r && r < rows) {
                if (mPreviousFlippedCol == c && mPreviousFlippedRow == r
                        && mPreviousFlippedTime + 500 > System.currentTimeMillis()) {
                    return;
                } else {
                    mPreviousFlippedCol = c;
                    mPreviousFlippedRow = r;
                    mPreviousFlippedTime = System.currentTimeMillis();
                }

                mGameOfLife.getGrid()[r][c] = (mGameOfLife.getGrid()[r][c] != 0 ? 0 : 1);
            }
        }

    }

    private float getScale() {
        return mDrawMatrix.mapRadius(1.f);
    }

    /**
     * Callback for the ScaleGestureDetector
     */
    private class ScaleListner extends ScaleGestureDetector.SimpleOnScaleGestureListener {

        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            float factor = detector.getScaleFactor();

            // limit zooming
            final float scale = factor * getScale();
            if (scale < 10.f || 100.f < scale) {
                return false;
            }

            final float focusX = detector.getFocusX();
            final float focusY = detector.getFocusY();
            mDrawMatrix.postScale(factor, factor, focusX, focusY);

            Log.d(TAG, "New scale: " + getScale());
            return true;
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (mState) {
        case EDITING:
            // Let pinch to zoom process the event
            mScaleDetector.onTouchEvent(event);
            // if that one is not doing anything, lets edit
            if (mScaleDetector.isInProgress() == false) {
                mEditListner.onTouchEvent(event);
            }
            break;

        case MOVING:
        case RUNNING:
            // Let pinch to zoom process the event
            mScaleDetector.onTouchEvent(event);
            // give the gesture detector the event
            mMoveDetector.onTouchEvent(event);
            break;
        }

        invalidate();
        return true;
    }

    public void clearGrid() {
        mUndoManager.pushState();
        mGameOfLife.resetGrid();
        invalidate();
    }

    public void doSingleStep() {
        mUndoManager.pushState();
        mGameOfLife.generateNextGeneration();
        invalidate();
    }

    public boolean canUndo() {
        return mUndoManager.canUndo();
    }

    public void doUndo() {
        mUndoManager.popState();
        invalidate();
    }

    public void doLoad(InputStream is) {
        mGameOfLife.loadGridFromFile(is);
        invalidate();
    }

    public void doLoad(Uri uri) {
        Context context = getContext();
        try {
            File file = new File(uri.getPath());
            InputStream is;
            is = new FileInputStream(file);
            mGameOfLife.loadGridFromFile(is);
        } catch (FileNotFoundException e) {
            Toast.makeText(context, R.string.file_not_found, Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        } catch (GameOfLife.FormatNotSupportedException e) {
            Toast.makeText(context, R.string.file_format_not_supported, Toast.LENGTH_LONG)
                    .show();
            e.printStackTrace();
        }
    }

    public void doSave(Uri uri) {
        Context context = getContext();
        File file = new File(uri.getPath());
        try {
            OutputStream out = new FileOutputStream(file);
            mGameOfLife.saveGridToFile(out);
        } catch (FileNotFoundException e) {
            Toast.makeText(context, R.string.file_not_found, Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
    }
}




Java Source Code List

be.ppareit.android.GameLoopView.java
be.ppareit.gameoflife.GameOfLifeView.java
be.ppareit.gameoflife.GameOfLife.java
be.ppareit.gameoflife.UndoManager.java