Commits

Anonymous committed 9b6cb34

Initial import of RUBE II sources, version 1.0 revision 2001.0201.

  • Participants
  • Tags rel_1_0_2001_0201

Comments (0)

Files changed (9)

File doc/impl.txt

+RUBE II:  Das Klickenklacker, First Sketch at Implementation:
+
+Supported Command-Line:
+
+The RUBE II interpreter ("klik") supports the following syntax in the
+command line:
+	klik <sourcefile>
+	klik <options> <sourcefile>
+	klik <sourcefile> <options>
+where options are a single "word," prefaced by a dash ('-') or a
+slash ('/'), and are made up of any ordering of the following:
+	option	example	description
+	a	a	No ANSI signals to clear screen
+	b<num>	b150	Begin animation at specified frame number
+	e<num>	e400	End program after specified frame number
+	h<num>	h12	Display no more than the first lines
+	q	q	"Quiet Mode"--does not show animation
+	t<num>	t78	Truncate display lines to a particular width
+	x<num>	x25	Sets the width (x-dimension) of the warehouse
+	y<num>	y10	Sets the depth (y-dimension) of the warehouse
+Options can appear in any order.
+
+So, as an example, we can have something like:
+	klik -h24t80x500y750 hugeprog.rub
+which will run the RUBE program in a warehouse 500 by 750 cells, but
+only display the upper-left screenful.
+
+Example Programs:
+
+A language implementation is useless without good samples, so the
+following (which, honestly, were created for testing, so don't
+expect miracles) are included:
+	echo.rub	A simple program that parrots keyboard input
+	hi.rub		Hello, World! - inelegant
+	hi2.rub		Hello, World! - slightly more RUBE-ish
+	test.rub	Fairly extensive test
+	bob.rub		Not quite 99 Bottles of Beer, but a few bottles
+			of something, at least...
+
+Random Meanderings:
+
+The primary object in RUBE II will be the grid of objects, much
+like in RUBE.  Each object in the grid will have to be one of
+the three types:  Stationary, Motive, and Moveable.
+
+This suggests a preliminary structure for the objects in general:
+	struct	thing
+		{
+		 char	display;
+		 int	type;
+		 int	pushed;
+		 int	motivation;
+		 int	direction;
+		}
+Stationary Objects have pushed and motivation set to zero at all
+times, resetting each round if necessary.
+Motive Objects have motivation set to one.  Each cycle, an engine
+tries to set pushed to one, which will succeed if nothing if
+there is no conflict.
+Moveable Objects have motivation and pushed set to zero, but a
+Motive Object can set pushed to one until the end of the round.
+
+The basic RUBE resolution algorithm, therefore, is something like
+	save old gridstate
+	for each square in grid
+		conflict = no
+		if square+(direction*pushed) is filled
+			check old state of square
+			resolve conflict = (yes/no)
+		if conflict = no
+			square+(direction*pushed) = square
+			point square to square+(direction*pushed)
+			square.pushed = square.motivation
+
+Which works, but precedence rules might be better suited
+	create newgrid
+	for each square in grid, ordered by precedence
+		point sq to newgrid.square+(direction*pushed)
+		if sq is empty
+			sq = grid.square
+		else if newgrid.square is empty
+			point sq to newgrid.square
+			sq = grid.square
+		else
+			point sq to newgrid.square
+			square.pushed = sq.motivation
+			square.direction = sq.direction
+			choose square on newgrid again
+		sq.pushed = sq.motivation;
+
+Precedence Rules are:
+	 1.  Stationary Objects
+	 2.  Motive Objects - Vertical
+	 3.  Motive Objects - Horizontal
+	 4.  Moveable Objects - Vertical
+	 5.  Moveable Objects - Horizontal
+
+Optimizations:
+
+It seems to be much easier to treat gravity independently from the
+rest of the action.  Not only does it speed up execution, since
+spaces have no interesting semantics to speak of, but it also serves
+as a template for other forces (wind, maybe?  slower falling?).
+
+Specific comments on this implementation:
+
+ 1- The core code is fairly well-structured, in my opinion, but
+    the "peripheral" code could be better managed as functions
+    pointed to from within the Thing structure.  However, great
+    thought needs to be given to the parameters, since changing
+    every last call for every code change is not an appealing
+    notion.
+ 2- The code, as it stands, is 100% ANSI, as far as I know,
+    except for input.  It makes it through Borland C++ 5.01
+    (bcc -wall) and gcc/CygWin (gcc -ansi -pedantic) without
+    warnings.  To do so, however, requires that the input command
+    be conditionally compiled, meaning that input is impossible
+    on other platforms (until someone tells me how to poll the
+    input stream on other systems).
+
+Where to go from here:
+
+One thing I would really like to do is to clean up the "Before" and
+"On Collision" code, and make it a bit more object-oriented, as
+mentioned in the code, itself.  If that gets done in a reasonable way,
+it might even be possible to create a "RUBE framework" where the set
+of objects and rules can be defined externally, rather than hardcoding
+everything.
+
+Color might be a useful visualization technique, changing the color
+each cycle that an object stays in the same place.
+
+It would be really nice if input worked for all the major systems
+out there.
+
+There are one or two other objects that might come in handy for RUBE
+programmers, like comparison objects which can block or free
+neighboring cells.  An addition to that might be to give each crate
+a weight given its value, making this a scale.
+
+Speaking of addition, an "overflow crate" might be nice.
+
+Finally, There appears to be a minor bug or two regarding gravity.
+The only known such bug is an admitted oversight--I need to add
+vertical collision code for the ramps, so that objects that fall on a
+ramp slide down.  It's probably just another line of code for each
+case, but that shouldn't delay the interpreter any more than it
+already has, right?

