More refactoring: PuzzlyRouteFinder is seperated, Animator dependency is reversed.
This commit is contained in:
@@ -2,11 +2,9 @@ package org.vostan.banvor;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import org.vostan.banvor.game.State;
|
||||
import org.vostan.banvor.model.PuzzleContainer;
|
||||
import org.vostan.banvor.model.IPuzzleSource;
|
||||
import org.vostan.banvor.game.PuzzleContainer;
|
||||
|
||||
public class App extends Application
|
||||
{
|
||||
|
||||
@@ -31,10 +31,10 @@ public class Animator implements Runnable
|
||||
|
||||
protected static abstract class XYAction extends Action
|
||||
{
|
||||
protected XYPair _xy;
|
||||
protected XYPair _xy = new XYPair();
|
||||
|
||||
public XYAction( XYPair xy ){
|
||||
_xy = xy;
|
||||
_xy.set(xy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.vostan.banvor.board;
|
||||
|
||||
import org.vostan.banvor.model.IPuzzleAnimator;
|
||||
import org.vostan.banvor.model.XYPair;
|
||||
|
||||
public class PuzzleAnimator implements IPuzzleAnimator {
|
||||
|
||||
Animator animator = null;
|
||||
|
||||
public PuzzleAnimator(Animator a){
|
||||
animator = a;
|
||||
}
|
||||
|
||||
public void move(XYPair xy )
|
||||
{
|
||||
animator.queue( new Animator.Move(xy) );
|
||||
}
|
||||
|
||||
public void push(XYPair xy )
|
||||
{
|
||||
animator.queue( new Animator.Push(xy) );
|
||||
}
|
||||
|
||||
public void select(XYPair xy )
|
||||
{
|
||||
animator.queue( new Animator.Select(xy) );
|
||||
}
|
||||
|
||||
public void unselect()
|
||||
{
|
||||
animator.queue( new Animator.Unselect() );
|
||||
}
|
||||
|
||||
public void buzz()
|
||||
{
|
||||
//animator.queue( new Animator.NoMove(x,y) );
|
||||
}
|
||||
|
||||
public void undoable(XYPair xy )
|
||||
{
|
||||
animator.queue( new Animator.NoMove(xy) );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
|
||||
import org.vostan.banvor.game.PuzzleLogic;
|
||||
import org.vostan.banvor.model.PuzzleChoreographer;
|
||||
import org.vostan.banvor.model.Puzzle;
|
||||
import org.vostan.banvor.model.XYPair;
|
||||
|
||||
@@ -29,12 +29,13 @@ public class PuzzleControl extends PuzzleView
|
||||
{
|
||||
protected GestureDetector simpled;
|
||||
protected ScaleGestureDetector scaled;
|
||||
protected PuzzleLogic logic;
|
||||
protected PuzzleChoreographer logic;
|
||||
protected Animator animator;
|
||||
protected PuzzleAnimator puzzleAnimator;
|
||||
protected PuzzleControlLister lister;
|
||||
|
||||
protected float lastSpan;
|
||||
protected Point singleTapTile = new Point();
|
||||
protected XYPair singleTapTile = new XYPair();
|
||||
|
||||
public interface PuzzleControlLister
|
||||
{
|
||||
@@ -46,9 +47,10 @@ public class PuzzleControl extends PuzzleView
|
||||
public PuzzleControl(Context c, AttributeSet attributeSet)
|
||||
{
|
||||
super(c,attributeSet);
|
||||
logic = new PuzzleLogic();
|
||||
logic = new PuzzleChoreographer();
|
||||
animator = new Animator( this );
|
||||
animator.setAnimationLister(this);
|
||||
puzzleAnimator = new PuzzleAnimator(animator);
|
||||
|
||||
simpled = new GestureDetector( c, this );
|
||||
simpled.setOnDoubleTapListener(this);
|
||||
@@ -118,12 +120,12 @@ public class PuzzleControl extends PuzzleView
|
||||
// If the tap is not on a valid tile then there is not point to
|
||||
// continue.
|
||||
//
|
||||
if ( !puzzle.isValid(new XYPair(singleTapTile.x, singleTapTile.y) ))
|
||||
if ( !puzzle.isValid(singleTapTile) )
|
||||
return true;
|
||||
//
|
||||
// Create sequence of steps and then animate it.
|
||||
//
|
||||
if ( logic.createSteps(animator, singleTapTile.x, singleTapTile.y) )
|
||||
if ( logic.createSteps(puzzleAnimator, singleTapTile))
|
||||
{
|
||||
puzzle.save();
|
||||
animator.play();
|
||||
|
||||
@@ -242,7 +242,7 @@ public class PuzzleView extends View
|
||||
* 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 )
|
||||
public void getTile(float scr_x, float scr_y, XYPair /*out*/ tile )
|
||||
{
|
||||
float [] scr_p = { scr_x, scr_y };
|
||||
scr2col.mapPoints( scr_p );
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.vostan.banvor.model;
|
||||
package org.vostan.banvor.game;
|
||||
|
||||
import static org.vostan.banvor.App.TAG;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.vostan.banvor.model.Puzzle;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@@ -12,7 +14,7 @@ public class PuzzleBinLoader {
|
||||
/*
|
||||
* Load puzzle from the puzzle.bin.
|
||||
*/
|
||||
public static Puzzle loadNthPuzzle( InputStream is, int i )
|
||||
public static Puzzle loadNthPuzzle(InputStream is, int i )
|
||||
{try{
|
||||
//
|
||||
// Read amount of puzzles.
|
||||
@@ -1,11 +1,14 @@
|
||||
package org.vostan.banvor.model;
|
||||
package org.vostan.banvor.game;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.vostan.banvor.App.theApp;
|
||||
import org.vostan.banvor.model.Puzzle;
|
||||
|
||||
import org.vostan.banvor.game.PuzzleBinLoader;
|
||||
|
||||
import org.vostan.banvor.R;
|
||||
import org.vostan.banvor.model.IPuzzleSource;
|
||||
import org.vostan.banvor.model.Puzzle;
|
||||
|
||||
public class PuzzleContainer implements IPuzzleSource
|
||||
{
|
||||
@@ -14,7 +17,7 @@ public class PuzzleContainer implements IPuzzleSource
|
||||
/*
|
||||
* Load puzzle from the puzzle.bin.
|
||||
*/
|
||||
public Puzzle getPuzzle( int i )
|
||||
public Puzzle getPuzzle(int i )
|
||||
{
|
||||
InputStream is = theApp().getResources().openRawResource(R.raw.puzzles);
|
||||
return PuzzleBinLoader.loadNthPuzzle(is, i);
|
||||
@@ -1,509 +0,0 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.vostan.banvor.game;
|
||||
|
||||
import static java.lang.Math.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.vostan.banvor.board.Animator;
|
||||
import org.vostan.banvor.model.Puzzle;
|
||||
import org.vostan.banvor.model.XYPair;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class PuzzleLogic
|
||||
{
|
||||
//protected PlayActivity activity;
|
||||
protected Puzzle puzzle = null;
|
||||
protected int [] moves;
|
||||
protected Vector<XYPair> setOfCells = new Vector<XYPair>();
|
||||
|
||||
public PuzzleLogic()
|
||||
{}
|
||||
|
||||
public void setPuzzle( Puzzle p )
|
||||
{
|
||||
puzzle = p;
|
||||
moves = new int[puzzle.getColumnCount()*puzzle.getRowCount()];
|
||||
setOfCells.ensureCapacity(puzzle.getColumnCount()*puzzle.getRowCount());
|
||||
}
|
||||
|
||||
public boolean createSteps(Animator animator, final int x, final int y )
|
||||
{
|
||||
return createSteps(animator, new XYPair(x,y));
|
||||
}
|
||||
public boolean createSteps(Animator animator, XYPair xy )
|
||||
{
|
||||
//
|
||||
// Check that the x,y are valid.
|
||||
//
|
||||
if ( !puzzle.isValid(xy) )
|
||||
return false;
|
||||
//
|
||||
// Now check what tile was tapped.
|
||||
// If the tapped is a floor then ...
|
||||
//
|
||||
int tile = puzzle.getSym(xy);
|
||||
if ( Puzzle.isEmpty(tile) )
|
||||
{
|
||||
//
|
||||
// Calculate possible moves map.
|
||||
//
|
||||
calcMoves();
|
||||
//
|
||||
// Check if worker selected a box and can push it to the location?
|
||||
// If yes then we are done.
|
||||
//
|
||||
if ( puzzle.isSelected()
|
||||
&& tryMoveBox(animator, xy) )
|
||||
return true;
|
||||
//
|
||||
// Either nothing was selected or the box cannot be moved to
|
||||
// tapped location. Try move worker alone.
|
||||
//
|
||||
if ( tryMoveWorker( animator, xy ) )
|
||||
return true;
|
||||
//
|
||||
// Show that action is not allowed.
|
||||
//
|
||||
undoable(animator, xy);
|
||||
}
|
||||
//
|
||||
// The tapped is the worker. Try move the box. If not possible
|
||||
// then unselect if selected.
|
||||
//
|
||||
else if ( Puzzle.hasWorker(tile) )
|
||||
{
|
||||
if ( !puzzle.isSelected() )
|
||||
{
|
||||
buzz( animator );
|
||||
return true;
|
||||
}
|
||||
//
|
||||
// Calculate possible moves map.
|
||||
//
|
||||
calcMoves();
|
||||
//
|
||||
// Check if worker selected a box and can push it to the location?
|
||||
// If yes then we are done.
|
||||
//
|
||||
if ( tryMoveBox(animator, xy) )
|
||||
return true;
|
||||
//
|
||||
// If the box is not movable then unselect it.
|
||||
//
|
||||
unselect( animator );
|
||||
}
|
||||
//
|
||||
// The tapped is a box.
|
||||
//
|
||||
else if ( Puzzle.hasBox(tile) )
|
||||
{
|
||||
//
|
||||
// Calculate possible moves map.
|
||||
//
|
||||
calcMoves();
|
||||
//
|
||||
// If the box is selected then unselect it.
|
||||
//
|
||||
if ( puzzle.isSelected(xy) )
|
||||
{
|
||||
unselect( animator );
|
||||
return true;
|
||||
}
|
||||
//
|
||||
// Try move the worker next to the box and select it if the
|
||||
// box is not selected yet.
|
||||
//
|
||||
if ( trySelectBox( animator, xy ) )
|
||||
return true;
|
||||
//
|
||||
// Show that action is not allowed if reached till here.
|
||||
//
|
||||
undoable( animator, xy );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Routes to accessible cells from where worker stands.
|
||||
//
|
||||
protected boolean tryMoveWorker( Animator animator, XYPair xy )
|
||||
{
|
||||
//
|
||||
// If the filed is not accessable then move failed.
|
||||
//
|
||||
if ( !isAccessible(xy) )
|
||||
return false;
|
||||
//
|
||||
// First unselect box.
|
||||
//
|
||||
if ( puzzle.isSelected() )
|
||||
unselect( animator );
|
||||
//
|
||||
// Get directions and queue moves accordingly.
|
||||
//
|
||||
Vector<XYPair> dirs = getDirections(xy);
|
||||
ListIterator<XYPair> it = dirs.listIterator(dirs.size());
|
||||
while ( it.hasPrevious() ) {
|
||||
move(animator, it.previous());
|
||||
}
|
||||
//
|
||||
// Done.
|
||||
//
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean tryMoveBox( Animator animator, XYPair xy )
|
||||
{
|
||||
//
|
||||
// If no box is selected then we cannot move no box.
|
||||
//
|
||||
if ( !puzzle.isSelected() )
|
||||
return false;
|
||||
//
|
||||
// Check that asked move is orthogonal to the slected box.
|
||||
//
|
||||
XYPair box_xy = puzzle.getSelected();
|
||||
XYPair dxy = xy.sub(box_xy);
|
||||
if ( dxy.x() != 0 && dxy.y() != 0 )
|
||||
return false;
|
||||
//
|
||||
// There is no point to continue also in case if the asked cell
|
||||
// is the box.
|
||||
//
|
||||
if ( dxy.isEqual(XYPair.ZERO) )
|
||||
return false;
|
||||
//
|
||||
// Now find the desired place for the worker to start push this
|
||||
// box.
|
||||
//
|
||||
XYPair w = new XYPair(box_xy);
|
||||
if ( xy.x() < box_xy.x() )
|
||||
w = w.right();
|
||||
else if ( xy.x() > box_xy.x() )
|
||||
w = w.left();
|
||||
else if ( xy.y() < box_xy.y() )
|
||||
w = w.up();
|
||||
else
|
||||
w = w.down();
|
||||
//
|
||||
// Check if the desired place for the worker is accessable? If not
|
||||
// then there is no point to continue.
|
||||
//
|
||||
if ( !isAccessible(w) )
|
||||
return false;
|
||||
//
|
||||
// Now check that all cell till x,y are empty and that we can
|
||||
// push box till there.
|
||||
//
|
||||
XYPair step_xy = box_xy.sub(w);
|
||||
for ( XYPair i = box_xy; !xy.isEqual(i); )
|
||||
{
|
||||
i = i.add(step_xy);
|
||||
int v = puzzle.getSym(i);
|
||||
if ( !puzzle.isEmpty(v) && !puzzle.hasWorker(v) )
|
||||
return false;
|
||||
}
|
||||
//
|
||||
// Ok, looks we can do the desired action. Now put instructions on
|
||||
// what to do. First move worker to desired position if he is not
|
||||
// there already.
|
||||
//
|
||||
if ( !w.isEqual(puzzle.getWorker()) )
|
||||
{
|
||||
tryMoveWorker( animator, w );
|
||||
select( animator, box_xy );
|
||||
}
|
||||
//
|
||||
// Now create the steps to push the box.
|
||||
//
|
||||
for ( XYPair i = box_xy; !i.isEqual(xy); i = i.add(step_xy) ) {
|
||||
push( animator, i );
|
||||
}
|
||||
//
|
||||
// Done
|
||||
//
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean trySelectBox( Animator animator, XYPair xy )
|
||||
{
|
||||
int north = puzzle.getSym(xy.up());
|
||||
int south = puzzle.getSym(xy.down());
|
||||
int west = puzzle.getSym(xy.left());
|
||||
int east = puzzle.getSym(xy.right());
|
||||
//
|
||||
// First check if there is a worker in a nighbour cell. If
|
||||
// yes then simplly select the box. If the box is already selected
|
||||
// then do nothing and if othe box is selected then unselect it first
|
||||
// and then select the box.
|
||||
//
|
||||
if ( Puzzle.hasWorker( west )
|
||||
|| Puzzle.hasWorker( east )
|
||||
|| Puzzle.hasWorker( north )
|
||||
|| Puzzle.hasWorker( south ) )
|
||||
{
|
||||
if ( !puzzle.isSelected(xy) )
|
||||
{
|
||||
if ( puzzle.isSelected() )
|
||||
unselect( animator );
|
||||
select( animator, xy );
|
||||
}
|
||||
}
|
||||
//
|
||||
// Otherwise, check which is of the cells is in closes walking
|
||||
// distance and move worker to that cell, then select.
|
||||
//
|
||||
else
|
||||
{
|
||||
XYPair pref = new XYPair(-1, -1);
|
||||
int shortest = Integer.MAX_VALUE;
|
||||
if ( Puzzle.isEmpty( north )
|
||||
&& shortest > stepsAway(xy.down()) )
|
||||
{
|
||||
shortest = stepsAway(xy.down());
|
||||
pref.set(xy.down());
|
||||
}
|
||||
if ( Puzzle.isEmpty( south )
|
||||
&& shortest > stepsAway(xy.up()) )
|
||||
{
|
||||
shortest = stepsAway(xy.up());
|
||||
pref.set(xy.up());
|
||||
}
|
||||
if ( Puzzle.isEmpty( west )
|
||||
&& shortest > stepsAway(xy.left()) )
|
||||
{
|
||||
shortest = stepsAway(xy.left());
|
||||
pref.set(xy.left());
|
||||
}
|
||||
if ( Puzzle.isEmpty( east )
|
||||
&& shortest > stepsAway( xy.right()) )
|
||||
{
|
||||
shortest = stepsAway( xy.right());
|
||||
pref.set(xy.right());
|
||||
}
|
||||
//
|
||||
// Move the worker to the direction. If we cannot move worker
|
||||
// next to the box then we cannot select it.
|
||||
//
|
||||
if ( !puzzle.isValid(pref)
|
||||
|| !tryMoveWorker(animator, pref) )
|
||||
return false;
|
||||
//
|
||||
// Select the box.
|
||||
//
|
||||
select(animator,xy);
|
||||
}
|
||||
//
|
||||
// Done
|
||||
//
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// These should move to a callback.
|
||||
//
|
||||
|
||||
protected void move( Animator animator, XYPair xy )
|
||||
{
|
||||
animator.queue( new Animator.Move(xy) );
|
||||
}
|
||||
|
||||
protected void push( Animator animator, XYPair xy )
|
||||
{
|
||||
animator.queue( new Animator.Push(xy) );
|
||||
}
|
||||
|
||||
protected void select( Animator animator, XYPair xy )
|
||||
{
|
||||
animator.queue( new Animator.Select(xy) );
|
||||
}
|
||||
|
||||
protected void unselect( Animator animator )
|
||||
{
|
||||
animator.queue( new Animator.Unselect() );
|
||||
}
|
||||
|
||||
protected void buzz( Animator animator )
|
||||
{
|
||||
//animator.queue( new Animator.NoMove(x,y) );
|
||||
}
|
||||
|
||||
protected void undoable( Animator animator, XYPair xy )
|
||||
{
|
||||
animator.queue( new Animator.NoMove(xy) );
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Routes to accessible cells from where worker stands.
|
||||
//
|
||||
|
||||
protected final int getMoves( XYPair xy )
|
||||
{
|
||||
return moves[puzzle.getIndex(xy)];
|
||||
}
|
||||
protected void setMoves( XYPair xy, int v )
|
||||
{
|
||||
moves[puzzle.getIndex(xy)]=v;
|
||||
}
|
||||
private boolean setMovesIfGreater( XYPair xy, int l )
|
||||
{
|
||||
//
|
||||
// If out of borders then nothing to do.
|
||||
//
|
||||
if ( !puzzle.isValid(xy) )
|
||||
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(xy) > l && puzzle.isEmpty(puzzle.getSym(xy)))
|
||||
{
|
||||
setMoves(xy,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;
|
||||
setOfCells.removeAllElements();
|
||||
//
|
||||
// Set the seed.
|
||||
//
|
||||
setMoves(puzzle.getWorker(),0);
|
||||
setOfCells.add(puzzle.getWorker());
|
||||
//
|
||||
// Now on each loop pop one cell from the list and calculate the
|
||||
// distance of cell around that cell.
|
||||
//
|
||||
while ( front < setOfCells.size() )
|
||||
{
|
||||
//
|
||||
// Pop the cell.
|
||||
//
|
||||
XYPair xy = setOfCells.elementAt(front++);
|
||||
//
|
||||
// Increase the length of cells all around given cell and push
|
||||
// them into the list.
|
||||
//
|
||||
int l = getMoves(xy)+1;
|
||||
if ( setMovesIfGreater(xy.left(),l) ) {
|
||||
setOfCells.add(xy.left());
|
||||
}
|
||||
if ( setMovesIfGreater(xy.right(),l) ) {
|
||||
setOfCells.add(xy.right());
|
||||
}
|
||||
if ( setMovesIfGreater(xy.down(),l) ) {
|
||||
setOfCells.add(xy.down());
|
||||
}
|
||||
if ( setMovesIfGreater(xy.up(),l) ) {
|
||||
setOfCells.add(xy.up());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final int stepsAway( XYPair xy )
|
||||
{
|
||||
return getMoves(xy);
|
||||
}
|
||||
|
||||
public final boolean isAccessible( XYPair xy )
|
||||
{
|
||||
return puzzle.isValid(xy) && stepsAway(xy) != Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
public Vector<XYPair> getDirections( XYPair xy )
|
||||
{
|
||||
int away = stepsAway(xy);
|
||||
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.
|
||||
//
|
||||
Vector<XYPair> steps = new Vector<XYPair>(away);
|
||||
steps.add(xy);
|
||||
while ( steps.size() < away )
|
||||
{
|
||||
xy = steps.lastElement();
|
||||
int j = stepsAway(xy);
|
||||
if ( stepsAway(xy.left()) < j ) {
|
||||
steps.add(xy.left());
|
||||
}
|
||||
else if ( stepsAway(xy.right()) < j ) {
|
||||
steps.add(xy.right());
|
||||
}
|
||||
else if ( stepsAway(xy.down()) < j ) {
|
||||
steps.add(xy.down());
|
||||
}
|
||||
else if ( stepsAway(xy.up()) < j ) {
|
||||
steps.add(xy.up());
|
||||
}
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
|
||||
public XYPair [] getPushDirections( XYPair xy )
|
||||
{
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
XYPair sel = puzzle.getSelected();
|
||||
XYPair dist = XYPair.sub(xy, puzzle.getSelected());
|
||||
XYPair dir = XYPair.sub(puzzle.getSelected(), puzzle.getWorker());
|
||||
double scaliar = (double)(dist.x())*(double)(dir.x())
|
||||
+(double)(dist.y())*(double)(dir.y());
|
||||
int len = dist.l1_norm();
|
||||
if ( scaliar != (double)len )
|
||||
return null;
|
||||
//
|
||||
// Now check that all cell till x,y are free.
|
||||
//
|
||||
XYPair ixy = new XYPair(puzzle.getSelected());
|
||||
while ( !xy.isEqual(ixy) )
|
||||
{
|
||||
ixy = ixy.add(dir);
|
||||
if ( !puzzle.isEmpty(puzzle.getSym(ixy)) )
|
||||
return null;
|
||||
}
|
||||
//
|
||||
// Looks we could move the box till x,y. Create the steps array
|
||||
// and fill it with push steps.
|
||||
//
|
||||
XYPair steps[] = new XYPair[len];
|
||||
int i = 0;
|
||||
ixy.set(puzzle.getSelected());
|
||||
while ( !xy.isEqual(ixy) )
|
||||
{
|
||||
steps[i++] = ixy;
|
||||
ixy = ixy.add(dir);
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.vostan.banvor.model;
|
||||
|
||||
public interface IPuzzleAnimator {
|
||||
public void move(XYPair xy );
|
||||
public void push( XYPair xy );
|
||||
public void select( XYPair xy );
|
||||
public void unselect();
|
||||
public void buzz();
|
||||
public void undoable( XYPair xy );
|
||||
}
|
||||
@@ -21,8 +21,7 @@ public class Puzzle
|
||||
public final static int SELECTED = 0x40;
|
||||
public final static int MAX_COUNT = 0x80-1;
|
||||
|
||||
private final static XYPair unselected = new XYPair(-1,-1);
|
||||
private final static XYPair zero = new XYPair(0,0);
|
||||
private final static XYPair UNSELECTED = new XYPair(-1,-1);
|
||||
|
||||
|
||||
private int [] board;
|
||||
@@ -36,7 +35,7 @@ public class Puzzle
|
||||
}
|
||||
public Puzzle(int cols, int rows, int [] b)
|
||||
{
|
||||
board_size.set(new XYPair(cols, rows));
|
||||
board_size.set(cols, rows);
|
||||
box_count = 0;
|
||||
bingos = 0;
|
||||
// board = new int[board_size.x()*board_size.y()];
|
||||
@@ -72,27 +71,17 @@ public class Puzzle
|
||||
return MAX_COUNT;
|
||||
}
|
||||
|
||||
public final int getIndex( XYPair c )
|
||||
{
|
||||
public final int getIndex( final XYPair c ) {
|
||||
return c.y()*board_size.x()+c.x();
|
||||
}
|
||||
public final XYPair getXY( int idx )
|
||||
{
|
||||
return new XYPair(getX(idx), getY(idx));
|
||||
}
|
||||
public final int getX( int idx )
|
||||
{
|
||||
return idx % board_size.x();
|
||||
}
|
||||
public final int getY( int idx )
|
||||
{
|
||||
return idx / board_size.x();
|
||||
public final XYPair getXY( int idx ) {
|
||||
return new XYPair(idx % board_size.x(), idx / board_size.x());
|
||||
}
|
||||
|
||||
public final int getSym( XYPair c ) {
|
||||
return board[getIndex(c)];
|
||||
}
|
||||
public void setSym( XYPair xy, int v )
|
||||
public void setSym( final XYPair xy, int v )
|
||||
{
|
||||
board[getIndex(xy)]=v;
|
||||
}
|
||||
@@ -110,32 +99,27 @@ public class Puzzle
|
||||
return (v & WORKER) != 0;
|
||||
}
|
||||
|
||||
public final XYPair getWorker()
|
||||
{
|
||||
return worker;
|
||||
public final XYPair getWorker() {
|
||||
return new XYPair(worker);
|
||||
}
|
||||
|
||||
public final boolean isSelected()
|
||||
{
|
||||
return !selected.isEqual(unselected);
|
||||
return !selected.isEqual(UNSELECTED);
|
||||
}
|
||||
|
||||
public final boolean isSelected( XYPair s )
|
||||
{
|
||||
public final boolean isSelected( final XYPair s ) {
|
||||
return selected.isEqual(s);
|
||||
}
|
||||
|
||||
public final XYPair getSelected()
|
||||
{
|
||||
return selected;
|
||||
return new XYPair(selected);
|
||||
}
|
||||
|
||||
public final boolean isValid( XYPair xy )
|
||||
{
|
||||
return xy.isInside(zero,board_size);
|
||||
public final boolean isValid( final XYPair xy ) {
|
||||
return xy.isInside(XYPair.ZERO,board_size);
|
||||
}
|
||||
|
||||
public final boolean isOneStep( XYPair xy ) {
|
||||
public final boolean isOneStep( final XYPair xy ) {
|
||||
return isValid(xy) && XYPair.sub(worker, xy).l1_norm() == 1;
|
||||
}
|
||||
|
||||
@@ -149,7 +133,7 @@ public class Puzzle
|
||||
// Move worker.
|
||||
//
|
||||
|
||||
private int worker_direction(XYPair xy) {
|
||||
private final int worker_direction(XYPair xy) {
|
||||
//
|
||||
// Find direction to move.
|
||||
//
|
||||
@@ -235,11 +219,11 @@ public class Puzzle
|
||||
//
|
||||
setSym(worker,getSym(worker)&~SELECTED);
|
||||
setSym(selected,getSym(selected)&~SELECTED);
|
||||
selected.set(unselected);
|
||||
selected.set(UNSELECTED);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean push(XYPair xy)
|
||||
public boolean push(final XYPair xy)
|
||||
{
|
||||
//
|
||||
// If not selected then do nothing.
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.vostan.banvor.model;
|
||||
|
||||
import java.util.ListIterator;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.vostan.banvor.model.IPuzzleAnimator;
|
||||
import org.vostan.banvor.model.Puzzle;
|
||||
import org.vostan.banvor.model.PuzzleRouteFinder;
|
||||
import org.vostan.banvor.model.XYPair;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class PuzzleChoreographer
|
||||
{
|
||||
protected Puzzle puzzle = null;
|
||||
protected PuzzleRouteFinder routeFinder;
|
||||
|
||||
public PuzzleChoreographer()
|
||||
{}
|
||||
|
||||
public void setPuzzle( Puzzle p )
|
||||
{
|
||||
puzzle = p;
|
||||
routeFinder = new PuzzleRouteFinder(p);
|
||||
}
|
||||
|
||||
public boolean createSteps(IPuzzleAnimator animator, XYPair xy )
|
||||
{
|
||||
//
|
||||
// Check that the x,y are valid.
|
||||
//
|
||||
if ( !puzzle.isValid(xy) )
|
||||
return false;
|
||||
//
|
||||
// Now check what tile was tapped.
|
||||
// If the tapped is a floor then ...
|
||||
//
|
||||
int tile = puzzle.getSym(xy);
|
||||
if ( Puzzle.isEmpty(tile) )
|
||||
{
|
||||
//
|
||||
// Calculate possible moves map.
|
||||
//
|
||||
routeFinder.calcMoves();
|
||||
//
|
||||
// Check if worker selected a box and can push it to the location?
|
||||
// If yes then we are done.
|
||||
//
|
||||
if ( puzzle.isSelected()
|
||||
&& tryMoveBox(animator, xy) )
|
||||
return true;
|
||||
//
|
||||
// Either nothing was selected or the box cannot be moved to
|
||||
// tapped location. Try move worker alone.
|
||||
//
|
||||
if ( tryMoveWorker( animator, xy ) )
|
||||
return true;
|
||||
//
|
||||
// Show that action is not allowed.
|
||||
//
|
||||
animator.undoable(xy);
|
||||
}
|
||||
//
|
||||
// The tapped is the worker. Try move the box. If not possible
|
||||
// then unselect if selected.
|
||||
//
|
||||
else if ( Puzzle.hasWorker(tile) )
|
||||
{
|
||||
if ( !puzzle.isSelected() )
|
||||
{
|
||||
animator.buzz();
|
||||
return true;
|
||||
}
|
||||
//
|
||||
// Calculate possible moves map.
|
||||
//
|
||||
routeFinder.calcMoves();
|
||||
//
|
||||
// Check if worker selected a box and can push it to the location?
|
||||
// If yes then we are done.
|
||||
//
|
||||
if ( tryMoveBox(animator, xy) )
|
||||
return true;
|
||||
//
|
||||
// If the box is not movable then unselect it.
|
||||
//
|
||||
animator.unselect();
|
||||
}
|
||||
//
|
||||
// The tapped is a box.
|
||||
//
|
||||
else if ( Puzzle.hasBox(tile) )
|
||||
{
|
||||
//
|
||||
// Calculate possible moves map.
|
||||
//
|
||||
routeFinder.calcMoves();
|
||||
//
|
||||
// If the box is selected then unselect it.
|
||||
//
|
||||
if ( puzzle.isSelected(xy) )
|
||||
{
|
||||
animator.unselect();
|
||||
return true;
|
||||
}
|
||||
//
|
||||
// Try move the worker next to the box and select it if the
|
||||
// box is not selected yet.
|
||||
//
|
||||
if ( trySelectBox( animator, xy ) )
|
||||
return true;
|
||||
//
|
||||
// Show that action is not allowed if reached till here.
|
||||
//
|
||||
animator.undoable(xy);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Routes to accessible cells from where worker stands.
|
||||
//
|
||||
protected boolean tryMoveWorker( IPuzzleAnimator animator, XYPair xy )
|
||||
{
|
||||
//
|
||||
// If the filed is not accessable then move failed.
|
||||
//
|
||||
if ( !routeFinder.isAccessible(xy) )
|
||||
return false;
|
||||
//
|
||||
// First unselect box.
|
||||
//
|
||||
if ( puzzle.isSelected() )
|
||||
animator.unselect();
|
||||
//
|
||||
// Get directions and queue moves accordingly.
|
||||
//
|
||||
Vector<XYPair> dirs = routeFinder.getDirections(xy);
|
||||
ListIterator<XYPair> it = dirs.listIterator(dirs.size());
|
||||
while ( it.hasPrevious() ) {
|
||||
animator.move(it.previous());
|
||||
}
|
||||
//
|
||||
// Done.
|
||||
//
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean tryMoveBox( IPuzzleAnimator animator, XYPair xy )
|
||||
{
|
||||
//
|
||||
// If no box is selected then we cannot move no box.
|
||||
//
|
||||
if ( !puzzle.isSelected() )
|
||||
return false;
|
||||
//
|
||||
// Check that asked move is orthogonal to the slected box.
|
||||
//
|
||||
final XYPair box_xy = puzzle.getSelected();
|
||||
XYPair dxy = xy.sub(box_xy);
|
||||
if ( dxy.x() != 0 && dxy.y() != 0 )
|
||||
return false;
|
||||
//
|
||||
// There is no point to continue also in case if the asked cell
|
||||
// is the box.
|
||||
//
|
||||
if ( dxy.isEqual(XYPair.ZERO) )
|
||||
return false;
|
||||
//
|
||||
// Now find the desired place for the worker to start push this
|
||||
// box.
|
||||
//
|
||||
XYPair w = new XYPair(box_xy);
|
||||
if ( xy.x() < box_xy.x() )
|
||||
w = w.right();
|
||||
else if ( xy.x() > box_xy.x() )
|
||||
w = w.left();
|
||||
else if ( xy.y() < box_xy.y() )
|
||||
w = w.up();
|
||||
else
|
||||
w = w.down();
|
||||
//
|
||||
// Check if the desired place for the worker is accessable? If not
|
||||
// then there is no point to continue.
|
||||
//
|
||||
if ( !routeFinder.isAccessible(w) )
|
||||
return false;
|
||||
//
|
||||
// Now check that all cell till x,y are empty and that we can
|
||||
// push box till there.
|
||||
//
|
||||
XYPair step_xy = box_xy.sub(w);
|
||||
for ( XYPair i = box_xy; !xy.isEqual(i); )
|
||||
{
|
||||
i = i.add(step_xy);
|
||||
int v = puzzle.getSym(i);
|
||||
if ( !puzzle.isEmpty(v) && !puzzle.hasWorker(v) )
|
||||
return false;
|
||||
}
|
||||
//
|
||||
// Ok, looks we can do the desired action. Now put instructions on
|
||||
// what to do. First move worker to desired position if he is not
|
||||
// there already.
|
||||
//
|
||||
if ( !w.isEqual(puzzle.getWorker()) )
|
||||
{
|
||||
tryMoveWorker( animator, w );
|
||||
animator.select(box_xy);
|
||||
}
|
||||
//
|
||||
// Now create the steps to push the box.
|
||||
//
|
||||
for ( XYPair i = box_xy; !i.isEqual(xy); i = i.add(step_xy) ) {
|
||||
animator.push(i);
|
||||
}
|
||||
//
|
||||
// Done
|
||||
//
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean trySelectBox( IPuzzleAnimator animator, XYPair xy )
|
||||
{
|
||||
int north = puzzle.getSym(xy.up());
|
||||
int south = puzzle.getSym(xy.down());
|
||||
int west = puzzle.getSym(xy.left());
|
||||
int east = puzzle.getSym(xy.right());
|
||||
//
|
||||
// First check if there is a worker in a nighbour cell. If
|
||||
// yes then simplly select the box. If the box is already selected
|
||||
// then do nothing and if othe box is selected then unselect it first
|
||||
// and then select the box.
|
||||
//
|
||||
if ( Puzzle.hasWorker( west )
|
||||
|| Puzzle.hasWorker( east )
|
||||
|| Puzzle.hasWorker( north )
|
||||
|| Puzzle.hasWorker( south ) )
|
||||
{
|
||||
if ( !puzzle.isSelected(xy) )
|
||||
{
|
||||
if ( puzzle.isSelected() )
|
||||
animator.unselect();
|
||||
animator.select(xy);
|
||||
}
|
||||
}
|
||||
//
|
||||
// Otherwise, check which is of the cells is in closes walking
|
||||
// distance and move worker to that cell, then select.
|
||||
//
|
||||
else
|
||||
{
|
||||
XYPair pref = new XYPair(-1, -1);
|
||||
int shortest = Integer.MAX_VALUE;
|
||||
if ( Puzzle.isEmpty( north )
|
||||
&& shortest > routeFinder.stepsAway(xy.down()) )
|
||||
{
|
||||
shortest = routeFinder.stepsAway(xy.down());
|
||||
pref.set(xy.down());
|
||||
}
|
||||
if ( Puzzle.isEmpty( south )
|
||||
&& shortest > routeFinder.stepsAway(xy.up()) )
|
||||
{
|
||||
shortest = routeFinder.stepsAway(xy.up());
|
||||
pref.set(xy.up());
|
||||
}
|
||||
if ( Puzzle.isEmpty( west )
|
||||
&& shortest > routeFinder.stepsAway(xy.left()) )
|
||||
{
|
||||
shortest = routeFinder.stepsAway(xy.left());
|
||||
pref.set(xy.left());
|
||||
}
|
||||
if ( Puzzle.isEmpty( east )
|
||||
&& shortest > routeFinder.stepsAway( xy.right()) )
|
||||
{
|
||||
shortest = routeFinder.stepsAway( xy.right());
|
||||
pref.set(xy.right());
|
||||
}
|
||||
//
|
||||
// Move the worker to the direction. If we cannot move worker
|
||||
// next to the box then we cannot select it.
|
||||
//
|
||||
if ( !puzzle.isValid(pref)
|
||||
|| !tryMoveWorker(animator, pref) )
|
||||
return false;
|
||||
//
|
||||
// Select the box.
|
||||
//
|
||||
animator.select(xy);
|
||||
}
|
||||
//
|
||||
// Done
|
||||
//
|
||||
return true;
|
||||
}
|
||||
}
|
||||
176
app/src/main/java/org/vostan/banvor/model/PuzzleRouteFinder.java
Normal file
176
app/src/main/java/org/vostan/banvor/model/PuzzleRouteFinder.java
Normal file
@@ -0,0 +1,176 @@
|
||||
package org.vostan.banvor.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Vector;
|
||||
|
||||
public class PuzzleRouteFinder {
|
||||
protected Puzzle puzzle = null;
|
||||
protected int [] moves;
|
||||
protected Vector<XYPair> setOfCells = new Vector<XYPair>();
|
||||
|
||||
public PuzzleRouteFinder(Puzzle p) {
|
||||
puzzle = p;
|
||||
moves = new int[puzzle.getColumnCount() * puzzle.getRowCount()];
|
||||
setOfCells.ensureCapacity(puzzle.getColumnCount() * puzzle.getRowCount());
|
||||
}
|
||||
|
||||
protected final int getMoves( XYPair xy )
|
||||
{
|
||||
return moves[puzzle.getIndex(xy)];
|
||||
}
|
||||
protected void setMoves( XYPair xy, int v )
|
||||
{
|
||||
moves[puzzle.getIndex(xy)]=v;
|
||||
}
|
||||
private boolean setMovesIfGreater( XYPair xy, int l )
|
||||
{
|
||||
//
|
||||
// If out of borders then nothing to do.
|
||||
//
|
||||
if ( !puzzle.isValid(xy) )
|
||||
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(xy) > l && puzzle.isEmpty(puzzle.getSym(xy)))
|
||||
{
|
||||
setMoves(xy,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;
|
||||
setOfCells.removeAllElements();
|
||||
//
|
||||
// Set the seed.
|
||||
//
|
||||
setMoves(puzzle.getWorker(),0);
|
||||
setOfCells.add(puzzle.getWorker());
|
||||
//
|
||||
// Now on each loop pop one cell from the list and calculate the
|
||||
// distance of cell around that cell.
|
||||
//
|
||||
while ( front < setOfCells.size() )
|
||||
{
|
||||
//
|
||||
// Pop the cell.
|
||||
//
|
||||
XYPair xy = setOfCells.elementAt(front++);
|
||||
//
|
||||
// Increase the length of cells all around given cell and push
|
||||
// them into the list.
|
||||
//
|
||||
int l = getMoves(xy)+1;
|
||||
if ( setMovesIfGreater(xy.left(),l) ) {
|
||||
setOfCells.add(xy.left());
|
||||
}
|
||||
if ( setMovesIfGreater(xy.right(),l) ) {
|
||||
setOfCells.add(xy.right());
|
||||
}
|
||||
if ( setMovesIfGreater(xy.down(),l) ) {
|
||||
setOfCells.add(xy.down());
|
||||
}
|
||||
if ( setMovesIfGreater(xy.up(),l) ) {
|
||||
setOfCells.add(xy.up());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final int stepsAway( XYPair xy )
|
||||
{
|
||||
return getMoves(xy);
|
||||
}
|
||||
|
||||
public final boolean isAccessible( XYPair xy )
|
||||
{
|
||||
return puzzle.isValid(xy) && stepsAway(xy) != Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
public Vector<XYPair> getDirections( XYPair xy )
|
||||
{
|
||||
int away = stepsAway(xy);
|
||||
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.
|
||||
//
|
||||
Vector<XYPair> steps = new Vector<XYPair>(away);
|
||||
steps.add(xy);
|
||||
while ( steps.size() < away )
|
||||
{
|
||||
xy = steps.lastElement();
|
||||
int j = stepsAway(xy);
|
||||
if ( stepsAway(xy.left()) < j ) {
|
||||
steps.add(xy.left());
|
||||
}
|
||||
else if ( stepsAway(xy.right()) < j ) {
|
||||
steps.add(xy.right());
|
||||
}
|
||||
else if ( stepsAway(xy.down()) < j ) {
|
||||
steps.add(xy.down());
|
||||
}
|
||||
else if ( stepsAway(xy.up()) < j ) {
|
||||
steps.add(xy.up());
|
||||
}
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
|
||||
public XYPair [] getPushDirections( XYPair xy )
|
||||
{
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
XYPair sel = puzzle.getSelected();
|
||||
XYPair dist = XYPair.sub(xy, puzzle.getSelected());
|
||||
XYPair dir = XYPair.sub(puzzle.getSelected(), puzzle.getWorker());
|
||||
double scaliar = (double)(dist.x())*(double)(dir.x())
|
||||
+(double)(dist.y())*(double)(dir.y());
|
||||
int len = dist.l1_norm();
|
||||
if ( scaliar != (double)len )
|
||||
return null;
|
||||
//
|
||||
// Now check that all cell till x,y are free.
|
||||
//
|
||||
XYPair ixy = new XYPair(puzzle.getSelected());
|
||||
while ( !xy.isEqual(ixy) )
|
||||
{
|
||||
ixy = ixy.add(dir);
|
||||
if ( !puzzle.isEmpty(puzzle.getSym(ixy)) )
|
||||
return null;
|
||||
}
|
||||
//
|
||||
// Looks we could move the box till x,y. Create the steps array
|
||||
// and fill it with push steps.
|
||||
//
|
||||
XYPair steps[] = new XYPair[len];
|
||||
int i = 0;
|
||||
ixy.set(puzzle.getSelected());
|
||||
while ( !xy.isEqual(ixy) )
|
||||
{
|
||||
steps[i++] = ixy;
|
||||
ixy = ixy.add(dir);
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,8 @@ package org.vostan.banvor.model;
|
||||
|
||||
import static java.lang.Math.abs;
|
||||
|
||||
public class XYPair {
|
||||
public class XYPair
|
||||
{
|
||||
public static final XYPair UP = new XYPair(0, 1);
|
||||
public static final XYPair DOWN = new XYPair(0, -1);
|
||||
public static final XYPair LEFT = new XYPair(-1, 0);
|
||||
@@ -11,6 +12,11 @@ public class XYPair {
|
||||
private int _x = 0;
|
||||
private int _y = 0;
|
||||
|
||||
public XYPair(){
|
||||
_x = 0;
|
||||
_y = 0;
|
||||
}
|
||||
|
||||
public XYPair(int x, int y){
|
||||
_x = x;
|
||||
_y = y;
|
||||
@@ -29,6 +35,11 @@ public class XYPair {
|
||||
return _y;
|
||||
}
|
||||
|
||||
public void set(int x, int y){
|
||||
_x = x;
|
||||
_y = y;
|
||||
}
|
||||
|
||||
public void set(XYPair xy){
|
||||
_x = xy.x();
|
||||
_y = xy.y();
|
||||
|
||||
Reference in New Issue
Block a user