Click here to Skip to main content
15,893,588 members
Articles / Mobile Apps / Android

Android Puzzles Solver

Rate me:
Please Sign up or sign in to vote.
4.87/5 (17 votes)
1 Oct 2012Apache13 min read 102.8K   7.1K   77  
Puzzles Solver is an Android application for playing and solving puzzles.
package gr.sullenart.games.puzzles.gameengine;

import gr.sullenart.games.graphics.ImageResizer;
import gr.sullenart.games.puzzles.R;
import gr.sullenart.games.puzzles.gameengine.solo.Solo;
import gr.sullenart.games.puzzles.gameengine.solo.SoloBoard;
import gr.sullenart.games.puzzles.gameengine.solo.SoloBoardPosition;
import gr.sullenart.games.puzzles.gameengine.solo.SoloCustomBoardActivity;
import gr.sullenart.games.puzzles.gameengine.solo.SoloGame;
import gr.sullenart.games.puzzles.gameengine.solo.SoloPuzzleRepository;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;

/**
 * Class for Solo Puzzle.
 * Objective of the game is to remove all the pegs off the board,
 * except of the last one. A peg can move to a free position, two
 * positions at the left, right, up or down, jumping over another peg.
 * The peg that was jumped over is removed.
 *
 */
public class SoloPuzzle extends Puzzle {
	/** Number of moves so far (0..TILES).*/
	int moveCount;

	/** Position of selected square 0..BOARD_SIZE. */
	int selPos;

	/** True if user picked a tile to move. */
	boolean fired;
   
	/** Board's rows.*/
    int boardRows;

    /** Board's columns.*/
    int boardColumns;

    /** Number of tiles on the board.*/
    int tiles;

    /** Table with board layout.*/
    int boardTable[];

    /** Initial board table. Used for resetting the board. */
	private int[] boardInitTable;

    /**Size of boardTable.*/
    int boardTableSize;

	/** Code for START state.*/
	static final int START = 0;

	/** Code for PLAYING state.*/
	static final int PLAYING = 1;

	/** Code for WON state.*/
	static final int WON = 2;

	/** Variable for storing game's state*/
	int gameState;

	/** Tile's size.*/
	int tileSize = 20;

	/** Image for free position.*/
	Bitmap freePosImage;

	/** Image for tile.*/
	Bitmap tileImage;

	/** Image for unselected tile.*/
	Bitmap tileSelectedImage;

	/** Image for empty space.*/
	Bitmap emptyImage;

	Bitmap freePosAllowedMoveImage;

	ImageResizer imageResizer;

	private int lastMoveUndone = -1;
	
	private boolean isInEditMode = false;
	
	private boolean isInSetTargetMode = false;
	
	/** Display theme. Default value is 'wood'. Must match value in XML file! */
	private String theme = "wood";

	/** Puzzle type. Default value is 'classic'. Must match value in XML file!  */
	private String type = "classic";

	private SoloBoard soloBoard;

	private SoloPuzzleRepository puzzleRepository;
	
	private List<SoloBoardPosition> boardPositions;	
	
    private int soloMovesCount;

	private SoloGame soloGame = null;
    
	public int getSoloMovesCount() {
        soloMovesCount = soloBoard.getMoveCount(movesTable);
		return soloMovesCount;
	}    
    
	public void setSoloPuzzleRepository(SoloPuzzleRepository puzzleRepository) {
		this.puzzleRepository = puzzleRepository;
	}	
	
	public int[] getBoardTable() {
		return boardTable;
	}
	
	public void setEditMode(boolean isInEditMode) {
		this.isInEditMode = isInEditMode;
	}
	
	public void setInSetTargetMode(boolean isInSetTargetMode) {
		this.isInSetTargetMode = isInSetTargetMode;
	}	
	
	public boolean isInSetTargetMode() {
		return isInSetTargetMode;
	}	

	public int getTargetPosition() {
		return soloBoard.getTargetPosition();
	}	
	
	public SoloPuzzle(Context context) {
		super(context);
		family = "Solo";
		enableAdd = true;
	}

	public void setGame(SoloGame soloGame) {
		this.soloGame  = soloGame;
	}	

