import java.awt.*; import java.awt.geom.*; /** * The Boulder class represents an Asteroids-like boulder that has a trajectory as * well as a current position. It wraps around when it gets to the edge of the * screen -- or, at least, it will someday if it doesn't yet. It's based on the * book's Circle class, but with some extra fields and methods. * * @author Brad Richards */ public class Boulder { // These fields were all there in the Circle class private int diameter; private double xPosition; // This used to be an integer, but is now a double private double yPosition; // This used to be an integer, but is now a double private String color; private boolean isVisible; // These new fields keep track of how quickly the boulder is moving private double xVelocity; // How far it moves horizontally between steps private double yVelocity; // How far it moves vertically between steps // We added this constant in class. It helps make our code more legible, // and easier to change later. As a style thing, we make the names of // constants all upper case. private final int MAX_SPEED = 50; /** * Create a new boulder at specified position with specified velocity and size. * * @param x Starting x position * @param y Starting y position * @param dx How many pixels it moves horizontally per update step * @param dy How many pixels it moves vertically per update step * @param size The diameter of the boulder, in pixels */ public Boulder(double x, double y, double dx, double dy, int size) { diameter = size; xPosition = x; yPosition = y; xVelocity = dx; yVelocity = dy; color = "black"; isVisible = false; } /** * Displays information about the Canvas object, and about our own state. Note that * the Canvas is a separate object, and to interact with it we need to use the . operator * and run a "getter" method on that object to learn about its state. When referring to * our "own" state, we can just use the names of our fields in the print statement. * (The default is that we're talking about our own fields and/or methods if we don't * say anything about who we're "bossing around".) */ public void printStatus() { System.out.println("Boulder is on a canvas that's "+Canvas.getWidth()+" by "+Canvas.getHeight()); System.out.println("It's at "+xPosition+", "+yPosition+" with velocity "+ xVelocity+", "+yVelocity); // Here's one way to check for the three separate warning cases. // It matches the "decision tree" diagram on the lab. if (Math.abs(xVelocity) > MAX_SPEED && Math.abs(yVelocity) > MAX_SPEED) { System.out.println("Both the horizontal and vertical speeds are high!"); } else { if (Math.abs(xVelocity) > MAX_SPEED) { System.out.println("Horizontal speed is pretty high!"); } else { if (Math.abs(yVelocity) > MAX_SPEED) { System.out.println("Vertical speed is pretty high!"); } } } // This was added in the next step. We ALWAYS want to do this check // and print a line of output, regardless of the warnings above, so // this is a separate IF statement instead of being tucked into the // code above or joined on with an ELSE. We use an else between the // two cases here since we always want to do one or the other of // the two output lines. if (isVisible) { System.out.println("The Boulder is visible"); } else { System.out.println("The Boulder is not visible"); } } /** * Each time this method is called, it calculates a new position for the * boulder using the x and y velocities. (It adds the xVelocity to the * current x position to get an updated position, and does the same for y.) */ public void updatePosition() { erase(); // Adjust the x and y positions based on velocities xPosition = xPosition + xVelocity; yPosition = yPosition + yVelocity; // If we've gone off left or right, wrap around. // I put the check for the right edge in the the ELSE // part since they can't *both* be true -- I can't be // off the left edge AND off the right edge at the same // time, so I only need to handle one or the other. if (xPosition < 0) { xPosition = xPosition + Canvas.getWidth(); } else { if (xPosition > Canvas.getWidth()) { xPosition = xPosition - Canvas.getWidth(); } } // If we've gone off the top or bottom, wrap around. Same // logic here with the ELSE, but note that this entire IF // is *outside* of the IF above. We need to run BOTH of them // each time we update the boulder's position since the two // are independent -- I always have to consider running off // the top or bottom, no matter what happened above with the // left and right edges. if (yPosition < 0) { yPosition = yPosition + Canvas.getHeight(); } else { if (yPosition > Canvas.getHeight()) { yPosition = yPosition - Canvas.getHeight(); } } // At this point we've computed our new position, and "fixed" // it if we went off the edge. Time to see what color the // Boulder should be. if (xPosition < 100 || Canvas.getWidth()-xPosition < 100 || yPosition < 100 || Canvas.getHeight()-yPosition < 100) { changeColor("red"); } else { changeColor("black"); } // Draw us in our new position draw(); } /** * Takes a reference to another Boulder object and returns true * if two out of three of the following are the same: The * xPosition, yPosition, and diameter. */ public boolean similar(Boulder other) { if (diameter == other.diameter) { if (xPosition == other.xPosition || yPosition == other.yPosition) { return true; } else { return false; } } else { // Other two might still match if (xPosition==other.xPosition && yPosition==other.yPosition) { return true; } else { return false; } } } /** * This version glues together all of the possible ways to "succeed" into a * single test. */ public boolean similar2(Boulder other) { if ( (diameter == other.diameter && xPosition == other.xPosition) || (diameter == other.diameter && yPosition == other.yPosition) || (xPosition == other.xPosition && yPosition == other.yPosition)) { return true; } else { return false; } } /** * Turns out we don't even need the IF anymore! Return the value of the * boolean expression -- it evaluates to true when we want to return true, etc. */ public boolean similar3(Boulder other) { return ( (diameter == other.diameter && xPosition == other.xPosition) || (diameter == other.diameter && yPosition == other.yPosition) || (xPosition == other.xPosition && yPosition == other.yPosition)); } /** * This method checks whether we overlap with another Boulder. It calculates the * distance between our center and theirs, and compares that to the sum of our * radius and their radius. * * @param other The boulder we're comparing ourselves to * @return True if we overlap with other and don't have the same parent */ public boolean overlaps(Boulder other) { double distance; // Distance is sqrt of the x distance squared + y distance squared distance = Math.sqrt( Math.pow(xPosition-other.xPosition,2) + Math.pow(yPosition-other.yPosition,2)); return distance < (this.diameter/2 + other.diameter/2); } // ------------ Methods borrowed from Circle from here on down -------------- /** * Make this boulder visible. If it was already visible, do nothing. */ public void makeVisible() { isVisible = true; draw(); } /** * Make this boulder invisible. If it was already invisible, do nothing. */ public void makeInvisible() { erase(); isVisible = false; } /** * Change the size to the new size (in pixels). Size must be >= 0. */ public void changeSize(int newDiameter) { erase(); diameter = newDiameter; draw(); } /** * Change the color. Valid colors are "red", "yellow", "blue", "green", * "magenta" and "black". */ public void changeColor(String newColor) { color = newColor; draw(); } /** * Draw the boulder with current specifications on screen. */ private void draw() { if(isVisible) { Canvas canvas = Canvas.getCanvas(); canvas.draw(this, color, new Ellipse2D.Double(xPosition, yPosition, diameter, diameter)); } } /** * Erase the boulder on screen. */ private void erase() { if(isVisible) { Canvas canvas = Canvas.getCanvas(); canvas.erase(this); } } }