import java.lang.*;
import java.awt.*;
import java.applet.*;

public class Sokoban extends Applet {

	String levels[] = {
	
		"M^^^^#####" +
		"M^^^^#   #" +
		"M^^^^#$  #" +
		"M^^###  $##" +
		"M^^#  $ $ #" +
		"M### # ## #^^^######" +
		"M#   # ## #####  ..#" +
		"M# $  $          ..#" +
		"M##### ### #@##  ..#" +
		"M^^^^#     #########" +
		"M^^^^#######",
		
		"M############" +
		"M#..  #     ###" +
		"M#..  # $  $  #" +
		"M#..  #$####  #" +
		"M#..    @ ##  #" +
		"M#..  # #  $ ##" +
		"M###### ##$ $ #" +
		"M^^# $  $ $ $ #" +
		"M^^#    #     #" +
		"M^^############",
		
		"M^^^^^^^^########" +
		"M^^^^^^^^#     @#" +
		"M^^^^^^^^# $#$ ##" +
		"M^^^^^^^^# $  $#" +
		"M^^^^^^^^##$ $ #" +
		"M######### $ # ###" +
		"M#....  ## $  $  #" +
		"M##...    $  $   #" +
		"M#....  ##########" +
		"M########M",
		
		"M^^^^^^^^^^^########" +
		"M^^^^^^^^^^^#  ....#" +
		"M############  ....#" +
		"M#    #  $ $   ....#" +
		"M# $$$#$  $ #  ....#" +
		"M#  $     $ #  ....#" +
		"M# $$ #$ $ $########" +
		"M#  $ #     #" +
		"M## #########" +
		"M#    #    ##" +
		"M#     $   ##" +
		"M#  $$#$$  @#" +
		"M#    #    ##" +
		"M###########",
		
		"M^^^^^^^^#####" +
		"M^^^^^^^^#   #####" +
		"M^^^^^^^^# #$##  #" +
		"M^^^^^^^^#     $ #" +
		"M######### ###   #" +
		"M#....  ## $  $###" +
		"M#....    $ $$ ##" +
		"M#....  ##$  $ @#" +
		"M#########  $  ##" +
		"M^^^^^^^^# $ $  #" +
		"M^^^^^^^^### ## #" +
		"M^^^^^^^^^^#    #" +
		"M^^^^^^^^^^######",
		
		"M######^^###" +
		"M#..  #^##@##" +
		"M#..  ###   #" +
		"M#..     $$ #" +
		"M#..  # # $ #" +
		"M#..### # $ #" +
		"M#### $ #$  #" +
		"M^^^#  $# $ #" +
		"M^^^# $  $  #" +
		"M^^^#  ##   #" +
		"M^^^#########",
		
		"M^^^^^^^#####" +
		"M^#######   ##" +
		"M## # @## $$ #" +
		"M#    $      #" +
		"M#  $  ###   #" +
		"M### #####$###" +
		"M# $  ### ..#" +
		"M# $ $ $ ...#" +
		"M#    ###...#" +
		"M# $$ #^#...#" +
		"M#  ###^#####" +
		"M####",
		
		"M^^^^^^^^^^#######" +
		"M^^^^^^^^^^#  ...#" +
		"M^^^^^^#####  ...#" +
		"M^^^^^^#      . .#" +
		"M^^^^^^#  ##  ...#" +
		"M^^^^^^## ##  ...#" +
		"M^^^^^### ########" +
		"M^^^^^# $$$ ##" +
		"M^#####  $ $ #####" +
		"M##   #$ $   #   #" +
		"M#@ $  $    $  $ #" +
		"M###### $$ $ #####" +
		"M^^^^^#      #" +
		"M^^^^^########",
		
		"M^###^^#############" +
		"M##@####       #   #" +
		"M# $$   $$  $ $ ...#" +
		"M#  $$$#    $  #...#" +
		"M# $   # $$ $$ #...#" +
		"M###   #  $    #...#" +
		"M#     # $ $ $ #...#" +
		"M#    ###### ###...#" +
		"M## #  #  $ $  #...#" +
		"M#  ## # $$ $ $##..#" +
		"M# ..# #  $      #.#" +
		"M# ..# # $$$ $$$ #.#" +
		"M##### #       # #.#" +
		"M^^^^# ######### #.#" +
		"M^^^^#           #.#" +
		"M^^^^###############",
		
		"M^^^^^^^^^^####" +
		"M^^^^^####^#  #" +
		"M^^^### @###$ #" +
		"M^^##      $  #" +
		"M^##  $ $$## ##" +
		"M^#  #$##     #" +
		"M^# # $ $$ # ###" +
		"M^#   $ #  # $ #####" +
		"M####    #  $$ #   #" +
		"M#### ## $         #" +
		"M#.    ###  ########" +
		"M#.. ..#^####" +
		"M#...#.#" +
		"M#.....#" +
		"M#######",
		
		"M^^####" +
		"M^^#  ###########" +
		"M^^#    $   $ $ #" +
		"M^^# $# $ #  $  #" +
		"M^^#  $ $  #    #" +
		"M### $# #  #### #" +
		"M#@#$ $ $  ##   #" +
		"M#    $ #$#   # #" +
		"M#   $    $ $ $ #" +
		"M^####  #########" +
		"M^^#      #" +
		"M^^#      #" +
		"M^^#......#" +
		"M^^#......#" +
		"M^^#......#" +
		"M^^########",

		"M################" +
		"M#              #" +
		"M# # ######     #" +
		"M# #  $ $ $ $#  #" +
		"M# #   $@$   ## ##" +
		"M# #  $ $ $###...#" +
		"M# #   $ $  ##...#" +
		"M# ###$$$ $ ##...#" +
		"M#     # ## ##...#" +
		"M#####   ## ##...#" +
		"M^^^^#####     ###" +
		"M^^^^^^^^#     #" +
		"M^^^^^^^^#######",

		"M^^^#########" +
		"M^^##   ##  #####" +
		"M###     #  #    ###" +
		"M#  $ #$ #  #  ... #" +
		"M# # $#@$## # #.#. #" +
		"M#  # #$  #    . . #" +
		"M# $    $ # # #.#. #" +
		"M#   ##  ##$ $ . . #" +
		"M# $ #   #  #$#.#. #" +
		"M## $  $   $  $... #" +
		"M^#$ ######    ##  #" +
		"M^#  #^^^^##########" +
		"M^####",

		"M^^^^^^^#######" +
		"M^#######     #" +
		"M^#     # $@$ #" +
		"M^#$$ #   #########" +
		"M^# ###......##   #" +
		"M^#   $......## # #" +
		"M^# ###......     #" +
		"M##   #### ### #$##" +
		"M#  #$   #  $  # #" +
		"M#  $ $$$  # $## #" +
		"M#   $ $ ###$$ # #" +
		"M#####     $   # #" +
		"M^^^^### ###   # #" +
		"M^^^^^^#     #   #" +
		"M^^^^^^########  #" +
		"M^^^^^^^^^^^^^####",

		"M^^^^#######" +
		"M^^^#   #  #" +
		"M^^^#  $   #" +
		"M^### #$   ####" +
		"M^#  $  ##$   #" +
		"M^#  # @ $ # $#" +
		"M^#  #      $ ####" +
		"M^## ####$##     #" +
		"M^# $#.....# #   #" +
		"M^#  $..**. $# ###" +
		"M##  #.....#   #" +
		"M#   ### #######" +
		"M# $$  #  #" +
		"M#  #     #" +
		"M######   #" +
		"M^^^^^#####",

		"M#####" +
		"M#   ##" +
		"M#    #^^####" +
		"M# $  ####  #" +
		"M#  $$ $   $#" +
		"M###@ #$    ##" +
		"M^#  ##  $ $ ##" +
		"M^# $  ## ## .#" +
		"M^#  #$##$  #.#" +
		"M^###   $..##.#" +
		"M^^#    #.*...#" +
		"M^^# $$ #.....#" +
		"M^^#  #########" +
		"M^^#  #" +
		"M^^####",

		"M^^^##########" +
		"M^^^#..  #   #" +
		"M^^^#..      #" +
		"M^^^#..  #  ####" +
		"M^^#######  #  ##" +
		"M^^#            #" +
		"M^^#  #  ##  #  #" +
		"M#### ##  #### ##" +
		"M#  $  ##### #  #" +
		"M# # $  $  # $  #" +
		"M# @$  $   #   ##" +
		"M#### ## #######" +
		"M^^^#    #" +
		"M^^^######",

		"M^^^^^###########" +
		"M^^^^^#  .  #   #" +
		"M^^^^^# #.    @ #" +
		"M^##### ##..# ####" +
		"M##  # ..###     ###" +
		"M# $ #...   $ #  $ #" +
		"M#    .. ##  ## ## #" +
		"M####$##$# $ #   # #" +
		"M^^## #    #$ $$ # #" +
		"M^^#  $ # #  # $## #" +
		"M^^#               #" +
		"M^^#  ###########  #" +
		"M^^####^^^^^^^^^####",

		"M^^######" +
		"M^^#   @####" +
		"M##### $   #" +
		"M#   ##    ####" +
		"M# $ #  ##    #" +
		"M# $ #  ##### #" +
		"M## $  $    # #" +
		"M## $ $ ### # #" +
		"M## #  $  # # #" +
		"M## # #$#   # #" +
		"M## ###   # # ######" +
		"M#  $  #### # #....#" +
		"M#    $    $   ..#.#" +
		"M####$  $# $   ....#" +
		"M#       #  ## ....#" +
		"M###################",

		"M^^^^##########" +
		"M#####        ####" +
		"M#     #   $  #@ #" +
		"M# #######$####  ###" +
		"M# #    ## #  #$ ..#" +
		"M# # $     #  #  #.#" +
		"M# # $  #     #$ ..#" +
		"M# #  ### ##     #.#" +
		"M# ###  #  #  #$ ..#" +
		"M# #    #  ####  #.#" +
		"M# #$   $  $  #$ ..#" +
		"M#    $ # $ $ #  #.#" +
		"M#### $###    #$ ..#" +
		"M^^^#    $$ ###....#" +
		"M^^^#      ##^######" +
		"M^^^########"
	
	};
	