File doc/klik.txt

+RUBE II:  Das Klickenklacker
+
+Introduction:
+
+RUBE II, like its predecessor, is an automaton-like system, where a set
+of objects are governed by the predetermined laws of "physics."  Once
+constructed, the RUBE program executes forever in time units known as
+"frames," and there is no "current-" or "next instruction," in the
+traditional programming sense.
+
+Frames are not unlike frames in a movie, where each is a step in the
+action.  Each frame is created from the last through the following a
+simple process of determining the next location for objects, resolving
+impending collisions between objects, and committing the resolved
+movements.
+
+The collision resolution is made up of two parts:  Precedence rules,
+and recursive effects.  Precedence rules govern which objects and/or
+directions are more important in a collision:  A higher-precedence
+object involved in a collision will be allowed to progress, while any
+other objects will be held in their previous locations.  A Recursive
+Effect is just a fancy term stating that if the resolution of one
+collision causes another potential collision, the new collision must
+also be resolved, using the same precedence rules.
+
+Objects and Collisions:
+
+Objects fall into three major categories.  In order of their precedence,
+they are Stationary Objects, Motive Objects, and Moveable Objects.
+
+Stationary Objects are items that are immobile throughout the course of
+the program.  As such, they have the highest precedence of all objects,
+since they must always be in the same location.  They include structural
+objects, and data manipulation objects.
+
+Motive Objects are objects which can change the location of other
+(Moveable) objects.  They automatically move in a preset direction and,
+when a Moveable Object (see below) is encountered, "push" that object
+at the same speed.  These include the so-called dozers.
+
+Moveable Objects are data.  They can be pushed by Motive Objects, and
+are frequently operated upon by Stationary Objects.  Note that, when
+being pushed by a Motive Object, the recursive nature of RUBE implicitly
+"promotes" the Moveable Object to a Motive Object, for purposes of
+collision resolution.
+
+Within each category of objects (except Stationary Objects, for obvious
+reasons), there are two axes of motion along which the object might
+move.  The precedence of axes are ordered such that vertical movement
+is resolved before horizontal movement.  Diagonal movement, if possible,
+is resolved after the "cardinal" movements have been resolved.
+
+The only remaining precedence issues are along the same axis of motion--
+so-called "head-to-head" collisions.  Within this case, there are two
+subcases.  Each gives both objects the same precedence, but the
+collision must be handled differently in each case.
+
+First, the easy one:  If two already-adjacent objects are trying to
+enter each other's locations, the resulting collision halts both
+objects.
+
+Alternatively, if two non-adjacent objects are trying to enter the same
+location, the result is undefined.  One object or the other will be
+treated as though it has a higher precedence for the purposes of the
+single collision.
+
+Finally, at the bottom of the precedence rules are Moveable Objects
+which are "transferred" to a new location by a Stationary Object (see
+the Conveyor Objects, Incline Objects, Winch Object, Swinch Objects, and
+Gate Object).  In such a collision, the transfer is simply ignored.
+
+Forces:
+
+RUBE supports a single force, a pseudo-gravity which pulls all non-
+Stationary Objects downward at a rate of one location per frame.  For
+purposes of conflict resolution, one can consider each object to have
+an implicit Motive Object above it, pushing downward.
+
+That is, unless stopped, all objects (other than Stationary) will move
+downward, one location every frame.
+
+Stationary Objects:
+
+As described before, Stationary Objects have the highest precedence, as
+befits their use as "operation nodes" and/or structural features.  Among
+them are:
+
+Structural:  Structural Objects are Stationary Objects which do not
+alter Crates (see Moveable Objects).  At most, they simply change the
+location of a nearby Object.
+        Object  Name            Special Features
+          =     Girder          None
+          /     Incline Right   Motive force up 1 to right, left 1 to
+                                above
+          \     Incline Left    Motive force up 1 to left, right 1 to
+                                above
+          >     Right Conveyor  Motive force right 1 to above
+          <     Left Conveyor   Motive force left 1 to above
+          W     Winch Up        Move below to above
+	  M	Winch Down	Move above to below
+          V     Swinch Up       Move below to above, change to A
+          A     Swinch Down     Move above to below, change to V
+          ,     Reflector       Reverse Dozers (see below) that pass below
+Operational:  Operational Objects are Stationary Objects which, in
+addition to being usable as Structural Objects Girder), also make
+modification to nearby Moveable Objects.
+        Object  Name            Special Features
+          +     Packer          Adds left to right, sum below
+          -     Unpacker        Subtracts right from left, result below
+			NB:  Packers and Unpackers use Modular
+			     arithmetic.  Only values 0-f result.
+          :     Replicator      Copy above to below
+          .     Replicate Up    Copy below to above
+          K     Gate            Move above to left if less than below,
+                                Move above to right if greater than
+                                below
+          F     Furnace         Destroy surrounding
+Communications:  Communications Objects act entirely like Girders,
+but also allow for communications with the outside world.
+        Object  Name            Special Features
+          I     Input           Creates input character nibbles below
+          N     Output Number   Prints nibble above
+          C     Output Char     Prints character represented by nibbles
+                                above
+It should be noted that a RUBE program may contain more than one Input
+Object (I).  Should this happen, then input characters are delivered,
+each to a different Input Object in succession, from left to right, then
+top to bottom, like a scan line of a television.  When no more Input
+Objects are available, the first one is used again.
+
+Motive Objects:
+
+As mentioned above, Motive Objects move under their own power, and have
+a higher precedence than Moveable Objects, effectively pushing them to
+where they are (presumably) needed.  At this time, there are only three
+Motive Objects, plus the implicit Motive Object which handles gravity:
+        Object  Name            Special Features
+          (     Right Dozer     Moves Right
+          )     Left Dozer      Moves Left
+          ^     Rocket          Moves Up
+
+Moveable Objects:
+
+Moveable Objects, as described above, are the data of the RUBE program.
+They can be moved by Motive Objects, and manipulated by Stationary
+Objects.
+        Object  Name            Special Features
+         0..9   Crate           Values 0 through 9
+         a..f   Crate           Values 10 through 15
+

