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