import java.awt.*; import java.io.*; import java.util.*; /**

This class defines the graphics operations, and creates a window environment for an arbitrary number of Spider objects. The class name, "Room", refers to the container of Feldman's "Spider graphics" programs developed in the textbook "Ada 95".

To use Room and Spider objects in Java programs, the programmer will typically instantiate Room first, then install the Room object in a Frame. New Spider instances can be created at any time. Sample code for these three steps may look like this:

    int    width    = 400;   // pixels
    int    height   = 300;
    Room   barn     = new Room();
    Frame  myWindow = new AppFrame("My Room", barn, width, height);
    Spider itzy     = new Spider();
   

After setup, the methods defined here and in the Spider class can be used programmatically in Java code, to effect graphic operations in the target Room (or multiple Rooms), in the sequence specified. Although it would be possible to run individual spiders asynchronously as threads, only the synchronous operational mode is developed here. Spiders may be shared naturally, among different Room objects installed in a Frame, or even between different Frames instantiated within a program.

Although the Room class defines a main() method, other programs will not be calling Room.main(). This method serves as a builtin test program that can be run from the command line with

    java Room
   

In this context main() implements an interactive command loop, allowing the programmer to select simple one-shot graphics commands on behalf of one of six spiders, and then executing the selected operation in the only Room instance created in main(). The code of the main() method may also be useful as an example for writing other Spider graphics programs.

The AppFrame object in creating a Room, allows proper bootstrapping of the Graphics object (context), as is explained in the API of AppFrame. By subclassing Room from Applet, we make use of the Applet.init() method; this lets us call getGraphics() only after the Room object is safely installed in its Frame. @author Michael B. Feldman, The George Washington University (Ada 95) @author Istvan Mohos (Java translation) @see AppFrame @see Spider */ public class Room extends java.applet.Applet { /** The spider faces or moves toward the top wall of the room */ public static final int NORTH = 0; /** The spider faces or moves toward the right wall of the room */ public static final int EAST = 1; /** The spider faces or moves toward the bottom wall of the room */ public static final int SOUTH = 2; /** The spider faces or moves toward the left wall of the room */ public static final int WEST = 3; /** The Graphics object, context or handle to use for drawing in the room */ public Graphics mySurface; /** The width and height of the drawing area, in pixels */ public Dimension myFullSize; // Case labels for command decoding in this.main() private static final int CRAWL = 0; private static final int JUMPTO = 1; private static final int JUMPTOMIDDLE = 2; private static final int SHOWME = 3; private static final int SETHEADING = 4; private static final int TURNRIGHT = 5; private static final int TURNLEFT = 6; private static final int SETSILK = 7; // These dimensions are set by init(), and don't change thereafter. private int roomWide; // usable drawing width, pixels private int roomHigh; // usable drawing height, pixels private int northWall; // y ordinate private int eastWall; // x ordinate private int southWall; // y ordinate private int westWall; // x ordinate private int middleX; private int middleY; /** This is the builtin test program of the Room class. When starting Room.class as a standalone application, the Java interpreter first calls Room.main() --- much as if the code made a call to any static method, for example Math.min() --- without instantiating a Room object. As its first order of business, this.main() creates a new Room object, "inHere", and then passes this object as an Applet, to the AppFrame constructor method which creates a Frame object and installs inHere as a component in this Frame. Because we never again need the handle of this Frame, we do not assign or keep the reference returned by

       new AppFrame(myTitle,inHere, width, height);
@param args is the String array expected by the interpreter; not used here. @exception java.io.IOException on input error (when, for example, input is redirected from a file, and an End-of-File condition is detected). The exception is not caught here, and will terminate the program. */ public static void main(String[] args) throws IOException { DataInputStream lineStream = new DataInputStream(System.in); StreamTokenizer myTokens = new StreamTokenizer(System.in); String myLine; int width = 0; // horizontal size of window, in pixels int height = 0; // vertical size of window, in pixels // Acquire the horizontal and vertical window sizes and the title while (width < 50) { System.out.print("Input the width of the window (pixels, min. 50): "); System.out.flush(); myTokens.nextToken(); width = (int)myTokens.nval; } while (height < 50) { System.out.print("Input the height of the window (pixels, min. 50): "); System.out.flush(); myTokens.nextToken(); height = (int)myTokens.nval; } System.out.print ("Input the window's title (no need to quote): "); System.out.flush(); String myTitle = lineStream.readLine(); System.out.println("\n" + "This builtin test program has only one window (frame) as a home for\n" + "six spiders: itzy, bitzy, harry, arancha, woody, goldie (names and\n" + "commands are case sensitive). They move and spin their webs at your\n" + "command. All commands begin with the spider's name followed by the\n" + "desired action, in some cases followed by required arguments. Type\n" + "your command at the prompt, followed by ENTER. Click on the 'close'\n" + "button of the spider's window frame to quit. If your input is not\n" + "perfect, you will be reminded of the syntax, by a menu. But first,\n" + "move and resize your control window now, to make space for the\n" + "window (frame); then expose the window (frame).\n"); Room inHere = new Room(); new AppFrame(myTitle,inHere, width, height); Spider itzy = new Spider(); Spider bitzy = new Spider(200, 50, SOUTH, Color.blue, Color.cyan); Spider harry = new Spider( 50, 50, WEST, Color.gray, Color.gray); Spider arancha = new Spider(100, 150, EAST, Color.red, Color.magenta); Spider woody = new Spider(100, 250, NORTH, Color.black, Color.black); Spider goldie = new Spider(100, 250, EAST, Color.orange, Color.yellow); System.out.print("Press ENTER to start your spiders: "); System.out.flush(); myLine = lineStream.readLine(); boolean malformedInput = true; String name; // name of spider input Spider which; // which spider to command int targetX; int targetY; String menu = "\n" + " COMMAND EXAMPLES: COLOR CHOICES: \n" + "-------------------------- ----------------- \n" + "arancha crawl 39 black orange \n" + "bitzy jumpTo 155 100 blue pink \n" + "goldie jumpToMiddle cyan red \n" + "harry showMe darkGray white \n" + "itzy setHeading NORTH gray yellow \n" + "woody turnRight green \n" + "arancha turnLeft lightGray \n" + "bitzy setSilk blue magenta \n"; int commandID; Hashtable colors = new Hashtable(); colors.put("black", Color.black); colors.put("blue", Color.blue); colors.put("cyan", Color.cyan); colors.put("darkGray", Color.darkGray); colors.put("gray", Color.gray); colors.put("green", Color.green); colors.put("lightGray", Color.lightGray); colors.put("magenta", Color.magenta); colors.put("orange", Color.orange); colors.put("pink", Color.pink); colors.put("red", Color.red); colors.put("white", Color.white); colors.put("yellow", Color.yellow); Color chosenColor; // This redraws the "walls" of the room which probably got erased while // the System window was moved and resized. inHere.mySurface.drawRect(5, 5, inHere.roomWide - 10, inHere.roomHigh - 10); inHere.jumpToMiddle(itzy); // itzy otherwise gets lost in top left corner while (true) { if (malformedInput) { System.out.println(menu); malformedInput = false; } System.out.print("Command: "); System.out.flush(); StringTokenizer strtoks = new StringTokenizer(lineStream.readLine()); // Decode the spider name chosen if (!strtoks.hasMoreTokens()) continue; // not setting malformedInput on an empty line name = strtoks.nextToken(); if (name.equals("arancha")) which = arancha; else if (name.equals("bitzy")) which = bitzy; else if (name.equals("goldie")) which = goldie; else if (name.equals("harry")) which = harry; else if (name.equals("itzy")) which = itzy; else if (name.equals("woody")) which = woody; else { malformedInput = true; continue; // the infinite while-loop } // Decode the command chosen if (!strtoks.hasMoreTokens()) { malformedInput = true; continue; // the infinite while-loop } name = strtoks.nextToken(); if (name.equals("crawl")) commandID = CRAWL; else if (name.equals("jumpTo")) commandID = JUMPTO; else if (name.equals("jumpToMiddle")) commandID = JUMPTOMIDDLE; else if (name.equals("showMe")) commandID = SHOWME; else if (name.equals("setHeading")) commandID = SETHEADING; else if (name.equals("turnRight")) commandID = TURNRIGHT; else if (name.equals("turnLeft")) commandID = TURNLEFT; else if (name.equals("setSilk")) commandID = SETSILK; else { malformedInput = true; continue; // the infinite while-loop } // Execute the command switch (commandID) { default: System.out.println("Programming error at " + name); System.exit(1); break; case CRAWL: if (!strtoks.hasMoreTokens()) { malformedInput = true; continue; // the infinite while-loop } inHere.crawl(which, Integer.parseInt(strtoks.nextToken())); break; case JUMPTO: if (strtoks.countTokens() < 2) { malformedInput = true; continue; // the infinite while-loop } targetX = Integer.parseInt(strtoks.nextToken()); targetY = Integer.parseInt(strtoks.nextToken()); inHere.jumpTo(which, targetX, targetY); break; case JUMPTOMIDDLE: inHere.jumpToMiddle(which); break; case SHOWME: inHere.showMe(which); break; case SETHEADING: if (!strtoks.hasMoreTokens()) { malformedInput = true; continue; // the infinite while-loop } if ((name = strtoks.nextToken()).equals("NORTH")) which.setHeading(NORTH); else if (name.equals("EAST")) which.setHeading(EAST); else if (name.equals("SOUTH")) which.setHeading(SOUTH); else if (name.equals("WEST")) which.setHeading(WEST); else { malformedInput = true; continue; // the infinite while-loop } break; case TURNRIGHT: which.turnRight(); break; case TURNLEFT: which.turnLeft(); break; case SETSILK: if (!strtoks.hasMoreTokens()) { malformedInput = true; continue; // the infinite while-loop } name = strtoks.nextToken(); if ((chosenColor = (Color)colors.get(name)) == null) { malformedInput = true; continue; // the infinite while-loop } which.setSilk(chosenColor); break; } // end of the command-execute switch } // end of infinite interpretive while-loop } // end definition of main() method /** This method overrides java.applet.Applet.init() to perform initialization once the Graphics object (graphics context) of the drawable surface has been established. For an applet program, this method would be called by the browser or the "appletviewer" to inform the applet that it has been loaded into the system. Here, instead of a browser or appletviewer, the constructor method of AppFrame (a subclass of Frame) calls this.init(), when the constructor has already sized and shown the new Frame. */ public void init() { mySurface = getGraphics(); myFullSize = size(); // method in Component roomWide = myFullSize.width; roomHigh = myFullSize.height; northWall = 5; // y coordinate eastWall = roomWide - 5; // x coordinate southWall = roomHigh - 5; // y coordinate westWall = 5; // x coordinate middleX = roomWide / 2; middleY = roomHigh / 2; mySurface.drawRect(5, 5, roomWide - 10, roomHigh - 10); repaint(); } /** This method overrides java.awt.Component.update() to prevent it from clearing the background every time it calls paint(). @param mySurface is the current Graphics object (graphics context) of this room. @see java.awt.Component.update */ public void update(Graphics mySurface) { paint(mySurface); } /** This method draws a Spider object at its current coordinates in the room. The spider is shaped like a letter M, with a small circle for the spider's belly at the point where the slanted inner lines meet. The four lines that represent the (front) legs of the spider are always in the foreground color (black), the belly color is specific to the spider. The spider is oriented upright (like the letter M) if its heading is NORTH, it is drawn like the letter W if its heading is SOUTH. A spider headed WEST looks like a letter M turned on its left side; a spider headed EAST looks like a letter M turned on its right side.

To be able to show a spider and then erase it, a spider is always drawn in the XOR mode. A second call to draw the same spider (in the same place and headed the same way as before) will erase the previously drawn spider. @param which selects a particular spider (object) to show. @param mySurface is the Graphics handle for drawing operations. */ public void drawSpider(Spider which, Graphics mySurface) { Point at = which.getXY(); int cX = at.x; int cY = at.y; int heading = which.getHeading(); Color belly = which.getBelly(); mySurface.setColor(Color.black); // set up the color pen mySurface.setXORMode(this.getBackground()); // erases the second time switch (heading) { case NORTH: mySurface.drawLine(cX - 5, cY + 3, cX - 3, cY - 9); // left leg mySurface.drawLine(cX - 3, cY - 9, cX, cY); // left thigh mySurface.drawLine(cX + 5, cY + 3, cX + 3, cY - 9); // right leg mySurface.drawLine(cX + 3, cY - 9, cX, cY); // right thigh break; case EAST: mySurface.drawLine(cX - 3, cY - 5, cX + 9, cY - 3); // left leg mySurface.drawLine(cX + 9, cY - 3, cX, cY); // left thigh mySurface.drawLine(cX - 3, cY + 5, cX + 9, cY + 3); // right leg mySurface.drawLine(cX + 9, cY + 3, cX, cY); // right thigh break; case SOUTH: mySurface.drawLine(cX - 5, cY - 3, cX - 3, cY + 9); // right leg mySurface.drawLine(cX - 3, cY + 9, cX, cY); // right thigh mySurface.drawLine(cX + 5, cY - 3, cX + 3, cY + 9); // left leg mySurface.drawLine(cX + 3, cY + 9, cX, cY); // left thigh break; case WEST: mySurface.drawLine(cX + 3, cY + 5, cX - 9, cY + 3); // left leg mySurface.drawLine(cX - 9, cY + 3, cX, cY); // left thigh mySurface.drawLine(cX + 3, cY - 5, cX - 9, cY - 3); // right leg mySurface.drawLine(cX - 9, cY - 3, cX, cY); // right thigh break; } mySurface.setColor(belly); // color of belly mySurface.fillOval(cX - 2, cY - 2, 5, 5); // body mySurface.setPaintMode(); // no more XOR } // end definition of drawSpider() method /** This method advances a spider in its current direction and draws a trail of colored "silk" to mark the spider's path. The spider's silk is not allowed to get beyond the bounding box of the "walls" of the room (drawn as a rectangle 5 pixels inside the drawable surface), although the body of the spider may protrude beyond the walls. The silk trail will not be made visible until the next repaint() operation which may be triggered by events or by calls to showMe() or showRoom(). @param which selects a particular spider (object) to advance. @param howFar specifies the maximum distance (in pixels) to advance the spider. The actual distance moved may be less, if the spider hits a wall. Negative values are silently converted to absolute values. @return the new X and Y coordinates of the spider, as a Point object. @see showMe @see showRoom @see java.awt.Component.repaint */ public Point crawl(Spider which, int howFar) { howFar = Math.abs(howFar); // disallow negative advancement int targetX; int targetY; Point at = which.getXY(); int currentX = at.x; int currentY = at.y; int heading = which.getHeading(); switch (heading) { case NORTH: default: if (currentY - howFar < northWall) targetY = northWall; else targetY = currentY - howFar; targetX = currentX; break; case EAST: if (currentX + howFar > eastWall) targetX = eastWall; else targetX = currentX + howFar; targetY = currentY; break; case SOUTH: if (currentY + howFar > southWall) targetY = southWall; else targetY = currentY + howFar; targetX = currentX; break; case WEST: if (currentX - howFar < westWall) targetX = westWall; else targetX = currentX - howFar; targetY = currentY; break; } // end of switch(heading) Color ink = which.getSilk(); mySurface.setColor(ink); // set up the color pen mySurface.drawLine(currentX, currentY, targetX, targetY); which.setXY(targetX, targetY); return(new Point(targetX, targetY)); } // end definition of crawl() method /** This method advances a spider in its current direction biased by a positive or negative offset, and draws a trail of colored "silk" to mark the spider's path. The spider's silk is not allowed to get beyond the bounding box of the "walls" of the room (drawn as a rectangle 5 pixels inside the drawable surface), although the body of the spider may protrude beyond the walls. The silk trail will not be made visible until the next repaint() operation which may be triggered by events or by calls to showMe() or showRoom(). @param which selects a particular spider (object) to advance. @param howFar specifies the maximum distance (in pixels) to advance the spider in its current direction. The actual distance moved may be less, if the spider hits a wall. Negative values are silently converted to absolute values. @param offset a positive or negative displacement (in pixels) at right angles to the direction of travel. The actual displacement is bounded by the surrounding walls. @return the new X and Y coordinates of the spider, as a Point object. @see showMe @see showRoom @see java.awt.Component.repaint */ public Point veer(Spider which, int howFar, int offset) { howFar = Math.abs(howFar); // disallow negative advancement int targetX; int targetY; Point at = which.getXY(); int currentX = at.x; int currentY = at.y; int heading = which.getHeading(); Color ink = which.getSilk(); switch (heading) { case NORTH: default: if (currentY - howFar < northWall) targetY = northWall; else targetY = currentY - howFar; targetX = currentX + offset; if (targetX < westWall) targetX = westWall; else if (targetX > eastWall) targetX = eastWall; break; case EAST: if (currentX + howFar > eastWall) targetX = eastWall; else targetX = currentX + howFar; targetY = currentY + offset; if (targetY < northWall) targetY = northWall; else if (targetY > southWall) targetY = southWall; break; case SOUTH: if (currentY + howFar > southWall) targetY = southWall; else targetY = currentY + howFar; targetX = currentX + offset; if (targetX < westWall) targetX = westWall; else if (targetX > eastWall) targetX = eastWall; break; case WEST: if (currentX - howFar < westWall) targetX = westWall; else targetX = currentX - howFar; targetY = currentY + offset; if (targetY < northWall) targetY = northWall; else if (targetY > southWall) targetY = southWall; break; } // end of switch(heading) mySurface.setColor(ink); // set up the color pen mySurface.drawLine(currentX, currentY, targetX, targetY); which.setXY(targetX, targetY); return(new Point(targetX, targetY)); } // end definition of crawl() method /** This method returns "true" if the spider is at a wall and faces the wall, it returns "false" otherwise. @param which selects a particular spider (object) to test. @return true if the spider is at a wall and faces the wall, false otherwise. */ public boolean isAtWall(Spider which) { Point at = which.getXY(); int currentX = at.x; int currentY = at.y; int heading = which.getHeading(); boolean isAtWall; if ((currentX == westWall && heading == WEST) || (currentX == eastWall && heading == EAST) || (currentY == northWall && heading == NORTH) || (currentY == southWall && heading == SOUTH)) isAtWall = true; else isAtWall = false; return isAtWall; } /** This method relocates a spider to the specified X, Y coordinates within the room, without leaving a silk trail. The spider is never allowed to breach the walls; out of bounds values are silently adjusted to the nearest wall. @param which selects a particular spider (object) to relocate. @param x is the horizontal (pixel) ordinate of the new position. @param y is the vertical (pixel) ordinate of the new position. @return the new X and Y coordinates of the spider, as a Point object. */ public Point jumpTo(Spider which, int x, int y) { int newX; int newY; if (x < 0) newX = 0; else newX = Math.min(roomWide, x); if (newX < westWall) newX = westWall; else if (newX > eastWall) newX = eastWall; if (y < 0) newY = 0; else newY = Math.min(roomHigh, y); if (newY < northWall) newY = northWall; else if (newY > southWall) newY = southWall; which.setXY(newX, newY); return (new Point(newX, newY)); } /** This method relocates a spider to the specified X, Y coordinates within the room, without leaving a silk trail. The spider is never allowed to breach the walls; out of bounds values are silently adjusted to the nearest wall. @param which selects a particular spider (object) to relocate. @param newSpot is the Point representing the new position. @return the new X and Y coordinates of the spider, as a Point object (which will be different from the Point passed in, if the passed Point was out of bounds). */ public Point jumpTo(Spider which, Point newSpot) { int newX = Math.min(roomWide, newSpot.x); if (newX < westWall) newX = westWall; else if (newX > eastWall) newX = eastWall; int newY = Math.min(roomHigh, newSpot.y); if (newY < northWall) newY = northWall; else if (newY > southWall) newY = southWall; which.setXY(newX, newY); return (new Point(newX, newY)); } /** This method relocates a spider to the specified X, Y coordinates within the room, leaving a silk trail. The spider is never allowed to breach the walls; out of bounds values are silently adjusted to the nearest wall. @param which selects a particular spider (object) to relocate. @param x is the horizontal (pixel) ordinate of the new position. @param y is the vertical (pixel) ordinate of the new position. @return the new X and Y coordinates of the spider, as a Point object. */ public Point drawTo(Spider which, int x, int y) { Point at = which.getXY(); int currentX = at.x; int currentY = at.y; int newX; int newY; if (x < 0) newX = 0; else newX = Math.min(roomWide, x); if (newX < westWall) newX = westWall; else if (newX > eastWall) newX = eastWall; if (y < 0) newY = 0; else newY = Math.min(roomHigh, y); if (newY < northWall) newY = northWall; else if (newY > southWall) newY = southWall; Color ink = which.getSilk(); mySurface.setColor(ink); // set up the color pen mySurface.drawLine(currentX, currentY, newX, newY); which.setXY(newX, newY); return (new Point(newX, newY)); } /** This method relocates a spider to the specified X, Y coordinates within the room, leaving a silk trail. The spider is never allowed to breach the walls; out of bounds values are silently adjusted to the nearest wall. @param which selects a particular spider (object) to relocate. @param newSpot is the Point representing the new position. @return the new X and Y coordinates of the spider, as a Point object (which will be different from the Point passed in, if the passed Point was out of bounds). */ public Point drawTo(Spider which, Point newSpot) { Point at = which.getXY(); int currentX = at.x; int currentY = at.y; int newX = Math.min(roomWide, newSpot.x); if (newX < westWall) newX = westWall; else if (newX > eastWall) newX = eastWall; int newY = Math.min(roomHigh, newSpot.y); if (newY < northWall) newY = northWall; else if (newY > southWall) newY = southWall; Color ink = which.getSilk(); mySurface.setColor(ink); // set up the color pen mySurface.drawLine(currentX, currentY, newX, newY); which.setXY(newX, newY); return (new Point(newX, newY)); } /** This method relocates a spider to the center of the room. @param which selects a particular spider (object) to relocate. @return the new X and Y coordinates of the spider, as a Point object. */ public Point jumpToMiddle(Spider which) { which.setXY(middleX, middleY); return (new Point(middleX, middleY)); } /** This method displays a spider and updates the room so that all silk trails laid since the last repaint() become visible. ShowMe is really just a wrapper for calling drawSpider() followed by java.awt.Component.repaint(). @param which selects a particular spider (object) to display. @see java.awt.Component.repaint */ public void showMe(Spider which) { drawSpider(which, mySurface); repaint(); } /** This method displays all silk trails laid since the last call to repaint(), showMe() or showRoom(). showRoom is really just a wrapper for java.awt.Component.repaint(), to keep the method names of class Room consistent. @see showMe @see showRoom @see java.awt.Component.repaint */ public void showRoom() { repaint(); } } // end definition of class Room