nl.vincentketelaars.mexen.activities.RollDice.java Source code

Java tutorial

Introduction

Here is the source code for nl.vincentketelaars.mexen.activities.RollDice.java

Source

/*  This file is part of Simple Dice.
 *
 *  Simple Dice 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.
 *
 *  Simple Dice 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 Simple Dice.  If not, see <http://www.gnu.org/licenses/>.
 */

package nl.vincentketelaars.mexen.activities;

import java.util.Locale;
import java.util.Random;

import nl.vincentketelaars.mexen.R;
import nl.vincentketelaars.mexen.database.MexGameDbHelper;
import nl.vincentketelaars.mexen.objects.Game;
import nl.vincentketelaars.mexen.objects.GameMode;
import nl.vincentketelaars.mexen.objects.Throw;
import nl.vincentketelaars.mexen.objects.Turn;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.Display;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

public abstract class RollDice extends GenericActivity implements SensorEventListener {
    private Activity activity;
    private final int rollAnimations = 50;
    private final int delayTime = 15;
    private final int[] diceImages = new int[] { R.drawable.d1, R.drawable.d2, R.drawable.d3, R.drawable.d4,
            R.drawable.d5, R.drawable.d6 };
    private Drawable dice[] = new Drawable[6];
    private final Random randomGen = new Random();
    private int roll[];
    private ImageView[] dies;
    private ImageView[] vastImages;
    private boolean[] vast;
    private Handler animationHandler;
    private TextView chanceTextView;
    private TextView higherChanceTextView;
    protected Button throwButton;
    private SoundPool soundPool;
    private SparseIntArray soundMap;

    protected GameMode currentMode = GameMode.FREEPLAY;
    protected Game currentGame;
    protected Turn currentTurn;
    private MexGameDbHelper MGDbHelper;
    private SensorManager sensorManager;
    private float MINIMUM_ACCELARATION = 2;
    private boolean throwOnShake;

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

        // Make title bar icon clickable, and go home
        getActionBar().setDisplayHomeAsUpEnabled(true);

        setVolumeControlStream(AudioManager.STREAM_MUSIC); // User can modify music volume
        soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
        soundMap = new SparseIntArray();
        soundMap.put(R.raw.roll, soundPool.load(this, R.raw.roll, 1)); // Note: If load returns 0 it failed

        activity = this;

        currentGame = new Game(currentMode);

        MGDbHelper = new MexGameDbHelper(this);

        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

