Exercises - Abstract Classes

Some of the exercises below provide sample runs to better clarify what the program in question is supposed to do. In these sample runs, text given in green has been typed by the user, while white text has been output by the program. Additionally, the "$" symbol indicates the command prompt, while the "↵" symbol indicates the user has pressed the return key.

Exercises identified by the ACM logo (shown at left) require one or more of the following libraries:
acm.jar, acm.breadboards.jar, or acm.toys.jar.

  1. Write a class named Space that extends the OneButtonBreadboard class where one guides an alien spaceship through a series of ascending planes. To support this main class, write additional classes named Spaceship, Plane, and FlyingObject.

    The spaceship should initially appear on the left side of the screen, both moving at a constant rate to the right and beginning to fall. The spaceship should accelerate as it falls (as if under the force of gravity). When the user clicks the "Fire Rockets!" button, the spaceship should accelerate upwards for a fixed period of time, before gravity takes over again. If the spaceship makes it to the right side of the screen, it should wrap around and re-appear on the left side.

    Meanwhile, five planes set at random heights and moving at random, but constant speeds should rise from the bottom of the screen until they disappear at the top -- only to reappear again from the bottom again.

    If the spaceship gets too close to a plane, it should die -- freezing in place on the screen -- and an appropriate message (i.e., "You died.") should be displayed in the text area at the top of the breadboard window. The spaceship should also die if it gets too close to either the top or bottom of the screen.

    The instructions to the user should simply be: "Fire your rockets to dodge the planes, but don't go too high or too low, or you will die."

    Hints:

    • Use three GOvals and subclass a GCompound to create the Spaceship class.

    • Use the following picture (a PNG file with transparency) and a GCompound to create the Plane class.

    • Given the similarity between planes and spaceship (i.e., they both GCompounds that must be able to move on the canvas), make them both subclasses of an abstract class named FlyingObject

     
    
    import java.awt.Color;
    
    import acm.breadboards.OneButtonBreadboard;
    
    public class Space extends OneButtonBreadboard {
        
        public static final int BREADBOARD_WIDTH = 900;
        public static final int BREADBOARD_HEIGHT = 600;
        
        Spaceship spaceship;
        FlyingObject[] flyingObjects;
        
        public void run() {
            this.setSize(BREADBOARD_WIDTH,BREADBOARD_HEIGHT);
            this.setBackground(Color.BLACK);
            this.getTextArea().setText("Fire your rockets to dodge the planes, "
                                     + "but don't go too high or too low, or "
                                     + "you will die.");
            this.getButton().setText("Fire Rockets!");
            spaceship = new Spaceship(100,200);
            this.add(spaceship);
            spaceship.setVelocity(2, 0);
            spaceship.setAcceleration(0, 0.1);
            
            flyingObjects = new FlyingObject[6];
            flyingObjects[0] = spaceship;
            for (int i = 1; i < 6; i++) {
                flyingObjects[i] = new Plane(100 + 125*i, 100 * i % 300);
                flyingObjects[i].setVelocity(0, -1 - 2 *Math.random());
                this.add(flyingObjects[i]);
            }
            
            this.getTimer().setDelay(20);
            this.getTimer().start();
        }
        
        public void onButtonClick() {
            spaceship.lightRockets();
        }
        
        public void onTimerTick() {
            for (int i = 0; i < flyingObjects.length; i++) {
                flyingObjects[i].move();
            }
            
            if (spaceship.hasDied()) {
                this.getTextArea().setText("You died");
            }
            
            //check for collisions...
            for (int i = 1; i < flyingObjects.length; i++) {
                if (spaceship.getBounds().intersects(
      	                   flyingObjects[i].getBounds())) {
                    spaceship.kill();
                }
            }
        }
    }
    
    
    import acm.graphics.GCompound;
    
    public abstract class FlyingObject extends GCompound {
        
        private double vx;
        private double vy;
        private double ax;
        private double ay;
        
        public double getXVelocity() {
            return vx;
        }
        
        public double getYVelocity() {
            return vy;
        }
        
        public double getXAcceleration() {
            return ax;
        }
        
        public double getYAcceleration() {
            return ay;
        }
        
        public void setVelocity(double vx, double vy) {
            this.vx = vx;
            this.vy = vy;
        }
        
        public void setAcceleration(double ax, double ay) {
            this.ax = ax;
            this.ay = ay;
        }
        
        public abstract void move();
    }
    
    
    import java.awt.Color;
    
    import acm.graphics.GCompound;
    import acm.graphics.GOval;
    
    public class Spaceship extends FlyingObject {
    
        private GOval shipBody;
        private GOval bubble;
        private GOval alien;
        private boolean died;
        private int burnTimeLeft;
        
        public Spaceship(double x, double y) {
           shipBody = new GOval(-30,-15,60,30);
           shipBody.setFilled(true);
           shipBody.setFillColor(Color.RED);
           
           bubble = new GOval(-15,-30,30,30);
           bubble.setFilled(true);
           bubble.setFillColor(Color.WHITE);
           
           alien = new GOval(-5,-23,10,10);
           alien.setFilled(true);
           alien.setFillColor(Color.GREEN);
           
           this.add(bubble);
           this.add(alien);
           this.add(shipBody);
           
           this.setLocation(x,y);
        }
        
        public String toString() {
            return "spaceship at (" 
                   + this.getX() + ", " 
                   + this.getY() 
                   + ") with velocity ("  
                   + this.getXVelocity() + ", " 
                   + this.getYVelocity() + ")";
        }
        
        public boolean hasDied() {
            return this.died;
        }
        
        public void kill() {
            this.died = true;
        }
        
        public void move() {
            if (! died) {
                if (burnTimeLeft > 0) {
                    burnTimeLeft--;
                }
                else {
                    this.setAcceleration(0, 0.1);
                }
                
                double vx = this.getXVelocity();
                double vy = this.getYVelocity();
    			
                this.setLocation(this.getX() + vx, 
                                 this.getY() + vy);
    							 
                this.setVelocity(vx + this.getXAcceleration(), 
                                 vy + this.getYAcceleration());
                
                if (this.getX() > Space.BREADBOARD_WIDTH) {
                    this.setLocation(0,this.getY());
    				
                }
                if ((this.getY() < 0) || 
                    (this.getY() > Space.BREADBOARD_HEIGHT)) {
                    this.died = true;
                    this.setVelocity(0, 0);
                    this.setAcceleration(0, 0);
                }
            }
        }
        
        public void lightRockets() {
            burnTimeLeft = 10;
            this.setAcceleration(0, -0.2);
        }    
    }
    
    
    import acm.graphics.GCompound;
    import acm.graphics.GImage;
    
    public class Plane extends FlyingObject {
        
        private GImage image;
        
        public Plane(double x, double y) {
            image = new GImage("spaceship.png");
            this.add(image);
            this.setLocation(x,y);
        }
        
        public void move() {
            double vx = this.getXVelocity();
            double vy = this.getYVelocity();
            this.setLocation(this.getX() + vx, this.getY() + vy);
            
            if (this.getY() < -this.image.getHeight()) {
                this.setLocation(
                       this.getX(),
                       Space.BREADBOARD_HEIGHT + this.image.getHeight());
            }
        }
    }
    

  2. Write a class named FruitDrop that extends the NoButtonsBreadboard class of the acm.breadboards package and "rains" random fruit in the form of oranges, cherries, and apples, as suggested by the image below.

    All fruit should accelerate as they fall. Oranges and cherries should be composed of geometric objects available in the acm libraries (i.e., GOval, GRect, GArc, etc.) Apples should incorporate a GImage object (constructed using a file named "apple.png") so they can be more "photo-realistic". Feel free to use the following image to this end:

    Note: If one wanted to incorporate additional fruit, one would need to save any images created or found on the web as a ".png" file, add a transparency layer (this is easily done in the free photo editing software GIMP), and erase-to-transparency all of the area outside of the fruit, and save the changes. The modified image file should be saved in the bin directory inside the appropriate project folder of the relevant Eclipse workspace. One can then create a GImage object for the image by passing the name of the file to the GImage constructor.

    In writing this program, you should incorporate additional classes named Fruit, Orange, Cherries, and Apple, where the last three classes are subclasses of the Fruit class, and Fruit is a subclass of the GCompound class of the ACM libraries.

    Also, use the built in Timer object (which utilizes the onTimerTick() method) to control the animation.

    The methods getElementCount() and getElement(int i) can be used to work with elements you have previously added to the breadboard's canvas. You should investigate these in the API here, under the GraphicsProgram class.