Files
android_app/src/org/dyndns/vahagn/sokoban/Puzzle.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;
}
}