Click here to Skip to main content
15,881,812 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.3K   7.1K   77   18
Puzzles Solver is an Android application for playing and solving puzzles.
Puzzles Solver

Introduction

Puzzles Solver is an application that I've uploaded to the Android Market some time ago. The idea for the application came from an old CodeProject article. In that article, I have presented a collection of some well known puzzles along with a mechanism for computer solving them. The puzzles were implemented as Java Applets, so it was easy to port them to Android platform. The application provides the following games:

  • 8 Queens: The player needs to place 8 queens in a chess board, so that no one attacks another.
  • Knight Tour: Guide a knight through all the squares of a chess board and return to the beginning.
  • Solo: Jump over pegs to remove them. Remove all but the last peg.

The application provides variations for all the games. It can also solve the puzzle for you. No pre-stored solutions are used. Instead the application tries to compute a solution on the fly, by applying a simple brutal-search algorithm.

In this article, I want to present all the design decisions I've made and the patterns I've followed, while developing the application. I hope that readers of the article will find them useful and perhaps re-use them on their own applications.

UI Diagram

The following diagram depicts the wireframes of the application's activities. When starting to design a new application, it is a good idea to try to sketch out, how the application will look. This will help you identify User Interface challenges early enough. It will also help on the mapping of visual components to Java classes.

Wireframe diagram for Puzzle Solver

In the context of this article, the wireframes help in presenting some important User Interface patterns. The application can launch pretty quick, so there is no need for a splash screen. The application starts immediately with the main screen, which just presents a row of buttons. This screen provides visual cues of what functionality the applications offers (start a puzzle, see the scores, get help) and allows the user to access it with one or two touches. Of course, you can style the first screen to be something more interesting than a row of buttons, but there will be very few exceptions to the rule that every mobile application should make all important functionality easily accessible from the first screen. The first screen also offers a menu, but this isn't necessary. The menu offers the same functionality as the menus. Only the about box is hidden in it. You can hide such functionality in the menu, in order to save screen estate and not to distract the user.

The transition from the main screen to the puzzles and to the scores (each puzzle has a different scores screen) is done through a list. When the button is pressed, a list with the available puzzles appear. Another solution would be to use a separate screen (implemented by a new activity) to present all the choices. This would be ideal, if there were many game choices to be made (e.g. online game, timed and non-timed mode, level selection).

Game Engine

The game engine is based on an article I wrote here on CodeProject as late back as in 2004. The idea of supporting more than one puzzle in the same application is to create an abstract class that implements all common functionality. For each individual puzzle, there is a concrete implementation of the abstract class. This is an excerpt from the solve method, as this is implemented in the abstract class (slightly modified for demonstration purposes):

Java
while(!searched_all && movesMade() != movesTableSize) {
    if (!findNextMove()) {
        searched_all = !goBack();
        while(!searched_all &&
              movesMade() == 0) {
            searched_all = !goBack();
        }
    }
}

This while loop is the heart of the solver algorithm. The individual puzzles implement the methods called in it, namely movesMade(), goBack() and findNextMove(). This is actually a form of the template method design pattern.

The abstract Puzzle class is also used to provide an Interface for callers of the individual Puzzle classes. The concrete implementations appear only when a puzzle is initialized. The following class diagram depicts the main classes of the application, namely PuzzleActivity, PuzzleView and Puzzle.

Puzzles Solver Class Diagram

In onCreate method of the PuzzleActivity, a PuzzleView instance is initialized. Also a Puzzle object is created and is injected into the PuzzleView.

Java
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Intent intent = getIntent();
    Bundle bundle = intent.getExtras();
    gameType = bundle.getInt("GameType", 0);

    switch(gameType) {
    case 0:
        puzzle = new Q8Puzzle(this);
        break;
    case 1:
        puzzle = new NumberSquarePuzzle(this);
        break;
    case 2:
        puzzle = new SoloPuzzle(this);
        break;
    }

    timeCounter = new TimeCounter();

    puzzle.init();
    puzzleView = new PuzzleView(this);
    puzzleView.setPuzzle(puzzle);
    puzzleView.setTimeCounter(timeCounter);

    setContentView(puzzleView);

    timerHandler = new Handler();
    replayHandler = new Handler();

    scoresManager = ScoresManagerFactory.getScoresManager(getApplicationContext());
}