	public boolean configure(SharedPreferences preferences) {
		boolean modified = false;
		String newTheme = preferences.getString("Solo_Theme", theme);
		String newType = preferences.getString("Solo_Puzzle_Type", type);
		if (SoloCustomBoardActivity.boardsListUpdated) {
			SoloCustomBoardActivity.boardsListUpdated = false;
			soloGame = null;
		}
		
		if (!newTheme.equals(theme) || !newType.equals(type) || soloGame == null) {
			theme = newTheme;
			if (!newType.equals(type) || soloGame == null) {
				type = newType;
				soloGame = puzzleRepository.getGame(newType);
				init();
				modified = true;
			}
			onSizeChanged(screenWidth, screenHeight);
		}

		return modified;
	}

	
	private void updateName() {
		boolean found = false;
		CharSequence[] names = context.getResources().getTextArray(R.array.solo_puzzles);
		CharSequence[] keys = context.getResources().getTextArray(R.array.solo_puzzle_values);

		for(int i=0; i<keys.length; i++) {
			String key = keys[i].toString();
			if (key.equals(type)) {
				name = names[i].toString();
				found = true;
				break;
			}
		}
		if (!found) {
			name = type;
		}
	}

	public void onSizeChanged(int w, int h) {
		super.onSizeChanged(w, h);

		int boardSize = (boardRows > boardColumns) ? boardRows : boardColumns;
		if (w==0 || h == 0 || boardSize == 0) {
			return;
		}
		int sizeX = (w - 10) / boardSize;
		int sizeY = (h - 10 - topBarHeight - bottomBarHeight) / boardSize;
		
		tileSize = sizeX < sizeY ? sizeX : sizeY;
		
		offsetX = (screenWidth - tileSize*boardColumns)/2;
		offsetY = (screenHeight - tileSize*boardRows)/2;
		
		if (offsetY < topBarHeight + 5) {
			offsetY = topBarHeight + 5;
		}

		imageResizer = new ImageResizer();

		if (theme.equals("marble")) {
			emptyImage = BitmapFactory.decodeResource(context.getResources(),
							R.drawable.marble_tile);
			tileImage = BitmapFactory.decodeResource(context.getResources(),
							R.drawable.glass);
			tileSelectedImage = BitmapFactory.decodeResource(context.getResources(),
							R.drawable.glass_selected);
		}
		else if (theme.equals("wood2")) {
			emptyImage = BitmapFactory.decodeResource(context.getResources(),
					R.drawable.wood);
			tileImage = BitmapFactory.decodeResource(context.getResources(),
							R.drawable.marble_sphere);
			tileSelectedImage = BitmapFactory.decodeResource(context.getResources(),
							R.drawable.marble_sphere_selected);
		}
		else {
			emptyImage = BitmapFactory.decodeResource(context.getResources(),
					R.drawable.wood);
			tileImage = BitmapFactory.decodeResource(context.getResources(),
							R.drawable.glass);
			tileSelectedImage = BitmapFactory.decodeResource(context.getResources(),
							R.drawable.glass_selected);			
		}

		freePosImage = BitmapFactory.decodeResource(context.getResources(),
				R.drawable.hole);
		freePosAllowedMoveImage  = BitmapFactory.decodeResource(context.getResources(),
				R.drawable.hole_selected);

		//imageResizer.init(emptyImage.getWidth(), emptyImage.getHeight(), tileSize, tileSize);
		//emptyImage = imageResizer.resize(emptyImage);
		imageResizer.init(freePosImage.getWidth(), freePosImage.getHeight(), tileSize, tileSize);
		freePosImage = imageResizer.resize(freePosImage);
		tileImage = imageResizer.resize(tileImage);
		tileSelectedImage = imageResizer.resize(tileSelectedImage);
		freePosAllowedMoveImage = imageResizer.resize(freePosAllowedMoveImage);
	}

    /**
     * Initialize puzzle's context. 
     */
	@Override
    public void init() {
    	moveCount = 0;

    	if (soloGame != null) {
			int [] board = soloGame.getBoard();
			boardInitTable = new int [board.length];
			boardTable = new int [board.length];
			System.arraycopy(board, 0,
					boardTable, 0, boardTable.length);
			System.arraycopy(board, 0,
					boardInitTable, 0, boardTable.length);
			int size = Solo.GET_BOARD_SIZE(boardTable);
			soloBoard = new SoloBoard(boardTable, size, size,
									  soloGame.getType(),
									  soloGame.getTargetPosition());
			boardColumns = size;
			boardRows = size;
			
	    	boardTableSize = boardTable.length;
	        tiles = 0;
	    	for(int i =0; i<boardTableSize; i++) {
	    		if (boardTable[i] == 1)
	    		    tiles++;
	    	}
	    	movesTableSize = tiles - 1;			
    	}

    	selPos = -1;
    	fired = false;

		// set up initial state
        soloMovesCount = 0;
		gameState = START;
		
		updateName();
        super.init();
    }