	final static char wall = '#';
	final static char floor = ' ';
	final static char me = '@';
	final static char megoal = '&';
	final static char occupied = '*';
	final static char dollar = '$';
	final static char cr = 'M';
	final static char blank = '^';
	final static char goal = '.';
	Image tiles[] = new Image[128];
	
	AudioClip buzz, wow;
	
	char[] level;
	int currlevel, w, h, push, move;
	int lastcount, pos1, pos2, pos3;
	Rectangle lastrect;
	boolean uc;
	
	char savelevel[];
	int savecurrlevel, savew, saveh, savepush, savemove;
	boolean gamesaved = false;
	
	Font font = new Font("Helvetica", Font.PLAIN, 12);
	Font fontb = new Font("Helvetica", Font.BOLD, 12);
	
	public void init() {
		buzz = getAudioClip(getDocumentBase(), "buzz.au");
		wow = getAudioClip(getDocumentBase(), "wow.au");
		MediaTracker tracker = new MediaTracker(this);
		Image j = getImage(getDocumentBase(), "Sokoban.gif");
		tracker.addImage(j,0);
		try { tracker.waitForAll(); } catch (InterruptedException e) {}
		String tile = "# @$.&*";
		for (int i = 0; i < tile.length(); i++) {
			tiles[(int) tile.charAt(i)] = createImage(16, 16);
			Graphics g = tiles[(int) tile.charAt(i)].getGraphics();
			g.drawImage(j, -i*16, 0, this);
		}
		j.flush();
		newLevel(0);
		requestFocus();
	}
	
