Question

Write an app that implements the tile puzzle game: on a 3 × 3 grid, the...

Write an app that implements the tile puzzle game: on a 3 × 3 grid, the digits 1 through 8 are randomly positioned inside TextViews on the grid. By touching a TextView, it moves to its adjacent empty square (if it is next to the empty square). When the digits are in order (1, 2, 3 on the first row, 4, 5, 6 on the second row, and 7, 8 on the last row), the game is over and touch events are disabled. Include a Model. Using android studio java language

Homework Answers

Answer #1

MainActivity.java

ackage com.caiolopes.slidepuzzle;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.caiolopes.slidepuzzle.model.Board;
import com.caiolopes.slidepuzzle.model.Place;
import com.caiolopes.slidepuzzle.R;

/**
 * The Class MainActivity.
 * 
 * @see BoardView
 * @see Board
 * @author Caio Lopes
 * @version 1.0 $
 */
public class MainActivity extends ActionBarActivity {

        /** The main view. */
        private ViewGroup mainView;

        /** The game board. */
        private Board board;

        /** The board view that generates the tiles and lines using 2-D graphics. */
        private BoardView boardView;

        /** Text view to show the user the number of movements. */
        private TextView moves;

        /** The board size. Default value is an 4x4 game. */
        private int boardSize = 4;