File doc/license.txt

+Boring Licensing Information:
+
+The contents of this file are essentially in the Public Domain, with the
+exception that anyone who does anything interesting with it (including, but
+not limited to, such pursuits as modifications, sales, professional usage,
+and integration with other products) is required to notify the author (the
+forwarding e-mail address jcolag@bigfoot.com should be stable and will be
+kept current) as to what that will be.
+
+This is only for the purposes of getting an feel for the direction that this
+work should take in the future, and also to see if I have any more recent
+suggestions (or newer version) that might be of use to you.
+
+In exchange for such a liberal policy, this comes with absolutely no
+guarantees, express or implied, on the suitability or stability of this
+work.  As an example, don't come crying if it transforms your baby sister
+into a marauding supervillain.  You've been warned, now, so it's your own
+darn fault.
+
+Licensing the License:  Go ahead and use it.  I would greatly appreciate it
+if the liability example were different for every product, however.
+ I                  ,                                     ,
+                                                         )
+ F                   ========M============================
+         ,               ,
+           (a                M
+          ===========:===
+                       1     M
+         >>>>>>>>>>>>  : F  
+                      -      M
+                       =     
+      ,                      M ,
+       (
+       =====================:==
+     
+      >>>>>>>>>>>>>>>>>>>>>>
+                          ,   ,
+                            )
+  000a7d63657c74646f2002 F =N= F
+, ::::::::::::::::::::::            ,
+                                   )
+ =================================W=
+                                   C  F
+F                                 = >
+ I
+ C
+
+ F
+(002667652266664   85ccfc07f2c41ad)
+================ C ================
+                 I
+
+                 F
+(000a2d61747c625f27206c6f6c6c5084
+=================================W=
+ I                                C  F
+ F                               = >
+                                   8
+                                   1
+                                   K
+ 246813  +42                       6
+  ===>>>> <<          )            =
+                                    
+       =                      2   ===
+  /=\ a        )                  2F3                        4 7
+======= ================          ===             abcdef8)    C
+  I                                         ===N==================
+ 
+  F                                        F
+                 1aaaaaa5               /==
+                 V.:.:.:W     ^        /
+                  2222224             /
+       ^         ========      (123  /
+                               =======
+/* Interpreter for:
+**      RUBE II:  Das Klickenklacker
+** Based on a novel (language) by Christopher A. Pressey.
+** RUBE II Interpreter code by John Colagioia, with some ideas
+** borrowed from the original RUBE interpreter, also by Pressey;
+** released on 28 August 2000.
+**
+** See license.txt for redistribution and disclaimer details.
+**
+** See impl.txt for various implementation notes.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <malloc.h>
+#ifdef __MSDOS__
+#include <conio.h>
+#include <mem.h>
+#include <dos.h>
+#endif /* __MSDOS__ */
+
+/* Directional Constants */
+#define	LEFT	(1)
+#define	UP	(2)
+#define	RIGHT	(3)
+#define	DOWN	(4)
+#define	STOP	(0)
+
+/* Object Type Constants */
+#define	STA	(0)
+#define	MOT	(1)
+#define	MOV	(2)
+
+/* The force, lowercase 'f' */
+#define	GRAV	(1)
+
+/* Objects in the warehouse */
+struct	thing
+	{
+	 char	display;	/* The image displayed */
+	 int	type;		/* The kind of object */
+	 int	pushed;		/* How fast it must move */
+	 int	motivation;	/* How fast it naturally moves */
+	 int	direction;	/* Current direction of motion */
+	 int	freefall;	/* Does gravity apply? */
+	 int	checked;	/* Administrative, to move only once */
+	};
+
+struct	thing	**warehouse;	/* The main board */
+	int	  height,	/* Height of board */
+		  width,	/* Width of board */
+		  ansi,		/* ANSI terminal? */
+		  trunc,	/* Line length */
+		  top;		/* Screen height */
+
+/* Grid maintenance functions */
+struct thing **buildWarehouse (int x, int y);
+void condemnWarehouse (struct thing **warehouse, int x);
+void initializeWarehouse (struct thing **warehouse, int x, int y);
+void fillWarehouse (struct thing **warehouse, int x, int y);
+
+/* Administrative functions */
+int readFile (char *filename, struct thing **warehouse);
+void showGrid (struct thing **warehouse, int x, int y);
+
+/* Actual RUBE-ing functions */
+void cycle (struct thing ***warehouse, int x, int y);
+int moveThing (struct thing **new, struct thing **old, int x, int y);
+int priority (struct thing *object);
+
+int main (int argc, char *argv[])
+{
+ int	 cont = 1,
+	 display = 1,
+	 tempdisplay;
+ unsigned long
+	 xsize = 80,
+	 ysize = 24,
+	 cycleno = 0,
+	 mincycle = 0,
+	 maxcycle;
+ char	*fname = NULL,
+	*opts = NULL;
+
+ ansi = 1;	/* Assume ANSI-compatible unless told otherwise. */
+ trunc = 0;	/* Assume no line truncation */
+ top = 0;	/* Assume no vertical truncation */
+ maxcycle = (unsigned) -1;	/* Assume no termination */
+
+ /* Get the execution details from the command line */
+ switch (argc)
+	{
+	 case 1:
+		fprintf (stderr, "%s:  No filename specified!\n", argv[0]);
+		return (-1);
+	 case 2:
+		fname = argv[1];
+		break;
+	 case 3:
+		if (argv[1][0] == '-' || argv[1][0] == '/')
+			{
+			 opts = &argv[1][1];
+			 fname = argv[2];
+			}
+		else if (argv[2][0] == '-' || argv[2][0] == '/')
+			{
+			 opts = &argv[2][1];
+			 fname = argv[1];
+			}
+		else	{
+			 fprintf (stderr, "%s:  Invalid command sequence!\n", argv[0]);
+			 return (-2);
+			}
+		for (;*opts;++opts)
+			switch (*opts)
+				{
+				 case 'a':	/* Not an ANSI terminal? */
+				 case 'A':
+					ansi = 0;
+					break;
+				 case 'b':	/* Delay animation? */
+				 case 'B':
+					++opts;
+					mincycle = atol (opts);
+					break;
+				 case 'e':	/* End program early? */
+				 case 'E':
+					++opts;
+					maxcycle = atol (opts);
+					break;
+				 case 'h':	/* Show top only? */
+				 case 'H':
+					++opts;
+					top = atol (opts);
+					break;
+				 case 'q':	/* Quiet/No animation? */
+				 case 'Q':
+					display = 0;
+					break;
+				 case 't':	/* Change line length */
+				 case 'T':
+					++opts;
+					trunc = atol (opts);
+					break;
+				 case 'x':	/* Change X size? */
+				 case 'X':
+					++opts;
+					xsize = atol (opts);
+					if (xsize < 2)
+						fprintf (stderr,
+						 "%s:  Warehouse must be at least 2x2 cells!\n",
+						 argv[0]);
+					break;
+				 case 'y':	/* Change Y size? */
+				 case 'Y':
+					++opts;
+					ysize = atol (opts);
+					if (ysize < 2)
+						fprintf (stderr,
+						 "%s:  Warehouse must be at least 2x2 cells!\n",
+						 argv[0]);
+					break;
+				}
+		break;
+	 default:
+		fprintf (stderr, "%s:  Too many parameters!\n", argv[0]);
+		return (-3);
+	}
+
+ /* Allocate the "warehouse" space */
+ warehouse = buildWarehouse (xsize, ysize);
+ initializeWarehouse (warehouse, width, height);
+
+ /* Other initializations */
+ tempdisplay = display;
+ if (mincycle && display)
+	display = 0;
+
+ /* Fill the grid from the given file */
+ if (readFile (fname, warehouse))
+	exit (-4);
+ fillWarehouse (warehouse, width, height);
+
+ /* A second command-line argument shows the animation */
+ if (display)
+	showGrid (warehouse, width, height);
+ /* Allow the simulation to run, animating if necessary */
+ while (cont)
+	{
+	 cycle (&warehouse, width, height);
+	 if (display)
+		{
+		 sleep (1);
+		 showGrid (warehouse, width, height);
+		}
+	 if (cycleno > (unsigned)maxcycle)
+		cont = 0;
+	 if (cycleno == mincycle)
+		display = tempdisplay;
+	 ++cycleno;
+	}
+
+ /* Should we ever exit the loop normally (which would happen if
+ ** the value in cont would ever get changed), clean up the toys
+ */
+ condemnWarehouse (warehouse, width);
+ warehouse = NULL;
+ return (0);
+}
+
+/*** Warehouse-maintainance Functions ***/
+
+struct thing **buildWarehouse (int x, int y)
+{
+ /* Dyanamically allocate a grid of the specified size, plus
+ ** boundary regions.
+ */
+ struct	thing	**grid;
+ int		  i, j;
+
+ width = x;
+ height = y;
+ grid = (struct thing **) malloc (sizeof (struct thing *) * (width + 2));
+ for (i=0;i<width+2;i++)
+	grid[i] = (struct thing *) malloc (sizeof (struct thing) * (height + 2));
+ return (grid);
+}
+
+void condemnWarehouse (struct thing **warehouse, int x)
+{
+ /* Deallocate the memory used for a particular grid. */
+ int	i, j;
+
+ for (i=0;i<x+2;i++)
+	free (warehouse[i]);
+ free (warehouse);
+ return;
+}
+
+void initializeWarehouse (struct thing **warehouse, int x, int y)
+{
+ /* Fill in obvious initial data for a RUBE grid. */
+ int	i, j;
+
+ for (j=0;j<y+2;j++)
+	for (i=0;i<x+2;i++)
+		{
+		 warehouse[i][j].display = ' ';
+		 warehouse[i][j].freefall = 0;
+		}
+ fillWarehouse (warehouse, x, y);
+ return;
+}
+
+void fillWarehouse (struct thing **warehouse, int x, int y)
+{
+ /* Complete the data for each cell, based on the object currently
+ ** assigned to the cell.
+ */
+ struct	thing	*cell;
+ int		 i, j;
+
+ for (i=0;i<x;i++)
+	for (j=y-1;j>-1;j--)
+	/* Examining each cell... */
+		{
+		 cell = &warehouse[i][j];
+		 cell->checked = 0;	/* No cell is checked to start */
+		 switch (cell->display)
+			{
+			 case '=': case '/': case '\\': case '>':
+			 case '<': case 'W': case  'V': case 'A':
+			 case ',': case '+': case  '-': case ':':
+			 case '.': case 'K': case  'F': case 'I':
+			 case 'N': case 'C': case  'M':
+				/* Stationary Objects never move */
+				cell->direction = STOP;
+				cell->motivation = 0;
+				cell->type = STA;
+				cell->freefall = 0;
+#ifndef __MSDOS__
+				/* In non-DOS systems, 'I' isn't
+				** going to work.  We should still
+				** accept it in the program, though,
+				** for portability.
+				*/
+				if (cell->display == 'I') fprintf (stderr,
+				 "Warning:  Input will not function on this system\n");
+#endif /* not __MSDOS__ */
+				break;
+			 /* Assign data to the "movers and shakers" */
+			 case '(':
+				cell->direction = RIGHT;
+				cell->motivation = 1;
+				cell->type = MOT;
+				break;
+			 case ')':
+				cell->direction = LEFT;
+				cell->motivation = 1;
+				cell->type = MOT;
+				break;
+			 case '^':
+				cell->direction = UP;
+				cell->motivation = 1;
+				cell->type = MOT;
+				break;
+			 case ' ':
+				cell->direction = DOWN;
+				cell->motivation = 1;
+				cell->type = MOT;
+				cell->freefall = 0;
+				break;
+			 case '0': case '1': case '2': case '3':
+			 case '4': case '5': case '6': case '7':
+			 case '8': case '9': case 'a': case 'b':
+			 case 'c': case 'd': case 'e': case 'f':
+				/* "Crates"--or Moveable Objects--can
+				** move, but are immoble to start.
+				*/
+				cell->direction = STOP;
+				cell->motivation = 0;
+				cell->type = MOV;
+				break;
+			 default:
+				fprintf (stderr, "Invalid character: %c\n",
+					cell->display);
+				break;
+			}
+		 /* Each Object that moves really "pushes itself." */
+		 cell->pushed = cell->motivation;
+		 /* Unsupported Objects deal with gravity. */
+		 if (warehouse[i][j+1].display == ' ' && cell->type != STA)
+			cell->freefall = 1;
+		 /* Things that fall onto other things no longer worry
+		 ** about gravity.
+		 */
+		 if (cell->freefall && warehouse[i][j+1].display != ' ' &&
+			!warehouse[i][j+1].freefall)
+			cell->freefall = 0;
+		 /* The one exception to gravity is the Rocket, which
+		 ** must go up, instead of down.
+		 */
+		 if (cell->display == '^') cell->freefall = 0;
+		}
+ return;
+}
+
+/*** Initialization Functions ***/
+
+int readFile (char *filename, struct thing **warehouse)
+{
+ /* Relatively straightforward function which opens a file, and places
+ ** the data into the Warehouse grid, truncating lines where it becomes
+ ** necessary, and ignoring any excess lines.
+ */
+ FILE	*infile;
+ int	 x, y, count;
+ char	 ch;
+
+ /* Obvious file stuff */
+ infile = fopen (filename, "r");
+ if (infile == NULL)
+	{
+	 perror (filename);
+	 return (-1);
+	}
+ x = y = 1;
+
+ /* Start reading, character by character */
+ while (!feof (infile) && (y < height + 2))
+	{
+	 fscanf (infile, "%c", &ch);
+	 /* Lines cannot be longer than the grid is wide */
+	 if (x > width)
+		{
+		 fprintf (stderr, "Warning:  %s, Line #%d truncated", filename, y);
+		 /* "Carriage Return," and then search for the next line */
+		 count = 0;
+		 while (ch != '\n')
+			{
+			 fscanf (infile, "%c", &ch);
+			 ++count;
+			}
+		 /* Tell user how many characters were lost */
+		 fprintf (stderr, " by %d characters.\n", count);
+		}
+	 /* Execute a pseudo-"Carriage Return" on a newline */
+	 if (ch == '\n')
+		{
+		 x = 1;
+		 ++y;
+		}
+	 /* Otherwise, just store the character */
+	 else	{
+		 warehouse[x][y].display = ch;
+		 ++x;
+		}
+	}
+ /* Can't have more lines than there are in the grid */
+ if (y > height + 1)
+	{
+	 /* Provide a count of how many lines are ignored */
+	 count = 0;
+	 do	{
+		 do	fscanf (infile, "%c", &ch);
+		 while (!feof (infile) && ch != '\n');
+		 ++count;
+		}
+	 while (!feof (infile));
+	 fprintf (stderr, "Warning:  %d lines truncated from file.\n", count);
+	}
+ return (0);
+}
+
+void showGrid (struct thing **warehouse, int x, int y)
+{
+ /* Print the warehouse contents to the screen. */
+ int	i, j;
+
+ /* ANSI code to clear the screen */
+ if (ansi)
+	printf ("%c[22;1H", 27);
+ else	printf ("\n");
+
+ /* If horizontal/vertical size is limited, change it here */
+ if (trunc && trunc < x)
+	x = trunc;
+ if (top && top < y)
+	y = top;
+
+ /* Run through the grid, printing each character */
+ for (j=1;j<=y;j++)
+	{
+	 for (i=1;i<=x;i++)
+		printf ("%c", warehouse[i][j].display);
+	 if (trunc != 0 && x >= trunc) printf ("\n");
+	}
+ return;
+}
+
+/*** RUBE Activity Functions ***/
+
+int moveThing (struct thing **new, struct thing **old, int x, int y)
+{
+ /* Relocate an object based on its direction and speed. */
+ struct	thing	*object;
+ int		 dir, mot,
+		 i, j, itmp, jtmp,
+		 olddir, oldmot;
+
+ /* Set the starting point */
+ i = x;
+ j = y;
+ object = &(old[x][y]);
+
+ /* Quick optimization:  With gravity handled with the .freefall
+ ** field (see below), spaces don't have to do anything, so we can
+ ** ignore them.
+ */
+ if (object->display == ' ')
+	{
+	 return (0);
+	}
+
+ /* Stationary objects don't move.  Ever. */
+ if (object->type == STA)
+	{
+	 memcpy (&(new[i][j]), object, sizeof (struct thing));
+	 return (0);
+	}
+
+ /* An object in freefall acts like an object being pushed downward,
+ ** so just treat it as if it is moving downward.
+ */
+ if (object->freefall)
+	{
+	 mot = GRAV;
+	 dir = DOWN;
+	 if (new[x][y + mot].display == ' ')
+		j = y + mot;
+	}
+ /* Otherwise, change the coordinates based on the direction and
+ ** velocity of the object in question.  Nothing fancy.
+ */
+ else	{
+	 mot = object->pushed;
+	 dir = object->direction;
+	 if (dir == LEFT)
+		i = x - mot;
+	 else if (dir == RIGHT)
+		i = x + mot;
+	 else if (dir == UP)
+		j = y - mot;
+	 else if (dir == DOWN)
+		j = y + mot;
+	 else	/* Do Nothing */;
+	}
+
+ /* If a motive object is about to move horizontally beneath a
+ ** reflecting object, then change the "dozer," rather than moving
+ ** it.  Change the direction, too.
+ */
+ if ((new[i][j-1].display == ',') && (object->type == MOT) && (dir % 2 == 1))
+	{
+	 dir = (dir==LEFT)?RIGHT:LEFT;
+	 object->display = (object->display=='(')?')':'(';
+	 i = x;
+	 j = y;
+	}
+ /* An object that has become unsupported should enter freefall. */
+ else if ((dir % 2 == 1) && !(object->freefall) && (new[i][j].display == ' ') &&
+	(new[i][j+1].display == ' '))
+	{
+	 j += GRAV;
+	 object->freefall = 1;
+	}
+ /* Objects colliding with ramps will be pushed upwards.  Technically,
+ ** it would be much nicer if ramps and similar ideas were handled
+ ** with an "OnCollision" function accessible through a function
+ ** pointer.  I was going to do it that way, but could not find a
+ ** usable parameter list that was still readable.
+ */
+ else if ((new[i][j].display == '/') && (dir == RIGHT) && (new[i][j-1].display == ' '))
+	j -= 1;
+ else if ((new[i][j].display == '\\') && (dir == LEFT) && (new[i][j-1].display == ' '))
+	j -= 1;
+
+ /* Collisions are special cases which can only occur if any
+ ** movement is about to happen and the destination cell is full.
+ */
+ if ((i != x || j != y) && (old[i][j].display != ' '))
+	/* If the object in the destination cell can still be moved */
+	if (old[i][j].checked == 0)
+		{
+		 /* Save the old heading */
+		 olddir = old[i][j].direction;
+		 oldmot = old[i][j].pushed;
+		 /* Replace its heading with ours */
+		 old[i][j].pushed = mot;
+		 old[i][j].direction = dir;
+		 itmp = i;
+		 jtmp = j;
+		 /* Make sure we haven't already snuck through */
+		 if (new[x][y].display == object->display)
+			new[x][y].display = ' ';
+		 /* Push the thing that's in the way */
+		 if (!moveThing (new, old, i, j))
+			{
+			 /* If it failed, we can't move */
+			 i = x;
+			 j = y;
+			}
+		 /* Restore the heading to the object we just shoved */
+		 old[itmp][jtmp].pushed = oldmot;
+		 old[itmp][jtmp].direction = olddir;
+		}
+	 /* If we're not allowed to move the object in the way, then
+	 ** we can't move, ourselves.
+	 */
+	 else	{
+		 i = x;
+		 j = y;
+		}
+
+ /* Transfer the data from the previous "frame" to the new. */
+ memcpy (&(new[i][j]), object, sizeof (struct thing));
+ /* Only return a 1 if we moved */
+ if (i == x && j == y)
+	return (0);
+ if (object->type != MOT)
+	object->checked = 1;
+ else	object->checked = 0;
+ return (1);
+}
+
+int priority (struct thing *object)
+{
+ /* Return the priority of a particular object.  Bigger numbers
+ ** represent higher priority objects.
+ */
+ int	pri;
+
+ /* An imaginary object doesn't have a priority. */
+ if (object == NULL)
+	return (-1);
+ /* Stationary objects cannot be moved.  Ever.  Therefore, they are
+ ** the highest priority of all.
+ */
+ if (object->type == STA) pri = 5;
+ /* Motive Objects are next, with vertical movement before the
+ ** horizontal.
+ */
+ else if (object->type == MOT)
+	if (object->direction % 2 && !object->freefall)
+		pri = 3;
+	else	pri = 4;
+ /* Finally, we have the Moveable Objects/Crates. */
+ else if (object->type == MOV)
+	if (object->direction % 2 && !object->freefall)
+		pri = 1;
+	else	pri = 2;
+ /* Anything else is the lowest priority. */
+ else	pri = 0;
+ return (pri);
+}
+
+void cycle (struct thing ***warehouse, int x, int y)
+{
+ /* Move all the objects in the grid. */
+ struct	thing	**temp,
+		 *object, *tempobj;
+ int		  i, j, pri, tempval, tempval2;
+
+ /* Create a temporary grid in which we can place objects. */
+ temp = buildWarehouse (x, y);
+ initializeWarehouse (temp, x, y);
+
+ /* For each cell in the original grid, handle their "special
+ ** traits."  This really should be handled with a more object-
+ ** oriented mentality.  I was originally going to put a function
+ ** pointer into the Thing structure, but couldn't decide on the
+ ** right parameters.
+ */
+ for (j=1;j<y;j++) for (i=1;i<x;i++)
+	{
+	 object = &(*warehouse)[i][j];
+	 switch (object->display)
+		{
+		 /* Conveyor belts move the object immediately above
+		 ** one cell in the appropriate direction.
+		 */
+		 case '>':
+			tempobj = &((*warehouse)[i][j-1]);
+			if (tempobj->display != ' ')
+				{
+				 tempobj->direction = RIGHT;
+				 tempobj->pushed = 1;
+				}
+			break;
+		 case '<':
+			tempobj = &((*warehouse)[i][j-1]);
+			if (tempobj->display != ' ')
+				{
+				 tempobj->direction = LEFT;
+				 tempobj->pushed = 1;
+				}
+			break;
+		 /* Arithmetic operators, "packing" and "unpacking,"
+		 ** take a crate on either side, and place the result
+		 ** immediately underneath, assuming there is space.
+		 ** Of course, it is a destructive calculation.
+		 */
+		 case '+':
+			/* Check the left digit. */
+			tempobj = &((*warehouse)[i-1][j]);
+			if (isxdigit (tempobj->display))
+				{
+				 /* Account for hex digits */
+				 if (isdigit (tempobj->display))
+					tempval = tempobj->display - '0';
+				 else	tempval = tempobj->display - 'a' + 10;
+				 /* If successful so far, check the
+				 ** right digit.
+				 */
+				 tempobj = &((*warehouse)[i+1][j]);
+				 if (isxdigit (tempobj->display))
+					{
+					 if (isdigit (tempobj->display))
+						tempval += tempobj->display - '0';
+					 else	tempval += tempobj->display - 'a' + 10;
+					 tempobj->display = ' ';
+					 (*warehouse)[i-1][j].display = ' ';
+					 /* No overflow in RUBE--just
+					 ** give the remainder.
+					 */
+					 tempval %= 16;
+					 tempobj = &((*warehouse)[i][j+1]);
+					 if (tempval < 10)
+						tempobj->display = tempval + '0';
+					 else	tempobj->display = tempval - 10 + 'a';
+					}
+				}
+			break;
+		 case '-':
+			tempobj = &((*warehouse)[i-1][j]);
+			if (isxdigit (tempobj->display))
+				{
+				 if (isdigit (tempobj->display))
+					tempval = tempobj->display - '0';
+				 else	tempval = tempobj->display - 'a' + 10;
+				 tempobj = &((*warehouse)[i+1][j]);
+				 if (isxdigit (tempobj->display))
+					{
+					 if (isdigit (tempobj->display))
+						tempval -= tempobj->display - '0';
+					 else	tempval -= tempobj->display - 'a' + 10;
+					 tempobj->display = ' ';
+					 (*warehouse)[i-1][j].display = ' ';
+					 tempval = (tempval + 16) % 16;
+					 tempobj = &((*warehouse)[i][j+1]);
+					 if (tempval < 10)
+						tempobj->display = tempval + '0';
+					 else	tempobj->display = tempval - 10 + 'a';
+					}
+				}
+			break;
+		 /* Replicators, above-to-below, and below-to-above. */
+		 case ':':
+			tempobj = &((*warehouse)[i][j-1]);
+			if (tempobj->type == MOV && (*warehouse)[i][j+1].display == ' ')
+				memcpy (&((*warehouse)[i][j+1]), tempobj,
+					sizeof (struct thing));
+			break;
+		 case '.':
+			tempobj = &((*warehouse)[i][j+1]);
+			if (tempobj->type == MOV && (*warehouse)[i][j-1].display == ' ')
+				memcpy (&((*warehouse)[i][j-1]), tempobj,
+					sizeof (struct thing));
+			break;
+		 /* So-called winches and "swinches," each works like
+		 ** a replicator, except that they move rather than
+		 ** copy, and a swinch transforms to the other
+		 ** direction each cycle.
+		 */
+		 case 'V':
+			object->display = 'A';
+			/* No break; -- Fall through! to next case */
+		 case 'W':
+			tempobj = &((*warehouse)[i][j-1]);
+			if (tempobj->type == MOV && (*warehouse)[i][j+1].display == ' ')
+				{
+				 memcpy (&((*warehouse)[i][j+1]), tempobj,
+					sizeof (struct thing));
+				 tempobj->display = ' ';
+				}
+			break;
+		 case 'A':
+			object->display = 'V';
+			/* No break; -- Fall through! to next case */
+		 case 'M':
+			tempobj = &((*warehouse)[i][j+1]);
+			if (tempobj->type == MOV && (*warehouse)[i][j-1].display == ' ')
+				{
+				 memcpy (&((*warehouse)[i][j-1]), tempobj,
+					sizeof (struct thing));
+				 tempobj->display = ' ';
+				}
+			break;
+		 /* The Gate is the generic comparator.  The crate
+		 ** above is compared with the crate below.  If the
+		 ** first crate (the one above) is less, it is moved
+		 ** to the left of the gate.  If greater, it is moved
+		 ** to the right of the gate.  Otherwise, it is left
+		 ** on top.
+		 */
+		 case 'K':
+			tempobj = &((*warehouse)[i][j-1]);
+			if (tempobj->type == MOV)
+				{
+				 if (isdigit (tempobj->display))
+					tempval = tempobj->display - '0';
+				 else	tempval = tempobj->display - 'a' + 10;
+				 tempobj = &((*warehouse)[i][j+1]);
+				 if (isdigit (tempobj->display))
+					tempval2 = tempobj->display - '0';
+				 else	tempval2 = tempobj->display - 'a' + 10;
+				 tempobj = &((*warehouse)[i][j-1]);
+				 if (tempval > tempval2 && (*warehouse)[i+1][j].display == ' ')
+					{
+					 memcpy (&((*warehouse)[i+1][j]), tempobj,
+						sizeof (struct thing));
+					 tempobj->display = ' ';
+					}
+				 if (tempval < tempval2 && (*warehouse)[i-1][j].display == ' ')
+					{
+					 memcpy (&((*warehouse)[i-1][j]), tempobj,
+						sizeof (struct thing));
+					 tempobj->display = ' ';
+					}
+				}
+			break;
+		 /* Numerical output prints the decimal value of the
+		 ** crate that is above it. */
+		 case 'N':
+			tempobj = &((*warehouse)[i][j-1]);
+			if (tempobj->type == MOV)
+				if (isdigit (tempobj->display))
+					printf ("%d", tempobj->display - '0');
+				else	printf ("%d", tempobj->display - 'a' + 10);
+			break;
+		 /* Character output prints the character whose ASCII
+		 ** value (in hex) are the crates to the left and
+		 ** right of it.
+		 */
+		 case 'C':
+			tempobj = &((*warehouse)[i-1][j]);
+			if (tempobj->type == MOV)
+				if (isdigit (tempobj->display))
+					tempval = tempobj->display - '0';
+				else	tempval = tempobj->display - 'a' + 10;
+			else break;
+			tempobj = &((*warehouse)[i+1][j]);
+			if (tempobj->type == MOV)
+				if (isdigit (tempobj->display))
+					tempval2 = tempobj->display - '0';
+				else	tempval2 = tempobj->display - 'a' + 10;
+			else break;
+			tempval = tempval * 16 + tempval2;
+			printf ("%c", tempval);
+			(*warehouse)[i-1][j].display = ' ';
+			(*warehouse)[i+1][j].display = ' ';
+			break;
+		 /* Character input, as written, will only work under
+		 ** DOS-like systems, since that's the only place that
+		 ** kbhit() is available.
+		 ** Where it is available, each key pressed generates
+		 ** nibbles to the left and right of the 'I', which
+		 ** work are equivalent to those used in character
+		 ** output.
+		 */
+		 case 'I':
+#ifdef __MSDOS__
+			if (kbhit ())
+				tempval = getch ();
+			else	break;
+			tempval2 = tempval % 16;
+			tempval /= 16;
+			if (tempval < 10)
+				tempval += '0';
+			else	tempval += 'a' - 10;
+			tempobj = &((*warehouse)[i-1][j]);
+			if (tempobj->display == ' ')
+				tempobj->display = tempval;
+			if (tempval2 < 10)
+				tempval2 += '0';
+			else	tempval2 += 'a' - 10;
+			tempobj = &((*warehouse)[i+1][j]);
+			if (tempobj->display == ' ')
+				tempobj->display = tempval2;
+#endif /* __MSDOS__ -- 'I' */
+			break;
+		 /* The furnace is the cleanup crew, destroying
+		 ** everything surrounding it.
+		 */
+		 case 'F':
+			(*warehouse)[i-1][j-1].display = ' ';
+			(*warehouse)[ i ][j-1].display = ' ';
+			(*warehouse)[i+1][j-1].display = ' ';
+			(*warehouse)[i-1][ j ].display = ' ';
+			(*warehouse)[i+1][ j ].display = ' ';
+			(*warehouse)[i-1][j+1].display = ' ';
+			(*warehouse)[ i ][j+1].display = ' ';
+			(*warehouse)[i+1][j+1].display = ' ';
+			break;
+		 default:
+			break;
+		}
+	}
+ /* Now move all the objects in priority order.  Conflicts within a
+ ** priority are undefined, so the left-to-right, top-to-bottom
+ ** approach to resolution won't bother anyone.
+ */
+ for (pri=5;pri>0;pri--)
+    for (j=1;j<y;j++)
+	for (i=1;i<x;i++)
+		if ((priority (&(*warehouse)[i][j]) == pri) &&
+		    ((*warehouse)[i][j].checked == 0))
+			moveThing (temp, *warehouse, i, j);
+ /* Update the new data. */
+ fillWarehouse (temp, x, y);
+ /* Destroy the old data. */
+ condemnWarehouse (*warehouse, x);
+ /* Update the pointer. */
+ *warehouse = temp;
+ return;
+}