    private void drawBackgroundRepeat(Canvas canvas, Bitmap bgTile) {
    	float left = 0, top = 0;
    	float bgTileWidth = bgTile.getWidth();
    	float bgTileHeight = bgTile.getWidth();

    	while (left < screenWidth) {
    		while (top < screenHeight) {
    			canvas.drawBitmap(bgTile, left, top, null);
    			top += bgTileHeight;
    		}
    		left += bgTileWidth;
    		top = 0;
    	}
    }
    
    public void draw(Canvas canvas) {
		int index;
		Bitmap im;

		Paint targetPaint = new Paint();
		targetPaint.setColor(0xE0C0C0C0);
		targetPaint.setStyle(Paint.Style.FILL);
		
		drawBackgroundRepeat(canvas, emptyImage);

		for(int r = 0; r < boardRows; r++) {
			for(int c = 0; c < boardColumns; c++) {
				index = r * boardColumns + c;
				
				if (boardTable[index] == 0) {
                    if (fired && soloBoard.isAllowedJumpPosition(selPos, r, c)) {
                        im = freePosAllowedMoveImage;
					}
					else {
						im = freePosImage;
					}
				}
				else if (boardTable[index] == 1) {
					if (fired && index == selPos)
						im = tileSelectedImage;
					else
						im = tileImage;
				}
				else {
					continue;
				}
                int x = offsetX + c*tileSize;
                int y = offsetY + r*tileSize;
                
                if (soloBoard.isTriangular()) {
                    x+= ((boardColumns - r - 1)*tileSize)/2;
                }
                
				if (index == soloBoard.getTargetPosition()) {
					canvas.drawRect(x, y, x+tileSize, y+tileSize, 
								    targetPaint);
				}                
                
				canvas.drawBitmap(im, x, y, null);
			}
    	}
    }

	/**
	 * Processes input from user (mouse clicks).
	 *
	 * @param x Cursor's x position.
	 * @param y Cursor's y position.
	 * @return Return code for mouse click. One from RIDDLE_SOLVED,
	 *         MOVE_OUT_OF_BOUNDS, MOVE_NOT_ALLOWED, MOVE_SUCCESSFUL.
	 */
	public MoveResult onTouchEvent(float x, float y) {
        // Figure out the row/column
        int r = ((int) y - offsetY) / tileSize;                
        if (soloBoard.isTriangular()) {
        	x-= ((boardColumns - r - 1)*tileSize)/2;
        }
        int c = ((int) x - offsetX) / tileSize;
        int newPos = r*boardColumns + c;
        
        if (isInEditMode) {
        	if (newPos >= 0 && newPos < boardTable.length) {
	        	if (isInSetTargetMode) {
	        		if (boardTable[newPos] >= 0) {
	        			soloBoard.setTargetPosition(newPos);
	        		}
	        	}
	        	else {
		        	if (boardTable[newPos] == 1)
		        		boardTable[newPos] = -1;
		        	else if (boardTable[newPos] == -1)
		        		boardTable[newPos] = 0;
		        	else if (boardTable[newPos] == 0)
		        		boardTable[newPos] = 1; 
	        	}
        	}
        	return MoveResult.MOVE_EDIT;
        }
        
        
		if (gameState == WON) {
			return (MoveResult.RIDDLE_SOLVED);
		}

		if (newPos < 0 || newPos >= boardTableSize)
			return (MoveResult.MOVE_OUT_OF_BOUNDS);

		if (!fired) {
			if (boardTable[newPos]!=1)
				return(MoveResult.MOVE_NOT_ALLOWED);

			selPos = newPos;			
			if (soloBoard.isMoveAvailable(selPos)){
				fired = true;
				if (!isStarted) {
					isStarted = true;
				}
				return(MoveResult.MOVE_SUCCESSFUL);
			}
		}
		else {
			if (newPos == selPos) {
				fired = false;
				if (!isStarted) {
					isStarted = true;
				}
				return(MoveResult.MOVE_SUCCESSFUL);
			}
			else {
				if (boardTable[newPos]!=0)
					return(MoveResult.MOVE_NOT_ALLOWED);
				
				int dir = soloBoard.getDir(selPos, newPos);

				if (dir >= 0 && soloBoard.isMoveLegal(Solo.MAKE_VAL(selPos, dir))) {
					makeMove(dir);
                    soloMovesCount = soloBoard.getMoveCount(movesTable);
					fired = false;
					if (isSolved()) {
						gameState = WON;
						return (MoveResult.RIDDLE_SOLVED);
					}
					if (!isStarted) {
						isStarted = true;
					}
					return(MoveResult.MOVE_SUCCESSFUL);
				}
			}
		}
		return(MoveResult.MOVE_NOT_ALLOWED);
    }