        /*
         * (non-Javadoc)
         * 
         * @see android.support.v7.app.ActionBarActivity#onCreate(android.os.Bundle)
         */
        @Override
        protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                mainView = (ViewGroup) findViewById(R.id.mainLayout);
                moves = (TextView) findViewById(R.id.moves);
                moves.setTextColor(Color.WHITE);
                moves.setTextSize(20);
                this.newGame();
        }

        /*
         * (non-Javadoc)
         * 
         * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
         */
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
                // Inflate the menu; this adds items to the action bar if it is present.
                getMenuInflater().inflate(R.menu.main, menu);
                return true;
        }

        /**
         * Generates a new game.
         */
        private void newGame() {
                this.board = new Board(this.boardSize);
                this.board.addBoardChangeListener(boardChangeListener);
                this.board.rearrange();
                this.mainView.removeView(boardView);
                this.boardView = new BoardView(this, board);
                this.mainView.addView(boardView);
                this.moves.setText("Number of movements: 0");
        }

        /**
         * Changes the size of the board
         *
         * @param newSize
         */
        public void changeSize(int newSize) {
                if (newSize != this.boardSize) {
                        this.boardSize = newSize;
                        this.newGame();
                        boardView.invalidate();
                }
        }

        /*
         * (non-Javadoc)
         * 
         * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
         */
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
                // Handle action bar item clicks here. The action bar will
                // automatically handle clicks on the Home/Up button, so long
                // as you specify a parent activity in AndroidManifest.xml.
                switch (item.getItemId()) {
                case R.id.action_settings:
                        FragmentManager fm = getSupportFragmentManager();
                        SettingsDialogFragment settings = new SettingsDialogFragment(
                                        this.boardSize);
                        settings.show(fm, "fragment_settings");
                        break;
                case R.id.action_new_game:
                        new AlertDialog.Builder(this)
                                        .setTitle("New Game")
                                        .setMessage("Are you sure you want to begin a new game?")
                                        .setPositiveButton(android.R.string.yes,
                                                        new DialogInterface.OnClickListener() {
                                                                public void onClick(DialogInterface dialog,
                                                                                int which) {
                                                                        board.rearrange();
                                                                        moves.setText("Number of movements: 0");
                                                                        boardView.invalidate();
                                                                }
                                                        })
                                        .setNegativeButton(android.R.string.no,
                                                        new DialogInterface.OnClickListener() {
                                                                public void onClick(DialogInterface dialog,
                                                                                int which) {
                                                                        // do nothing
                                                                }
                                                        }).setIcon(android.R.drawable.ic_dialog_alert)
                                        .show();
                        break;
                case R.id.action_help:
                        new AlertDialog.Builder(this)
                                        .setTitle("Instructions")
                                        .setMessage(
                                                        "The goal of the puzzle is to place the tiles in order by making sliding moves that use the empty space. The only valid moves are to move a tile which is immediately adjacent to the blank into the location of the blank.")
                                        .setPositiveButton("Understood. Let's play!",
                                                        new DialogInterface.OnClickListener() {
                                                                public void onClick(DialogInterface dialog,
                                                                                int which) {
                                                                        dialog.dismiss();
                                                                }
                                                        }).show();
                        break;
                }
                return super.onOptionsItemSelected(item);
        }

        /** The board change listener. */
        private Board.BoardChangeListener boardChangeListener = new Board.BoardChangeListener() {
                public void tileSlid(Place from, Place to, int numOfMoves) {
                        moves.setText("Number of movements: "
                                        + Integer.toString(numOfMoves));
                }

                public void solved(int numOfMoves) {
                        moves.setText("Solved in " + Integer.toString(numOfMoves)
                                        + " moves!");
                        Toast.makeText(getApplicationContext(), "You won!",
                                        Toast.LENGTH_LONG).show();
                }
        };

        /**
         * The Class SettingsDialogFragment. Shows the settings alert dialog in
         * order to change the size of the board.
         */
        public class SettingsDialogFragment extends DialogFragment {

                /** The size. */
                private int size;

                /**
                 * Instantiates a new settings dialog fragment.
                 *
                 * @param size
                 *            the size
                 */
                public SettingsDialogFragment(int size) {
                        this.size = size;
                }

                /**
                 * Sets the size.
                 *
                 * @param size
                 *            the new size
                 */
                void setSize(int size) {
                        this.size = size;
                }

                /**
                 * Gets the size.
                 *
                 * @return the size
                 */
                int getSize() {
                        return this.size;
                }

                /*
                 * (non-Javadoc)
                 * 
                 * @see
                 * android.support.v4.app.DialogFragment#onCreateDialog(android.os.Bundle
                 * )
                 */
                @Override
                public Dialog onCreateDialog(Bundle savedInstanceState) {
                        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
                        // Set the dialog title
                        builder.setTitle("Define the size of the puzzle")
                                        .setSingleChoiceItems(R.array.size_options, this.size - 2,
                                                        new DialogInterface.OnClickListener() {

                                                                @Override
                                                                public void onClick(DialogInterface dialog,
                                                                                int which) {
                                                                        setSize(which + 2);

                                                                }

                                                        })
                                        .setPositiveButton("Change",
                                                        new DialogInterface.OnClickListener() {
                                                                @Override
                                                                public void onClick(DialogInterface dialog,
                                                                                int id) {
                                                                        ((MainActivity) getActivity())
                                                                                        .changeSize(getSize());
                                                                }
                                                        })
                                        .setNegativeButton("Cancel",
                                                        new DialogInterface.OnClickListener() {
                                                                @Override
                                                                public void onClick(DialogInterface dialog,
                                                                                int id) {
                                                                        dialog.cancel();
                                                                }
                                                        });

                        return builder.create();
                }
        }
}

BoardView.java

package com.caiolopes.slidepuzzle;

import java.util.Iterator;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.graphics.Paint.Style;
import android.view.MotionEvent;
import android.view.View;
import com.caiolopes.slidepuzzle.model.Board;
import com.caiolopes.slidepuzzle.model.Place;
import com.caiolopes.slidepuzzle.R;

/**
 * The Class BoardView. It uses 2-D graphics to display the puzzle board.
 * 
 * @author Caio Lopes
 * @version 1.0 $
 */
public class BoardView extends View {

        /** The board. */
        private Board board;

        /** The width. */
        private float width;

        /** The height. */
        private float height;

        /**
         * Instantiates a new board view.
         *
         * @param context
         *            the context
         * @param board
         *            the board
         */
        public BoardView(Context context, Board board) {
                super(context);
                this.board = board;
                setFocusable(true);
                setFocusableInTouchMode(true);
        }