This is the only place, where concrete implementations of a Puzzle appear. The PuzzleView, which is responsible for servicing the User Interface, performs the job by calling the Puzzle.draw and Puzzle.onTouchEvent methods. If a new puzzle were to be added, the only thing that will be needed is a new puzzle case statement.

In the second version of the article, I've added support for creating custom boards in Solo puzzle. This led to some special handling added:

Java
case R.id.custom_boards:
    if (puzzle instanceof SoloPuzzle) {
        if (soloPuzzleRepository != null && 
                soloPuzzleRepository.getCustomBoardsCount() > 0) {
            Intent editIntent  = new Intent
        ("gr.sullenart.games.puzzles.SOLO_EDIT_BOARDS");
            startActivity(editIntent);                    
        }
        else {
            SoloCustomBoardActivity.showDirections = true;
            Intent addIntent  = new Intent
        ("gr.sullenart.games.puzzles.SOLO_ADD_BOARD");
            startActivity(addIntent);
        }
    }

However the base class could also be extended in order to support custom boards. After all, adding custom boards is a common feature of many puzzles. This way, the special handling would go away.

Supporting Multiple Screens

As Android runs on a variety of devices, it is important for an application to be able to support different screen sizes and densities. The official documentation provides a thorough guidance of the screens support process. This is a constantly evolving document, as with every new version of the SDK new screen sizes and densities are being added.

On top of the practices described there, I will present here a technique of using tiles to draw the User Interface. The tiles are small rectangular images (for example 80x80 or 100x100 pixels). The User Interface is constructed by repeating these images horizontally and vertically. This technique takes advantage of the fact that in Android you can easily resize an image dynamically. Thus a single set of tiles can support a variety of screen sizes, as well as both portrait and landscape orientations. I should note however that this technique is appropriate for puzzle-like games or other applications, where there are only slow changes on the User Interface. It may not be efficient to use it for fast changing User Interfaces. This technique also provides a way of supporting theming in an application.

Let's start from using a tile for creating a background in a layout. This is as easy as defining a drawable XML:

XML
<?xml version="1.0" encoding="utf-8"?>

<bitmap    xmlns:android="http://schemas.android.com/apk/res/android"
        android:src="AndroidPuzzlesSolver/@drawable/bg_tile"
        android:tileMode="repeat"
        android:dither="true" />

And then in the layout XML:

XML
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:orientation="vertical"

                android:background="@drawable/background">

This solves the problem, when you create the User Interface from a layout. For puzzles and simple games however, it is very common to draw everything on the onDraw method of a View. The following method will draw the background on a canvas, when called at the beginning of the onDraw:

Java
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;
    }
}

In order to use images as tiles to draw the User Interface in multiple screen sizes, some resizing is needed. The following diagram depicts the organization of the screen. The dashed lines show the background tiles. Since this is just a repetition of the same image and there is no need for showing full images at the edges, no resizing is necessary. The solid lines show where the game tiles must be placed in order to form the game board.

Using tiles for creating the User Interface

As an example, the Solo puzzle screen is drawn with the following tiles. All images were generated using Gimp.

TypeWood ThemeMarble Theme
board tileImage 5Image 6
hole tileImage 7Image 8
peg on a holeImage 9Image 10
selected pegImage 11Image 12
selected holeImage 13Image 14

First let's look at the ImageResizer class.

Java
public class ImageResizer {

    private Matrix matrix;

    public void init(int oldWidth, int oldHeight, float newWidth, float newHeight) {
        float scaleWidth = newWidth / oldWidth;
        float scaleHeight = newHeight / oldHeight;
        matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);
    }

    public Bitmap resize(Bitmap bitmap) {
        if (matrix == null) {
            return bitmap;
        }

        int width = bitmap.getWidth();
        int height = bitmap.getHeight();

        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
                                    width, height, matrix, true);
        return resizedBitmap;
    }
}

