Working version. Yet there are bugs and TODOs.

This commit is contained in:
2013-05-14 09:12:56 +04:00
parent c300d95089
commit 6ed963b2f3
22 changed files with 1409 additions and 313 deletions

BIN
libs/android-support-v4.jar Normal file

Binary file not shown.

BIN
res/drawable-hdpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
res/drawable-ldpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
res/drawable-mdpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
res/drawable-xhdpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
res/drawable/bingo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
res/drawable/box.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
res/drawable/floor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
res/drawable/goal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
res/drawable/lock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
res/drawable/unlock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
res/drawable/wall.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
res/drawable/worker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -1,82 +0,0 @@
package org.dyndns.vahagn.sokoban;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
//import android.widget.TextView;
//import org.dyndns.vahagn.sokoban.R;
import static org.dyndns.vahagn.sokoban.App.theApp;
public class PlayActivity extends Activity
implements View.OnClickListener
{
protected final String TAG = "PlayActivity";
protected Puzzle puzzle = null;
public PlayView board;
protected int level;
@Override
public void onCreate(Bundle savedInstanceState)
{
Log.d(TAG, "onCreate: " + savedInstanceState );
super.onCreate(savedInstanceState);
board = new PlayView( this );
setContentView( board );
board.setOnClickListener(this);
if (savedInstanceState == null)
{
level = 0;
setPuzzle( level );
// We were just launched -- set up a new game
// mSnakeView.setMode(SnakeView.READY);
}
else
{
// We are being restored
// Bundle map = savedInstanceState.getBundle(ICICLE_KEY);
// if (map != null) {
// mSnakeView.restoreState(map);
// } else {
// mSnakeView.setMode(SnakeView.PAUSE);
// }
}
//
//
//
// super.onCreate(savedInstanceState);
// setContentView(R.layout.menu);
//
// try {
// theApp().getPuzzleContainer().getPuzzle( 0 );
// } catch (Exception e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
}
@Override
public void onClick( View v )
{
Log.d(TAG, "onClick: " );
++level;
if ( level >= theApp().getPuzzleContainer().getCount() )
level = 0;
setPuzzle( level );
board.invalidate();
}
private void setPuzzle( int level )
{
String title = "Sokoban: level " + new Integer(level+1).toString();
setTitle(title);
puzzle = theApp().getPuzzleContainer().getPuzzle( level );
board.setPuzzle(puzzle);
}
}

View File

