Click here to Skip to main content
15,867,889 members
Articles / Mobile Apps / Android

EnigmaPuzzle for Android

Rate me:
Please Sign up or sign in to vote.
4.98/5 (55 votes)
17 Nov 2014CPOL16 min read 95.4K   12.7K   114   27
Enigma Puzzle for Android – a game as difficult as the Rubik's cube

Image 1

Introduction

Welcome to the Enigma Puzzle App – a game as difficult as the Rubik's cube.

A few weeks ago, I posted an article about a game I've written with C#. This article here reports on the porting of this program to the Android platform. You can read the original CodeProject article about the game - EnigmaPuzzle. There you can find information about the game and how it is implemented with C#. I will not repeat that here.

Since this is my first Java program with more then 10 lines of code and the first Android App at all, I used a lot of time to Google for examples and tips on how things have to be done in Java and/or on the Android platform. A lot of things are very different from the way I am used to program and I had to find a way to implement it (maybe sometime it's not the right way). Some keywords may be: Activity, Layout, Preferences and SurfaceView. But quite a lot of things are also very similar to C# and .NET. For example, the base syntax of C# looks almost like Java. And the best thing is that the basics to draw graphics to the screen are very similar. There are Bitmaps, Paths and other graphic objects on both systems and they are used in similar ways.

The First Steps

I started my project by installing the Android SDK and Eclipse like it is described in Installing the SDK. After that, I went through the Hello World tutorial (Hello World tutorial) to get a feeling of how the Android framework looks like. I also checked out the Lunar Lander example which can be found at Lunar Lander.

Then I was ready to create my first Android project in Eclipse. Thanks to the ADT plugin for Eclipse, the work with the Android framework is very handy and very well supported. The functions to use Android are integrated in the context menus of Eclipse. So I was able to create the project structure in Eclipse by just using the menu: File-New-Project-Android Project. For the build target, I set Android 2.2, but I think even a lower target would have been possible. Next, I had to define a package name (ch.adiuvaris.enigma) which must be unique if the App will be offered via the Android market. I left the other settings as proposed by the system. After the click on the Finish button, I had a minimal Android project with all the necessary folders and files.

Image 2

There are a lot of folders but I only worked in two of them. The Java source code files go to the folder src/ch.adiuvaris.enigma and the resource files (layouts and strings) are stored in the folder res and some sub-folders of it.

The Base Classes

Now I started to port some of my base classes from C# to Java and the Android framework. First of all, I had to find the right classes and methods in the framework to reflect some .NET classes and their methods.

Java C#
Java
Path
    drawPath()
    addArc(), arcTo()
    moveTo(), lineTo()
    addPath()
    transform()

RectF

Matrix
    setRotate()
    setTranslate()

Paint, Canvas
    setAntiAlias()

Bitmap
C#
GraphicsPath
    FillPath(), DrawPath()
    AddArc()
    AddLine()
    AddPath()
    Transform()

RectangleF

Matrix
    RotateAt()
    Translate()

Brush, Pen, Graphics
    SmoothingMode()

Bitmap

As you can see, there are similar methods and classes. But I had to realize that they do not work in the very same way or that they have different parameters. There are, for example, the C# methods FillPath to fill the area of a closed path with a certain color and DrawPath which just draws the border of a path. The Android version of Path knows only the method drawPath and it depends on one of the parameters (Paint) what will be drawn. The class Paint contains a method (setStyle()) which defines if the path will be filled (Style.FILL) or if the border will be drawn (Style.STROKE).

Sometimes the functionality of classes overlap. For example, the C# classes Brush, Pen and Graphics and the Android classes Paint and Canvas offer the same functionality but the methods are spread over the classes. In C#, you have to draw into a Graphics object whereas in Android, you have to use a Canvas object. In C#, the settings for antialiasing of the drawing has to be set in the Graphics object but in Android, it has to be set in the Paint object.

Even things that seem to be the same may differ. Look at the C# class RectangleF and the Android class RectF and their constructors.

Java C#
Java
RectangleF r = new RectangleF(6.60254F, 20, 160, 160);
C#
RectF r = new RectF(6.60254F, -80, 166.60254F, 80);

They look the same but the C# version expects left, top, width and the height of the rectangle and the Android version expects left, top, right and bottom. So I had to do some calculations to get the correct rectangles in the Java code.

The Class Block

To start, I added some files to the folder src/ch.adiuvaris.enigma to reflect some of the classes I programmed for the C# version of the game. The filenames were Block.java, Figure.java and Board.java.

At the beginning, the porting of the class Block was straight forward as you can see in the following code section:

Java C#
Java
public class Block {

    public Path GP;
    public int Col;
    public int Edge;

    public Block() {
        GP = new Path();
        Col = -1;
        Edge = -1;
    }

    public void onPaint(Canvas canvas) {
        if (Col >= 0 && 
	Col < m_colors.length) {
            canvas.drawPath
		(GP, m_colors[Col]);
        }
        if (Edge >= 0 && 
	Edge < m_pens.length) {
            canvas.drawPath
		(GP, m_pens[Edge]);
        }
    }

    ...
C#
public class Block
{
    public GraphicsPath GP { get; set; }
    public int Col { get; set; }
    public int Edge { get; set; }

    public Block()
    {
        GP = new GraphicsPath();
        Col = -1;
        Edge = -1;
    }

    public void Paint(Graphics g)
    {
        if (Col >= 0 && Col < m_colors.Count())
        {
            g.FillPath(m_colors[Col], GP);
        }
        if (Edge >= 0 && Edge < m_pens.Count())
        {
            g.DrawPath(m_pens[Edge], GP);
        }
    }
    ...

It was also no problem to port the static members and arrays in the class Block by using the Android class Paint instead of the C# classes Brush and Pen. Because the Android class Paint defines the color and if the region will be filled or just outlined.

Java C#
Java
private static int[][] m_games = new int[][] {
    ...
};

private static Block[] m_blocks = null;
private static Paint[] m_colors = null;
private static Paint[] m_pens = null;
C#
private static int[,] m_games = new int[11, 62]  {
    ...
};

private static Block[] m_blocks = null;
private static Brush[] m_colors = null;
private static Pen[] m_pens = null;

But then came the creation of the graphic parts int the Init() method and the already mentioned problems of the different constructors for rectangles and there is also a different handling of adding lines and arcs to the Path objects.

Java C#
Java
// Create the first sub-part of the first stone
m_blocks[0].GP.addArc(new RectF
(6.60254F, 20, 166.60254F, 180), 180, 21.31781F);
m_blocks[0].GP.arcTo(new RectF
(-80, 70, 80, 230), 278.68219F, 21.31781F);
m_blocks[0].GP.lineTo(28.86751F, 100);
m_blocks[0].GP.close();

Matrix mat120 = new Matrix();
mat120.setRotate(120.0F, 28.86751F, 100);

// The second sub-part of the first stone 
// (rotate the first by 120 degrees)
m_blocks[1].GP.addPath(m_blocks[0].GP);
m_blocks[1].GP.transform(mat120);
C#
// Create the first sub-part of the 
// first stone
m_blocks[0].GP.AddArc(new RectangleF
(6.60254F, 20, 160, 160), 180, 21.31781F);
m_blocks[0].GP.AddArc(new RectangleF

(-80, 70, 160, 160), 278.68219F, 21.31781F);
m_blocks[0].GP.AddLine(new PointF(40.00000F, 80.71797F), 
new PointF(28.86751F, 100));
m_blocks[0].GP.AddLine(new PointF(28.86751F, 100), 
new PointF(6.60254F, 100F));

Matrix mat120 = new Matrix();
mat120.RotateAt(120.0F, new PointF(28.86751F, 100));

// The second sub-part of the 
// first stone (rotate the first 
// by 120 degrees)
m_blocks[1].GP.AddPath(m_blocks[0].GP, false);
m_blocks[1].GP.Transform(mat120);

In C# I added just some sort of lines together to get the path. Each part has a start point and an endpoint. Look, for example, at the C# methods AddLine(startpoint,endpoint) or AddArc(rect,startangle,sweep). Android, on the other hand, expects that the parts of a path are line segments which starts at the last endpoint. It is therefore not possible to use addArc() for all parts, but I had to use addArc() for the first part of the path and then arcTo() and lineTo() for the next parts of the path. Fortunately, the parameters of AddArc and arcTo are the same (except for the rectangles of course).

The rotations and translations with the Matrix objects work the same way on both systems. So no problems to port that. To create new Path objects based on another Path object with addPath() was also easy to port, because it works the same way.

The Class Figure

This class was very easy to port, because it doesn't use any special things - except the C# class List. I replaced that with Java class ArrayList and had to change the syntax to loop through the array. Some members of the array classes are also different (size() instead of Count) and the access of an element at a given index is in Java not possible via the [idx] syntax and I had to use the get() method of the array as you can see in the getColorString() method.

Java C#
Java
public class Figure {

    private ArrayList<Block> m_blocks;
    private int m_orient;

    public Figure() {
        m_blocks = new ArrayList<Block>();
        m_orient = 0;
    }

    public void incOrient() {
        m_orient++;
        m_orient %= 3;
    }

    public void decOrient() {
        m_orient--;
        if (m_orient < 0) {
            m_orient += 3;
        }
    }

    public void addBlock(int nr) {
        m_blocks.add(Block.getBlocks()[nr]);
    }


    public void onPaint(Canvas canvas) {
        for (Block block : m_blocks) {
            block.onPaint(canvas);
        }
    }

    public void transform(Matrix mat) {
        for (Block block : m_blocks) {
            block.GP.transform(mat);
        }
    }

    public String getColorString() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; 
	i < m_blocks.size(); i++) {
            int idx = (i + m_orient) % 3;
            sb.append(Integer.toString
		(m_blocks.get(idx).Col));
        }
        return sb.toString();
    }
}
C#
public class Figure
{
    private List<block> m_blocks;
    private int m_orient;

    public Figure()
    {
        m_blocks = new List<block>();
        m_orient = 0;
    }

    public void IncOrient()
    {
        m_orient++;
        m_orient %= 3;
    }

    public void DecOrient()
    {
        m_orient--;
        if (m_orient < 0)
        {
            m_orient += 3;
        }
    }

    public void AddBlock(int nr)
    {
        m_blocks.Add(Block.Blocks[nr]);
    }

    public void Paint(Graphics g)
    {
        foreach (Block block in m_blocks)
        {
            block.Paint(g);
        }
    }

    public void Transform(Matrix mat)
    {
        foreach (Block block in m_blocks)
        {
            block.GP.Transform(mat);
        }
    }

    public string GetColorString()
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < m_blocks.Count; i++)
        {
            int idx = (i + m_orient) % 3;
            sb.Append(m_blocks[idx].Col.ToString());
        }
        return sb.ToString();
    }
}