This is a utility class that provides a method for dynamically resizing a bitmap. An instance of this class needs to be initialized by providing the dimensions of the original and resized bitmaps. This class is utilized in the onSizeChanged method of the puzzle View. The onSizeChanged method is called whenever the dimensions of the screen change (e.g. when an orientation change happens or the screen is first displayed). At this method, the tile images are loaded from the application's resources. Depending on the theme, different images are loaded. The loaded bitmaps are then resized to fit into the existing screen and kept in memory. The images are drawn on the canvas at the draw method of every puzzle.

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

    int boardSize = (boardRows > boardColumns) ? boardRows : boardColumns;

    if (w < h) {
        tileSize = (w - 10) / boardSize;
    }
    else {
        tileSize = (h - 10) / boardSize;
    }
    offsetX = (screenWidth - tileSize*boardColumns)/2;
    offsetY = (screenHeight - tileSize*boardRows)/2;

    imageResizer = new ImageResizer();

    if (theme.equals("marble")) {
        emptyImage = BitmapFactory.decodeResource(context.getResources(),
                        R.drawable.marble_tile);
        tileImage = BitmapFactory.decodeResource(context.getResources(),
                        R.drawable.wood_sphere);
        tileSelectedImage = BitmapFactory.decodeResource(context.getResources(),
                        R.drawable.golden_sphere);
    }
    else {
        emptyImage = BitmapFactory.decodeResource(context.getResources(),
                R.drawable.wood_tile);
        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_move);

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

Popup Window

In the third revision of the article, I've replaced the simple list puzzle selector with a much nicer Popup Window. You can compare the difference of these two selectors in the following figure:

Puzzle Selectors

The new Popup Window is aesthetically more appealing. It also makes the puzzle selection quicker, as it appears closer to the point, where the user first touched and it doesn't grey out the whole screen. It can also be easy animated. In order to create a Popup Window, you start from a layout XML file, as you would have done for a Dialog or a View. It is important to define a background for your layout, because the Android PopupWindow doesn't provide one by default. In Java code, you make the PopupWindow appear with the following code:

Java
private void showGameSelectPopup(View parentView) {
    LayoutInflater inflater = (LayoutInflater)
                getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View layout = inflater.inflate(R.layout.puzzle_select, null, false);        
    gameSelectPopupWindow = new PopupWindow(this);
    gameSelectPopupWindow.setTouchInterceptor(new OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
                gameSelectPopupWindow.dismiss();
                gameSelectPopupWindow = null;
                return true;
            }
            return false;
        }
    });
    gameSelectPopupWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
    gameSelectPopupWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
    gameSelectPopupWindow.setTouchable(true);
    gameSelectPopupWindow.setFocusable(true);
    gameSelectPopupWindow.setOutsideTouchable(true);
    gameSelectPopupWindow.setContentView(layout);
    
    // Set click listeners by calling layout.findViewById()
    // layout.findViewById(R.id.queens_select).setOnClickListener();
    
    int [] location = new int [] {0, 0};
    parentView.getLocationInWindow(location);
    int width = parentView.getWidth()/2;
    int x = location[0] - width/2;
    int y = location[1] + parentView.getHeight();    
    
    gameSelectPopupWindow.setAnimationStyle(R.style.AnimationPopup);
    gameSelectPopupWindow.showAtLocation(layout, Gravity.NO_GRAVITY, x, y);    
}

@Override
public void onStop() {
    if (gameSelectPopupWindow != null) {
        gameSelectPopupWindow.dismiss();
    }
    super.onStop();
}

The show method accepts as parameter a view, which is the Button or other item that initiated the action. This allows to position the PopupWindow relative to the point the user first clicked. Initially, we create the View for the PopupWindow by inflating the layout. Then we create the PopupWindow object and configure it appropriately. We use the getLocationInWindow(), getWidth() and getHeight() methods of the parent View to position the Popup properly. Finally, we set the animation style and show the Popup Window. If the user clicks outside of the window, this will be dismissed. In order not to leak a window, we need to check if the popup is visible, when the activity is stopped and dismiss it ourselves. This can happen for instance, if the orientation is changed, while the Popup is visible.

Another tricky point is the animation style. This must be defined in a styles.xml file in the values folder and have the below form:

XML
<resources>
    <style name="AnimationPopup">
        <item name="@android:windowEnterAnimation">@anim/popup_show</item>
        <item name="@android:windowExitAnimation">@anim/popup_hide</item>
    </style>
</resources>

Keeping Scores

