diff --git a/projects/chuchu/bin/runjava b/projects/chuchu/bin/runjava
new file mode 100755
index 00000000..29176914
--- /dev/null
+++ b/projects/chuchu/bin/runjava
@@ -0,0 +1,103 @@
+#!/usr/bin/perl -w
+
+use Getopt::Std;
+
+my $usage = "Usage: $0 [-p pid_file] [-r server_root] args\n";
+
+# locations of stuff
+chomp($location = `dirname $0`);
+
+# get the server root by popping the /bin off of our parent directory
+@parts = split(/\//, $location);
+pop(@parts);
+my $root = join("/", @parts);
+
+my $jhome = $ENV{"JAVA_HOME"};
+
+# make sure JAVA_HOME is set
+if (!defined $jhome) {
+ warn "$0: Error: No JAVA_HOME specified!\n";
+ warn "\n";
+ warn "You must set your JAVA_HOME environment variable to the\n";
+ warn "the absolute path of your JDK installation. For example:\n";
+ warn "\n";
+ warn " % JAVA_HOME=/usr/local/jdk1.2\n";
+ warn " % export JAVA_HOME\n";
+ die "\n";
+}
+
+# make sure it's set to a directory
+if (! -d $jhome) {
+ die "$0: Can't find a java interpreter in '$jhome'.\n";
+}
+
+my $java = "$jhome/bin/java";
+my $jlib = "$jhome/lib/classes.zip";
+
+# determine our machine architecture
+my $ostype = `uname -s`;
+my $machtype = `uname -m`;
+chomp($ostype);
+chomp($machtype);
+my $arch = "$machtype-$ostype";
+
+# add our native libraries to the runtime library path
+my $libs = "$root/lib/$arch";
+my $libpath = $ENV{"LD_LIBRARY_PATH"};
+
+if (defined $libpath) {
+ $ENV{"LD_LIBRARY_PATH"} = "$libs:$libpath";
+} else {
+ $ENV{"LD_LIBRARY_PATH"} = $libs;
+}
+
+# put everything in our class path
+my $classpath = "-classpath $jlib:$root/dist/classes";
+
+# any zip or jar files in our lib/ directory get added to the class path
+if (opendir(DIR, "$root/lib")) {
+ foreach $lib (grep { /.(zip|jar)/ && -f "$root/lib/$_" } readdir(DIR)) {
+ $classpath .= ":$root/lib/$lib";
+ }
+ closedir DIR;
+}
+
+# specify our server root (this is for server code)
+my $serverroot = "-Dserver.root=$root";
+
+# specify our base directory (this is for client code)
+my $basedir = "-Dbase.dir=$root/src/java";
+
+my $pid_file = undef;
+my $i = 0;
+
+# strip out the -p and -r args (we'd use getopt() but the damned thing
+# provides no way of escaping arguments so that we can pass args to
+# runjava that get passed down to the JVM)
+for ($i = 0; $i < @ARGV; $i++) {
+ my $arg = $ARGV[$i];
+
+ # stop when we see -- (and strip it out because Java don't dig --)
+ if ($arg eq "--") {
+ splice(@ARGV, $i, 1);
+ last;
+ }
+
+ if ($arg eq "-p") {
+ $pid_file = $ARGV[$i+1];
+ splice(@ARGV, $i, 2);
+ $i -= 1; # decrement i so that things stay in sync
+
+ } elsif ($arg eq "-r") {
+ $serverroot = "-Dserver.root=" . $ARGV[$i+1];
+ splice(@ARGV, $i, 2);
+ $i -= 1; # decrement i so that things stay in sync
+ }
+}
+
+# log the pid file if requested to do so
+print `echo $$ > $pid_file` if (defined $pid_file);
+
+my $cmd = "$java -mx256M $classpath $serverroot $basedir " . join(" ", @ARGV);
+# print "$cmd\n";
+exec($cmd);
diff --git a/projects/chuchu/build.xml b/projects/chuchu/build.xml
new file mode 100644
index 00000000..15130563
--- /dev/null
+++ b/projects/chuchu/build.xml
@@ -0,0 +1,54 @@
+
+Piece object instances.
+ */
+public class Board
+{
+ /** The width of a standard Chu Chu board in squares. */
+ public static final int WIDTH = 12;
+
+ /** The height of a standard Chu Chu board in squares. */
+ public static final int HEIGHT = 9;
+
+ /** The total number of squares on the board. */
+ public static final int SQUARES = WIDTH * HEIGHT;
+
+ /**
+ * Constructs a blank board ready to be manipulated or configured from
+ * a file.
+ */
+ public Board (boolean debug)
+ {
+ _debug = debug;
+ }
+
+ /**
+ * Returns true if the specified board position is occupied by a
+ * feature (hole or rocket).
+ */
+ public boolean isOccupied (int index)
+ {
+ return (_board[index] & FEATURE_MASK) != 0;
+ }
+
+ /**
+ * Sets the board in motion with the specified set of arrow tiles. The
+ * board will continue executing until one of the following conditions
+ * occurs:
+ *
+ *
SQUARES long with a
+ * Piece direction constant at every coordinate where an
+ * arrow exists facing in the specified direction. The array should be
+ * set to -1 where there are no arrows. This array will be modified in
+ * the course of executing the board.
+ * @param maxiters the maximum number of iterations that the game is
+ * allowed to execute before aborting.
+ *
+ * @return >0 (number of iterations needed for solution) if the game
+ * terminated with all mice successfully reaching rockets, -1 if some
+ * failure condition occurred.
+ *
+ * @exception MaximumIterationsExceeded thrown if the game does not
+ * terminate within the maximum number of iterations.
+ */
+ public int execute (int[] arrows, int maxiters)
+ throws MaximumIterationsExceeded
+ {
+ // clear out our position array
+ Arrays.fill(_aboard, 0);
+
+ // duplicate our pieces and make a note of their starting positions
+ for (int i = 0; i < _cats.length; i++) {
+ Piece piece = _cats[i];
+ _aboard[piece.getIndex()] = CAT_ID;
+ _acats[i].init(piece);
+ }
+ for (int i = 0; i < _mice.length; i++) {
+ Piece piece = (Piece)_mice[i].clone();
+ _aboard[piece.getIndex()] = MOUSE_ID;
+ _amice[i].init(piece);
+ }
+
+ // eliminate frozen pieces
+
+ // adjust the positions of all of the pieces as if they just
+ // landed on their respective squares
+ for (int i = 0; i < _acats.length; i++) {
+ _acats[i].orientation = adjustOrientation(_acats[i], arrows, true);
+ }
+ for (int i = 0; i < _amice.length; i++) {
+ _amice[i].orientation = adjustOrientation(_amice[i], arrows, false);
+ }
+
+ // start up the cellular automaton and see what happens
+ for (int iter = 0; iter < maxiters; iter++) {
+ // first move the cats
+ for (int c = 0; c < _acats.length; c++) {
+ Piece cat = _acats[c];
+ // System.out.println("Examining cat: " + cat);
+
+ // skip inactive cats
+ if (!cat.active) {
+ continue;
+ }
+
+ // update the cat's offset
+ cat.offset = (cat.offset + 2) % 3;
+
+ // if the cat was in the limbo state, he remains in the
+ // same square, otherwise he moves in the direction of his
+ // orientation
+ if (cat.offset == 1) {
+ continue;
+ }
+
+ // clear out our previous board position
+ _aboard[cat.getIndex()] -= CAT_ID;
+
+ // move to our new position
+ cat.x += X_ADJUSTS[cat.orientation];
+ cat.y += Y_ADJUSTS[cat.orientation];
+
+ // check to see if we just moved into a hole or a rocket
+ int cidx = cat.getIndex();
+ if ((_board[cidx] & HOLE_MASK) != 0) {
+ cat.active = false;
+
+ } else if ((_board[cidx] & ROCKET_MASK) != 0) {
+ // too bad, so sad
+ return -1;
+
+ } else {
+ // we need to make sure we're not swapping spots with
+ // any mice because a precise orientation of a cat and
+ // mouse face to face will result in their swapping
+ // positions which is illegal
+ for (int m = 0; m < _amice.length; m++) {
+ Piece mouse = _amice[m];
+ if (mouse.active && (mouse.x == cat.x) &&
+ (mouse.y == cat.y) &&
+ (mouse.orientation == ((cat.orientation+2)%4))) {
+ // bang! howdy partner!
+ return -1;
+ }
+ }
+ // we didn't run into anything, so we update the board
+ // with our new position
+ _aboard[cidx] += CAT_ID;
+ // since we're in a new square, we need to potentially
+ // adjust the orientation of le chat
+ cat.orientation = adjustOrientation(cat, arrows, true);
+ }
+ }
+
+ // now move the meeses
+ int active = 0;
+ for (int m = 0; m < _amice.length; m++) {
+ Piece mouse = _amice[m];
+ // System.out.println("Examining mouse: " + mouse);
+
+ // skip inactive mice
+ if (!mouse.active) {
+ continue;
+ }
+
+ // move to our new position
+ mouse.x += X_ADJUSTS[mouse.orientation];
+ mouse.y += Y_ADJUSTS[mouse.orientation];
+
+ // check to see if we just moved into a hole or a rocket
+ // or a cat
+ int midx = mouse.getIndex();
+ if ((_board[midx] & HOLE_MASK) != 0) {
+ // too bad, so sad
+ return -1;
+
+ } else if ((_board[midx] & ROCKET_MASK) != 0) {
+ // whee!
+ mouse.active = false;
+
+ } else if ((_aboard[midx] & CAT_MASK) != 0) {
+ // too bad, so sad
+ return -1;
+
+ } else {
+ // since we're in a new square, we need to potentially
+ // adjust the orientation of le mouse
+ mouse.orientation = adjustOrientation(mouse, arrows, false);
+ // we're still active, make a note of it
+ active++;
+ }
+ }
+
+ // if we have no more active mice, the board worked. yay!
+ if (active == 0) {
+ return iter;
+ }
+
+ // print out our board position
+ if (_debug) {
+ System.out.println("After iteration " + iter + ".");
+ writeTo(System.out, _acats, _amice);
+ }
+ }
+
+ throw new MaximumIterationsExceeded();
+ }
+
+ /**
+ * Examines the walls and arrows in the vicinity of the supplied piece
+ * and returns the new orientation for that piece. Depending on the
+ * value of deduct this function will also reduce the
+ * potency of arrows that reflect a piece 180 degrees.
+ *
+ * @param piece the piece whose orientation will be adjusted.
+ * @param arrows the arrow information currently in effect.
+ * @param deduct if true, deduct one unit of potency from an arrow
+ * that reflects the piece 180 degrees. Arrows have only two units of
+ * potency and will thus disappear after two such reflections.
+ *
+ * @return the new orientation for the piece in question.
+ */
+ protected int adjustOrientation (Piece piece, int[] arrows, boolean deduct)
+ {
+ int orient = piece.orientation;
+ int pos = piece.getIndex();
+
+ // if there's an arrow, we need to first account for that
+ int aorient = arrows[pos];
+ if (aorient >= 0) {
+ aorient = (aorient & 0xF);
+ if (aorient == ((orient + 2) % 4)) {
+ // deduct one from the potency of this arrow
+ if ((arrows[pos] & 0x10) != 0) {
+ // we've already deducted one, wipe it out
+ arrows[pos] = -1;
+ } else {
+ arrows[pos] |= 0x10;
+ }
+ }
+ orient = aorient;
+ // System.out.println("Reoriented " + piece);
+ }
+
+ // now account for the walls. if there's a wall in front of us, we
+ // need to examine things further
+ int fpos = (pos + FRONT_OFFSET[orient]) % SQUARES;
+ if ((_board[fpos] & FRONT_MASK[orient]) == 0) {
+ // nothing in front, we're done
+ return orient;
+ }
+
+ int rpos = (pos + RIGHT_OFFSET[orient]) % SQUARES;
+ if ((_board[rpos] & RIGHT_MASK[orient]) == 0) {
+ // nothing on our right, so we rotate right and we're done
+ // System.out.println("Right turned " + piece);
+ return (orient + 1) % 4;
+ }
+
+ int lpos = (pos + LEFT_OFFSET[orient]) % SQUARES;
+ if ((_board[lpos] & LEFT_MASK[orient]) == 0) {
+ // nothing on our left, so we rotate left and we're done
+ // System.out.println("Left turned " + piece);
+ return (orient + 3) % 4;
+ }
+
+ // otherwise we turn around
+ // System.out.println("Reversed " + piece);
+ return (orient + 2) % 4;
+ }
+
+ /**
+ * Reads in a text description of a Chu Chu board which should first
+ * be a grid of 12x9 characters containing the wall information, aside
+ * a grid of 12x9 characters containing the feature information
+ * (holes, rockets, cats, mice). The two grids are separated by one
+ * space. The wall grid contains L, | and _ to indicate left and
+ * bottom, left only and bottom only respectively. The feature grid
+ * contains H, R, N/E/W/S and n/e/w/s. Uppercase compass directions
+ * represent cats and lowercase directions represent mice. All
+ * wall-less and featureless squares are represented by a dot. For
+ * example, the first puzzle in the Mania stage would be represented
+ * like so:
+ *
+ * + * |._......... e........... + * |L|.L......| ............ + * |._|._...L_. ............ + * |._..||_.._. ............ + * |L.....L|.|| ............ + * |||....._.L| ..SNS.NSN... + * |L|_.....|.. ......R..... + * |.._|....... ............ + * L___________ ............ + *+ * + * Note that because the entire board is outlined in walls, the upper + * and right walls need not be communicated in the grid. The lower and + * left walls will automatically be added even if they are not + * specified in the input. + */ + public void readFrom (BufferedReader input) + throws IOException + { + ArrayList cats = new ArrayList(); + ArrayList mice = new ArrayList(); + String line; + + for (int h = 0; h < HEIGHT; h++) { + line = input.readLine(); + if (line == null) { + throw new IOException("Ran out of input before parsing " + + "entire board."); + } + + // parse the wall definition + for (int w = 0; w < WIDTH; w++) { + char pos = line.charAt(w); + int coord = h*WIDTH + w; + switch (pos) { + case 'L': + _board[coord] |= BOTH_WALLS_MASK; + break; + + case '|': + _board[coord] |= LEFT_WALL_MASK; + break; + + case '_': + _board[coord] |= BOTTOM_WALL_MASK; + break; + + case '.': + case ' ': + // blank space, do nothing + break; + + default: + String msg = "Unexpected wall character '" + pos + + "' at position +" + w + "+" + h + "."; + throw new IOException(msg); + } + } + + // parse the board features + for (int w = 0; w < WIDTH; w++) { + char pos = line.charAt(w + WIDTH + 1); + int coord = h*WIDTH + w; + switch (pos) { + case 'R': + _board[coord] |= ROCKET_MASK; + break; + + case 'H': + _board[coord] |= HOLE_MASK; + break; + + case 'N': + case 'E': + case 'W': + case 'S': + cats.add(new Piece(Piece.charToDirection(pos), w, h)); + break; + + case 'n': + case 'e': + case 'w': + case 's': + mice.add(new Piece(Piece.charToDirection(pos), w, h)); + break; + + case '.': + case ' ': + // blank space, do nothing + break; + + default: + String msg = "Unexpected feature character '" + pos + + "' at position +" + w + "+" + h + "."; + throw new IOException(msg); + } + } + } + + // stick our pieces into an array + _cats = new Piece[cats.size()]; + cats.toArray(_cats); + _mice = new Piece[mice.size()]; + mice.toArray(_mice); + + // we'll use these later + _acats = new Piece[cats.size()]; + for (int i = 0; i < _cats.length; i++) { + Piece piece = (Piece)_cats[i].clone(); + _acats[i] = piece; + } + _amice = new Piece[mice.size()]; + for (int i = 0; i < _mice.length; i++) { + Piece piece = (Piece)_mice[i].clone(); + _amice[i] = piece; + } + } + + /** + * Outputs the board in a format in which it can be read back in via + *
readFrom().
+ *
+ * @see #readFrom
+ */
+ public void writeTo (PrintStream out)
+ {
+ writeTo(out, _cats, _mice);
+ }
+
+ protected void writeTo (PrintStream out, Piece[] cats, Piece[] mice)
+ {
+ for (int h = 0; h < HEIGHT; h++) {
+ // print the wall description
+ for (int w = 0; w < WIDTH; w++) {
+ int pos = h*WIDTH + w;
+ switch (_board[pos] & BOTH_WALLS_MASK) {
+ case LEFT_WALL_MASK:
+ out.print("|");
+ break;
+ case BOTTOM_WALL_MASK:
+ out.print("_");
+ break;
+ case BOTH_WALLS_MASK:
+ out.print("L");
+ break;
+ default:
+ out.print(".");
+ break;
+ }
+ }
+
+ // separate the grids by a space
+ out.print(" ");
+
+ // print the feature description
+ Piece p;
+ for (int w = 0; w < WIDTH; w++) {
+ int pos = h*WIDTH + w;
+ if ((_board[pos] & ROCKET_MASK) != 0) {
+ out.print("R");
+ } else if ((_board[pos] & HOLE_MASK) != 0) {
+ out.print("H");
+ } else if ((p = pieceAtCoordinates(cats, w, h)) != null) {
+ out.print(Piece.directionToChar(p.orientation, true));
+ } else if ((p = pieceAtCoordinates(mice, w, h)) != null) {
+ out.print(Piece.directionToChar(p.orientation, false));
+ } else {
+ out.print(".");
+ }
+ }
+
+ // output a newline
+ out.println("");
+ }
+ }
+
+ public void dump (PrintStream out)
+ {
+ for (int h = 0; h < HEIGHT; h++) {
+ for (int w = 0; w < WIDTH; w++) {
+ out.print(HEX[_board[h*WIDTH + w]]);
+ }
+ out.println("");
+ }
+ }
+
+ /**
+ * Returns any piece located at the requested coordinates, or null if
+ * no piece is at those coordinates.
+ */
+ protected Piece pieceAtCoordinates (Piece[] pieces, int x, int y)
+ {
+ for (int i = 0; i < pieces.length; i++) {
+ if (pieces[i].x == x && pieces[i].y == y) {
+ return pieces[i];
+ }
+ }
+ return null;
+ }
+
+ public static void main (String[] args)
+ {
+ if (args.length < 1) {
+ System.err.println("Usage: chuchu.Board board_file");
+ System.exit(-1);
+ }
+
+ try {
+ Board board = new Board(true);
+ FileReader fin = new FileReader(args[0]);
+ BufferedReader in = new BufferedReader(fin);
+ board.readFrom(in);
+ board.writeTo(System.out);
+
+ int[] arrows = new int[SQUARES];
+ Arrays.fill(arrows, -1);
+ arrows[2*WIDTH+0] = Piece.EAST;
+ arrows[0*WIDTH+1] = Piece.WEST;
+ if (board.execute(arrows, 100) >= 0) {
+ System.out.println("Success!");
+ } else {
+ System.out.println("Failure!");
+ }
+
+ } catch (MaximumIterationsExceeded mie) {
+ System.out.println("Maximum iterations exceeded.");
+
+ } catch (IOException ioe) {
+ ioe.printStackTrace(System.err);
+ }
+ }
+
+ /**
+ * The board is represented by an integer for each square. Individual
+ * bits of that integer represent activated walls and features.
+ */
+ protected int[] _board = new int[SQUARES];
+
+ // the pieces on the board (cats and mice) are maintained in these
+ // arrays
+ protected Piece[] _cats;
+ protected Piece[] _mice;
+
+ // temporary copies of our board and pieces for use when executing
+ protected int[] _aboard = new int[SQUARES];
+ protected Piece[] _acats;
+ protected Piece[] _amice;
+
+ protected boolean _debug;
+
+ protected static final int LEFT_WALL_MASK = 0x01;
+ protected static final int BOTTOM_WALL_MASK = 0x02;
+ protected static final int BOTH_WALLS_MASK =
+ LEFT_WALL_MASK|BOTTOM_WALL_MASK;
+
+ protected static final int ROCKET_MASK = 0x04;
+ protected static final int HOLE_MASK = 0x08;
+ protected static final int FEATURE_MASK = ROCKET_MASK|HOLE_MASK;
+
+ protected static final char[] HEX = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+ protected static final int CAT_ID = 0x100;
+ protected static final int MOUSE_ID = 0x01;
+ protected static final int CAT_MASK = 0xFF00;
+
+ protected static final int[] X_ADJUSTS = { 0, 1, 0, -1 };
+ protected static final int[] Y_ADJUSTS = { -1, 0, 1, 0 };
+
+ protected static final int[] FRONT_OFFSET = { WIDTH*(HEIGHT-1), 1, 0, 0 };
+ protected static final int[] FRONT_MASK = {
+ BOTTOM_WALL_MASK, LEFT_WALL_MASK, BOTTOM_WALL_MASK, LEFT_WALL_MASK };
+
+ protected static final int[] RIGHT_OFFSET = { 1, 0, 0, WIDTH*(HEIGHT-1) };
+ protected static final int[] RIGHT_MASK = {
+ LEFT_WALL_MASK, BOTTOM_WALL_MASK, LEFT_WALL_MASK, BOTTOM_WALL_MASK };
+
+ protected static final int[] LEFT_OFFSET = { 0, WIDTH*(HEIGHT-1), 1, 0 };
+ protected static final int[] LEFT_MASK = {
+ LEFT_WALL_MASK, BOTTOM_WALL_MASK, LEFT_WALL_MASK, BOTTOM_WALL_MASK };
+}
diff --git a/projects/chuchu/src/java/com/samskivert/chuchu/MaximumIterationsExceeded.java b/projects/chuchu/src/java/com/samskivert/chuchu/MaximumIterationsExceeded.java
new file mode 100644
index 00000000..f7a441f0
--- /dev/null
+++ b/projects/chuchu/src/java/com/samskivert/chuchu/MaximumIterationsExceeded.java
@@ -0,0 +1,12 @@
+//
+// $Id: MaximumIterationsExceeded.java,v 1.1 2001/07/09 09:27:40 mdb Exp $
+
+package com.samskivert.chuchu;
+
+public class MaximumIterationsExceeded extends Exception
+{
+ public MaximumIterationsExceeded ()
+ {
+ super("");
+ }
+}
diff --git a/projects/chuchu/src/java/com/samskivert/chuchu/Piece.java b/projects/chuchu/src/java/com/samskivert/chuchu/Piece.java
new file mode 100644
index 00000000..d8ffcd3d
--- /dev/null
+++ b/projects/chuchu/src/java/com/samskivert/chuchu/Piece.java
@@ -0,0 +1,115 @@
+//
+// $Id: Piece.java,v 1.1 2001/07/09 09:27:40 mdb Exp $
+
+package com.samskivert.chuchu;
+
+public class Piece implements Cloneable
+{
+ /** Compass orientation constant. */
+ public static final int NORTH = 0;
+
+ /** Compass orientation constant. */
+ public static final int EAST = 1;
+
+ /** Compass orientation constant. */
+ public static final int SOUTH = 2;
+
+ /** Compass orientation constant. */
+ public static final int WEST = 3;
+
+ /** Compass orientation of this piece. */
+ public int orientation;
+
+ /** X coordinate of this piece. */
+ public int x;
+
+ /** Y coordinate of this piece. */
+ public int y;
+
+ /** The fractional offset of a unit for this piece. */
+ public int offset;
+
+ /** Indicates whether this piece is active. */
+ public boolean active = true;
+
+ /**
+ * Constructs a new piece with the specified orientation and
+ * coordinates.
+ */
+ public Piece (int orientation, int x, int y)
+ {
+ this.orientation = orientation;
+ this.x = x;
+ this.y = y;
+ }
+
+ /**
+ * Returns the board index occupied by this piece.
+ */
+ public int getIndex ()
+ {
+ return y * Board.WIDTH + x;
+ }
+
+ /**
+ * Initializes this piece with the values from the supplied piece.
+ */
+ public final void init (Piece other)
+ {
+ this.orientation = other.orientation;
+ this.x = other.x;
+ this.y = other.y;
+ this.offset = 0;
+ this.active = true;
+ }
+
+ /**
+ * Returns a clone of this piece instance.
+ */
+ public Object clone ()
+ {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException cnse) {
+ return null;
+ }
+ }
+
+ public String toString ()
+ {
+ return "[orient=" + orientation + ", x=" + x + ", y=" + y +
+ ", offset=" + offset + ", active=" + active + "]";
+ }
+
+ /**
+ * Converts a direction constant into a character suitable for
+ * printing in a board description.
+ */
+ public static char directionToChar (int direction, boolean upper)
+ {
+ return upper ? UPPER_DIRS[direction] : LOWER_DIRS[direction];
+ }
+
+ /**
+ * Converts the letters n, e, w, and s into the proper direction code
+ * for their respective directions.
+ */
+ public static int charToDirection (char pos)
+ {
+ if (pos == 'N' || pos == 'n') {
+ return Piece.NORTH;
+ } else if (pos == 'E' || pos == 'e') {
+ return Piece.EAST;
+ } else if (pos == 'W' || pos == 'w') {
+ return Piece.WEST;
+ } else if (pos == 'S' || pos == 's') {
+ return Piece.SOUTH;
+ } else {
+ String msg = "Invalid direction character '" + pos + "'.";
+ throw new RuntimeException(msg);
+ }
+ }
+
+ protected static final char[] UPPER_DIRS = { 'N', 'E', 'S', 'W' };
+ protected static final char[] LOWER_DIRS = { 'n', 'e', 's', 'w' };
+}
diff --git a/projects/chuchu/src/java/com/samskivert/chuchu/Solver.java b/projects/chuchu/src/java/com/samskivert/chuchu/Solver.java
new file mode 100644
index 00000000..253da628
--- /dev/null
+++ b/projects/chuchu/src/java/com/samskivert/chuchu/Solver.java
@@ -0,0 +1,122 @@
+//
+// $Id: Solver.java,v 1.1 2001/07/09 09:27:40 mdb Exp $
+
+package com.samskivert.chuchu;
+
+import java.io.*;
+import java.util.Arrays;
+
+public class Solver
+{
+ public Solver (Board board, String arrows)
+ {
+ _board = board;
+
+ // parse the arrow description
+ _arrows = new int[arrows.length()];
+ for (int i = 0; i < _arrows.length; i++) {
+ _arrows[i] = Piece.charToDirection(arrows.charAt(i));
+ }
+ }
+
+ public void solve ()
+ {
+ int[] arrows = new int[Board.SQUARES];
+ Arrays.fill(arrows, -1);
+ solve(arrows, 0);
+ }
+
+ protected void solve (int[] arrows, int aidx)
+ {
+ for (int i = 0; i < arrows.length; i++) {
+ // skip occupied positions
+ if (_board.isOccupied(i)) {
+ continue;
+ }
+
+ // skip positions that already have an arrow in them
+ if (arrows[i] >= 0) {
+ continue;
+ }
+
+ // note our arrow position
+ arrows[i] = _arrows[aidx];
+
+ // and if we placed the last arrow, execute
+ if (aidx+1 == _arrows.length) {
+ // copy out arrow configuration into a temporary array
+ System.arraycopy(arrows, 0, _tmparrows, 0, arrows.length);
+
+ // execute
+ try {
+ int iters = _board.execute(_tmparrows, MAX_ITERS);
+ if ((_count++ % 100000) == 99999) {
+ System.out.print(".");
+ System.out.flush();
+ }
+ if (iters >= 0) {
+ System.out.println("\nSOLUTION: " + iters);
+ printArrangement(arrows);
+ }
+
+ } catch (MaximumIterationsExceeded mie) {
+ // System.out.println("INFINITE LOOP");
+ // printArrangement(arrows);
+ }
+
+ } else {
+ // otherwise place the next arrow
+ solve(arrows, aidx+1);
+ }
+
+ // clear out our arrow position candidate
+ arrows[i] = -1;
+ }
+ }
+
+ protected void printArrangement (int[] arrows)
+ {
+ for (int i = 0; i < arrows.length; i++) {
+ if (arrows[i] < 0) {
+ continue;
+ }
+ System.out.println(Piece.directionToChar(arrows[i], true) +
+ " => +" + (i % Board.WIDTH) +
+ "+" + (i / Board.WIDTH) + ".");
+ }
+ }
+
+ protected void printArrows (int[] arrows)
+ {
+ }
+
+ public static void main (String[] args)
+ {
+ if (args.length < 2) {
+ System.err.println("Usage: chuchu.Solver board_file arrow_desc");
+ System.err.println(" where arrow_desc = [NESW]+");
+ System.exit(-1);
+ }
+
+ try {
+ Board board = new Board(false);
+ FileReader fin = new FileReader(args[0]);
+ BufferedReader in = new BufferedReader(fin);
+ board.readFrom(in);
+
+ Solver solver = new Solver(board, args[1]);
+ solver.solve();
+
+ } catch (IOException ioe) {
+ ioe.printStackTrace(System.err);
+ }
+ }
+
+ protected Board _board;
+ protected int[] _arrows;
+ protected int[] _tmparrows = new int[Board.SQUARES];
+ protected int _count;
+
+ // if we haven't seen a solution in this many iterations, bail
+ protected static final int MAX_ITERS = 150;
+}
diff --git a/projects/chuchu/src/java/com/samskivert/chuchu/blank.txt b/projects/chuchu/src/java/com/samskivert/chuchu/blank.txt
new file mode 100644
index 00000000..7a06ec37
--- /dev/null
+++ b/projects/chuchu/src/java/com/samskivert/chuchu/blank.txt
@@ -0,0 +1,9 @@
+|........... ............
+|........... ............
+|........... ............
+|........... ............
+|........... ............
+|........... ............
+|........... ............
+|........... ............
+L___________ ............
diff --git a/projects/chuchu/src/java/com/samskivert/chuchu/board.txt b/projects/chuchu/src/java/com/samskivert/chuchu/board.txt
new file mode 100644
index 00000000..5ff2661e
--- /dev/null
+++ b/projects/chuchu/src/java/com/samskivert/chuchu/board.txt
@@ -0,0 +1,39 @@
+L____.|_____ e..........w N
+|..|__L_ L.. H.R......R.H E
+||.|..|..|.| ............ W
+||....|....| ............ S
+||....|....| ............
+|.....|..... ............
+|..L..|._|.. .....HH.....
+|.....|..... ...HHHHHH...
+L___________ HH.NNNNNN.HH
+
+|.........|. HHHHHHHHHH.. E
+|..........| HHHHHHHHHH.. W
+L__...____.| HHHHHHHHHH..
+|_.|..|._.|| E..HHH......
+|L|..._.L|.| R.......H...
+L__|...L_..| .E.HHHH.....
+|....____..| HHHHHHHHH...
+|....____.._ HHHHHeee....
+L________L_L HHHHHHHHH..H
+
+|_.......... e........... E
+|L|.L......| ............ W
+|._|._...L_. ............
+|._..||_.._. ............
+|L.....L|.|| ............
+|||....._.L| ..SNS.NSN...
+|L|_.....|.. ......R.....
+|.._|....... ............
+L___________ ............
+
+|...||.....| ............ N
+|..||.....|| ............ W
+|..||.||..|| ......S.....
+|.|||||||||| ..sss.S.R...
+|.|||||||||| ..sss.S.....
+|.|||||||||| ..sss.S.....
+L__||.....|| ............
+|___|.....|| ............
+L_________L_ ............