com.pseudosudostudios.jdd.views.Grid.java Source code

Java tutorial

Introduction

Here is the source code for com.pseudosudostudios.jdd.views.Grid.java

Source

/*
 * Copyright (c) 2015. Ben O'Halloran/Pseudo Sudo Studios.
 * All rights reserved.
 * This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.
 * Full license can be found here: http://creativecommons.org/licenses/by-nc-nd/3.0/deed.en_US
 */
package com.pseudosudostudios.jdd.views;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.os.AsyncTask;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.Toast;

import com.pseudosudostudios.jdd.R;
import com.pseudosudostudios.jdd.activities.GameActivity;
import com.pseudosudostudios.jdd.utils.Difficulty;
import com.pseudosudostudios.jdd.utils.TileFactory;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.Arrays;
import java.util.Random;

public class Grid extends View {
    //Constants
    public static int tileSize = -1;
    public static final double tilePercent = .25D;

    public static final int divisions = 12;
    private static final Paint divPaint = new Paint();

    //Tile things
    public Tile[][] tiles = new Tile[3][3];
    public Tile[][] sol1;
    private Tile[][] sol2;
    private Tile[][] sol3;
    private Tile[][] sol4;
    private Tile[][] usingSol;
    private double padding;
    private int vertical_offset, horizontal_offset;
    private int move_count = 0;
    private final int[] blackArray = { 0, 0, 0, 0 };

    /**
     * Instance Variables
     */
    private Difficulty level = Difficulty.EASY;
    private Tile firstSelect = null;
    public long startTime;
    public int moves = 0;
    private Tile centerTile;
    private boolean isNotFirstDraw = false;
    private long extraTime = 0; // TODO make relevant

    final GestureDetector gestureDetect = new GestureDetector(getContext(), new GridListener(), new Handler());

    public static int numberOfColors = TileFactory.colors.length;

    public boolean hasUsed = false;

    public Grid(Context context) {
        super(context);
        constructorCommon();
    }

    public Grid(Context context, AttributeSet s, int i) {
        super(context, s, i);
        constructorCommon();
    }

    public Grid(Context context, AttributeSet s) {
        super(context, s);
        constructorCommon();
    }