In order to store the user's scores, you need a persistence mechanism. Persistence options for the Android platform are described in the documentation. From these options, the most appropriate for storing scores is the SQLite database. The following statement presents the schema of the table I have used:

Java
private static final String DATABASE_CREATE =
        "create table Scores (_id integer primary key autoincrement, " +
        "game text not null, category text not null, 
        player text not null, date text, " +
        "score integer not null);";

In order to abstract the database from the rest of the code, I have created a ScoresManager class, which provides methods for accessing the scores.

Java
public class ScoresManager {
    public boolean addScore(String game, String group, String player, int score);
    public boolean isHighScore(String game, int score);
    public List<Score> getScores(String game);
    public List<Score> getScoresByGroup(String group);
}

I should point out that there are many open source score libraries for Android. You can find well tested libraries that provide a wealth of features, including integration with Web score systems. If not for the point of learning to interact with an SQLite database, it would be better to use one of these for your scoring needs.

Settings

Another persistence need is to store user's selections. Since each puzzle comes in many variations, it would be good if the application remembered, which version of the game the user last played. You can easily achieve this in Android using SharedPreferences.

The SharedPreferences are defined in a separate XML file for every puzzle. The Puzzle abstract class defines the configure method. The concrete classes implement this method in order to read the user's settings.

Java
public boolean configure(SharedPreferences preferences)

Android provides the PreferenceActivity, which you can very easily extend in order to create a simple User Interface for editing the settings.

Java
public class PuzzleOptionsActivity extends PreferenceActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            Intent intent = getIntent();
            Bundle bundle = intent.getExtras();
            int gameResources = bundle.getInt("GameResources", 0);
               addPreferencesFromResource(gameResources);
    }
}

Of course, this is not the only way for exploiting SharedPreferences. You can create a fancy User Interface and read and write to SharedPreferences using Java code. In Puzzles Solver, I am using a PreferencesScreen without actually displaying anything, in order to store the user's name. When the user achieves a high score, the congratulations screen that asks her to enter her name is prefilled with the name previously used.

Localization

The application is localized in English, German and Greek. The localized elements are the text of the application (stored in strings.xml files at values, values-de and values-el folders), the logo of the application (stored in drawable, drawable-de, drawable-el folders) and the application's help page, which is stored as an HTML page in the assets folder (more details for this later).

Help

It is very important to provide instructions on the use of the application. Of course, the application should be easy to use and the interface intuitive, so that a user can start using the application, without reading a single line of a help file. That is not a reason for not having a good help page. Some users may not be familiar with the concept of your game or application and will need some guidance. Also, you may be able to provide some tips and tricks in your help page that even advanced users will find helpful.

I believe that using a WebView to display an HTML file is an ideal solution for implementing the help page in an Android Application. The amount of Java code required is minimal and then you only need one or more HTML files in the assets folder. It is very easy to author an HTML help page. Styling the text and adding images is trivial. You may also be able to re-use an existing page describing your application. Using HTML for the help page is so easy that it makes me wonder why so many applications still prefer to use other help systems, like displaying all information on a dialog.

If you want to support more than one language, then you will need different versions of the HTML page. Place all the pages in the assets folder. Then in the strings.xml, define the name of the page for every language:

XML
<string name="help_file_name">index.html</string>

When loading the page in the WebView, read the name from the resources:

Java
webview.loadUrl("file:///android_asset/" +
        getResources().getString(R.string.help_file_name));

Unobtrusive Ads

It is very common for mobile applications, especially for these given away for free, to display some sort of ads. These ads can generate revenue for the developer, while still allowing her to freely distribute the application. There are many Ad frameworks available to chose from. For PuzzleSolver, I have chosen AdMob. Whatever framework you chose, you need to make sure that the ads do not distract the user. In games, ads should only be displayed in auxiliary screens (game selection, scores, help) and not in the game screen. Ads should not distract the user for playing the game.

In order to be able to display ads in more than one Activity, without repeating the same code, I have created a utility class called AdsManager. AdsManager implements AdListener and provides addAdsView method, which sets the publisher id, the test devices' ids and adds the request to the view.

Java
public class AdsManager implements AdListener {
    private String publisherId = "your publisher id here";