	public void start() {}
	
	public void stop() {}
	
	public void destroy() {}
		
	public void paint(Graphics g) {
		update(g);
	}
	
	public synchronized void update(Graphics g) {
		Dimension d = size();
		if (d.width * d.height == 0) return; // supposedly this can happen!
		Rectangle r = g.getClipRect();

		if (r.x < 72) { // only do this if necessary!
			g.setColor(Color.lightGray);
			g.fillRect(0, 0, d.width, d.height);
			g.setFont(fontb);
			g.setColor(Color.blue);
			g.drawString("Sokoban",0,16);
			g.setFont(font);
			g.setColor(Color.black);
			String help[] = { "h=Left", "j=Down", "k=Up", "l=Right", " (or Arrows)",
				"H,J,K,L=", " FastMove", "u=Undo", "A=Restart", "S=Save", "R=Restore",
				"+=UpLevel", "-=DownLevel" };
			for (int i = 0; i < help.length; i++)
				g.drawString(help[i], 0, 80 + 16 * i);
			g.setFont(fontb);
			g.drawString("Level:", 0, 32);
			g.drawString("Move:", 0, 48);
			g.drawString("Push:", 0, 64);
			drawStatus(g);
		}

		int y = -16 + h, x = -16 + w;
		for (int i = 0; i < level.length; i++)
			if (level[i] == cr) {
				x = -16 + w; y += 16;
			} else {
				x += 16;
				if (level[i] == blank) continue;
				if (r.inside(x,y)) // only draw the images necessary for move!
					g.drawImage(tiles[(int) level[i]], x, y, this);
			}
	}
	
	public void drawStatus(Graphics g) {
		g.setColor(Color.lightGray);
		g.fillRect(40, 16, 32, 48);
		g.setColor(Color.black);
		g.setFont(font);
		g.drawString("" + (currlevel + 1), 40, 32);
		g.drawString("" + move, 40, 48);
		g.drawString("" + push, 40, 64);
	}
	
	public void drawMove() {
		Graphics g = getGraphics();
		drawStatus(g); // order is important, since update munges the clipRect
		repaint(lastrect.x, lastrect.y, lastrect.width, lastrect.height);
	}
		