        /*
         * (non-Javadoc)
         * 
         * @see android.view.View#onSizeChanged(int, int, int, int)
         */
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
                this.width = w / this.board.size();
                this.height = h / this.board.size();
                super.onSizeChanged(w, h, oldw, oldh);
        }

        /**
         * Locate place.
         *
         * @param x
         *            the x
         * @param y
         *            the y
         * @return the place
         */
        private Place locatePlace(float x, float y) {
                int ix = (int) (x / width);
                int iy = (int) (y / height);

                return board.at(ix + 1, iy + 1);
        }

        /*
         * (non-Javadoc)
         * 
         * @see android.view.View#onTouchEvent(android.view.MotionEvent)
         */
        @Override
        public boolean onTouchEvent(MotionEvent event) {
                if (event.getAction() != MotionEvent.ACTION_DOWN)
                        return super.onTouchEvent(event);
                Place p = locatePlace(event.getX(), event.getY());
                if (p != null && p.slidable() && !board.solved()) {
                        p.slide();
                        invalidate();
                }
                return true;
        }

        /*
         * (non-Javadoc)
         * 
         * @see android.view.View#onDraw(android.graphics.Canvas)
         */
        @Override
        protected void onDraw(Canvas canvas) {
                Paint background = new Paint();
                background.setColor(getResources().getColor(R.color.board_color));
                canvas.drawRect(0, 0, getWidth(), getHeight(), background);

                Paint dark = new Paint();
                dark.setColor(getResources().getColor(R.color.tile_color));
                dark.setStrokeWidth(15);

                // Draw the major grid lines
                for (int i = 0; i < this.board.size(); i++) {
                        canvas.drawLine(0, i * height, getWidth(), i * height, dark);
                        canvas.drawLine(i * width, 0, i * width, getHeight(), dark);
                }

                Paint foreground = new Paint(Paint.ANTI_ALIAS_FLAG);
                foreground.setColor(getResources().getColor(R.color.tile_color));
                foreground.setStyle(Style.FILL);
                foreground.setTextSize(height * 0.75f);
                foreground.setTextScaleX(width / height);
                foreground.setTextAlign(Paint.Align.CENTER);

                float x = width / 2;
                FontMetrics fm = foreground.getFontMetrics();
                float y = (height / 2) - (fm.ascent + fm.descent) / 2;

                Iterator<Place> it = board.places().iterator();
                for (int i = 0; i < board.size(); i++) {
                        for (int j = 0; j < board.size(); j++) {
                                if (it.hasNext()) {
                                        Place p = it.next();
                                        if (p.hasTile()) {
                                                String number = Integer.toString(p.getTile().number());
                                                canvas.drawText(number, i * width + x, j * height + y,
                                                                foreground);
                                        } else {
                                                canvas.drawRect(i * width, j * height, i * width
                                                                + width, j * height + height, dark);
                                        }
                                }
                        }
                }
        }
}

Tile.java

package com.caiolopes.slidepuzzle.model;

/** A tile of the puzzle. Each tile has a (unique) number. */
public class Tile {

    /** The number of this tile. */
    private final int number;
    
    /** Create a new tile that has the given number */
    public Tile(int number) {
        this.number = number;
    }

    /** Return the number of this tile. */
    public int number() {
        return number;
    }
}

Place.java

package com.caiolopes.slidepuzzle.model;

/** 
 * A place in a puzzle board. Each place has a pair of 1-based
 * indices---<code>x</code> for column and <code>y</code> for
 * row---that uniquely identify it in the board. A place can be
 * occupied by a tile.
 *
 * @see Tile
 */
public class Place {

    /** 1-based column index of this place. */
    private final int x;

    /** 1-based row index of this place. */
    private final int y;

    /** Tile currently placed at this place. */
    private Tile tile;

    /** Board which this place belongs to. */
    private Board board;
    