The Class Board

Surprisingly, it was not too hard to port this class - after I removed some methods for the beginning. I added the removed functionality later or implemented it in the BoardView class.

Methods like initBoard, newGame, getColorString, onResize and others could be ported straight forward. Even the port of the most complex methods in this class like rotateDisk, rotate and turnDisk were quite easy.

Even in methods like paintDisk or paintBackground, I had just to replace a parameter (Canvas instead of Graphics) and made some syntax adjustments for Java.

Java C#
Java
private void paintDisk(Canvas canvas, eDisc disc) {
    if (disc == eDisc.UpperDisc) {
        for (int i = 0; i < 6; i++) {
            m_bones[m_upperBones[i]].
			onPaint(canvas);
            m_stones[m_upperStones[i]].
			onPaint(canvas);
        }
    } else {
        for (int i = 0; i < 6; i++) {
            m_bones[m_lowerBones[i]].
			onPaint(canvas);
            m_stones[m_lowerStones[i]].
			onPaint(canvas);
        }
    }
}
C#
private void PaintDisk(Graphics g, eDisc disc)
{
    if (disc == eDisc.eUpperDisc)
    {
        for (int i = 0; i < 6; i++)
        {
            m_bones[m_upperBones[i]].Paint(g);
            m_stones[m_upperStones[i]].Paint(g);
        }
    }
    else
    {
        for (int i = 0; i < 6; i++)
        {
            m_bones[m_lowerBones[i]].Paint(g);
            m_stones[m_lowerStones[i]].Paint(g);
        }
    }
}

