// The Science of Computing robot class. // History: // June 2003 -- Created by Doug Baldwin. package geneseo.cs.sc; import java.awt.Graphics; import java.awt.Color; import java.awt.Point; /** * Represents simple simulated robots. These robots occupy rooms with walls and * tiled floors. Robots walk around on the floors (one tile at a time), changing * and sensing the colors of tiles if they wish. But robots can't move through * walls, thus they have to stay in their room. Robots can face in any of four * directions, which are described as compass directions in analogy to a map: * north is towards the top of the robot's room as drawn on a computer screen, * east is towards the right, etc. * @see geneseo.cs.sc.RobotRoom */ public class Robot { /** * The heading a robot has when facing north. */ public static final int NORTH = 0; /** * The heading a robot has when facing east. */ public static final int EAST = 1; /** * The heading a robot has when facing south. */ public static final int SOUTH = 2; /** * The heading a robot has when facing west. */ public static final int WEST = 3; // Internally, a robot records the room it is in, its row and column // coordinates (in tiles) within that room, its heading, whether // or not it is visible, and the amount of time it should delay // after movements: private RobotRoom room; private int row; private int col; private int direction; private boolean visible; private int delayTime; private static final int initialDelay = 500; // Number of milliseconds delay for new robots // Robots also have some data that they use to draw themselves: x and y offsets // of vertices of the robot relative to its center, and a color to draw it in // (see the comments for the "draw" method below for more details): private static final int[] xOffsets = { 0, -10, -5, -5, 5, 5, 10 }; private static final int[] yOffsets = { -10, 0, 0, 10, 10, 0, 0 }; private static final Color robotColor = new Color( (float)0.25, (float)0.0, (float)0.3 ); /** * Initialize a robot and a room for it to occupy. The room will be a default room, * and this robot will be its only occupant. The robot will have the default position * and heading (center of the room, facing north). For example *

Robot r = new Robot();