    /** Create a new place with the given indices for the given board. */
    public Place(int x, int y, Board board) {
        this.x = x;
        this.y = y;
        this.board = board;
    }

    /** Create a new place with the given indices and a tile marked with
     * the given number for the given board. */
    public Place(int x, int y, int number, Board board) {
        this(x, y, board);
        tile = new Tile(number);
    }

    /** Return the 1-based column index of this place. */
    public int getX() {
        return x;
    }

    /** Return the 1-based row index of this place. */
    public int getY() {
        return y;
    }

    /** Does this place have a tile at it? */
    public boolean hasTile() {
        return tile != null;
    }

    /** Return the tile placed in this place; null is returned if no
     * tile is placed. */
    public Tile getTile() {
        return tile;
    }

    /** Place the given tile in this place. */
    public void setTile(Tile tile) {
        this.tile = tile;
    }
    
    /** Is the tile in this place slidable? Return false if this place
     * is empty, i.e., no tile is placed. */
    public boolean slidable() {
        return hasTile() && board.slidable(this);
    }
    
    public void slide() {
        board.slide(getTile());
    }
}

Board.Java

package com.caiolopes.slidepuzzle.model;

import java.util.*;

/**
 * A puzzle frame consisting of <code>size</code> * <code>size</code>
 * places where puzzle tiles can be placed. 
 *
 * @see Place
 * @see Tile
 */
public class Board {

    /** Dimension of this board. This board will have 
     *  <code>size</code> * <code>size</code> places. */
    private final int size;

    /** Number of tile moves made so far. */
    private int numOfMoves;

    /** Places of this board. */
    private final List<Place> places;
    
    /** Listeners listening to board changes such as sliding of tiles. */
    private final List<BoardChangeListener> listeners;
    
    /** To arrange tiles randomly. */
    private final static Random random = new Random();

    /** Create a new board of the given dimension. Initially, the tiles
     * are ordered with the blank tile as the last tile. */
    public Board(int size) {
        listeners = new ArrayList<BoardChangeListener>();
        this.size = size;
        places = new ArrayList<Place>(size * size);
        for (int x = 1; x <= size; x++) {
            for (int y = 1; y <= size; y++) {
                places.add(x == size && y == size ?
                           new Place(x, y, this)
                           : new Place(x, y, (y - 1)* size + x, this));
            }
        }
        numOfMoves = 0;
    }

    /** Rearrange the tiles to create a new, solvable puzzle. */
    public void rearrange() {
        numOfMoves = 0;
        for (int i = 0; i < size*size; i++) {
            swapTiles();
        }
        do { 
            swapTiles();
        } while (!solvable() || solved());
    }

    /** Swap two tiles randomly. */
    private void swapTiles() {
        Place p1 = at(random.nextInt(size) + 1, random.nextInt(size) + 1);
        Place p2 = at(random.nextInt(size) + 1, random.nextInt(size) + 1);
        if (p1 != p2) {
            Tile t = p1.getTile();
            p1.setTile(p2.getTile());
            p2.setTile(t);
        }
    }

    /** Is the puzzle (current arrangement of tiles) solvable? */
    private boolean solvable() {
        // alg. from: http://www.cs.bham.ac.uk/~mdr/teaching/modules04/
        //                 java2/TilesSolvability.html
        //
        // count the number of inversions, where an inversion is when
        // a tile precedes another tile with a lower number on it.
        int inversion = 0;
        for (Place p: places) {
            Tile pt = p.getTile();
            for (Place q: places) {
                Tile qt = q.getTile();
                if (p != q && pt != null && qt != null &&
                    indexOf(p) < indexOf(q) &&
                    pt.number() > qt.number()) {
                    inversion++;
                }                    
            }
        }
        final boolean isEvenSize = size % 2 == 0;
        final boolean isEvenInversion = inversion % 2 == 0;
        boolean isBlankOnOddRow = blank().getY() % 2 == 1;
        // from the bottom
        isBlankOnOddRow = isEvenSize ? !isBlankOnOddRow : isBlankOnOddRow;
        return (!isEvenSize && isEvenInversion) ||
            (isEvenSize && isBlankOnOddRow == isEvenInversion);
    }