    public void addAdsView(Activity activity, LinearLayout layout) {
        AdView adView;
        int screenLayout = activity.getResources().getConfiguration().screenLayout;
        if ((screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= 3) {
        	adView = new AdView(activity, AdSize.IAB_LEADERBOARD, publisherId);
        }
        else {
        	adView = new AdView(activity, AdSize.BANNER, publisherId);
        }
        layout.addView(adView);
        AdRequest request = new AdRequest();
        request.addTestDevice(AdRequest.TEST_EMULATOR);
        request.addTestDevice("Your test device id here - Find the id in Log Cat");
        adView.loadAd(request);
   } 

Notice that there is a differentation for different screen sizes. For small and normal screens (values 1 and 2) a Banner is displayed. For large and extra large screens (values 3 and 4) a Leaderboard is displayed.

This method is called in the onCreate method of every activity that displays ads: 

Java
LinearLayout layout = (LinearLayout)findViewById(R.id.banner_layout);
(new AdsManager()).addAdsView(this, layout);

History

  • October 2012:
    • Added support for triangular boards and diagonal moves in Solo.
    • On board buttons (Undo, Restart, Solve).
    • A message appears when there are no more moves left.
  • July 2012: Fixed bugs. New icon. Use latest version of Android tools. Admob jar included in the libs folder.
  • April 2012: New scores screen. Better support for large screens, including changes for AdMob. One new Solo theme. Option for displaying connecting lines in Knight's tour. Eliminated most of Lint warnings.
  • January 2012: Added new Popup Window for puzzle selection
  • January 2012: Added support for creating custom boards in Solo. Bug fixing
  • First version of the article

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

 
QuestionNullpointer Pin
Member 110686408-Sep-14 22:39
Member 110686408-Sep-14 22:39 
Questionproblem! Pin
debdarshini kikku20-Jun-12 0:05
debdarshini kikku20-Jun-12 0:05 
AnswerRe: problem! Pin
Giannakakis Kostas20-Jun-12 20:15
professionalGiannakakis Kostas20-Jun-12 20:15 
GeneralRe: problem! Pin
debdarshini kikku2-Jul-12 20:57
debdarshini kikku2-Jul-12 20:57 
GeneralRe: problem! Pin
Giannakakis Kostas11-Jul-12 22:34
professionalGiannakakis Kostas11-Jul-12 22:34 
AnswerRe: problem! Pin
Giannakakis Kostas11-Jul-12 22:33
professionalGiannakakis Kostas11-Jul-12 22:33 
GeneralRe: problem! Pin
debdarshini kikku14-Aug-12 19:43
debdarshini kikku14-Aug-12 19:43 
QuestionSource Code Pin
Dewey19-Apr-12 9:16
Dewey19-Apr-12 9:16 
AnswerRe: Source Code Pin
Giannakakis Kostas20-Apr-12 7:00
professionalGiannakakis Kostas20-Apr-12 7:00 
GeneralMy vote of 5 Pin
Saleem CB2-Feb-12 19:45
Saleem CB2-Feb-12 19:45 
GeneralMy Vote for 5 Pin
Asif Bg30-Jan-12 19:58
Asif Bg30-Jan-12 19:58 
GeneralRe: My Vote for 5 Pin
Giannakakis Kostas31-Jan-12 7:00
professionalGiannakakis Kostas31-Jan-12 7:00 
GeneralMy vote of 5 Pin
Kanasz Robert17-Jan-12 4:46
professionalKanasz Robert17-Jan-12 4:46 
GeneralRe: My vote of 5 Pin
Giannakakis Kostas17-Jan-12 18:22
professionalGiannakakis Kostas17-Jan-12 18:22 
GeneralMy vote of 5 Pin
Alberto Bar-Noy7-Dec-11 3:45
Alberto Bar-Noy7-Dec-11 3:45 
GeneralRe: My vote of 5 Pin
Giannakakis Kostas7-Dec-11 9:26
professionalGiannakakis Kostas7-Dec-11 9:26 
GeneralRe: My vote of 5 Pin
Alberto Bar-Noy7-Dec-11 10:06
Alberto Bar-Noy7-Dec-11 10:06 
GeneralRe: My vote of 5 Pin
Giannakakis Kostas7-Dec-11 20:18
professionalGiannakakis Kostas7-Dec-11 20:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.