Creating a Bitmap

I had to rewrite the methods which create the bitmaps for the board (e.g. createUpperDisk()). They look really different.

Java C#
Java
private void createUpperDisk() {
    if (m_upperDisk != null) {
        m_upperDisk.recycle();
        m_upperDisk = null;
    }
    m_upperDisk = Bitmap.createBitmap
   (m_w, m_h, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas();
    canvas.setBitmap(m_upperDisk);
    paintDisk(canvas, eDisc.UpperDisc);
    canvas = null;
    System.gc();
}
C#
private void CreateUpperDisk()
{
    if (m_upperDisk != null)
    {
        m_upperDisk.Dispose();
    }
    m_upperDisk = new Bitmap(m_w, m_h);
    Graphics g = Graphics.FromImage(m_upperDisk);
    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
    PaintDisk(g, eDisc.eUpperDisc);
    g.Dispose();
}

The member m_upperDisk is a Bitmap object. In both languages, the bitmap has to be created for the necessary size. Then both systems need an object to paint to. In C#, it's a Graphics and in Android, a Canvas object. The call of PaintDisk() does the painting. You can find the details in onPaint() of the class Block.

In both systems, it is important to free the allocated memory for the bitmaps. In C#, that's done with a simple Dispose(). In the Java code, I had to use recyle().

BoardView an the Main Activity

As soon as I was able to compile the base classes, I wanted to see my board on the Android emulator. And it was not too hard to achieve that after I learned that I need a special kind of view to paint onto the screen. The keyword is SurfaceView. So I added a new class BoardView to my project which extends the class SurfaceView.

Java
public class BoardView extends SurfaceView {
    private Context m_ctx = null;
    private Paint m_paint = null;

    public BoardView(Context context) {
        super(context);
        m_ctx = context;

        m_paint = new Paint();
        m_paint.setColor(Color.BLACK);
        m_paint.setAntiAlias(true);

        m_board = new Board();
        m_board.initBoard(9);
    }

    @Override
    public void onDraw(Canvas canvas) {
        if (m_board == null || canvas == null) {
            return;
        }
        super.onDraw(canvas);

        // Draw the bitmaps in the right order
        canvas.drawBitmap(m_board.getBackground(), 0, 0, m_paint);
        if (m_board.getRotDisk() == Board.eDisc.UpperDisc) {
            canvas.drawBitmap(m_board.getLowerDisk(), 0, 0, m_paint);
            canvas.drawBitmap(m_board.getUpperDisk(), 0, 0, m_paint);
        } else {
            canvas.drawBitmap(m_board.getUpperDisk(), 0, 0, m_paint);
            canvas.drawBitmap(m_board.getLowerDisk(), 0, 0, m_paint);
        }
    }
}

In the constructor, I create a Paint object that will be used during the painting of the board. The Board object itself is also created there. After the creation of the board, I can use the bitmaps of the board to paint them to the screen. The bitmap objects may be accessed via the getter methods getLowerDisk(), getLowerDisk() and getBackground(). These methods are used in the onDraw() method of the view.

The Android method onDraw() in the code above reflects the C# method OnPaint() from EnigmaPuzzleDlg.cs and they look very similar as you can see in the following C# code fragment.

C#
protected override void OnPaint(PaintEventArgs e)
{
    if (e.ClipRectangle.Width == 0 || m_b == null)
    {
        return;
    }

    base.OnPaint(e);

    // Optimize to repaint only the necessary disk
    e.Graphics.DrawImageUnscaled(m_b.Background, 0, 0);
    if (m_b.RotDisk == Board.eDisc.eUpperDisc)
    {
        e.Graphics.DrawImageUnscaled(m_b.LowerDisk, 0, 0);
        e.Graphics.DrawImageUnscaled(m_b.UpperDisk, 0, 0);
    }
    else
    {
        e.Graphics.DrawImageUnscaled(m_b.UpperDisk, 0, 0);
        e.Graphics.DrawImageUnscaled(m_b.LowerDisk, 0, 0);
    }
}

There was only one single step to see the board on the screen. I had to set BoardView as the main view for the main activity. Therefore I had to change the generated code in EnigmaPuzzle.java in the following way:

Java
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(new BoardView(this));
}

