Working version. Yet there are bugs and TODOs.
BIN
libs/android-support-v4.jar
Normal file
BIN
res/drawable-hdpi/icon.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
res/drawable-ldpi/icon.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
res/drawable-mdpi/icon.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
res/drawable-xhdpi/icon.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
res/drawable/bingo.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
res/drawable/box.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
res/drawable/floor.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
res/drawable/goal.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
res/drawable/lock.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
res/drawable/unlock.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
res/drawable/wall.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
res/drawable/worker.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
res/drawable/worker_select.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
209
src/org/dyndns/vahagn/sokoban/play/Animator.java
Normal 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 )
|
||||
{}
|
||||
}
|
||||
}
|
||||
190
src/org/dyndns/vahagn/sokoban/play/PlayActivity.java
Normal 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);
|
||||
}
|
||||
}
|
||||
186
src/org/dyndns/vahagn/sokoban/play/PuzzleControl.java
Normal 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();
|
||||
}
|
||||
}
|
||||
380
src/org/dyndns/vahagn/sokoban/play/PuzzleLogic.java
Normal 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;
|
||||
}
|
||||
}
|
||||
444
src/org/dyndns/vahagn/sokoban/play/PuzzleView.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||