Moving to Android Studio and Gradle.
30
app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.dyndns.vahagn.sokoban"
|
||||
android:versionCode="0x00010001"
|
||||
android:versionName="@string/git_version">
|
||||
<uses-sdk android:minSdkVersion="10"
|
||||
android:targetSdkVersion="14"/>
|
||||
<application android:label="@string/app_name"
|
||||
android:icon="@drawable/icon"
|
||||
android:name="org.dyndns.vahagn.sokoban.App"
|
||||
android:theme="@android:style/Theme">
|
||||
<activity android:name="org.dyndns.vahagn.sokoban.menu.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboardHidden|orientation"
|
||||
android:screenOrientation="portrait" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="org.dyndns.vahagn.sokoban.play.PlayActivity"
|
||||
android:label="@string/play_activity"
|
||||
android:configChanges="keyboardHidden|orientation"
|
||||
android:screenOrientation="portrait" >
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.dyndns.vahagn.sokoban.MainMenu" />
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
125
app/src/main/java/org/dyndns/vahagn/sokoban/App.java
Normal file
@@ -0,0 +1,125 @@
|
||||
package org.dyndns.vahagn.sokoban;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
public class App extends Application
|
||||
{
|
||||
public static final String TAG = "Sokoban";
|
||||
public static final String PREFS = "org.dyndns.vahagn.sokoban.prefs";
|
||||
public static final int MIN_LEVEL = 1;
|
||||
|
||||
private static App mApp = null;
|
||||
protected PuzzleContainer pc;
|
||||
protected int current_level;
|
||||
protected int achieved_level;
|
||||
protected int max_level;
|
||||
protected SharedPreferences prefs;
|
||||
protected SharedPreferences.Editor prefsEdit;
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
super.onCreate();
|
||||
mApp = this;
|
||||
pc = new PuzzleContainer();
|
||||
prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
prefsEdit = prefs.edit();
|
||||
max_level = pc.getCount();
|
||||
current_level = prefs.getInt("current_level", MIN_LEVEL);
|
||||
achieved_level = prefs.getInt("achieved_level", MIN_LEVEL+10);
|
||||
}
|
||||
//
|
||||
// This is a singleton object.
|
||||
//
|
||||
public static App theApp()
|
||||
{
|
||||
return mApp;
|
||||
}
|
||||
//
|
||||
// Puzzles.
|
||||
//
|
||||
public Puzzle getPuzzle( int level )
|
||||
{
|
||||
return pc.getPuzzle( level-1 );
|
||||
}
|
||||
public Puzzle getCurrentPuzzle()
|
||||
{
|
||||
return getPuzzle( getCurrentLevel() );
|
||||
}
|
||||
public Puzzle getPrevPuzzle()
|
||||
{
|
||||
if ( current_level != MIN_LEVEL )
|
||||
return getPuzzle( getCurrentLevel()-1 );
|
||||
else
|
||||
return null;
|
||||
}
|
||||
public Puzzle getNextPuzzle()
|
||||
{
|
||||
if ( current_level != achieved_level )
|
||||
return getPuzzle( getCurrentLevel()+1 );
|
||||
else
|
||||
return null;
|
||||
}
|
||||
//
|
||||
// Provide amount of puzzles.
|
||||
//
|
||||
public final int getPuzzleCount()
|
||||
{
|
||||
return max_level;
|
||||
}
|
||||
//
|
||||
// Provide highest solved puzzle.
|
||||
//
|
||||
public final int getAchivedLevel()
|
||||
{
|
||||
return achieved_level;
|
||||
}
|
||||
//
|
||||
// Provide last played puzzle.
|
||||
//
|
||||
public final int getCurrentLevel()
|
||||
{
|
||||
return current_level;
|
||||
}
|
||||
//
|
||||
// Set the current puzzle level.
|
||||
//
|
||||
public void setCurrentLevel( int l )
|
||||
{
|
||||
if ( l > achieved_level )
|
||||
l = MIN_LEVEL;
|
||||
if ( l < MIN_LEVEL )
|
||||
l = achieved_level;
|
||||
if ( l != current_level )
|
||||
{
|
||||
current_level = l;
|
||||
prefsEdit.putInt("current_level", current_level);
|
||||
prefsEdit.apply();
|
||||
if ( !prefs.edit().commit() )
|
||||
Log.d(TAG, "prefs.edit().commit() failed.");
|
||||
}
|
||||
}
|
||||
//
|
||||
// Set achieved level.
|
||||
//
|
||||
public void advanceAchivedLevel( int l )
|
||||
{
|
||||
if ( l > max_level )
|
||||
l = max_level;
|
||||
if ( l > achieved_level )
|
||||
{
|
||||
achieved_level = l;
|
||||
prefsEdit.putInt("achieved_level", achieved_level);
|
||||
prefsEdit.apply();
|
||||
if ( !prefs.edit().commit() )
|
||||
Log.d(TAG, "prefs.edit().commit() failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
404
app/src/main/java/org/dyndns/vahagn/sokoban/Puzzle.java
Normal file
@@ -0,0 +1,404 @@
|
||||
package org.dyndns.vahagn.sokoban;
|
||||
|
||||
//import java.lang.Exception;
|
||||
import java.io.*;
|
||||
import static java.lang.Math.*;
|
||||
import android.util.Log;
|
||||
import android.graphics.Point;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import static org.dyndns.vahagn.sokoban.App.TAG;
|
||||
|
||||
public class Puzzle
|
||||
{
|
||||
public final static int FLOOR = 0x0;
|
||||
public final static int GOAL = 0x1;
|
||||
public final static int FLOOR_MASK = ~(GOAL | FLOOR);
|
||||
public final static int WALL = 0x2;
|
||||
public final static int BOX = 0x4;
|
||||
public final static int BINGO = BOX | GOAL;
|
||||
public final static int WORKER = 0x8;
|
||||
public final static int WORKER_NORTH = 0x00 | WORKER;
|
||||
public final static int WORKER_SOUTH = 0x10 | WORKER;
|
||||
public final static int WORKER_WEST = 0x20 | WORKER;
|
||||
public final static int WORKER_EAST = 0x30 | WORKER;
|
||||
public final static int WORKER_MASK = WORKER_EAST | WORKER_WEST | WORKER_SOUTH | WORKER_NORTH;
|
||||
public final static int SELECTED = 0x40;
|
||||
public final static int MAX_COUNT = 0x80-1;
|
||||
|
||||
protected int columns;
|
||||
protected int rows;
|
||||
protected int box_count;
|
||||
|
||||
protected int [] board;
|
||||
protected int worker_x;
|
||||
protected int worker_y;
|
||||
protected int selected_x;
|
||||
protected int selected_y;
|
||||
protected int bingos;
|
||||
|
||||
public Puzzle( InputStream is )
|
||||
{
|
||||
load( is );
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Low level helper functions.
|
||||
//
|
||||
|
||||
public final int getColumnCount()
|
||||
{
|
||||
return columns;
|
||||
}
|
||||
public final int getRowCount()
|
||||
{
|
||||
return rows;
|
||||
}
|
||||
public static int getSymCount()
|
||||
{
|
||||
return MAX_COUNT;
|
||||
}
|
||||
|
||||
public final int getIndex( int x, int y )
|
||||
{
|
||||
return y*columns+x;
|
||||
}
|
||||
public final int getX( int idx )
|
||||
{
|
||||
return idx % columns;
|
||||
}
|
||||
public final int getY( int idx )
|
||||
{
|
||||
return idx / columns;
|
||||
}
|
||||
|
||||
public final int getSym( int x, int y )
|
||||
{
|
||||
return board[getIndex(x,y)];
|
||||
}
|
||||
public void setSym( int x, int y, int v )
|
||||
{
|
||||
board[getIndex(x,y)]=v;
|
||||
}
|
||||
|
||||
public static boolean isEmpty( int v )
|
||||
{
|
||||
return (v & FLOOR_MASK) == 0;
|
||||
}
|
||||
public static boolean hasBox( int v )
|
||||
{
|
||||
return (v & BOX) != 0;
|
||||
}
|
||||
public static boolean hasWorker( int v )
|
||||
{
|
||||
return (v & WORKER) != 0;
|
||||
}
|
||||
|
||||
public final int getWorkerX()
|
||||
{
|
||||
return worker_x;
|
||||
}
|
||||
|
||||
public final int getWorkerY()
|
||||
{
|
||||
return worker_y;
|
||||
}
|
||||
|
||||
public final boolean isSelected()
|
||||
{
|
||||
return selected_x != -1;
|
||||
}
|
||||
|
||||
public final boolean isSelected( int x, int y )
|
||||
{
|
||||
return selected_x == x && selected_y == y;
|
||||
}
|
||||
|
||||
public final int getSelectedX()
|
||||
{
|
||||
return selected_x;
|
||||
}
|
||||
|
||||
public final int getSelectedY()
|
||||
{
|
||||
return selected_y;
|
||||
}
|
||||
|
||||
public final boolean isValid( int x, int y )
|
||||
{
|
||||
return ( 0 <= x && 0 <= y
|
||||
&& x < getColumnCount()
|
||||
&& y < getRowCount() );
|
||||
}
|
||||
|
||||
public final boolean isOneStep( int x, int y )
|
||||
{
|
||||
return isValid(x,y)
|
||||
&& (abs(worker_x-x )+abs(worker_y-y) == 1);
|
||||
}
|
||||
|
||||
public final boolean isDone()
|
||||
{
|
||||
return bingos == box_count;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Loading.
|
||||
//
|
||||
private void load( InputStream is )
|
||||
{try{
|
||||
columns = is.read();
|
||||
rows = is.read();
|
||||
box_count = 0;
|
||||
bingos = 0;
|
||||
selected_x = -1;
|
||||
selected_y = -1;
|
||||
board = new int[columns*rows];
|
||||
byte [] b = new byte [columns*rows];
|
||||
is.read( b, 0, columns*rows);
|
||||
for( int i = 0; i < b.length; ++i )
|
||||
{
|
||||
switch( b[i] )
|
||||
{
|
||||
case 0:
|
||||
board[i] = FLOOR;
|
||||
break;
|
||||
case 1:
|
||||
board[i] = WALL;
|
||||
break;
|
||||
case 2:
|
||||
board[i] = BOX;
|
||||
++box_count;
|
||||
break;
|
||||
case 3:
|
||||
board[i] = GOAL;
|
||||
break;
|
||||
case 4:
|
||||
board[i] = BINGO;
|
||||
++box_count;
|
||||
++bingos;
|
||||
break;
|
||||
case 5:
|
||||
board[i] = WORKER;
|
||||
worker_x = getX(i);
|
||||
worker_y = getY(i);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
Log.d( TAG, "load()", e);
|
||||
}}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Move worker.
|
||||
//
|
||||
|
||||
public boolean walk( int x, int y)
|
||||
{
|
||||
//
|
||||
// Check that this is one step away.
|
||||
//
|
||||
if ( !isOneStep(x,y) )
|
||||
return false;
|
||||
//
|
||||
// Check that this is empty space.
|
||||
//
|
||||
int v = getSym(x,y);
|
||||
if ( !isEmpty(v) )
|
||||
return false;
|
||||
//
|
||||
// If something is selected then unselect first.
|
||||
//
|
||||
if ( isSelected() )
|
||||
unselect();
|
||||
//
|
||||
// Find direction to move.
|
||||
//
|
||||
int worker;
|
||||
if ( worker_x < x )
|
||||
worker = WORKER_WEST;
|
||||
else if ( worker_x > x )
|
||||
worker = WORKER_EAST;
|
||||
else if ( worker_y > y )
|
||||
worker = WORKER_SOUTH;
|
||||
else
|
||||
worker = WORKER_NORTH;
|
||||
//
|
||||
// Move worker marker from current position to asked position.
|
||||
//
|
||||
setSym(worker_x,worker_y,getSym(worker_x,worker_y)&~WORKER_MASK);
|
||||
worker_x =x;
|
||||
worker_y =y;
|
||||
setSym(worker_x,worker_y,getSym(worker_x,worker_y)|worker);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean select( int x, int y)
|
||||
{
|
||||
//
|
||||
// Check that this is one step away.
|
||||
//
|
||||
if ( !isOneStep(x,y) )
|
||||
return false;
|
||||
//
|
||||
// Check that this is empty space.
|
||||
//
|
||||
int v = getSym(x,y);
|
||||
if ( !hasBox(v) )
|
||||
return false;
|
||||
//
|
||||
// If something is selected then unselect first.
|
||||
//
|
||||
if ( isSelected() )
|
||||
unselect();
|
||||
//
|
||||
// Find direction to move.
|
||||
//
|
||||
int worker;
|
||||
if ( worker_x < x )
|
||||
worker = WORKER_WEST;
|
||||
else if ( worker_x > x )
|
||||
worker = WORKER_EAST;
|
||||
else if ( worker_y > y )
|
||||
worker = WORKER_SOUTH;
|
||||
else
|
||||
worker = WORKER_NORTH;
|
||||
//
|
||||
// Move worker marker from current position to asked position.
|
||||
//
|
||||
selected_x =x;
|
||||
selected_y =y;
|
||||
setSym(worker_x,worker_y,getSym(worker_x,worker_y)&~WORKER_MASK|worker|SELECTED);
|
||||
setSym(selected_x,selected_y,getSym(selected_x,selected_y)|SELECTED);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean unselect()
|
||||
{
|
||||
//
|
||||
// If not selected then do nothing.
|
||||
//
|
||||
if ( !isSelected() )
|
||||
return false;
|
||||
//
|
||||
// Move worker marker from current position to asked position.
|
||||
//
|
||||
setSym(worker_x,worker_y,getSym(worker_x,worker_y)&~SELECTED);
|
||||
setSym(selected_x,selected_y,getSym(selected_x,selected_y)&~SELECTED);
|
||||
selected_x =-1;
|
||||
selected_y =-1;
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean push(int x, int y)
|
||||
{
|
||||
//
|
||||
// If not selected then do nothing.
|
||||
//
|
||||
if ( !isSelected() )
|
||||
return false;
|
||||
//
|
||||
// Check that we go to the selected direction.
|
||||
//
|
||||
if ( selected_x != x && selected_y != y )
|
||||
return false;
|
||||
//
|
||||
// Check that this is a box that we move.
|
||||
//
|
||||
int v = getSym(x,y);
|
||||
if ( !hasBox(v) )
|
||||
return false;
|
||||
//
|
||||
// Check that the next space to the box is empty.
|
||||
//w - 2*(w-x) = 2x -w
|
||||
int next_x = 2*x - worker_x;
|
||||
int next_y = 2*y - worker_y;
|
||||
int next_v = getSym(next_x,next_y);
|
||||
if ( !isEmpty(next_v) )
|
||||
return false;
|
||||
//
|
||||
// Ok, looks we can move the box. Do it actually.
|
||||
//
|
||||
unselect();
|
||||
setSym(x,y,getSym(x,y)&~BOX);
|
||||
setSym(next_x,next_y,getSym(next_x,next_y)|BOX);
|
||||
walk(x,y);
|
||||
select(next_x,next_y);
|
||||
//
|
||||
// Keep track of box count in place.
|
||||
//
|
||||
if ( (v & GOAL) != 0 )
|
||||
--bingos;
|
||||
if ( (next_v & GOAL) != 0 )
|
||||
++bingos;
|
||||
return true;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Undo/Redo.
|
||||
//
|
||||
|
||||
protected class State
|
||||
{
|
||||
public int [] board;
|
||||
public int worker_x;
|
||||
public int worker_y;
|
||||
public int selected_x;
|
||||
public int selected_y;
|
||||
public int bingos;
|
||||
|
||||
public State(Puzzle puzzle)
|
||||
{
|
||||
board = puzzle.board.clone();
|
||||
worker_x = puzzle.worker_x;
|
||||
worker_y = puzzle.worker_y;
|
||||
selected_x = puzzle.selected_x;
|
||||
selected_y = puzzle.selected_y;
|
||||
bingos = puzzle.bingos;
|
||||
}
|
||||
|
||||
public void restore(Puzzle puzzle)
|
||||
{
|
||||
puzzle.board = board;
|
||||
puzzle.worker_x = worker_x;
|
||||
puzzle.worker_y = worker_y;
|
||||
puzzle.selected_x = selected_x;
|
||||
puzzle.selected_y = selected_y;
|
||||
puzzle.bingos = bingos;
|
||||
}
|
||||
}
|
||||
protected LinkedList<State> undoStack = new LinkedList<State>();
|
||||
|
||||
//
|
||||
// Save this state to be able to restore later.
|
||||
//
|
||||
public void save()
|
||||
{
|
||||
State s = new State(this);
|
||||
undoStack.addFirst(s);
|
||||
}
|
||||
//
|
||||
// Restore state.
|
||||
//
|
||||
public void restore()
|
||||
{
|
||||
State s = undoStack.removeFirst();
|
||||
if ( s != null )
|
||||
s.restore(this);
|
||||
}
|
||||
//
|
||||
// Check if there are items in the undo stack.
|
||||
//
|
||||
public final boolean isUndoable()
|
||||
{
|
||||
return undoStack.size() != 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.dyndns.vahagn.sokoban;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import static org.dyndns.vahagn.sokoban.App.theApp;
|
||||
import android.util.Log;
|
||||
|
||||
public class PuzzleContainer
|
||||
{
|
||||
protected int count;
|
||||
|
||||
/*
|
||||
* Load puzzle from the puzzle.bin.
|
||||
*/
|
||||
public Puzzle getPuzzle( int i )
|
||||
{try{
|
||||
InputStream is = theApp().getResources().openRawResource(R.raw.puzzles);
|
||||
|
||||
//
|
||||
// Read amount of puzzles.
|
||||
//
|
||||
count = read_i32(is);
|
||||
if ( i >= count )
|
||||
return null;
|
||||
//
|
||||
// Read the offset.
|
||||
//
|
||||
is.skip( i*4 );
|
||||
int offset = read_i32(is);
|
||||
//
|
||||
// Jump to the offset and read the puzzle.
|
||||
//
|
||||
is.skip( offset - 4 - (i+1)*4);
|
||||
Puzzle p = new Puzzle( is );
|
||||
//
|
||||
// Finally return the puzzle and we are done.
|
||||
//
|
||||
return p;
|
||||
}
|
||||
catch ( java.lang.Exception e )
|
||||
{
|
||||
//Log.d(TAG, "Exception: " + e.getMessage() );
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}}
|
||||
//
|
||||
// Retrive amount of puzzles.
|
||||
//
|
||||
public int getCount()
|
||||
{try{
|
||||
if ( count == 0 )
|
||||
{
|
||||
InputStream is = theApp().getResources().openRawResource(R.raw.puzzles);
|
||||
count = read_i32(is);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
catch ( java.lang.Exception e )
|
||||
{
|
||||
//Log.d(TAG, "Exception: " + e.getMessage() );
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}}
|
||||
//
|
||||
// Helper function to read little endian 32bit integer.
|
||||
//
|
||||
protected int read_i32( InputStream is ) throws IOException
|
||||
{
|
||||
int i = is.read();
|
||||
i |= is.read() << 8;
|
||||
i |= is.read() << 16;
|
||||
i |= is.read() << 24;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package org.dyndns.vahagn.sokoban.menu;
|
||||
|
||||
import org.dyndns.vahagn.sokoban.play.PlayActivity;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Display;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.GridView;
|
||||
import android.widget.ImageView;
|
||||
import org.dyndns.vahagn.sokoban.R;
|
||||
import static org.dyndns.vahagn.sokoban.App.theApp;
|
||||
|
||||
public class MainActivity extends FragmentActivity
|
||||
{
|
||||
protected final String TAG = "SokobanMenu";
|
||||
protected final String LEVEL= "org.dyndns.vahagn.sokoban.LEVEL";
|
||||
protected MainFragment mainFragment;
|
||||
protected PuzzleListFragment puzzleListFragment;
|
||||
protected float puzzleListXScroll;
|
||||
protected float puzzleListXScrollMax;
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main_activity);
|
||||
|
||||
mainFragment = new MainFragment();
|
||||
puzzleListFragment = new PuzzleListFragment();
|
||||
puzzleListFragment.setOnPuzzleListAction(new PuzzleListFragment.OnPuzzleListAction() {
|
||||
public boolean onPuzzleGridDown() {
|
||||
return onPuzzleGridDown1();
|
||||
}
|
||||
public boolean onPuzzleGridXScroll(float x) {
|
||||
return onPuzzleGridXScroll1(x);
|
||||
}
|
||||
public void onPuzzleLevelClicked(int level) {
|
||||
onPuzzleClicked1(level);
|
||||
}
|
||||
});
|
||||
puzzleListXScrollMax = getWindowManager().getDefaultDisplay().getWidth();
|
||||
|
||||
//
|
||||
// Set initila fragment.
|
||||
//
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
||||
fragmentTransaction.add(R.id.fragment_container, mainFragment);
|
||||
// fragmentTransaction.add(R.id.fragment_container, puzzleListFragment);
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
//@Override
|
||||
public void onConfigurationChanged()
|
||||
{
|
||||
// super.onConfigurationChanged();
|
||||
// setContentView(R.layout-l.menu);
|
||||
}
|
||||
|
||||
public void onStartCurrentPuzzle(View v)
|
||||
{
|
||||
Intent intent = new Intent(this, PlayActivity.class);
|
||||
startActivity( intent );
|
||||
}
|
||||
|
||||
public void onPuzzleListShow(View v)
|
||||
{
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.setCustomAnimations(R.animator.fragment_slide_left_enter,
|
||||
R.animator.fragment_slide_left_exit)
|
||||
.replace(R.id.fragment_container, puzzleListFragment)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public boolean onPuzzleGridDown1()
|
||||
{
|
||||
puzzleListXScroll = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean onPuzzleGridXScroll1( float x )
|
||||
{
|
||||
puzzleListXScroll += x;
|
||||
/*
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.setCustomAnimations(R.animator.fragment_slide_right_enter,
|
||||
R.animator.fragment_slide_right_exit)
|
||||
.commit();
|
||||
*/
|
||||
if ( -puzzleListXScroll/puzzleListXScrollMax > 0.5 )
|
||||
{
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.setCustomAnimations(R.animator.fragment_slide_right_enter,
|
||||
R.animator.fragment_slide_right_exit)
|
||||
.replace(R.id.fragment_container, mainFragment)
|
||||
.commit();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onPuzzleClicked1( int level )
|
||||
{
|
||||
if ( level <= theApp().getAchivedLevel() )
|
||||
{
|
||||
theApp().setCurrentLevel(level);
|
||||
Intent intent = new Intent(this, PlayActivity.class);
|
||||
startActivity( intent );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.dyndns.vahagn.sokoban.menu;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import static org.dyndns.vahagn.sokoban.App.theApp;
|
||||
import org.dyndns.vahagn.sokoban.R;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author vahagnk
|
||||
*/
|
||||
public class MainFragment extends Fragment
|
||||
{
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle)
|
||||
{
|
||||
super.onCreate(icicle);
|
||||
View v = inflater.inflate(R.layout.main_fragment, container, false);
|
||||
if ( theApp().getAchivedLevel() == 1 )
|
||||
((Button)v.findViewById(R.id.btn_start)).setText(R.string.btn_start_begin);
|
||||
//
|
||||
// Set Footer.
|
||||
//
|
||||
String str = getString(R.string.copyright) + "\n"
|
||||
+getString(R.string.version_tag) + " " + getString(R.string.git_version);
|
||||
((TextView)v.findViewById(R.id.footer_txt)).setText(str);
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.dyndns.vahagn.sokoban.menu;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.TypedValue;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.GridView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import org.dyndns.vahagn.sokoban.R;
|
||||
import org.dyndns.vahagn.sokoban.R;
|
||||
import static org.dyndns.vahagn.sokoban.App.theApp;
|
||||
import org.dyndns.vahagn.sokoban.play.PlayActivity;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class PuzzleListFragment extends Fragment
|
||||
{
|
||||
protected GridView puzzleGrid;
|
||||
protected PuzzlesAdapter puzzleGridAdapter;
|
||||
protected GestureDetector gridViewXScrollDetector;
|
||||
protected OnPuzzleListAction actionListener;
|
||||
|
||||
/*
|
||||
* These are events which the fragments delegates to activity.
|
||||
*/
|
||||
public interface OnPuzzleListAction
|
||||
{
|
||||
public boolean onPuzzleGridDown();
|
||||
public boolean onPuzzleGridXScroll( float x );
|
||||
public void onPuzzleLevelClicked( int level );
|
||||
}
|
||||
public void setOnPuzzleListAction( OnPuzzleListAction l )
|
||||
{
|
||||
actionListener = l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle)
|
||||
{
|
||||
super.onCreate(icicle);
|
||||
View v = inflater.inflate(R.layout.puzzle_list_fragment, container, false);
|
||||
//
|
||||
// Configure GridView.
|
||||
//
|
||||
puzzleGrid = (GridView)v.findViewById(R.id.puzzle_grid);
|
||||
puzzleGridAdapter = new PuzzlesAdapter(getActivity());
|
||||
puzzleGrid.setAdapter( puzzleGridAdapter );
|
||||
puzzleGrid.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> parent, View v, int position, long id)
|
||||
{
|
||||
onPuzzleClicked(v,(int)id);
|
||||
}
|
||||
});
|
||||
puzzleGrid.setOnTouchListener( new View.OnTouchListener() {
|
||||
public boolean onTouch(View view, MotionEvent me) {
|
||||
gridViewXScrollDetector.onTouchEvent( me );
|
||||
return false;
|
||||
}
|
||||
});
|
||||
gridViewXScrollDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
|
||||
public boolean onDown(MotionEvent e) {
|
||||
return (actionListener!=null) ? actionListener.onPuzzleGridDown() : false;
|
||||
}
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) {
|
||||
return (actionListener!=null) ? actionListener.onPuzzleGridXScroll(dx) : false;
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
if ( puzzleGrid != null )
|
||||
puzzleGridAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void onPuzzleClicked(View v, int level )
|
||||
{
|
||||
if (actionListener!=null)
|
||||
actionListener.onPuzzleLevelClicked(level);
|
||||
}
|
||||
|
||||
public class PuzzlesAdapter extends BaseAdapter
|
||||
{
|
||||
private Context context;
|
||||
private int icon_size;
|
||||
private int text_x;
|
||||
private int text_y;
|
||||
private Bitmap lock_icon;
|
||||
private Bitmap unlock_icon;
|
||||
private Paint paint;
|
||||
|
||||
public PuzzlesAdapter(Context c)
|
||||
{
|
||||
context = c;
|
||||
icon_size = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 10, getResources().getDisplayMetrics() );
|
||||
int text_size = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 4, getResources().getDisplayMetrics() );
|
||||
text_x = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 9, getResources().getDisplayMetrics() );
|
||||
text_y = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 9, getResources().getDisplayMetrics() );
|
||||
|
||||
Bitmap lock_icon_tmp = BitmapFactory.decodeResource( getResources(), R.drawable.lock );
|
||||
lock_icon = Bitmap.createBitmap(icon_size,icon_size,Bitmap.Config.ARGB_8888);
|
||||
Canvas lock_canvas = new Canvas(lock_icon);
|
||||
lock_canvas.drawBitmap(lock_icon_tmp,
|
||||
new Rect(0,0,lock_icon_tmp.getWidth()-1,lock_icon_tmp.getHeight()-1),
|
||||
new Rect(0,0,icon_size, icon_size),
|
||||
null);
|
||||
|
||||
Bitmap unlock_icon_tmp = BitmapFactory.decodeResource( getResources(), R.drawable.unlock );
|
||||
unlock_icon = Bitmap.createBitmap(icon_size,icon_size,Bitmap.Config.ARGB_8888);
|
||||
Canvas unlock_canvas = new Canvas(unlock_icon);
|
||||
unlock_canvas.drawBitmap(unlock_icon_tmp,
|
||||
new Rect(0,0,unlock_icon_tmp.getWidth()-1,unlock_icon_tmp.getHeight()-1),
|
||||
new Rect(0,0,icon_size, icon_size),
|
||||
null);
|
||||
|
||||
paint = new Paint();
|
||||
paint.setColor( Color.WHITE );
|
||||
paint.setTextSize( text_size );
|
||||
paint.setTextAlign(Paint.Align.RIGHT);
|
||||
}
|
||||
|
||||
public int getCount()
|
||||
{
|
||||
return theApp().getPuzzleCount();
|
||||
}
|
||||
|
||||
public Object getItem(int position)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public long getItemId(int position)
|
||||
{
|
||||
return position+1;
|
||||
}
|
||||
|
||||
// create a new ImageView for each item referenced by the Adapter
|
||||
public View getView(int position, View convertView, ViewGroup parent)
|
||||
{
|
||||
ImageView imageView;
|
||||
if (convertView == null)
|
||||
{ // if it's not recycled, initialize some attributes
|
||||
imageView = new ImageView(context);
|
||||
imageView.setLayoutParams(new GridView.LayoutParams(icon_size, icon_size));
|
||||
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||
}
|
||||
else
|
||||
imageView = (ImageView) convertView;
|
||||
|
||||
int id = (int)getItemId(position);
|
||||
Bitmap icon = ( id <= theApp().getAchivedLevel() )
|
||||
? unlock_icon.copy(Bitmap.Config.ARGB_8888,true)
|
||||
: lock_icon.copy(Bitmap.Config.ARGB_8888,true);
|
||||
Canvas canvas = new Canvas(icon);
|
||||
canvas.drawText( Integer.toString(id), text_x, text_y, paint);
|
||||
imageView.setImageBitmap(icon);
|
||||
return imageView;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
209
app/src/main/java/org/dyndns/vahagn/sokoban/play/Animator.java
Normal file
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.dyndns.vahagn.sokoban.play;
|
||||
|
||||
import org.dyndns.vahagn.sokoban.play.PuzzleView;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author vahagnk
|
||||
*/
|
||||
public class Animator implements Runnable
|
||||
{
|
||||
protected PuzzleView view;
|
||||
protected LinkedList<Action> actions = new LinkedList<Action>();
|
||||
protected boolean stop;
|
||||
protected int step;
|
||||
protected Action currentAction;
|
||||
protected AnimationLister theLister;
|
||||
|
||||
public interface AnimationLister
|
||||
{
|
||||
public void onAnimationEnd();
|
||||
}
|
||||
|
||||
public Animator( PuzzleView v)
|
||||
{
|
||||
view = v;
|
||||
}
|
||||
|
||||
public void setAnimationLister( AnimationLister lister )
|
||||
{
|
||||
theLister = lister;
|
||||
}
|
||||
|
||||
public void queue( Action a )
|
||||
{
|
||||
actions.addLast(a);
|
||||
}
|
||||
|
||||
public void play()
|
||||
{
|
||||
//
|
||||
// If no actions exist then nothing to do.
|
||||
//
|
||||
if ( actions.size() == 0 )
|
||||
return;
|
||||
//
|
||||
// Get the first action an play it.
|
||||
//
|
||||
stop = false;
|
||||
step = 0;
|
||||
currentAction = actions.removeFirst();
|
||||
ViewCompat.postOnAnimation(view, this);
|
||||
}
|
||||
|
||||
public void stop()
|
||||
{
|
||||
stop = true;
|
||||
view.removeCallbacks(this);
|
||||
if ( theLister != null )
|
||||
theLister.onAnimationEnd();
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
actions.clear();
|
||||
currentAction = null;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
if ( currentAction == null )
|
||||
return;
|
||||
else if ( currentAction.steps > step )
|
||||
{
|
||||
currentAction.intermediate(this,step);
|
||||
step++;
|
||||
ViewCompat.postOnAnimation(view, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentAction.last(this);
|
||||
step = 0;
|
||||
currentAction = null;
|
||||
if ( !stop && actions.size() > 0 )
|
||||
{
|
||||
currentAction = actions.removeFirst();
|
||||
step = 0;
|
||||
ViewCompat.postOnAnimation(view, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( theLister != null )
|
||||
theLister.onAnimationEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class Action
|
||||
{
|
||||
public int steps;
|
||||
|
||||
public Action()
|
||||
{
|
||||
steps = 0;
|
||||
}
|
||||
|
||||
public abstract void intermediate( Animator ap, int step );
|
||||
public abstract void last( Animator ap );
|
||||
}
|
||||
|
||||
protected static abstract class XYAction extends Action
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
|
||||
public XYAction( int _x, int _y )
|
||||
{
|
||||
x = _x;
|
||||
y = _y;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Move extends XYAction
|
||||
{
|
||||
public Move( int x, int y )
|
||||
{
|
||||
super(x,y);
|
||||
}
|
||||
|
||||
public void intermediate( Animator ap, int step )
|
||||
{}
|
||||
|
||||
public void last( Animator ap )
|
||||
{
|
||||
ap.view.getPuzzle().walk(x,y);
|
||||
ap.view.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Push extends XYAction
|
||||
{
|
||||
public Push( int x, int y )
|
||||
{
|
||||
super(x,y);
|
||||
}
|
||||
|
||||
public void intermediate( Animator ap, int step )
|
||||
{
|
||||
}
|
||||
|
||||
public void last( Animator ap )
|
||||
{
|
||||
ap.view.getPuzzle().push(x,y);
|
||||
ap.view.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Select extends XYAction
|
||||
{
|
||||
public Select(int x, int y )
|
||||
{
|
||||
super(x,y);
|
||||
}
|
||||
|
||||
public void intermediate( Animator ap, int step )
|
||||
{
|
||||
}
|
||||
|
||||
public void last( Animator ap )
|
||||
{
|
||||
ap.view.getPuzzle().select(x,y);
|
||||
ap.view.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Unselect extends Action
|
||||
{
|
||||
public Unselect()
|
||||
{}
|
||||
|
||||
public void intermediate( Animator ap, int step )
|
||||
{}
|
||||
|
||||
public void last( Animator ap )
|
||||
{
|
||||
ap.view.getPuzzle().unselect();
|
||||
ap.view.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static class NoMove extends XYAction
|
||||
{
|
||||
public NoMove( int x, int y )
|
||||
{
|
||||
super(x,y);
|
||||
}
|
||||
|
||||
public void intermediate( Animator ap, int step )
|
||||
{}
|
||||
|
||||
public void last( Animator ap )
|
||||
{}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package org.dyndns.vahagn.sokoban.play;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.zip.Inflater;
|
||||
import org.dyndns.vahagn.sokoban.App;
|
||||
import static org.dyndns.vahagn.sokoban.App.TAG;
|
||||
import static org.dyndns.vahagn.sokoban.App.theApp;
|
||||
import org.dyndns.vahagn.sokoban.R;
|
||||
import org.dyndns.vahagn.sokoban.Puzzle;
|
||||
import org.dyndns.vahagn.sokoban.menu.MainActivity;
|
||||
|
||||
public class PlayActivity extends FragmentActivity
|
||||
implements PuzzleControl.PuzzleControlLister
|
||||
{
|
||||
protected final static int RESET_ITEM = 1;
|
||||
protected final static int UNDO_ITEM = 2;
|
||||
|
||||
public Puzzle puzzle = null;
|
||||
public PuzzleControl puzzle_view;
|
||||
View title_view;
|
||||
public TextView title_text;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
//
|
||||
// No title.
|
||||
//
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
//
|
||||
// Create and set up the puzzle_view.
|
||||
//
|
||||
puzzle_view = new PuzzleControl( this );
|
||||
setContentView( puzzle_view );
|
||||
puzzle_view.setPuzzleControlLister( this );
|
||||
//
|
||||
// Create action bar.
|
||||
//
|
||||
FrameLayout rootLayout = (FrameLayout)findViewById(android.R.id.content);
|
||||
View.inflate(this, R.layout.puzzle_view_title, rootLayout);
|
||||
title_text = (TextView)findViewById(R.id.title_text);
|
||||
title_view = findViewById(R.id.puzzle_view_title_layout);
|
||||
//
|
||||
// Load the puzzle.
|
||||
//
|
||||
loadCurrentPuzzle();
|
||||
}
|
||||
|
||||
public void onSolved()
|
||||
{
|
||||
//
|
||||
// Advance current level and achieved level.
|
||||
//
|
||||
final int nextl = theApp().getCurrentLevel()+1;
|
||||
theApp().advanceAchivedLevel(nextl);
|
||||
theApp().setCurrentLevel(nextl);
|
||||
//
|
||||
// Bring a dialog for user to choose to continue or stop.
|
||||
//
|
||||
DialogFragment df = new DialogFragment()
|
||||
{
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||
{
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setTitle("Congratulations! You Won!");
|
||||
builder.setMessage( "Would you like to try the next puzzle?" );
|
||||
builder.setNegativeButton("Enough",
|
||||
new DialogInterface.OnClickListener()
|
||||
{
|
||||
public void onClick(DialogInterface d, int w)
|
||||
{
|
||||
finish();
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton("Please",
|
||||
new DialogInterface.OnClickListener()
|
||||
{
|
||||
public void onClick(DialogInterface d, int w)
|
||||
{
|
||||
loadCurrentPuzzle();
|
||||
puzzle_view.invalidate();
|
||||
}
|
||||
});
|
||||
// Create the AlertDialog object and return it
|
||||
Dialog dlg = builder.create();
|
||||
dlg.setCancelable(false);
|
||||
dlg.setCanceledOnTouchOutside(false);
|
||||
return dlg;
|
||||
}
|
||||
};
|
||||
df.setCancelable(false);
|
||||
df.show(getSupportFragmentManager(), null);
|
||||
//dlg.show();
|
||||
}
|
||||
|
||||
public void onLongPress()
|
||||
{
|
||||
if ( title_view.getVisibility() != View.VISIBLE )
|
||||
showTitle();
|
||||
}
|
||||
|
||||
public void onTouch()
|
||||
{
|
||||
if ( title_view.getVisibility() == View.VISIBLE )
|
||||
hideTitle();
|
||||
}
|
||||
|
||||
public void showTitle()
|
||||
{
|
||||
Animation anim = AnimationUtils.loadAnimation(this, R.animator.puzzle_action_bar_enter);
|
||||
title_view.startAnimation(anim);
|
||||
title_view.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void hideTitle()
|
||||
{
|
||||
Animation anim = AnimationUtils.loadAnimation(this, R.animator.puzzle_action_bar_exit);
|
||||
title_view.startAnimation(anim);
|
||||
title_view.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
public boolean onReset( View v )
|
||||
{
|
||||
while ( puzzle.isUndoable() )
|
||||
puzzle.restore();
|
||||
puzzle_view.invalidate();
|
||||
return true;
|
||||
}
|
||||
//
|
||||
// Go to previouse puzzle.
|
||||
//
|
||||
public boolean onPrev( View v )
|
||||
{
|
||||
//
|
||||
// If current level is less than achived level the move to next
|
||||
// puzzle.
|
||||
//
|
||||
if ( theApp().getCurrentLevel() > App.MIN_LEVEL )
|
||||
{
|
||||
theApp().setCurrentLevel( theApp().getCurrentLevel()-1 );
|
||||
loadCurrentPuzzle();
|
||||
puzzle_view.invalidate();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
//
|
||||
// Go to next puzzle.
|
||||
//
|
||||
public boolean onNext( View v )
|
||||
{
|
||||
//
|
||||
// If current level is less than achived level the move to next
|
||||
// puzzle.
|
||||
//
|
||||
if ( theApp().getCurrentLevel() < theApp().getAchivedLevel() )
|
||||
{
|
||||
theApp().setCurrentLevel( theApp().getCurrentLevel()+1 );
|
||||
loadCurrentPuzzle();
|
||||
puzzle_view.invalidate();
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
//
|
||||
// Undo last action.
|
||||
//
|
||||
public boolean onUndo( View v )
|
||||
{
|
||||
if ( puzzle.isUndoable() )
|
||||
{
|
||||
puzzle.restore();
|
||||
puzzle_view.invalidate();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private void loadCurrentPuzzle()
|
||||
{
|
||||
puzzle = theApp().getCurrentPuzzle();
|
||||
puzzle_view.setPuzzle(puzzle);
|
||||
updateTitle();
|
||||
}
|
||||
public void updateTitle()
|
||||
{
|
||||
String title = "Level " + new Integer(theApp().getCurrentLevel()).toString();
|
||||
setTitle(title);
|
||||
}
|
||||
public void setTitle(CharSequence title)
|
||||
{
|
||||
super.setTitle(title);
|
||||
if (title_text != null) {
|
||||
title_text.setText(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.dyndns.vahagn.sokoban.play;
|
||||
|
||||
import org.dyndns.vahagn.sokoban.play.Animator;
|
||||
import org.dyndns.vahagn.sokoban.play.PuzzleView;
|
||||
import org.dyndns.vahagn.sokoban.play.PuzzleLogic;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
import org.dyndns.vahagn.sokoban.Puzzle;
|
||||
import static org.dyndns.vahagn.sokoban.App.TAG;
|
||||
|
||||
//import android.support.v4.view.GestureDetectorCompat;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author vahagnk
|
||||
*/
|
||||
public class PuzzleControl extends PuzzleView
|
||||
implements GestureDetector.OnGestureListener
|
||||
, GestureDetector.OnDoubleTapListener
|
||||
, ScaleGestureDetector.OnScaleGestureListener
|
||||
, Animator.AnimationLister
|
||||
{
|
||||
protected GestureDetector simpled;
|
||||
protected ScaleGestureDetector scaled;
|
||||
protected PuzzleLogic logic;
|
||||
protected Animator animator;
|
||||
protected PuzzleControlLister lister;
|
||||
|
||||
protected float lastSpan;
|
||||
protected Point singleTapTile = new Point();
|
||||
|
||||
public interface PuzzleControlLister
|
||||
{
|
||||
public void onSolved();
|
||||
public void onLongPress();
|
||||
public void onTouch();
|
||||
}
|
||||
|
||||
public PuzzleControl(Context c)
|
||||
{
|
||||
super(c);
|
||||
logic = new PuzzleLogic();
|
||||
animator = new Animator( this );
|
||||
animator.setAnimationLister(this);
|
||||
|
||||
simpled = new GestureDetector( c, this );
|
||||
simpled.setOnDoubleTapListener(this);
|
||||
scaled = new ScaleGestureDetector( c, this );
|
||||
}
|
||||
|
||||
public void setPuzzle( Puzzle p )
|
||||
{
|
||||
super.setPuzzle(p);
|
||||
logic.setPuzzle(p);
|
||||
}
|
||||
|
||||
public void setPuzzleControlLister( PuzzleControlLister l )
|
||||
{
|
||||
lister = l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event)
|
||||
{
|
||||
//
|
||||
// Stop any pending animations.
|
||||
//
|
||||
animator.stop();
|
||||
animator.clear();
|
||||
//
|
||||
// Pass this event to scale and gesture detectors.
|
||||
//
|
||||
boolean r = scaled.onTouchEvent(event);
|
||||
r = simpled.onTouchEvent(event) || r;
|
||||
//r = super.onTouchEvent(event) || r;
|
||||
return r;
|
||||
}
|
||||
|
||||
public boolean onDown(MotionEvent e)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
public void onLongPress(MotionEvent e)
|
||||
{
|
||||
if ( lister != null )
|
||||
lister.onLongPress();
|
||||
}
|
||||
public void onShowPress(MotionEvent e)
|
||||
{
|
||||
}
|
||||
public boolean onSingleTapUp(MotionEvent e)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
public boolean onDoubleTap(MotionEvent e)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
public boolean onDoubleTapEvent(MotionEvent e)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean onSingleTapConfirmed(MotionEvent e)
|
||||
{
|
||||
//
|
||||
// Translate the tap point into tile column and row.
|
||||
//
|
||||
getTile( e.getX(), e.getY(), singleTapTile );
|
||||
//
|
||||
// If the tap is not on a valid tile then there is not point to
|
||||
// continue.
|
||||
//
|
||||
if ( !puzzle.isValid(singleTapTile.x, singleTapTile.y) )
|
||||
return true;
|
||||
//
|
||||
// Create sequence of steps and then animate it.
|
||||
//
|
||||
if ( logic.createSteps(animator, singleTapTile.x, singleTapTile.y) )
|
||||
{
|
||||
puzzle.save();
|
||||
animator.play();
|
||||
}
|
||||
//
|
||||
// Notify that we were touched.
|
||||
//
|
||||
if ( lister != null )
|
||||
lister.onTouch();
|
||||
//
|
||||
// This event is processed.
|
||||
//
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
|
||||
{
|
||||
scrollViewport( distanceX, distanceY );
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean onScaleBegin(ScaleGestureDetector detector)
|
||||
{
|
||||
lastSpan = scaled.getCurrentSpan();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean onScale(ScaleGestureDetector detector)
|
||||
{
|
||||
float span = scaled.getCurrentSpan();
|
||||
float focusX = scaled.getFocusX();
|
||||
float focusY = scaled.getFocusY();
|
||||
scaleViewport(focusX,focusY,lastSpan/span);
|
||||
lastSpan = span;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onScaleEnd(ScaleGestureDetector detector)
|
||||
{
|
||||
scaleViewportDone();
|
||||
}
|
||||
|
||||
public void onAnimationEnd()
|
||||
{
|
||||
if ( puzzle.isDone() )
|
||||
if ( lister != null )
|
||||
lister.onSolved();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,541 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.dyndns.vahagn.sokoban.play;
|
||||
|
||||
import static java.lang.Math.*;
|
||||
import java.util.Arrays;
|
||||
import org.dyndns.vahagn.sokoban.Puzzle;
|
||||
import static org.dyndns.vahagn.sokoban.App.TAG;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author vahagnk
|
||||
*/
|
||||
public class PuzzleLogic
|
||||
{
|
||||
//protected PlayActivity activity;
|
||||
protected Puzzle puzzle = null;
|
||||
protected int [] moves;
|
||||
protected int [] setOfCells;
|
||||
|
||||
public PuzzleLogic()
|
||||
{}
|
||||
|
||||
public void setPuzzle( Puzzle p )
|
||||
{
|
||||
puzzle = p;
|
||||
moves = new int[puzzle.getColumnCount()*puzzle.getRowCount()];
|
||||
setOfCells = new int[2*puzzle.getColumnCount()*puzzle.getRowCount()];
|
||||
}
|
||||
|
||||
public boolean createSteps(Animator animator, final int x, final int y )
|
||||
{
|
||||
//
|
||||
// Check that the x,y are valid.
|
||||
//
|
||||
if ( !puzzle.isValid(x, y) )
|
||||
return false;
|
||||
//
|
||||
// Now check what tile was tapped.
|
||||
// If the tapped is a floor then ...
|
||||
//
|
||||
int tile = puzzle.getSym( x, y);
|
||||
if ( Puzzle.isEmpty(tile) )
|
||||
{
|
||||
//
|
||||
// Calculate possible moves map.
|
||||
//
|
||||
calcMoves();
|
||||
//
|
||||
// Check if worker selected a box and can push it to the location?
|
||||
// If yes then we are done.
|
||||
//
|
||||
if ( puzzle.isSelected()
|
||||
&& tryMoveBox(animator, x, y) )
|
||||
return true;
|
||||
//
|
||||
// Either nothing was selected or the box cannot be moved to
|
||||
// tapped location. Try move worker alone.
|
||||
//
|
||||
if ( tryMoveWorker( animator, x, y ) )
|
||||
return true;
|
||||
//
|
||||
// Show that action is not allowed.
|
||||
//
|
||||
undoable( animator, x, y );
|
||||
}
|
||||
//
|
||||
// The tapped is the worker. Try move the box. If not possible
|
||||
// then unselect if selected.
|
||||
//
|
||||
else if ( Puzzle.hasWorker(tile) )
|
||||
{
|
||||
if ( !puzzle.isSelected() )
|
||||
{
|
||||
buzz( animator );
|
||||
return true;
|
||||
}
|
||||
//
|
||||
// Calculate possible moves map.
|
||||
//
|
||||
calcMoves();
|
||||
//
|
||||
// Check if worker selected a box and can push it to the location?
|
||||
// If yes then we are done.
|
||||
//
|
||||
if ( tryMoveBox(animator, x, y) )
|
||||
return true;
|
||||
//
|
||||
// If the box is not movable then unselect it.
|
||||
//
|
||||
unselect( animator );
|
||||
}
|
||||
//
|
||||
// The tapped is a box.
|
||||
//
|
||||
else if ( Puzzle.hasBox(tile) )
|
||||
{
|
||||
//
|
||||
// Calculate possible moves map.
|
||||
//
|
||||
calcMoves();
|
||||
//
|
||||
// If the box is selected then unselect it.
|
||||
//
|
||||
if ( puzzle.isSelected(x,y) )
|
||||
{
|
||||
unselect( animator );
|
||||
return true;
|
||||
}
|
||||
//
|
||||
// Try move the worker next to the box and select it if the
|
||||
// box is not selected yet.
|
||||
//
|
||||
if ( trySelectBox( animator, x, y ) )
|
||||
return true;
|
||||
//
|
||||
// Show that action is not allowed if reached till here.
|
||||
//
|
||||
undoable( animator, x, y );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Routes to accessible cells from where worker stands.
|
||||
//
|
||||
protected boolean tryMoveWorker( Animator animator, int x, int y )
|
||||
{
|
||||
//
|
||||
// If the filed is not accessable then move failed.
|
||||
//
|
||||
if ( !isAccessible(x,y) )
|
||||
return false;
|
||||
//
|
||||
// First unselect box.
|
||||
//
|
||||
if ( puzzle.isSelected() )
|
||||
unselect( animator );
|
||||
//
|
||||
// Get directions and queue moves accordingly.
|
||||
//
|
||||
int dirs[] = getDirections(x, y);
|
||||
for ( int i = 0; i < dirs.length; i+=2 )
|
||||
move( animator, dirs[i],dirs[i+1] );
|
||||
//
|
||||
// Done.
|
||||
//
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean tryMoveBox( Animator animator, int x, int y )
|
||||
{
|
||||
//
|
||||
// If no box is selected then we cannot move no box.
|
||||
//
|
||||
if ( !puzzle.isSelected() )
|
||||
return false;
|
||||
//
|
||||
// Check that asked move is orthogonal to the slected box.
|
||||
//
|
||||
int bx = puzzle.getSelectedX();
|
||||
int by = puzzle.getSelectedY();
|
||||
int dx = x-bx;
|
||||
int dy = y-by;
|
||||
if ( dx != 0 && dy != 0 )
|
||||
return false;
|
||||
//
|
||||
// There is no point to continue also in case if the asked cell
|
||||
// is the box.
|
||||
//
|
||||
if ( dx == 0 && dy == 0 )
|
||||
return false;
|
||||
//
|
||||
// Now find the desired place for the worker to start push this
|
||||
// box.
|
||||
//
|
||||
int wx = bx;
|
||||
int wy = by;
|
||||
if ( x < bx )
|
||||
++wx;
|
||||
else if ( x > bx )
|
||||
--wx;
|
||||
else if ( y < by )
|
||||
++wy;
|
||||
else
|
||||
--wy;
|
||||
//
|
||||
// Check if the desired place for the worker is accessable? If not
|
||||
// then there is no point to continue.
|
||||
//
|
||||
if ( !isAccessible(wx, wy) )
|
||||
return false;
|
||||
//
|
||||
// Now check that all cell till x,y are empty and that we can
|
||||
// push box till there.
|
||||
//
|
||||
int sx = bx-wx;
|
||||
int sy = by-wy;
|
||||
int ix = bx;
|
||||
int iy = by;
|
||||
while ( x != ix || y != iy )
|
||||
{
|
||||
ix+=sx;
|
||||
iy+=sy;
|
||||
int v = puzzle.getSym(ix,iy);
|
||||
if ( !puzzle.isEmpty(v) && !puzzle.hasWorker(v) )
|
||||
return false;
|
||||
}
|
||||
//
|
||||
// Ok, looks we can do the desired action. Now put instructions on
|
||||
// what to do. First move worker to desired position if he is not
|
||||
// there already.
|
||||
//
|
||||
if ( wx != puzzle.getWorkerX() || wy != puzzle.getWorkerY() )
|
||||
{
|
||||
tryMoveWorker( animator, wx, wy );
|
||||
select( animator, bx, by );
|
||||
}
|
||||
//
|
||||
// Now create the steps to push the box.
|
||||
//
|
||||
ix = bx;
|
||||
iy = by;
|
||||
while ( x != ix || y != iy )
|
||||
{
|
||||
push( animator, ix, iy );
|
||||
ix+=sx;
|
||||
iy+=sy;
|
||||
}
|
||||
//
|
||||
// Done
|
||||
//
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean trySelectBox( Animator animator, int x, int y )
|
||||
{
|
||||
int north = puzzle.getSym(x,y-1);
|
||||
int south = puzzle.getSym(x,y+1);
|
||||
int west = puzzle.getSym(x-1,y);
|
||||
int east = puzzle.getSym(x+1,y);
|
||||
//
|
||||
// First check if there is a worker in a nighbour cell. If
|
||||
// yes then simplly select the box. If the box is already selected
|
||||
// then do nothing and if othe box is selected then unselect it first
|
||||
// and then select the box.
|
||||
//
|
||||
if ( Puzzle.hasWorker( west )
|
||||
|| Puzzle.hasWorker( east )
|
||||
|| Puzzle.hasWorker( north )
|
||||
|| Puzzle.hasWorker( south ) )
|
||||
{
|
||||
if ( !puzzle.isSelected(x,y) )
|
||||
{
|
||||
if ( puzzle.isSelected() )
|
||||
unselect( animator );
|
||||
select( animator, x, y );
|
||||
}
|
||||
}
|
||||
//
|
||||
// Otherwise, check which is of the cells is in closes walking
|
||||
// distance and move worker to that cell, then select.
|
||||
//
|
||||
else
|
||||
{
|
||||
int pref_x = -1;
|
||||
int pref_y = -1;
|
||||
int shortest = Integer.MAX_VALUE;
|
||||
if ( Puzzle.isEmpty( north )
|
||||
&& shortest > stepsAway( x, y-1) )
|
||||
{
|
||||
shortest = stepsAway( x, y-1);
|
||||
pref_x = x;
|
||||
pref_y = y-1;
|
||||
}
|
||||
if ( Puzzle.isEmpty( south )
|
||||
&& shortest > stepsAway( x, y+1) )
|
||||
{
|
||||
shortest = stepsAway( x, y+1);
|
||||
pref_x = x;
|
||||
pref_y = y+1;
|
||||
}
|
||||
if ( Puzzle.isEmpty( west )
|
||||
&& shortest > stepsAway( x-1, y) )
|
||||
{
|
||||
shortest = stepsAway( x-1, y);
|
||||
pref_x = x-1;
|
||||
pref_y = y;
|
||||
}
|
||||
if ( Puzzle.isEmpty( east )
|
||||
&& shortest > stepsAway( x+1, y) )
|
||||
{
|
||||
shortest = stepsAway( x+1, y);
|
||||
pref_x = x+1;
|
||||
pref_y = y;
|
||||
}
|
||||
//
|
||||
// Move the worker to the direction. If we cannot move worker
|
||||
// next to the box then we cannot select it.
|
||||
//
|
||||
if ( !puzzle.isValid(pref_x, pref_y)
|
||||
|| !tryMoveWorker(animator, pref_x, pref_y) )
|
||||
return false;
|
||||
//
|
||||
// Select the box.
|
||||
//
|
||||
select(animator,x,y);
|
||||
}
|
||||
//
|
||||
// Done
|
||||
//
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void move( Animator animator, int x, int y )
|
||||
{
|
||||
animator.queue( new Animator.Move(x,y) );
|
||||
}
|
||||
|
||||
protected void push( Animator animator, int x, int y )
|
||||
{
|
||||
animator.queue( new Animator.Push(x,y) );
|
||||
}
|
||||
|
||||
protected void select( Animator animator, int x, int y )
|
||||
{
|
||||
animator.queue( new Animator.Select(x,y) );
|
||||
}
|
||||
|
||||
protected void unselect( Animator animator )
|
||||
{
|
||||
animator.queue( new Animator.Unselect() );
|
||||
}
|
||||
|
||||
protected void buzz( Animator animator )
|
||||
{
|
||||
//animator.queue( new Animator.NoMove(x,y) );
|
||||
}
|
||||
|
||||
protected void undoable( Animator animator, int x, int y )
|
||||
{
|
||||
animator.queue( new Animator.NoMove(x,y) );
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Routes to accessible cells from where worker stands.
|
||||
//
|
||||
|
||||
protected final int getMoves( int x, int y )
|
||||
{
|
||||
return moves[puzzle.getIndex(x,y)];
|
||||
}
|
||||
protected void setMoves( int x, int y, int v )
|
||||
{
|
||||
moves[puzzle.getIndex(x,y)]=v;
|
||||
}
|
||||
private boolean setMovesIfGreater( int x, int y, int l )
|
||||
{
|
||||
//
|
||||
// If out of borders then nothing to do.
|
||||
//
|
||||
if ( y < 0 || y >= puzzle.getRowCount()
|
||||
|| x < 0 || x >= puzzle.getColumnCount() )
|
||||
return false;
|
||||
//
|
||||
// Check if the cell is a floor or goal. If yes then set the l
|
||||
// if current value is greater than l.
|
||||
//
|
||||
if ( getMoves(x,y) > l && puzzle.isEmpty(puzzle.getSym(x,y)))
|
||||
{
|
||||
setMoves(x,y,l);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public void calcMoves()
|
||||
{
|
||||
//
|
||||
// Erase moves array.
|
||||
//
|
||||
Arrays.fill(moves,Integer.MAX_VALUE);
|
||||
//
|
||||
// For the beginning there is no cell in the list.
|
||||
//
|
||||
int front = 0;
|
||||
int last = 0;
|
||||
//
|
||||
// Set the seed.
|
||||
//
|
||||
setMoves(puzzle.getWorkerX(),puzzle.getWorkerY(),0);
|
||||
setOfCells[last++] = puzzle.getWorkerX();
|
||||
setOfCells[last++] = puzzle.getWorkerY();
|
||||
//
|
||||
// Now on each loop pop one cell from the list and calculate the
|
||||
// distance of cell around that cell.
|
||||
//
|
||||
while ( front < last )
|
||||
{
|
||||
//
|
||||
// Pop the cell.
|
||||
//
|
||||
int x = setOfCells[front++];
|
||||
int y = setOfCells[front++];
|
||||
//
|
||||
// Increase the length of cells all around given cell and push
|
||||
// them into the list.
|
||||
//
|
||||
int l = getMoves(x,y)+1;
|
||||
if ( setMovesIfGreater(x-1,y,l) )
|
||||
{
|
||||
setOfCells[last++] = x-1;
|
||||
setOfCells[last++] = y;
|
||||
}
|
||||
if ( setMovesIfGreater(x+1,y,l) )
|
||||
{
|
||||
setOfCells[last++] = x+1;
|
||||
setOfCells[last++] = y;
|
||||
}
|
||||
if ( setMovesIfGreater(x,y-1,l) )
|
||||
{
|
||||
setOfCells[last++] = x;
|
||||
setOfCells[last++] = y-1;
|
||||
}
|
||||
if ( setMovesIfGreater(x,y+1,l) )
|
||||
{
|
||||
setOfCells[last++] = x;
|
||||
setOfCells[last++] = y+1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final int stepsAway( int x, int y )
|
||||
{
|
||||
return getMoves(x,y);
|
||||
}
|
||||
|
||||
public final boolean isAccessible( int x, int y )
|
||||
{
|
||||
return puzzle.isValid(x, y) && stepsAway(x, y) != Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
public int [] getDirections( int x, int y )
|
||||
{
|
||||
int away = stepsAway(x,y);
|
||||
if (away == Integer.MAX_VALUE)
|
||||
return null;
|
||||
//
|
||||
// Ok looks there is a routh to given cell. Now create an array
|
||||
// and fill in the step to get to the cell.
|
||||
//
|
||||
int [] steps = new int[2*away];
|
||||
int i = steps.length;
|
||||
steps[--i] = y;
|
||||
steps[--i] = x;
|
||||
while ( i > 0 )
|
||||
{
|
||||
x = steps[i];
|
||||
y = steps[i+1];
|
||||
int j = stepsAway(x,y);
|
||||
if ( stepsAway(x-1,y) < j )
|
||||
{
|
||||
steps[--i] = y;
|
||||
steps[--i] = x-1;
|
||||
}
|
||||
else if ( stepsAway(x+1,y) < j )
|
||||
{
|
||||
steps[--i] = y;
|
||||
steps[--i] = x+1;
|
||||
}
|
||||
else if ( stepsAway(x,y-1) < j )
|
||||
{
|
||||
steps[--i] = y-1;
|
||||
steps[--i] = x;
|
||||
}
|
||||
else if ( stepsAway(x,y+1) < j )
|
||||
{
|
||||
steps[--i] = y+1;
|
||||
steps[--i] = x;
|
||||
}
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
|
||||
public int [] getPushDirections( int x, int y )
|
||||
{
|
||||
//
|
||||
// The selected box can be moved only orthogonally.
|
||||
// Check that worker is on opposite side of the box.
|
||||
// Statement:
|
||||
// If scaliar product of selected->x,y and worker->selected is equal
|
||||
// to manhatten distance of x,y from the selected box then the worker
|
||||
// push direction is directed to x,y and is not opposit. In other words
|
||||
// selected->x,y and worker->selected are codirectional.
|
||||
//
|
||||
int selx = puzzle.getSelectedX();
|
||||
int sely = puzzle.getSelectedY();
|
||||
int xx = x-selx;
|
||||
int yy = y-sely;
|
||||
int sx = selx-puzzle.getWorkerX();
|
||||
int sy = sely-puzzle.getWorkerY();
|
||||
double scaliar = (double)(xx)*(double)(sx)
|
||||
+(double)(yy)*(double)(sy);
|
||||
int len = abs(xx)+abs(yy);
|
||||
if ( scaliar != (double)len )
|
||||
return null;
|
||||
//
|
||||
// Now check that all cell till x,y are free.
|
||||
//
|
||||
int ix = selx;
|
||||
int iy = sely;
|
||||
while ( x != ix || y != iy )
|
||||
{
|
||||
ix+=sx;
|
||||
iy+=sy;
|
||||
if ( !puzzle.isEmpty(puzzle.getSym(ix,iy)) )
|
||||
return null;
|
||||
}
|
||||
//
|
||||
// Looks we could move the box till x,y. Create the steps array
|
||||
// and fill it with push steps.
|
||||
//
|
||||
int steps[] = new int [2*len];
|
||||
int i = 0;
|
||||
ix = selx;
|
||||
iy = sely;
|
||||
while ( x != ix || y != iy )
|
||||
{
|
||||
steps[i++] = ix;
|
||||
steps[i++] = iy;
|
||||
ix+=sx;
|
||||
iy+=sy;
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
}
|
||||
442
app/src/main/java/org/dyndns/vahagn/sokoban/play/PuzzleView.java
Normal file
@@ -0,0 +1,442 @@
|
||||
package org.dyndns.vahagn.sokoban.play;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.view.View;
|
||||
import static java.lang.Math.*;
|
||||
import static org.dyndns.vahagn.sokoban.App.TAG;
|
||||
import org.dyndns.vahagn.sokoban.Puzzle;
|
||||
import static org.dyndns.vahagn.sokoban.Puzzle.*;
|
||||
import org.dyndns.vahagn.sokoban.R;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class PuzzleView extends View
|
||||
{
|
||||
protected Puzzle puzzle;
|
||||
|
||||
private RectF screen = new RectF(0,0,0,0);
|
||||
private RectF viewport = new RectF();
|
||||
private RectF board = new RectF();
|
||||
|
||||
private Matrix col2scr = new Matrix();
|
||||
private Matrix scr2col = new Matrix();
|
||||
private Bitmap[] tile = new Bitmap[Puzzle.getSymCount()];
|
||||
private Point tile_size = new Point(0,0);
|
||||
private int rotated;
|
||||
|
||||
private final Paint mPaint = new Paint();
|
||||
private Paint mSelectPaint = new Paint();
|
||||
private Rect tile_src = new Rect();
|
||||
private RectF tile_dest = new RectF();
|
||||
|
||||
public PuzzleView(Context context)
|
||||
{
|
||||
super(context);
|
||||
setFocusable(true);
|
||||
|
||||
//
|
||||
// Resources r = this.getContext().getResources();
|
||||
|
||||
// TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TileView);
|
||||
//
|
||||
// mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);
|
||||
//
|
||||
// a.recycle();
|
||||
}
|
||||
|
||||
public void setPuzzle( Puzzle p )
|
||||
{
|
||||
puzzle = p;
|
||||
board.set(0,0,puzzle.getColumnCount(),puzzle.getRowCount());
|
||||
viewport.set(board);
|
||||
tile_size.set(0,0);
|
||||
calcTransforms();
|
||||
initTiles();
|
||||
}
|
||||
|
||||
public Puzzle getPuzzle()
|
||||
{
|
||||
return puzzle;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh)
|
||||
{
|
||||
screen.set(0, 0, w, h);
|
||||
tile_size.set(0,0);
|
||||
calcTransforms();
|
||||
initTiles();
|
||||
}
|
||||
|
||||
|
||||
public void scaleViewport( float focusX, float focusY, float scale )
|
||||
{
|
||||
float left = scale*(focusX - screen.left);
|
||||
float right = scale*(screen.right - focusX);
|
||||
float top = scale*(focusY - screen.top);
|
||||
float bottom = scale*(screen.bottom - focusY);
|
||||
viewport.set( focusX-left, focusY-top, focusX+right, focusY+bottom );
|
||||
scr2col.mapRect(viewport);
|
||||
constrainViewport();
|
||||
calcTransforms();
|
||||
ViewCompat.postInvalidateOnAnimation(this);
|
||||
}
|
||||
|
||||
public void scaleViewportDone()
|
||||
{
|
||||
//initTiles();
|
||||
ViewCompat.postInvalidateOnAnimation(this);
|
||||
}
|
||||
|
||||
public void scrollViewport( float distanceX, float distanceY )
|
||||
{
|
||||
viewport.set( screen.left+distanceX,
|
||||
screen.top+distanceY,
|
||||
screen.right+distanceX,
|
||||
screen.bottom+distanceY );
|
||||
scr2col.mapRect(viewport);
|
||||
constrainViewport();
|
||||
calcTransforms();
|
||||
ViewCompat.postInvalidateOnAnimation(this);
|
||||
}
|
||||
|
||||
private void constrainViewport()
|
||||
{
|
||||
if ( !board.contains(viewport) )
|
||||
{
|
||||
if ( board.width() < viewport.width() )
|
||||
{
|
||||
viewport.left = board.left;
|
||||
viewport.right = board.right;
|
||||
}
|
||||
if ( viewport.left < board.left )
|
||||
{
|
||||
viewport.right += board.left - viewport.left;
|
||||
viewport.left = board.left;
|
||||
}
|
||||
if ( board.right < viewport.right )
|
||||
{
|
||||
viewport.left -= viewport.right - board.right;
|
||||
viewport.right = board.right;
|
||||
}
|
||||
|
||||
if ( board.height() < viewport.height() )
|
||||
{
|
||||
viewport.top = board.top;
|
||||
viewport.bottom = board.bottom;
|
||||
}
|
||||
if ( viewport.top < board.top )
|
||||
{
|
||||
viewport.bottom += board.top - viewport.top;
|
||||
viewport.top = board.top;
|
||||
}
|
||||
if ( viewport.bottom > board.bottom )
|
||||
{
|
||||
viewport.top -= viewport.bottom - board.bottom;
|
||||
viewport.bottom = board.bottom;
|
||||
}
|
||||
}
|
||||
if ( viewport.width() < 4 )
|
||||
{
|
||||
float center = (viewport.left + viewport.right)/2;
|
||||
viewport.left = center-2;
|
||||
viewport.right = center+2;
|
||||
}
|
||||
if ( viewport.height() < 4 )
|
||||
{
|
||||
float center = (viewport.top + viewport.bottom)/2;
|
||||
viewport.top = center-2;
|
||||
viewport.bottom = center+2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enlarge viewport to meet aspect ratio and calculate transforms.
|
||||
*/
|
||||
private void calcTransforms()
|
||||
{
|
||||
//
|
||||
// Get the sizes of screen. If it is zero then simplly do nothing.
|
||||
// Otherwise, make sure that the width is bigger the height.
|
||||
//
|
||||
double sw = (double)screen.width();
|
||||
double sh = (double)screen.height();
|
||||
if ( sw <= 0 || sh <= 0 || puzzle == null )
|
||||
return;
|
||||
else if ( sw < sh )
|
||||
{
|
||||
double t = sw;
|
||||
sw = sh;
|
||||
sh = t;
|
||||
rotated = 90;
|
||||
}
|
||||
//
|
||||
// Enlarge the view to have the same aspect ratio as the screen.
|
||||
//
|
||||
double wscale = sw/(double)viewport.width();
|
||||
double hscale = sh/(double)viewport.height();
|
||||
// double scale = floor(min(wscale,hscale));
|
||||
double scale = min(wscale,hscale);
|
||||
float enlarge_vert = (float)(sh / scale - viewport.height())/2;
|
||||
float enlarge_horiz = (float)(sw / scale - viewport.width())/2;
|
||||
viewport.top -= enlarge_vert;
|
||||
viewport.bottom += enlarge_vert;
|
||||
viewport.left -= enlarge_horiz;
|
||||
viewport.right += enlarge_horiz;
|
||||
//
|
||||
// Calculate the tile sizes which is the scale.
|
||||
//
|
||||
tile_size.set((int)scale,(int)scale);
|
||||
//
|
||||
// Set transformations from and to screen.
|
||||
//
|
||||
col2scr.reset();
|
||||
col2scr.postTranslate(-viewport.left, -viewport.top);
|
||||
col2scr.postScale((float)scale,(float)scale);
|
||||
col2scr.postTranslate(screen.left, screen.top);
|
||||
if ( screen.width() < screen.height() )
|
||||
{
|
||||
float around = screen.width()/2;
|
||||
col2scr.postRotate(90, around, around);
|
||||
}
|
||||
col2scr.invert(scr2col);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up tiles and matrices based on puzzle and view sizes.
|
||||
*/
|
||||
private void initTiles()
|
||||
{
|
||||
//
|
||||
// Prevent this from being called if tile_size is not set.
|
||||
//
|
||||
if ( tile_size.x <= 0 && tile_size.y <= 0 || puzzle == null )
|
||||
return;
|
||||
//
|
||||
// Selection rect.
|
||||
//
|
||||
mSelectPaint.setARGB(0xff,0x40,0x40,0xf0);
|
||||
mSelectPaint.setStrokeWidth((float)3.0);
|
||||
//
|
||||
// Create tile bitmaps for given width and height.
|
||||
//
|
||||
tile[FLOOR] = createFloorTile();
|
||||
tile[GOAL] = createGoalTile();
|
||||
tile[WALL] = createWallTile();
|
||||
tile[BOX] = createBoxTile();
|
||||
tile[BOX|SELECTED] = createBoxSelTile();
|
||||
tile[BINGO] = createBingoTile();
|
||||
tile[BINGO|SELECTED] = createBingoSelTile();
|
||||
tile[WORKER] = createWorkerTile();
|
||||
tile[WORKER|SELECTED] = createWorkerSelTile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply screen to board mapping and if the points are in the
|
||||
* column,row range then return it. Otherwise return null.
|
||||
*/
|
||||
public void getTile( float scr_x, float scr_y, Point /*out*/ tile )
|
||||
{
|
||||
float [] scr_p = { scr_x, scr_y };
|
||||
scr2col.mapPoints( scr_p );
|
||||
|
||||
if ( 0 <= scr_p[0] && scr_p[0] < puzzle.getColumnCount()
|
||||
&& 0 <= scr_p[1] && scr_p[1] < puzzle.getRowCount())
|
||||
tile.set((int)scr_p[0], (int)scr_p[1] );
|
||||
else
|
||||
tile.set(-1, -1);
|
||||
}
|
||||
|
||||
private Bitmap createFloorTile()
|
||||
{
|
||||
Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.floor );
|
||||
Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
//canvas.drawARGB(0xff,0x40,0x40,0x40);
|
||||
canvas.drawBitmap(tmp,
|
||||
new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1),
|
||||
new Rect(0,0,tile_size.x, tile_size.y),
|
||||
mPaint);
|
||||
return bitmap;
|
||||
}
|
||||
private Bitmap createGoalTile()
|
||||
{
|
||||
Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.goal );
|
||||
Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
// canvas.drawARGB(0xff,0x40,0x40,0xF0);
|
||||
canvas.drawBitmap(tmp,
|
||||
new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1),
|
||||
new Rect(0,0,tile_size.x, tile_size.y),
|
||||
mPaint);
|
||||
return bitmap;
|
||||
}
|
||||
private Bitmap createWallTile()
|
||||
{
|
||||
Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.wall );
|
||||
Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
//canvas.drawARGB(0xff,0xF0,0x40,0x40);
|
||||
canvas.drawBitmap(tmp,
|
||||
new Rect(1,1,tmp.getWidth()-2,tmp.getHeight()-2),
|
||||
new Rect(0,0,tile_size.x, tile_size.y),
|
||||
mPaint);
|
||||
return bitmap;
|
||||
}
|
||||
private Bitmap createBoxTile()
|
||||
{
|
||||
Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.box );
|
||||
Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
// canvas.drawARGB(0xff,0x40,0xF0,0x40);
|
||||
canvas.drawBitmap(tmp,
|
||||
new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1),
|
||||
new Rect(0,0,tile_size.x, tile_size.y),
|
||||
mPaint);
|
||||
return bitmap;
|
||||
}
|
||||
private Bitmap createBoxSelTile()
|
||||
{
|
||||
Bitmap bitmap = createBoxTile();
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
canvas.drawLine(0,0,tile_size.x-1, 0, mSelectPaint);
|
||||
canvas.drawLine(tile_size.x-1, 0,tile_size.x-1, tile_size.y-1, mSelectPaint);
|
||||
canvas.drawLine(tile_size.x-1, tile_size.y-1, 0, tile_size.y-1, mSelectPaint);
|
||||
canvas.drawLine(0, tile_size.y-1, 0, 0, mSelectPaint);
|
||||
return bitmap;
|
||||
}
|
||||
private Bitmap createBingoTile()
|
||||
{
|
||||
Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.bingo );
|
||||
Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
// canvas.drawARGB(0xff,0x40,0xF0,0xF0);
|
||||
canvas.drawBitmap(tmp,
|
||||
new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1),
|
||||
new Rect(0,0,tile_size.x, tile_size.y),
|
||||
mPaint);
|
||||
return bitmap;
|
||||
}
|
||||
private Bitmap createBingoSelTile()
|
||||
{
|
||||
Bitmap bitmap = createBingoTile();
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
canvas.drawLine(0,0,tile_size.x-1, 0, mSelectPaint);
|
||||
canvas.drawLine(tile_size.x-1, 0,tile_size.x-1, tile_size.y-1, mSelectPaint);
|
||||
canvas.drawLine(tile_size.x-1, tile_size.y-1, 0, tile_size.y-1, mSelectPaint);
|
||||
canvas.drawLine(0, tile_size.y-1, 0, 0, mSelectPaint);
|
||||
return bitmap;
|
||||
}
|
||||
private Bitmap createWorkerTile()
|
||||
{
|
||||
Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.worker );
|
||||
Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
canvas.drawBitmap(tmp,
|
||||
new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1),
|
||||
new Rect(0,0,tile_size.x, tile_size.y),
|
||||
mPaint);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
private Bitmap createWorkerSelTile()
|
||||
{
|
||||
Bitmap tmp = BitmapFactory.decodeResource( getResources(), R.drawable.worker_select );
|
||||
Bitmap bitmap = Bitmap.createBitmap(tile_size.x, tile_size.y, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
canvas.drawBitmap(tmp,
|
||||
new Rect(0,0,tmp.getWidth()-1,tmp.getHeight()-1),
|
||||
new Rect(0,0,tile_size.x, tile_size.y),
|
||||
mPaint);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas)
|
||||
{
|
||||
super.onDraw(canvas);
|
||||
if ( puzzle != null )
|
||||
{
|
||||
//int cols = puzzle.getColumnCount();
|
||||
//int rows = puzzle.getRowCount();
|
||||
// canvas.concat(col2scr);
|
||||
int xbgn = Math.max((int)viewport.left,(int)board.left);
|
||||
int xend = Math.min((int)viewport.right,(int)board.right-1);
|
||||
int ybgn = Math.max((int)viewport.top,(int)board.top);
|
||||
int yend = Math.min((int)viewport.bottom,(int)board.bottom-1);
|
||||
tile_src.set(0,0,tile_size.x,tile_size.y);
|
||||
for (int y = ybgn; y <= yend; ++y)
|
||||
{
|
||||
for (int x = xbgn; x <= xend; ++x)
|
||||
{
|
||||
tile_dest.set(x,y,x+1,y+1);
|
||||
col2scr.mapRect(tile_dest);
|
||||
//tile_hlpr.roundOut(tile_hlpr);
|
||||
//tile_hlpr.right -=1;
|
||||
//tile_hlpr.bottom -=1;
|
||||
int sym = puzzle.getSym(x,y);
|
||||
if ( !Puzzle.hasWorker(sym) )
|
||||
{
|
||||
canvas.drawBitmap(tile[sym],
|
||||
tile_src,tile_dest,
|
||||
mPaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// Draw floor.
|
||||
//
|
||||
canvas.drawBitmap(tile[sym&~(WORKER_MASK|SELECTED)],
|
||||
tile_src,tile_dest,
|
||||
mPaint);
|
||||
//
|
||||
// Draw worker.
|
||||
//
|
||||
canvas.save();
|
||||
Bitmap normal_worker = ((sym&SELECTED)!=0) ?
|
||||
tile[WORKER|SELECTED] : tile[WORKER];
|
||||
int direction = sym & WORKER_MASK;
|
||||
if ( direction == WORKER_NORTH )
|
||||
{
|
||||
canvas.translate(tile_dest.centerX(), tile_dest.centerY());
|
||||
canvas.rotate(180+rotated);
|
||||
canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
|
||||
}
|
||||
else if ( direction == WORKER_SOUTH )
|
||||
{
|
||||
canvas.translate(tile_dest.centerX(), tile_dest.centerY());
|
||||
canvas.rotate(0+rotated);
|
||||
canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
|
||||
}
|
||||
else if ( direction == WORKER_WEST )
|
||||
{
|
||||
canvas.translate(tile_dest.centerX(), tile_dest.centerY());
|
||||
canvas.rotate(90+rotated);
|
||||
canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
|
||||
}
|
||||
else if ( direction == WORKER_EAST )
|
||||
{
|
||||
canvas.translate(tile_dest.centerX(), tile_dest.centerY());
|
||||
canvas.rotate(-90+rotated);
|
||||
canvas.translate(-tile_dest.centerX(), -tile_dest.centerY());
|
||||
}
|
||||
canvas.drawBitmap(normal_worker,
|
||||
tile_src,tile_dest,
|
||||
mPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
app/src/main/res/animator/fragment_slide_left_enter.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
** Copyright 2011, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:interpolator="@android:anim/accelerate_interpolator"
|
||||
android:fromXDelta="100%"
|
||||
android:toXDelta="0%"
|
||||
android:fromYDelta="0"
|
||||
android:toYDelta="0"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
</set>
|
||||
27
app/src/main/res/animator/fragment_slide_left_exit.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
** Copyright 2011, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:interpolator="@android:anim/accelerate_interpolator"
|
||||
android:fromXDelta="0%"
|
||||
android:toXDelta="-100%"
|
||||
android:fromYDelta="0"
|
||||
android:toYDelta="0"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
</set>
|
||||
27
app/src/main/res/animator/fragment_slide_right_enter.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
** Copyright 2011, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:interpolator="@android:anim/accelerate_interpolator"
|
||||
android:fromXDelta="-100%"
|
||||
android:toXDelta="0"
|
||||
android:fromYDelta="0"
|
||||
android:toYDelta="0"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
</set>
|
||||
27
app/src/main/res/animator/fragment_slide_right_exit.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
** Copyright 2011, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:interpolator="@android:anim/accelerate_interpolator"
|
||||
android:fromXDelta="0%"
|
||||
android:toXDelta="100%"
|
||||
android:fromYDelta="0"
|
||||
android:toYDelta="0"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
</set>
|
||||
27
app/src/main/res/animator/puzzle_action_bar_enter.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
** Copyright 2011, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:interpolator="@android:anim/accelerate_interpolator"
|
||||
android:fromXDelta="0"
|
||||
android:toXDelta="0"
|
||||
android:fromYDelta="-100%"
|
||||
android:toYDelta="0"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
</set>
|
||||
27
app/src/main/res/animator/puzzle_action_bar_exit.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
** Copyright 2011, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:interpolator="@android:anim/accelerate_interpolator"
|
||||
android:fromXDelta="0"
|
||||
android:toXDelta="0"
|
||||
android:fromYDelta="0%"
|
||||
android:toYDelta="-100%"
|
||||
android:duration="@android:integer/config_mediumAnimTime" />
|
||||
</set>
|
||||
BIN
app/src/main/res/drawable-hdpi/icon.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
app/src/main/res/drawable-hdpi/icon_debug.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
app/src/main/res/drawable-hdpi/next.png
Normal file
|
After Width: | Height: | Size: 787 B |
BIN
app/src/main/res/drawable-hdpi/prev.png
Normal file
|
After Width: | Height: | Size: 942 B |
BIN
app/src/main/res/drawable-hdpi/reset.png
Normal file
|
After Width: | Height: | Size: 982 B |
BIN
app/src/main/res/drawable-hdpi/undo.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
app/src/main/res/drawable-ldpi/icon.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/main/res/drawable-ldpi/icon_debug.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/drawable-ldpi/next.png
Normal file
|
After Width: | Height: | Size: 397 B |
BIN
app/src/main/res/drawable-ldpi/prev.png
Normal file
|
After Width: | Height: | Size: 710 B |
BIN
app/src/main/res/drawable-ldpi/reset.png
Normal file
|
After Width: | Height: | Size: 558 B |
BIN
app/src/main/res/drawable-ldpi/undo.png
Normal file
|
After Width: | Height: | Size: 520 B |
BIN
app/src/main/res/drawable-mdpi/icon.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
app/src/main/res/drawable-mdpi/icon_debug.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
app/src/main/res/drawable-mdpi/next.png
Normal file
|
After Width: | Height: | Size: 496 B |
BIN
app/src/main/res/drawable-mdpi/prev.png
Normal file
|
After Width: | Height: | Size: 692 B |
BIN
app/src/main/res/drawable-mdpi/reset.png
Normal file
|
After Width: | Height: | Size: 698 B |
BIN
app/src/main/res/drawable-mdpi/undo.png
Normal file
|
After Width: | Height: | Size: 651 B |
BIN
app/src/main/res/drawable-xhdpi/icon.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
app/src/main/res/drawable-xhdpi/icon_debug.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
app/src/main/res/drawable-xhdpi/next.png
Normal file
|
After Width: | Height: | Size: 802 B |
BIN
app/src/main/res/drawable-xhdpi/prev.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
app/src/main/res/drawable-xhdpi/reset.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/drawable-xhdpi/undo.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/drawable/bingo.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
app/src/main/res/drawable/box.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
app/src/main/res/drawable/floor.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
app/src/main/res/drawable/goal.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
app/src/main/res/drawable/lock.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
app/src/main/res/drawable/splash.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
app/src/main/res/drawable/unlock.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
app/src/main/res/drawable/wall.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
app/src/main/res/drawable/worker.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
app/src/main/res/drawable/worker_select.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
8
app/src/main/res/layout/main_activity.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/fragment_container"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
</LinearLayout>
|
||||
|
||||
61
app/src/main/res/layout/main_fragment.xml
Normal file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:background="@drawable/splash"
|
||||
android:gravity="center_vertical">
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="top"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center" />
|
||||
<Button
|
||||
android:id="@+id/btn_start"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/main_menu_btn_vmargin"
|
||||
android:layout_marginBottom="@dimen/main_menu_btn_vmargin"
|
||||
android:layout_marginLeft="@dimen/main_menu_btn_hmargin"
|
||||
android:layout_marginRight="@dimen/main_menu_btn_hmargin"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="0"
|
||||
android:padding="@dimen/main_menu_btn_padding"
|
||||
android:background="@color/main_menu_btn_bg"
|
||||
android:textColor="@color/main_menu_btn_text"
|
||||
android:textSize="@dimen/main_menu_text_size"
|
||||
android:text="@string/btn_start_continue"
|
||||
android:onClick="onStartCurrentPuzzle" />
|
||||
<Button
|
||||
android:id="@+id/btn_puzzles"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/main_menu_btn_vmargin"
|
||||
android:layout_marginBottom="@dimen/main_menu_btn_vmargin"
|
||||
android:layout_marginLeft="@dimen/main_menu_btn_hmargin"
|
||||
android:layout_marginRight="@dimen/main_menu_btn_hmargin"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="0"
|
||||
android:padding="@dimen/main_menu_btn_padding"
|
||||
android:background="@color/main_menu_btn_bg"
|
||||
android:textColor="@color/main_menu_btn_text"
|
||||
android:textSize="@dimen/main_menu_text_size"
|
||||
android:text="@string/btn_puzzles"
|
||||
android:onClick="onPuzzleListShow" />
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center" />
|
||||
<TextView
|
||||
android:id="@+id/footer_txt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/git_version"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_weight="0"
|
||||
android:gravity="center" />
|
||||
</LinearLayout>
|
||||
|
||||
17
app/src/main/res/layout/puzzle_list_fragment.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
<GridView
|
||||
android:id="@+id/puzzle_grid"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:horizontalSpacing="2mm"
|
||||
android:verticalSpacing="2mm"
|
||||
android:columnWidth="10mm"
|
||||
android:numColumns="auto_fit"
|
||||
android:stretchMode="columnWidth"
|
||||
android:gravity="center" />
|
||||
</LinearLayout>
|
||||
|
||||
71
app/src/main/res/layout/puzzle_view_title.xml
Normal file
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/puzzle_view_title_layout"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_horizontal"
|
||||
android:background="#60404040"
|
||||
android:layout_height="48dp"
|
||||
android:layout_width="match_parent">
|
||||
<!--ImageButton
|
||||
android:src="@drawable/prev"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="0"
|
||||
android:layout_margin="3dp"
|
||||
android:background="#00000000"
|
||||
android:onClick="onPrev"
|
||||
android:id="@+id/prev_btn"/-->
|
||||
<TextView
|
||||
android:text="Level"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="0"
|
||||
android:textSize="40dp"
|
||||
android:paddingLeft="5dp"
|
||||
android:textScaleX="1"
|
||||
android:textColor="#FFE680"
|
||||
android:textStyle="bold"
|
||||
android:typeface="normal"
|
||||
android:gravity="center_vertical"
|
||||
android:id="@+id/title_text"/>
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
<ImageButton
|
||||
android:src="@drawable/undo"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="0"
|
||||
android:layout_margin="3dp"
|
||||
android:background="#00000000"
|
||||
android:onClick="onUndo"
|
||||
android:id="@+id/undo_btn"/>
|
||||
<ImageButton
|
||||
android:src="@drawable/reset"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="0"
|
||||
android:layout_margin="3dp"
|
||||
android:background="#00000000"
|
||||
android:onClick="onReset"
|
||||
android:id="@+id/reset_btn"/>
|
||||
<!--ImageButton
|
||||
android:src="@drawable/next"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="0"
|
||||
android:layout_margin="3dp"
|
||||
android:background="#00000000"
|
||||
android:onClick="onNext"
|
||||
android:id="@+id/next_btn"/-->
|
||||
</LinearLayout>
|
||||
|
||||
BIN
app/src/main/res/raw/puzzles.bin
Normal file
23
app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
**
|
||||
** Copyright 2008, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
<resources>
|
||||
<color name="main_menu_btn_bg">#80404040</color>
|
||||
<color name="main_menu_btn_text">#C0C0C0C0</color>
|
||||
</resources>
|
||||
26
app/src/main/res/values/dimens.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
**
|
||||
** Copyright 2008, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<dimen name="main_menu_text_size">18pt</dimen>
|
||||
<dimen name="main_menu_btn_hmargin">10pt</dimen>
|
||||
<dimen name="main_menu_btn_vmargin">2pt</dimen>
|
||||
<dimen name="main_menu_btn_padding">10pt</dimen>
|
||||
</resources>
|
||||
12
app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Free Sokoban</string>
|
||||
<string name="app_name_debug">Debug Sokoban</string>
|
||||
<string name="menu">Menu</string>
|
||||
<string name="play_activity">play_activity</string>
|
||||
<string name="btn_start_begin">Begin</string>
|
||||
<string name="btn_start_continue">Continue</string>
|
||||
<string name="btn_puzzles">Puzzles</string>
|
||||
<string name="version_tag">Version:</string>
|
||||
<string name="copyright">(c) 2013 Vahagn Khachatryan</string>
|
||||
</resources>
|
||||
35
app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!--style name="WindowTitle">
|
||||
<item name="android:singleLine">true</item-->
|
||||
<!--item name="android:textAppearance">@style/TextAppearance.WindowTitle</item-->
|
||||
<!-- item name="android:shadowColor">#BB000000</item>
|
||||
<item name="android:shadowRadius">2.75</item>
|
||||
</style-->
|
||||
<!-- Base application theme is the default theme. -->
|
||||
<!--style name="Theme.puzzle_view_title" parent="android:Theme"-->
|
||||
<!--style name="puzzle_view_title_layout">
|
||||
<item name="android:windowTitleStyle">@style/title_bar</item>
|
||||
<item name="android:windowTitleSize">10mm</item-->
|
||||
<!--item name="android:windowTitleBackgroundStyle">@style/title_background</item-->
|
||||
<!--/style-->
|
||||
<!--style name="title_bar">
|
||||
<item name="android:singleLine">false</item>
|
||||
<item name="android:textAppearance">@android:style/TextAppearance.WindowTitle</item>
|
||||
<item name="android:shadowColor">#BB0000FF</item>
|
||||
<item name="android:shadowRadius">2.75</item>
|
||||
</style-->
|
||||
<!--style name="title_background">
|
||||
<item name="android:background">@drawable/background_square</item>
|
||||
</style-->
|
||||
|
||||
<!--style name="PlainText">
|
||||
<item name="android:textAppearance">@style/TextAppearance.Theme.PlainText</item>
|
||||
</style-->
|
||||
|
||||
<!--style name="ImageView240dpi">
|
||||
<item name="android:src">@drawable/stylogo240dpi</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
</style-->
|
||||
</resources>
|
||||