    /** Return the 1-based index of the given place when all the places
     * are arranged in row-major order. */
    private int indexOf(Place p) {
        return (p.getY() - 1) * size + p.getX();
        
    }

   /** Is this puzzle solved? */
    public boolean solved() {
        boolean result = true;
        for (Place p: places) {
            result = result &&
                ((p.getX() == size && p.getY() == size) ||
                 (p.getTile() != null && 
                  p.getTile().number() == indexOf(p)));
        }
        return result;
    }

    /** Slide the given tile, which is assumed to be slidable, and
     * notify the change to registered board change listeners, if any.
     *  
     * @see Board#slidable(Place) */
    public void slide(Tile tile) {
        for (Place p: places) {
            if (p.getTile() == tile) {
                final Place to = blank();
                to.setTile(tile);
                p.setTile(null);
                numOfMoves++;
                notifyTileSliding(p, to, numOfMoves);
                if (solved()) {
                        notifyPuzzleSolved(numOfMoves);
                }
                return;
            }
        }
    }
   
    /** Is the tile in the given place slidable? */
    public boolean slidable(Place place) {
        int x = place.getX();
        int y = place.getY();
        return isBlank(x - 1, y) || isBlank(x + 1, y)
            || isBlank(x, y - 1) || isBlank(x, y + 1);
    }

    /** Is the place at the given indices empty? */
    private boolean isBlank(int x, int y) {
        return (0 < x && x <= size) && (0 < y && y <= size)
            && at(x,y).getTile() == null;
    }

    /** Return the blank place. */
    public Place blank() {
        for (Place p: places) {
            if (p.getTile() == null) {
                return p;
            }
        }
        //assert false : "should never reach here!";
        return null; 
    }

    /** Return all the places of this board. */
    public Iterable<Place> places() {
        return places;
    }

    /** Return the place at the given indices. */
    public Place at(int x, int y) {
        for (Place p: places) {
            if (p.getX() == x && p.getY() == y) {
                return p;
            }
        }
        //assert false : "precondition violation!";
        return null; 
    }

    /** Return the dimension of this board. */
    public int size() {
        return size;
    }

    /** Return the number of tile moves made so far. */
    public int numOfMoves() {
        return numOfMoves;
    }

    /** Register the given listener to listen to board changes. */
    public void addBoardChangeListener(BoardChangeListener listener) {
        if (!listeners.contains(listener)) {
                listeners.add(listener);
        }
    }
    
    /** Unregister the given listener from listening to board changes. */
    public void removeBoardChangeListener(BoardChangeListener listener) {
                listeners.remove(listener);
    }
    
    /** Notify a tile sliding to registered board change listeners. */
    private void notifyTileSliding(Place from, Place to, int numOfMove) {
        for (BoardChangeListener listener: listeners) {
                listener.tileSlid(from, to, numOfMoves);
        }
    }
    
    /** Notify solving of the puzzle to registered board change listeners. */
    private void notifyPuzzleSolved(int numOfMoves) {
        for (BoardChangeListener listener: listeners) {
                listener.solved(numOfMoves);
        }
    }
    
    /** To listen to board changes such as tile sliding. */
    public interface BoardChangeListener {
        
        /** Called when the tile located at the <code>from</code>
         * place was slid to the empty <code>to</code> place. Both places
         * will be provided in new states; i.e., <code>from</code> will
         * be empty and <code>to</code> will be the tile moved. */
        void tileSlid(Place from, Place to, int numOfMoves);
        
        /** Called when the puzzle is solved. The number of tile moves
         * is provided as the argument. */
        void solved(int numOfMoves);
    }
    
}
Know the answer?
Your Answer:

Post as a guest

Your Name:

What's your source?

Earn Coins

Coins can be redeemed for fabulous gifts.

Not the answer you're looking for?
Ask your own homework help question
Similar Questions