@@ -1,231 +0,0 @@
package org.dyndns.vahagn.sokoban;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.graphics.*;
import android.view.*;
import static org.dyndns.vahagn.sokoban.Puzzle.symbole.*;
/**
*/
public class PlayView extends View
{
private final static String TAG = "PlayView";
private Puzzle puzzle;
private Matrix col2scr = new Matrix();
private Matrix scr2col = new Matrix();
private Bitmap[] tile;
private int tile_x_size;
private int tile_y_size;
private int offset_x;
private int offset_y;
private final Paint mPaint = new Paint();
public PlayView(Context context)
{
super(context);
setFocusable(true);
//
// Resources r = this.getContext().getResources();
// TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TileView);
//
// mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);
//
// a.recycle();
}
public void setPuzzle( Puzzle p )
{
puzzle = p;
initTiles();
}
/**
* Set up tiles and matrices based on puzzle and view sizes.
*/
private void initTiles()
{
//
// Get the sizes of view. If it is zero then simplly do nothing.
// Otherwise, make sure that the width is bigger the height.
//
int w = getWidth();
int h = getHeight();
if ( w==0 || h==0 )
return;
else if ( w < h )
{
col2scr.reset();
col2scr.setRotate(90, w/2, w/2);
col2scr.invert(scr2col);
int t = w;
w = h;
h = t;
}
else
{
col2scr.reset();
}
//
// Calculate the tile sizes.
// NOTE: since puzzle width is always bigger the height then we
// rotate the puzzle.
//
tile_x_size = w / puzzle.getColumnCount();
tile_y_size = h / puzzle.getRowCount();
if ( tile_x_size < tile_y_size )
tile_y_size = tile_x_size;
else
tile_x_size = tile_y_size;
//
// Calculate the offset of puzzle.
//
offset_x = (w-tile_x_size*puzzle.getColumnCount())/2;
offset_y = (h-tile_y_size*puzzle.getRowCount())/2;
//
// Set up inverse matrix to get col,row from screen pixel.
//
col2scr.invert(scr2col);
scr2col.postTranslate(-offset_x, -offset_y);
scr2col.postScale((float)1./tile_x_size, (float)1./tile_y_size);
//
// Create tile bitmaps for given width and height.
//
tile = new Bitmap[Puzzle.getSymCount()];
tile[FLOOR.ordinal()] = createFloorTile();
tile[WALL.ordinal()] = createWallTile();
tile[BOX.ordinal()] = createBoxTile();
tile[HOLE.ordinal()] = createHoleTile();
tile[BOX_IN_HOLE.ordinal()] = createBingoTile();
tile[WORKER.ordinal()] = createWorkerTile();
}
/**
* Apply screen to board mapping and if the points are in the
* column,row range then return it. Otherwise return null.
*/
public Point getTile( float scr_x, float scr_y )
{
float [] scr_p = { scr_x, scr_y };
scr2col.mapPoints( scr_p );
if ( 0 <= scr_p[0] && scr_p[0] < puzzle.getColumnCount()
&& 0 <= scr_p[1] && scr_p[1] < puzzle.getRowCount())
return new Point( (int)scr_p[0], (int)scr_p[1] );
else
return null;
}
private Bitmap createFloorTile()
{
Bitmap bitmap = Bitmap.createBitmap(tile_x_size, tile_y_size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawARGB(0xff,0x40,0x40,0x40);
return bitmap;
}
private Bitmap createWallTile()
{
Bitmap bitmap = Bitmap.createBitmap(tile_x_size, tile_y_size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawARGB(0xff,0xF0,0x40,0x40);
return bitmap;
}
private Bitmap createBoxTile()
{
Bitmap bitmap = Bitmap.createBitmap(tile_x_size, tile_y_size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawARGB(0xff,0x40,0xF0,0x40);
return bitmap;
}
private Bitmap createHoleTile()
{
Bitmap bitmap = Bitmap.createBitmap(tile_x_size, tile_y_size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawARGB(0xff,0x40,0x40,0xF0);
return bitmap;
}
private Bitmap createBingoTile()
{
Bitmap bitmap = Bitmap.createBitmap(tile_x_size, tile_y_size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawARGB(0xff,0x40,0xF0,0xF0);
return bitmap;
}
private Bitmap createWorkerTile()
{
Bitmap bitmap = createFloorTile();
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setARGB(0xff,0xF0,0x40,0xF0);
canvas.drawOval(new RectF(1,1,tile_x_size-2,tile_y_size-2), paint);
Paint p = new Paint();
p.setColor( Color.YELLOW );
canvas.drawLine(tile_x_size/2, tile_y_size/2, tile_x_size/2, 0, p );
return bitmap;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
initTiles();
}
@Override
public boolean onTouchEvent (MotionEvent event)
{
int a = event.getAction();
int ps = event.getPointerCount();
int hs = event.getHistorySize();
for ( int p = 0; p < ps; ++p )
{
String msg = String.format("Action %d Pointer %d History %d", a, event.getPointerId(p), hs);
// for ( int h = 0; h < hs; ++h )
// msg += String.format("(%f,%f)", event.getHistoricalX(p,h), event.getHistoricalY(p,h));
msg += String.format("(%f,%f)", event.getX(p), event.getY(p));
Log.d("Sokoban", msg);
}
Point p = getTile(event.getX(),event.getY());
if ( p == null )
{
Log.d(TAG, "onTouchEvent: outside.");
return super.onTouchEvent(event);
}
// Log.d(TAG, "onTouchEvent: " + p.x + " " + p.y);
return true; //super.onTouchEvent(event);
}
@Override
public void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if ( puzzle != null )
{
int cols = puzzle.getColumnCount();
int rows = puzzle.getRowCount();
canvas.concat(col2scr);
for (int i = 0; i < rows; ++i)
{
for (int j = 0; j < cols; ++j)
{
canvas.drawBitmap(tile[puzzle.getSym(i,j).ordinal()],
offset_x + j * tile_x_size,
offset_y + i * tile_y_size,
mPaint);
}
}
}
}
}

View File

@@ -0,0 +1,209 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.dyndns.vahagn.sokoban.play;
import org.dyndns.vahagn.sokoban.play.PuzzleView;
import android.support.v4.view.ViewCompat;
import java.util.LinkedList;
/**
*
* @author vahagnk
*/
public class Animator implements Runnable
{
protected PuzzleView view;
protected LinkedList<Action> actions = new LinkedList<Action>();
protected boolean stop;
protected int step;
protected Action currentAction;
protected AnimationLister theLister;
public interface AnimationLister
{
public void onAnimationEnd();
}
public Animator( PuzzleView v)
{
view = v;
}
public void setAnimationLister( AnimationLister lister )
{
theLister = lister;
}
public void queue( Action a )
{
actions.addLast(a);
}
public void play()
{
//
// If no actions exist then nothing to do.
//
if ( actions.size() == 0 )
return;
//
// Get the first action an play it.
//
stop = false;
step = 0;
currentAction = actions.removeFirst();
ViewCompat.postOnAnimation(view, this);
}
public void stop()
{
stop = true;
view.removeCallbacks(this);
if ( theLister != null )
theLister.onAnimationEnd();
}
public void clear()
{
actions.clear();
currentAction = null;
}
public void run()
{
if ( currentAction == null )
return;
else if ( currentAction.steps > step )
{
currentAction.intermediate(this,step);
step++;
ViewCompat.postOnAnimation(view, this);
}
else
{
currentAction.last(this);
step = 0;
currentAction = null;
if ( !stop && actions.size() > 0 )
{
currentAction = actions.removeFirst();
step = 0;
ViewCompat.postOnAnimation(view, this);
}
else
{
if ( theLister != null )
theLister.onAnimationEnd();
}
}
}
public static abstract class Action
{
public int steps;
public Action()
{
steps = 0;
}
public abstract void intermediate( Animator ap, int step );
public abstract void last( Animator ap );
}
protected static abstract class XYAction extends Action
{
int x;
int y;
public XYAction( int _x, int _y )
{
x = _x;
y = _y;
}
}
public static class Move extends XYAction
{
public Move( int x, int y )
{
super(x,y);
}
public void intermediate( Animator ap, int step )
{}
public void last( Animator ap )
{
ap.view.getPuzzle().walk(x,y);
ap.view.invalidate();
}
}
public static class Push extends XYAction
{
public Push( int x, int y )
{
super(x,y);
}
public void intermediate( Animator ap, int step )
{
}
public void last( Animator ap )
{
ap.view.getPuzzle().push(x,y);
ap.view.invalidate();
}
}
public static class Select extends XYAction
{
public Select(int x, int y )
{
super(x,y);
}
public void intermediate( Animator ap, int step )
{
}
public void last( Animator ap )
{
ap.view.getPuzzle().select(x,y);
ap.view.invalidate();
}
}
public static class Unselect extends Action
{
public Unselect()
{}
public void intermediate( Animator ap, int step )
{}
public void last( Animator ap )
{
ap.view.getPuzzle().unselect();
ap.view.invalidate();
}
}
public static class NoMove extends XYAction
{
public NoMove( int x, int y )
{
super(x,y);
}
public void intermediate( Animator ap, int step )
{}
public void last( Animator ap )
{}
}
}

View File

@@ -0,0 +1,190 @@
package org.dyndns.vahagn.sokoban.play;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import static org.dyndns.vahagn.sokoban.App.TAG;
import static org.dyndns.vahagn.sokoban.App.theApp;
import org.dyndns.vahagn.sokoban.Puzzle;
import org.dyndns.vahagn.sokoban.MainMenu;
public class PlayActivity extends Activity
implements PuzzleControl.PuzzleControlLister
{
protected final static int RESET_ITEM = 1;
protected final static int UNDO_ITEM = 2;
public Puzzle puzzle = null;
public PuzzleControl view;
public View title;
@Override
public void onCreate(Bundle savedInstanceState)
{
Log.d(TAG, "onCreate: " + savedInstanceState );
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
// getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
// WindowManager.LayoutParams.FLAG_FULLSCREEN);
//requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
//requestWindowFeature(Window.FEATURE_OPTIONS_PANEL);
//getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, RESULT_OK);
//getWindow().getWindowStyle().
//getWindow().
//
// Create and set up the view.
//
view = new PuzzleControl( this );
setContentView( view );
view.setPuzzleControlLister( this );
registerForContextMenu(view);
//
// Load the puzzle.
//
loadCurrentPuzzle();
if (savedInstanceState == null)
{
// We were just launched -- set up a new game
// mSnakeView.setMode(SnakeView.READY);
}
else
{
// We are being restored
// Bundle map = savedInstanceState.getBundle(ICICLE_KEY);
// if (map != null) {
// mSnakeView.restoreState(map);
// } else {
// mSnakeView.setMode(SnakeView.PAUSE);
// }
}
//
//
//
// super.onCreate(savedInstanceState);
// setContentView(R.layout.menu);
//
// try {
// theApp().getPuzzleContainer().getPuzzle( 0 );
// } catch (Exception e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
}
public void onSolved()
{
//
// Advance current level and achieved level.
//
theApp().advanceCurrentLevel();
//
// Bring a dialog for user to choose to continue or stop.
//
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("");
builder.setMessage( "Congratulations! You Won!" );
builder.setNegativeButton("Menu",
new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface d, int w)
{
Intent intent = new Intent(PlayActivity.this, MainMenu.class);
startActivity( intent );
}
});
builder.setPositiveButton("Next",
new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface d, int w)
{
loadCurrentPuzzle();
view.invalidate();
}
});
AlertDialog dlg = builder.create();
dlg.show();
}
public void onLongPress()
{
openContextMenu(view);
}
public boolean onReset()
{
loadCurrentPuzzle();
view.invalidate();
return true;
}
public boolean onUndo()
{
if ( puzzle.isUndoable() )
{
puzzle.restore();
view.invalidate();
return true;
}
else
return false;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo)
{
super.onCreateContextMenu(menu, v, menuInfo);
//
// Undo item.
//
if ( puzzle.isUndoable() )
{
menu.add("Undo").setOnMenuItemClickListener(
new MenuItem.OnMenuItemClickListener(){
public boolean onMenuItemClick(MenuItem mi){
return onUndo();
}
});
}
//
// Reset item.
//
menu.add("Reset").setOnMenuItemClickListener(
new MenuItem.OnMenuItemClickListener(){
public boolean onMenuItemClick(MenuItem mi){
return onReset();
}
});
}
private void loadCurrentPuzzle()
{
puzzle = theApp().getCurrentPuzzle();
view.setPuzzle(puzzle);
String title = "Sokoban: level " + new Integer(theApp().getCurrentLevel()).toString();
setTitle(title);
}
}