The method setContentView is used to define the view that will be shown if the program starts. After I started the project as an Android Application from the Eclipse context menu, I saw my board. It was small and sat on the top left corner.

Of course, it didn't work the first time I started the App, because of the already mentioned differences in the rectangle and path classes. But after I figured out how the parameter has to be set correctly and how to create the paths, I saw my board.

The Real BoardView Runs in a Thread

Because I had already ported all the rotation methods in the class Board, it should have been possible to rotate the discs on the screen. But when I tried to turn a disc I only saw the resulting board, but no animation of the turning disc. The problem was that the onDraw() method of my BoardView was never called during the calculation of the rotation. A call to invalidate did not help either.

The solution of the problem was a thread for the BoardView. Only in a thread, it is possible to redraw the screen during a calculation. So I added another class GameThread to my project which extends the class Thread. I found the code for that class (and for the view) in different examples on the web and adapted it for my needs.

Java
public class GameThread extends Thread {
    private BoardView m_view;
    private boolean m_run = false;

    public GameThread(BoardView view) {
        m_view = view;
    }

    public void setRunning(boolean run) {
        m_run = run;
    }

    public void repaint() {
        if (!m_run) {
            return;
        }

        // Get the canvas and lock it
        Canvas c = null;
        try {
            c = m_view.getHolder().lockCanvas(null);
            synchronized (m_view.getHolder()) {
                m_view.onDraw(c);
            }
        } finally {

            // Make sure we don't leave the Surface in an inconsistent state
            if (c != null) {
                m_view.getHolder().unlockCanvasAndPost(c);
            }
        }
    }

