/* * Lab 5, Part 2 - UFO Game * * For this lab you will be working on writing a UFO game. The * UFO will fall from the top of the screen, the player will control * a tank at the bottom of the screen attempting to shoot it, and * the game will end if the UFO is shot or if it lands. */ /* * First of all, we need to import some classes that are built into * ProfessorJ. */ import draw.*; /* This includes the following: * * The class Posn, an (x,y) location. * * The class World, which handles things like drawing, events, * etc. You'll create a subclass of World later in this lab. * * The class Canvas, which handles the actual drawing of shapes on * the screen. * * The Color class is an abstract class from which all the * various color classes (Blue, Red, etc.) are derived. * The color classes are created like so: * * Color c = new Blue(); * * Your choices are Red, White, Green, Blue, and Black. * * You won't need to use Random directly in this lab as we'll * be giving you code that uses it. */ // We need random for the UFO's horizontal movement. import java.util.Random; /* * To get you started, we provide the UFO class and the corresponding * manipulation of the World and Canvas to draw the UFO. If you run * this lab without any modifications, you will see a UFO decending * from the sky. * * From here, you will need to add a Tank and a list of Shots, which * come from the tank and travel up toward the UFO. * * First you should create the class to represent a Tank. * Like UFOs, Tanks have a position in the game world, * and that's all the information it needs to store. * * You also need to add a move() method that takes the * distances to move the Tank in the x and y directions and * also the size of the world (to do checks in case the tank or * ufo is moving outside the world, in which case it should stop * moving in that direction). See the UFO move() method as an example. * * You should also write a same() method for each in order to test * the move() method (and others later) appropriately. */ /* * Now we'll start working on how to draw these onto a canvas. * Write a method in Tank with the following contract, similar to that * in UFO. * * boolean draw(World w) * * The World class has one field, theCanvas of type Canvas. The following methods * of class Canvas will be useful for writing this methods (you only really need * drawRect for the Tank): * * boolean drawDisk(Posn p, int radius, Color c) * boolean drawRect(Posn p, int width, int height, Color c) */ /* * We will also need shots in this game as the tank will be attempting to stop the * UFO from landing, so create a Shot class and a series of classes to model lists * of shots (ALoS, MTLoS, ConsLoS). Each should have move(), draw(), and same() * methods like the UFO and Tank classes. You may also want to write a method for * lists of classes that removes shots from the list once they've left the canvas, * but that's not necessary. * * Also write a method in the Tank class that creates a new Shot (which will be * useful when we make the game interactive). */ /* * Now that you can draw the UFO, Tank, and Shot(s), you should modify the UFOGameWorld * (which is a subclass of World) to hold all the information about our game. * It already contains a UFO object, you should add a Tank object and a list of shots * Make it so that the constructor has the following header: * * UFOGameWorld(int width, int height, UFO ufo, Tank tank, ALoS los) * * This way you can use the example given to you in the Examples class. * * You will need to modify the following methods for UFOGameWorld to include the * appropriate handling of your new Tank and list of Shots. The code you add will * be similar to that for the UFO that already exists. * * // Draws the world * boolean draw() * * You should use calls to the other classes' draw() methods, and you will also * want to use drawRect to "clear" the canvas before doing so by redrawing the * background. */ /* * Now you can draw the world, but it's pretty boring just sitting there, so let's * make it do something. In order to handle key events (like pressing the arrow * keys), you'll need to modify the following method in your UFOGameWorld: * * World onKeyEvent(String ke) * * ke will be a string describing the key pressed. For this game, you will only * need to know about three possible values: "left" (for pressing the left arrow * key), "right" (for pressing the right arrow key) and "up" (for pressing the up * arrow key). * * The only object that responds to key events is the tank: you want to make it * so that the following happens: * * - Pressing the left arrow key moves the tank left. * - Pressing the right arrow key moves the tank right. * - Pressing the up arrow key makes the tank shoot. */ /* * Now we need to add a last bit of functionality so that the UFO and shots move -- * otherwise it's a very boring and pointless game! Modify the following method * in UFOGameWorld: * * World onTick() * * You should handle the shots moving upward here. This method already handles * the UFO moving downwards. * * If the UFO lands (i.e. gets too close to the bottom of the screen), the game * ends. In order to end the game, the following method of World (and thus * of UFOGameWorld) is be called: * * World endOfWorld() * */ /* * Everything moves now, but there's still a problem -- the UFO just goes through the * shots! In Shot, create a method that creates an appropriate BoundingBox * (which is already provided), then create a method for lists of shots which tests * to see if any of the shots in the list hits the UFO. UFO already contains a method * to create its BoundingBox. * * Once you have that written, make sure to end the game if any of the shots hit the * UFO during gameplay and you're done! */ /* * Everything below are the classes we're giving to you -- a class that implements * bounding boxes, a UFO class, a UFOGameWorld class that extend World, and an * Examples class with some definitions that start the game (when you make an instance * Examples e = new Examples();) */ // We use rectangles for bounding boxes -- the main thing you // want them for is to tell when they overlap. class BoundingBox { Posn loc; // Upper left of bounding box int width; int height; BoundingBox(Posn loc, int width, int height) { this.loc = loc; this.width = width; this.height = height; } // When two bounding boxes overlap, we want to return true. // We do this by the following: // // Check the sides of the boxes. If the we have that there's // an overlap between the boxes both horizontally and vertically, // then we have an overlap. // // For each, we check the left-most (top-most) right (bottom) side // and subtract from it the right-most (bottom-most) left (top) // side. Draw it out and you'll see what I mean. boolean intersects(BoundingBox bb) { return (Math.min(this.loc.x + this.width, bb.loc.x + bb.width) - Math.max(this.loc.x, bb.loc.x) > 0) && (Math.min(this.loc.y + this.height, bb.loc.y + bb.height) - Math.max(this.loc.y, bb.loc.y) > 0); } } // This class represents the UFO object in the game. class UFO { Posn loc; // Position of the middle of the UFO UFO(Posn loc) { this.loc = loc; } // Checks to see if the UFO is still within the bounds of the world // after moving and if so, returns a new one that's moved the appropriate // distance. If it would move outside the left or right of the world, // then we only change the y. If it would move below the world, then // we don't move at all. UFO move(int diffx, int diffy, int width, int height) { if((0 < (this.loc.y + diffy)) && ((this.loc.y + diffy) < height)) if((0 < (this.loc.x + diffx)) && ((this.loc.x + diffx) < width)) return new UFO(new Posn(this.loc.x + diffx, this.loc.y + diffy)); else return new UFO(new Posn(this.loc.x, this.loc.y + diffy)); else return this; } // Given n, we pick (randomly) a distance to move horizontally whose absolute // value is <= n. We also move the UFO down by 10 pixels. // // width and height are the dimensions of the world, which we use in move(). UFO moveRandom(int n, int width, int height) { return this.move(-n + (new Random()).nextInt(2 * n), 10, width, height); } // bb() returns the bounding box for this UFO. Since this.loc represents // the middle of the UFO, we need to adjust it for the bounding box, whose // loc is in the upper left. BoundingBox bb() { return new BoundingBox(new Posn(this.loc.x - 10, this.loc.y - 5), 20, 10); } // draw() draws the UFO to the world. UFOs look like flying saucers -- // they have a spherical body with a halo-like disk. Of course, we're seeing // these UFOs on edge-wise so we use a circle for the body and a rectangle for // the disk. boolean draw(World w) { return w.theCanvas.drawDisk(this.loc, 5, new Blue()) && w.theCanvas.drawRect(new Posn(this.loc.x - 10, this.loc.y - 2), 20, 4, new Green()); } // Test and see if two UFOs are the same -- we can use this to test move(). boolean same(UFO u) { return this.loc.x == u.loc.x && this.loc.y == u.loc.y; } } // Represents a world in the UFO game class UFOGameWorld extends World { // The world has a width and a height (dimensions of the canvas) int width; int height; // Here's our major players. UFO ufo; //Tank tank; //ALoS los; UFOGameWorld(int width, int height, UFO ufo) { this.width = width; this.height = height; this.ufo = ufo; //this.tank = tank; //this.los = los; } // To draw the world, we draw the players. boolean draw() { return this.theCanvas.drawRect(new Posn(0,0), this.width, this.height, new White()) && this.ufo.draw(this); } // The game's over when a) a shot hits the UFO or b) the UFO lands. boolean gameOver(UFO ufo) { return (ufo.loc.y >= (this.height - 15)); // The UFO has landed } // If the game isn't over, return the new world. If it is, then // call endOfTime(). UFOGameWorld move(UFO ufo) { return new UFOGameWorld(this.width, this.height, ufo); } // When KeyEvents happen (which are represented by strings), then we need to do // the appropriate action, which is: // // - "up" : the tank shoots // - "left" : the tank moves left // - "right" : the tank moves right. // // The tank move method handles the latter two, and we handle telling the tank to shoot. World onKeyEvent(String ke) { return this; } // When the tick event happens, then we need to move the UFO and the shots // (the tank only moves when the player moves it). World onTick() { if(this.gameOver(this.ufo)) return this.endOfWorld(); else return this.move(this.ufo.moveRandom(10, this.width, this.height)); } // run() takes the amount of time between ticks as a double and calls all the necessary // functions. boolean run(double tick) { return this.bigBang(this.width, this.height, tick); } // erase the canvas boolean erase() { return this.theCanvas.drawRect(new Posn(0,0), this.width, this.height, new White()); } } // Examples class class Examples { int width = 200; // How wide we want the game to be int height = 500; // How tall we want it to be Examples() {} // The UFO should start in the middle of the canvas, a little bit down from the top. UFO ufo = new UFO(new Posn(this.width / 2, 10)); // The tank should start in the middle of the canvas, a little bit up from the bottom. // Tank tank = new Tank(new Posn(this.width / 2, this.height - 10)); // There are no shots at the start of the game. // ALoS los = new MTLoS(); // Create a new game world with the actors defined above. UFOGameWorld ugw = new UFOGameWorld(this.width, this.height, this.ufo); // When you are finished, the UFOGameWould should include a tank and a list of shots, along // with the UFO // UFOGameWorld ugw = new UFOGameWorld(this.width, this.height, this.ufo, this.tank, this.los); // Upon instantiation of the example class, a test game runs. boolean b1 = this.ugw.run(0.3); }