diff --git a/.classpath b/.classpath index a4763d1..a4f1e40 100644 --- a/.classpath +++ b/.classpath @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/.project b/.project index 18c54cc..887fe24 100644 --- a/.project +++ b/.project @@ -1,33 +1,33 @@ - - - SliderPuzzle - - - - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - - - - - com.android.ide.eclipse.adt.PreCompilerBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - com.android.ide.eclipse.adt.ApkBuilder - - - - - - com.android.ide.eclipse.adt.AndroidNature - org.eclipse.jdt.core.javanature - - + + + Slider Puzzle + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index f77b31c..1bd7087 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,4 +1,2 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 -org.eclipse.jdt.core.compiler.compliance=1.5 -org.eclipse.jdt.core.compiler.source=1.5 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.processAnnotations=disabled diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5354c72..8f0180d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,17 +1,19 @@ - + + android:theme="@style/Theme.Sherlock.ForceOverflow" > @@ -19,6 +21,11 @@ + + + \ No newline at end of file diff --git a/project.properties b/project.properties index 2cc58fa..de89b38 100644 --- a/project.properties +++ b/project.properties @@ -8,4 +8,5 @@ # project structure. # Project target. -target=android-12 +target=android-15 +android.library.reference.1=..\\\\\\\\ActionBarSherlock\\\\\\\\library diff --git a/readme.pdf b/readme.pdf new file mode 100644 index 0000000..9349947 Binary files /dev/null and b/readme.pdf differ diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png index 8074c4c..76b70a4 100644 Binary files a/res/drawable-hdpi/ic_launcher.png and b/res/drawable-hdpi/ic_launcher.png differ diff --git a/res/drawable-hdpi/settle_up.png b/res/drawable-hdpi/settle_up.png new file mode 100644 index 0000000..1e1772a Binary files /dev/null and b/res/drawable-hdpi/settle_up.png differ diff --git a/res/drawable-ldpi/ic_launcher.png b/res/drawable-ldpi/ic_launcher.png deleted file mode 100644 index 1095584..0000000 Binary files a/res/drawable-ldpi/ic_launcher.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png index a07c69f..e6d697e 100644 Binary files a/res/drawable-mdpi/ic_launcher.png and b/res/drawable-mdpi/ic_launcher.png differ diff --git a/res/layout/activity_about.xml b/res/layout/activity_about.xml new file mode 100644 index 0000000..9037c6e --- /dev/null +++ b/res/layout/activity_about.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/activity_main.xml b/res/layout/activity_main.xml new file mode 100644 index 0000000..c6a0d43 --- /dev/null +++ b/res/layout/activity_main.xml @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/res/layout/main.xml b/res/layout/main.xml deleted file mode 100644 index ffc0192..0000000 --- a/res/layout/main.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/menu/main_menu.xml b/res/menu/main_menu.xml new file mode 100644 index 0000000..d8d1bec --- /dev/null +++ b/res/menu/main_menu.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml new file mode 100644 index 0000000..a60dfb9 --- /dev/null +++ b/res/values/colors.xml @@ -0,0 +1,6 @@ + + + + #3b3b3b + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 9ab8c13..1f0616e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1,7 +1,22 @@ - Hello World, SliderPuzzleActivity! - SliderPuzzle - + + Slider Puzzle + + New Game + + About + + This app was created by <a href="http://www.destil.cz">David Vávra</a>. + + The following open-source projects were used: + + - <a href="https://github.com/thillerson/Android-Slider-Puzzle">Android-Slider-Puzzle</a> by Tony Hillerson (MIT License) + + - <a href="http://actionbarsherlock.com/">ActionBarSherlock</a> by Jake Wharton (Apache License) + + Check out my most successful app: + + <a href="https://play.google.com/store/apps/details?id=cz.destil.settleup">Settle Up</a \ No newline at end of file diff --git a/src/com/tackmobile/GameTile.java b/src/com/tackmobile/GameTile.java deleted file mode 100644 index 18deab3..0000000 --- a/src/com/tackmobile/GameTile.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.tackmobile; - -import com.tackmobile.GameboardView.Coordinate; - -import android.content.Context; -import android.widget.ImageView; - -public class GameTile extends ImageView { - - public Coordinate coordinate; - protected boolean empty; - - public GameTile(Context context, Coordinate coordinate) { - super(context); - this.coordinate = coordinate; - } - - @Override - public String toString() { - return String.format(" tiles; - protected GameTile emptyTile, movedTile; - private boolean boardCreated; - private PointF lastDragPoint; - private TileServer tileServer; - protected ArrayList currentMotionDescriptors; - - public GameboardView(Context context, AttributeSet attrSet) { - super(context, attrSet); - Drawable globe = getResources().getDrawable(R.drawable.globe); - Bitmap original = ((BitmapDrawable)globe).getBitmap(); - tileServer = new TileServer(original, 4, 4, 68); - - createTiles(); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (!boardCreated) { - determineGameboardSizes(); - placeTiles(); - boardCreated = true; - } - } - - protected void placeTiles() { - for (GameTile tile : tiles) { - placeTile(tile); - } - } - - protected void placeTile(GameTile tile) { - Rect tileRect = rectForCoordinate(tile.coordinate); - RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(tileSize.width, tileSize.height); - params.topMargin = tileRect.top; - params.leftMargin = tileRect.left; - addView(tile, params); - tile.setImageBitmap(tileServer.serveRandomSlice()); - } - - protected void createTiles() { - tiles = new HashSet(); - for (int rowI=0; rowI<4; rowI++) { - for (int colI=0; colI<4; colI++) { - GameTile tile = createTileAtCoordinate( new Coordinate(rowI, colI) ); - if (rowI == 3 && colI == 3) { - emptyTile = tile; - tile.setEmpty(true); - } - } - } - } - - public boolean onTouch(View v, MotionEvent event) { - try { - GameTile touchedTile = (GameTile)v; - if (touchedTile.isEmpty() || !touchedTile.isInRowOrColumnOf(emptyTile)) { - return false; - } else { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - movedTile = touchedTile; - currentMotionDescriptors = getTilesBetweenEmptyTileAndTile(movedTile); - } else if (event.getActionMasked() == MotionEvent.ACTION_MOVE) { - if (lastDragPoint != null) { - moveDraggedTilesByMotionEventDelta(event); - } - lastDragPoint = new PointF(event.getRawX(), event.getRawY()); - } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { - // reload the motion descriptors in case of position change. - currentMotionDescriptors = getTilesBetweenEmptyTileAndTile(movedTile); - // if last move was a dragging move and the move was over half way to the empty tile - if (lastDragPoint != null && lastDragMovedAtLeastHalfWay()) { - animateCurrentMovedTilesToEmptySpace(); - // otherwise, if it wasn't a drag, do the move - } else if (lastDragPoint == null) { - animateCurrentMovedTilesToEmptySpace(); - // Animate tiles back to origin - } else { - animateMovedTilesBackToOrigin(); - } - currentMotionDescriptors = null; - lastDragPoint = null; - movedTile = null; - } - return true; - } - } catch (ClassCastException e) { - return false; - } - } - - protected boolean lastDragMovedAtLeastHalfWay() { - if (currentMotionDescriptors != null && currentMotionDescriptors.size() > 0) { - GameTileMotionDescriptor firstMotionDescriptor = currentMotionDescriptors.get(0); - if (firstMotionDescriptor.axialDelta > tileSize.width/2) { - return true; - } - } - return false; - } - - protected void moveDraggedTilesByMotionEventDelta(MotionEvent event) { - boolean impossibleMove = true; - float dxTile, dyTile; - float dxEvent = event.getRawX() - lastDragPoint.x; - float dyEvent = event.getRawY() - lastDragPoint.y; - GameTile tile; - for (GameTileMotionDescriptor gameTileMotionDescriptor : currentMotionDescriptors) { - tile = gameTileMotionDescriptor.tile; - dxTile = tile.getX() + dxEvent; - dyTile = tile.getY() + dyEvent; - - RectF candidateRect = new RectF(dxTile, dyTile, dxTile + tile.getWidth(), dyTile + tile.getHeight()); - HashSet tilesToCheck = null; - if (tile.coordinate.row == emptyTile.coordinate.row) { - tilesToCheck = allTilesInRow(tile.coordinate.row); - } else if (tile.coordinate.column == emptyTile.coordinate.column) { - tilesToCheck = allTilesInColumn(tile.coordinate.column); - } - - boolean candidateRectInGameboard = (gameboardRect.contains(candidateRect)); - boolean collides = candidateRectForTileCollidesWithAnyTileInSet(candidateRect, tile, tilesToCheck); - - impossibleMove = impossibleMove && (!candidateRectInGameboard || collides); - } - if (!impossibleMove) { - for (GameTileMotionDescriptor gameTileMotionDescriptor : currentMotionDescriptors) { - tile = gameTileMotionDescriptor.tile; - dxTile = tile.getX() + dxEvent; - dyTile = tile.getY() + dyEvent; - if (!impossibleMove) { - if (tile.coordinate.row == emptyTile.coordinate.row) { - tile.setX(dxTile); - } else if (tile.coordinate.column == emptyTile.coordinate.column) { - tile.setY(dyTile); - } - } - } - } - } - - protected boolean candidateRectForTileCollidesWithAnyTileInSet(RectF candidateRect, GameTile tile, HashSet set) { - RectF otherTileRect; - for (GameTile otherTile : set) { - if (!otherTile.isEmpty() && otherTile != tile) { - otherTileRect = new RectF(otherTile.getX(), otherTile.getY(), otherTile.getX() + otherTile.getWidth(), otherTile.getY() + otherTile.getHeight()); - if (RectF.intersects(otherTileRect, candidateRect)) { - return true; - } - } - } - return false; - } - - protected void animateCurrentMovedTilesToEmptySpace() { - emptyTile.setX(movedTile.getX()); - emptyTile.setY(movedTile.getY()); - emptyTile.coordinate = movedTile.coordinate; - ObjectAnimator animator; - for (final GameTileMotionDescriptor motionDescriptor : currentMotionDescriptors) { - animator = ObjectAnimator.ofObject( - motionDescriptor.tile, - motionDescriptor.property, - new FloatEvaluator(), - motionDescriptor.from, - motionDescriptor.to); - animator.setDuration(16); - animator.addListener(new AnimatorListener() { - - public void onAnimationStart(Animator animation) { } - public void onAnimationCancel(Animator animation) { } - public void onAnimationRepeat(Animator animation) { } - - public void onAnimationEnd(Animator animation) { - motionDescriptor.tile.coordinate = motionDescriptor.finalCoordinate; - motionDescriptor.tile.setX(motionDescriptor.finalRect.left); - motionDescriptor.tile.setY(motionDescriptor.finalRect.top); - } - }); - animator.start(); - } - } - - protected void animateMovedTilesBackToOrigin() { - ObjectAnimator animator; - if (currentMotionDescriptors != null) { - for (final GameTileMotionDescriptor motionDescriptor : currentMotionDescriptors) { - animator = ObjectAnimator.ofObject( - motionDescriptor.tile, - motionDescriptor.property, - new FloatEvaluator(), - motionDescriptor.currentPosition(), - motionDescriptor.originalPosition()); - animator.setDuration(16); - animator.addListener(new AnimatorListener() { - - public void onAnimationStart(Animator animation) { } - public void onAnimationCancel(Animator animation) { } - public void onAnimationRepeat(Animator animation) { } - public void onAnimationEnd(Animator animation) { - } - }); - animator.start(); - } - } - } - - private ArrayList getTilesBetweenEmptyTileAndTile(GameTile tile) { - ArrayList descriptors = new ArrayList(); - Coordinate coordinate, finalCoordinate; - GameTile foundTile; - GameTileMotionDescriptor motionDescriptor; - Rect finalRect, currentRect; - float axialDelta; - if (tile.isToRightOf(emptyTile)) { - for (int i = tile.coordinate.column; i > emptyTile.coordinate.column; i--) { - coordinate = new Coordinate(tile.coordinate.row, i); - foundTile = (tile.coordinate.matches(coordinate)) ? tile : getTileAtCoordinate(coordinate) ; - finalCoordinate = new Coordinate(tile.coordinate.row, i-1); - currentRect = rectForCoordinate(foundTile.coordinate); - finalRect = rectForCoordinate(finalCoordinate); - axialDelta = Math.abs(foundTile.getX() - currentRect.left); - motionDescriptor = new GameTileMotionDescriptor( - foundTile, - "x", - foundTile.getX(), - finalRect.left - ); - motionDescriptor.finalCoordinate = finalCoordinate; - motionDescriptor.finalRect = finalRect; - motionDescriptor.axialDelta = axialDelta; - descriptors.add(motionDescriptor); - } - } else if (tile.isToLeftOf(emptyTile)) { - for (int i = tile.coordinate.column; i < emptyTile.coordinate.column; i++) { - coordinate = new Coordinate(tile.coordinate.row, i); - foundTile = (tile.coordinate.matches(coordinate)) ? tile : getTileAtCoordinate(coordinate) ; - finalCoordinate = new Coordinate(tile.coordinate.row, i+1); - currentRect = rectForCoordinate(foundTile.coordinate); - finalRect = rectForCoordinate(finalCoordinate); - axialDelta = Math.abs(foundTile.getX() - currentRect.left); - motionDescriptor = new GameTileMotionDescriptor( - foundTile, - "x", - foundTile.getX(), - finalRect.left - ); - motionDescriptor.finalCoordinate = finalCoordinate; - motionDescriptor.finalRect = finalRect; - motionDescriptor.axialDelta = axialDelta; - descriptors.add(motionDescriptor); - } - } else if (tile.isAbove(emptyTile)) { - for (int i = tile.coordinate.row; i < emptyTile.coordinate.row; i++) { - coordinate = new Coordinate(i, tile.coordinate.column); - foundTile = (tile.coordinate.matches(coordinate)) ? tile : getTileAtCoordinate(coordinate) ; - finalCoordinate = new Coordinate(i+1, tile.coordinate.column); - currentRect = rectForCoordinate(foundTile.coordinate); - finalRect = rectForCoordinate(finalCoordinate); - axialDelta = Math.abs(foundTile.getY() - currentRect.top); - motionDescriptor = new GameTileMotionDescriptor( - foundTile, - "y", - foundTile.getY(), - finalRect.top - ); - motionDescriptor.finalCoordinate = finalCoordinate; - motionDescriptor.finalRect = finalRect; - motionDescriptor.axialDelta = axialDelta; - descriptors.add(motionDescriptor); - } - } else if (tile.isBelow(emptyTile)) { - for (int i = tile.coordinate.row; i > emptyTile.coordinate.row; i--) { - coordinate = new Coordinate(i, tile.coordinate.column); - foundTile = (tile.coordinate.matches(coordinate)) ? tile : getTileAtCoordinate(coordinate) ; - finalCoordinate = new Coordinate(i-1, tile.coordinate.column); - currentRect = rectForCoordinate(foundTile.coordinate); - finalRect = rectForCoordinate(finalCoordinate); - axialDelta = Math.abs(foundTile.getY() - currentRect.top); - motionDescriptor = new GameTileMotionDescriptor( - foundTile, - "y", - foundTile.getY(), - finalRect.top - ); - motionDescriptor.finalCoordinate = finalCoordinate; - motionDescriptor.finalRect = finalRect; - motionDescriptor.axialDelta = axialDelta; - descriptors.add(motionDescriptor); - } - } - return descriptors; - } - - protected GameTile getTileAtCoordinate(Coordinate coordinate) { - //Ln.d("Finding tile at %s", coordinate); - for (GameTile tile : tiles) { - if (tile.coordinate.matches(coordinate)) { - //Ln.d("Found tile %s", tile); - return tile; - } - } - return null; - } - - protected HashSet allTilesInRow(int row) { - HashSet tilesInRow = new HashSet(); - for (GameTile tile : tiles) { - if (tile.coordinate.row == row) { - tilesInRow.add(tile); - } - } - return tilesInRow; - } - - protected HashSet allTilesInColumn(int column) { - HashSet tilesInColumn = new HashSet(); - for (GameTile tile : tiles) { - if (tile.coordinate.column == column) { - tilesInColumn.add(tile); - } - } - return tilesInColumn; - } - - protected GameTile createTileAtCoordinate(Coordinate coordinate) { - GameTile tile = new GameTile(getContext(), coordinate); - tiles.add(tile); - tile.setOnTouchListener(this); - return tile; - } - - protected void determineGameboardSizes() { - int viewWidth = getWidth(); - int viewHeight = getHeight(); - // ostensibly tiles can be sized based on view geometry. Hardcode for now. - tileSize = new Size(68, 68); - int gameboardWidth = tileSize.width * 4; - int gameboardHeight = tileSize.height * 4; - int gameboardTop = viewHeight/2 - gameboardHeight/2; - int gameboardLeft = viewWidth/2 - gameboardWidth/2; - gameboardRect = new RectF(gameboardLeft, gameboardTop, gameboardLeft + gameboardWidth, gameboardTop + gameboardHeight); - createTiles(); - } - - protected Rect rectForCoordinate(Coordinate coordinate) { - int gameboardY = (int) Math.floor(gameboardRect.top); - int gameboardX = (int) Math.floor(gameboardRect.left); - int top = (coordinate.row * tileSize.height) + gameboardY; - int left = (coordinate.column * tileSize.width) + gameboardX; - return new Rect(left, top, left + tileSize.width, top + tileSize.height); - } - - public class Size { - - public int width; - public int height; - - public Size(int width, int height) { - this.width = width; - this.height = height; - } - - } - - public class Coordinate { - - public int row; - public int column; - - public Coordinate(int row, int column) { - this.row = row; - this.column = column; - } - - public boolean matches(Coordinate coordinate) { - return coordinate.row == row && coordinate.column == column; - } - - public boolean sharesAxisWith(Coordinate coordinate) { - return (row == coordinate.row || column == coordinate.column); - } - - public boolean isToRightOf(Coordinate coordinate) { - return sharesAxisWith(coordinate) && (column > coordinate.column); - } - - public boolean isToLeftOf(Coordinate coordinate) { - return sharesAxisWith(coordinate) && (column < coordinate.column); - } - - public boolean isAbove(Coordinate coordinate) { - return sharesAxisWith(coordinate) && (row < coordinate.row); - } - - public boolean isBelow(Coordinate coordinate) { - return sharesAxisWith(coordinate) && (row > coordinate.row); - } - - @Override - public String toString() { - return "Coordinate [row=" + row + ", column=" + column + "]"; - } - - } - - public class GameTileMotionDescriptor { - - public Rect finalRect; - public String property; - public GameTile tile; - public float from, to, axialDelta; - public Coordinate finalCoordinate; - - public GameTileMotionDescriptor(GameTile tile, String property, float from, float to) { - super(); - this.tile = tile; - this.from = from; - this.to = to; - this.property = property; - } - - public float currentPosition() { - if (property.equals("x")) { - return tile.getX(); - } else if (property.equals("y")) { - return tile.getY(); - } - return 0; - } - - public float originalPosition() { - Rect originalRect = rectForCoordinate(tile.coordinate); - if (property.equals("x")) { - return originalRect.left; - } else if (property.equals("y")) { - return originalRect.top; - } - return 0; - } - - @Override - public String toString() { - return "GameTileMotionDescriptor [property=" + property + ", tile=" - + tile + ", from=" + from + ", to=" + to + "]"; - } - - } - -} diff --git a/src/com/tackmobile/SliderPuzzleActivity.java b/src/com/tackmobile/SliderPuzzleActivity.java deleted file mode 100644 index 2aeabc0..0000000 --- a/src/com/tackmobile/SliderPuzzleActivity.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.tackmobile; - -import android.app.Activity; -import android.os.Bundle; - -public class SliderPuzzleActivity extends Activity { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - } -} \ No newline at end of file diff --git a/src/com/tackmobile/TileServer.java b/src/com/tackmobile/TileServer.java deleted file mode 100644 index 3f9ac9e..0000000 --- a/src/com/tackmobile/TileServer.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.tackmobile; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Random; - -import android.graphics.Bitmap; - -public class TileServer { - - Bitmap original, scaledImage; - int rows, columns, width, tileSize; - HashSet slices; - ArrayList unservedSlices; - Random random; - - public TileServer(Bitmap original, int rows, int columns, int tileSize) { - super(); - this.original = original; - this.rows = rows; - this.columns = columns; - this.tileSize = tileSize; - - random = new Random(); - slices = new HashSet(); - sliceOriginal(); - } - - protected void sliceOriginal() { - int fullWidth = tileSize * rows; - int fullHeight = tileSize * columns; - scaledImage = Bitmap.createScaledBitmap(original, fullWidth, fullHeight, true); - - int x, y; - Bitmap bitmap; - for (int rowI=0; rowI<4; rowI++) { - for (int colI=0; colI<4; colI++) { - x = rowI * tileSize; - y = colI * tileSize; - bitmap = Bitmap.createBitmap(scaledImage, x, y, tileSize, tileSize); - slices.add(bitmap); - } - } - unservedSlices = new ArrayList(); - unservedSlices.addAll(slices); - } - - public Bitmap serveRandomSlice() { - if (unservedSlices.size() > 0) { - int randomIndex = random.nextInt(unservedSlices.size()); - Bitmap drawable = unservedSlices.remove(randomIndex); - return drawable; - } - return null; - } - -} diff --git a/src/cz/destil/sliderpuzzle/data/Coordinate.java b/src/cz/destil/sliderpuzzle/data/Coordinate.java new file mode 100644 index 0000000..5b10cc1 --- /dev/null +++ b/src/cz/destil/sliderpuzzle/data/Coordinate.java @@ -0,0 +1,48 @@ +package cz.destil.sliderpuzzle.data; + +/** + * + * Object for storing row and column coordinate. Contains useful functions for + * comparing coordinates. + * + * @author David Vavra + */ +public class Coordinate { + + public int row; + public int column; + + public Coordinate(int row, int column) { + this.row = row; + this.column = column; + } + + public boolean matches(Coordinate coordinate) { + return coordinate.row == row && coordinate.column == column; + } + + public boolean sharesAxisWith(Coordinate coordinate) { + return (row == coordinate.row || column == coordinate.column); + } + + public boolean isToRightOf(Coordinate coordinate) { + return sharesAxisWith(coordinate) && (column > coordinate.column); + } + + public boolean isToLeftOf(Coordinate coordinate) { + return sharesAxisWith(coordinate) && (column < coordinate.column); + } + + public boolean isAbove(Coordinate coordinate) { + return sharesAxisWith(coordinate) && (row < coordinate.row); + } + + public boolean isBelow(Coordinate coordinate) { + return sharesAxisWith(coordinate) && (row > coordinate.row); + } + + @Override + public String toString() { + return "[R: "+row+" C:"+column+"]"; + } +} \ No newline at end of file diff --git a/src/cz/destil/sliderpuzzle/ui/AboutActivity.java b/src/cz/destil/sliderpuzzle/ui/AboutActivity.java new file mode 100644 index 0000000..ad5e1b5 --- /dev/null +++ b/src/cz/destil/sliderpuzzle/ui/AboutActivity.java @@ -0,0 +1,46 @@ +package cz.destil.sliderpuzzle.ui; + +import android.os.Bundle; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.widget.TextView; + +import com.actionbarsherlock.app.SherlockActivity; + +import cz.destil.sliderpuzzle.R; + +/** + * + * Screen containing information about the app. + * + * @author David Vavra + * + */ +public class AboutActivity extends SherlockActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_about); + makeLinksClickable(new int[] { R.id.created_by, R.id.project1, R.id.project2, R.id.settle_up }); + } + + /** + * Makes links in the TextViews clickable. Links must be defined as HTML + * text. + * + * @param resourceIds + * Resource IDs of TextViews with links. + */ + private void makeLinksClickable(int[] resourceIds) { + for (int resourceId : resourceIds) { + if (findViewById(resourceId) instanceof TextView) { + TextView textView = (TextView) findViewById(resourceId); + if (textView != null) { + textView.setText(Html.fromHtml(textView.getText().toString())); + textView.setMovementMethod(LinkMovementMethod.getInstance()); + } + } + } + } +} \ No newline at end of file diff --git a/src/cz/destil/sliderpuzzle/ui/GameboardView.java b/src/cz/destil/sliderpuzzle/ui/GameboardView.java new file mode 100644 index 0000000..f258cb9 --- /dev/null +++ b/src/cz/destil/sliderpuzzle/ui/GameboardView.java @@ -0,0 +1,578 @@ +package cz.destil.sliderpuzzle.ui; + +import java.util.ArrayList; +import java.util.LinkedList; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Pair; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; +import android.widget.RelativeLayout; + +import com.actionbarsherlock.internal.nineoldandroids.animation.Animator; +import com.actionbarsherlock.internal.nineoldandroids.animation.Animator.AnimatorListener; +import com.actionbarsherlock.internal.nineoldandroids.animation.FloatEvaluator; +import com.actionbarsherlock.internal.nineoldandroids.animation.ObjectAnimator; + +import cz.destil.sliderpuzzle.R; +import cz.destil.sliderpuzzle.data.Coordinate; +import cz.destil.sliderpuzzle.util.TileSlicer; + +/** + * + * Layout handling creation and interaction of the game tiles. Captures gestures + * and performs animations. + * + * Based on: + * https://github.com/thillerson/Android-Slider-Puzzle/blob/master/src/ + * com/tackmobile/GameboardView.java + * + * @author David Vavra + * + */ +public class GameBoardView extends RelativeLayout implements OnTouchListener { + + public static final int GRID_SIZE = 4; // 4x4 + + public enum Direction { + X, Y + }; // movement along x or y axis + + private int tileSize; + private ArrayList tiles; + private TileView emptyTile, movedTile; + private boolean boardCreated; + private RectF gameboardRect; + private PointF lastDragPoint; + private ArrayList currentMotionDescriptors; + private LinkedList tileOrder; + + public GameBoardView(Context context, AttributeSet attrSet) { + super(context, attrSet); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (!boardCreated) { + determineGameboardSizes(); + fillTiles(); + boardCreated = true; + } + } + + /** + * Detect game board size and tile size based on current screen. + */ + private void determineGameboardSizes() { + int viewWidth = getWidth(); + int viewHeight = getHeight(); + // fit in portrait or landscape + if (viewWidth > viewHeight) { + tileSize = viewHeight / GRID_SIZE; + } else { + tileSize = viewWidth / GRID_SIZE; + } + int gameboardSize = tileSize * GRID_SIZE; + // center gameboard + int gameboardTop = viewHeight / 2 - gameboardSize / 2; + int gameboardLeft = viewWidth / 2 - gameboardSize / 2; + gameboardRect = new RectF(gameboardLeft, gameboardTop, gameboardLeft + gameboardSize, gameboardTop + + gameboardSize); + } + + /** + * Fills game board with tiles sliced from the globe image. + */ + public void fillTiles() { + removeAllViews(); + // load image to slicer + Drawable globe = getResources().getDrawable(R.drawable.globe); + Bitmap original = ((BitmapDrawable) globe).getBitmap(); + TileSlicer tileSlicer = new TileSlicer(original, GRID_SIZE, getContext()); + // order slices + if (tileOrder == null) { + tileSlicer.randomizeSlices(); + } else { + tileSlicer.setSliceOrder(tileOrder); + } + // fill game board with slices + tiles = new ArrayList(); + for (int rowI = 0; rowI < GRID_SIZE; rowI++) { + for (int colI = 0; colI < GRID_SIZE; colI++) { + TileView tile; + if (tileOrder == null) { + tile = tileSlicer.getTile(); + } else { + tile = tileSlicer.getTile(); + } + tile.coordinate = new Coordinate(rowI, colI); + if (tile.isEmpty()) { + emptyTile = tile; + } + placeTile(tile); + tiles.add(tile); + } + } + } + + /** + * Places tile on appropriate place in the layout. + * + * @param tile + * Tile to place + */ + private void placeTile(TileView tile) { + Rect tileRect = rectForCoordinate(tile.coordinate); + RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(tileSize, tileSize); + params.topMargin = tileRect.top; + params.leftMargin = tileRect.left; + addView(tile, params); + tile.setOnTouchListener(this); + } + + /** + * Handling of touch events. High-level logic for moving tiles on the game + * board. + */ + public boolean onTouch(View v, MotionEvent event) { + TileView touchedTile = (TileView) v; + if (touchedTile.isEmpty() || !touchedTile.isInRowOrColumnOf(emptyTile)) { + return false; + } else { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + // start of the gesture + movedTile = touchedTile; + currentMotionDescriptors = getTilesBetweenEmptyTileAndTile(movedTile); + movedTile.numberOfDrags = 0; + } else if (event.getActionMasked() == MotionEvent.ACTION_MOVE) { + // during the gesture + if (lastDragPoint != null) { + followFinger(event); + } + lastDragPoint = new PointF(event.getRawX(), event.getRawY()); + } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { + // end of gesture + // reload the motion descriptors in case of position change + currentMotionDescriptors = getTilesBetweenEmptyTileAndTile(movedTile); + // if drag was over 50% or it's click, do the move + if (lastDragMovedAtLeastHalfWay() || isClick()) { + animateTilesToEmptySpace(); + } else { + animateTilesBackToOrigin(); + } + currentMotionDescriptors = null; + lastDragPoint = null; + movedTile = null; + } + return true; + } + } + + /** + * @return Whether last drag moved with the tile more than 50% of its size + */ + private boolean lastDragMovedAtLeastHalfWay() { + if (lastDragPoint != null && currentMotionDescriptors != null && currentMotionDescriptors.size() > 0) { + GameTileMotionDescriptor firstMotionDescriptor = currentMotionDescriptors.get(0); + if (firstMotionDescriptor.axialDelta > tileSize / 2) { + return true; + } + } + return false; + } + + /** + * Detects click - either true click (no drags) or small involuntary drag + * + * @return Whether last gesture was a click + */ + private boolean isClick() { + if (lastDragPoint == null) { + return true; // no drags + } + // just small amount of MOVE events counts as click + if (currentMotionDescriptors != null && currentMotionDescriptors.size() > 0 && movedTile.numberOfDrags < 10) { + GameTileMotionDescriptor firstMotionDescriptor = currentMotionDescriptors.get(0); + // just very small drag counts as click + if (firstMotionDescriptor.axialDelta < tileSize / 20) { + return true; + } + } + return false; + } + + /** + * Follows finger while dragging all currently moved tiles. Allows movement + * only along x axis for row and y axis for column. + * + * @param event + */ + private void followFinger(MotionEvent event) { + boolean impossibleMove = true; + float dxEvent = event.getRawX() - lastDragPoint.x; + float dyEvent = event.getRawY() - lastDragPoint.y; + TileView tile; + movedTile.numberOfDrags++; + for (GameTileMotionDescriptor descriptor : currentMotionDescriptors) { + tile = descriptor.tile; + Pair xy = getXYFromEvent(tile, dxEvent, dyEvent, descriptor.direction); + // detect if this move is valid + RectF candidateRect = new RectF(xy.first, xy.second, xy.first + tile.getWidth(), xy.second + + tile.getHeight()); + ArrayList tilesToCheck = null; + if (tile.coordinate.row == emptyTile.coordinate.row) { + tilesToCheck = allTilesInRow(tile.coordinate.row); + } else if (tile.coordinate.column == emptyTile.coordinate.column) { + tilesToCheck = allTilesInColumn(tile.coordinate.column); + } + + boolean candidateRectInGameboard = (gameboardRect.contains(candidateRect)); + boolean collides = collidesWithTitles(candidateRect, tile, tilesToCheck); + + impossibleMove = impossibleMove && (!candidateRectInGameboard || collides); + } + if (!impossibleMove) { + // perform the move for all moved tiles in the descriptors + for (GameTileMotionDescriptor descriptor : currentMotionDescriptors) { + tile = descriptor.tile; + Pair xy = getXYFromEvent(tile, dxEvent, dyEvent, descriptor.direction); + tile.setXY(xy.first, xy.second); + } + } + } + + /** + * Computes new x,y coordinates for given tile in given direction (x or y). + * + * @param tile + * @param dxEvent + * change of x coordinate from touch gesture + * @param dyEvent + * change of y coordinate from touch gesture + * @param direction + * x or y direction + * @return pair of first x coordinates, second y coordinates + */ + private Pair getXYFromEvent(TileView tile, float dxEvent, float dyEvent, Direction direction) { + float dxTile = 0, dyTile = 0; + if (direction == Direction.X) { + dxTile = tile.getXPos() + dxEvent; + dyTile = tile.getYPos(); + } + if (direction == Direction.Y) { + dyTile = tile.getYPos() + dyEvent; + dxTile = tile.getXPos(); + } + return new Pair(dxTile, dyTile); + } + + /** + * @param candidateRect + * rectangle to check + * @param tile + * tile belonging to rectangle + * @param tilesToCheck + * list of tiles to check + * @return Whether candidateRect collides with any tilesToCheck + */ + private boolean collidesWithTitles(RectF candidateRect, TileView tile, ArrayList tilesToCheck) { + RectF otherTileRect; + for (TileView otherTile : tilesToCheck) { + if (!otherTile.isEmpty() && otherTile != tile) { + otherTileRect = new RectF(otherTile.getXPos(), otherTile.getYPos(), otherTile.getXPos() + + otherTile.getWidth(), otherTile.getYPos() + otherTile.getHeight()); + if (RectF.intersects(otherTileRect, candidateRect)) { + return true; + } + } + } + return false; + } + + /** + * Performs animation of currently moved tiles into empty space. Happens + * when valid tile is clicked or is dragged over 50%. + */ + private void animateTilesToEmptySpace() { + emptyTile.setXY(movedTile.getXPos(), movedTile.getYPos()); + emptyTile.coordinate = movedTile.coordinate; + ObjectAnimator animator; + for (final GameTileMotionDescriptor motionDescriptor : currentMotionDescriptors) { + animator = ObjectAnimator.ofObject(motionDescriptor.tile, motionDescriptor.direction.toString(), + new FloatEvaluator(), motionDescriptor.from, motionDescriptor.to); + animator.setDuration(16); + animator.addListener(new AnimatorListener() { + + public void onAnimationStart(Animator animation) { + } + + public void onAnimationCancel(Animator animation) { + } + + public void onAnimationRepeat(Animator animation) { + } + + public void onAnimationEnd(Animator animation) { + motionDescriptor.tile.coordinate = motionDescriptor.finalCoordinate; + motionDescriptor.tile.setXY(motionDescriptor.finalRect.left, motionDescriptor.finalRect.top); + } + }); + animator.start(); + } + } + + /** + * Performs animation of currently moved tiles back to origin. Happens when + * the drag was less than 50%. + */ + private void animateTilesBackToOrigin() { + ObjectAnimator animator; + if (currentMotionDescriptors != null) { + for (final GameTileMotionDescriptor motionDescriptor : currentMotionDescriptors) { + animator = ObjectAnimator.ofObject(motionDescriptor.tile, motionDescriptor.direction.toString(), + new FloatEvaluator(), motionDescriptor.currentPosition(), motionDescriptor.originalPosition()); + animator.setDuration(16); + animator.addListener(new AnimatorListener() { + + public void onAnimationStart(Animator animation) { + } + + public void onAnimationCancel(Animator animation) { + } + + public void onAnimationRepeat(Animator animation) { + } + + public void onAnimationEnd(Animator animation) { + motionDescriptor.tile.setXY(motionDescriptor.originalRect.left, + motionDescriptor.originalRect.top); + } + }); + animator.start(); + } + } + } + + /** + * Finds tiles between checked tile and empty tile and initializes motion + * descriptors for those tiles. + * + * @param tile + * A tile to be checked + * @return list of tiles between checked tile and empty tile + */ + private ArrayList getTilesBetweenEmptyTileAndTile(TileView tile) { + ArrayList descriptors = new ArrayList(); + Coordinate coordinate, finalCoordinate; + TileView foundTile; + GameTileMotionDescriptor motionDescriptor; + Rect finalRect, currentRect; + float axialDelta; + if (tile.isToRightOf(emptyTile)) { + // add all tiles left of the tile + for (int i = tile.coordinate.column; i > emptyTile.coordinate.column; i--) { + coordinate = new Coordinate(tile.coordinate.row, i); + foundTile = (tile.coordinate.matches(coordinate)) ? tile : getTileAtCoordinate(coordinate); + finalCoordinate = new Coordinate(tile.coordinate.row, i - 1); + currentRect = rectForCoordinate(foundTile.coordinate); + finalRect = rectForCoordinate(finalCoordinate); + axialDelta = Math.abs(foundTile.getXPos() - currentRect.left); + motionDescriptor = new GameTileMotionDescriptor(foundTile, Direction.X, foundTile.getXPos(), + finalRect.left); + motionDescriptor.finalCoordinate = finalCoordinate; + motionDescriptor.finalRect = finalRect; + motionDescriptor.axialDelta = axialDelta; + descriptors.add(motionDescriptor); + } + } else if (tile.isToLeftOf(emptyTile)) { + // add all tiles right of the tile + for (int i = tile.coordinate.column; i < emptyTile.coordinate.column; i++) { + coordinate = new Coordinate(tile.coordinate.row, i); + foundTile = (tile.coordinate.matches(coordinate)) ? tile : getTileAtCoordinate(coordinate); + finalCoordinate = new Coordinate(tile.coordinate.row, i + 1); + currentRect = rectForCoordinate(foundTile.coordinate); + finalRect = rectForCoordinate(finalCoordinate); + axialDelta = Math.abs(foundTile.getXPos() - currentRect.left); + motionDescriptor = new GameTileMotionDescriptor(foundTile, Direction.X, foundTile.getXPos(), + finalRect.left); + motionDescriptor.finalCoordinate = finalCoordinate; + motionDescriptor.finalRect = finalRect; + motionDescriptor.axialDelta = axialDelta; + descriptors.add(motionDescriptor); + } + } else if (tile.isAbove(emptyTile)) { + // add all tiles bellow the tile + for (int i = tile.coordinate.row; i < emptyTile.coordinate.row; i++) { + coordinate = new Coordinate(i, tile.coordinate.column); + foundTile = (tile.coordinate.matches(coordinate)) ? tile : getTileAtCoordinate(coordinate); + finalCoordinate = new Coordinate(i + 1, tile.coordinate.column); + currentRect = rectForCoordinate(foundTile.coordinate); + finalRect = rectForCoordinate(finalCoordinate); + axialDelta = Math.abs(foundTile.getYPos() - currentRect.top); + motionDescriptor = new GameTileMotionDescriptor(foundTile, Direction.Y, foundTile.getYPos(), + finalRect.top); + motionDescriptor.finalCoordinate = finalCoordinate; + motionDescriptor.finalRect = finalRect; + motionDescriptor.axialDelta = axialDelta; + descriptors.add(motionDescriptor); + } + } else if (tile.isBelow(emptyTile)) { + // add all tiles above the tile + for (int i = tile.coordinate.row; i > emptyTile.coordinate.row; i--) { + coordinate = new Coordinate(i, tile.coordinate.column); + foundTile = (tile.coordinate.matches(coordinate)) ? tile : getTileAtCoordinate(coordinate); + finalCoordinate = new Coordinate(i - 1, tile.coordinate.column); + currentRect = rectForCoordinate(foundTile.coordinate); + finalRect = rectForCoordinate(finalCoordinate); + axialDelta = Math.abs(foundTile.getYPos() - currentRect.top); + motionDescriptor = new GameTileMotionDescriptor(foundTile, Direction.Y, foundTile.getYPos(), + finalRect.top); + motionDescriptor.finalCoordinate = finalCoordinate; + motionDescriptor.finalRect = finalRect; + motionDescriptor.axialDelta = axialDelta; + descriptors.add(motionDescriptor); + } + } + return descriptors; + } + + /** + * @param coordinate + * coordinate of the tile + * @return tile at given coordinate + */ + private TileView getTileAtCoordinate(Coordinate coordinate) { + for (TileView tile : tiles) { + if (tile.coordinate.matches(coordinate)) { + return tile; + } + } + return null; + } + + /** + * @param row + * number of row + * @return list of tiles in the row + */ + private ArrayList allTilesInRow(int row) { + ArrayList tilesInRow = new ArrayList(); + for (TileView tile : tiles) { + if (tile.coordinate.row == row) { + tilesInRow.add(tile); + } + } + return tilesInRow; + } + + /** + * @param column + * number of column + * @return list of tiles in the column + */ + private ArrayList allTilesInColumn(int column) { + ArrayList tilesInColumn = new ArrayList(); + for (TileView tile : tiles) { + if (tile.coordinate.column == column) { + tilesInColumn.add(tile); + } + } + return tilesInColumn; + } + + /** + * @param coordinate + * @return Rectangle for given coordinate + */ + private Rect rectForCoordinate(Coordinate coordinate) { + int gameboardY = (int) Math.floor(gameboardRect.top); + int gameboardX = (int) Math.floor(gameboardRect.left); + int top = (coordinate.row * tileSize) + gameboardY; + int left = (coordinate.column * tileSize) + gameboardX; + return new Rect(left, top, left + tileSize, top + tileSize); + } + + /** + * Returns current tile locations. Useful for preserving state when + * orientation changes. + * + * @return current tile locations + */ + public LinkedList getTileOrder() { + LinkedList tileLocations = new LinkedList(); + for (int rowI = 0; rowI < GRID_SIZE; rowI++) { + for (int colI = 0; colI < GRID_SIZE; colI++) { + TileView tile = getTileAtCoordinate(new Coordinate(rowI, colI)); + tileLocations.add(tile.originalIndex); + } + } + return tileLocations; + } + + /** + * Sets tile locations from previous state. + * + * @param tileLocations + * list of integers marking order + */ + public void setTileOrder(LinkedList tileLocations) { + this.tileOrder = tileLocations; + } + + /** + * Describes movement of the tile. It is used to move several tiles at once. + */ + public class GameTileMotionDescriptor { + + public Rect finalRect, originalRect; + public Direction direction; // x or y + public TileView tile; + public float from, to, axialDelta; + public Coordinate finalCoordinate; + + public GameTileMotionDescriptor(TileView tile, Direction direction, float from, float to) { + super(); + this.tile = tile; + this.from = from; + this.to = to; + this.direction = direction; + this.originalRect = rectForCoordinate(tile.coordinate); + } + + /** + * @return current position of the tile + */ + public float currentPosition() { + if (direction == Direction.X) { + return tile.getXPos(); + } else if (direction == Direction.Y) { + return tile.getYPos(); + } + return 0; + } + + /** + * @return original position of the tile. It is used in movement to + * original position. + */ + public float originalPosition() { + if (direction == Direction.X) { + return originalRect.left; + } else if (direction == Direction.Y) { + return originalRect.top; + } + return 0; + } + + } + +} diff --git a/src/cz/destil/sliderpuzzle/ui/MainActivity.java b/src/cz/destil/sliderpuzzle/ui/MainActivity.java new file mode 100644 index 0000000..bd21df1 --- /dev/null +++ b/src/cz/destil/sliderpuzzle/ui/MainActivity.java @@ -0,0 +1,66 @@ +package cz.destil.sliderpuzzle.ui; + +import java.util.LinkedList; + +import android.content.Intent; +import android.os.Bundle; + +import com.actionbarsherlock.app.SherlockActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +import cz.destil.sliderpuzzle.R; + +/** + * + * Main activity where the game is played. + * + * @author David Vavra + * + */ +public class MainActivity extends SherlockActivity { + + private GameBoardView gameBoard; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + gameBoard = (GameBoardView) findViewById(R.id.gameboard); + // use preserved tile locations when orientation changed + @SuppressWarnings({ "deprecation", "unchecked" }) + final LinkedList tileOrder = (LinkedList) getLastNonConfigurationInstance(); + if (tileOrder != null) { + gameBoard.setTileOrder(tileOrder); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getSupportMenuInflater(); + inflater.inflate(R.menu.main_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.new_game: + gameBoard.setTileOrder(null); + gameBoard.fillTiles(); + return true; + case R.id.about: + startActivity(new Intent(this, AboutActivity.class)); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public Object onRetainNonConfigurationInstance() { + // preserve state when rotated + return gameBoard.getTileOrder(); + } +} \ No newline at end of file diff --git a/src/cz/destil/sliderpuzzle/ui/TileView.java b/src/cz/destil/sliderpuzzle/ui/TileView.java new file mode 100644 index 0000000..15ffb2b --- /dev/null +++ b/src/cz/destil/sliderpuzzle/ui/TileView.java @@ -0,0 +1,112 @@ +package cz.destil.sliderpuzzle.ui; + +import android.content.Context; +import android.os.Build; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import cz.destil.sliderpuzzle.data.Coordinate; + +/** + * + * ImageView displaying tile of the puzzle. Contains useful functions for + * comparing with other tiles. + * + * Based on: + * https://github.com/thillerson/Android-Slider-Puzzle/blob/master/src/ + * com/tackmobile/GameTile.java + * + * @author David Vavra + */ +public class TileView extends ImageView { + + public Coordinate coordinate; + public int originalIndex; + public int numberOfDrags; + private boolean empty; + + public TileView(Context context, int originalIndex) { + super(context); + this.originalIndex = originalIndex; + } + + public boolean isEmpty() { + return empty; + } + + public void setEmpty(boolean empty) { + this.empty = empty; + if (empty) { + setBackgroundDrawable(null); + setAlpha(0); + } + } + + public boolean isInRowOrColumnOf(TileView otherTile) { + return (coordinate.sharesAxisWith(otherTile.coordinate)); + } + + public boolean isToRightOf(TileView tile) { + return coordinate.isToRightOf(tile.coordinate); + } + + public boolean isToLeftOf(TileView tile) { + return coordinate.isToLeftOf(tile.coordinate); + } + + public boolean isAbove(TileView tile) { + return coordinate.isAbove(tile.coordinate); + } + + public boolean isBelow(TileView tile) { + return coordinate.isBelow(tile.coordinate); + } + + /** + * Sets X Y coordinate for the view - works for all Android versions. + * + * @param x + * @param y + */ + public void setXY(float x, float y) { + if (Build.VERSION.SDK_INT >= 11) { + // native and more precise + setX(x); + setY(y); + } else { + // emulated on older versions of Android + RelativeLayout.LayoutParams params = (android.widget.RelativeLayout.LayoutParams) getLayoutParams(); + params.leftMargin = (int) x; + params.topMargin = (int) y; + setLayoutParams(params); + } + } + + /** + * @return get x position for all versions of Android + */ + public float getXPos() { + if (Build.VERSION.SDK_INT >= 11) { + // native and more precise + return getX(); + } else { + // emulated on older versions of Android + RelativeLayout.LayoutParams params = (android.widget.RelativeLayout.LayoutParams) getLayoutParams(); + return params.leftMargin; + } + } + + /** + * @return get y position for all versions of Android + */ + public float getYPos() { + if (Build.VERSION.SDK_INT >= 11) { + // native and more precise + return getY(); + } else { + // emulated on older versions of Android + RelativeLayout.LayoutParams params = (android.widget.RelativeLayout.LayoutParams) getLayoutParams(); + return params.topMargin; + } + } + +} diff --git a/src/cz/destil/sliderpuzzle/util/TileSlicer.java b/src/cz/destil/sliderpuzzle/util/TileSlicer.java new file mode 100644 index 0000000..bac7a31 --- /dev/null +++ b/src/cz/destil/sliderpuzzle/util/TileSlicer.java @@ -0,0 +1,142 @@ +package cz.destil.sliderpuzzle.util; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import cz.destil.sliderpuzzle.ui.TileView; + +/** + * + * Slices original bitmap into tiles and adds border. Provides randomized or + * ordered access to tiles. + * + * Based on + * https://github.com/thillerson/Android-Slider-Puzzle/blob/master/src/com + * /tackmobile/TileServer.java + * + * @author David Vavra + */ +public class TileSlicer { + + private Bitmap original; + private int tileSize, gridSize; + private List slices; + private int lastSliceServed; + private List sliceOrder; + private Context context; + + /** + * Initializes TileSlicer. + * + * @param original + * Bitmap which should be sliced + * @param gridSize + * Grid size, for example 4 for 4x4 grid + */ + public TileSlicer(Bitmap original, int gridSize, Context context) { + super(); + this.original = original; + this.gridSize = gridSize; + this.tileSize = original.getWidth() / gridSize; + this.context = context; + slices = new LinkedList(); + sliceOriginal(); + } + + /** + * Slices original bitmap and adds border to slices. + */ + private void sliceOriginal() { + int x, y; + Bitmap bitmap; + lastSliceServed = 0; + for (int rowI = 0; rowI < gridSize; rowI++) { + for (int colI = 0; colI < gridSize; colI++) { + // don't slice last part - empty slice + if (rowI == gridSize - 1 && colI == gridSize - 1) { + continue; + } else { + x = rowI * tileSize; + y = colI * tileSize; + // slice + bitmap = Bitmap.createBitmap(original, x, y, tileSize, tileSize); + // draw border lines + Canvas canvas = new Canvas(bitmap); + Paint paint = new Paint(); + paint.setColor(Color.parseColor("#fbfdff")); + int end = tileSize - 1; + canvas.drawLine(0, 0, 0, end, paint); + canvas.drawLine(0, end, end, end, paint); + canvas.drawLine(end, end, end, 0, paint); + canvas.drawLine(end, 0, 0, 0, paint); + slices.add(bitmap); + } + } + } + // remove reference to original bitmap + original = null; + } + + /** + * Randomizes slices in case no previous state is available. + */ + public void randomizeSlices() { + // randomize first slices + Collections.shuffle(slices); + // last one is empty slice + slices.add(null); + sliceOrder = null; + } + + /** + * Sets slice order in case of previous instance is available, eg. from + * screen rotation. + * + * @param order + * list of integers marking order of slices + */ + public void setSliceOrder(List order) { + List newSlices = new LinkedList(); + for (int o : order) { + if (o < slices.size()) { + newSlices.add(slices.get(o)); + } else { + // empty slice + newSlices.add(null); + } + } + sliceOrder = order; + slices = newSlices; + } + + /** + * Serves slice and creates a tile for gameboard. + * + * @return TileView with the image or null if there are no more slices + */ + public TileView getTile() { + TileView tile = null; + if (slices.size() > 0) { + int originalIndex; + if (sliceOrder == null) { + originalIndex = lastSliceServed++; + } else { + originalIndex = sliceOrder.get(lastSliceServed++); + } + tile = new TileView(context, originalIndex); + if (slices.get(0) == null) { + // empty slice + tile.setEmpty(true); + } + tile.setImageBitmap(slices.remove(0)); + } + return tile; + } + +}