*/ public Robot() { room = new RobotRoom(); // A default room for this robot. col = room.getRoomWidth() / 2; row = room.getRoomHeight() / 2; direction = NORTH; visible = true; delayTime = initialDelay; room.addRobot( this, col, row ); } /** * Initialize a robot from its position, orientation, and room. For example *

Robot r = new Robot( 1, 3, Robot.NORTH, someRoom );

* @param col The horizontal coordinate of the tile this robot is on (i.e., the * tile column the robot is in). This must be within the range of columns * for the room the robot is in, and, with the row parameter, must specify * an unobstructed tile. * @param row The vertical coordinate of the tile this robot is on (i.e., the * tile row the robot is in). This must be within the range of rows for * the room the robot is in, and, with the column parameter, must specify * an unobstructed tile. * @param heading The robot's heading. This should be one of the four headings * defined for robots. * @param room The room the robot is in. */ public Robot( int col, int row, int heading, RobotRoom room ) { this.room = room; this.col = col; this.row = row; this.direction = heading; this.visible = true; this.delayTime = initialDelay; room.addRobot( this, col, row ); } /** * Move a robot one tile forward, unless the way is blocked by a wall or * other robot. If the way is blocked, the robot will flash briefly to * indicate that it can't move. For example *

r.move();

*/ public void move() { if ( this.okToMove() ) { Point next = this.nextTile(); room.moveRobot( col, row, next.x, next.y ); col = next.x; row = next.y; this.delay(); } else { this.flash(); } } /** * Find out whether a robot can move forward. A robot can move whenever * the tile in front of it doesn't contain a wall or another robot. * For example *

if ( r.okToMove() ) {...

* @return True if the robot can move, false if moving would cause the * robot to collide with a wall or another robot. */ public boolean okToMove() { Point next = this.nextTile(); return ! room.isObstructed( next.x, next.y ); } /** * Turn a robot 90 degrees to its left (i.e., counterclockwise about * its center). For example *

r.turnLeft();

*/ // I do a 90-degree left turn as 3 90-degree right turns, 'though done // all at once so the user doesn't see distinct turns. public void turnLeft() { this.turn( 3 ); } /** * Turn a robot 90 degrees to its right (i.e., clockwise about its center). * For example *

r.turnRight();

*/ public void turnRight() { this.turn( 1 ); } /** * Change the color of the tile under a robot. For example *

r.paint( java.awt.Color.yellow );

* @param newColor The color the tile changes to. */ public void paint( Color newColor ) { room.setColor( col, row, newColor ); } /** * Find out what color the tile under a robot is. For example *

if ( r.colorOfTile() == java.awt.Color.red ) {...

* @return The color of the tile under the robot. */ public Color colorOfTile() { return room.getColor( col, row ); } /** * Find out what direction a robot is facing. For example *

if ( r.heading() == Robot.NORTH ) {...

* @return The robot's direction, encoded as one of the values * Robot.NORTH, Robot.EAST, * Robot.SOUTH, or Robot.WEST. */ public int heading() { return direction; } /** * Set the speed at which a robot moves and turns. For example *

r.setSpeed( 100 );

* @param speed An integer between 1 and 1000, which indicates approximately * how many moves or turns per second the robot should do. Values below 1 * or above 1000 are treated as if they were 1 and 1000, respectively. */ public void setSpeed( int speed ) { if ( speed < 1 ) { speed = 1; } if ( speed > 1000 ) { speed = 1000; } delayTime = 1000 / speed; } /** * Draw a robot. * @param context The graphics context in which to draw. * @param x The horizontal coordinate at which to place the robot's center. * @param y The vertical coordinate at which to place the robot's center */ // The robot is a polygon, filled with a robot color not likely to be // generated by a student -- right now, a dark purple. I define the polygon // by giving lists of X and Y offsets for its vertices from the center of // the robot, when the robot is facing north. For other orientations, I // rotate these offsets using the standard equations for 2D rotation // (x' = x cos(a) + y sin(a); y' = -x sin(a) + y cos(a)), keeping in mind // that the rotations are all multiples of 90 degrees, so the sines and cosines // are all -1, 1, or 0. To actually draw the robot, I add the rotated offsets // to the center coordinates provided as parameters, and use the results as // the vertices of a filled polygon. protected void draw( Graphics context, int x, int y ) { if ( visible ) { int sine; // Will hold the sine of the rotation angle int cosine; // Will hold the cosine of the rotation angle switch ( direction ) { case NORTH: // no rotation sine = 0; cosine = 1; break; case WEST: // 90 degrees sine = 1; cosine = 0; break; case SOUTH: // 180 degrees sine = 0; cosine = -1; break; case EAST: // -90 degrees sine = -1; cosine = 0; break; default: // Invalid rotations act like no rotation sine = 0; cosine = 1; break; } int[] xVertices = new int[ xOffsets.length ]; int[] yVertices = new int[ yOffsets.length ]; for ( int i = 0; i < xOffsets.length; i++ ) { xVertices[i] = x + cosine * xOffsets[i] + sine * yOffsets[i]; yVertices[i] = y - sine * xOffsets[i] + cosine * yOffsets[i]; } context.setColor( robotColor ); context.fillPolygon( xVertices, yVertices, xVertices.length ); } } /** * Find out the tile coordinates of the tile a robot would move to if it moved * forward (assuming that move is permitted). * @return A Point containing the tile column (x coordinate) and row (y * coordinate) of the tile the robot would move to. */ private Point nextTile() { switch ( direction ) { case NORTH: return new Point( col, row - 1 ); case EAST: return new Point( col + 1, row ); case SOUTH: return new Point( col, row + 1 ); case WEST: return new Point( col - 1, row ); default: // Treat invalid headings as if they were north return new Point( col, row - 1 ); } } /** * Pause. This is useful in order to slow down robot programs enough that users * can see where their robot is moving, for visual debugging. */ private void delay() { try { Thread.sleep( delayTime ); } catch ( InterruptedException interrupt ) { } } /** * Make a robot flash on the screen. Typically used to signal some error, e.g., * running into a wall or other robot. */ // This works by toggling a "visible" flag in the robot, while redrawing its // room. A pause between redraws ensures that the user's brain has time to // process the alternately visible and invisible robots as "flashing". private void flash() { final int flashes = 6; // The number of times to toggle the robot final int flashDelay = 80; // Milliseconds to wait between toggles for ( int i = 0; i < flashes; i++ ) { visible = ! visible; room.repaint(); try { Thread.sleep( flashDelay ); } catch ( InterruptedException interrupt ) { } } } /** * Turn a robot an arbitrary number of 90-degree steps clockwise. * @param steps The number of 90-degree steps to turn. */ // Since directions are represented by integers in the range 0 to 3, // increasing clockwise from north, I turn by simply adding the number // of steps to the current direction, and taking the result mod 4 to keep // it a valid direction. private void turn( int steps ) { direction = ( direction + steps ) % 4; room.repaint(); this.delay(); } }