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.app.Application;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.util.Log;
import org.vostan.banvor.game.State; import org.vostan.banvor.game.State;
import org.vostan.banvor.model.PuzzleContainer; import org.vostan.banvor.game.PuzzleContainer;
import org.vostan.banvor.model.IPuzzleSource;
public class App extends Application public class App extends Application
{ {

View File

@@ -31,10 +31,10 @@ public class Animator implements Runnable
protected static abstract class XYAction extends Action protected static abstract class XYAction extends Action
{ {
protected XYPair _xy; protected XYPair _xy = new XYPair();
public XYAction( XYPair xy ){ 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.MotionEvent;
import android.view.ScaleGestureDetector; 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.Puzzle;
import org.vostan.banvor.model.XYPair; import org.vostan.banvor.model.XYPair;
@@ -29,12 +29,13 @@ public class PuzzleControl extends PuzzleView
{ {
protected GestureDetector simpled; protected GestureDetector simpled;
protected ScaleGestureDetector scaled; protected ScaleGestureDetector scaled;
protected PuzzleLogic logic; protected PuzzleChoreographer logic;
protected Animator animator; protected Animator animator;
protected PuzzleAnimator puzzleAnimator;
protected PuzzleControlLister lister; protected PuzzleControlLister lister;
protected float lastSpan; protected float lastSpan;
protected Point singleTapTile = new Point(); protected XYPair singleTapTile = new XYPair();
public interface PuzzleControlLister public interface PuzzleControlLister
{ {
@@ -46,9 +47,10 @@ public class PuzzleControl extends PuzzleView
public PuzzleControl(Context c, AttributeSet attributeSet) public PuzzleControl(Context c, AttributeSet attributeSet)
{ {
super(c,attributeSet); super(c,attributeSet);
logic = new PuzzleLogic(); logic = new PuzzleChoreographer();
animator = new Animator( this ); animator = new Animator( this );
animator.setAnimationLister(this); animator.setAnimationLister(this);
puzzleAnimator = new PuzzleAnimator(animator);
simpled = new GestureDetector( c, this ); simpled = new GestureDetector( c, this );
simpled.setOnDoubleTapListener(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 // If the tap is not on a valid tile then there is not point to
// continue. // continue.
// //
if ( !puzzle.isValid(new XYPair(singleTapTile.x, singleTapTile.y) )) if ( !puzzle.isValid(singleTapTile) )
return true; return true;
// //
// Create sequence of steps and then animate it. // Create sequence of steps and then animate it.
// //
if ( logic.createSteps(animator, singleTapTile.x, singleTapTile.y) ) if ( logic.createSteps(puzzleAnimator, singleTapTile))
{ {
puzzle.save(); puzzle.save();
animator.play(); 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 * Apply screen to board mapping and if the points are in the
* column,row range then return it. Otherwise return null. * 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 }; float [] scr_p = { scr_x, scr_y };
scr2col.mapPoints( scr_p ); 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 static org.vostan.banvor.App.TAG;
import android.util.Log; import android.util.Log;
import org.vostan.banvor.model.Puzzle;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;

View File

@@ -1,11 +1,14 @@
package org.vostan.banvor.model; package org.vostan.banvor.game;
import java.io.InputStream; import java.io.InputStream;
import java.io.IOException;
import static org.vostan.banvor.App.theApp; 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.R;
import org.vostan.banvor.model.IPuzzleSource;
import org.vostan.banvor.model.Puzzle;
public class PuzzleContainer implements IPuzzleSource public class PuzzleContainer implements IPuzzleSource
{ {

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 SELECTED = 0x40;
public final static int MAX_COUNT = 0x80-1; public final static int MAX_COUNT = 0x80-1;
private final static XYPair unselected = new XYPair(-1,-1); private final static XYPair UNSELECTED = new XYPair(-1,-1);
private final static XYPair zero = new XYPair(0,0);
private int [] board; private int [] board;
@@ -36,7 +35,7 @@ public class Puzzle
} }
public Puzzle(int cols, int rows, int [] b) public Puzzle(int cols, int rows, int [] b)
{ {
board_size.set(new XYPair(cols, rows)); board_size.set(cols, rows);
box_count = 0; box_count = 0;
bingos = 0; bingos = 0;
// board = new int[board_size.x()*board_size.y()]; // board = new int[board_size.x()*board_size.y()];
@@ -72,27 +71,17 @@ public class Puzzle
return MAX_COUNT; return MAX_COUNT;
} }
public final int getIndex( XYPair c ) public final int getIndex( final XYPair c ) {
{
return c.y()*board_size.x()+c.x(); return c.y()*board_size.x()+c.x();
} }
public final XYPair getXY( int idx ) public final XYPair getXY( int idx ) {
{ return new XYPair(idx % board_size.x(), idx / board_size.x());
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 int getSym( XYPair c ) { public final int getSym( XYPair c ) {
return board[getIndex(c)]; return board[getIndex(c)];
} }
public void setSym( XYPair xy, int v ) public void setSym( final XYPair xy, int v )
{ {
board[getIndex(xy)]=v; board[getIndex(xy)]=v;
} }
@@ -110,32 +99,27 @@ public class Puzzle
return (v & WORKER) != 0; return (v & WORKER) != 0;
} }
public final XYPair getWorker() public final XYPair getWorker() {
{ return new XYPair(worker);
return worker;
} }
public final boolean isSelected() public final boolean isSelected()
{ {
return !selected.isEqual(unselected); return !selected.isEqual(UNSELECTED);
} }
public final boolean isSelected( final XYPair s ) {
public final boolean isSelected( XYPair s )
{
return selected.isEqual(s); return selected.isEqual(s);
} }
public final XYPair getSelected() public final XYPair getSelected()
{ {
return selected; return new XYPair(selected);
} }
public final boolean isValid( XYPair xy ) public final boolean isValid( final XYPair xy ) {
{ return xy.isInside(XYPair.ZERO,board_size);
return xy.isInside(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; return isValid(xy) && XYPair.sub(worker, xy).l1_norm() == 1;
} }
@@ -149,7 +133,7 @@ public class Puzzle
// Move worker. // Move worker.
// //
private int worker_direction(XYPair xy) { private final int worker_direction(XYPair xy) {
// //
// Find direction to move. // Find direction to move.
// //
@@ -235,11 +219,11 @@ public class Puzzle
// //
setSym(worker,getSym(worker)&~SELECTED); setSym(worker,getSym(worker)&~SELECTED);
setSym(selected,getSym(selected)&~SELECTED); setSym(selected,getSym(selected)&~SELECTED);
selected.set(unselected); selected.set(UNSELECTED);
return true; return true;
} }
public boolean push(XYPair xy) public boolean push(final XYPair xy)
{ {
// //
// If not selected then do nothing. // If not selected then do nothing.

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; 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 UP = new XYPair(0, 1);
public static final XYPair DOWN = new XYPair(0, -1); public static final XYPair DOWN = new XYPair(0, -1);
public static final XYPair LEFT = new XYPair(-1, 0); public static final XYPair LEFT = new XYPair(-1, 0);
@@ -11,6 +12,11 @@ public class XYPair {
private int _x = 0; private int _x = 0;
private int _y = 0; private int _y = 0;
public XYPair(){
_x = 0;
_y = 0;
}
public XYPair(int x, int y){ public XYPair(int x, int y){
_x = x; _x = x;
_y = y; _y = y;
@@ -29,6 +35,11 @@ public class XYPair {
return _y; return _y;
} }
public void set(int x, int y){
_x = x;
_y = y;
}
public void set(XYPair xy){ public void set(XYPair xy){
_x = xy.x(); _x = xy.x();
_y = xy.y(); _y = xy.y();