diff --git a/app/src/main/java/org/vostan/banvor/App.java b/app/src/main/java/org/vostan/banvor/App.java index 326e21e..bc52a91 100644 --- a/app/src/main/java/org/vostan/banvor/App.java +++ b/app/src/main/java/org/vostan/banvor/App.java @@ -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 { diff --git a/app/src/main/java/org/vostan/banvor/board/Animator.java b/app/src/main/java/org/vostan/banvor/board/Animator.java index 8340be3..7945244 100644 --- a/app/src/main/java/org/vostan/banvor/board/Animator.java +++ b/app/src/main/java/org/vostan/banvor/board/Animator.java @@ -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); } } diff --git a/app/src/main/java/org/vostan/banvor/board/PuzzleAnimator.java b/app/src/main/java/org/vostan/banvor/board/PuzzleAnimator.java new file mode 100644 index 0000000..bfd0359 --- /dev/null +++ b/app/src/main/java/org/vostan/banvor/board/PuzzleAnimator.java @@ -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) ); + } + +} diff --git a/app/src/main/java/org/vostan/banvor/board/PuzzleControl.java b/app/src/main/java/org/vostan/banvor/board/PuzzleControl.java index 1d7c3db..12c7669 100644 --- a/app/src/main/java/org/vostan/banvor/board/PuzzleControl.java +++ b/app/src/main/java/org/vostan/banvor/board/PuzzleControl.java @@ -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(); diff --git a/app/src/main/java/org/vostan/banvor/board/PuzzleView.java b/app/src/main/java/org/vostan/banvor/board/PuzzleView.java index aef66c9..1242c89 100644 --- a/app/src/main/java/org/vostan/banvor/board/PuzzleView.java +++ b/app/src/main/java/org/vostan/banvor/board/PuzzleView.java @@ -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 ); diff --git a/app/src/main/java/org/vostan/banvor/model/PuzzleBinLoader.java b/app/src/main/java/org/vostan/banvor/game/PuzzleBinLoader.java similarity index 95% rename from app/src/main/java/org/vostan/banvor/model/PuzzleBinLoader.java rename to app/src/main/java/org/vostan/banvor/game/PuzzleBinLoader.java index e37f721..40664f1 100644 --- a/app/src/main/java/org/vostan/banvor/model/PuzzleBinLoader.java +++ b/app/src/main/java/org/vostan/banvor/game/PuzzleBinLoader.java @@ -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. diff --git a/app/src/main/java/org/vostan/banvor/model/PuzzleContainer.java b/app/src/main/java/org/vostan/banvor/game/PuzzleContainer.java similarity index 79% rename from app/src/main/java/org/vostan/banvor/model/PuzzleContainer.java rename to app/src/main/java/org/vostan/banvor/game/PuzzleContainer.java index 9ef24f9..7445599 100644 --- a/app/src/main/java/org/vostan/banvor/model/PuzzleContainer.java +++ b/app/src/main/java/org/vostan/banvor/game/PuzzleContainer.java @@ -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); diff --git a/app/src/main/java/org/vostan/banvor/game/PuzzleLogic.java b/app/src/main/java/org/vostan/banvor/game/PuzzleLogic.java deleted file mode 100644 index ad897ec..0000000 --- a/app/src/main/java/org/vostan/banvor/game/PuzzleLogic.java +++ /dev/null @@ -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 setOfCells = new Vector(); - - 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 dirs = getDirections(xy); - ListIterator 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 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 steps = new Vector(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; - } -} diff --git a/app/src/main/java/org/vostan/banvor/model/IPuzzleAnimator.java b/app/src/main/java/org/vostan/banvor/model/IPuzzleAnimator.java new file mode 100644 index 0000000..d5fb543 --- /dev/null +++ b/app/src/main/java/org/vostan/banvor/model/IPuzzleAnimator.java @@ -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 ); +} diff --git a/app/src/main/java/org/vostan/banvor/model/Puzzle.java b/app/src/main/java/org/vostan/banvor/model/Puzzle.java index ace2ba8..c8a11db 100644 --- a/app/src/main/java/org/vostan/banvor/model/Puzzle.java +++ b/app/src/main/java/org/vostan/banvor/model/Puzzle.java @@ -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 undoStack = new LinkedList(); - + // // 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; } } diff --git a/app/src/main/java/org/vostan/banvor/model/PuzzleChoreographer.java b/app/src/main/java/org/vostan/banvor/model/PuzzleChoreographer.java new file mode 100644 index 0000000..3a176c7 --- /dev/null +++ b/app/src/main/java/org/vostan/banvor/model/PuzzleChoreographer.java @@ -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 dirs = routeFinder.getDirections(xy); + ListIterator 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; + } +} diff --git a/app/src/main/java/org/vostan/banvor/model/PuzzleRouteFinder.java b/app/src/main/java/org/vostan/banvor/model/PuzzleRouteFinder.java new file mode 100644 index 0000000..e790598 --- /dev/null +++ b/app/src/main/java/org/vostan/banvor/model/PuzzleRouteFinder.java @@ -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 setOfCells = new Vector(); + + 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 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 steps = new Vector(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; + } + +} diff --git a/app/src/main/java/org/vostan/banvor/model/XYPair.java b/app/src/main/java/org/vostan/banvor/model/XYPair.java index 5234d2d..b5b1389 100644 --- a/app/src/main/java/org/vostan/banvor/model/XYPair.java +++ b/app/src/main/java/org/vostan/banvor/model/XYPair.java @@ -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();