Quick viewing(Text Mode)

Space Invaders Team Lab Assignment Specification (V

Space Invaders Team Lab Assignment Specification (V

Space Invaders Team Lab Assignment Specification (v. 0.3.0)

Abstract In this lab, you will be working with two or three of your peers to develop an object-oriented implementation of the classic in Java. This will involve developing several Java classes within a complex inheritance hierarchy (“complex” here does not mean hard, just non-linear), using these classes and objects created from these classes to realize the eventual game-play, and using provided libraries to render the game and interact with the user. This lab introduces a few new concepts in object-oriented design and inheritance and seeks to reinforce concepts that you have already learned.

1. Description of game-play The game you will be implementing is an abbreviated adaptation of the arcade game Space Invaders.

This game is a single-player, multi- shooting game that takes place within a finite two-dimensional space.

The player operates a tank, whose movement is confined along the x-axis and which is located at the bottom of the playing space. The player controls the tank using the left arrow key, which moves the tank to the left, the right arrow key, which moves the tank to the right, and the space key, which fires the tank's cannon, propelling a missile vertically upward at a constant velocity. The tank must observe an implementation-specific firing rate throttle. That is, a tank may fire, for example, only once per second. Any attempts to fire during this one second period must fail to cause the tank to fire, but must not reset the count.

The player's objective is to destroy the invaders, which descend from the top of the screen. The invaders are formed into rows and columns and move at the exact speed in the exact same direction. They begin by moving either to the left or to the right. Once one invader reaches the boundary of the playing space, all invaders simultaneously descend and then begin to move in the opposite direction, again all at the exact same rate. This pattern repeats until either all invaders are destroyed or one or more reaches the bottom of the playing space, at which point the tank is destroyed.

The invaders fire missiles at random. These missiles travel vertically downward.

When an invader is struck by a missile fired by the tank, it is destroyed and removed from the playing space. When the tank is struck by a missile fired by an invader, the tank is destroyed. If the player has remaining lives, then a new tank is inserted into the playing space at the exact place where the tank was previously destroyed, the player's number of lives is decremented by one, and the game-play continues. If the invaders reach the bottom of the playing space, then the tank is destroyed, and similarly, if the player has additional lives, his tank is replaced and his lives are decremented. However, in the case where the invaders have reached the bottom of the playing space, the invaders must then move some distance upward after a new tank is inserted, and they then continue along their former course—traveling to the side, and dropping down and reversing direction when they reach a boundary.

An invader struck by a missile fired by another invader is not destroyed. Instead, the missile is removed from the playing space, but the invader remains.

As invaders are destroyed, they gradually increase their speed. The exact rate of increase in speed is implementation-specific. (My implementation fits the speed of the invaders to a logarithm of the number of invaders multiplied by some factor.) Once there is only one invader remaining, it not only travels faster than it did when there were two, but it also begins firing much, much more rapidly. Again, the exact change in firing frequency is implementation-specific.

Once all invaders have been destroyed, the level is considered completed. The player's count is incremented by one, and the tank's position is reset to the middle of the screen. Then, a new, more difficult wave of invaders forms and again begins its advance. There should be a minimum of three levels, each somehow more difficult than the previous.

Each time an invader is destroyed, the player's score is incremented by ten. At the completion of a level, the player's score is incremented by 100 multiplied by the level number. That is, at the completion of level one, the player receives 100 points; at the completion of level two, the player receives 200 points; etc. Each time the player fires his cannon, his score is decremented by one.

The player's score, number of lives, and the level number are displayed at the bottom of the screen.

Once all levels are complete, the text “WINNER!” is displayed. In the event that the player loses all his/her lives, the text “” is displayed.

3. Provided library The code I have provided to you will handle the user front-end of the game, as well as much of the back-end processing and book-keeping.

The primary class you will be working with is “SpaceInvadersWorld”. This class represents a virtual Space Invaders universe that can be used to keep track of the various entities in the game, update them at a certain interval, render and display the entities to the user, and handle user input via the keyboard. This class interacts with your code via three provided interfaces: ISpaceInvadersEntity, ISpaceInvadersText, and IGameController.

To use this class in conjunction with the code you will be writing, you will first need to create an instance of the class using its default constructor and then call the method “launchGame”.

The “launchGame” method displays the GUI and starts the update process (which calls updatePosition on the entities it contains at a certain interval). If a GameController has been set for the instance, it will call the start method on this controller, to ask it to begin the game—that is, the GameController will obtain control of the program execution to add entities to the SpaceInvadersWorld and then return control to the world.

Entities, such as an Invader, a Tank, or a Missile, can be added to this world via the “addEntity” method. They can similarly be removed, such as when the entity is destroyed or when the level is completed, using the “removeEntity” method. After an entity is added to the world, and before it is removed, it will be rendered using its “getX()”, “getY()”, and “getImage()” methods, and at a certain interval its “updatePosition” method will be called by the update process.

Text objects can be added and removed to the world using the “addText” and “removeText” methods, respectively. When an ISpaceInvadersText object is added to the world, it will also be rendered in the display using its various methods.

An example usage of this class without a GameController is as follows:

// Create the world. SpaceInvadersWorld world = new SpaceInvadersWorld(); // Add a “Hello!” label at 100, 100 world.addText ( new SpaceInvadersText ( 100, 100, “Hello!” ) ); // Add a Tank to the display centered and at Y = 450. world.setPlayerEntity ( new Tank ( world.getWidth() / 2, 450 ) ); // Launch the display world.launchGame();

This example assumes that you have fully written the Tank and SpaceInvadersText classes such that they implement ISpaceInvadersEntity and ISpaceInvadersText, respectively, and that they have constructors that take the provided arguments.

Once a GameController class has been created, you can then use the world in the following fashion:

SpaceInvadersWorld world = new SpaceInvadersWorld(); world.setController ( new GameController() ); world.launchGame();

If your GameController class implements IGameController and provides the following implementation of the “start” method, this usage will be equivalent to the previous example. public void start() { this.getWorld().addText ( new SpaceInvadersText ( 100, 100, “Hello!” ); this.getWorld().setPlayerEntity ( new Tank ( world.getWidth() / 2, 450 ) ); }

4. Inheritance hierarchies

4.1. SpaceInvadersText You must create a class named SpaceInvadersText that implements the provided interface ISpaceInvadersText. A SpaceInvadersText object is essentially a text label that can be rendered at a certain position, with a given font, color, horizontal alignment, and vertical alignment in the game display.

It should be possible to create a new SpaceInvadersText object for any string, with any position, font, color, horizontal alignment, and vertical alignment. How you choose to implement this behavior is up to you to decide—you may simply pack all of these parameters into a constructor, use delegate setter methods, or some combination of the two. The only requirement is that whatever values for these parameters are set by the user must be returned by their equivalent getter methods as specified in the ISpaceInvadersText interface.

4.2. AbstractSpaceInvadersEntity You must create an abstract base class that implements the interface ISpaceInvadersEntity. This base class should provide concrete implementations of all the methods defined in the interface, except for getWidth(), getHeight(), and getImage(), which should be left abstract for extending classes to implement.

The abstract class should provide a constructor taking two arguments—an initial X and Y position, which should be stored in the instance and returned by subsequent calls on getX() and getY().

The abstract class should provide full implementations of all setter methods, such as “setXSpeed” and “setYSpeed,” by storing the values passed to these methods and returning them on subsequent calls to their corresponding getter methods, e.g. “getXSpeed” and “getYSpeed.”

The abstract class should provide a default implementation of “updatePosition” that increments the entity's X-coordinate by its X-speed and its Y-coordinate by its Y-speed. After updating its position, it should check if the entity is “out-of-bounds” by using the method “getWorld().outOfBounds(...)”. You should create a separate protected method, “onOutOfBounds()”, and call this method from updatePosition when the entity is out-of-bounds. By default, the “onOutOfBounds” method should reverse the actions of updatePosition; that is, it should decrement its X-position by its X-speed and its Y-position by Y-speed. Note: This must be in a separate method so that it can be overridden by one or more extending classes. The abstract class must also provide a concrete implementation of the “destroy” method. By default, this method should remove the entity from the SpaceInvadersWorld using the “removeEntity” method.

Please consult the provided javadoc to determine what each of the methods in this class should do, or contact me if you get completely stuck.

4.2.1. Tank You should create a class named Tank that extends AbstractSpaceInvadersEntity. In this class, you should implement the methods “getWidth()”, “getHeight()”, and “getImage()” such that they return the width, height, and “image” that the display should use to render a tank.

You may use the field ISpaceInvadersEntity.TANK_IMAGE as your return value for the getImage() method. If using this image, your getWidth() and getHeight() methods should return 20 and 7, respectively.

Tank must also provide a concrete implementation of the “fire()” method. This method should create a new Missile that travels in the negative-Y direction and that targets Invaders. This newly-created Missile must then be added to the world using the “addEntity” method of SpaceInvadersWorld.

4.2.2. Missile The Missile class extends AbstractSpaceInvadersEntity and implements the methods “getWidth()”, “getHeight()”, and “getImage()” such that it is rendered as a small square or diamond on the display.

A missile is restricted to movement in either the positive or negative Y directions. A missile fired by an Invader will travel in the positive Y direction (toward the bottom of the display), while a missile fired by a Tank will travel in the negative Y direction (toward the top of the display).

At each tick, a Missile should, in its updatePosition method, determine if it is has struck an enemy. Use the method “getAtLocation” in SpaceInvadersWorld to determine if any entities overlap the current position of the missile. It must then determine if any of these entities are hostile—that is, if the Missile was fired by an Invader, a Tank is considered hostile, and the opposite is true for a Missile fired by a Tank. The underlying mechanism for determining if an entity is hostile is unspecified and left to you to decide. Any hostile entities found to be overlapping the position of the Missile in updatePosition should then be destroyed using the “destroy” method. When the Missile destroys a hostile entity, it must also destroy itself.

Furthermore, when a Missile travels outside the bounds of the playing space, as indicated by a call of the method “onOutOfBounds”, the Missile should destroy itself. 4.2.3. Invader The Invader class extends AbstractSpaceInvadersEntity and implements the methods “getWidth()”, “getHeight()”, and “getImage()” such that it alternates between two images. You may use the images provided by IspaceInvadersEntity, INVADER_CLOSED_IMAGE and INVADER_OPEN_IMAGE, and a width and height of 20. To enable the alternation between images, you will need an instance variable to store the current image, which should be changed after a certain number of calls to updatePosition.

An Invader exhibits a unique behavior when one or more Invaders reaches the bounds of the world. Rather than simply stopping at the bounds of the world, as is the default behavior in AbstractSpaceInvadersEntity, all Invaders simultaneously drop down and reverse direction. There are a few different ways to accomplish this behavior; however, the easiest way is to recognize that all Invaders move simultaneously, and thus their behavior can be modified using a set of static fields (class variables) that can be written in the “onOutOfBounds” method, read in the “validatePosition” method (which is called after updatePosition has been called on all of the entities), and reset in subsequent calls to “updatePosition”.

Furthermore, the X-speed of an Invader is determined by the number of Invaders currently alive. Again, recognize that this X-speed is common to all Invaders and thus can be controlled via a static field, which can be updated when an Invader is destroyed or created.

An Invader must also fire Missiles at random intervals. These Missiles travel vertically downward (in the positive Y direction) and target Tanks. The frequency of firing remains constant until only one Invader remains, at which the the frequency of firing dramatically increases. Yet again, think about how this can be done with static fields.

Once an Invader has reached or exceeded the Y-coordinate of the player, the player is destroyed. This can be accomplished by checking the Y-coordinate of the player entity (ascertained using SpaceInvadersWorld.getPlayerEntity) and calling “destroy” on the player entity if the Invader's Y is greater than the player's from within updatePosition.

Note that to accomplish all of these tasks, you will need to override updatePosition, call the super-method from within the override, and then provide your custom code after the superclass call.

4.3. GameController Coming soon...

5. Development timetable

This is a proposed timetable for implementing this project. You will need to have a final implementation ready by October 7. If time permits, each group will be able to demo their implementations on October 14. 5.1. September 16 This first day is going to spent getting ourselves oriented. I will go over the basics of interfaces and abstract classes (Andree will be covering these topics in much more depth in the lecture), explain the libraries that I have provided you, and give you some insight into how to approach this problem. You should formulate your groups—I would recommend three to four in a group—and one member of your group should e-mail me with a list of the members in the group. You should then create a new project in eclipse and import the provided JAR into the project. If there is time, you might start implementing AbstractSpaceInvadersEntity, which should implement the IspaceInvadersEntity interface and provide concrete implementations for all methods except getWidth(), getHeight(), and getImage().

5.2. September 23 By the end of today you should have a full implementation of the interface ISpaceInvadersText, and you should have some brief demo code to illustrate how you can add a text object to the display to render a piece of text, such as “Hello, world!”

You should also have a more-or-less complete implementation of the AbstractSpaceInvadersEntity base class, which should implement ISpaceInvadersEntity as specified above (see “Inheritance hierarchy”). You should also have a full implementation of the “Tank” class, which extends AbstractSpaceInvadersEntity, and you should have some demo code to illustrate how you can add a Tank to the display, which a user can control using the left and right arrow keys. The tank should stop at the boundary of the display.

My advice to you would be to have 2-3 people work at one computer on AbstractSpaceInvadersEntity while 2 people work at another computer on SpaceInvadersText.

5.3. September 30 Continue working on AbstractSpaceInvadersEntity. Once you believe your implementation is complete, please ask me to look it over to verify that it meets the specification.

Then proceed to implement the Tank, Missile, and Invader classes. By the end of today, you should be able to add a Tank to the world, move it left and right, and fire a missile when the spacebar is pressed. This missile should travel in the negative-Y direction and destroy any Invader in its path. You should also be able to add Invaders to the world, and Invaders should be capable of moving from one side to the other and of alternating between two images at some interval as they move. The drop-down and reverse direction behavior of the Invaders does not have to be fully implemented by the end of today—instead, it is okay if the Invaders bunch up at the bounds of the world or simply disappear from the world.

How you choose to delegate work amongst team members on this part is entirely up to you.

5.4. October 7 TBD.

5.5. October 14 Time-permitting, we will use this lab to allow each group to demonstrate their implementations and answer questions.