	public boolean keyDown(Event e, int key) {
		uc = false;
		switch (e.key) {
			case 'H': uc = true;
			case 'h': case Event.LEFT: movearound(-1, 0); break;
			case 'L': uc = true;
			case 'l': case Event.RIGHT: movearound(1, 0); break;
			case 'K': uc = true;
			case 'k': case Event.UP: movearound(0, -1); break;
			case 'J': uc = true;
			case 'j': case Event.DOWN: movearound(0, 1); break;
			case '+':
			case '-': currlevel += e.key == '+' ? 1 : -1;
						if (currlevel < 0) currlevel = 0;
						else if (currlevel == levels.length) currlevel = levels.length - 1;
			case 'A': newLevel(currlevel); repaint(); break;
			case 'u': undomove(); break;
			case 'S': saveGame(); break;
			case 'R': if (gamesaved) restoreGame(); break;
		}
		return true;
	}
	
	public void newLevel(int l) {
		currlevel = l; push = 0; move = 0;
		w = 0; h = 0; level = levels[currlevel].toCharArray();
		lastcount = 0;
		int W = 0;
		for (int i = 0; i < level.length; i++)
			if (level[i] == cr)
				{ if (W > w) w = W; W = 0; h++; }
			else W++;
		Dimension d = size();
		w = 72 + (d.width - 72 - 16 * w) / 2; h = (d.height - 16 * h) / 2;
	}
	
	public void restoreGame() {
		currlevel = savecurrlevel;
		w = savew; h = saveh; push = savepush; move = savemove;
		level = savelevel; gamesaved = false;
		repaint();
	}
	
	public void saveGame() {
		savecurrlevel = currlevel;
		savew = w; saveh = h; savepush = push; savemove = move;
		savelevel = new char[level.length];
		System.arraycopy(level ,0, savelevel, 0, level.length);
		gamesaved = true;
	}
	
	public int	moveone(int pos, int x, int y, int dx, int dy) {
		int i;
		if (dx != 0) return pos + dx;
		else if (dy == -1) for (i = pos - x - 2; level[i] != cr; i--);
		else for (i = pos + 1; level[i] != cr; i++);
		return i + x + 1;
	}
	
	public void movearound(int dx, int dy) {
		do {
			int x = 0, y = -1, savepos1 = pos1, savepos2 = pos2, savepos3 = pos3;
			for (pos1 = 0; pos1 < level.length; pos1++)
				if (level[pos1] == cr) { x = 0; y++; }
				else if ((level[pos1] != me) && (level[pos1] != megoal)) x++; else break;
			pos2 = moveone(pos1, x, y, dx, dy);
			int count = 0;
			if (level[pos2] == floor || level[pos2] == goal) count = 1;
			else {
				if (uc) { lastcount = 1; pos1 = savepos1; pos2 = savepos2; break; }
				if (level[pos2] == dollar || level[pos2] == occupied) {
					pos3 = moveone(pos2, x, y, dx, dy);
					if (level[pos3] == floor || level[pos3] == goal) count = 2;
				}
			}
			if (count > 0) {
				level[pos1] = level[pos1] == me ? floor : goal;
				level[pos2] = level[pos2] == floor || level[pos2] == dollar ? me : megoal;
				move++;
				if (count > 1) {
					level[pos3] = level[pos3] == floor ? dollar : occupied;
					push++;
				}
				lastcount = count;
				int xo = x + dx * count, yo = y + dy * count;
				lastrect = new Rectangle(w + Math.min(xo, x) * 16, h + Math.min(yo, y) * 16,
					(Math.abs(xo - x) + 1) * 16, (Math.abs(yo - y) + 1) * 16);
				drawMove();
				boolean b = true;
				for (int i = 0; i < level.length; i++) if (level[i] == dollar) b = false;
				if (b) {
					wow.play();
					try { Thread.sleep(2000); } catch (InterruptedException e) {};
					newLevel(currlevel + 1);
					repaint();
				}
			} else {
				pos1 = savepos1; pos2 = savepos2; pos3 = savepos3;
				buzz.play();
			}
		} while (uc);
	}
	
	public void undomove() {
		if (lastcount > 0) {
			level[pos1] = level[pos1] == floor ? me : megoal;
			move--;
			if (lastcount > 1) {
				level[pos2] = level[pos2] == me ? dollar : occupied;
				level[pos3] = level[pos3] == dollar ? floor : goal;
				push--;
			} else level[pos2] = level[pos2] == me ? floor : goal;
			lastcount = 0;
			drawMove();
		}
	}
			
	public String getAppletInfo() {
		return "Sokoban 1.0, Written by Yossie Silverman.";
	}
}