	/**
	 * Makes move. Uses selPos static variable. Updates board's table,
	 * registes move, so that undoing is possible.
	 *
	 * @param dir Direction of move.
	 */
    public void makeMove(int dir) {
    	soloBoard.makeMove(selPos, dir);
		movesTable[moveCount] = Solo.MAKE_VAL(selPos, dir);

		moveCount++;

		if (gameState == START) {
			gameState = PLAYING;
		}
    }

	/**
	 * Returns true if undoing is supported and allowed.
	 *
	 * @return true if undoing is supported and allowed.
	 */    
    @Override
	public boolean isUndoPermitted() {
		return enableUndo && moveCount > 0 && gameState != WON;
	}    
    
    /**
     * Undoes last move.
     */
    public void undoLastMove() {
		if (moveCount == 0 || gameState == WON)
		    return;

		moveCount--;
		int pos = Solo.GET_BOARD_POS(movesTable[moveCount]),
		    dir = Solo.GET_DIR(movesTable[moveCount]);
		lastMoveUndone = movesTable[moveCount];


		movesTable[moveCount] = -1;
		soloBoard.undoMove(pos, dir);
		selPos = pos;

		if (moveCount == 0) {
			gameState = START;
		}

    	fired = false;
    }

	/**
	 * Returns puzzle's status.
	 *
	 * @return true if the puzzle is solved.
	 */
	public boolean isSolved() {
		return(moveCount == movesTableSize);
	}

	/**
	 * Initializes solver.
	 */
	protected void initSolver() {
		boardPositions = new ArrayList<SoloBoardPosition>();
		for(int i=0;i<boardTable.length;i++) {
			if (boardTable[i] >= 0) {
				boardPositions.add(new SoloBoardPosition(i));
			}
		}
	}

	/**
	 * Returns moves made so far.
	 *
	 * @return Returns moves made so far.
	 */
	protected int movesMade() {
		return(moveCount);
	}
	
	private int solvedDepth = 0;
	
	/**
	 * Finds the next possible move.
	 *
	 * @return true if a move has been found.
	 */
	protected boolean findNextMove() {
		int pos = 0;
		int dir = 0;
		
		if (lastMoveUndone >= 0) {
			pos = Solo.GET_BOARD_POS(lastMoveUndone);
			dir = 1 + Solo.GET_DIR(lastMoveUndone);
		}
		
		for(int i=pos; i<boardTableSize; i++) {
			for(int j=dir; j<Solo.DIRECTIONS_COUNT; j++) {
				selPos = i;
				if(soloBoard.isMoveLegal(Solo.MAKE_VAL(selPos, j)) &&
						isTargetPositionHit(selPos, j)) {
					lastMoveUndone = -1;
					makeMove(j);
					
					if (moveCount > solvedDepth) {
						solvedDepth = moveCount;
						//debugPrintBoard();
					}
					
					return(true);
				}
			}
			dir = 0;
		}

		return(false);
	}
	
	private boolean isTargetPositionHit(int selPos, int dir) {
		if (moveCount < movesTableSize - 1)
			return true;
		int targetPosition = soloBoard.getTargetPosition();
		if (targetPosition < 0)
			return true;
		int lastPos = soloBoard.getNewPos(selPos, dir);
		return targetPosition == lastPos;
	}	

	
	private boolean replayShowSelectedPeg = false;
	
	/**
	 * Replays one move.
	 *
	 * @return false if there are no more moves to replay.
	 */
    protected boolean playNextMove() {
        if (solMovesTableIndex >= solMovesTableSize)
            return(false);
    	
    	if (!replayShowSelectedPeg) {
    		replayShowSelectedPeg = true;
    		fired = true;
	
	        int pos = Solo.GET_BOARD_POS(solMovesTable[solMovesTableIndex]);
	
	        selPos = pos;
	
	        if (solMovesTableIndex >= solMovesTableSize)
	            return false;
    	}
    	else {
    		replayShowSelectedPeg = false;
    		fired = false;
	        int dir = Solo.GET_DIR(solMovesTable[solMovesTableIndex]);
	        solMovesTableIndex++;
	        makeMove(dir);
            soloMovesCount = soloBoard.getMoveCount(movesTable);
    	}
    	return true;
    }

	/**
	 * Goes back (undo last move) when a deadlock is encountered.
	 *
	 * @return If it is impossible to go back false is returned. This
	 *         means that the puzzle cannot be solved.
	 */
	protected boolean goBack()	{

		if (gameState == START)
			return(false);

		undoLastMove();
		
		return(true);
	}
    
    public boolean areMovesLeft() {
    	if (soloBoard == null) {
    		return false;
    	}    	
        return soloBoard.isNotSolvable();
    }

}


By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0


Written By
Software Developer (Senior) Self employed
Greece Greece
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions