405 lines
7.9 KiB
Java
405 lines
7.9 KiB
Java
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;
|
|
}
|
|
}
|