View File

@@ -0,0 +1,186 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.dyndns.vahagn.sokoban.play;
import org.dyndns.vahagn.sokoban.play.Animator;
import org.dyndns.vahagn.sokoban.play.PuzzleView;
import org.dyndns.vahagn.sokoban.play.PuzzleLogic;
import android.content.Context;
import android.graphics.Point;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import org.dyndns.vahagn.sokoban.Puzzle;
import static org.dyndns.vahagn.sokoban.App.TAG;
//import android.support.v4.view.GestureDetectorCompat;
/**
*
* @author vahagnk
*/
public class PuzzleControl extends PuzzleView
implements GestureDetector.OnGestureListener
, GestureDetector.OnDoubleTapListener
, ScaleGestureDetector.OnScaleGestureListener
, Animator.AnimationLister
{
protected GestureDetector simpled;
protected ScaleGestureDetector scaled;
protected PuzzleLogic logic;
protected Animator animator;
protected PuzzleControlLister lister;
protected float lastSpan;
protected Point singleTapTile = new Point();
public interface PuzzleControlLister
{
public void onSolved();
public void onLongPress();
}
public PuzzleControl(Context c)
{
super(c);
logic = new PuzzleLogic();
animator = new Animator( this );
animator.setAnimationLister(this);
simpled = new GestureDetector( c, this );
simpled.setOnDoubleTapListener(this);
scaled = new ScaleGestureDetector( c, this );
}
public void setPuzzle( Puzzle p )
{
super.setPuzzle(p);
logic.setPuzzle(p);
}
public void setPuzzleControlLister( PuzzleControlLister l )
{
lister = l;
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
//
// Stop any pending animations.
//
animator.stop();
animator.clear();
//
// Pass this event to scale and gesture detectors.
//
boolean r = scaled.onTouchEvent(event);
r = simpled.onTouchEvent(event) || r;
//r = super.onTouchEvent(event) || r;
return r;
}
public boolean onDown(MotionEvent e)
{
Log.d(TAG, "onDown");
return true;
}
public void onLongPress(MotionEvent e)
{
Log.d(TAG, "onLongPress");
if ( lister != null )
lister.onLongPress();
}
public void onShowPress(MotionEvent e)
{
Log.d(TAG, "onShowPress");
}
public boolean onSingleTapUp(MotionEvent e)
{
Log.d(TAG, "onSingleTapUp");
return true;
}
public boolean onDoubleTap(MotionEvent e)
{
Log.d(TAG, "onDoubleTap");
return true;
}
public boolean onDoubleTapEvent(MotionEvent e)
{
Log.d(TAG, "onDoubleTapEvent");
return true;
}
public boolean onSingleTapConfirmed(MotionEvent e)
{
//
// Translate the tap point into tile column and row.
//
getTile( e.getX(), e.getY(), singleTapTile );
//
// If the tap is not on a valid tile then there is not point to
// continue.
//
if ( !puzzle.isValid(singleTapTile.x, singleTapTile.y) )
return true;
//
// Create sequence of steps and then animate it.
//
if ( logic.createSteps(animator, singleTapTile.x, singleTapTile.y) )
{
puzzle.save();
animator.play();
}
//
// This event is processed.
//
return true;
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
Log.d(TAG, "onFling");
return true;
}
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
{
Log.d(TAG, "onScroll");
scrollViewport( distanceX, distanceY );
return true;
}
public boolean onScaleBegin(ScaleGestureDetector detector)
{
Log.d(TAG, "onScaleBegin");
lastSpan = scaled.getCurrentSpan();
return true;
}
public boolean onScale(ScaleGestureDetector detector)
{
Log.d(TAG, "onScale");
float span = scaled.getCurrentSpan();
float focusX = scaled.getFocusX();
float focusY = scaled.getFocusY();
scaleViewport(focusX,focusY,lastSpan/span);
lastSpan = span;
return true;
}
public void onScaleEnd(ScaleGestureDetector detector)
{
Log.d(TAG, "onScaleEnd");
scaleViewportDone();
}
public void onAnimationEnd()
{
if ( puzzle.isDone() )
if ( lister != null )
lister.onSolved();
}
}

