Compare commits
5 Commits
0c0938d651
...
c172905788
| Author | SHA1 | Date | |
|---|---|---|---|
| c172905788 | |||
| 66d04087f8 | |||
| faa6ccc296 | |||
| 0a4f131e2b | |||
| 56679bd1be |
@@ -4,6 +4,9 @@ plugins {
|
||||
|
||||
apply plugin: "androidx.navigation.safeargs"
|
||||
|
||||
//This is needed for SVG->PNG
|
||||
//apply plugin: 'com.trello.victor'
|
||||
|
||||
//
|
||||
// Convert SVG images from art directory into PNG files.
|
||||
//
|
||||
@@ -19,7 +22,7 @@ apply plugin: "androidx.navigation.safeargs"
|
||||
//}
|
||||
//preBuild.dependsOn generateDrawablesFromArt
|
||||
|
||||
String gitVersion() {
|
||||
static String gitVersion() {
|
||||
def versionP = 'git describe --tags --long --dirty=-x --always --abbrev=8'
|
||||
.execute()
|
||||
versionP.waitFor()
|
||||
@@ -28,15 +31,15 @@ String gitVersion() {
|
||||
return versionP.text.trim()
|
||||
}
|
||||
|
||||
String gitHash() {
|
||||
def githashP = 'git rev-parse HEAD'.execute()
|
||||
githashP.waitFor()
|
||||
if (githashP.exitValue())
|
||||
static String gitHash() {
|
||||
def hashP = 'git rev-parse HEAD'.execute()
|
||||
hashP.waitFor()
|
||||
if (hashP.exitValue())
|
||||
throw new GradleException("Couldn't extract git_hash. Git exited unexpectedly.")
|
||||
return githashP.text.trim()
|
||||
return hashP.text.trim()
|
||||
}
|
||||
|
||||
Integer gitVersionCode() {
|
||||
static Integer gitVersionCode() {
|
||||
def gitP = 'git rev-list v2..HEAD --count'.execute()
|
||||
gitP.waitFor()
|
||||
if (gitP.exitValue())
|
||||
@@ -44,19 +47,26 @@ Integer gitVersionCode() {
|
||||
return gitP.text.trim().toInteger()
|
||||
}
|
||||
|
||||
String buildDate() {
|
||||
def builddate = new Date()
|
||||
return builddate.toString()
|
||||
static String buildDate() {
|
||||
def date = new Date()
|
||||
return date.toString()
|
||||
}
|
||||
|
||||
android {
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile file('/Users/vahagnk/devel/banvor/keys/signing_key')
|
||||
storeFile file('keys/signing_key')
|
||||
keyAlias 'key0'
|
||||
}
|
||||
}
|
||||
compileSdk 31
|
||||
|
||||
// sourceSets {
|
||||
// main {
|
||||
// svg.srcDir 'src/main/art'
|
||||
// }
|
||||
// }
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.vostan.banvor"
|
||||
minSdk 21
|
||||
@@ -106,6 +116,8 @@ dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
|
||||
|
||||
// This is for unit tests.
|
||||
testImplementation 'junit:junit:4.+'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -61,27 +61,24 @@ public class PuzzleBoardFragment extends Fragment
|
||||
public void onTouch() {
|
||||
}
|
||||
});
|
||||
binding.btnTest.setOnClickListener((View.OnClickListener) view4 -> {
|
||||
binding.btnTest.setOnClickListener((View.OnClickListener) v -> {
|
||||
createNextLevelDialog((d, w) -> {
|
||||
initAndShowCurrentPuzzle();
|
||||
}).show();
|
||||
});
|
||||
binding.btnPrev.setOnClickListener((View.OnClickListener) view1 -> {
|
||||
binding.btnPrev.setOnClickListener((View.OnClickListener) v -> {
|
||||
gameState.setCurrentLevel(gameState.getCurrentLevel()-1);
|
||||
PuzzleBoardFragment.this.initAndShowCurrentPuzzle();
|
||||
});
|
||||
binding.btnNext.setOnClickListener((View.OnClickListener) view2 -> {
|
||||
binding.btnNext.setOnClickListener((View.OnClickListener) v -> {
|
||||
gameState.setCurrentLevel(gameState.getCurrentLevel()+1);
|
||||
PuzzleBoardFragment.this.initAndShowCurrentPuzzle();
|
||||
});
|
||||
binding.btnReset.setOnClickListener((View.OnClickListener) view3 -> {
|
||||
binding.btnReset.setOnClickListener((View.OnClickListener) v -> {
|
||||
PuzzleBoardFragment.this.initAndShowCurrentPuzzle();
|
||||
});
|
||||
binding.btnUndo.setOnClickListener((View.OnClickListener) view4 -> {
|
||||
createNextLevelDialog((d, w) -> {
|
||||
gameState.advanceCurrentLevel();
|
||||
initAndShowCurrentPuzzle();
|
||||
}).show();
|
||||
binding.btnUndo.setOnClickListener((View.OnClickListener) v -> {
|
||||
binding.gameBoard.undoLastMove();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -99,17 +96,6 @@ public class PuzzleBoardFragment extends Fragment
|
||||
//
|
||||
// Bring a dialog for user to choose to continue or stop.
|
||||
//
|
||||
// DialogFragment df = new DialogFragment() {
|
||||
// public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
// return createNextLevelDialog(new DialogInterface.OnClickListener(){
|
||||
// public void onClick(DialogInterface d, int w){
|
||||
// gameState.advanceCurrentLevel();
|
||||
// initAndShowCurrentPuzzle();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
// df.show(getChildFragmentManager(), null);
|
||||
createNextLevelDialog((d, w) -> {
|
||||
gameState.advanceCurrentLevel();
|
||||
initAndShowCurrentPuzzle();
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
package org.vostan.banvor.board;
|
||||
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
||||
import org.vostan.banvor.model.XYPair;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
@@ -28,21 +31,17 @@ public class Animator implements Runnable
|
||||
|
||||
protected static abstract class XYAction extends Action
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
protected XYPair _xy = new XYPair();
|
||||
|
||||
public XYAction( int _x, int _y )
|
||||
{
|
||||
x = _x;
|
||||
y = _y;
|
||||
public XYAction( XYPair xy ){
|
||||
_xy.set(xy);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Move extends XYAction
|
||||
{
|
||||
public Move( int x, int y )
|
||||
{
|
||||
super(x,y);
|
||||
public Move( XYPair xy ){
|
||||
super(xy);
|
||||
}
|
||||
|
||||
public void intermediate( Animator ap, int step )
|
||||
@@ -50,16 +49,15 @@ public class Animator implements Runnable
|
||||
|
||||
public void last( Animator ap )
|
||||
{
|
||||
ap.view.getPuzzle().walk(x,y);
|
||||
ap.view.getPuzzle().walk(_xy);
|
||||
ap.view.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Push extends XYAction
|
||||
{
|
||||
public Push( int x, int y )
|
||||
{
|
||||
super(x,y);
|
||||
public Push( XYPair xy ){
|
||||
super(xy);
|
||||
}
|
||||
|
||||
public void intermediate( Animator ap, int step )
|
||||
@@ -68,16 +66,15 @@ public class Animator implements Runnable
|
||||
|
||||
public void last( Animator ap )
|
||||
{
|
||||
ap.view.getPuzzle().push(x,y);
|
||||
ap.view.getPuzzle().push(_xy);
|
||||
ap.view.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Select extends XYAction
|
||||
{
|
||||
public Select(int x, int y )
|
||||
{
|
||||
super(x,y);
|
||||
public Select( XYPair xy ){
|
||||
super(xy);
|
||||
}
|
||||
|
||||
public void intermediate( Animator ap, int step )
|
||||
@@ -86,7 +83,7 @@ public class Animator implements Runnable
|
||||
|
||||
public void last( Animator ap )
|
||||
{
|
||||
ap.view.getPuzzle().select(x,y);
|
||||
ap.view.getPuzzle().select(_xy);
|
||||
ap.view.invalidate();
|
||||
}
|
||||
}
|
||||
@@ -108,9 +105,8 @@ public class Animator implements Runnable
|
||||
|
||||
public static class NoMove extends XYAction
|
||||
{
|
||||
public NoMove( int x, int y )
|
||||
{
|
||||
super(x,y);
|
||||
public NoMove( XYPair xy ){
|
||||
super(xy);
|
||||
}
|
||||
|
||||
public void intermediate( Animator ap, int step )
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
package org.vostan.banvor.board;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.vostan.banvor.App;
|
||||
|
||||
import static org.vostan.banvor.App.theApp;
|
||||
import org.vostan.banvor.R;
|
||||
import org.vostan.banvor.model.Puzzle;
|
||||
|
||||
public class PlayActivity extends FragmentActivity
|
||||
implements PuzzleControl.PuzzleControlLister
|
||||
{
|
||||
protected final static int RESET_ITEM = 1;
|
||||
protected final static int UNDO_ITEM = 2;
|
||||
|
||||
public Puzzle puzzle = null;
|
||||
public PuzzleControl puzzle_view;
|
||||
View title_view;
|
||||
public TextView title_text;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
//
|
||||
// No title.
|
||||
//
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
//
|
||||
// Create and set up the puzzle_view.
|
||||
//
|
||||
// puzzle_view = new PuzzleControl(this, new AttributeSet() {
|
||||
// });
|
||||
setContentView( puzzle_view );
|
||||
puzzle_view.setPuzzleControlLister( this );
|
||||
//
|
||||
// Create action bar.
|
||||
//
|
||||
FrameLayout rootLayout = (FrameLayout)findViewById(android.R.id.content);
|
||||
View.inflate(this, R.layout.fragment_puzzle_board, rootLayout);
|
||||
title_text = (TextView)findViewById(R.id.level_text);
|
||||
// title_view = findViewById(R.id.puzzle_view_title_layout);
|
||||
//
|
||||
// Load the puzzle.
|
||||
//
|
||||
loadCurrentPuzzle();
|
||||
}
|
||||
|
||||
public void onSolved()
|
||||
{
|
||||
//
|
||||
// Advance current level and achieved level.
|
||||
//
|
||||
final int nextl = theApp().state().getCurrentLevel()+1;
|
||||
// theApp().state().(nextl);
|
||||
theApp().state().setCurrentLevel(nextl);
|
||||
//
|
||||
// Bring a dialog for user to choose to continue or stop.
|
||||
//
|
||||
DialogFragment df = new DialogFragment()
|
||||
{
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||
{
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setTitle("Congratulations! You Won!");
|
||||
builder.setMessage( "Would you like to try the next puzzle?" );
|
||||
builder.setNegativeButton("Enough",
|
||||
new DialogInterface.OnClickListener()
|
||||
{
|
||||
public void onClick(DialogInterface d, int w)
|
||||
{
|
||||
finish();
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton("Please",
|
||||
new DialogInterface.OnClickListener()
|
||||
{
|
||||
public void onClick(DialogInterface d, int w)
|
||||
{
|
||||
loadCurrentPuzzle();
|
||||
puzzle_view.invalidate();
|
||||
}
|
||||
});
|
||||
// Create the AlertDialog object and return it
|
||||
Dialog dlg = builder.create();
|
||||
dlg.setCancelable(false);
|
||||
dlg.setCanceledOnTouchOutside(false);
|
||||
return dlg;
|
||||
}
|
||||
};
|
||||
df.setCancelable(false);
|
||||
df.show(getSupportFragmentManager(), null);
|
||||
//dlg.show();
|
||||
}
|
||||
|
||||
public void onLongPress()
|
||||
{
|
||||
if ( title_view.getVisibility() != View.VISIBLE )
|
||||
showTitle();
|
||||
}
|
||||
|
||||
public void onTouch()
|
||||
{
|
||||
if ( title_view.getVisibility() == View.VISIBLE )
|
||||
hideTitle();
|
||||
}
|
||||
|
||||
public void showTitle()
|
||||
{
|
||||
Animation anim = AnimationUtils.loadAnimation(this, R.animator.puzzle_action_bar_enter);
|
||||
title_view.startAnimation(anim);
|
||||
title_view.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void hideTitle()
|
||||
{
|
||||
Animation anim = AnimationUtils.loadAnimation(this, R.animator.puzzle_action_bar_exit);
|
||||
title_view.startAnimation(anim);
|
||||
title_view.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
public boolean onReset( View v )
|
||||
{
|
||||
while ( puzzle.isUndoable() )
|
||||
puzzle.restore();
|
||||
puzzle_view.invalidate();
|
||||
return true;
|
||||
}
|
||||
//
|
||||
// Go to previouse puzzle.
|
||||
//
|
||||
public boolean onPrev( View v )
|
||||
{
|
||||
//
|
||||
// If current level is less than achived level the move to next
|
||||
// puzzle.
|
||||
//
|
||||
if ( theApp().state().getCurrentLevel() > App.MIN_LEVEL )
|
||||
{
|
||||
theApp().state().setCurrentLevel( theApp().state().getCurrentLevel()-1 );
|
||||
loadCurrentPuzzle();
|
||||
puzzle_view.invalidate();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
//
|
||||
// Go to next puzzle.
|
||||
//
|
||||
public boolean onNext( View v )
|
||||
{
|
||||
//
|
||||
// If current level is less than achived level the move to next
|
||||
// puzzle.
|
||||
//
|
||||
if ( theApp().state().getCurrentLevel() < theApp().state().getHighestSolvedLevel() )
|
||||
{
|
||||
theApp().state().setCurrentLevel( theApp().state().getCurrentLevel()+1 );
|
||||
loadCurrentPuzzle();
|
||||
puzzle_view.invalidate();
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
//
|
||||
// Undo last action.
|
||||
//
|
||||
public boolean onUndo( View v )
|
||||
{
|
||||
if ( puzzle.isUndoable() )
|
||||
{
|
||||
puzzle.restore();
|
||||
puzzle_view.invalidate();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private void loadCurrentPuzzle()
|
||||
{
|
||||
puzzle = theApp().state().getCurrentPuzzle();
|
||||
puzzle_view.setPuzzle(puzzle);
|
||||
updateTitle();
|
||||
}
|
||||
public void updateTitle()
|
||||
{
|
||||
String title = "Level " + new Integer(theApp().state().getCurrentLevel()).toString();
|
||||
setTitle(title);
|
||||
}
|
||||
public void setTitle(CharSequence title)
|
||||
{
|
||||
super.setTitle(title);
|
||||
if (title_text != null) {
|
||||
title_text.setText(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 moveImpossible(XYPair xy )
|
||||
{
|
||||
animator.queue( new Animator.NoMove(xy) );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,14 +5,14 @@
|
||||
package org.vostan.banvor.board;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.util.AttributeSet;
|
||||
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;
|
||||
|
||||
//import android.support.v4.view.GestureDetectorCompat;
|
||||
|
||||
@@ -28,12 +28,13 @@ public class PuzzleControl extends PuzzleView
|
||||
{
|
||||
protected GestureDetector simpled;
|
||||
protected ScaleGestureDetector scaled;
|
||||
protected PuzzleLogic logic;
|
||||
protected PuzzleChoreographer choregrapher;
|
||||
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
|
||||
{
|
||||
@@ -45,9 +46,10 @@ public class PuzzleControl extends PuzzleView
|
||||
public PuzzleControl(Context c, AttributeSet attributeSet)
|
||||
{
|
||||
super(c,attributeSet);
|
||||
logic = new PuzzleLogic();
|
||||
choregrapher = new PuzzleChoreographer();
|
||||
animator = new Animator( this );
|
||||
animator.setAnimationLister(this);
|
||||
puzzleAnimator = new PuzzleAnimator(animator);
|
||||
|
||||
simpled = new GestureDetector( c, this );
|
||||
simpled.setOnDoubleTapListener(this);
|
||||
@@ -57,7 +59,7 @@ public class PuzzleControl extends PuzzleView
|
||||
public void setPuzzle( Puzzle p )
|
||||
{
|
||||
super.setPuzzle(p);
|
||||
logic.setPuzzle(p);
|
||||
choregrapher.setPuzzle(p);
|
||||
}
|
||||
|
||||
public void setPuzzleControlLister( PuzzleControlLister l )
|
||||
@@ -65,6 +67,13 @@ public class PuzzleControl extends PuzzleView
|
||||
lister = l;
|
||||
}
|
||||
|
||||
public void undoLastMove(){
|
||||
if ( getPuzzle().isUndoable() ){
|
||||
getPuzzle().restore();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event)
|
||||
{
|
||||
@@ -117,12 +126,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(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 ( choregrapher.createSteps(puzzleAnimator, singleTapTile))
|
||||
{
|
||||
puzzle.save();
|
||||
animator.play();
|
||||
|
||||
@@ -18,6 +18,7 @@ import static java.lang.Math.*;
|
||||
import org.vostan.banvor.model.Puzzle;
|
||||
import static org.vostan.banvor.model.Puzzle.*;
|
||||
import org.vostan.banvor.R;
|
||||
import org.vostan.banvor.model.XYPair;
|
||||
|
||||
/**
|
||||
*/
|
||||
@@ -241,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 );
|
||||
@@ -381,7 +382,7 @@ public class PuzzleView extends View
|
||||
//tile_hlpr.roundOut(tile_hlpr);
|
||||
//tile_hlpr.right -=1;
|
||||
//tile_hlpr.bottom -=1;
|
||||
int sym = puzzle.getSym(x,y);
|
||||
int sym = puzzle.getSym(new XYPair(x,y));
|
||||
if ( !Puzzle.hasWorker(sym) )
|
||||
{
|
||||
canvas.drawBitmap(tile[sym],
|
||||
@@ -403,30 +404,30 @@ public class PuzzleView extends View
|
||||
Bitmap normal_worker = ((sym&SELECTED)!=0) ?
|
||||
tile[WORKER|SELECTED] : tile[WORKER];
|
||||
int direction = sym & WORKER_MASK;
|
||||
if ( direction == WORKER_NORTH )
|
||||
{
|
||||
canvas.translate(tile_dest.centerX(), tile_dest.centerY());
|
||||
canvas.rotate(180+rotated);
|
||||
canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
|
||||
}
|
||||
else if ( direction == WORKER_SOUTH )
|
||||
if ( direction == WORKER_FACE_DOWN)
|
||||
{
|
||||
canvas.translate(tile_dest.centerX(), tile_dest.centerY());
|
||||
canvas.rotate(0+rotated);
|
||||
canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
|
||||
}
|
||||
else if ( direction == WORKER_WEST )
|
||||
else if ( direction == WORKER_FACE_UP)
|
||||
{
|
||||
canvas.translate(tile_dest.centerX(), tile_dest.centerY());
|
||||
canvas.rotate(90+rotated);
|
||||
canvas.rotate(180+rotated);
|
||||
canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
|
||||
}
|
||||
else if ( direction == WORKER_EAST )
|
||||
else if ( direction == WORKER_FACE_LEFT)
|
||||
{
|
||||
canvas.translate(tile_dest.centerX(), tile_dest.centerY());
|
||||
canvas.rotate(-90+rotated);
|
||||
canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
|
||||
}
|
||||
else if ( direction == WORKER_FACE_RIGHT)
|
||||
{
|
||||
canvas.translate(tile_dest.centerX(), tile_dest.centerY());
|
||||
canvas.rotate(90+rotated);
|
||||
canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
|
||||
}
|
||||
canvas.drawBitmap(normal_worker,
|
||||
tile_src,tile_dest,
|
||||
mPaint);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.vostan.banvor.game;
|
||||
|
||||
import org.vostan.banvor.model.Puzzle;
|
||||
|
||||
public interface IPuzzleSource {
|
||||
public int getCount();
|
||||
public Puzzle getPuzzle(int i );
|
||||
}
|
||||
113
app/src/main/java/org/vostan/banvor/game/PuzzleBinLoader.java
Normal file
113
app/src/main/java/org/vostan/banvor/game/PuzzleBinLoader.java
Normal file
@@ -0,0 +1,113 @@
|
||||
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;
|
||||
|
||||
public class PuzzleBinLoader {
|
||||
|
||||
/*
|
||||
* Load puzzle from the puzzle.bin.
|
||||
*/
|
||||
public static Puzzle loadNthPuzzle(InputStream is, int i )
|
||||
{try{
|
||||
//
|
||||
// Read amount of puzzles.
|
||||
//
|
||||
int count = read_i32(is);
|
||||
if ( i >= count )
|
||||
return null;
|
||||
//
|
||||
// Read the offset.
|
||||
//
|
||||
is.skip( i*4 );
|
||||
int offset = read_i32(is);
|
||||
//
|
||||
// Jump to the offset and read the puzzle.
|
||||
//
|
||||
is.skip( offset - 4 - (i+1)*4);
|
||||
Puzzle p = loadPuzzle(is);
|
||||
//
|
||||
// Finally return the puzzle and we are done.
|
||||
//
|
||||
return p;
|
||||
}
|
||||
catch ( java.lang.Exception e )
|
||||
{
|
||||
//Log.d(TAG, "Exception: " + e.getMessage() );
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}}
|
||||
//
|
||||
// Retrive amount of puzzles.
|
||||
//
|
||||
public static int getCount(InputStream is)
|
||||
{try{
|
||||
int count = read_i32(is);
|
||||
return count;
|
||||
}
|
||||
catch ( java.lang.Exception e )
|
||||
{
|
||||
//Log.d(TAG, "Exception: " + e.getMessage() );
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}}
|
||||
|
||||
private static Puzzle loadPuzzle( InputStream is )
|
||||
{ try{
|
||||
int cols = is.read();
|
||||
int rows = is.read();
|
||||
|
||||
int [] board = new int[cols*rows];
|
||||
byte [] b = new byte [cols*rows];
|
||||
is.read( b, 0, cols*rows);
|
||||
for( int i = 0; i < b.length; ++i )
|
||||
{
|
||||
switch( b[i] )
|
||||
{
|
||||
case 0:
|
||||
board[i] = Puzzle.FLOOR;
|
||||
break;
|
||||
case 1:
|
||||
board[i] = Puzzle.WALL;
|
||||
break;
|
||||
case 2:
|
||||
board[i] = Puzzle.BOX;
|
||||
break;
|
||||
case 3:
|
||||
board[i] = Puzzle.GOAL;
|
||||
break;
|
||||
case 4:
|
||||
board[i] = Puzzle.BINGO;
|
||||
break;
|
||||
case 5:
|
||||
board[i] = Puzzle.WORKER;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return new Puzzle(cols, rows, board);
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
Log.d( TAG, "load()", e);
|
||||
return null;
|
||||
}}
|
||||
|
||||
//
|
||||
// Helper function to read little endian 32bit integer.
|
||||
//
|
||||
private static int read_i32( InputStream is ) throws IOException
|
||||
{
|
||||
int i = is.read();
|
||||
i |= is.read() << 8;
|
||||
i |= is.read() << 16;
|
||||
i |= is.read() << 24;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.vostan.banvor.game;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.vostan.banvor.App.theApp;
|
||||
|
||||
import org.vostan.banvor.R;
|
||||
import org.vostan.banvor.model.Puzzle;
|
||||
|
||||
public class PuzzleContainer implements IPuzzleSource
|
||||
{
|
||||
protected int count;
|
||||
|
||||
/*
|
||||
* Load puzzle from the puzzle.bin.
|
||||
*/
|
||||
public Puzzle getPuzzle(int i )
|
||||
{
|
||||
InputStream is = theApp().getResources().openRawResource(R.raw.puzzles);
|
||||
return PuzzleBinLoader.loadNthPuzzle(is, i);
|
||||
}
|
||||
//
|
||||
// Retrieve amount of puzzles.
|
||||
//
|
||||
public int getCount()
|
||||
{
|
||||
if ( count == 0 )
|
||||
{
|
||||
InputStream is = theApp().getResources().openRawResource(R.raw.puzzles);
|
||||
count = PuzzleBinLoader.getCount(is);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -1,541 +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 org.vostan.banvor.board.Animator;
|
||||
import org.vostan.banvor.model.Puzzle;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class PuzzleLogic
|
||||
{
|
||||
//protected PlayActivity activity;
|
||||
protected Puzzle puzzle = null;
|
||||
protected int [] moves;
|
||||
protected int [] setOfCells;
|
||||
|
||||
public PuzzleLogic()
|
||||
{}
|
||||
|
||||
public void setPuzzle( Puzzle p )
|
||||
{
|
||||
puzzle = p;
|
||||
moves = new int[puzzle.getColumnCount()*puzzle.getRowCount()];
|
||||
setOfCells = new int[2*puzzle.getColumnCount()*puzzle.getRowCount()];
|
||||
}
|
||||
|
||||
public boolean createSteps(Animator animator, final int x, final int y )
|
||||
{
|
||||
//
|
||||
// Check that the x,y are valid.
|
||||
//
|
||||
if ( !puzzle.isValid(x, y) )
|
||||
return false;
|
||||
//
|
||||
// Now check what tile was tapped.
|
||||
// If the tapped is a floor then ...
|
||||
//
|
||||
int tile = puzzle.getSym( x, y);
|
||||
if ( Puzzle.isEmpty(tile) )
|
||||
{
|
||||
//
|
||||
// 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, x, y) )
|
||||
return true;
|
||||
//
|
||||
// Either nothing was selected or the box cannot be moved to
|
||||
// tapped location. Try move worker alone.
|
||||
//
|
||||
if ( tryMoveWorker( animator, x, y ) )
|
||||
return true;
|
||||
//
|
||||
// Show that action is not allowed.
|
||||
//
|
||||
undoable( animator, x, y );
|
||||
}
|
||||
//
|
||||
// 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, x, y) )
|
||||
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(x,y) )
|
||||
{
|
||||
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, x, y ) )
|
||||
return true;
|
||||
//
|
||||
// Show that action is not allowed if reached till here.
|
||||
//
|
||||
undoable( animator, x, y );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Routes to accessible cells from where worker stands.
|
||||
//
|
||||
protected boolean tryMoveWorker( Animator animator, int x, int y )
|
||||
{
|
||||
//
|
||||
// If the filed is not accessable then move failed.
|
||||
//
|
||||
if ( !isAccessible(x,y) )
|
||||
return false;
|
||||
//
|
||||
// First unselect box.
|
||||
//
|
||||
if ( puzzle.isSelected() )
|
||||
unselect( animator );
|
||||
//
|
||||
// Get directions and queue moves accordingly.
|
||||
//
|
||||
int dirs[] = getDirections(x, y);
|
||||
for ( int i = 0; i < dirs.length; i+=2 )
|
||||
move( animator, dirs[i],dirs[i+1] );
|
||||
//
|
||||
// Done.
|
||||
//
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean tryMoveBox( Animator animator, int x, int y )
|
||||
{
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
int bx = puzzle.getSelectedX();
|
||||
int by = puzzle.getSelectedY();
|
||||
int dx = x-bx;
|
||||
int dy = y-by;
|
||||
if ( dx != 0 && dy != 0 )
|
||||
return false;
|
||||
//
|
||||
// There is no point to continue also in case if the asked cell
|
||||
// is the box.
|
||||
//
|
||||
if ( dx == 0 && dy == 0 )
|
||||
return false;
|
||||
//
|
||||
// Now find the desired place for the worker to start push this
|
||||
// box.
|
||||
//
|
||||
int wx = bx;
|
||||
int wy = by;
|
||||
if ( x < bx )
|
||||
++wx;
|
||||
else if ( x > bx )
|
||||
--wx;
|
||||
else if ( y < by )
|
||||
++wy;
|
||||
else
|
||||
--wy;
|
||||
//
|
||||
// Check if the desired place for the worker is accessable? If not
|
||||
// then there is no point to continue.
|
||||
//
|
||||
if ( !isAccessible(wx, wy) )
|
||||
return false;
|
||||
//
|
||||
// Now check that all cell till x,y are empty and that we can
|
||||
// push box till there.
|
||||
//
|
||||
int sx = bx-wx;
|
||||
int sy = by-wy;
|
||||
int ix = bx;
|
||||
int iy = by;
|
||||
while ( x != ix || y != iy )
|
||||
{
|
||||
ix+=sx;
|
||||
iy+=sy;
|
||||
int v = puzzle.getSym(ix,iy);
|
||||
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 ( wx != puzzle.getWorkerX() || wy != puzzle.getWorkerY() )
|
||||
{
|
||||
tryMoveWorker( animator, wx, wy );
|
||||
select( animator, bx, by );
|
||||
}
|
||||
//
|
||||
// Now create the steps to push the box.
|
||||
//
|
||||
ix = bx;
|
||||
iy = by;
|
||||
while ( x != ix || y != iy )
|
||||
{
|
||||
push( animator, ix, iy );
|
||||
ix+=sx;
|
||||
iy+=sy;
|
||||
}
|
||||
//
|
||||
// Done
|
||||
//
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean trySelectBox( Animator animator, int x, int y )
|
||||
{
|
||||
int north = puzzle.getSym(x,y-1);
|
||||
int south = puzzle.getSym(x,y+1);
|
||||
int west = puzzle.getSym(x-1,y);
|
||||
int east = puzzle.getSym(x+1,y);
|
||||
//
|
||||
// First check if there is a worker in a nighbour cell. If
|
||||
// yes then simplly select the box. If 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(x,y) )
|
||||
{
|
||||
if ( puzzle.isSelected() )
|
||||
unselect( animator );
|
||||
select( animator, x, y );
|
||||
}
|
||||
}
|
||||
//
|
||||
// Otherwise, check which is of the cells is in closes walking
|
||||
// distance and move worker to that cell, then select.
|
||||
//
|
||||
else
|
||||
{
|
||||
int pref_x = -1;
|
||||
int pref_y = -1;
|
||||
int shortest = Integer.MAX_VALUE;
|
||||
if ( Puzzle.isEmpty( north )
|
||||
&& shortest > stepsAway( x, y-1) )
|
||||
{
|
||||
shortest = stepsAway( x, y-1);
|
||||
pref_x = x;
|
||||
pref_y = y-1;
|
||||
}
|
||||
if ( Puzzle.isEmpty( south )
|
||||
&& shortest > stepsAway( x, y+1) )
|
||||
{
|
||||
shortest = stepsAway( x, y+1);
|
||||
pref_x = x;
|
||||
pref_y = y+1;
|
||||
}
|
||||
if ( Puzzle.isEmpty( west )
|
||||
&& shortest > stepsAway( x-1, y) )
|
||||
{
|
||||
shortest = stepsAway( x-1, y);
|
||||
pref_x = x-1;
|
||||
pref_y = y;
|
||||
}
|
||||
if ( Puzzle.isEmpty( east )
|
||||
&& shortest > stepsAway( x+1, y) )
|
||||
{
|
||||
shortest = stepsAway( x+1, y);
|
||||
pref_x = x+1;
|
||||
pref_y = y;
|
||||
}
|
||||
//
|
||||
// Move the worker to the direction. If we cannot move worker
|
||||
// next to the box then we cannot select it.
|
||||
//
|
||||
if ( !puzzle.isValid(pref_x, pref_y)
|
||||
|| !tryMoveWorker(animator, pref_x, pref_y) )
|
||||
return false;
|
||||
//
|
||||
// Select the box.
|
||||
//
|
||||
select(animator,x,y);
|
||||
}
|
||||
//
|
||||
// Done
|
||||
//
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void move( Animator animator, int x, int y )
|
||||
{
|
||||
animator.queue( new Animator.Move(x,y) );
|
||||
}
|
||||
|
||||
protected void push( Animator animator, int x, int y )
|
||||
{
|
||||
animator.queue( new Animator.Push(x,y) );
|
||||
}
|
||||
|
||||
protected void select( Animator animator, int x, int y )
|
||||
{
|
||||
animator.queue( new Animator.Select(x,y) );
|
||||
}
|
||||
|
||||
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, int x, int y )
|
||||
{
|
||||
animator.queue( new Animator.NoMove(x,y) );
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Routes to accessible cells from where worker stands.
|
||||
//
|
||||
|
||||
protected final int getMoves( int x, int y )
|
||||
{
|
||||
return moves[puzzle.getIndex(x,y)];
|
||||
}
|
||||
protected void setMoves( int x, int y, int v )
|
||||
{
|
||||
moves[puzzle.getIndex(x,y)]=v;
|
||||
}
|
||||
private boolean setMovesIfGreater( int x, int y, int l )
|
||||
{
|
||||
//
|
||||
// If out of borders then nothing to do.
|
||||
//
|
||||
if ( y < 0 || y >= puzzle.getRowCount()
|
||||
|| x < 0 || x >= puzzle.getColumnCount() )
|
||||
return false;
|
||||
//
|
||||
// Check if the cell is a floor or goal. If yes then set the l
|
||||
// if current value is greater than l.
|
||||
//
|
||||
if ( getMoves(x,y) > l && puzzle.isEmpty(puzzle.getSym(x,y)))
|
||||
{
|
||||
setMoves(x,y,l);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public void calcMoves()
|
||||
{
|
||||
//
|
||||
// Erase moves array.
|
||||
//
|
||||
Arrays.fill(moves,Integer.MAX_VALUE);
|
||||
//
|
||||
// For the beginning there is no cell in the list.
|
||||
//
|
||||
int front = 0;
|
||||
int last = 0;
|
||||
//
|
||||
// Set the seed.
|
||||
//
|
||||
setMoves(puzzle.getWorkerX(),puzzle.getWorkerY(),0);
|
||||
setOfCells[last++] = puzzle.getWorkerX();
|
||||
setOfCells[last++] = puzzle.getWorkerY();
|
||||
//
|
||||
// Now on each loop pop one cell from the list and calculate the
|
||||
// distance of cell around that cell.
|
||||
//
|
||||
while ( front < last )
|
||||
{
|
||||
//
|
||||
// Pop the cell.
|
||||
//
|
||||
int x = setOfCells[front++];
|
||||
int y = setOfCells[front++];
|
||||
//
|
||||
// Increase the length of cells all around given cell and push
|
||||
// them into the list.
|
||||
//
|
||||
int l = getMoves(x,y)+1;
|
||||
if ( setMovesIfGreater(x-1,y,l) )
|
||||
{
|
||||
setOfCells[last++] = x-1;
|
||||
setOfCells[last++] = y;
|
||||
}
|
||||
if ( setMovesIfGreater(x+1,y,l) )
|
||||
{
|
||||
setOfCells[last++] = x+1;
|
||||
setOfCells[last++] = y;
|
||||
}
|
||||
if ( setMovesIfGreater(x,y-1,l) )
|
||||
{
|
||||
setOfCells[last++] = x;
|
||||
setOfCells[last++] = y-1;
|
||||
}
|
||||
if ( setMovesIfGreater(x,y+1,l) )
|
||||
{
|
||||
setOfCells[last++] = x;
|
||||
setOfCells[last++] = y+1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final int stepsAway( int x, int y )
|
||||
{
|
||||
return getMoves(x,y);
|
||||
}
|
||||
|
||||
public final boolean isAccessible( int x, int y )
|
||||
{
|
||||
return puzzle.isValid(x, y) && stepsAway(x, y) != Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
public int [] getDirections( int x, int y )
|
||||
{
|
||||
int away = stepsAway(x,y);
|
||||
if (away == Integer.MAX_VALUE)
|
||||
return null;
|
||||
//
|
||||
// Ok looks there is a routh to given cell. Now create an array
|
||||
// and fill in the step to get to the cell.
|
||||
//
|
||||
int [] steps = new int[2*away];
|
||||
int i = steps.length;
|
||||
steps[--i] = y;
|
||||
steps[--i] = x;
|
||||
while ( i > 0 )
|
||||
{
|
||||
x = steps[i];
|
||||
y = steps[i+1];
|
||||
int j = stepsAway(x,y);
|
||||
if ( stepsAway(x-1,y) < j )
|
||||
{
|
||||
steps[--i] = y;
|
||||
steps[--i] = x-1;
|
||||
}
|
||||
else if ( stepsAway(x+1,y) < j )
|
||||
{
|
||||
steps[--i] = y;
|
||||
steps[--i] = x+1;
|
||||
}
|
||||
else if ( stepsAway(x,y-1) < j )
|
||||
{
|
||||
steps[--i] = y-1;
|
||||
steps[--i] = x;
|
||||
}
|
||||
else if ( stepsAway(x,y+1) < j )
|
||||
{
|
||||
steps[--i] = y+1;
|
||||
steps[--i] = x;
|
||||
}
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
|
||||
public int [] getPushDirections( int x, int y )
|
||||
{
|
||||
//
|
||||
// The selected box can be moved only orthogonally.
|
||||
// Check that worker is on opposite side of the box.
|
||||
// Statement:
|
||||
// If scaliar product of selected->x,y and worker->selected is equal
|
||||
// to manhatten distance of x,y from the selected box then the worker
|
||||
// push direction is directed to x,y and is not opposit. In other words
|
||||
// selected->x,y and worker->selected are codirectional.
|
||||
//
|
||||
int selx = puzzle.getSelectedX();
|
||||
int sely = puzzle.getSelectedY();
|
||||
int xx = x-selx;
|
||||
int yy = y-sely;
|
||||
int sx = selx-puzzle.getWorkerX();
|
||||
int sy = sely-puzzle.getWorkerY();
|
||||
double scaliar = (double)(xx)*(double)(sx)
|
||||
+(double)(yy)*(double)(sy);
|
||||
int len = abs(xx)+abs(yy);
|
||||
if ( scaliar != (double)len )
|
||||
return null;
|
||||
//
|
||||
// Now check that all cell till x,y are free.
|
||||
//
|
||||
int ix = selx;
|
||||
int iy = sely;
|
||||
while ( x != ix || y != iy )
|
||||
{
|
||||
ix+=sx;
|
||||
iy+=sy;
|
||||
if ( !puzzle.isEmpty(puzzle.getSym(ix,iy)) )
|
||||
return null;
|
||||
}
|
||||
//
|
||||
// Looks we could move the box till x,y. Create the steps array
|
||||
// and fill it with push steps.
|
||||
//
|
||||
int steps[] = new int [2*len];
|
||||
int i = 0;
|
||||
ix = selx;
|
||||
iy = sely;
|
||||
while ( x != ix || y != iy )
|
||||
{
|
||||
steps[i++] = ix;
|
||||
steps[i++] = iy;
|
||||
ix+=sx;
|
||||
iy+=sy;
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.vostan.banvor.model.IPuzzleSource;
|
||||
import org.vostan.banvor.model.Puzzle;
|
||||
|
||||
public class State {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package org.vostan.banvor.model;
|
||||
|
||||
public class Coord {
|
||||
public int x;
|
||||
public int y;
|
||||
}
|
||||
@@ -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 moveImpossible(XYPair xy );
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package org.vostan.banvor.model;
|
||||
|
||||
public interface IPuzzleSource {
|
||||
public int getCount();
|
||||
public Puzzle getPuzzle( int i );
|
||||
}
|
||||
@@ -1,14 +1,9 @@
|
||||
package org.vostan.banvor.model;
|
||||
|
||||
//import java.lang.Exception;
|
||||
import java.io.*;
|
||||
import static java.lang.Math.*;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import static org.vostan.banvor.App.TAG;
|
||||
|
||||
public class Puzzle
|
||||
{
|
||||
public final static int FLOOR = 0x0;
|
||||
@@ -18,28 +13,44 @@ public class Puzzle
|
||||
public final static int BOX = 0x4;
|
||||
public final static int BINGO = BOX | GOAL;
|
||||
public final static int WORKER = 0x8;
|
||||
public final static int WORKER_NORTH = 0x00 | WORKER;
|
||||
public final static int WORKER_SOUTH = 0x10 | WORKER;
|
||||
public final static int WORKER_WEST = 0x20 | WORKER;
|
||||
public final static int WORKER_EAST = 0x30 | WORKER;
|
||||
public final static int WORKER_MASK = WORKER_EAST | WORKER_WEST | WORKER_SOUTH | WORKER_NORTH;
|
||||
public final static int WORKER_FACE_DOWN = 0x00 | WORKER;
|
||||
public final static int WORKER_FACE_UP = 0x10 | WORKER;
|
||||
public final static int WORKER_FACE_LEFT = 0x20 | WORKER;
|
||||
public final static int WORKER_FACE_RIGHT = 0x30 | WORKER;
|
||||
public final static int WORKER_MASK = WORKER_FACE_RIGHT | WORKER_FACE_LEFT | WORKER_FACE_UP | WORKER_FACE_DOWN;
|
||||
public final static int SELECTED = 0x40;
|
||||
public final static int MAX_COUNT = 0x80-1;
|
||||
|
||||
protected int columns;
|
||||
protected int rows;
|
||||
protected int box_count;
|
||||
private final static XYPair UNSELECTED = new XYPair(-1,-1);
|
||||
|
||||
protected int [] board;
|
||||
protected int worker_x;
|
||||
protected int worker_y;
|
||||
protected int selected_x;
|
||||
protected int selected_y;
|
||||
protected int bingos;
|
||||
|
||||
public Puzzle( InputStream is )
|
||||
private int [] board;
|
||||
private XYPair board_size = new XYPair(0,0);
|
||||
private int bingos = 0;
|
||||
private int box_count = 0;
|
||||
private XYPair worker = new XYPair(0,0);
|
||||
private XYPair selected = new XYPair(-1,-1);
|
||||
|
||||
public Puzzle(){
|
||||
}
|
||||
public Puzzle(int cols, int rows, int [] b)
|
||||
{
|
||||
load( is );
|
||||
board_size.set(cols, rows);
|
||||
box_count = 0;
|
||||
bingos = 0;
|
||||
// board = new int[board_size.x()*board_size.y()];
|
||||
board = b;
|
||||
for( int i = 0; i < board.length; ++i ){
|
||||
if ((board[i] & BOX) == BOX){
|
||||
++box_count;
|
||||
}
|
||||
if ((board[i] & BINGO) == BINGO) {
|
||||
++bingos;
|
||||
}
|
||||
if ((board[i] & WORKER) == WORKER){
|
||||
worker = getXY(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@@ -49,37 +60,30 @@ public class Puzzle
|
||||
|
||||
public final int getColumnCount()
|
||||
{
|
||||
return columns;
|
||||
return board_size.x();
|
||||
}
|
||||
public final int getRowCount()
|
||||
{
|
||||
return rows;
|
||||
return board_size.y();
|
||||
}
|
||||
public static int getSymCount()
|
||||
{
|
||||
return MAX_COUNT;
|
||||
}
|
||||
|
||||
public final int getIndex( int x, int y )
|
||||
{
|
||||
return y*columns+x;
|
||||
public final int getIndex( final XYPair c ) {
|
||||
return c.y()*board_size.x()+c.x();
|
||||
}
|
||||
public final int getX( int idx )
|
||||
{
|
||||
return idx % columns;
|
||||
}
|
||||
public final int getY( int idx )
|
||||
{
|
||||
return idx / columns;
|
||||
public final XYPair getXY( int idx ) {
|
||||
return new XYPair(idx % board_size.x(), idx / board_size.x());
|
||||
}
|
||||
|
||||
public final int getSym( int x, int y )
|
||||
{
|
||||
return board[getIndex(x,y)];
|
||||
public final int getSym( XYPair c ) {
|
||||
return board[getIndex(c)];
|
||||
}
|
||||
public void setSym( int x, int y, int v )
|
||||
public void setSym( final XYPair xy, int v )
|
||||
{
|
||||
board[getIndex(x,y)]=v;
|
||||
board[getIndex(xy)]=v;
|
||||
}
|
||||
|
||||
public static boolean isEmpty( int v )
|
||||
@@ -95,47 +99,28 @@ public class Puzzle
|
||||
return (v & WORKER) != 0;
|
||||
}
|
||||
|
||||
public final int getWorkerX()
|
||||
{
|
||||
return worker_x;
|
||||
}
|
||||
|
||||
public final int getWorkerY()
|
||||
{
|
||||
return worker_y;
|
||||
public final XYPair getWorker() {
|
||||
return new XYPair(worker);
|
||||
}
|
||||
|
||||
public final boolean isSelected()
|
||||
{
|
||||
return selected_x != -1;
|
||||
return !selected.isEqual(UNSELECTED);
|
||||
}
|
||||
public final boolean isSelected( final XYPair s ) {
|
||||
return selected.isEqual(s);
|
||||
}
|
||||
public final XYPair getSelected()
|
||||
{
|
||||
return new XYPair(selected);
|
||||
}
|
||||
|
||||
public final boolean isSelected( int x, int y )
|
||||
{
|
||||
return selected_x == x && selected_y == y;
|
||||
public final boolean isValid( final XYPair xy ) {
|
||||
return xy.isInside(XYPair.ZERO,board_size);
|
||||
}
|
||||
|
||||
public final int getSelectedX()
|
||||
{
|
||||
return selected_x;
|
||||
}
|
||||
|
||||
public final int getSelectedY()
|
||||
{
|
||||
return selected_y;
|
||||
}
|
||||
|
||||
public final boolean isValid( int x, int y )
|
||||
{
|
||||
return ( 0 <= x && 0 <= y
|
||||
&& x < getColumnCount()
|
||||
&& y < getRowCount() );
|
||||
}
|
||||
|
||||
public final boolean isOneStep( int x, int y )
|
||||
{
|
||||
return isValid(x,y)
|
||||
&& (abs(worker_x-x )+abs(worker_y-y) == 1);
|
||||
public final boolean isOneStep( final XYPair xy ) {
|
||||
return isValid(xy) && XYPair.sub(worker, xy).l1_norm() == 1;
|
||||
}
|
||||
|
||||
public final boolean isDone()
|
||||
@@ -143,73 +128,42 @@ public class Puzzle
|
||||
return bingos == box_count;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Loading.
|
||||
//
|
||||
private void load( InputStream is )
|
||||
{try{
|
||||
columns = is.read();
|
||||
rows = is.read();
|
||||
box_count = 0;
|
||||
bingos = 0;
|
||||
selected_x = -1;
|
||||
selected_y = -1;
|
||||
board = new int[columns*rows];
|
||||
byte [] b = new byte [columns*rows];
|
||||
is.read( b, 0, columns*rows);
|
||||
for( int i = 0; i < b.length; ++i )
|
||||
{
|
||||
switch( b[i] )
|
||||
{
|
||||
case 0:
|
||||
board[i] = FLOOR;
|
||||
break;
|
||||
case 1:
|
||||
board[i] = WALL;
|
||||
break;
|
||||
case 2:
|
||||
board[i] = BOX;
|
||||
++box_count;
|
||||
break;
|
||||
case 3:
|
||||
board[i] = GOAL;
|
||||
break;
|
||||
case 4:
|
||||
board[i] = BINGO;
|
||||
++box_count;
|
||||
++bingos;
|
||||
break;
|
||||
case 5:
|
||||
board[i] = WORKER;
|
||||
worker_x = getX(i);
|
||||
worker_y = getY(i);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
Log.d( TAG, "load()", e);
|
||||
}}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Move worker.
|
||||
//
|
||||
|
||||
public boolean walk( int x, int y)
|
||||
private final int worker_direction(XYPair xy) {
|
||||
//
|
||||
// Find direction to move.
|
||||
//
|
||||
int w;
|
||||
if (worker.left().isEqual(xy)) {
|
||||
w = WORKER_FACE_LEFT;
|
||||
}
|
||||
else if (worker.right().isEqual(xy)) {
|
||||
w = WORKER_FACE_RIGHT;
|
||||
}
|
||||
else if (worker.up().isEqual(xy)) {
|
||||
w = WORKER_FACE_UP;
|
||||
}
|
||||
else {
|
||||
w = WORKER_FACE_DOWN;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
public boolean walk(XYPair xy)
|
||||
{
|
||||
//
|
||||
// Check that this is one step away.
|
||||
//
|
||||
if ( !isOneStep(x,y) )
|
||||
if ( !isOneStep(xy) )
|
||||
return false;
|
||||
//
|
||||
// Check that this is empty space.
|
||||
//
|
||||
int v = getSym(x,y);
|
||||
int v = getSym(xy);
|
||||
if ( !isEmpty(v) )
|
||||
return false;
|
||||
//
|
||||
@@ -218,38 +172,25 @@ public class Puzzle
|
||||
if ( isSelected() )
|
||||
unselect();
|
||||
//
|
||||
// Find direction to move.
|
||||
//
|
||||
int worker;
|
||||
if ( worker_x < x )
|
||||
worker = WORKER_WEST;
|
||||
else if ( worker_x > x )
|
||||
worker = WORKER_EAST;
|
||||
else if ( worker_y > y )
|
||||
worker = WORKER_SOUTH;
|
||||
else
|
||||
worker = WORKER_NORTH;
|
||||
//
|
||||
// Move worker marker from current position to asked position.
|
||||
//
|
||||
setSym(worker_x,worker_y,getSym(worker_x,worker_y)&~WORKER_MASK);
|
||||
worker_x =x;
|
||||
worker_y =y;
|
||||
setSym(worker_x,worker_y,getSym(worker_x,worker_y)|worker);
|
||||
setSym(worker,getSym(worker)&~WORKER_MASK);
|
||||
worker.set(xy);
|
||||
setSym(worker,getSym(worker)|worker_direction(xy));
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean select( int x, int y)
|
||||
public boolean select(XYPair xy)
|
||||
{
|
||||
//
|
||||
// Check that this is one step away.
|
||||
//
|
||||
if ( !isOneStep(x,y) )
|
||||
if ( !isOneStep(xy) )
|
||||
return false;
|
||||
//
|
||||
// Check that this is empty space.
|
||||
//
|
||||
int v = getSym(x,y);
|
||||
int v = getSym(xy);
|
||||
if ( !hasBox(v) )
|
||||
return false;
|
||||
//
|
||||
@@ -258,24 +199,11 @@ public class Puzzle
|
||||
if ( isSelected() )
|
||||
unselect();
|
||||
//
|
||||
// Find direction to move.
|
||||
//
|
||||
int worker;
|
||||
if ( worker_x < x )
|
||||
worker = WORKER_WEST;
|
||||
else if ( worker_x > x )
|
||||
worker = WORKER_EAST;
|
||||
else if ( worker_y > y )
|
||||
worker = WORKER_SOUTH;
|
||||
else
|
||||
worker = WORKER_NORTH;
|
||||
//
|
||||
// Move worker marker from current position to asked position.
|
||||
//
|
||||
selected_x =x;
|
||||
selected_y =y;
|
||||
setSym(worker_x,worker_y,getSym(worker_x,worker_y)&~WORKER_MASK|worker|SELECTED);
|
||||
setSym(selected_x,selected_y,getSym(selected_x,selected_y)|SELECTED);
|
||||
selected.set(xy);
|
||||
setSym(worker,getSym(worker)&~WORKER_MASK|SELECTED|worker_direction(xy));
|
||||
setSym(selected,getSym(selected)|SELECTED);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -289,14 +217,13 @@ public class Puzzle
|
||||
//
|
||||
// Move worker marker from current position to asked position.
|
||||
//
|
||||
setSym(worker_x,worker_y,getSym(worker_x,worker_y)&~SELECTED);
|
||||
setSym(selected_x,selected_y,getSym(selected_x,selected_y)&~SELECTED);
|
||||
selected_x =-1;
|
||||
selected_y =-1;
|
||||
setSym(worker,getSym(worker)&~SELECTED);
|
||||
setSym(selected,getSym(selected)&~SELECTED);
|
||||
selected.set(UNSELECTED);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean push(int x, int y)
|
||||
public boolean push(final XYPair xy)
|
||||
{
|
||||
//
|
||||
// If not selected then do nothing.
|
||||
@@ -306,30 +233,29 @@ public class Puzzle
|
||||
//
|
||||
// Check that we go to the selected direction.
|
||||
//
|
||||
if ( selected_x != x && selected_y != y )
|
||||
if ( !selected.isEqual(xy) )
|
||||
return false;
|
||||
//
|
||||
// Check that this is a box that we move.
|
||||
//
|
||||
int v = getSym(x,y);
|
||||
int v = getSym(xy);
|
||||
if ( !hasBox(v) )
|
||||
return false;
|
||||
//
|
||||
// Check that the next space to the box is empty.
|
||||
//w - 2*(w-x) = 2x -w
|
||||
int next_x = 2*x - worker_x;
|
||||
int next_y = 2*y - worker_y;
|
||||
int next_v = getSym(next_x,next_y);
|
||||
XYPair next = xy.mul(2).sub(worker);
|
||||
int next_v = getSym(next);
|
||||
if ( !isEmpty(next_v) )
|
||||
return false;
|
||||
//
|
||||
// Ok, looks we can move the box. Do it actually.
|
||||
//
|
||||
unselect();
|
||||
setSym(x,y,getSym(x,y)&~BOX);
|
||||
setSym(next_x,next_y,getSym(next_x,next_y)|BOX);
|
||||
walk(x,y);
|
||||
select(next_x,next_y);
|
||||
setSym(xy,getSym(xy)&~BOX);
|
||||
setSym(next,getSym(next)|BOX);
|
||||
walk(xy);
|
||||
select(next);
|
||||
//
|
||||
// Keep track of box count in place.
|
||||
//
|
||||
@@ -348,29 +274,23 @@ public class Puzzle
|
||||
protected class State
|
||||
{
|
||||
public int [] board;
|
||||
public int worker_x;
|
||||
public int worker_y;
|
||||
public int selected_x;
|
||||
public int selected_y;
|
||||
public int bingos;
|
||||
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();
|
||||
worker_x = puzzle.worker_x;
|
||||
worker_y = puzzle.worker_y;
|
||||
selected_x = puzzle.selected_x;
|
||||
selected_y = puzzle.selected_y;
|
||||
worker.set(puzzle.worker);
|
||||
selected.set(puzzle.selected);
|
||||
bingos = puzzle.bingos;
|
||||
}
|
||||
|
||||
public void restore(Puzzle puzzle)
|
||||
{
|
||||
puzzle.board = board;
|
||||
puzzle.worker_x = worker_x;
|
||||
puzzle.worker_y = worker_y;
|
||||
puzzle.selected_x = selected_x;
|
||||
puzzle.selected_y = selected_y;
|
||||
puzzle.worker.set(worker);
|
||||
puzzle.selected.set(selected);
|
||||
puzzle.bingos = bingos;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class PuzzleChoreographer
|
||||
{
|
||||
private Puzzle puzzle = null;
|
||||
private PuzzleRouteFinder routeFinder = null;
|
||||
private IPuzzleAnimator animator;
|
||||
|
||||
public PuzzleChoreographer()
|
||||
{}
|
||||
|
||||
public void setPuzzle( Puzzle p )
|
||||
{
|
||||
puzzle = p;
|
||||
routeFinder = new PuzzleRouteFinder(p);
|
||||
}
|
||||
|
||||
public boolean createSteps(IPuzzleAnimator _animator, XYPair xy )
|
||||
{
|
||||
animator = _animator;
|
||||
//
|
||||
// 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() && tryMoveSelectedBox(xy) )
|
||||
return true;
|
||||
//
|
||||
// Either nothing was selected or the box cannot be moved to
|
||||
// tapped location. Try move worker alone.
|
||||
//
|
||||
if ( tryMoveWorker(xy) )
|
||||
return true;
|
||||
//
|
||||
// Show that action is not allowed.
|
||||
//
|
||||
animator.moveImpossible(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 ( tryMoveSelectedBox(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(xy) ) {
|
||||
return true;
|
||||
}
|
||||
//
|
||||
// Show that action is not allowed if reached till here.
|
||||
//
|
||||
animator.moveImpossible(xy);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Routes to accessible cells from where worker stands.
|
||||
//
|
||||
protected boolean tryMoveWorker(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();
|
||||
while ( it.hasNext() ) {
|
||||
animator.move(it.next());
|
||||
}
|
||||
//
|
||||
// Done.
|
||||
//
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected boolean tryMoveSelectedBox(XYPair xy)
|
||||
{
|
||||
//
|
||||
// If no box is selected then we cannot move no box.
|
||||
//
|
||||
if ( !puzzle.isSelected() ) {
|
||||
return false;
|
||||
}
|
||||
final XYPair box_xy = puzzle.getSelected();
|
||||
//
|
||||
// There is no point to continue if we are asked to move the box to where is it.
|
||||
//
|
||||
if ( xy.isEqual(box_xy) ) {
|
||||
return false;
|
||||
}
|
||||
//
|
||||
// Check that asked move is orthogonal to the slected box.
|
||||
//
|
||||
// TODO: check that all cells are empty on the route.
|
||||
//
|
||||
if ( xy.x() == box_xy.x() || xy.y() == box_xy.y() ) {
|
||||
return tryOrthogonalMoveSelectedBox(xy);
|
||||
}
|
||||
//
|
||||
// NOT IMPLEMENTED.
|
||||
//
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean tryOrthogonalMoveSelectedBox(XYPair xy)
|
||||
{
|
||||
//
|
||||
// If no box is selected then we cannot move no box.
|
||||
//
|
||||
if ( !puzzle.isSelected() ) {
|
||||
return false;
|
||||
}
|
||||
final XYPair box_xy = puzzle.getSelected();
|
||||
//
|
||||
// There is no point to continue if we are asked to move the box to where is it.
|
||||
//
|
||||
if ( xy.isEqual(box_xy) ) {
|
||||
return false;
|
||||
}
|
||||
//
|
||||
// Check that asked move is orthogonal to the selected box.
|
||||
//
|
||||
if ( xy.x() != box_xy.x() && xy.y() != box_xy.y() ) {
|
||||
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()) )
|
||||
{
|
||||
if (!tryMoveWorker(w)){
|
||||
return false;
|
||||
}
|
||||
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(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 neighboring cell. If
|
||||
// yes then simply select the box. If the box is already selected
|
||||
// then do nothing and if other 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 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(pref) ) {
|
||||
return false;
|
||||
}
|
||||
//
|
||||
// Select the box.
|
||||
//
|
||||
animator.select(xy);
|
||||
}
|
||||
//
|
||||
// Done
|
||||
//
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package org.vostan.banvor.model;
|
||||
|
||||
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.R;
|
||||
|
||||
public class PuzzleContainer implements IPuzzleSource
|
||||
{
|
||||
protected int count;
|
||||
|
||||
/*
|
||||
* Load puzzle from the puzzle.bin.
|
||||
*/
|
||||
public Puzzle getPuzzle( int i )
|
||||
{try{
|
||||
InputStream is = theApp().getResources().openRawResource(R.raw.puzzles);
|
||||
|
||||
//
|
||||
// Read amount of puzzles.
|
||||
//
|
||||
count = read_i32(is);
|
||||
if ( i >= count )
|
||||
return null;
|
||||
//
|
||||
// Read the offset.
|
||||
//
|
||||
is.skip( i*4 );
|
||||
int offset = read_i32(is);
|
||||
//
|
||||
// Jump to the offset and read the puzzle.
|
||||
//
|
||||
is.skip( offset - 4 - (i+1)*4);
|
||||
Puzzle p = new Puzzle( is );
|
||||
//
|
||||
// Finally return the puzzle and we are done.
|
||||
//
|
||||
return p;
|
||||
}
|
||||
catch ( java.lang.Exception e )
|
||||
{
|
||||
//Log.d(TAG, "Exception: " + e.getMessage() );
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}}
|
||||
//
|
||||
// Retrive amount of puzzles.
|
||||
//
|
||||
public int getCount()
|
||||
{try{
|
||||
if ( count == 0 )
|
||||
{
|
||||
InputStream is = theApp().getResources().openRawResource(R.raw.puzzles);
|
||||
count = read_i32(is);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
catch ( java.lang.Exception e )
|
||||
{
|
||||
//Log.d(TAG, "Exception: " + e.getMessage() );
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}}
|
||||
//
|
||||
// Helper function to read little endian 32bit integer.
|
||||
//
|
||||
protected int read_i32( InputStream is ) throws IOException
|
||||
{
|
||||
int i = is.read();
|
||||
i |= is.read() << 8;
|
||||
i |= is.read() << 16;
|
||||
i |= is.read() << 24;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
180
app/src/main/java/org/vostan/banvor/model/PuzzleRouteFinder.java
Normal file
180
app/src/main/java/org/vostan/banvor/model/PuzzleRouteFinder.java
Normal file
@@ -0,0 +1,180 @@
|
||||
package org.vostan.banvor.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Vector;
|
||||
|
||||
public class PuzzleRouteFinder {
|
||||
protected Puzzle puzzle = null;
|
||||
protected int [] moves;
|
||||
|
||||
//
|
||||
// This is to avoid fragmentation of memory.
|
||||
//
|
||||
private 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());
|
||||
}
|
||||
}
|
||||
Collections.reverse(steps);
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
100
app/src/main/java/org/vostan/banvor/model/XYPair.java
Normal file
100
app/src/main/java/org/vostan/banvor/model/XYPair.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package org.vostan.banvor.model;
|
||||
|
||||
import static java.lang.Math.abs;
|
||||
|
||||
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);
|
||||
public static final XYPair RIGHT = new XYPair(1, 0);
|
||||
public static final XYPair ZERO = new XYPair(0, 0);
|
||||
private int _x = 0;
|
||||
private int _y = 0;
|
||||
|
||||
public XYPair(){
|
||||
_x = 0;
|
||||
_y = 0;
|
||||
}
|
||||
|
||||
public XYPair(int x, int y){
|
||||
_x = x;
|
||||
_y = y;
|
||||
}
|
||||
|
||||
public XYPair(XYPair op){
|
||||
_x = op.x();
|
||||
_y = op.y();
|
||||
}
|
||||
|
||||
public final int x(){
|
||||
return _x;
|
||||
}
|
||||
|
||||
public final int y(){
|
||||
return _y;
|
||||
}
|
||||
|
||||
public void set(int x, int y){
|
||||
_x = x;
|
||||
_y = y;
|
||||
}
|
||||
|
||||
public void set(XYPair xy){
|
||||
_x = xy.x();
|
||||
_y = xy.y();
|
||||
}
|
||||
|
||||
public static XYPair add(XYPair op1, XYPair op2){
|
||||
return new XYPair(op1._x+op2._x, op1._y+op2._y);
|
||||
}
|
||||
|
||||
public static XYPair sub(XYPair op1, XYPair op2){
|
||||
return new XYPair(op1._x-op2._x, op1._y-op2._y);
|
||||
}
|
||||
|
||||
public static XYPair mul(XYPair op1, int scaliar){
|
||||
return new XYPair(op1._x*scaliar, op1._y*scaliar);
|
||||
}
|
||||
|
||||
public final XYPair mul(int scaliar){
|
||||
return mul(this, scaliar);
|
||||
}
|
||||
|
||||
public final XYPair sub(XYPair op){
|
||||
return sub(this, op);
|
||||
}
|
||||
|
||||
public final XYPair add(XYPair op){
|
||||
return add(this, op);
|
||||
}
|
||||
|
||||
public final int l1_norm(){
|
||||
return abs(_x)+abs(_y);
|
||||
}
|
||||
|
||||
public final boolean isEqual(XYPair op){
|
||||
return _x == op._x && _y == op._y;
|
||||
}
|
||||
|
||||
public final boolean isInside(XYPair op1, XYPair op2){
|
||||
return op1.x() <= this.x() && this.x() < op2.x()
|
||||
&& op1.y() <= this.y() && this.y() < op2.y();
|
||||
}
|
||||
|
||||
public final XYPair left(){
|
||||
return this.add(LEFT);
|
||||
}
|
||||
|
||||
public final XYPair right(){
|
||||
return this.add(RIGHT);
|
||||
}
|
||||
|
||||
public final XYPair up(){
|
||||
return this.add(UP);
|
||||
}
|
||||
|
||||
public final XYPair down(){
|
||||
return this.add(DOWN);
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,7 @@
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_prev"
|
||||
android:contentDescription="Previous Puzzle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
@@ -79,6 +80,7 @@
|
||||
|
||||
<ImageButton
|
||||
android:src="@drawable/next"
|
||||
android:contentDescription="Next Puzzle"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -90,6 +92,7 @@
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_reset"
|
||||
android:contentDescription="Reset Puzzle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
@@ -101,6 +104,7 @@
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_undo"
|
||||
android:contentDescription="Undo the last move."
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
@@ -109,7 +113,6 @@
|
||||
android:background="@color/transparent"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/undo"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -120,6 +123,7 @@
|
||||
|
||||
<org.vostan.banvor.board.PuzzleControl
|
||||
android:id="@+id/game_board"
|
||||
android:contentDescription="The game board."
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.vostan.banvor;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
114
app/src/test/java/org/vostan/banvor/UnitTestXYPair.java
Normal file
114
app/src/test/java/org/vostan/banvor/UnitTestXYPair.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package org.vostan.banvor;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.vostan.banvor.model.XYPair;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class UnitTestXYPair {
|
||||
public XYPair bottom_left = new XYPair(0, 0);
|
||||
public XYPair upper_right = new XYPair(10, 10);
|
||||
public XYPair middle = new XYPair(5, 5);
|
||||
|
||||
@Test
|
||||
public void isInside_bottom_left_is_inside() {
|
||||
assertTrue(bottom_left.isInside(bottom_left, upper_right));
|
||||
assertTrue(new XYPair(bottom_left.x(), middle.y()).isInside(bottom_left, upper_right));
|
||||
assertTrue(new XYPair(middle.x(), bottom_left.y()).isInside(bottom_left, upper_right));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isInside_upper_right_is_not_inside() {
|
||||
assertFalse(upper_right.isInside(bottom_left, upper_right));
|
||||
assertFalse(new XYPair(upper_right.x(), middle.y()).isInside(bottom_left, upper_right));
|
||||
assertFalse(new XYPair(middle.x(), upper_right.y()).isInside(bottom_left, upper_right));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isInside_middle_is_inside() {
|
||||
assertTrue(middle.isInside(bottom_left, upper_right));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isInside_outside_is_not_inside() {
|
||||
assertFalse(bottom_left.left().isInside(bottom_left, upper_right));
|
||||
assertFalse(new XYPair(middle.x(), bottom_left.y()-1).isInside(bottom_left, upper_right));
|
||||
assertFalse(new XYPair(upper_right.x()+1, middle.y()).isInside(bottom_left, upper_right));
|
||||
assertFalse(new XYPair(middle.x(), upper_right.y()+1).isInside(bottom_left, upper_right));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void l1_norne_test() {
|
||||
assertEquals(6, new XYPair(2,4).l1_norm());
|
||||
assertEquals(6, new XYPair(-2,4).l1_norm());
|
||||
assertEquals(6, new XYPair(2,-4).l1_norm());
|
||||
assertEquals(6, new XYPair(-2,-4).l1_norm());
|
||||
assertEquals(0, new XYPair(0,0).l1_norm());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void add_test() {
|
||||
XYPair c = XYPair.add(new XYPair(2,4), new XYPair(3, 5));
|
||||
assertEquals(5, c.x());
|
||||
assertEquals(9, c.y());
|
||||
|
||||
c = XYPair.add(new XYPair(-2,4), new XYPair(3, -5));
|
||||
assertEquals(1, c.x());
|
||||
assertEquals(-1, c.y());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sub_test() {
|
||||
XYPair c = XYPair.sub(new XYPair(2,9), new XYPair(3, 5));
|
||||
assertEquals(-1, c.x());
|
||||
assertEquals(4, c.y());
|
||||
|
||||
c = XYPair.sub(new XYPair(-2,-4), new XYPair(3, -5));
|
||||
assertEquals(-5, c.x());
|
||||
assertEquals(1, c.y());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mul_test() {
|
||||
XYPair c = XYPair.mul(new XYPair(2,9), 0);
|
||||
assertEquals(0, c.x());
|
||||
assertEquals(0, c.y());
|
||||
|
||||
c = XYPair.mul(new XYPair(-2,-4), 3);
|
||||
assertEquals(-6, c.x());
|
||||
assertEquals(-12, c.y());
|
||||
|
||||
c = XYPair.mul(new XYPair(-2,-4), -2);
|
||||
assertEquals(4, c.x());
|
||||
assertEquals(8, c.y());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void left_test() {
|
||||
XYPair c = new XYPair(5,5).left();
|
||||
assertEquals(4, c.x());
|
||||
assertEquals(5, c.y());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void right_test() {
|
||||
XYPair c = new XYPair(5,5).right();
|
||||
assertEquals(6, c.x());
|
||||
assertEquals(5, c.y());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void up_test() {
|
||||
XYPair c = new XYPair(5,5).up();
|
||||
assertEquals(5, c.x());
|
||||
assertEquals(6, c.y());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void down_test() {
|
||||
XYPair c = new XYPair(5,5).down();
|
||||
assertEquals(5, c.x());
|
||||
assertEquals(4, c.y());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,10 @@ buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
//This is needed for SVG->PNG
|
||||
// maven {
|
||||
// url "https://plugins.gradle.org/m2/"
|
||||
// }
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:7.0.2"
|
||||
@@ -10,6 +14,9 @@ buildscript {
|
||||
def nav_version = "2.3.5"
|
||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
|
||||
|
||||
//This is needed for SVG->PNG
|
||||
// classpath 'com.trello:victor:1.1.2'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user