    @Override
    public void run() {
        repaint();

        while (m_run) {
            try {
                Thread.sleep(1000);
            } catch (Exception ex) {
            }
        }
    }
}

The main functionality lies in repaint(). There I get thread safe access to a Canvas to paint the view. After I created my own canvas with m_view.getHolder().lockCanvas(null) and asked for a lock on the view with synchronized (m_view.getHolder()), I could call the onDraw() method of the view to paint the current bitmaps of the game board. Because no other code may call lockCanvas it is very important to release that lock with m_view.getHolder().unlockCanvasAndPost(c) in a finally block.

The class BoardView has to be changed to use the thread and it must implement the SurfaceHolder.Callback interface. The interface offers three methods which will be called when the surface has been created, when it has changed or when it will be destroyed. These methods may be used to start and stop the thread.

Java
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}

public void surfaceCreated(SurfaceHolder holder) {
    if (m_thread == null) {
        m_thread = new GameThread(this);
    }
    m_thread.setRunning(true);
    m_thread.start();
}

public void surfaceDestroyed(SurfaceHolder holder) {
    boolean retry = true;

    // Stop the thread
    m_thread.setRunning(false);
    while (retry) {
        try {
            m_thread.join();
            retry = false;
            m_thread = null;
            System.gc();
        } catch (InterruptedException e) {
            // we will try it again and again...
        }
    }
}

With these changes, the animation was visible and the main work of porting was done.

Using the full screen