    /**
     * Common code between the constructors
     */
    public void constructorCommon() {
        setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                gestureDetect.onTouchEvent(event);
                return true;
            }
        });
        for (int i = 0; i < tiles.length; i++) {
            for (int c = 0; c < tiles[i].length; c++) {
                Tile t = new Tile(getContext());
                t.setId(i * 10 + c);
                t.setVisibility(View.INVISIBLE);

                tiles[i][c] = t;
            }
        }
        divPaint.setColor(Color.TRANSPARENT);
        moves = 0;
    }

    /**
     * Defines the centers for the tiles
     */
    public void setCenters() {
        Point gridCenter = new Point(getWidth() / 2, getHeight() / 2);
        vertical_offset = Math.abs(vertical_offset);
        horizontal_offset = Math.abs(horizontal_offset);

        int space = Math.min(vertical_offset, horizontal_offset);

        int x, y;
        for (int row = 0; row < tiles.length; row++) {
            for (int col = 0; col < tiles[row].length; col++) {
                if (row == 0)
                    y = gridCenter.y - space;
                else if (row == 1)
                    y = gridCenter.y;
                else
                    y = gridCenter.y + space;

                if (col == 0)
                    x = gridCenter.x - space;
                else if (col == 1)
                    x = gridCenter.x;
                else
                    x = gridCenter.x + space;
                tiles[row][col].updateCenter(new Point(x, y));
            }
        }
    }

    private void testForColoredTiles() {
        while (move_count <= 500 && !isValidGame()) {
            loadNewGame();
            move_count++;
        }
        move_count = 0;
    }

    private boolean isValidGame() {
        for (Tile[] r : tiles)
            for (Tile t : r)
                if (Arrays.equals(t.toIntArray(), blackArray))
                    return false;
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (!isNotFirstDraw) {
            testForColoredTiles();
            isNotFirstDraw = true;
            setTileSize();
        }
        setCenters();
        for (Tile[] row : tiles) {
            for (Tile t : row)
                t.draw(canvas);
        }
    }

    /**
     * Prompts the user and calls loadGame()
     */
    public void makeNewGame() {
        hasUsed = false;
        if (tiles != null)
            for (Tile[] row : tiles)
                for (Tile t : row)
                    t.setVisibility(View.INVISIBLE);

        AlertDialog.Builder build = new AlertDialog.Builder(getContext());

        build.setTitle("How many colors?").setCancelable(false);

        View myView = ((LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE))
                .inflate(R.layout.number_colors_input, null);
        final EditText input = (EditText) myView.findViewById(R.id.colorInputET);
        final RadioButton easy = (RadioButton) myView.findViewById(R.id.easyRB);
        final RadioButton medium = (RadioButton) myView.findViewById(R.id.mediumRB);
        final RadioButton hard = (RadioButton) myView.findViewById(R.id.hardRB);
        build.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                try {
                    Grid.numberOfColors = Integer.parseInt(input.getText().toString());
                    if (numberOfColors < 2) {
                        numberOfColors = 2;
                        Toast.makeText(getContext(), getContext().getString(R.string.too_few_colors),
                                Toast.LENGTH_SHORT).show();
                    }
                    if (numberOfColors > TileFactory.colors.length)
                        numberOfColors = TileFactory.colors.length;
                } catch (NumberFormatException e) {
                    Grid.numberOfColors = 6;
                }
                if (easy.isChecked())
                    setDifficulty(Difficulty.EASY);
                if (medium.isChecked())
                    setDifficulty(Difficulty.MEDIUM);
                if (hard.isChecked())
                    setDifficulty(Difficulty.HARD);
                Tile.initPaints();
                loadNewGame();
                invalidate(); // let it draw!
            }
        }).setView(myView).show();
    }

    /**
     * Loads a game, and makes the Tiles
     */
    private void loadNewGame() {
        moves = 0;
        hasUsed = false;
        Tile.initPaints();
        Tile[] read = TileFactory.makeLiveGame(getContext());

        int i = 0;
        for (int row = 0; row < tiles.length; row++)
            for (int col = 0; col < tiles[row].length; col++) {
                tiles[row][col].setColors(read[i]);
                i++;
            }
        setCenterTile(tiles[1][1]);

        sol1 = sol2 = sol3 = sol4 = usingSol = null;
        sol1 = rotateCW(tiles);
        sol2 = rotateCW(sol1);
        sol3 = rotateCW(sol2);
        sol4 = rotateCW(sol3);

        setTileSize();
        setCenters();

        randomRotate();

        if (getDifficulty() != Difficulty.EASY)
            jumbleTiles();
        if (getDifficulty() == Difficulty.MEDIUM)
            updateGlows();

        startTime = System.currentTimeMillis();
        extraTime = 0;
        isNotFirstDraw = false;

        for (Tile[] r : tiles)
            for (Tile t : r)
                t.setVisibility(VISIBLE);
    }

    /**
     * Randomly swaps tiles in the array
     */
    public void jumbleTiles() {
        int size = tiles.length;
        int x, y, x1, y1;
        Random rand = new Random();
        Tile temp;
        for (int i = 0; i < 2500; i++) {
            x = rand.nextInt(size);
            y = rand.nextInt(size);
            temp = tiles[x][y];
            x1 = rand.nextInt(size);
            y1 = rand.nextInt(size);
            tiles[x][y] = tiles[x1][y1];
            tiles[x1][y1] = temp;
        }
    }

    /**
     * Returns a new array that is the parameter rotated about its center
     */
    public static Tile[][] rotateCW(Tile[][] mat) {
        Tile[][] ret = new Tile[mat.length][mat[0].length];
        // swap the corners
        ret[0][0] = new Tile(mat[2][0]);
        ret[0][2] = new Tile(mat[0][0]);
        ret[2][2] = new Tile(mat[0][2]);
        ret[2][0] = new Tile(mat[2][2]);
        // swap the interior points
        ret[0][1] = new Tile(mat[1][0]);
        ret[1][2] = new Tile(mat[0][1]);
        ret[2][1] = new Tile(mat[1][2]);
        ret[1][0] = new Tile(mat[2][1]);
        // set the center tile
        ret[1][1] = new Tile(mat[1][1]);
        // rotate everything clockwise once
        for (Tile[] row : ret)
            for (Tile t : row)
                t.rotateCW();
        return ret;
    }

    /**
     * @return the Point of the parameter in the gird
     */
    private Point getPointFirstPatch(Tile clicked) {
        for (int row = 0; row < tiles.length; row++)
            for (int col = 0; col < tiles[row].length; col++)
                if (tiles[row][col] == clicked)
                    return new Point(row, col);
        return null;
    }

    /**
     * @return the tile closest to the origin iff its within tolerance
     */
    private Tile findClosestByPoint(Point origin, double tolerance) {
        double distances[][] = new double[tiles.length][tiles[0].length];
        for (int row = 0; row < tiles.length; row++) {
            for (int col = 0; col < tiles[row].length; col++) {
                Point check = tiles[row][col].getLocation();
                double xDiffSquared = Math.pow(check.x - origin.x, 2);
                double yDiffSquared = Math.pow(check.y - origin.y, 2);
                distances[row][col] = Math.sqrt(xDiffSquared + yDiffSquared);
            }
        }
        int minRow = 0;
        int minCol = 0;
        for (int row = 0; row < tiles.length; row++) {
            for (int col = 0; col < tiles[row].length; col++) {
                if (distances[row][col] < distances[minRow][minCol]) {
                    minRow = row;
                    minCol = col;
                }
            }
        }
        if (distances[minRow][minCol] <= tolerance)
            return tiles[minRow][minCol];

        return null;
    }

    public void onUpdate() {
        if (isPuzzleSolved())
            winMethod();
        else if (level == Difficulty.MEDIUM)
            updateGlows();
        invalidate();
    }

    public void setTileArray(Tile[][] newTiles, Tile[][] solution, int moves, long time, int size) {
        tiles = newTiles;
        Grid.tileSize = size;
        System.out.println(Grid.tileSize);
        sol1 = sol2 = sol3 = sol4 = usingSol = null;
        sol1 = rotateCW(solution);
        sol2 = rotateCW(sol1);
        sol3 = rotateCW(sol2);
        sol4 = rotateCW(sol3);
        setCenterTile(sol1[1][1]);
        this.moves = moves;
        startTime = System.currentTimeMillis();
        Tile.initPaints();
        invalidate();
        setExtraTime(time);
        System.out.println("ET: " + extraTime);
    }

    /**
     * This method adds the medium level helper glows
     */
    private void updateGlows() {
        if (usingSol == null) {
            for (int r = 0; r < tiles.length; r++)
                for (int c = 0; c < tiles[r].length; c++) {
                    if (tiles[r][c].equals(sol1[r][c]))
                        usingSol = sol1;
                    else if (tiles[r][c].equals(sol2[r][c]))
                        usingSol = sol2;
                    else if (tiles[r][c].equals(sol3[r][c]))
                        usingSol = sol3;
                    else if (tiles[r][c].equals(sol4[r][c]))
                        usingSol = sol4;
                    if (usingSol != null) {
                        updateGlows();
                        return;
                    }
                }
            invalidate();
        } else
            handleSetPath();
    }

    private void handleSetPath() {
        for (int row = 0; row < tiles.length; row++) {
            for (int col = 0; col < tiles[row].length; col++) {
                if (tiles[row][col].equalsColors(usingSol[row][col])) {
                    tiles[row][col].addLocationGlow();
                    tiles[row][col].setMovable(false);
                    tiles[row][col].setIsRotatable(true);
                }
            }
        }
        invalidate();
    }

    public long getTimeSinceStart() {
        return (System.currentTimeMillis() - startTime) / 1000 + extraTime;
    }

    public long getTimeRaw() {
        return System.currentTimeMillis() - startTime + extraTime * 1000;
    }

    public void setExtraTime(long loadedTime) {
        extraTime = loadedTime;
    }

    /**
     * @return true iff the puzzle is solved
     */
    private boolean isPuzzleSolved() {
        for (int row = 0; row < tiles.length; row++) {
            if (tiles[row][0].getDir(Tile.Direction.EAST) != tiles[row][1].getDir(Tile.Direction.WEST))
                return false;
            else if (tiles[row][1].getDir(Tile.Direction.EAST) != tiles[row][2].getDir(Tile.Direction.WEST))
                return false;
            // col check
            if (tiles[0][row].getDir(Tile.Direction.SOUTH) != tiles[1][row].getDir(Tile.Direction.NORTH))
                return false;
            else if (tiles[1][row].getDir(Tile.Direction.SOUTH) != tiles[2][row].getDir(Tile.Direction.NORTH))
                return false;
        }
        return true;
    }

    /**
     * Sets the tile size, the offsets, and then calls the setTiles() method
     */
    public void setTileSize() {
        int height = getHeight();
        int width = getWidth();
        double toBeUsed;
        if (height == 0)
            height = 1000;
        if (width == 0)
            width = 5000;
        if (height >= width)
            toBeUsed = width;
        else
            toBeUsed = height;

        padding = toBeUsed / divisions;
        tileSize = (int) (padding * 3.5);

        // set the hor & vert offsets

        vertical_offset = tileSize + getHeight() / divisions;
        horizontal_offset = tileSize + getWidth() / divisions;

        while (height - (2 * vertical_offset) - tileSize - (Tile.glowExtra * 2) <= 7)
            vertical_offset--;
        while (width - 2 * horizontal_offset - tileSize - Tile.glowExtra * 2 <= 7)
            horizontal_offset--;
        if (toBeUsed - (3 * tileSize) - (6 * Tile.glowExtra) <= 10) {
            Tile.glowExtra--;
            setTileSize();
        }
        setCenters();
    }

    /**
     * Randomly rotates each tile in the grid
     */
    public void randomRotate() {
        Random rand = new Random();
        for (int row = 0; row < tiles.length; row++)
            for (int col = 0; col < tiles[row].length; col++) {
                int count = rand.nextInt(4); // 0, 1, 2, 3
                for (int rCount = 0; rCount < count; rCount++)
                    tiles[row][col].rotateCW();
            }
    }

    // TODO Remove for release
    public void hint() {
        Point oldCenterLocation = getPointFirstPatch(getCenterTile());
        swapTiles(tiles[1][1], tiles[oldCenterLocation.x][oldCenterLocation.y]);
        tiles[1][1].setMovable(false);
        if (level == Difficulty.MEDIUM)
            updateGlows();
        invalidate();
        hasUsed = true;
    }

    public boolean isReadyToPlay() {
        try {
            for (Tile[] row : tiles)
                for (Tile t : row)
                    for (int c : t.toIntArray())
                        if (c == Color.TRANSPARENT)
                            return false;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    public Difficulty getDifficulty() {
        return level;
    }

    public void setDifficulty(Difficulty d) {
        level = d;
    }

    public void setDifficulty(String string) {
        if (string.equals(Difficulty.HARD.toString()))
            setDifficulty(Difficulty.HARD);
        else if (string.equals(Difficulty.MEDIUM.toString()))
            setDifficulty(Difficulty.MEDIUM);
        else
            setDifficulty(Difficulty.EASY);
    }

    private void swapTiles(Tile firstSelect, Tile touched) {
        if (firstSelect == null || touched == null)
            return;
        if (!firstSelect.isMovable() || !touched.isMovable()) {
            return;
        }
        Point localPoint2 = getPointFirstPatch(touched);
        Point localPoint3 = getPointFirstPatch(firstSelect);
        if (localPoint2 == null || localPoint3 == null)
            return;
        tiles[localPoint3.x][localPoint3.y] = tiles[localPoint2.x][localPoint2.y];
        tiles[localPoint2.x][localPoint2.y] = firstSelect;

        // kill the glows for all tiles
        for (int r = 0; r < tiles.length; r++)
            for (int c = 0; c < tiles[r].length; c++)
                tiles[r][c].removeGlow();
    }

    public void winMethod() {
        new WinPretty().execute(-9);
        // ((GameActivity) getContext()).win(moves);
    }

    public Tile getCenterTile() {
        return centerTile;
    }

    public void setCenterTile(Tile centerTile) {
        this.centerTile = centerTile;
    }

    public JSONObject toJSONObject() throws JSONException {
        JSONObject obj = new JSONObject();
        JSONArray array = new JSONArray();
        JSONArray sol = new JSONArray();
        for (int row = 0; row < tiles.length; row++) {
            for (int col = 0; col < tiles[row].length; col++) {
                JSONObject currTileJSON = new JSONObject();
                int[] colors = tiles[row][col].toIntArray();
                // colors
                currTileJSON.put(GameActivity.northKey, colors[0]);
                currTileJSON.put(GameActivity.eastKey, colors[1]);
                currTileJSON.put(GameActivity.southKey, colors[2]);
                currTileJSON.put(GameActivity.westKey, colors[3]);
                // Location in the grid
                currTileJSON.put(GameActivity.pointX, tiles[row][col].center.x);
                currTileJSON.put(GameActivity.pointY, tiles[row][col].center.y);
                array.put(currTileJSON);

                JSONObject solution = new JSONObject();
                int[] solCol = tiles[row][col].toIntArray();
                // colors
                solution.put(GameActivity.northKey, solCol[0]);
                solution.put(GameActivity.eastKey, solCol[1]);
                solution.put(GameActivity.southKey, solCol[2]);
                solution.put(GameActivity.westKey, solCol[3]);
                // Location in the grid
                solution.put(GameActivity.pointX, sol1[row][col].center.x);
                solution.put(GameActivity.pointY, sol1[row][col].center.y);
                sol.put(currTileJSON);
            }
        }
        obj.put(GameActivity.moveKey, moves);
        obj.put(GameActivity.timeKey, getTimeSinceStart());
        obj.put(GameActivity.onSaveTime, getTimeSinceStart());
        obj.put(GameActivity.arrayKey, array);

        obj.put(GameActivity.numColorsKey, numberOfColors);
        obj.put(GameActivity.jsonTileSize, tileSize);
        obj.put(GameActivity.onSaveSolution, sol);
        obj.put(GameActivity.levelKey, getDifficulty().toString());
        obj.put(GameActivity.numColorsKey, numberOfColors);

        return obj;
    }

    /**
     * This class handles the input from the user including level logic
     */
    private class GridListener extends SimpleOnGestureListener {
        @Override
        /**Rotates the touched tile CCW*/
        public boolean onDoubleTap(MotionEvent event) {
            Tile touched = getTileFromEvent(event);
            if (touched != null) {
                touched.rotateCW();
                touched.rotateCW();
                touched.rotateCW();
                moves++;
                onUpdate();
            }
            return true;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            Tile touched = getTileFromEvent(e);
            if (level != Difficulty.EASY) {
                if (firstSelect == null) {
                    if (touched != null) {
                        firstSelect = touched;
                        firstSelect.addSelectedGlow();
                    }
                } else {
                    if (!firstSelect.equals(touched)) {
                        swapTiles(firstSelect, touched);
                        firstSelect = null;
                    } else {
                        firstSelect.removeGlow();
                        firstSelect = null;
                    }
                }
                moves++;
                onUpdate();
            } else {

                Toast.makeText(getContext(), R.string.no_long, Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent event) {
            // set firstSelect, if already defined swap the tiles
            Tile touched = getTileFromEvent(event);
            if (touched != null) {
                moves++;
                if (firstSelect != null && getDifficulty() != Difficulty.EASY) {
                    onLongPress(event);
                } else
                    touched.rotateCW();
            }
            onUpdate();
            return true;
        }

        private Tile getTileFromEvent(MotionEvent event) {
            Point clickPoint = new Point((int) event.getX(), (int) event.getY());
            return findClosestByPoint(clickPoint, tileSize / Math.sqrt(2.0D));
        }
    }

    /**
     * Starts off slow and then ramps up to almost no delay between rotations of
     * the entire grid of tiles.
     */
    private class WinPretty extends AsyncTask<Integer, Integer, String> {

        @Override
        protected String doInBackground(Integer... params) {
            for (int i = 0; i < 20; i++) {

                try {
                    Thread.sleep(100);
                    publishProgress(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            for (Tile[] r : tiles)
                for (Tile t : r)
                    t.rotateCW();
            invalidate();
        }

        @Override
        protected void onPostExecute(String result) {
            ((GameActivity) getContext()).win(moves);
        }
    }
}