From 54c0b092cf25081cbf734f367ce0e431a5c393e9 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 May 2012 15:57:11 +0200 Subject: [PATCH 01/13] Renamed package, use full width for the puzzle. --- .project | 66 +++++++++---------- AndroidManifest.xml | 4 +- project.properties | 2 +- res/layout/main.xml | 4 +- .../destil/sliderpuzzle}/GameTile.java | 4 +- .../destil/sliderpuzzle}/GameboardView.java | 11 +++- .../sliderpuzzle}/SliderPuzzleActivity.java | 2 +- .../destil/sliderpuzzle}/TileServer.java | 2 +- 8 files changed, 50 insertions(+), 45 deletions(-) rename src/{com/tackmobile => cz/destil/sliderpuzzle}/GameTile.java (92%) rename src/{com/tackmobile => cz/destil/sliderpuzzle}/GameboardView.java (98%) rename src/{com/tackmobile => cz/destil/sliderpuzzle}/SliderPuzzleActivity.java (89%) rename src/{com/tackmobile => cz/destil/sliderpuzzle}/TileServer.java (97%) 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/AndroidManifest.xml b/AndroidManifest.xml index 5354c72..ac3e2dd 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ @@ -11,7 +11,7 @@ android:label="@string/app_name" android:theme="@android:style/Theme.NoTitleBar"> diff --git a/project.properties b/project.properties index 2cc58fa..8da376a 100644 --- a/project.properties +++ b/project.properties @@ -8,4 +8,4 @@ # project structure. # Project target. -target=android-12 +target=android-15 diff --git a/res/layout/main.xml b/res/layout/main.xml index ffc0192..a8a550c 100644 --- a/res/layout/main.xml +++ b/res/layout/main.xml @@ -2,8 +2,8 @@ - - + \ No newline at end of file diff --git a/src/com/tackmobile/GameTile.java b/src/cz/destil/sliderpuzzle/GameTile.java similarity index 92% rename from src/com/tackmobile/GameTile.java rename to src/cz/destil/sliderpuzzle/GameTile.java index 18deab3..89bc7ea 100644 --- a/src/com/tackmobile/GameTile.java +++ b/src/cz/destil/sliderpuzzle/GameTile.java @@ -1,6 +1,6 @@ -package com.tackmobile; +package cz.destil.sliderpuzzle; -import com.tackmobile.GameboardView.Coordinate; +import cz.destil.sliderpuzzle.GameboardView.Coordinate; import android.content.Context; import android.widget.ImageView; diff --git a/src/com/tackmobile/GameboardView.java b/src/cz/destil/sliderpuzzle/GameboardView.java similarity index 98% rename from src/com/tackmobile/GameboardView.java rename to src/cz/destil/sliderpuzzle/GameboardView.java index 91a47de..449c374 100644 --- a/src/com/tackmobile/GameboardView.java +++ b/src/cz/destil/sliderpuzzle/GameboardView.java @@ -1,4 +1,4 @@ -package com.tackmobile; +package cz.destil.sliderpuzzle; import java.util.ArrayList; import java.util.HashSet; @@ -360,8 +360,13 @@ protected GameTile createTileAtCoordinate(Coordinate coordinate) { 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 tileWidth = 0; + if (viewWidth > viewHeight) { + tileWidth = viewHeight/4; + } else { + tileWidth = viewWidth/4; + } + tileSize = new Size(tileWidth, tileWidth); int gameboardWidth = tileSize.width * 4; int gameboardHeight = tileSize.height * 4; int gameboardTop = viewHeight/2 - gameboardHeight/2; diff --git a/src/com/tackmobile/SliderPuzzleActivity.java b/src/cz/destil/sliderpuzzle/SliderPuzzleActivity.java similarity index 89% rename from src/com/tackmobile/SliderPuzzleActivity.java rename to src/cz/destil/sliderpuzzle/SliderPuzzleActivity.java index 2aeabc0..bce582b 100644 --- a/src/com/tackmobile/SliderPuzzleActivity.java +++ b/src/cz/destil/sliderpuzzle/SliderPuzzleActivity.java @@ -1,4 +1,4 @@ -package com.tackmobile; +package cz.destil.sliderpuzzle; import android.app.Activity; import android.os.Bundle; diff --git a/src/com/tackmobile/TileServer.java b/src/cz/destil/sliderpuzzle/TileServer.java similarity index 97% rename from src/com/tackmobile/TileServer.java rename to src/cz/destil/sliderpuzzle/TileServer.java index 3f9ac9e..10ace8f 100644 --- a/src/com/tackmobile/TileServer.java +++ b/src/cz/destil/sliderpuzzle/TileServer.java @@ -1,4 +1,4 @@ -package com.tackmobile; +package cz.destil.sliderpuzzle; import java.util.ArrayList; import java.util.HashSet; From 63993ef68e7ea928745674f19f0bade0eee016d4 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 May 2012 17:15:17 +0200 Subject: [PATCH 02/13] TileSlicer refactoring and decrease memory consumption --- res/layout/main.xml | 15 ++-- res/values/colors.xml | 7 ++ res/values/strings.xml | 3 +- src/cz/destil/sliderpuzzle/GameboardView.java | 19 ++--- src/cz/destil/sliderpuzzle/TileServer.java | 57 -------------- src/cz/destil/sliderpuzzle/TileSlicer.java | 76 +++++++++++++++++++ 6 files changed, 101 insertions(+), 76 deletions(-) create mode 100644 res/values/colors.xml delete mode 100644 src/cz/destil/sliderpuzzle/TileServer.java create mode 100644 src/cz/destil/sliderpuzzle/TileSlicer.java diff --git a/res/layout/main.xml b/res/layout/main.xml index a8a550c..1282b07 100644 --- a/res/layout/main.xml +++ b/res/layout/main.xml @@ -1,9 +1,8 @@ - - - - \ No newline at end of file + + + \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml new file mode 100644 index 0000000..0c90d62 --- /dev/null +++ b/res/values/colors.xml @@ -0,0 +1,7 @@ + + + + #585858 + #fbfdff + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 9ab8c13..2019772 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1,7 +1,6 @@ - Hello World, SliderPuzzleActivity! - SliderPuzzle + Slider Puzzle \ No newline at end of file diff --git a/src/cz/destil/sliderpuzzle/GameboardView.java b/src/cz/destil/sliderpuzzle/GameboardView.java index 449c374..f197e55 100644 --- a/src/cz/destil/sliderpuzzle/GameboardView.java +++ b/src/cz/destil/sliderpuzzle/GameboardView.java @@ -22,20 +22,21 @@ public class GameboardView extends RelativeLayout implements OnTouchListener { + public static final int GRID_SIZE = 4; //4x4 protected Size tileSize; protected RectF gameboardRect; protected HashSet tiles; protected GameTile emptyTile, movedTile; private boolean boardCreated; private PointF lastDragPoint; - private TileServer tileServer; + private TileSlicer tileSlicer; 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); + tileSlicer = new TileSlicer(original, GRID_SIZE); createTiles(); } @@ -62,13 +63,13 @@ protected void placeTile(GameTile tile) { params.topMargin = tileRect.top; params.leftMargin = tileRect.left; addView(tile, params); - tile.setImageBitmap(tileServer.serveRandomSlice()); + tile.setImageBitmap(tileSlicer.getRandomSlice()); } protected void createTiles() { tiles = new HashSet(); - for (int rowI=0; rowI<4; rowI++) { - for (int colI=0; colI<4; colI++) { + for (int rowI=0; rowI viewHeight) { - tileWidth = viewHeight/4; + tileWidth = viewHeight/GRID_SIZE; } else { - tileWidth = viewWidth/4; + tileWidth = viewWidth/GRID_SIZE; } tileSize = new Size(tileWidth, tileWidth); - int gameboardWidth = tileSize.width * 4; - int gameboardHeight = tileSize.height * 4; + int gameboardWidth = tileSize.width * GRID_SIZE; + int gameboardHeight = tileSize.height * GRID_SIZE; int gameboardTop = viewHeight/2 - gameboardHeight/2; int gameboardLeft = viewWidth/2 - gameboardWidth/2; gameboardRect = new RectF(gameboardLeft, gameboardTop, gameboardLeft + gameboardWidth, gameboardTop + gameboardHeight); diff --git a/src/cz/destil/sliderpuzzle/TileServer.java b/src/cz/destil/sliderpuzzle/TileServer.java deleted file mode 100644 index 10ace8f..0000000 --- a/src/cz/destil/sliderpuzzle/TileServer.java +++ /dev/null @@ -1,57 +0,0 @@ -package cz.destil.sliderpuzzle; - -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/TileSlicer.java b/src/cz/destil/sliderpuzzle/TileSlicer.java new file mode 100644 index 0000000..d38be28 --- /dev/null +++ b/src/cz/destil/sliderpuzzle/TileSlicer.java @@ -0,0 +1,76 @@ +package cz.destil.sliderpuzzle; + +import java.util.ArrayList; +import java.util.Random; + +import android.graphics.Bitmap; + +/** + * + * Slices original bitmap into tiles and adds border. Provides randomized 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 static final String TAG = "TileSlicer"; + private Bitmap original; + private int tileSize; + private ArrayList slices; + private Random random; + + /** + * 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) { + super(); + this.original = original; + this.tileSize = original.getWidth() / gridSize; + random = new Random(); + slices = new ArrayList(); + sliceOriginal(); + } + + /** + * Slices original bitmap and adds border to slices. + */ + private void sliceOriginal() { + 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(original, x, y, tileSize, tileSize); + slices.add(bitmap); + } + } + // remove original bitmap from memory + original = null; + } + + /** + * Serves random slice and frees it from memory + * + * @return Bitmap of random slice + */ + public Bitmap getRandomSlice() { + if (slices.size() > 0) { + int randomIndex = random.nextInt(slices.size()); + Bitmap drawable = slices.remove(randomIndex); + return drawable; + } + return null; + } + +} From 2b8ba7d5fefbd150ac5460e7f5d224d5f1832409 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 May 2012 18:00:46 +0200 Subject: [PATCH 03/13] borders to tiles --- res/values/colors.xml | 1 - src/cz/destil/sliderpuzzle/GameboardView.java | 179 ++++++++---------- src/cz/destil/sliderpuzzle/TileSlicer.java | 22 ++- 3 files changed, 101 insertions(+), 101 deletions(-) diff --git a/res/values/colors.xml b/res/values/colors.xml index 0c90d62..e136b98 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -2,6 +2,5 @@ #585858 - #fbfdff \ No newline at end of file diff --git a/src/cz/destil/sliderpuzzle/GameboardView.java b/src/cz/destil/sliderpuzzle/GameboardView.java index f197e55..f7d7c6b 100644 --- a/src/cz/destil/sliderpuzzle/GameboardView.java +++ b/src/cz/destil/sliderpuzzle/GameboardView.java @@ -21,8 +21,8 @@ import android.widget.RelativeLayout; public class GameboardView extends RelativeLayout implements OnTouchListener { - - public static final int GRID_SIZE = 4; //4x4 + + public static final int GRID_SIZE = 4; // 4x4 protected Size tileSize; protected RectF gameboardRect; protected HashSet tiles; @@ -31,16 +31,16 @@ public class GameboardView extends RelativeLayout implements OnTouchListener { private PointF lastDragPoint; private TileSlicer tileSlicer; protected ArrayList currentMotionDescriptors; - + public GameboardView(Context context, AttributeSet attrSet) { super(context, attrSet); Drawable globe = getResources().getDrawable(R.drawable.globe); - Bitmap original = ((BitmapDrawable)globe).getBitmap(); + Bitmap original = ((BitmapDrawable) globe).getBitmap(); tileSlicer = new TileSlicer(original, GRID_SIZE); - + createTiles(); } - + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); @@ -56,7 +56,7 @@ protected void placeTiles() { placeTile(tile); } } - + protected void placeTile(GameTile tile) { Rect tileRect = rectForCoordinate(tile.coordinate); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(tileSize.width, tileSize.height); @@ -68,10 +68,10 @@ protected void placeTile(GameTile tile) { protected void createTiles() { tiles = new HashSet(); - for (int rowI=0; rowI 0) { GameTileMotionDescriptor firstMotionDescriptor = currentMotionDescriptors.get(0); - if (firstMotionDescriptor.axialDelta > tileSize.width/2) { + if (firstMotionDescriptor.axialDelta > tileSize.width / 2) { return true; } } @@ -137,7 +138,7 @@ protected void moveDraggedTilesByMotionEventDelta(MotionEvent event) { 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) { @@ -145,10 +146,10 @@ protected void moveDraggedTilesByMotionEventDelta(MotionEvent event) { } 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) { @@ -167,11 +168,13 @@ protected void moveDraggedTilesByMotionEventDelta(MotionEvent event) { } } - protected boolean candidateRectForTileCollidesWithAnyTileInSet(RectF candidateRect, GameTile tile, HashSet set) { + 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()); + otherTileRect = new RectF(otherTile.getX(), otherTile.getY(), otherTile.getX() + otherTile.getWidth(), + otherTile.getY() + otherTile.getHeight()); if (RectF.intersects(otherTileRect, candidateRect)) { return true; } @@ -179,26 +182,27 @@ protected boolean candidateRectForTileCollidesWithAnyTileInSet(RectF candidateRe } 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 = 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 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); @@ -208,23 +212,25 @@ public void onAnimationEnd(Animator animation) { 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 = 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 onAnimationStart(Animator animation) { + } + + public void onAnimationCancel(Animator animation) { + } + + public void onAnimationRepeat(Animator animation) { + } + public void onAnimationEnd(Animator animation) { } }); @@ -243,17 +249,12 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Game 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); + 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 = new GameTileMotionDescriptor(foundTile, "x", foundTile.getX(), finalRect.left); motionDescriptor.finalCoordinate = finalCoordinate; motionDescriptor.finalRect = finalRect; motionDescriptor.axialDelta = axialDelta; @@ -262,17 +263,12 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Game } 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); + 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 = new GameTileMotionDescriptor(foundTile, "x", foundTile.getX(), finalRect.left); motionDescriptor.finalCoordinate = finalCoordinate; motionDescriptor.finalRect = finalRect; motionDescriptor.axialDelta = axialDelta; @@ -281,17 +277,12 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Game } 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); + 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 = new GameTileMotionDescriptor(foundTile, "y", foundTile.getY(), finalRect.top); motionDescriptor.finalCoordinate = finalCoordinate; motionDescriptor.finalRect = finalRect; motionDescriptor.axialDelta = axialDelta; @@ -300,17 +291,12 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Game } 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); + 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 = new GameTileMotionDescriptor(foundTile, "y", foundTile.getY(), finalRect.top); motionDescriptor.finalCoordinate = finalCoordinate; motionDescriptor.finalRect = finalRect; motionDescriptor.axialDelta = axialDelta; @@ -319,18 +305,16 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Game } 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) { @@ -361,18 +345,19 @@ protected GameTile createTileAtCoordinate(Coordinate coordinate) { protected void determineGameboardSizes() { int viewWidth = getWidth(); int viewHeight = getHeight(); - int tileWidth = 0; + int tileWidth = 0; if (viewWidth > viewHeight) { - tileWidth = viewHeight/GRID_SIZE; + tileWidth = viewHeight / GRID_SIZE; } else { - tileWidth = viewWidth/GRID_SIZE; + tileWidth = viewWidth / GRID_SIZE; } tileSize = new Size(tileWidth, tileWidth); int gameboardWidth = tileSize.width * GRID_SIZE; int gameboardHeight = tileSize.height * GRID_SIZE; - int gameboardTop = viewHeight/2 - gameboardHeight/2; - int gameboardLeft = viewWidth/2 - gameboardWidth/2; - gameboardRect = new RectF(gameboardLeft, gameboardTop, gameboardLeft + gameboardWidth, gameboardTop + gameboardHeight); + int gameboardTop = viewHeight / 2 - gameboardHeight / 2; + int gameboardLeft = viewWidth / 2 - gameboardWidth / 2; + gameboardRect = new RectF(gameboardLeft, gameboardTop, gameboardLeft + gameboardWidth, gameboardTop + + gameboardHeight); createTiles(); } @@ -383,21 +368,21 @@ protected Rect rectForCoordinate(Coordinate coordinate) { 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; @@ -429,22 +414,22 @@ public boolean isAbove(Coordinate coordinate) { 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; @@ -474,10 +459,10 @@ public float originalPosition() { @Override public String toString() { - return "GameTileMotionDescriptor [property=" + property + ", tile=" - + tile + ", from=" + from + ", to=" + to + "]"; + return "GameTileMotionDescriptor [property=" + property + ", tile=" + tile + ", from=" + from + ", to=" + + to + "]"; } - + } } diff --git a/src/cz/destil/sliderpuzzle/TileSlicer.java b/src/cz/destil/sliderpuzzle/TileSlicer.java index d38be28..514e9df 100644 --- a/src/cz/destil/sliderpuzzle/TileSlicer.java +++ b/src/cz/destil/sliderpuzzle/TileSlicer.java @@ -4,6 +4,9 @@ import java.util.Random; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; /** * @@ -18,9 +21,10 @@ */ public class TileSlicer { + @SuppressWarnings("unused") private static final String TAG = "TileSlicer"; private Bitmap original; - private int tileSize; + private int tileSize, gridSize; private ArrayList slices; private Random random; @@ -35,6 +39,7 @@ public class TileSlicer { public TileSlicer(Bitmap original, int gridSize) { super(); this.original = original; + this.gridSize = gridSize; this.tileSize = original.getWidth() / gridSize; random = new Random(); slices = new ArrayList(); @@ -47,15 +52,26 @@ public TileSlicer(Bitmap original, int gridSize) { private void sliceOriginal() { int x, y; Bitmap bitmap; - for (int rowI = 0; rowI < 4; rowI++) { - for (int colI = 0; colI < 4; colI++) { + for (int rowI = 0; rowI < gridSize; rowI++) { + for (int colI = 0; colI < gridSize; colI++) { 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 original bitmap from memory + original.recycle(); original = null; } From 077b7fc6bee9094e47fff5ea453d7dc380b3ae59 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 13 May 2012 19:21:29 +0200 Subject: [PATCH 04/13] refactoring, commenting --- AndroidManifest.xml | 4 +- res/layout/{main.xml => activity_main.xml} | 4 +- .../sliderpuzzle/SliderPuzzleActivity.java | 13 -- .../destil/sliderpuzzle/data/Coordinate.java | 43 ++++ .../sliderpuzzle/{ => ui}/GameTile.java | 25 +- .../sliderpuzzle/{ => ui}/GameboardView.java | 213 +++++++----------- .../sliderpuzzle/ui/SliderPuzzleActivity.java | 23 ++ .../sliderpuzzle/{ => util}/TileSlicer.java | 3 +- 8 files changed, 172 insertions(+), 156 deletions(-) rename res/layout/{main.xml => activity_main.xml} (58%) delete mode 100644 src/cz/destil/sliderpuzzle/SliderPuzzleActivity.java create mode 100644 src/cz/destil/sliderpuzzle/data/Coordinate.java rename src/cz/destil/sliderpuzzle/{ => ui}/GameTile.java (71%) rename src/cz/destil/sliderpuzzle/{ => ui}/GameboardView.java (76%) create mode 100644 src/cz/destil/sliderpuzzle/ui/SliderPuzzleActivity.java rename src/cz/destil/sliderpuzzle/{ => util}/TileSlicer.java (97%) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index ac3e2dd..f2f7142 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -11,8 +11,8 @@ android:label="@string/app_name" android:theme="@android:style/Theme.NoTitleBar"> + android:name=".ui.SliderPuzzleActivity" + android:label="@string/app_name"> diff --git a/res/layout/main.xml b/res/layout/activity_main.xml similarity index 58% rename from res/layout/main.xml rename to res/layout/activity_main.xml index 1282b07..b8ab47d 100644 --- a/res/layout/main.xml +++ b/res/layout/activity_main.xml @@ -1,8 +1,8 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/src/cz/destil/sliderpuzzle/SliderPuzzleActivity.java b/src/cz/destil/sliderpuzzle/SliderPuzzleActivity.java deleted file mode 100644 index bce582b..0000000 --- a/src/cz/destil/sliderpuzzle/SliderPuzzleActivity.java +++ /dev/null @@ -1,13 +0,0 @@ -package cz.destil.sliderpuzzle; - -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/cz/destil/sliderpuzzle/data/Coordinate.java b/src/cz/destil/sliderpuzzle/data/Coordinate.java new file mode 100644 index 0000000..ceae6ac --- /dev/null +++ b/src/cz/destil/sliderpuzzle/data/Coordinate.java @@ -0,0 +1,43 @@ +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); + } +} \ No newline at end of file diff --git a/src/cz/destil/sliderpuzzle/GameTile.java b/src/cz/destil/sliderpuzzle/ui/GameTile.java similarity index 71% rename from src/cz/destil/sliderpuzzle/GameTile.java rename to src/cz/destil/sliderpuzzle/ui/GameTile.java index 89bc7ea..252a328 100644 --- a/src/cz/destil/sliderpuzzle/GameTile.java +++ b/src/cz/destil/sliderpuzzle/ui/GameTile.java @@ -1,24 +1,29 @@ -package cz.destil.sliderpuzzle; - -import cz.destil.sliderpuzzle.GameboardView.Coordinate; +package cz.destil.sliderpuzzle.ui; +import cz.destil.sliderpuzzle.data.Coordinate; import android.content.Context; import android.widget.ImageView; +/** + * + * 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 GameTile extends ImageView { - + public Coordinate coordinate; - protected boolean empty; + private 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 RectF gameboardRect; + private ArrayList tiles; + private GameTile emptyTile, movedTile; private boolean boardCreated; private PointF lastDragPoint; - private TileSlicer tileSlicer; - protected ArrayList currentMotionDescriptors; + private int tileSize; + private ArrayList currentMotionDescriptors; public GameboardView(Context context, AttributeSet attrSet) { super(context, attrSet); - Drawable globe = getResources().getDrawable(R.drawable.globe); - Bitmap original = ((BitmapDrawable) globe).getBitmap(); - tileSlicer = new TileSlicer(original, GRID_SIZE); - - createTiles(); } @Override @@ -46,38 +55,73 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto super.onLayout(changed, left, top, right, bottom); if (!boardCreated) { determineGameboardSizes(); - placeTiles(); + // load image to slicer + Drawable globe = getResources().getDrawable(R.drawable.globe); + Bitmap original = ((BitmapDrawable) globe).getBitmap(); + TileSlicer tileSlicer = new TileSlicer(original, GRID_SIZE); + + fillTiles(tileSlicer); boardCreated = true; } } - - protected void placeTiles() { - for (GameTile tile : tiles) { - placeTile(tile); + + /** + * Detect gameboard 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; } + // leave a bit on the sides + tileSize -= 5; + 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); } - 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(tileSlicer.getRandomSlice()); - } - - protected void createTiles() { - tiles = new HashSet(); + /** + * Fills gameboard with tiles + * @param tileSlicer TileSlicer with loaded image + */ + private void fillTiles(TileSlicer tileSlicer) { + tiles = new ArrayList(); for (int rowI = 0; rowI < GRID_SIZE; rowI++) { for (int colI = 0; colI < GRID_SIZE; colI++) { - GameTile tile = createTileAtCoordinate(new Coordinate(rowI, colI)); + GameTile tile = new GameTile(getContext(), new Coordinate(rowI, colI)); + tile.setOnTouchListener(this); if (rowI == GRID_SIZE - 1 && colI == GRID_SIZE - 1) { + // empty tile emptyTile = tile; tile.setEmpty(true); + } else { + // tile with image - set random tile from slicer + tile.setImageBitmap(tileSlicer.getRandomSlice()); } + placeTile(tile); + tiles.add(tile); } } } + + /** + * Places tile on appropriate place in the layout + * @param tile Tile to place + */ + private void placeTile(GameTile 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); + } public boolean onTouch(View v, MotionEvent event) { try { @@ -121,14 +165,14 @@ public boolean onTouch(View v, MotionEvent event) { protected boolean lastDragMovedAtLeastHalfWay() { if (currentMotionDescriptors != null && currentMotionDescriptors.size() > 0) { GameTileMotionDescriptor firstMotionDescriptor = currentMotionDescriptors.get(0); - if (firstMotionDescriptor.axialDelta > tileSize.width / 2) { + if (firstMotionDescriptor.axialDelta > tileSize / 2) { return true; } } return false; } - protected void moveDraggedTilesByMotionEventDelta(MotionEvent event) { + private void moveDraggedTilesByMotionEventDelta(MotionEvent event) { boolean impossibleMove = true; float dxTile, dyTile; float dxEvent = event.getRawX() - lastDragPoint.x; @@ -168,7 +212,7 @@ protected void moveDraggedTilesByMotionEventDelta(MotionEvent event) { } } - protected boolean candidateRectForTileCollidesWithAnyTileInSet(RectF candidateRect, GameTile tile, + private boolean candidateRectForTileCollidesWithAnyTileInSet(RectF candidateRect, GameTile tile, HashSet set) { RectF otherTileRect; for (GameTile otherTile : set) { @@ -183,7 +227,7 @@ protected boolean candidateRectForTileCollidesWithAnyTileInSet(RectF candidateRe return false; } - protected void animateCurrentMovedTilesToEmptySpace() { + private void animateCurrentMovedTilesToEmptySpace() { emptyTile.setX(movedTile.getX()); emptyTile.setY(movedTile.getY()); emptyTile.coordinate = movedTile.coordinate; @@ -213,7 +257,7 @@ public void onAnimationEnd(Animator animation) { } } - protected void animateMovedTilesBackToOrigin() { + private void animateMovedTilesBackToOrigin() { ObjectAnimator animator; if (currentMotionDescriptors != null) { for (final GameTileMotionDescriptor motionDescriptor : currentMotionDescriptors) { @@ -306,7 +350,7 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Game return descriptors; } - protected GameTile getTileAtCoordinate(Coordinate coordinate) { + private GameTile getTileAtCoordinate(Coordinate coordinate) { for (GameTile tile : tiles) { if (tile.coordinate.matches(coordinate)) { return tile; @@ -315,7 +359,7 @@ protected GameTile getTileAtCoordinate(Coordinate coordinate) { return null; } - protected HashSet allTilesInRow(int row) { + private HashSet allTilesInRow(int row) { HashSet tilesInRow = new HashSet(); for (GameTile tile : tiles) { if (tile.coordinate.row == row) { @@ -325,7 +369,7 @@ protected HashSet allTilesInRow(int row) { return tilesInRow; } - protected HashSet allTilesInColumn(int column) { + private HashSet allTilesInColumn(int column) { HashSet tilesInColumn = new HashSet(); for (GameTile tile : tiles) { if (tile.coordinate.column == column) { @@ -335,91 +379,12 @@ protected HashSet allTilesInColumn(int column) { 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(); - int tileWidth = 0; - if (viewWidth > viewHeight) { - tileWidth = viewHeight / GRID_SIZE; - } else { - tileWidth = viewWidth / GRID_SIZE; - } - tileSize = new Size(tileWidth, tileWidth); - int gameboardWidth = tileSize.width * GRID_SIZE; - int gameboardHeight = tileSize.height * GRID_SIZE; - 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) { + private 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 + "]"; - } - + int top = (coordinate.row * tileSize) + gameboardY; + int left = (coordinate.column * tileSize) + gameboardX; + return new Rect(left, top, left + tileSize, top + tileSize); } public class GameTileMotionDescriptor { @@ -457,12 +422,6 @@ public float originalPosition() { return 0; } - @Override - public String toString() { - return "GameTileMotionDescriptor [property=" + property + ", tile=" + tile + ", from=" + from + ", to=" - + to + "]"; - } - } } diff --git a/src/cz/destil/sliderpuzzle/ui/SliderPuzzleActivity.java b/src/cz/destil/sliderpuzzle/ui/SliderPuzzleActivity.java new file mode 100644 index 0000000..1af6ee9 --- /dev/null +++ b/src/cz/destil/sliderpuzzle/ui/SliderPuzzleActivity.java @@ -0,0 +1,23 @@ +package cz.destil.sliderpuzzle.ui; + +import cz.destil.sliderpuzzle.R; +import android.app.Activity; +import android.os.Bundle; + +/** + * + * Main activity where the game is played. + * + * @author David Vavra + * + */ +public class SliderPuzzleActivity extends Activity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } + + // TODO: preserve state when rotated +} \ No newline at end of file diff --git a/src/cz/destil/sliderpuzzle/TileSlicer.java b/src/cz/destil/sliderpuzzle/util/TileSlicer.java similarity index 97% rename from src/cz/destil/sliderpuzzle/TileSlicer.java rename to src/cz/destil/sliderpuzzle/util/TileSlicer.java index 514e9df..0e7fe00 100644 --- a/src/cz/destil/sliderpuzzle/TileSlicer.java +++ b/src/cz/destil/sliderpuzzle/util/TileSlicer.java @@ -1,4 +1,4 @@ -package cz.destil.sliderpuzzle; +package cz.destil.sliderpuzzle.util; import java.util.ArrayList; import java.util.Random; @@ -71,7 +71,6 @@ private void sliceOriginal() { } } // remove original bitmap from memory - original.recycle(); original = null; } From 2f19f71b6884bcc83f263156a1c2c74caf938c0a Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 14 May 2012 11:40:53 +0200 Subject: [PATCH 05/13] better click handling --- AndroidManifest.xml | 8 +- .../destil/sliderpuzzle/data/Coordinate.java | 5 + .../destil/sliderpuzzle/ui/GameboardView.java | 156 ++++++++++-------- .../ui/{GameTile.java => TileView.java} | 14 +- 4 files changed, 107 insertions(+), 76 deletions(-) rename src/cz/destil/sliderpuzzle/ui/{GameTile.java => TileView.java} (74%) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f2f7142..0523cbc 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -4,15 +4,17 @@ android:versionCode="1" android:versionName="1.0" > - + + android:theme="@android:style/Theme.NoTitleBar" > + android:label="@string/app_name" > diff --git a/src/cz/destil/sliderpuzzle/data/Coordinate.java b/src/cz/destil/sliderpuzzle/data/Coordinate.java index ceae6ac..5b10cc1 100644 --- a/src/cz/destil/sliderpuzzle/data/Coordinate.java +++ b/src/cz/destil/sliderpuzzle/data/Coordinate.java @@ -40,4 +40,9 @@ public boolean isAbove(Coordinate coordinate) { 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/GameboardView.java b/src/cz/destil/sliderpuzzle/ui/GameboardView.java index f7d744f..4ccce9e 100644 --- a/src/cz/destil/sliderpuzzle/ui/GameboardView.java +++ b/src/cz/destil/sliderpuzzle/ui/GameboardView.java @@ -37,13 +37,15 @@ */ public class GameboardView extends RelativeLayout implements OnTouchListener { + @SuppressWarnings("unused") + private static final String TAG = "GameboardView"; public static final int GRID_SIZE = 4; // 4x4 - private RectF gameboardRect; - private ArrayList tiles; - private GameTile emptyTile, movedTile; + private int tileSize; + private ArrayList tiles; + private TileView emptyTile, movedTile; private boolean boardCreated; + private RectF gameboardRect; private PointF lastDragPoint; - private int tileSize; private ArrayList currentMotionDescriptors; public GameboardView(Context context, AttributeSet attrSet) { @@ -59,12 +61,12 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto Drawable globe = getResources().getDrawable(R.drawable.globe); Bitmap original = ((BitmapDrawable) globe).getBitmap(); TileSlicer tileSlicer = new TileSlicer(original, GRID_SIZE); - + fillTiles(tileSlicer); boardCreated = true; } } - + /** * Detect gameboard size and tile size based on current screen. */ @@ -89,80 +91,88 @@ private void determineGameboardSizes() { /** * Fills gameboard with tiles - * @param tileSlicer TileSlicer with loaded image + * + * @param tileSlicer + * TileSlicer with loaded image */ private void fillTiles(TileSlicer tileSlicer) { - tiles = new ArrayList(); + tiles = new ArrayList(); for (int rowI = 0; rowI < GRID_SIZE; rowI++) { for (int colI = 0; colI < GRID_SIZE; colI++) { - GameTile tile = new GameTile(getContext(), new Coordinate(rowI, colI)); - tile.setOnTouchListener(this); + TileView tile = new TileView(getContext(), new Coordinate(rowI, colI)); if (rowI == GRID_SIZE - 1 && colI == GRID_SIZE - 1) { // empty tile emptyTile = tile; tile.setEmpty(true); } else { // tile with image - set random tile from slicer - tile.setImageBitmap(tileSlicer.getRandomSlice()); + tile.setImageBitmap(tileSlicer.getRandomSlice()); } placeTile(tile); tiles.add(tile); } } } - + /** * Places tile on appropriate place in the layout - * @param tile Tile to place + * + * @param tile + * Tile to place */ - private void placeTile(GameTile tile) { + 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); + addView(tile, params); + tile.setOnTouchListener(this); } + /** + * Handling of touch events. High-level logic for moving tiles on gameboard. + */ 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; + TileView touchedTile = (TileView) v; + if (touchedTile.isEmpty() || !touchedTile.isInRowOrColumnOf(emptyTile)) { + return false; + } else { + // start of the gesture + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + movedTile = touchedTile; + currentMotionDescriptors = getTilesBetweenEmptyTileAndTile(movedTile); + // during the gesture + } else if (event.getActionMasked() == MotionEvent.ACTION_MOVE) { + if (lastDragPoint != null) { + moveDraggedTilesByMotionEventDelta(event); } - return true; + lastDragPoint = new PointF(event.getRawX(), event.getRawY()); + // end of gesture + } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { + // reload the motion descriptors in case of position change. + currentMotionDescriptors = getTilesBetweenEmptyTileAndTile(movedTile); + // if drag was over 50%, do the move + if (lastDragPoint != null && lastDragMovedAtLeastHalfWay()) { + animateCurrentMovedTilesToEmptySpace(); + // if it was a click, do the move + } else if (lastDragPoint == null || lastDragMovedMinimally()) { + animateCurrentMovedTilesToEmptySpace(); + // if it was a drag less than 50%, animate tiles back + } else { + animateMovedTilesBackToOrigin(); + } + currentMotionDescriptors = null; + lastDragPoint = null; + movedTile = null; } - } catch (ClassCastException e) { - return false; + return true; } } - protected boolean lastDragMovedAtLeastHalfWay() { + /** + * @return Whether last drag moved with the tile more than 50% of its size + */ + private boolean lastDragMovedAtLeastHalfWay() { if (currentMotionDescriptors != null && currentMotionDescriptors.size() > 0) { GameTileMotionDescriptor firstMotionDescriptor = currentMotionDescriptors.get(0); if (firstMotionDescriptor.axialDelta > tileSize / 2) { @@ -172,19 +182,33 @@ protected boolean lastDragMovedAtLeastHalfWay() { return false; } + /** + * @return Whether last drag moved just a little = involuntary move during + * click + */ + private boolean lastDragMovedMinimally() { + if (currentMotionDescriptors != null && currentMotionDescriptors.size() > 0) { + GameTileMotionDescriptor firstMotionDescriptor = currentMotionDescriptors.get(0); + if (firstMotionDescriptor.axialDelta < tileSize / 20) { + return true; + } + } + return false; + } + private void moveDraggedTilesByMotionEventDelta(MotionEvent event) { boolean impossibleMove = true; float dxTile, dyTile; float dxEvent = event.getRawX() - lastDragPoint.x; float dyEvent = event.getRawY() - lastDragPoint.y; - GameTile tile; + TileView 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; + HashSet tilesToCheck = null; if (tile.coordinate.row == emptyTile.coordinate.row) { tilesToCheck = allTilesInRow(tile.coordinate.row); } else if (tile.coordinate.column == emptyTile.coordinate.column) { @@ -212,10 +236,10 @@ private void moveDraggedTilesByMotionEventDelta(MotionEvent event) { } } - private boolean candidateRectForTileCollidesWithAnyTileInSet(RectF candidateRect, GameTile tile, - HashSet set) { + private boolean candidateRectForTileCollidesWithAnyTileInSet(RectF candidateRect, TileView tile, + HashSet set) { RectF otherTileRect; - for (GameTile otherTile : set) { + for (TileView otherTile : set) { if (!otherTile.isEmpty() && otherTile != tile) { otherTileRect = new RectF(otherTile.getX(), otherTile.getY(), otherTile.getX() + otherTile.getWidth(), otherTile.getY() + otherTile.getHeight()); @@ -283,10 +307,10 @@ public void onAnimationEnd(Animator animation) { } } - private ArrayList getTilesBetweenEmptyTileAndTile(GameTile tile) { + private ArrayList getTilesBetweenEmptyTileAndTile(TileView tile) { ArrayList descriptors = new ArrayList(); Coordinate coordinate, finalCoordinate; - GameTile foundTile; + TileView foundTile; GameTileMotionDescriptor motionDescriptor; Rect finalRect, currentRect; float axialDelta; @@ -350,8 +374,8 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Game return descriptors; } - private GameTile getTileAtCoordinate(Coordinate coordinate) { - for (GameTile tile : tiles) { + private TileView getTileAtCoordinate(Coordinate coordinate) { + for (TileView tile : tiles) { if (tile.coordinate.matches(coordinate)) { return tile; } @@ -359,9 +383,9 @@ private GameTile getTileAtCoordinate(Coordinate coordinate) { return null; } - private HashSet allTilesInRow(int row) { - HashSet tilesInRow = new HashSet(); - for (GameTile tile : tiles) { + private HashSet allTilesInRow(int row) { + HashSet tilesInRow = new HashSet(); + for (TileView tile : tiles) { if (tile.coordinate.row == row) { tilesInRow.add(tile); } @@ -369,9 +393,9 @@ private HashSet allTilesInRow(int row) { return tilesInRow; } - private HashSet allTilesInColumn(int column) { - HashSet tilesInColumn = new HashSet(); - for (GameTile tile : tiles) { + private HashSet allTilesInColumn(int column) { + HashSet tilesInColumn = new HashSet(); + for (TileView tile : tiles) { if (tile.coordinate.column == column) { tilesInColumn.add(tile); } @@ -391,11 +415,11 @@ public class GameTileMotionDescriptor { public Rect finalRect; public String property; - public GameTile tile; + public TileView tile; public float from, to, axialDelta; public Coordinate finalCoordinate; - public GameTileMotionDescriptor(GameTile tile, String property, float from, float to) { + public GameTileMotionDescriptor(TileView tile, String property, float from, float to) { super(); this.tile = tile; this.from = from; diff --git a/src/cz/destil/sliderpuzzle/ui/GameTile.java b/src/cz/destil/sliderpuzzle/ui/TileView.java similarity index 74% rename from src/cz/destil/sliderpuzzle/ui/GameTile.java rename to src/cz/destil/sliderpuzzle/ui/TileView.java index 252a328..b29c638 100644 --- a/src/cz/destil/sliderpuzzle/ui/GameTile.java +++ b/src/cz/destil/sliderpuzzle/ui/TileView.java @@ -15,12 +15,12 @@ * * @author David Vavra */ -public class GameTile extends ImageView { +public class TileView extends ImageView { public Coordinate coordinate; private boolean empty; - public GameTile(Context context, Coordinate coordinate) { + public TileView(Context context, Coordinate coordinate) { super(context); this.coordinate = coordinate; } @@ -37,23 +37,23 @@ public void setEmpty(boolean empty) { } } - public boolean isInRowOrColumnOf(GameTile otherTile) { + public boolean isInRowOrColumnOf(TileView otherTile) { return (coordinate.sharesAxisWith(otherTile.coordinate)); } - public boolean isToRightOf(GameTile tile) { + public boolean isToRightOf(TileView tile) { return coordinate.isToRightOf(tile.coordinate); } - public boolean isToLeftOf(GameTile tile) { + public boolean isToLeftOf(TileView tile) { return coordinate.isToLeftOf(tile.coordinate); } - public boolean isAbove(GameTile tile) { + public boolean isAbove(TileView tile) { return coordinate.isAbove(tile.coordinate); } - public boolean isBelow(GameTile tile) { + public boolean isBelow(TileView tile) { return coordinate.isBelow(tile.coordinate); } From 294a22ef718edb5e1e1954733daaca3de6179d81 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 14 May 2012 14:44:24 +0200 Subject: [PATCH 06/13] actionbarsherlock integration --- .classpath | 16 +-- .settings/org.eclipse.jdt.core.prefs | 6 +- AndroidManifest.xml | 4 +- project.properties | 1 + res/menu/main_menu.xml | 13 +++ res/values/strings.xml | 5 + .../destil/sliderpuzzle/ui/GameboardView.java | 101 ++++++++++++++---- .../destil/sliderpuzzle/ui/MainActivity.java | 47 ++++++++ .../sliderpuzzle/ui/SliderPuzzleActivity.java | 23 ---- 9 files changed, 160 insertions(+), 56 deletions(-) create mode 100644 res/menu/main_menu.xml create mode 100644 src/cz/destil/sliderpuzzle/ui/MainActivity.java delete mode 100644 src/cz/destil/sliderpuzzle/ui/SliderPuzzleActivity.java diff --git a/.classpath b/.classpath index a4763d1..a4f1e40 100644 --- a/.classpath +++ b/.classpath @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + 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 0523cbc..f7f8dfa 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -11,9 +11,9 @@ + android:theme="@style/Theme.Sherlock" > diff --git a/project.properties b/project.properties index 8da376a..a39542f 100644 --- a/project.properties +++ b/project.properties @@ -9,3 +9,4 @@ # Project target. target=android-15 +android.library.reference.1=../ActionBarSherlock/library 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/strings.xml b/res/values/strings.xml index 2019772..553bde1 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1,6 +1,11 @@ + Slider Puzzle + + New Game + + About \ No newline at end of file diff --git a/src/cz/destil/sliderpuzzle/ui/GameboardView.java b/src/cz/destil/sliderpuzzle/ui/GameboardView.java index 4ccce9e..a2e3e30 100644 --- a/src/cz/destil/sliderpuzzle/ui/GameboardView.java +++ b/src/cz/destil/sliderpuzzle/ui/GameboardView.java @@ -1,7 +1,6 @@ package cz.destil.sliderpuzzle.ui; import java.util.ArrayList; -import java.util.HashSet; import android.animation.Animator; import android.animation.Animator.AnimatorListener; @@ -144,7 +143,7 @@ public boolean onTouch(View v, MotionEvent event) { // during the gesture } else if (event.getActionMasked() == MotionEvent.ACTION_MOVE) { if (lastDragPoint != null) { - moveDraggedTilesByMotionEventDelta(event); + followFinger(event); } lastDragPoint = new PointF(event.getRawX(), event.getRawY()); // end of gesture @@ -153,13 +152,13 @@ public boolean onTouch(View v, MotionEvent event) { currentMotionDescriptors = getTilesBetweenEmptyTileAndTile(movedTile); // if drag was over 50%, do the move if (lastDragPoint != null && lastDragMovedAtLeastHalfWay()) { - animateCurrentMovedTilesToEmptySpace(); + animateTilesToEmptySpace(); // if it was a click, do the move } else if (lastDragPoint == null || lastDragMovedMinimally()) { - animateCurrentMovedTilesToEmptySpace(); + animateTilesToEmptySpace(); // if it was a drag less than 50%, animate tiles back } else { - animateMovedTilesBackToOrigin(); + animateTilesBackToOrigin(); } currentMotionDescriptors = null; lastDragPoint = null; @@ -196,7 +195,13 @@ private boolean lastDragMovedMinimally() { return false; } - private void moveDraggedTilesByMotionEventDelta(MotionEvent event) { + /** + * 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 dxTile, dyTile; float dxEvent = event.getRawX() - lastDragPoint.x; @@ -206,9 +211,9 @@ private void moveDraggedTilesByMotionEventDelta(MotionEvent event) { tile = gameTileMotionDescriptor.tile; dxTile = tile.getX() + dxEvent; dyTile = tile.getY() + dyEvent; - + // detect if this move is valid RectF candidateRect = new RectF(dxTile, dyTile, dxTile + tile.getWidth(), dyTile + tile.getHeight()); - HashSet tilesToCheck = null; + ArrayList tilesToCheck = null; if (tile.coordinate.row == emptyTile.coordinate.row) { tilesToCheck = allTilesInRow(tile.coordinate.row); } else if (tile.coordinate.column == emptyTile.coordinate.column) { @@ -216,11 +221,12 @@ private void moveDraggedTilesByMotionEventDelta(MotionEvent event) { } boolean candidateRectInGameboard = (gameboardRect.contains(candidateRect)); - boolean collides = candidateRectForTileCollidesWithAnyTileInSet(candidateRect, tile, tilesToCheck); + boolean collides = collidesWithTitles(candidateRect, tile, tilesToCheck); impossibleMove = impossibleMove && (!candidateRectInGameboard || collides); } if (!impossibleMove) { + // perform move for all moved tiles in the descriptors for (GameTileMotionDescriptor gameTileMotionDescriptor : currentMotionDescriptors) { tile = gameTileMotionDescriptor.tile; dxTile = tile.getX() + dxEvent; @@ -236,10 +242,18 @@ private void moveDraggedTilesByMotionEventDelta(MotionEvent event) { } } - private boolean candidateRectForTileCollidesWithAnyTileInSet(RectF candidateRect, TileView tile, - HashSet set) { + /** + * @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 : set) { + for (TileView otherTile : tilesToCheck) { if (!otherTile.isEmpty() && otherTile != tile) { otherTileRect = new RectF(otherTile.getX(), otherTile.getY(), otherTile.getX() + otherTile.getWidth(), otherTile.getY() + otherTile.getHeight()); @@ -251,7 +265,11 @@ private boolean candidateRectForTileCollidesWithAnyTileInSet(RectF candidateRect return false; } - private void animateCurrentMovedTilesToEmptySpace() { + /** + * Performs animation of currently moved tiles into empty space. Happens + * when valid tile is clicked or is dragged over 50%. + */ + private void animateTilesToEmptySpace() { emptyTile.setX(movedTile.getX()); emptyTile.setY(movedTile.getY()); emptyTile.coordinate = movedTile.coordinate; @@ -281,7 +299,11 @@ public void onAnimationEnd(Animator animation) { } } - private void animateMovedTilesBackToOrigin() { + /** + * 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) { @@ -307,6 +329,14 @@ public void onAnimationEnd(Animator animation) { } } + /** + * 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; @@ -315,6 +345,7 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Tile 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); @@ -329,6 +360,7 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Tile 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); @@ -343,6 +375,7 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Tile 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); @@ -357,6 +390,7 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Tile 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); @@ -374,6 +408,11 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Tile 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)) { @@ -383,8 +422,13 @@ private TileView getTileAtCoordinate(Coordinate coordinate) { return null; } - private HashSet allTilesInRow(int row) { - HashSet tilesInRow = new HashSet(); + /** + * @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); @@ -393,8 +437,13 @@ private HashSet allTilesInRow(int row) { return tilesInRow; } - private HashSet allTilesInColumn(int column) { - HashSet tilesInColumn = new HashSet(); + /** + * @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); @@ -403,6 +452,10 @@ private HashSet allTilesInColumn(int column) { 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); @@ -411,10 +464,13 @@ private Rect rectForCoordinate(Coordinate coordinate) { return new Rect(left, top, left + tileSize, top + tileSize); } + /** + * Describes movement of the tile. It is used to move several tiles at once. + */ public class GameTileMotionDescriptor { public Rect finalRect; - public String property; + public String property; // "x" or "y" public TileView tile; public float from, to, axialDelta; public Coordinate finalCoordinate; @@ -427,6 +483,9 @@ public GameTileMotionDescriptor(TileView tile, String property, float from, floa this.property = property; } + /** + * @return current position of the tile + */ public float currentPosition() { if (property.equals("x")) { return tile.getX(); @@ -436,6 +495,10 @@ public float currentPosition() { return 0; } + /** + * @return original position of the tile. It is used in movement to + * original position. + */ public float originalPosition() { Rect originalRect = rectForCoordinate(tile.coordinate); if (property.equals("x")) { diff --git a/src/cz/destil/sliderpuzzle/ui/MainActivity.java b/src/cz/destil/sliderpuzzle/ui/MainActivity.java new file mode 100644 index 0000000..8e702eb --- /dev/null +++ b/src/cz/destil/sliderpuzzle/ui/MainActivity.java @@ -0,0 +1,47 @@ +package cz.destil.sliderpuzzle.ui; + +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 { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } + + @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: + return true; + case R.id.about: + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + // TODO: preserve state when rotated +} \ No newline at end of file diff --git a/src/cz/destil/sliderpuzzle/ui/SliderPuzzleActivity.java b/src/cz/destil/sliderpuzzle/ui/SliderPuzzleActivity.java deleted file mode 100644 index 1af6ee9..0000000 --- a/src/cz/destil/sliderpuzzle/ui/SliderPuzzleActivity.java +++ /dev/null @@ -1,23 +0,0 @@ -package cz.destil.sliderpuzzle.ui; - -import cz.destil.sliderpuzzle.R; -import android.app.Activity; -import android.os.Bundle; - -/** - * - * Main activity where the game is played. - * - * @author David Vavra - * - */ -public class SliderPuzzleActivity extends Activity { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - } - - // TODO: preserve state when rotated -} \ No newline at end of file From 9dcbba33362c8c129ce09502098b89c2aab28537 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 14 May 2012 17:40:59 +0200 Subject: [PATCH 07/13] About screen, new icon, refactoring for making saving state possible --- AndroidManifest.xml | 7 ++- res/drawable-hdpi/ic_launcher.png | Bin 4147 -> 10820 bytes res/drawable-hdpi/settle_up.png | Bin 0 -> 7162 bytes res/drawable-ldpi/ic_launcher.png | Bin 1723 -> 0 bytes res/drawable-mdpi/ic_launcher.png | Bin 2574 -> 5389 bytes res/layout/activity_about.xml | 52 ++++++++++++++++++ res/layout/activity_main.xml | 13 +++-- res/values/colors.xml | 6 -- res/values/strings.xml | 13 ++++- .../destil/sliderpuzzle/ui/AboutActivity.java | 46 ++++++++++++++++ .../destil/sliderpuzzle/ui/GameboardView.java | 34 ++++-------- .../destil/sliderpuzzle/ui/MainActivity.java | 4 ++ src/cz/destil/sliderpuzzle/ui/TileView.java | 5 +- .../destil/sliderpuzzle/util/TileSlicer.java | 38 +++++++++---- 14 files changed, 172 insertions(+), 46 deletions(-) create mode 100644 res/drawable-hdpi/settle_up.png delete mode 100644 res/drawable-ldpi/ic_launcher.png create mode 100644 res/layout/activity_about.xml delete mode 100644 res/values/colors.xml create mode 100644 src/cz/destil/sliderpuzzle/ui/AboutActivity.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f7f8dfa..8f0180d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -11,7 +11,7 @@ + android:theme="@style/Theme.Sherlock.ForceOverflow" > @@ -21,6 +21,11 @@ + + + \ No newline at end of file diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png index 8074c4c571b8cd19e27f4ee5545df367420686d7..76b70a471a6396b7cafaa7faed40dc3714434b5e 100644 GIT binary patch literal 10820 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 diff --git a/res/drawable-hdpi/settle_up.png b/res/drawable-hdpi/settle_up.png new file mode 100644 index 0000000000000000000000000000000000000000..1e1772abc9996e822f2f05f603b5f95e73be9404 GIT binary patch literal 7162 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 literal 0 HcmV?d00001 diff --git a/res/drawable-ldpi/ic_launcher.png b/res/drawable-ldpi/ic_launcher.png deleted file mode 100644 index 1095584ec21f71cd0afc9e0993aa2209671b590c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1723 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 delta 2565 zcmV+g3i|bpDvlJ8BYz3iNklQ`Og{P|9^0MHK0_ue5La$BvqW(E{R@m;+b z+04~`K@~=KAlzM05bd(Nnm)?dxa?uEJXv zeu+q80Dqrd@f`f#Ip`ZXfIAz$i21em;CTN_=xEu3VAVXlcIsQuGdfb46xxDaSk?R` zH2PP;NRz%=?4~pTga_dz#}k`EC}jbv!cw~BHsU&lZlE=|p3FXv_J;eB)>By1vJIEU zj-b8%el*o|VkG)LqNx#TG>Jvj^jIte!!+RY)PJLY@)%lY-vfVDGlHHLY+U$V^iLdv zrg-Q+#?(5pRz7B(*J9e@sgV7wrlqlyV{Kn>8(4v2;-jwVy`N*NVyb04`0ujrO_sVN?6}QB&QD z4GSJe%j~t7rPZQi-XruqkF`oeU>Uj=euKg#v9@g+*0gTHJN>UCC)o`?7zD@J3ChBdeQ!oN8-bK z)HDs<-97KETX)x2xXJ)=m{^cXOT|Ert2__P^~`+Z&V#E9S;5N`C++P3qk^?v1rVm~ zjBq;ys6=tD0*LR676fk>l#4%C0c@r#(S$vBrrWXt+)4|uIHPyU|>La6h%92y4IPufI$9>Xu!@y z`TaNgtg&41@PwMwBdmSm7)xAWDStg10dJ$le#VeeKMN{LP+@#9)=Dy}s; zItmd)fR<7PtqBfRa1}tbFQyiv?qISgtA=i5gaug^9(5La2LFf?gTH4Ln}4k+ErF;= z-IQ`Bh0yq$6zokLP=AUI^S+6dO%L-+9m!-89*>7Y@(fexVMT0Y#VqQ+u?`Qld2!+h zqUjMX>VbzxMA73u+GgKvc?r?JS(VpwP!0HUiC7TuULkc_CJBKkqFd8gJbY!X=N|2>}SVTgb;KG z8CTHJxE0O8PV71L7+pwiVw}GVjqS6>G`g99Zl)=$51*L-G}qEGnSbbqHaCQ79+E=u zUXCOZ35jAMRz%R%0(P!0FMv=sk>Nr8%+OzY^c-M9@+ zfz=G`qj2+%Zca?2GF_~Ag8Z{+AH9ziZejL@FI=nNq3nz#ELXAuA;T*>;hBqKa81dvd z<6{SYg-4wiWq)s33zYnF+JI;-;N=Ylg%+L3uVj~+sx~~d^k>|VJ=ov3eJauU2EUa}8?jjAVjIdWLN~x|EkQziRAeJZ(CKl9w41uafUTzJe$=`uZi++SY2K7JA ze8&>W!#h=xt|D?2FrFI2zR-_dmv$9EzO;Scl}c_fpno-CaqR=VUaUvk>@F_89b@tH zs8R!*QKY;INJ<2_U+K6Ca3e9Gsl2{qY0%a7J?uICWgHuLfj+MB=GkAN1&ifT#2u}B z+2S#~$5jA(Qn^;H%CCmIae4AE-Dsng|Hl*Ov!z72k3ZnJs{pp|+pW`DDueC#mEWOf z=ucJ!dVgwUH@>j^1>TL0u`W8w;Jx`gA+b`%0Pb1H4q!VDoLX66B>p}bl~W&?H8pNf zf56)2Czv7W0=b~Qsa8^~W179L%2Mj#vRQCqI=|0g@wJLYT$WSW0u+`LdvR`TFYo?W zayL$LjI=%o=2*#5$-d=&T?LTKRr`!ux`mZK+kfjHWDJu_CL5173+G1viO%M)LDgs( zNkBJ&R^GCHEN-WinlC}A7?9kRIDY*2SZ{CdB=-Xmlu3c=bp$!2FUGt5ujP&Eh%~%T zAMQdgEh5EUr9e)lIFdhgjYQNO<97+7V?K4FDt+}iT+enfv5qJ;aLMZpj@?R-eY*9u||RB?bS+IKo}Kc5yWnb blIs5eNSY+j744LC00000NkvXXu0mjfFO}TS diff --git a/res/layout/activity_about.xml b/res/layout/activity_about.xml new file mode 100644 index 0000000..aa66a97 --- /dev/null +++ b/res/layout/activity_about.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/activity_main.xml b/res/layout/activity_main.xml index b8ab47d..7b8f7c2 100644 --- a/res/layout/activity_main.xml +++ b/res/layout/activity_main.xml @@ -1,8 +1,13 @@ - + android:padding="5dp" > - \ No newline at end of file + + + + \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml deleted file mode 100644 index e136b98..0000000 --- a/res/values/colors.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - #585858 - - \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 553bde1..1f0616e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7,5 +7,16 @@ 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/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 index a2e3e30..f75f425 100644 --- a/src/cz/destil/sliderpuzzle/ui/GameboardView.java +++ b/src/cz/destil/sliderpuzzle/ui/GameboardView.java @@ -36,8 +36,6 @@ */ public class GameboardView extends RelativeLayout implements OnTouchListener { - @SuppressWarnings("unused") - private static final String TAG = "GameboardView"; public static final int GRID_SIZE = 4; // 4x4 private int tileSize; private ArrayList tiles; @@ -56,12 +54,7 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto super.onLayout(changed, left, top, right, bottom); if (!boardCreated) { determineGameboardSizes(); - // load image to slicer - Drawable globe = getResources().getDrawable(R.drawable.globe); - Bitmap original = ((BitmapDrawable) globe).getBitmap(); - TileSlicer tileSlicer = new TileSlicer(original, GRID_SIZE); - - fillTiles(tileSlicer); + fillTiles(); boardCreated = true; } } @@ -78,8 +71,6 @@ private void determineGameboardSizes() { } else { tileSize = viewWidth / GRID_SIZE; } - // leave a bit on the sides - tileSize -= 5; int gameboardSize = tileSize * GRID_SIZE; // center gameboard int gameboardTop = viewHeight / 2 - gameboardSize / 2; @@ -89,23 +80,22 @@ private void determineGameboardSizes() { } /** - * Fills gameboard with tiles - * - * @param tileSlicer - * TileSlicer with loaded image + * Fills gameboard with tiles sliced from the globe image. */ - private void fillTiles(TileSlicer tileSlicer) { + 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()); + // fill gameboard with slices tiles = new ArrayList(); for (int rowI = 0; rowI < GRID_SIZE; rowI++) { for (int colI = 0; colI < GRID_SIZE; colI++) { - TileView tile = new TileView(getContext(), new Coordinate(rowI, colI)); - if (rowI == GRID_SIZE - 1 && colI == GRID_SIZE - 1) { - // empty tile + TileView tile = tileSlicer.getSlice(TileSlicer.RANDOM_SLICE); + tile.coordinate = new Coordinate(rowI, colI); + if (tile.isEmpty()) { emptyTile = tile; - tile.setEmpty(true); - } else { - // tile with image - set random tile from slicer - tile.setImageBitmap(tileSlicer.getRandomSlice()); } placeTile(tile); tiles.add(tile); diff --git a/src/cz/destil/sliderpuzzle/ui/MainActivity.java b/src/cz/destil/sliderpuzzle/ui/MainActivity.java index 8e702eb..7473318 100644 --- a/src/cz/destil/sliderpuzzle/ui/MainActivity.java +++ b/src/cz/destil/sliderpuzzle/ui/MainActivity.java @@ -1,5 +1,6 @@ package cz.destil.sliderpuzzle.ui; +import android.content.Intent; import android.os.Bundle; import com.actionbarsherlock.app.SherlockActivity; @@ -35,8 +36,11 @@ public boolean onCreateOptionsMenu(Menu menu) { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.new_game: + GameboardView gameboard = (GameboardView) findViewById(R.id.gameboard); + gameboard.fillTiles(); return true; case R.id.about: + startActivity(new Intent(this, AboutActivity.class)); return true; default: return super.onOptionsItemSelected(item); diff --git a/src/cz/destil/sliderpuzzle/ui/TileView.java b/src/cz/destil/sliderpuzzle/ui/TileView.java index b29c638..c26c035 100644 --- a/src/cz/destil/sliderpuzzle/ui/TileView.java +++ b/src/cz/destil/sliderpuzzle/ui/TileView.java @@ -18,11 +18,12 @@ public class TileView extends ImageView { public Coordinate coordinate; + public int originalIndex; private boolean empty; - public TileView(Context context, Coordinate coordinate) { + public TileView(Context context, int originalIndex) { super(context); - this.coordinate = coordinate; + this.originalIndex = originalIndex; } public boolean isEmpty() { diff --git a/src/cz/destil/sliderpuzzle/util/TileSlicer.java b/src/cz/destil/sliderpuzzle/util/TileSlicer.java index 0e7fe00..acdd439 100644 --- a/src/cz/destil/sliderpuzzle/util/TileSlicer.java +++ b/src/cz/destil/sliderpuzzle/util/TileSlicer.java @@ -3,10 +3,12 @@ import java.util.ArrayList; import java.util.Random; +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; /** * @@ -21,12 +23,12 @@ */ public class TileSlicer { - @SuppressWarnings("unused") - private static final String TAG = "TileSlicer"; + public static final int RANDOM_SLICE = -1; private Bitmap original; private int tileSize, gridSize; private ArrayList slices; private Random random; + private Context context; /** * Initializes TileSlicer. @@ -36,11 +38,12 @@ public class TileSlicer { * @param gridSize * Grid size, for example 4 for 4x4 grid */ - public TileSlicer(Bitmap original, int gridSize) { + public TileSlicer(Bitmap original, int gridSize, Context context) { super(); this.original = original; this.gridSize = gridSize; this.tileSize = original.getWidth() / gridSize; + this.context = context; random = new Random(); slices = new ArrayList(); sliceOriginal(); @@ -54,6 +57,10 @@ private void sliceOriginal() { Bitmap bitmap; 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; + } x = rowI * tileSize; y = colI * tileSize; // slice @@ -75,17 +82,28 @@ private void sliceOriginal() { } /** - * Serves random slice and frees it from memory + * Serves slice and frees it from memory * - * @return Bitmap of random slice + * @param index + * index of the slice. Serves random slice if + * TileSlicer.RANDOM_SLICE. + * @return TileView with the image or empty tile if there are no such + * slices. */ - public Bitmap getRandomSlice() { + public TileView getSlice(int index) { + TileView tile = null; if (slices.size() > 0) { - int randomIndex = random.nextInt(slices.size()); - Bitmap drawable = slices.remove(randomIndex); - return drawable; + if (index == RANDOM_SLICE) { + index = random.nextInt(slices.size()); + } + tile = new TileView(context, index); + tile.setImageBitmap(slices.remove(index)); + } else { + // empty slice + tile = new TileView(context, index); + tile.setEmpty(true); } - return null; + return tile; } } From cac7ee2b71623390ffa0a46913ef3d8b05822bd7 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 14 May 2012 20:12:32 +0200 Subject: [PATCH 08/13] preserving tile order when orientation changes --- res/layout/activity_main.xml | 4 +- .../destil/sliderpuzzle/ui/GameboardView.java | 48 +++++++++- .../destil/sliderpuzzle/ui/MainActivity.java | 24 ++++- .../destil/sliderpuzzle/util/TileSlicer.java | 96 +++++++++++++------ 4 files changed, 133 insertions(+), 39 deletions(-) diff --git a/res/layout/activity_main.xml b/res/layout/activity_main.xml index 7b8f7c2..a439b0d 100644 --- a/res/layout/activity_main.xml +++ b/res/layout/activity_main.xml @@ -4,10 +4,10 @@ android:layout_height="match_parent" android:padding="5dp" > - - + \ No newline at end of file diff --git a/src/cz/destil/sliderpuzzle/ui/GameboardView.java b/src/cz/destil/sliderpuzzle/ui/GameboardView.java index f75f425..3a84ffc 100644 --- a/src/cz/destil/sliderpuzzle/ui/GameboardView.java +++ b/src/cz/destil/sliderpuzzle/ui/GameboardView.java @@ -1,6 +1,7 @@ package cz.destil.sliderpuzzle.ui; import java.util.ArrayList; +import java.util.LinkedList; import android.animation.Animator; import android.animation.Animator.AnimatorListener; @@ -34,7 +35,7 @@ * @author David Vavra * */ -public class GameboardView extends RelativeLayout implements OnTouchListener { +public class GameBoardView extends RelativeLayout implements OnTouchListener { public static final int GRID_SIZE = 4; // 4x4 private int tileSize; @@ -44,8 +45,9 @@ public class GameboardView extends RelativeLayout implements OnTouchListener { private RectF gameboardRect; private PointF lastDragPoint; private ArrayList currentMotionDescriptors; + private LinkedList tileOrder; - public GameboardView(Context context, AttributeSet attrSet) { + public GameBoardView(Context context, AttributeSet attrSet) { super(context, attrSet); } @@ -89,10 +91,20 @@ public void fillTiles() { Bitmap original = ((BitmapDrawable) globe).getBitmap(); TileSlicer tileSlicer = new TileSlicer(original, GRID_SIZE, getContext()); // fill gameboard with slices + if (tileOrder == null) { + tileSlicer.randomizeSlices(); + } else { + tileSlicer.setSliceOrder(tileOrder); + } tiles = new ArrayList(); for (int rowI = 0; rowI < GRID_SIZE; rowI++) { for (int colI = 0; colI < GRID_SIZE; colI++) { - TileView tile = tileSlicer.getSlice(TileSlicer.RANDOM_SLICE); + TileView tile; + if (tileOrder == null) { + tile = tileSlicer.getTile(); + } else { + tile = tileSlicer.getTile(); + } tile.coordinate = new Coordinate(rowI, colI); if (tile.isEmpty()) { emptyTile = tile; @@ -186,8 +198,8 @@ private boolean lastDragMovedMinimally() { } /** - * Follows finger while dragging all currently moved tiles. - * Allows movement only along x axis for row and y axis for column. + * Follows finger while dragging all currently moved tiles. Allows movement + * only along x axis for row and y axis for column. * * @param event */ @@ -454,6 +466,32 @@ private Rect rectForCoordinate(Coordinate coordinate) { 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. */ diff --git a/src/cz/destil/sliderpuzzle/ui/MainActivity.java b/src/cz/destil/sliderpuzzle/ui/MainActivity.java index 7473318..1ca9cbd 100644 --- a/src/cz/destil/sliderpuzzle/ui/MainActivity.java +++ b/src/cz/destil/sliderpuzzle/ui/MainActivity.java @@ -1,7 +1,10 @@ package cz.destil.sliderpuzzle.ui; +import java.util.LinkedList; + import android.content.Intent; import android.os.Bundle; +import android.util.Log; import com.actionbarsherlock.app.SherlockActivity; import com.actionbarsherlock.view.Menu; @@ -19,10 +22,20 @@ */ 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) { + Log.d("tile locations",tileOrder.toString()); + gameBoard.setTileOrder(tileOrder); + } } @Override @@ -36,8 +49,8 @@ public boolean onCreateOptionsMenu(Menu menu) { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.new_game: - GameboardView gameboard = (GameboardView) findViewById(R.id.gameboard); - gameboard.fillTiles(); + gameBoard.setTileOrder(null); + gameBoard.fillTiles(); return true; case R.id.about: startActivity(new Intent(this, AboutActivity.class)); @@ -47,5 +60,10 @@ public boolean onOptionsItemSelected(MenuItem item) { } } - // TODO: preserve state when rotated + @Override + public Object onRetainNonConfigurationInstance() { + // preserve state when rotated + Log.d("tile locations",gameBoard.getTileOrder().toString()); + return gameBoard.getTileOrder(); + } } \ No newline at end of file diff --git a/src/cz/destil/sliderpuzzle/util/TileSlicer.java b/src/cz/destil/sliderpuzzle/util/TileSlicer.java index acdd439..a0dffb8 100644 --- a/src/cz/destil/sliderpuzzle/util/TileSlicer.java +++ b/src/cz/destil/sliderpuzzle/util/TileSlicer.java @@ -1,7 +1,8 @@ package cz.destil.sliderpuzzle.util; -import java.util.ArrayList; -import java.util.Random; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; import android.content.Context; import android.graphics.Bitmap; @@ -26,8 +27,9 @@ public class TileSlicer { public static final int RANDOM_SLICE = -1; private Bitmap original; private int tileSize, gridSize; - private ArrayList slices; - private Random random; + private List slices; + private int lastSliceServed; + private List sliceOrder; private Context context; /** @@ -44,8 +46,7 @@ public TileSlicer(Bitmap original, int gridSize, Context context) { this.gridSize = gridSize; this.tileSize = original.getWidth() / gridSize; this.context = context; - random = new Random(); - slices = new ArrayList(); + slices = new LinkedList(); sliceOriginal(); } @@ -55,32 +56,66 @@ public TileSlicer(Bitmap original, int gridSize, Context context) { 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); } - 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 original bitmap from memory original = null; } + /** + * Randomizes slices in case no previous instance is available. + */ + public void randomizeSlices() { + // randomize first 15 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 frees it from memory * @@ -90,18 +125,21 @@ private void sliceOriginal() { * @return TileView with the image or empty tile if there are no such * slices. */ - public TileView getSlice(int index) { + public TileView getTile() { TileView tile = null; if (slices.size() > 0) { - if (index == RANDOM_SLICE) { - index = random.nextInt(slices.size()); + 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 = new TileView(context, index); - tile.setImageBitmap(slices.remove(index)); - } else { - // empty slice - tile = new TileView(context, index); - tile.setEmpty(true); + tile.setImageBitmap(slices.remove(0)); } return tile; } From d8355717f7bdf23239d6196adf84ff068fb9bd14 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 14 May 2012 21:05:47 +0200 Subject: [PATCH 09/13] greatly improved responsivness of drag --- .../destil/sliderpuzzle/ui/GameboardView.java | 86 ++++++++++++------- .../destil/sliderpuzzle/ui/MainActivity.java | 3 - 2 files changed, 54 insertions(+), 35 deletions(-) diff --git a/src/cz/destil/sliderpuzzle/ui/GameboardView.java b/src/cz/destil/sliderpuzzle/ui/GameboardView.java index 3a84ffc..e3711f7 100644 --- a/src/cz/destil/sliderpuzzle/ui/GameboardView.java +++ b/src/cz/destil/sliderpuzzle/ui/GameboardView.java @@ -15,6 +15,7 @@ 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; @@ -38,6 +39,11 @@ 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; @@ -205,16 +211,14 @@ private boolean lastDragMovedMinimally() { */ private void followFinger(MotionEvent event) { boolean impossibleMove = true; - float dxTile, dyTile; float dxEvent = event.getRawX() - lastDragPoint.x; float dyEvent = event.getRawY() - lastDragPoint.y; TileView tile; - for (GameTileMotionDescriptor gameTileMotionDescriptor : currentMotionDescriptors) { - tile = gameTileMotionDescriptor.tile; - dxTile = tile.getX() + dxEvent; - dyTile = tile.getY() + dyEvent; + for (GameTileMotionDescriptor descriptor : currentMotionDescriptors) { + tile = descriptor.tile; + Pair xy = getXY(tile, dxEvent, dyEvent, descriptor.direction); // detect if this move is valid - RectF candidateRect = new RectF(dxTile, dyTile, dxTile + tile.getWidth(), dyTile + tile.getHeight()); + 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); @@ -229,21 +233,36 @@ private void followFinger(MotionEvent event) { } if (!impossibleMove) { // perform move for all moved tiles in the descriptors - 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); - } - } + for (GameTileMotionDescriptor descriptor : currentMotionDescriptors) { + tile = descriptor.tile; + Pair xy = getXY(descriptor.tile, dxEvent, dyEvent, descriptor.direction); + tile.setX(xy.first); + tile.setY(xy.second); } } } + /** + * Computes new x,y coordinates for given tile in given direction (x or y). + * @param tile tile to check + * @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 getXY(TileView tile, float dxEvent, float dyEvent, Direction direction) { + float dxTile = 0, dyTile = 0; + if (direction == Direction.X) { + dxTile = tile.getX() + dxEvent; + dyTile = tile.getY(); + } + if (direction == Direction.Y) { + dyTile = tile.getY() + dyEvent; + dxTile = tile.getX(); + } + return new Pair(dxTile, dyTile); + } + /** * @param candidateRect * rectangle to check @@ -277,8 +296,8 @@ private void animateTilesToEmptySpace() { 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 = ObjectAnimator.ofObject(motionDescriptor.tile, motionDescriptor.direction.toString(), + new FloatEvaluator(), motionDescriptor.from, motionDescriptor.to); animator.setDuration(16); animator.addListener(new AnimatorListener() { @@ -309,7 +328,7 @@ private void animateTilesBackToOrigin() { ObjectAnimator animator; if (currentMotionDescriptors != null) { for (final GameTileMotionDescriptor motionDescriptor : currentMotionDescriptors) { - animator = ObjectAnimator.ofObject(motionDescriptor.tile, motionDescriptor.property, + animator = ObjectAnimator.ofObject(motionDescriptor.tile, motionDescriptor.direction.toString(), new FloatEvaluator(), motionDescriptor.currentPosition(), motionDescriptor.originalPosition()); animator.setDuration(16); animator.addListener(new AnimatorListener() { @@ -355,7 +374,8 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Tile currentRect = rectForCoordinate(foundTile.coordinate); finalRect = rectForCoordinate(finalCoordinate); axialDelta = Math.abs(foundTile.getX() - currentRect.left); - motionDescriptor = new GameTileMotionDescriptor(foundTile, "x", foundTile.getX(), finalRect.left); + motionDescriptor = new GameTileMotionDescriptor(foundTile, Direction.X, foundTile.getX(), + finalRect.left); motionDescriptor.finalCoordinate = finalCoordinate; motionDescriptor.finalRect = finalRect; motionDescriptor.axialDelta = axialDelta; @@ -370,7 +390,8 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Tile currentRect = rectForCoordinate(foundTile.coordinate); finalRect = rectForCoordinate(finalCoordinate); axialDelta = Math.abs(foundTile.getX() - currentRect.left); - motionDescriptor = new GameTileMotionDescriptor(foundTile, "x", foundTile.getX(), finalRect.left); + motionDescriptor = new GameTileMotionDescriptor(foundTile, Direction.X, foundTile.getX(), + finalRect.left); motionDescriptor.finalCoordinate = finalCoordinate; motionDescriptor.finalRect = finalRect; motionDescriptor.axialDelta = axialDelta; @@ -385,7 +406,7 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Tile currentRect = rectForCoordinate(foundTile.coordinate); finalRect = rectForCoordinate(finalCoordinate); axialDelta = Math.abs(foundTile.getY() - currentRect.top); - motionDescriptor = new GameTileMotionDescriptor(foundTile, "y", foundTile.getY(), finalRect.top); + motionDescriptor = new GameTileMotionDescriptor(foundTile, Direction.Y, foundTile.getY(), finalRect.top); motionDescriptor.finalCoordinate = finalCoordinate; motionDescriptor.finalRect = finalRect; motionDescriptor.axialDelta = axialDelta; @@ -400,7 +421,7 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Tile currentRect = rectForCoordinate(foundTile.coordinate); finalRect = rectForCoordinate(finalCoordinate); axialDelta = Math.abs(foundTile.getY() - currentRect.top); - motionDescriptor = new GameTileMotionDescriptor(foundTile, "y", foundTile.getY(), finalRect.top); + motionDescriptor = new GameTileMotionDescriptor(foundTile, Direction.Y, foundTile.getY(), finalRect.top); motionDescriptor.finalCoordinate = finalCoordinate; motionDescriptor.finalRect = finalRect; motionDescriptor.axialDelta = axialDelta; @@ -486,7 +507,8 @@ public LinkedList getTileOrder() { /** * Sets tile locations from previous state. * - * @param tileLocations list of integers marking order + * @param tileLocations + * list of integers marking order */ public void setTileOrder(LinkedList tileLocations) { this.tileOrder = tileLocations; @@ -498,26 +520,26 @@ public void setTileOrder(LinkedList tileLocations) { public class GameTileMotionDescriptor { public Rect finalRect; - public String property; // "x" or "y" + public Direction direction; // x or y public TileView tile; public float from, to, axialDelta; public Coordinate finalCoordinate; - public GameTileMotionDescriptor(TileView tile, String property, float from, float to) { + public GameTileMotionDescriptor(TileView tile, Direction direction, float from, float to) { super(); this.tile = tile; this.from = from; this.to = to; - this.property = property; + this.direction = direction; } /** * @return current position of the tile */ public float currentPosition() { - if (property.equals("x")) { + if (direction == Direction.X) { return tile.getX(); - } else if (property.equals("y")) { + } else if (direction == Direction.Y) { return tile.getY(); } return 0; @@ -529,9 +551,9 @@ public float currentPosition() { */ public float originalPosition() { Rect originalRect = rectForCoordinate(tile.coordinate); - if (property.equals("x")) { + if (direction == Direction.X) { return originalRect.left; - } else if (property.equals("y")) { + } 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 index 1ca9cbd..bd21df1 100644 --- a/src/cz/destil/sliderpuzzle/ui/MainActivity.java +++ b/src/cz/destil/sliderpuzzle/ui/MainActivity.java @@ -4,7 +4,6 @@ import android.content.Intent; import android.os.Bundle; -import android.util.Log; import com.actionbarsherlock.app.SherlockActivity; import com.actionbarsherlock.view.Menu; @@ -33,7 +32,6 @@ public void onCreate(Bundle savedInstanceState) { @SuppressWarnings({ "deprecation", "unchecked" }) final LinkedList tileOrder = (LinkedList) getLastNonConfigurationInstance(); if (tileOrder != null) { - Log.d("tile locations",tileOrder.toString()); gameBoard.setTileOrder(tileOrder); } } @@ -63,7 +61,6 @@ public boolean onOptionsItemSelected(MenuItem item) { @Override public Object onRetainNonConfigurationInstance() { // preserve state when rotated - Log.d("tile locations",gameBoard.getTileOrder().toString()); return gameBoard.getTileOrder(); } } \ No newline at end of file From 443c7719967c4c2a010886f0d8acc9e851e65362 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 14 May 2012 21:40:25 +0200 Subject: [PATCH 10/13] gray background, better click detection --- res/layout/activity_about.xml | 5 ++- res/layout/activity_main.xml | 1 + res/values/colors.xml | 6 +++ .../destil/sliderpuzzle/ui/GameboardView.java | 42 ++++++++++++------- src/cz/destil/sliderpuzzle/ui/TileView.java | 1 + 5 files changed, 37 insertions(+), 18 deletions(-) create mode 100644 res/values/colors.xml diff --git a/res/layout/activity_about.xml b/res/layout/activity_about.xml index aa66a97..9037c6e 100644 --- a/res/layout/activity_about.xml +++ b/res/layout/activity_about.xml @@ -2,6 +2,7 @@ @@ -40,8 +41,8 @@ + android:contentDescription="@string/settle_up" + android:src="@drawable/settle_up" /> + + + #3b3b3b + + \ No newline at end of file diff --git a/src/cz/destil/sliderpuzzle/ui/GameboardView.java b/src/cz/destil/sliderpuzzle/ui/GameboardView.java index e3711f7..93d48a9 100644 --- a/src/cz/destil/sliderpuzzle/ui/GameboardView.java +++ b/src/cz/destil/sliderpuzzle/ui/GameboardView.java @@ -15,6 +15,7 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.util.Log; import android.util.Pair; import android.view.MotionEvent; import android.view.View; @@ -148,6 +149,7 @@ public boolean onTouch(View v, MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { movedTile = touchedTile; currentMotionDescriptors = getTilesBetweenEmptyTileAndTile(movedTile); + movedTile.numberOfDrags = 0; // during the gesture } else if (event.getActionMasked() == MotionEvent.ACTION_MOVE) { if (lastDragPoint != null) { @@ -158,13 +160,9 @@ public boolean onTouch(View v, MotionEvent event) { } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { // reload the motion descriptors in case of position change. currentMotionDescriptors = getTilesBetweenEmptyTileAndTile(movedTile); - // if drag was over 50%, do the move - if (lastDragPoint != null && lastDragMovedAtLeastHalfWay()) { + // if drag was over 50% or it's click, do the move + if (lastDragMovedAtLeastHalfWay() || isClick()) { animateTilesToEmptySpace(); - // if it was a click, do the move - } else if (lastDragPoint == null || lastDragMovedMinimally()) { - animateTilesToEmptySpace(); - // if it was a drag less than 50%, animate tiles back } else { animateTilesBackToOrigin(); } @@ -180,7 +178,7 @@ public boolean onTouch(View v, MotionEvent event) { * @return Whether last drag moved with the tile more than 50% of its size */ private boolean lastDragMovedAtLeastHalfWay() { - if (currentMotionDescriptors != null && currentMotionDescriptors.size() > 0) { + if (lastDragPoint != null && currentMotionDescriptors != null && currentMotionDescriptors.size() > 0) { GameTileMotionDescriptor firstMotionDescriptor = currentMotionDescriptors.get(0); if (firstMotionDescriptor.axialDelta > tileSize / 2) { return true; @@ -190,12 +188,17 @@ private boolean lastDragMovedAtLeastHalfWay() { } /** - * @return Whether last drag moved just a little = involuntary move during - * click + * Detects click - either true click (no drags) or small involuntary drag + * + * @return Whether last gesture was a click */ - private boolean lastDragMovedMinimally() { - if (currentMotionDescriptors != null && currentMotionDescriptors.size() > 0) { + private boolean isClick() { + if (lastDragPoint == null) { + return true; // no drag + } + if (currentMotionDescriptors != null && currentMotionDescriptors.size() > 0 && movedTile.numberOfDrags < 5) { GameTileMotionDescriptor firstMotionDescriptor = currentMotionDescriptors.get(0); + // just very small drag counts as click if (firstMotionDescriptor.axialDelta < tileSize / 20) { return true; } @@ -214,11 +217,13 @@ private void followFinger(MotionEvent event) { 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 = getXY(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()); + 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); @@ -244,10 +249,15 @@ private void followFinger(MotionEvent event) { /** * Computes new x,y coordinates for given tile in given direction (x or y). - * @param tile tile to check - * @param dxEvent change of x coordinate from touch gesture - * @param dyEvent change of y coordinate from touch gesture - * @param direction x or y direction + * + * @param tile + * tile to check + * @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 getXY(TileView tile, float dxEvent, float dyEvent, Direction direction) { diff --git a/src/cz/destil/sliderpuzzle/ui/TileView.java b/src/cz/destil/sliderpuzzle/ui/TileView.java index c26c035..c54f574 100644 --- a/src/cz/destil/sliderpuzzle/ui/TileView.java +++ b/src/cz/destil/sliderpuzzle/ui/TileView.java @@ -19,6 +19,7 @@ public class TileView extends ImageView { public Coordinate coordinate; public int originalIndex; + public int numberOfDrags; private boolean empty; public TileView(Context context, int originalIndex) { From 246f47921e09cc79de572a594aa7d25a9dba2d61 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 14 May 2012 23:11:42 +0200 Subject: [PATCH 11/13] for SDK < 11 --- project.properties | 2 +- .../destil/sliderpuzzle/ui/GameboardView.java | 58 +++++++++---------- src/cz/destil/sliderpuzzle/ui/TileView.java | 52 ++++++++++++++++- 3 files changed, 80 insertions(+), 32 deletions(-) diff --git a/project.properties b/project.properties index a39542f..de89b38 100644 --- a/project.properties +++ b/project.properties @@ -9,4 +9,4 @@ # Project target. target=android-15 -android.library.reference.1=../ActionBarSherlock/library +android.library.reference.1=..\\\\\\\\ActionBarSherlock\\\\\\\\library diff --git a/src/cz/destil/sliderpuzzle/ui/GameboardView.java b/src/cz/destil/sliderpuzzle/ui/GameboardView.java index 93d48a9..6cbdd54 100644 --- a/src/cz/destil/sliderpuzzle/ui/GameboardView.java +++ b/src/cz/destil/sliderpuzzle/ui/GameboardView.java @@ -3,10 +3,6 @@ import java.util.ArrayList; import java.util.LinkedList; -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; -import android.animation.FloatEvaluator; -import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.PointF; @@ -15,12 +11,17 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.util.Log; 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; @@ -220,7 +221,7 @@ private void followFinger(MotionEvent event) { movedTile.numberOfDrags++; for (GameTileMotionDescriptor descriptor : currentMotionDescriptors) { tile = descriptor.tile; - Pair xy = getXY(tile, dxEvent, dyEvent, descriptor.direction); + 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()); @@ -240,9 +241,8 @@ private void followFinger(MotionEvent event) { // perform move for all moved tiles in the descriptors for (GameTileMotionDescriptor descriptor : currentMotionDescriptors) { tile = descriptor.tile; - Pair xy = getXY(descriptor.tile, dxEvent, dyEvent, descriptor.direction); - tile.setX(xy.first); - tile.setY(xy.second); + Pair xy = getXYFromEvent(tile, dxEvent, dyEvent, descriptor.direction); + tile.setXY(xy.first, xy.second); } } } @@ -260,15 +260,15 @@ private void followFinger(MotionEvent event) { * x or y direction * @return pair of first x coordinates, second y coordinates */ - private Pair getXY(TileView tile, float dxEvent, float dyEvent, Direction direction) { + private Pair getXYFromEvent(TileView tile, float dxEvent, float dyEvent, Direction direction) { float dxTile = 0, dyTile = 0; if (direction == Direction.X) { - dxTile = tile.getX() + dxEvent; - dyTile = tile.getY(); + dxTile = tile.getXPos() + dxEvent; + dyTile = tile.getYPos(); } if (direction == Direction.Y) { - dyTile = tile.getY() + dyEvent; - dxTile = tile.getX(); + dyTile = tile.getYPos() + dyEvent; + dxTile = tile.getXPos(); } return new Pair(dxTile, dyTile); } @@ -286,8 +286,8 @@ private boolean collidesWithTitles(RectF candidateRect, TileView tile, ArrayList RectF otherTileRect; for (TileView otherTile : tilesToCheck) { if (!otherTile.isEmpty() && otherTile != tile) { - otherTileRect = new RectF(otherTile.getX(), otherTile.getY(), otherTile.getX() + otherTile.getWidth(), - otherTile.getY() + otherTile.getHeight()); + otherTileRect = new RectF(otherTile.getXPos(), otherTile.getYPos(), otherTile.getXPos() + otherTile.getWidth(), + otherTile.getYPos() + otherTile.getHeight()); if (RectF.intersects(otherTileRect, candidateRect)) { return true; } @@ -301,8 +301,7 @@ private boolean collidesWithTitles(RectF candidateRect, TileView tile, ArrayList * when valid tile is clicked or is dragged over 50%. */ private void animateTilesToEmptySpace() { - emptyTile.setX(movedTile.getX()); - emptyTile.setY(movedTile.getY()); + emptyTile.setXY(movedTile.getXPos(), movedTile.getYPos()); emptyTile.coordinate = movedTile.coordinate; ObjectAnimator animator; for (final GameTileMotionDescriptor motionDescriptor : currentMotionDescriptors) { @@ -322,8 +321,7 @@ 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); + motionDescriptor.tile.setXY(motionDescriptor.finalRect.left, motionDescriptor.finalRect.top); } }); animator.start(); @@ -383,8 +381,8 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Tile 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, Direction.X, foundTile.getX(), + axialDelta = Math.abs(foundTile.getXPos() - currentRect.left); + motionDescriptor = new GameTileMotionDescriptor(foundTile, Direction.X, foundTile.getXPos(), finalRect.left); motionDescriptor.finalCoordinate = finalCoordinate; motionDescriptor.finalRect = finalRect; @@ -399,8 +397,8 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Tile 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, Direction.X, foundTile.getX(), + axialDelta = Math.abs(foundTile.getXPos() - currentRect.left); + motionDescriptor = new GameTileMotionDescriptor(foundTile, Direction.X, foundTile.getXPos(), finalRect.left); motionDescriptor.finalCoordinate = finalCoordinate; motionDescriptor.finalRect = finalRect; @@ -415,8 +413,8 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Tile 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, Direction.Y, foundTile.getY(), finalRect.top); + 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; @@ -430,8 +428,8 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Tile 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, Direction.Y, foundTile.getY(), finalRect.top); + 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; @@ -548,9 +546,9 @@ public GameTileMotionDescriptor(TileView tile, Direction direction, float from, */ public float currentPosition() { if (direction == Direction.X) { - return tile.getX(); + return tile.getXPos(); } else if (direction == Direction.Y) { - return tile.getY(); + return tile.getYPos(); } return 0; } diff --git a/src/cz/destil/sliderpuzzle/ui/TileView.java b/src/cz/destil/sliderpuzzle/ui/TileView.java index c54f574..3e29751 100644 --- a/src/cz/destil/sliderpuzzle/ui/TileView.java +++ b/src/cz/destil/sliderpuzzle/ui/TileView.java @@ -1,8 +1,10 @@ package cz.destil.sliderpuzzle.ui; -import cz.destil.sliderpuzzle.data.Coordinate; import android.content.Context; +import android.os.Build; import android.widget.ImageView; +import android.widget.RelativeLayout; +import cz.destil.sliderpuzzle.data.Coordinate; /** * @@ -59,4 +61,52 @@ public boolean isBelow(TileView tile) { return coordinate.isBelow(tile.coordinate); } + /** + * Sets X Y coordinate for the view - works on 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; + } + } + } From 7c8ed7ac9384e385569eea6f3f023b4e6a8794fd Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 15 May 2012 00:02:11 +0200 Subject: [PATCH 12/13] proper click handling on Android 2.3 --- .../destil/sliderpuzzle/ui/GameboardView.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/cz/destil/sliderpuzzle/ui/GameboardView.java b/src/cz/destil/sliderpuzzle/ui/GameboardView.java index 6cbdd54..a225de8 100644 --- a/src/cz/destil/sliderpuzzle/ui/GameboardView.java +++ b/src/cz/destil/sliderpuzzle/ui/GameboardView.java @@ -197,7 +197,8 @@ private boolean isClick() { if (lastDragPoint == null) { return true; // no drag } - if (currentMotionDescriptors != null && currentMotionDescriptors.size() > 0 && movedTile.numberOfDrags < 5) { + // 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) { @@ -221,7 +222,7 @@ private void followFinger(MotionEvent event) { movedTile.numberOfDrags++; for (GameTileMotionDescriptor descriptor : currentMotionDescriptors) { tile = descriptor.tile; - Pair xy = getXYFromEvent(tile, dxEvent, dyEvent, descriptor.direction); + 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()); @@ -286,8 +287,8 @@ private boolean collidesWithTitles(RectF candidateRect, TileView tile, ArrayList 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()); + otherTileRect = new RectF(otherTile.getXPos(), otherTile.getYPos(), otherTile.getXPos() + + otherTile.getWidth(), otherTile.getYPos() + otherTile.getHeight()); if (RectF.intersects(otherTileRect, candidateRect)) { return true; } @@ -351,6 +352,8 @@ public void onAnimationRepeat(Animator animation) { } public void onAnimationEnd(Animator animation) { + motionDescriptor.tile.setXY(motionDescriptor.originalRect.left, + motionDescriptor.originalRect.top); } }); animator.start(); @@ -414,7 +417,8 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Tile 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 = new GameTileMotionDescriptor(foundTile, Direction.Y, foundTile.getYPos(), + finalRect.top); motionDescriptor.finalCoordinate = finalCoordinate; motionDescriptor.finalRect = finalRect; motionDescriptor.axialDelta = axialDelta; @@ -429,7 +433,8 @@ private ArrayList getTilesBetweenEmptyTileAndTile(Tile 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 = new GameTileMotionDescriptor(foundTile, Direction.Y, foundTile.getYPos(), + finalRect.top); motionDescriptor.finalCoordinate = finalCoordinate; motionDescriptor.finalRect = finalRect; motionDescriptor.axialDelta = axialDelta; @@ -527,7 +532,7 @@ public void setTileOrder(LinkedList tileLocations) { */ public class GameTileMotionDescriptor { - public Rect finalRect; + public Rect finalRect, originalRect; public Direction direction; // x or y public TileView tile; public float from, to, axialDelta; @@ -539,6 +544,7 @@ public GameTileMotionDescriptor(TileView tile, Direction direction, float from, this.from = from; this.to = to; this.direction = direction; + this.originalRect = rectForCoordinate(tile.coordinate); } /** @@ -558,7 +564,6 @@ public float currentPosition() { * original position. */ public float originalPosition() { - Rect originalRect = rectForCoordinate(tile.coordinate); if (direction == Direction.X) { return originalRect.left; } else if (direction == Direction.Y) { From fff70afe7b435a9c1be79a900e11bef4ff0450c5 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 15 May 2012 00:47:14 +0200 Subject: [PATCH 13/13] final cleaning --- readme.pdf | Bin 0 -> 55168 bytes .../destil/sliderpuzzle/ui/GameboardView.java | 25 +++++++++--------- src/cz/destil/sliderpuzzle/ui/TileView.java | 2 +- .../destil/sliderpuzzle/util/TileSlicer.java | 19 +++++-------- 4 files changed, 21 insertions(+), 25 deletions(-) create mode 100644 readme.pdf diff --git a/readme.pdf b/readme.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9349947aea5cc46cc6f68a9bd0e410d880827062 GIT binary patch literal 55168 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 literal 0 HcmV?d00001 diff --git a/src/cz/destil/sliderpuzzle/ui/GameboardView.java b/src/cz/destil/sliderpuzzle/ui/GameboardView.java index a225de8..f258cb9 100644 --- a/src/cz/destil/sliderpuzzle/ui/GameboardView.java +++ b/src/cz/destil/sliderpuzzle/ui/GameboardView.java @@ -70,7 +70,7 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto } /** - * Detect gameboard size and tile size based on current screen. + * Detect game board size and tile size based on current screen. */ private void determineGameboardSizes() { int viewWidth = getWidth(); @@ -90,7 +90,7 @@ private void determineGameboardSizes() { } /** - * Fills gameboard with tiles sliced from the globe image. + * Fills game board with tiles sliced from the globe image. */ public void fillTiles() { removeAllViews(); @@ -98,12 +98,13 @@ public void fillTiles() { Drawable globe = getResources().getDrawable(R.drawable.globe); Bitmap original = ((BitmapDrawable) globe).getBitmap(); TileSlicer tileSlicer = new TileSlicer(original, GRID_SIZE, getContext()); - // fill gameboard with slices + // 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++) { @@ -124,7 +125,7 @@ public void fillTiles() { } /** - * Places tile on appropriate place in the layout + * Places tile on appropriate place in the layout. * * @param tile * Tile to place @@ -139,27 +140,28 @@ private void placeTile(TileView tile) { } /** - * Handling of touch events. High-level logic for moving tiles on gameboard. + * 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 { - // start of the gesture if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + // start of the gesture movedTile = touchedTile; currentMotionDescriptors = getTilesBetweenEmptyTileAndTile(movedTile); movedTile.numberOfDrags = 0; - // during the gesture } else if (event.getActionMasked() == MotionEvent.ACTION_MOVE) { + // during the gesture if (lastDragPoint != null) { followFinger(event); } lastDragPoint = new PointF(event.getRawX(), event.getRawY()); - // end of gesture } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { - // reload the motion descriptors in case of position change. + // 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()) { @@ -195,7 +197,7 @@ private boolean lastDragMovedAtLeastHalfWay() { */ private boolean isClick() { if (lastDragPoint == null) { - return true; // no drag + return true; // no drags } // just small amount of MOVE events counts as click if (currentMotionDescriptors != null && currentMotionDescriptors.size() > 0 && movedTile.numberOfDrags < 10) { @@ -239,7 +241,7 @@ private void followFinger(MotionEvent event) { impossibleMove = impossibleMove && (!candidateRectInGameboard || collides); } if (!impossibleMove) { - // perform move for all moved tiles in the descriptors + // 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); @@ -252,7 +254,6 @@ private void followFinger(MotionEvent event) { * Computes new x,y coordinates for given tile in given direction (x or y). * * @param tile - * tile to check * @param dxEvent * change of x coordinate from touch gesture * @param dyEvent diff --git a/src/cz/destil/sliderpuzzle/ui/TileView.java b/src/cz/destil/sliderpuzzle/ui/TileView.java index 3e29751..15ffb2b 100644 --- a/src/cz/destil/sliderpuzzle/ui/TileView.java +++ b/src/cz/destil/sliderpuzzle/ui/TileView.java @@ -62,7 +62,7 @@ public boolean isBelow(TileView tile) { } /** - * Sets X Y coordinate for the view - works on all Android versions. + * Sets X Y coordinate for the view - works for all Android versions. * * @param x * @param y diff --git a/src/cz/destil/sliderpuzzle/util/TileSlicer.java b/src/cz/destil/sliderpuzzle/util/TileSlicer.java index a0dffb8..bac7a31 100644 --- a/src/cz/destil/sliderpuzzle/util/TileSlicer.java +++ b/src/cz/destil/sliderpuzzle/util/TileSlicer.java @@ -13,8 +13,8 @@ /** * - * Slices original bitmap into tiles and adds border. Provides randomized access - * to tiles. + * 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 @@ -24,7 +24,6 @@ */ public class TileSlicer { - public static final int RANDOM_SLICE = -1; private Bitmap original; private int tileSize, gridSize; private List slices; @@ -80,15 +79,15 @@ private void sliceOriginal() { } } } - // remove original bitmap from memory + // remove reference to original bitmap original = null; } /** - * Randomizes slices in case no previous instance is available. + * Randomizes slices in case no previous state is available. */ public void randomizeSlices() { - // randomize first 15 slices + // randomize first slices Collections.shuffle(slices); // last one is empty slice slices.add(null); @@ -117,13 +116,9 @@ public void setSliceOrder(List order) { } /** - * Serves slice and frees it from memory + * Serves slice and creates a tile for gameboard. * - * @param index - * index of the slice. Serves random slice if - * TileSlicer.RANDOM_SLICE. - * @return TileView with the image or empty tile if there are no such - * slices. + * @return TileView with the image or null if there are no more slices */ public TileView getTile() { TileView tile = null;