View File

@@ -0,0 +1,380 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.dyndns.vahagn.sokoban.play;
import static java.lang.Math.*;
import java.util.Arrays;
import org.dyndns.vahagn.sokoban.Puzzle;
/**
*
* @author vahagnk
*/
public class PuzzleLogic
{
//protected PlayActivity activity;
protected Puzzle puzzle = null;
protected int [] moves;
protected int [] setOfCells;
public PuzzleLogic()
{}
public void setPuzzle( Puzzle p )
{
puzzle = p;
moves = new int[puzzle.getColumnCount()*puzzle.getRowCount()];
setOfCells = new int[2*puzzle.getColumnCount()*puzzle.getRowCount()];
}
public boolean createSteps(Animator animator, int x, int y )
{
//
// Check that the x,y are valid.
//
if ( !puzzle.isValid(x, y) )
return false;
//
// Now check what tile was tapped.
// If the tapped is a floor then ...
//
int tile = puzzle.getSym( x, y);
if ( Puzzle.isEmpty(tile) )
{
//
// Check if worker selected a box and can push it?
//
if ( puzzle.isSelected() )
{
//
// Get directions to push and act accordingly.
//
int pushes[] = getPushDirections(x, y);
if ( pushes != null )
{
for ( int i = 0; i < pushes.length; i+=2 )
animator.queue( new Animator.Push(pushes[i],pushes[i+1]) );
return true;
}
}
//
// Either nothing was selected or the box cannot be moved to
// tapped location. Try move worker alone.
//
calcMoves();
if ( isAccessible(x,y) )
{
//
// First unselect box.
//
if ( puzzle.isSelected() )
animator.queue( new Animator.Unselect() );
//
// Get directions and queue moves accordingly.
//
int dirs[] = getDirections(x, y);
for ( int i = 0; i < dirs.length; i+=2 )
animator.queue( new Animator.Move(dirs[i],dirs[i+1]) );
}
else
{
//
// Show that action is not allowed.
//
animator.queue( new Animator.NoMove(x,y) );
}
}
//
// The tapped is the worker. Unselect if selected.
//
else if ( Puzzle.hasWorker(tile) )
{
if ( puzzle.isSelected() )
{
animator.queue( new Animator.Unselect() );
}
}
//
// The tapped is a box.
//
else if ( Puzzle.hasBox(tile) )
{
calcMoves();
int north = puzzle.getSym(x,y-1);
int south = puzzle.getSym(x,y+1);
int west = puzzle.getSym(x-1,y);
int east = puzzle.getSym(x+1,y);
//
// First check if there is a worker in a nighbour cell. If
// yes then simplly select the box.
//
if ( Puzzle.hasWorker( west )
|| Puzzle.hasWorker( east )
|| Puzzle.hasWorker( north )
|| Puzzle.hasWorker( south ) )
{
if ( !puzzle.isSelected(x,y) )
{
if ( puzzle.isSelected() )
animator.queue( new Animator.Unselect() );
animator.queue( new Animator.Select(x,y) );
}
}
//
// Otherwise, check which is of the cells is in closes walking
// distance and move worker to that cell, then select.
//
else
{
int pref_x = -1;
int pref_y = -1;
int shortest = Integer.MAX_VALUE;
if ( Puzzle.isEmpty( north )
&& shortest > stepsAway( x, y-1) )
{
shortest = stepsAway( x, y-1);
pref_x = x;
pref_y = y-1;
}
if ( Puzzle.isEmpty( south )
&& shortest > stepsAway( x, y+1) )
{
shortest = stepsAway( x, y+1);
pref_x = x;
pref_y = y+1;
}
if ( Puzzle.isEmpty( west )
&& shortest > stepsAway( x-1, y) )
{
shortest = stepsAway( x-1, y);
pref_x = x-1;
pref_y = y;
}
if ( Puzzle.isEmpty( east )
&& shortest > stepsAway( x+1, y) )
{
shortest = stepsAway( x+1, y);
pref_x = x+1;
pref_y = y;
}
//
// Looks there is place to approach to box.
//
if ( shortest < Integer.MAX_VALUE )
{
if ( puzzle.isSelected() )
animator.queue( new Animator.Unselect() );
int dirs[] = getDirections(pref_x, pref_y);
for ( int i = 0; i < dirs.length; i+=2 )
animator.queue( new Animator.Move(dirs[i],dirs[i+1]) );
//animator.queue( new Animator.Select(x,y) );
}
else
{
//
// Show that action is not allowed.
//
animator.queue( new Animator.NoMove(x,y) );
}
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////
//
// Routes to accessible cells from where worker stands.
//
protected final int getMoves( int x, int y )
{
return moves[puzzle.getIndex(x,y)];
}
protected void setMoves( int x, int y, int v )
{
moves[puzzle.getIndex(x,y)]=v;
}
private boolean setMovesIfGreater( int x, int y, int l )
{
//
// If out of borders then nothing to do.
//
if ( y < 0 || y >= puzzle.getRowCount()
|| x < 0 || x >= puzzle.getColumnCount() )
return false;
//
// Check if the cell is a floor or goal. If yes then set the l
// if current value is greater than l.
//
if ( getMoves(x,y) > l && puzzle.isEmpty(puzzle.getSym(x,y)))
{
setMoves(x,y,l);
return true;
}
else
return false;
}
public void calcMoves()
{
//
// Erase moves array.
//
Arrays.fill(moves,Integer.MAX_VALUE);
//
// For the beginning there is no cell in the list.
//
int front = 0;
int last = 0;
//
// Set the seed.
//
setMoves(puzzle.getWorkerX(),puzzle.getWorkerY(),0);
setOfCells[last++] = puzzle.getWorkerX();
setOfCells[last++] = puzzle.getWorkerY();
//
// Now on each loop pop one cell from the list and calculate the
// distance of cell around that cell.
//
while ( front < last )
{
//
// Pop the cell.
//
int x = setOfCells[front++];
int y = setOfCells[front++];
//
// Increase the length of cells all around given cell and push
// them into the list.
//
int l = getMoves(x,y)+1;
if ( setMovesIfGreater(x-1,y,l) )
{
setOfCells[last++] = x-1;
setOfCells[last++] = y;
}
if ( setMovesIfGreater(x+1,y,l) )
{
setOfCells[last++] = x+1;
setOfCells[last++] = y;
}
if ( setMovesIfGreater(x,y-1,l) )
{
setOfCells[last++] = x;
setOfCells[last++] = y-1;
}
if ( setMovesIfGreater(x,y+1,l) )
{
setOfCells[last++] = x;
setOfCells[last++] = y+1;
}
}
}
public final int stepsAway( int x, int y )
{
return getMoves(x,y);
}
public final boolean isAccessible( int x, int y )
{
return stepsAway(x, y) != Integer.MAX_VALUE;
}
public int [] getDirections( int x, int y )
{
int away = stepsAway(x,y);
if (away == Integer.MAX_VALUE)
return null;
//
// Ok looks there is a routh to given cell. Now create an array
// and fill in the step to get to the cell.
//
int [] steps = new int[2*away];
int i = steps.length;
steps[--i] = y;
steps[--i] = x;
while ( i > 0 )
{
x = steps[i];
y = steps[i+1];
int j = stepsAway(x,y);
if ( stepsAway(x-1,y) < j )
{
steps[--i] = y;
steps[--i] = x-1;
}
else if ( stepsAway(x+1,y) < j )
{
steps[--i] = y;
steps[--i] = x+1;
}
else if ( stepsAway(x,y-1) < j )
{
steps[--i] = y-1;
steps[--i] = x;
}
else if ( stepsAway(x,y+1) < j )
{
steps[--i] = y+1;
steps[--i] = x;
}
}
return steps;
}
public int [] getPushDirections( int x, int y )
{
//
// The selected box can be moved only orthogonally.
// Check that worker is on opposite side of the box.
// Statement:
// If scaliar product of selected->x,y and worker->selected is equal
// to manhatten distance of x,y from the selected box then the worker
// push direction is directed to x,y and is not opposit. In other words
// selected->x,y and worker->selected are codirectional.
//
int selx = puzzle.getSelectedX();
int sely = puzzle.getSelectedY();
int xx = x-selx;
int yy = y-sely;
int sx = selx-puzzle.getWorkerX();
int sy = sely-puzzle.getWorkerY();
double scaliar = (double)(xx)*(double)(sx)
+(double)(yy)*(double)(sy);
int len = abs(xx)+abs(yy);
if ( scaliar != (double)len )
return null;
//
// Now check that all cell till x,y are free.
//
int ix = selx;
int iy = sely;
while ( x != ix || y != iy )
{
ix+=sx;
iy+=sy;
if ( !puzzle.isEmpty(puzzle.getSym(ix,iy)) )
return null;
}
//
// Looks we could move the box till x,y. Create the steps array
// and fill it with push steps.
//
int steps[] = new int [2*len];
int i = 0;
ix = selx;
iy = sely;
while ( x != ix || y != iy )
{
steps[i++] = ix;
steps[i++] = iy;
ix+=sx;
iy+=sy;
}
return steps;
}
}

View File

@@ -0,0 +1,444 @@
package org.dyndns.vahagn.sokoban.play;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.v4.view.ViewCompat;
import android.util.Log;
import android.view.View;
import static java.lang.Math.*;
import static org.dyndns.vahagn.sokoban.App.TAG;
import org.dyndns.vahagn.sokoban.Puzzle;
import static org.dyndns.vahagn.sokoban.Puzzle.*;
import org.dyndns.vahagn.sokoban.R;
/**
*/
public class PuzzleView extends View
{
protected Puzzle puzzle;
private RectF screen = new RectF(0,0,0,0);
private RectF viewport = new RectF();
private RectF board = new RectF();
private Matrix col2scr = new Matrix();
private Matrix scr2col = new Matrix();
private Bitmap[] tile = new Bitmap[Puzzle.getSymCount()];
private Point tile_size = new Point(0,0);
private int rotated;
private final Paint mPaint = new Paint();
private Paint mSelectPaint = new Paint();
private Rect tile_src = new Rect();
private RectF tile_dest = new RectF();
public PuzzleView(Context context)
{
super(context);
setFocusable(true);
//
// Resources r = this.getContext().getResources();
// TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TileView);
//
// mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);
//
// a.recycle();
}
public void setPuzzle( Puzzle p )
{
puzzle = p;
board.set(0,0,puzzle.getColumnCount(),puzzle.getRowCount());
viewport.set(board);
tile_size.set(0,0);
calcTransforms();
initTiles();
}
public Puzzle getPuzzle()
{
return puzzle;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
screen.set(0, 0, w, h);
tile_size.set(0,0);
calcTransforms();
initTiles();
}
public void scaleViewport( float focusX, float focusY, float scale )
{
float left = scale*(focusX - screen.left);
float right = scale*(screen.right - focusX);
float top = scale*(focusY - screen.top);
float bottom = scale*(screen.bottom - focusY);
viewport.set( focusX-left, focusY-top, focusX+right, focusY+bottom );
scr2col.mapRect(viewport);
constrainViewport();
calcTransforms();
ViewCompat.postInvalidateOnAnimation(this);
}
public void scaleViewportDone()
{
//initTiles();
ViewCompat.postInvalidateOnAnimation(this);
}
public void scrollViewport( float distanceX, float distanceY )
{
viewport.set( screen.left+distanceX,
screen.top+distanceY,
screen.right+distanceX,
screen.bottom+distanceY );
scr2col.mapRect(viewport);
constrainViewport();
calcTransforms();
ViewCompat.postInvalidateOnAnimation(this);
}
private void constrainViewport()
{
if ( !board.contains(viewport) )
{
if ( board.width() < viewport.width() )
{
viewport.left = board.left;
viewport.right = board.right;
}
if ( viewport.left < board.left )
{
viewport.right += board.left - viewport.left;
viewport.left = board.left;
}
if ( board.right < viewport.right )
{
viewport.left -= viewport.right - board.right;
viewport.right = board.right;
}
if ( board.height() < viewport.height() )
{
viewport.top = board.top;
viewport.bottom = board.bottom;
}
if ( viewport.top < board.top )
{
viewport.bottom += board.top - viewport.top;
viewport.top = board.top;
}
if ( viewport.bottom > board.bottom )
{
viewport.top -= viewport.bottom - board.bottom;
viewport.bottom = board.bottom;
}
}
if ( viewport.width() < 4 )
{
float center = (viewport.left + viewport.right)/2;
viewport.left = center-2;
viewport.right = center+2;
}
if ( viewport.height() < 4 )
{
float center = (viewport.top + viewport.bottom)/2;
viewport.top = center-2;
viewport.bottom = center+2;
}
}
/**
* Enlarge viewport to meet aspect ratio and calculate transforms.
*/
private void calcTransforms()
{
//
// Get the sizes of screen. If it is zero then simplly do nothing.
// Otherwise, make sure that the width is bigger the height.
//
double sw = (double)screen.width();
double sh = (double)screen.height();
if ( sw <= 0 || sh <= 0 || puzzle == null )
return;
else if ( sw < sh )
{
double t = sw;
sw = sh;
sh = t;
rotated = 90;
}
//
// Enlarge the view to have the same aspect ratio as the screen.
//
double wscale = sw/(double)viewport.width();
double hscale = sh/(double)viewport.height();
// double scale = floor(min(wscale,hscale));
double scale = min(wscale,hscale);
float enlarge_vert = (float)(sh / scale - viewport.height())/2;
float enlarge_horiz = (float)(sw / scale - viewport.width())/2;
viewport.top -= enlarge_vert;
viewport.bottom += enlarge_vert;
viewport.left -= enlarge_horiz;
viewport.right += enlarge_horiz;
//
// Calculate the tile sizes which is the scale.
//
tile_size.set((int)scale,(int)scale);
//
// Set transformations from and to screen.
//
col2scr.reset();
col2scr.postTranslate(-viewport.left, -viewport.top);
col2scr.postScale((float)scale,(float)scale);
col2scr.postTranslate(screen.left, screen.top);
if ( screen.width() < screen.height() )
{
float around = screen.width()/2;
col2scr.postRotate(90, around, around);
}
col2scr.invert(scr2col);
}
/**
* Set up tiles and matrices based on puzzle and view sizes.
*/
private void initTiles()
{
//
// Prevent this from being called if tile_size is not set.
//
if ( tile_size.x <= 0 && tile_size.y <= 0 || puzzle == null )
return;
//
// Selection rect.
//
mSelectPaint.setARGB(0xff,0x40,0x40,0xf0);
mSelectPaint.setStrokeWidth((float)3.0);
//
// Create tile bitmaps for given width and height.
//
tile[FLOOR] = createFloorTile();
tile[GOAL] = createGoalTile();
tile[WALL] = createWallTile();
tile[BOX] = createBoxTile();
tile[BOX|SELECTED] = createBoxSelTile();
tile[BINGO] = createBingoTile();
tile[BINGO|SELECTED] = createBingoSelTile();
tile[WORKER] = createWorkerTile();
tile[WORKER|SELECTED] = createWorkerSelTile();
}
/**
* Apply screen to board mapping and if the points are in the
* column,row range then return it. Otherwise return null.
*/
public void getTile( float scr_x, float scr_y, Point /*out*/ tile )
{
float [] scr_p = { scr_x, scr_y };
scr2col.mapPoints( scr_p );
if ( 0 <= scr_p[0] && scr_p[0] < puzzle.getColumnCount()
&& 0 <= scr_p[1] && scr_p[1] < puzzle.getRowCount())
tile.set((int)scr_p[0], (int)scr_p[1] );
else
tile.set(-1, -1);
}
private Bitmap createFloorTile()
{
Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.floor );
Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
//canvas.drawARGB(0xff,0x40,0x40,0x40);
canvas.drawBitmap(tmp,
new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1),
new Rect(0,0,tile_size.x, tile_size.y),
mPaint);
return bitmap;
}
private Bitmap createGoalTile()
{
Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.goal );
Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
// canvas.drawARGB(0xff,0x40,0x40,0xF0);
canvas.drawBitmap(tmp,
new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1),
new Rect(0,0,tile_size.x, tile_size.y),
mPaint);
return bitmap;
}
private Bitmap createWallTile()
{
Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.wall );
Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
//canvas.drawARGB(0xff,0xF0,0x40,0x40);
canvas.drawBitmap(tmp,
new Rect(1,1,tmp.getWidth()-2,tmp.getHeight()-2),
new Rect(0,0,tile_size.x, tile_size.y),
mPaint);
return bitmap;
}
private Bitmap createBoxTile()
{
Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.box );
Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
// canvas.drawARGB(0xff,0x40,0xF0,0x40);
canvas.drawBitmap(tmp,
new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1),
new Rect(0,0,tile_size.x, tile_size.y),
mPaint);
return bitmap;
}
private Bitmap createBoxSelTile()
{
Bitmap bitmap = createBoxTile();
Canvas canvas = new Canvas(bitmap);
canvas.drawLine(0,0,tile_size.x-1, 0, mSelectPaint);
canvas.drawLine(tile_size.x-1, 0,tile_size.x-1, tile_size.y-1, mSelectPaint);
canvas.drawLine(tile_size.x-1, tile_size.y-1, 0, tile_size.y-1, mSelectPaint);
canvas.drawLine(0, tile_size.y-1, 0, 0, mSelectPaint);
return bitmap;
}
private Bitmap createBingoTile()
{
Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.bingo );
Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
// canvas.drawARGB(0xff,0x40,0xF0,0xF0);
canvas.drawBitmap(tmp,
new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1),
new Rect(0,0,tile_size.x, tile_size.y),
mPaint);
return bitmap;
}
private Bitmap createBingoSelTile()
{
Bitmap bitmap = createBingoTile();
Canvas canvas = new Canvas(bitmap);
canvas.drawLine(0,0,tile_size.x-1, 0, mSelectPaint);
canvas.drawLine(tile_size.x-1, 0,tile_size.x-1, tile_size.y-1, mSelectPaint);
canvas.drawLine(tile_size.x-1, tile_size.y-1, 0, tile_size.y-1, mSelectPaint);
canvas.drawLine(0, tile_size.y-1, 0, 0, mSelectPaint);
return bitmap;
}
private Bitmap createWorkerTile()
{
Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.worker );
Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(tmp,
new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1),
new Rect(0,0,tile_size.x, tile_size.y),
mPaint);
return bitmap;
}
private Bitmap createWorkerSelTile()
{
Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.worker_select );
Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(tmp,
new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1),
new Rect(0,0,tile_size.x, tile_size.y),
mPaint);
return bitmap;
}
@Override
public void onDraw(Canvas canvas)
{
Log.d(TAG, "onDraw");
super.onDraw(canvas);
if ( puzzle != null )
{
//int cols = puzzle.getColumnCount();
//int rows = puzzle.getRowCount();
// canvas.concat(col2scr);
int xbgn = Math.max((int)viewport.left,(int)board.left);
int xend = Math.min((int)viewport.right,(int)board.right-1);
int ybgn = Math.max((int)viewport.top,(int)board.top);
int yend = Math.min((int)viewport.bottom,(int)board.bottom-1);
tile_src.set(0,0,tile_size.x,tile_size.y);
for (int y = ybgn; y <= yend; ++y)
{
for (int x = xbgn; x <= xend; ++x)
{
tile_dest.set(x,y,x+1,y+1);
col2scr.mapRect(tile_dest);
//tile_hlpr.roundOut(tile_hlpr);
//tile_hlpr.right -=1;
//tile_hlpr.bottom -=1;
int sym = puzzle.getSym(x,y);
if ( !Puzzle.hasWorker(sym) )
{
canvas.drawBitmap(tile[sym],
tile_src,tile_dest,
mPaint);
}
else
{
//
// Draw floor.
//
canvas.drawBitmap(tile[sym&~(WORKER_MASK|SELECTED)],
tile_src,tile_dest,
mPaint);
//
// Draw worker.
//
canvas.save();
Bitmap normal_worker = ((sym&SELECTED)!=0) ?
tile[WORKER|SELECTED] : tile[WORKER];
int direction = sym & WORKER_MASK;
if ( direction == WORKER_NORTH )
{
canvas.translate(tile_dest.centerX(), tile_dest.centerY());
canvas.rotate(180+rotated);
canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
}
else if ( direction == WORKER_SOUTH )
{
canvas.translate(tile_dest.centerX(), tile_dest.centerY());
canvas.rotate(0+rotated);
canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
}
else if ( direction == WORKER_WEST )
{
canvas.translate(tile_dest.centerX(), tile_dest.centerY());
canvas.rotate(90+rotated);
canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
}
else if ( direction == WORKER_EAST )
{
canvas.translate(tile_dest.centerX(), tile_dest.centerY());
canvas.rotate(-90+rotated);
canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
}
canvas.drawBitmap(normal_worker,
tile_src,tile_dest,
mPaint);
canvas.restore();
}
}
}
}
}
}