Of course, the image of the board was still small and did not fill the whole screen. To correct that, I had to implement the method calcBoard() in the class BoardView and to call the method onResize() of the class Board. The following code shows the method calcBoard() which calculates the width and height of the board, so that the board fills the screen and the circles still keep its aspect and also that it doesn't matter if the device is hold landscape or portrait.

Java
private void calcBoard(int w, int h) {
    if (h > w) {
        BoardWidth = w;
        BoardHeight = (int) (h * Board.C_BoardWidth / Board.C_BoardHeight);
    } else {
        BoardHeight = h;
        BoardWidth = (int) (w * Board.C_BoardHeight / Board.C_BoardWidth);
    }
}

The following three lines of code can be called to create a board for a given game level an resize it so that if fills the screen. They are put together in the method createBoard() of the class BoardView.

Java
m_board.initBoard(m_level);
m_board.onResize(BoardWidth, BoardHeight, getWidth());
repaint();

The following screenshot shows the App in the emulator when you start it the first time.

Image 3

Extending the Main View

In C#, I used a timer to display and update the current standings of a running game. To display the current game standings was the next I wanted to do for the Android App. I started to use Toast messages, but that was not good at all because they will be shown a certain time and are too slow.

What I needed was a part of the screen where I could write to at any time. And in Android, a part of the screen is always a view. For that, I had to change the main view. It was no longer just a SurfaceView but a FrameLayout that holds the SurfaceView and an additional TextView for the status text.

I also had to add some sort of timer. That could be done by using a Handler that will be called after a defined time. The timer will be initialized by a call of the method postDelayed(). The call needs two parameters, a runnable method refreshStatusText() and a delay in milliseconds.

Java
public void onCreate(Bundle savedInstanceState) {
    ...
    // Create the main layout
    FrameLayout f = new FrameLayout(this);

    // Create the TextView for status text
    m_statusText = new TextView(this);
    m_statusText.setPadding(3, 3, 3, 3);

    // Create the surface view for the board
    m_view = new BoardView(this);

    // Add the two elements to the layout
    f.addView(m_view);
    f.addView(m_statusText);

    // Set the layout as view for the activity
    setContentView(f);

    // Add a handler for the refresh of the status text
    m_Handler = new Handler();
    m_Handler.removeCallbacks(refreshStatusText);
    m_Handler.postDelayed(refreshStatusText, 100);
}

The message handler shows the current standings in the TextView and always calls the postDelayed() method to initiate the next call to refresh the status text. The text that should be displayed is prepared by the method getStatusText() in the class BoardView.

Java
private Runnable refreshStatusText = new Runnable() {
    public void run() {
        m_statusText.setText(m_view.getStatusText());

        // Start the next refresh in a second
        m_Handler.postDelayed(this, 1000);
    }
};

Points of Interest

To work with the Android framework and Java was something new for me and I'm sure a lot of things in my program could be done more elegant, faster and easier. But I liked the work very much and it was very interesting to learn something new.

If you need more information about the game and how to play it, you find some more information in my article about the C# version of EnigmaPuzzle which can be found here.

In the following sections, you will find some information on additions I made for the EnigmaPuzzle for Android. For example, a menu or a preferences activity.

Options Menu

The options menu has to be defined in the main activity. But I implemented the handling of the menu in the BoardView class.

There are three methods in the activity to create and handle the options menu. You can find the code for them in the following fragment from EnigmaPuzzle.java. The method onPrepareOptionsMenu can be used to enable and disable menu items for the current state of the program.

Java
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    return m_view.onCreateOptionsMenu(menu);
}

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
    m_view.onPrepareOptionsMenu(menu);
    return super.onPrepareOptionsMenu(menu);
}

@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
    if (m_view.onMenuItemSelected(featureId, item)) {
        return true;
    }
    return super.onMenuItemSelected(featureId, item);
}

You can find the implementation of the real work for the options menu in the class BoardView. Some of the code will be shown in the following fragment:

Java
public boolean onCreateOptionsMenu(Menu menu) {
    MenuItem item;

    // Add the menu items
    item = menu.add(0, C_CmdNewGame, 0, R.string.menuNewGame);
    item.setIcon(R.drawable.newgame);

    item = menu.add(0, C_CmdStopGame, 0, R.string.menuStopGame);
    item.setIcon(R.drawable.resetgame);
    ...

    return true;
}

public void onPrepareOptionsMenu(Menu menu) {

    // Check which menu items have to be disabled
    if (m_gameState == eState.Active) {

        // The game is running so disable all but the stop item
        menu.findItem(C_CmdStopGame).setEnabled(true);
        menu.findItem(C_CmdNewGame).setEnabled(false);
    ...
}

/**
 * Handles the selected menu item
 */
public boolean onMenuItemSelected(int featureId, MenuItem item) {
    switch (item.getItemId()) {
    case C_CmdNewGame:
        newGame();
        return true;

    ....

    case C_CmdSettings: {
        Intent intent = new Intent();
        intent.setClass(m_ctx, GamePrefs.class);
        m_ctx.startActivity(intent);
        return true;
    }

    }
    return false;
}

In the method onCreateOptionsMenu, you can add menu items to the options menu. Therefore, you need a constant for the command and a string for the text. As you can see, it is very easy to add an icon to the menu item. The next method is onPrepareOptionsMenu where you can enable or disable menu items based on the state of the game. The last method is onMenuItemSelected where you have to handle a selected menu item. In the example, you can see how another activity (preferences activity) may be started.

When you open the menu, it looks like the following screenshot.

Image 4

Setting Preferences

I have added a second activity for the settings screen. I know (now) that there is a PreferenceActivity for that. But it was a nice exercise on how to work with a layout and how to access the view-elements from an activity.

In the previous section about the options menu, you can see how the activity will be started when selected in the menu (case C_CmdSettings).

The preferences screen looks like the following screenshot.

Image 5

If you add a new activity to a project, you must not forget (as I did) to add this to the AndroidManifest.xml file in your project.

The Help Activity

The help text will be displayed in its own activity (the third one in my project). It is a very simple example of a view that may be scrolled. That is necessary because the text is larger than a screen (on small devices). You can see that in the following screenshot.

Image 6

C# GraphicsPath.IsVisible()

The GraphicsObject in .NET offers a method IsVisible(). This method returns true if a point lies within the path. I did not find such a method in Android and I therefore implemented it in the class Block.

Java
public boolean isPointInBlock(PointF p) {
    RectF bounds = new RectF();

    // Get the bounds of the path and check the point against it
    GP.computeBounds(bounds, true);
    if (p.x >= bounds.left && p.x <= bounds.right && 
		p.y >= bounds.top && p.y <= bounds.bottom) {
        return true;
    }
    return false;
}

The class Path offers a method computeBounds() which I used to check if a point lies in the region of a Path object. The rest is a simple check for all directions if the point lies in the bounds of the path.

Multiple Languages

If you follow the recommendations and put all your strings to the resources, then it is very easy to add another locale. The strings for the default language are saved in the file strings.xml in the folder res/values. To add another language, you just have to add a folder with the short name of the locale in the name. For the string values in German, I had to add the folder res/values-de. There I put a copy of the file strings.xml and translated the texts - e voila the messages on the screen are in German if the device uses a German locale.

Even the texts in layout files can be placeholders from the file strings.xml. So you don't need multiple layout files for the different languages. You have to use the following syntax for texts in a layout file @string/slTitle where slTitle is the name you used in the strings.xml file for the text that should be displayed. In the strings.xml file, there has to be an entry like the following example:

XML
<string name="slTitle">"Enigma General Settings"</string>

iOS Version

Some time ago I've implemented a version of EnigmaPuzzle for Apple devices. The game can be found for free in the App Store (see link at the top of the article). I added the full source code of the iOS implementation to the article. It is an XCode project written in Objective-C using the GameKit from Apple. 

The code of the iOS version can't be compared to the other versions ot the game because it uses a GameKit to handle the presentation of the screens and the handling of user input. On the other hand the drawing of the board parts is allmost the same, just the handling of coordinates and sizes is different. 

History

  • Version 1.1 - 16.11.2014 - added iOS version
  • Version 1.0 - 16.12.2011

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


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

Comments and Discussions

 
GeneralJust Happy With This Pin
3DExterior7-Dec-14 23:46
3DExterior7-Dec-14 23:46 
GeneralRe: Just Happy With This Pin
Michael Hodel8-Dec-14 0:03
Michael Hodel8-Dec-14 0:03 
GeneralMy vote of 5 Pin
Agent__00720-Nov-14 17:03
professionalAgent__00720-Nov-14 17:03 
GeneralMy vote of 5 Pin
newton.saber17-Nov-14 9:26
newton.saber17-Nov-14 9:26 
GeneralMy vote of 5 Pin
dr.samuel.john12-Nov-14 19:30
dr.samuel.john12-Nov-14 19:30 
GeneralMy vote of 5 Pin
Member 1109084117-Sep-14 19:47
Member 1109084117-Sep-14 19:47 
GeneralMy vote of 5 Pin
altomaltes28-Oct-13 22:40
professionalaltomaltes28-Oct-13 22:40 
GeneralGreat Pin
Member 103203067-Oct-13 2:26
Member 103203067-Oct-13 2:26 
QuestionGood article Pin
Sumeet Kumar G9-Aug-13 22:27
Sumeet Kumar G9-Aug-13 22:27 
GeneralMy vote of 5 Pin
Pratik Butani26-Feb-13 20:12
professionalPratik Butani26-Feb-13 20:12 
GeneralMy vote of 5 Pin
WebMaster20-Jan-13 15:14
WebMaster20-Jan-13 15:14 
GeneralMy vote of 5 Pin
WebMaster5-Jan-13 5:07
WebMaster5-Jan-13 5:07 
GeneralMy vote of 5 Pin
csharpbd15-Nov-12 1:08
professionalcsharpbd15-Nov-12 1:08 
Questionenigma Pin
p-liks13-Aug-12 16:01
p-liks13-Aug-12 16:01 
QuestionSo Cool! Pin
Dave Kerr9-Jan-12 22:57
mentorDave Kerr9-Jan-12 22:57 
GeneralMy vote of 5 Pin
Marcelo Ricardo de Oliveira30-Dec-11 8:32
mvaMarcelo Ricardo de Oliveira30-Dec-11 8:32 
This is a great article, Michael! Thanks for sharing and keep it up
cheers,
marcelo
There's no free lunch. Let's wait for the dinner.

Take a look at Windows Phone Labyrinth here in The Code Project.

QuestionHigh 5 Pin
Herman Bakker27-Dec-11 22:14
Herman Bakker27-Dec-11 22:14 
AnswerRe: High 5 Pin
Herman Bakker27-Dec-11 22:30
Herman Bakker27-Dec-11 22:30 
GeneralRe: High 5 Pin
Michael Hodel27-Dec-11 22:32
Michael Hodel27-Dec-11 22:32 
AnswerRe: High 5 Pin
Michael Hodel27-Dec-11 22:32
Michael Hodel27-Dec-11 22:32 
QuestionCant wait to have a deeper look at this .. Pin
Garth J Lancaster22-Dec-11 11:10
professionalGarth J Lancaster22-Dec-11 11:10 
AnswerRe: Cant wait to have a deeper look at this .. Pin
Michael Hodel22-Dec-11 18:31
Michael Hodel22-Dec-11 18:31 
GeneralRe: Cant wait to have a deeper look at this .. Pin
Garth J Lancaster23-Dec-11 22:32
professionalGarth J Lancaster23-Dec-11 22:32 
AnswerRe: Cant wait to have a deeper look at this .. Pin
Michael Hodel15-Nov-14 22:05
Michael Hodel15-Nov-14 22:05 
GeneralRe: Cant wait to have a deeper look at this .. Pin
Garth J Lancaster16-Nov-14 13:44
professionalGarth J Lancaster16-Nov-14 13:44 

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.