diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c9716ab..aecd884 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -7,19 +7,24 @@
android:targetSdkVersion="14"/>
-
+ android:icon="@drawable/icon"
+ android:theme="@android:style/Theme">
+
-
+
+ android:value="org.dyndns.vahagn.sokoban.MainMenu" />
diff --git a/compiler/compiler.cpp b/compiler/compiler.cpp
index ad4b882..f857519 100644
--- a/compiler/compiler.cpp
+++ b/compiler/compiler.cpp
@@ -21,8 +21,8 @@ protected:
empty = 0,
wall,
box,
- hole,
- box_in_hole,
+ goal,
+ box_in_goal,
worker
};
@@ -69,10 +69,10 @@ public:
*it++ = box;
break;
case '.':
- *it++ = hole;
+ *it++ = goal;
break;
case '*':
- *it++ = box_in_hole;
+ *it++ = box_in_goal;
break;
case '@':
*it++ = worker;
diff --git a/res/layout/menu.xml b/res/layout/menu.xml
index 59fc39a..842b72d 100644
--- a/res/layout/menu.xml
+++ b/res/layout/menu.xml
@@ -2,10 +2,18 @@
+ android:layout_height="fill_parent">
+
+
diff --git a/src/org/dyndns/vahagn/sokoban/App.java b/src/org/dyndns/vahagn/sokoban/App.java
index 83ab2ef..8963272 100644
--- a/src/org/dyndns/vahagn/sokoban/App.java
+++ b/src/org/dyndns/vahagn/sokoban/App.java
@@ -1,12 +1,24 @@
package org.dyndns.vahagn.sokoban;
import android.app.Application;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.util.Log;
-public class App extends Application {
+public class App extends Application
+{
+ public static final String TAG = "Sokoban";
+ public static final String PREFS = "org.dyndns.vahagn.sokoban.prefs";
+ static final int MIN_LEVEL = 1;
private static App mApp = null;
protected PuzzleContainer pc;
- public int level;
+ protected int current_level;
+ protected int achieved_level;
+ protected int max_level;
+ protected SharedPreferences prefs;
+ protected SharedPreferences.Editor prefsEdit;
/*
*
@@ -17,21 +29,82 @@ public class App extends Application {
super.onCreate();
mApp = this;
pc = new PuzzleContainer();
- level = 0;
+ prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
+ prefsEdit = prefs.edit();
+ max_level = pc.getCount();
+ current_level = prefs.getInt("current_level", MIN_LEVEL);
+ achieved_level = prefs.getInt("achieved_level", MIN_LEVEL);
}
-
+ //
+ // This is a singleton object.
+ //
public static App theApp()
{
return mApp;
}
-
- public PuzzleContainer getPuzzleContainer()
+ //
+ // Puzzles.
+ //
+ public Puzzle getPuzzle( int level )
{
- return pc;
+ return pc.getPuzzle( level-1 );
}
-
public Puzzle getCurrentPuzzle()
{
- return getPuzzleContainer().getPuzzle( level );
+ return getPuzzle( getCurrentLevel() );
}
+ //
+ // Provide amount of puzzles.
+ //
+ public final int getPuzzleCount()
+ {
+ return max_level;
+ }
+ //
+ // Provide highest solved puzzle.
+ //
+ public final int getAchivedLevel()
+ {
+ return achieved_level;
+ }
+ //
+ // Provide last played puzzle.
+ //
+ public final int getCurrentLevel()
+ {
+ return current_level;
+ }
+ //
+ // Set the current puzzle level. It deons't alter
+ //
+ public void setCurrentLevel( int l )
+ {
+ if ( current_level != l )
+ {
+ current_level = l;
+ prefsEdit.putInt("current_level", current_level);
+ prefsEdit.apply();
+ if ( !prefs.edit().commit() )
+ Log.d(TAG, "prefs.edit().commit() failed.");
+ }
+ }
+ //
+ // Advances current level. It also reviews achived level if the
+ // new level is bigger than achieved level.
+ //
+ public void advanceCurrentLevel()
+ {
+ int new_level = current_level+1;
+ if ( new_level == max_level )
+ new_level = MIN_LEVEL;
+ if ( new_level > achieved_level )
+ {
+ achieved_level = new_level;
+ prefsEdit.putInt("achieved_level", achieved_level);
+ // Don't call apply() here since the call below
+ // to setCurrentLevel() will do that.
+ }
+ setCurrentLevel(new_level);
+ }
+
}
diff --git a/src/org/dyndns/vahagn/sokoban/MainMenu.java b/src/org/dyndns/vahagn/sokoban/MainMenu.java
index b2b3a0d..e6039d7 100644
--- a/src/org/dyndns/vahagn/sokoban/MainMenu.java
+++ b/src/org/dyndns/vahagn/sokoban/MainMenu.java
@@ -1,33 +1,135 @@
package org.dyndns.vahagn.sokoban;
+import org.dyndns.vahagn.sokoban.play.PlayActivity;
import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
import android.view.View;
import android.os.Bundle;
import android.util.Log;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
import static org.dyndns.vahagn.sokoban.App.theApp;
-public class SokobanMenu extends Activity
+public class MainMenu extends Activity
{
protected final String TAG = "SokobanMenu";
protected final String LEVEL= "org.dyndns.vahagn.sokoban.LEVEL";
+ protected GridView puzzle_grid;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
- Log.d(TAG, "onCreate: " + savedInstanceState );
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.menu);
-
- try {
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
+
+ puzzle_grid = (GridView)findViewById(R.id.puzzle_grid);
+ puzzle_grid.setAdapter( new PuzzlesAdapter(this) );
+ //puzzle_grid.addView(puzzle_grid);
+ puzzle_grid.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView> parent, View v, int position, long id)
+ {
+ onPuzzleClicked(v,(int)id);
+ }
+ });
}
+ public class PuzzlesAdapter extends BaseAdapter
+ {
+ private Context mContext;
+ private int icon_size;
+ private int text_x;
+ private int text_y;
+ private Bitmap lock_icon;
+ private Bitmap unlock_icon;
+ private Paint paint;
+
+ public PuzzlesAdapter(Context c)
+ {
+ mContext = c;
+ icon_size = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 10, getResources().getDisplayMetrics() );
+ int text_size = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 4, getResources().getDisplayMetrics() );
+ text_x = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 9, getResources().getDisplayMetrics() );
+ text_y = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 9, getResources().getDisplayMetrics() );
+
+ Bitmap lock_icon_tmp = BitmapFactory.decodeResource( getResources(), R.drawable.lock );
+ lock_icon = Bitmap.createBitmap(icon_size,icon_size,Bitmap.Config.ARGB_8888);
+ Canvas lock_canvas = new Canvas(lock_icon);
+ lock_canvas.drawBitmap(lock_icon_tmp,
+ new Rect(0,0,lock_icon_tmp.getWidth()-1,lock_icon_tmp.getHeight()-1),
+ new Rect(0,0,icon_size, icon_size),
+ null);
+
+ Bitmap unlock_icon_tmp = BitmapFactory.decodeResource( getResources(), R.drawable.unlock );
+ unlock_icon = Bitmap.createBitmap(icon_size,icon_size,Bitmap.Config.ARGB_8888);
+ Canvas unlock_canvas = new Canvas(unlock_icon);
+ unlock_canvas.drawBitmap(unlock_icon_tmp,
+ new Rect(0,0,unlock_icon_tmp.getWidth()-1,unlock_icon_tmp.getHeight()-1),
+ new Rect(0,0,icon_size, icon_size),
+ null);
+
+ paint = new Paint();
+ paint.setColor( Color.WHITE );
+ paint.setTextSize( text_size );
+ paint.setTextAlign(Paint.Align.RIGHT);
+ }
+
+ public int getCount()
+ {
+ return theApp().getPuzzleCount();
+ }
+
+ public Object getItem(int position)
+ {
+ return null;
+ }
+
+ public long getItemId(int position)
+ {
+ return position+1;
+ }
+
+ // create a new ImageView for each item referenced by the Adapter
+ public View getView(int position, View convertView, ViewGroup parent)
+ {
+ ImageView imageView;
+ if (convertView == null)
+ { // if it's not recycled, initialize some attributes
+ imageView = new ImageView(mContext);
+ imageView.setLayoutParams(new GridView.LayoutParams(icon_size, icon_size));
+ imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ // imageView.setPadding(8, 8, 8, 8);
+ }
+ else
+ imageView = (ImageView) convertView;
+
+ int id = (int)getItemId(position);
+ Bitmap icon = ( id <= theApp().getAchivedLevel() )
+ ? unlock_icon.copy(Bitmap.Config.ARGB_8888,true)
+ : lock_icon.copy(Bitmap.Config.ARGB_8888,true);
+ Canvas canvas = new Canvas(icon);
+ canvas.drawText( Integer.toString(id), text_x, text_y, paint);
+ imageView.setImageBitmap(icon);
+ return imageView;
+ }
+ }
+
@Override
public void onStart()
{
@@ -83,11 +185,20 @@ public class SokobanMenu extends Activity
// setContentView(R.layout-l.menu);
}
- public void onStartCurrentPuzzle( View v)
+ public void onStartCurrentPuzzle(View v)
{
- Intent intent = new Intent(this, PlayActivity.class);
-
+ Intent intent = new Intent(this, PlayActivity.class);
startActivity( intent );
- Log.d(TAG, "onStartCurrentPuzzle: " );
+ }
+
+ public void onPuzzleClicked(View v, int level )
+ {
+ if ( level <= theApp().getAchivedLevel() )
+ {
+ theApp().setCurrentLevel(level);
+
+ Intent intent = new Intent(this, PlayActivity.class);
+ startActivity( intent );
+ }
}
}
diff --git a/src/org/dyndns/vahagn/sokoban/Puzzle.java b/src/org/dyndns/vahagn/sokoban/Puzzle.java
index c079397..e41252d 100644
--- a/src/org/dyndns/vahagn/sokoban/Puzzle.java
+++ b/src/org/dyndns/vahagn/sokoban/Puzzle.java
@@ -2,73 +2,51 @@ package org.dyndns.vahagn.sokoban;
//import java.lang.Exception;
import java.io.*;
+import static java.lang.Math.*;
import android.util.Log;
+import android.graphics.Point;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import static org.dyndns.vahagn.sokoban.App.TAG;
public class Puzzle
{
- public enum symbole
- {
- FLOOR,
- WALL,
- BOX,
- HOLE,
- BOX_IN_HOLE,
- WORKER;
-
- public static int MAX_COUNT()
- {
- return 6;
- }
- };
+ public final static int FLOOR = 0x0;
+ public final static int GOAL = 0x1;
+ public final static int FLOOR_MASK = ~(GOAL | FLOOR);
+ public final static int WALL = 0x2;
+ public final static int BOX = 0x4;
+ public final static int BINGO = BOX | GOAL;
+ public final static int WORKER = 0x8;
+ public final static int WORKER_NORTH = 0x00 | WORKER;
+ public final static int WORKER_SOUTH = 0x10 | WORKER;
+ public final static int WORKER_WEST = 0x20 | WORKER;
+ public final static int WORKER_EAST = 0x30 | WORKER;
+ public final static int WORKER_MASK = WORKER_EAST | WORKER_WEST | WORKER_SOUTH | WORKER_NORTH;
+ public final static int SELECTED = 0x40;
+ public final static int MAX_COUNT = 0x80-1;
- protected static final String TAG = "Puzzle";
protected int columns;
protected int rows;
- protected symbole [] board;
+ protected int box_count;
+
+ protected int [] board;
+ protected int worker_x;
+ protected int worker_y;
+ protected int selected_x;
+ protected int selected_y;
+ protected int bingos;
public Puzzle( InputStream is )
{
load( is );
}
- private void load( InputStream is )
- {try{
- columns = is.read();
- rows = is.read();
- byte [] b = new byte [columns*rows];
- board = new symbole[columns*rows];
- is.read( b, 0, columns*rows);
- for( int i = 0; i < b.length; ++i )
- {
- switch( b[i] )
- {
- case 0:
- board[i] = symbole.FLOOR;
- break;
- case 1:
- board[i] = symbole.WALL;
- break;
- case 2:
- board[i] = symbole.BOX;
- break;
- case 3:
- board[i] = symbole.HOLE;
- break;
- case 4:
- board[i] = symbole.BOX_IN_HOLE;
- break;
- case 5:
- board[i] = symbole.WORKER;
- break;
- default:
- }
- }
- }
- catch ( Exception e )
- {
- Log.d( TAG, "load()", e);
- }}
-
+ //////////////////////////////////////////////////////////////////////////
+ //
+ // Low level helper functions.
+ //
public final int getColumnCount()
{
@@ -80,15 +58,347 @@ public class Puzzle
}
public static int getSymCount()
{
- return symbole.MAX_COUNT();
+ return MAX_COUNT;
}
- /*
- * r- row
- * c- column
- */
- public final symbole getSym( int r, int c )
+ public final int getIndex( int x, int y )
{
- return board[r*columns+c];
+ return y*columns+x;
+ }
+ public final int getX( int idx )
+ {
+ return idx % columns;
+ }
+ public final int getY( int idx )
+ {
+ return idx / columns;
+ }
+
+ public final int getSym( int x, int y )
+ {
+ return board[getIndex(x,y)];
+ }
+ public void setSym( int x, int y, int v )
+ {
+ board[getIndex(x,y)]=v;
+ }
+
+ public static boolean isEmpty( int v )
+ {
+ return (v & FLOOR_MASK) == 0;
+ }
+ public static boolean hasBox( int v )
+ {
+ return (v & BOX) != 0;
+ }
+ public static boolean hasWorker( int v )
+ {
+ return (v & WORKER) != 0;
+ }
+
+ public final int getWorkerX()
+ {
+ return worker_x;
+ }
+
+ public final int getWorkerY()
+ {
+ return worker_y;
+ }
+
+ public final boolean isSelected()
+ {
+ return selected_x != -1;
+ }
+
+ public final boolean isSelected( int x, int y )
+ {
+ return selected_x == x && selected_y == y;
+ }
+
+ public final int getSelectedX()
+ {
+ return selected_x;
+ }
+
+ public final int getSelectedY()
+ {
+ return selected_y;
+ }
+
+ public final boolean isValid( int x, int y )
+ {
+ return ( 0 <= x && 0 <= y
+ && x < getColumnCount()
+ && y < getRowCount() );
+ }
+
+ public final boolean isOneStep( int x, int y )
+ {
+ return isValid(x,y)
+ && (abs(worker_x-x )+abs(worker_y-y) == 1);
+ }
+
+ public final boolean isDone()
+ {
+ return bingos == box_count;
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ //
+ // Loading.
+ //
+ private void load( InputStream is )
+ {try{
+ columns = is.read();
+ rows = is.read();
+ box_count = 0;
+ bingos = 0;
+ selected_x = -1;
+ selected_y = -1;
+ board = new int[columns*rows];
+ byte [] b = new byte [columns*rows];
+ is.read( b, 0, columns*rows);
+ for( int i = 0; i < b.length; ++i )
+ {
+ switch( b[i] )
+ {
+ case 0:
+ board[i] = FLOOR;
+ break;
+ case 1:
+ board[i] = WALL;
+ break;
+ case 2:
+ board[i] = BOX;
+ ++box_count;
+ break;
+ case 3:
+ board[i] = GOAL;
+ break;
+ case 4:
+ board[i] = BINGO;
+ ++box_count;
+ ++bingos;
+ break;
+ case 5:
+ board[i] = WORKER;
+ worker_x = getX(i);
+ worker_y = getY(i);
+ break;
+ default:
+ }
+ }
+ }
+ catch ( Exception e )
+ {
+ Log.d( TAG, "load()", e);
+ }}
+
+ //////////////////////////////////////////////////////////////////////////
+ //
+ // Move worker.
+ //
+
+ public boolean walk( int x, int y)
+ {
+ //
+ // Check that this is one step away.
+ //
+ if ( !isOneStep(x,y) )
+ return false;
+ //
+ // Check that this is empty space.
+ //
+ int v = getSym(x,y);
+ if ( !isEmpty(v) )
+ return false;
+ //
+ // If something is selected then unselect first.
+ //
+ if ( isSelected() )
+ unselect();
+ //
+ // Find direction to move.
+ //
+ int worker;
+ if ( worker_x < x )
+ worker = WORKER_WEST;
+ else if ( worker_x > x )
+ worker = WORKER_EAST;
+ else if ( worker_y > y )
+ worker = WORKER_SOUTH;
+ else
+ worker = WORKER_NORTH;
+ //
+ // Move worker marker from current position to asked position.
+ //
+ setSym(worker_x,worker_y,getSym(worker_x,worker_y)&~WORKER_MASK);
+ worker_x =x;
+ worker_y =y;
+ setSym(worker_x,worker_y,getSym(worker_x,worker_y)|worker);
+ return true;
+ }
+
+ public boolean select( int x, int y)
+ {
+ //
+ // Check that this is one step away.
+ //
+ if ( !isOneStep(x,y) )
+ return false;
+ //
+ // Check that this is empty space.
+ //
+ int v = getSym(x,y);
+ if ( !hasBox(v) )
+ return false;
+ //
+ // If something is selected then unselect first.
+ //
+ if ( isSelected() )
+ unselect();
+ //
+ // Find direction to move.
+ //
+ int worker;
+ if ( worker_x < x )
+ worker = WORKER_WEST;
+ else if ( worker_x > x )
+ worker = WORKER_EAST;
+ else if ( worker_y > y )
+ worker = WORKER_SOUTH;
+ else
+ worker = WORKER_NORTH;
+ //
+ // Move worker marker from current position to asked position.
+ //
+ selected_x =x;
+ selected_y =y;
+ setSym(worker_x,worker_y,getSym(worker_x,worker_y)&~WORKER_MASK|worker|SELECTED);
+ setSym(selected_x,selected_y,getSym(selected_x,selected_y)|SELECTED);
+ return true;
+ }
+
+ public boolean unselect()
+ {
+ //
+ // If not selected then do nothing.
+ //
+ if ( !isSelected() )
+ return false;
+ //
+ // Move worker marker from current position to asked position.
+ //
+ setSym(worker_x,worker_y,getSym(worker_x,worker_y)&~SELECTED);
+ setSym(selected_x,selected_y,getSym(selected_x,selected_y)&~SELECTED);
+ selected_x =-1;
+ selected_y =-1;
+ return true;
+ }
+
+ public boolean push(int x, int y)
+ {
+ //
+ // If not selected then do nothing.
+ //
+ if ( !isSelected() )
+ return false;
+ //
+ // Check that we go to the selected direction.
+ //
+ if ( selected_x != x && selected_y != y )
+ return false;
+ //
+ // Check that this is a box that we move.
+ //
+ int v = getSym(x,y);
+ if ( !hasBox(v) )
+ return false;
+ //
+ // Check that the next space to the box is empty.
+ //w - 2*(w-x) = 2x -w
+ int next_x = 2*x - worker_x;
+ int next_y = 2*y - worker_y;
+ int next_v = getSym(next_x,next_y);
+ if ( !isEmpty(next_v) )
+ return false;
+ //
+ // Ok, looks we can move the box. Do it actually.
+ //
+ unselect();
+ setSym(x,y,getSym(x,y)&~BOX);
+ setSym(next_x,next_y,getSym(next_x,next_y)|BOX);
+ walk(x,y);
+ select(next_x,next_y);
+ //
+ // Keep track of box count in place.
+ //
+ if ( (v & GOAL) != 0 )
+ --bingos;
+ if ( (next_v & GOAL) != 0 )
+ ++bingos;
+ return true;
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ //
+ // Undo/Redo.
+ //
+
+ protected class State
+ {
+ public int [] board;
+ public int worker_x;
+ public int worker_y;
+ public int selected_x;
+ public int selected_y;
+ public int bingos;
+
+ public State(Puzzle puzzle)
+ {
+ board = puzzle.board.clone();
+ worker_x = puzzle.worker_x;
+ worker_y = puzzle.worker_y;
+ selected_x = puzzle.selected_x;
+ selected_y = puzzle.selected_y;
+ bingos = puzzle.bingos;
+ }
+
+ public void restore(Puzzle puzzle)
+ {
+ puzzle.board = board;
+ puzzle.worker_x = worker_x;
+ puzzle.worker_y = worker_y;
+ puzzle.selected_x = selected_x;
+ puzzle.selected_y = selected_y;
+ puzzle.bingos = bingos;
+ }
+ }
+ protected LinkedList undoStack = new LinkedList();
+
+ //
+ // Save this state to be able to restore later.
+ //
+ public void save()
+ {
+ State s = new State(this);
+ undoStack.addFirst(s);
+ }
+ //
+ // Restore state.
+ //
+ public void restore()
+ {
+ State s = undoStack.removeFirst();
+ if ( s != null )
+ s.restore(this);
+ }
+ //
+ // Check if there are items in the undo stack.
+ //
+ public final boolean isUndoable()
+ {
+ return undoStack.size() != 0;
}
}
diff --git a/src/org/dyndns/vahagn/sokoban/PuzzleContainer.java b/src/org/dyndns/vahagn/sokoban/PuzzleContainer.java
index ce5d53f..a9cb00e 100644
--- a/src/org/dyndns/vahagn/sokoban/PuzzleContainer.java
+++ b/src/org/dyndns/vahagn/sokoban/PuzzleContainer.java
@@ -7,8 +7,6 @@ import android.util.Log;
public class PuzzleContainer
{
- protected final String TAG = "PuzzleContainer";
-
protected int count;
/*
@@ -41,11 +39,13 @@ public class PuzzleContainer
}
catch ( java.lang.Exception e )
{
- Log.d(TAG, "Exception: " + e.getMessage() );
+ //Log.d(TAG, "Exception: " + e.getMessage() );
e.printStackTrace();
return null;
}}
-
+ //
+ // Retrive amount of puzzles.
+ //
public int getCount()
{try{
if ( count == 0 )
@@ -57,11 +57,13 @@ public class PuzzleContainer
}
catch ( java.lang.Exception e )
{
- Log.d(TAG, "Exception: " + e.getMessage() );
+ //Log.d(TAG, "Exception: " + e.getMessage() );
e.printStackTrace();
return 0;
}}
-
+ //
+ // Helper function to read little endian 32bit integer.
+ //
protected int read_i32( InputStream is ) throws IOException
{
int i = is.read();