        getActionBar().setTitle(String.format(getResources().getString(R.string.roll_activity_name), numDice()));
    }

    protected Point getSize() {
        Display display = getWindowManager().getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        return size;
    }

    protected void setupViews() {
        chanceTextView = (TextView) findViewById(R.id.throw_chance_result_textview);
        higherChanceTextView = (TextView) findViewById(R.id.throw_higher_chance_result_textview);

        throwButton = (Button) findViewById(R.id.throw_button);
        throwButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                rollDice();
            }
        });
    }

    protected void setupDice(ImageView[] diesIn, ImageView[] vastImagesIn, int[] rollIn, boolean vastIn[]) {
        this.dies = diesIn;
        this.vastImages = vastImagesIn;
        this.roll = rollIn;
        this.vast = vastIn;

        // Get the dice
        for (int i = 0; i < diceImages.length; i++) {
            dice[i] = getResources().getDrawable(diceImages[i]);
        }

        // Set drawable
        animationHandler = new Handler(new Handler.Callback() {
            public boolean handleMessage(Message msg) {
                for (int i = 0; i < dies.length; i++) {
                    dies[i].setImageDrawable(dice[roll[i]]);
                }
                return true;
            }
        });

        // Listener that increments the number on the dice
        OnClickListener diceListener = new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!changeDiceValueAllowed())
                    return;
                for (int i = 0; i < dies.length; i++) {
                    if (dies[i].getId() == v.getId() && !vast[i]) {
                        roll[i] = (roll[i] + 1) % diceImages.length;
                    }
                }
                animationHandler.sendEmptyMessage(0);
                evaluateChances();
            }
        };

        // Listener that sets vast
        OnLongClickListener vastListener = new OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                int numVast = 0;
                for (boolean v : vast) {
                    if (v)
                        numVast++;
                }
                for (int i = 0; i < dies.length; i++) {
                    if (dies[i].getId() == view.getId()) {
                        if (vastImages[i].getVisibility() == View.VISIBLE) {
                            setVast(i, false);
                            break;
                        }
                        if (isSpecialVastCase())
                            break;
                        if (vastImages[i].getVisibility() == View.INVISIBLE && numVast < dies.length - 1
                                && roll[i] < getHighestVastNumber()) { // Only vast till highest allowed number
                            setVast(i, true);
                        }
                    }
                }
                evaluateChances();
                return true;
            }
        };

        for (ImageView d : dies) {
            d.setOnClickListener(diceListener); // Set the listener for each dice
            d.setOnLongClickListener(vastListener); // Set the long listener for each dice
        }
        evaluateChances();
    }

    protected abstract boolean isSpecialVastCase();

    private void rollDice() {
        setThrowButtonEnabled(false);
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < rollAnimations; i++) {
                    doRoll();
                }
                evaluateChances();
                afterRollDice();
                setThrowButtonEnabled(true);
            }
        }).start();
        soundPool.play(soundMap.get(R.raw.roll), 1f, 1f, 0, 0, 1f); // Play once at current volume
    }

    private void setThrowButtonEnabled(final boolean enabled) {
        throwButton.post(new Runnable() {
            @Override
            public void run() {
                throwButton.setEnabled(enabled);
            }
        });
    }

    private void doRoll() { // only does a single roll
        for (int i = 0; i < dies.length; i++) {
            if (!vast[i])
                roll[i] = randomGen.nextInt(6);
        }
        synchronized (getLayoutInflater()) {
            animationHandler.sendEmptyMessage(0);
        }
        try { // delay to alloy for smooth animation
            Thread.sleep(delayTime);
        } catch (final InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * Evaluate the chances of throwing the current throw and higher and set these values to the 
     */
    protected void evaluateChances() {
        // Calculate chances before going on UI thread
        final float throwChance = determineThrowChance();
        final float higherChance = determineHigherChance();
        activity.runOnUiThread(new Runnable() {
            public void run() {
                setChances(throwChance, higherChance);
            }
        });
    }

    protected abstract float determineThrowChance();

    protected abstract float determineHigherChance();

    private String floatToPercentage(float x) {
        return String.format(Locale.getDefault(), "%.2f%%", x * 100);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        // Respond to the action bar's Up/Home button
        case android.R.id.home:
            onExitingGame(false, true);
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    protected void setChances(float diceThrough, float diceHigher) {
        chanceTextView.setText(floatToPercentage(diceThrough));
        higherChanceTextView.setText(floatToPercentage(diceHigher));
    }

    protected int getRoll(int index) {
        return roll[index];
    }

    protected int getNumber(int index) {
        return roll[index] + 1;
    }

    protected boolean getVast(int index) {
        return vast[index];
    }

    protected float determineChance(int d1, boolean v1, int d2, boolean v2) {
        if (v1 || v2)
            return 1 / 6f;
        if (d1 == d2)
            return 1 / 6f / 6f;
        return 1 / 6f / 6f * 2;
    }

    /**
     * Determines which dice are allowed to be vast
     * @return num between 1 and 6
     */
    protected abstract int getHighestVastNumber();

    protected boolean isMex(int d1, int d2) {
        return (d1 == 1 && d2 == 2 || d1 == 2 && d2 == 1); // 12 or 21
    }

    protected boolean isLow(int d1, int d2) {
        return (d1 == 3 && d2 == 2 || d1 == 2 && d2 == 3); // 32 or 23
    }

    protected boolean isPoint(int d1, int d2) {
        return (d1 == 3 && d2 == 1 || d1 == 1 && d2 == 3); // 31 or 13
    }

    /**
     * @return number of dice
     */
    protected abstract int numDice();

    /**
     * Is called after the dice is rolled
     */
    protected abstract void afterRollDice();

    /**
     * Set die vast / unvast
     * @param i
     */
    protected void setVast(final int i, final boolean v) {
        vast[i] = v;
        vastImages[i].post(new Runnable() {
            public void run() {
                vastImages[i].setVisibility(v ? View.VISIBLE : View.INVISIBLE);
            }
        });
    }

    protected void setThrowButtonLabel(final int labelString) {
        throwButton.post(new Runnable() {
            public void run() {
                String label = getResources().getString(labelString);
                throwButton.setText(label);
            }
        });
    }

    protected boolean changeDiceValueAllowed() {
        return true;
    }

    protected boolean setDie(int position, int number, boolean forceChange) {
        if (position >= roll.length || (!changeDiceValueAllowed() && !forceChange))
            return false;
        roll[position] = number - 1;
        animationHandler.sendEmptyMessage(0);
        evaluateChances();
        return true;
    }

    protected void storeTurn(boolean finished) {
        if (finished)
            currentTurn.setFinished();
        currentGame.addTurn(currentTurn);
        if (finished) // Only if the previous turn is finished should a new one be started
            currentTurn = new Turn(localPlayer()); // TODO: Is this always the local player?
    }

    protected void gameFinished() {
        storeTurn(true);
        currentGame.setFinished();
        writeCurrentGameToDb(currentGame);
    }

    protected void addToThrows(Throw t) {
        currentTurn.addThrow(t);
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.i("RollDice", "onStart");
        currentTurn = new Turn(localPlayer());
        retrieveCurrentGameFromDb();
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.i("RollDice", "onResume");
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
        throwOnShake = sp.getBoolean("pref_shake_throw", false);
        if (throwOnShake)
            sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
                    SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.i("RollDice", "onPause");
        sensorManager.unregisterListener(this);
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.i("RollDice", "onStop");
        currentGame.addPlayer(localPlayer());
        writeCurrentGameToDb(currentGame);
    }

    protected void writeCurrentGameToDb(Game game) {
        new CommitGameToDbAsyncTask().execute(game);
    }

    protected void retrieveCurrentGameFromDb() {
        if (currentGame == null || currentGame.getTurns().size() == 0)
            new RetrieveGameFromDbAsyncTask().execute();
    }

    private class CommitGameToDbAsyncTask extends AsyncTask<Game, Void, String> {
        @Override
        protected String doInBackground(Game... games) {
            MGDbHelper.addGameToDatabase(games[0]);
            Log.i("RollDice", "Added " + games[0]);
            return null;
        }
    }

    private class RetrieveGameFromDbAsyncTask extends AsyncTask<String, Void, Game> {
        @Override
        protected Game doInBackground(String... urls) {
            return MGDbHelper.retrieveLatestGame();
        }

        @Override
        protected void onPostExecute(Game game) {
            super.onPostExecute(game);
            Log.i("RollDice", game != null ? game.toString() : "");
            // Ensure game is not null and we're playing the same number of dice
            if (game != null && !game.isFinished() && (game.numDice() == -1 || game.numDice() == numDice())) {
                currentGame = game;
                currentGame.addPlayer(localPlayer());
                if (game.getTurns().size() > 0) {
                    Turn latest = game.getTurns().get(game.getTurns().size() - 1);
                    if (!latest.isFinished()) {
                        currentTurn = latest;
                    }
                }
                currentMode = currentGame.getGameMode();
                onGameRetrieved(game);
            }
        }
    }

    protected abstract void onGameRetrieved(Game game);

    @Override
    public void onBackPressed() {
        onExitingGame(true, false);
    }

    private void onExitingGame(final boolean backPressed, final boolean homePressed) {
        if (currentMode == GameMode.FREEPLAY) {
            gameFinished();
            if (backPressed)
                RollDice.super.onBackPressed();
            else if (homePressed)
                NavUtils.navigateUpFromSameTask(activity);
            return;
        }
        DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                switch (which) {
                case DialogInterface.BUTTON_POSITIVE:
                    gameFinished();
                    if (backPressed)
                        activity.onBackPressed();
                    else if (homePressed)
                        NavUtils.navigateUpFromSameTask(activity);
                    break;

                case DialogInterface.BUTTON_NEGATIVE:
                    // Doing nothing..
                    break;
                }
            }
        };

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage(getResources().getString(R.string.stop_game))
                .setPositiveButton(getResources().getString(R.string.yes), dialogClickListener)
                .setNegativeButton(getResources().getString(R.string.no), dialogClickListener).show();
    }

    protected void resetPlay() {
        gameFinished();
        currentGame = new Game(currentMode);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
            getAccelerometer(event);
        }
    }

    private void getAccelerometer(SensorEvent event) {
        float[] values = event.values;
        // Movement
        float x = values[0];
        float y = values[1];
        float z = values[2];

        float accelationSquareRoot = (x * x + y * y + z * z)
                / (SensorManager.GRAVITY_EARTH * SensorManager.GRAVITY_EARTH);
        if (accelationSquareRoot >= MINIMUM_ACCELARATION) {
            if (throwButton.isEnabled()) {
                throwButton.performClick();
            }
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }
}