More refactoring: PuzzlyRouteFinder is seperated, Animator dependency is reversed.

This commit is contained in:
2021-10-17 21:30:02 +01:00
parent 56679bd1be
commit 0a4f131e2b
13 changed files with 589 additions and 567 deletions

View File

@@ -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
{

View File

@@ -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);
}
}

View File

@@ -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) );
}
}

View File

@@ -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,10 +47,11 @@ 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);
scaled = new ScaleGestureDetector( c, 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();

View File

@@ -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 );

View File

@@ -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.

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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 );
}

View File

@@ -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.
@@ -286,14 +270,14 @@ public class Puzzle
//
// Undo/Redo.
//
protected class State
{
public int [] board;
public XYPair worker = new XYPair(0,0);
public XYPair selected = new XYPair(0, 0);
public int bingos = 0;
public State(Puzzle puzzle)
{
board = puzzle.board.clone();
@@ -301,7 +285,7 @@ public class Puzzle
selected.set(puzzle.selected);
bingos = puzzle.bingos;
}
public void restore(Puzzle puzzle)
{
puzzle.board = board;
@@ -311,7 +295,7 @@ public class Puzzle
}
}
protected LinkedList<State> undoStack = new LinkedList<State>();
//
// Save this state to be able to restore later.
//
@@ -334,6 +318,6 @@ public class Puzzle
//
public final boolean isUndoable()
{
return undoStack.size() != 0;
return undoStack.size() != 0;
}
}

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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();