PI: Laboratory 5: Polymorphism and Graphics | |
OverviewWe are going to try an experiment in this lab. Since we cancelled the pre-lab work, the actual layout of lab this week will be in a series of stations. You will start at the pre-lab station, where you will review graphics and painting. Once you are familiar with those ideas and comfortable writing code, you will move to the lab stations. There, you will work on today's target exercise. The third (optional) station is one in which you can work on extensions to the target exercise. Finally, you will move to the post-lab (checkout) station, where we will make sure that you've covered the material that you will need later in this course. The main themes of today's lab are polymorphism and some graphical user interface concepts. ContentsPre-Lab: Painting on GraphicsTo begin, we'll rewrite the code we dashed off frantically at the end of class on Thursday. That code consisted of two classes, a Main class and a Canvas. If you are comfortable re-writing the code from class (or have it handy and feel that you understand it), you can skip ahead a bit in this lab to the section on painting behavior. Build a Main class.You should begin by creating a class whose sole purpose is to be the place that Java starts executing. CodeWarrior will help you to do this in its own special way if you open a new project. Be sure to tell CodeWarrior that you intend for this to be a Java project (J2SE, application): On the main toolbar, click the second button from the left. This will give you a file with a public static void main(String[] args) method. Remember that Java begins by creating a single instruction-follower (aka a Thread) that will begin its execution at this method in the class that is your Java target. Save this file and compile it. Look, it's Hello World! Create a Frame.Java's java.awt package provides some useful GUI (Graphical User Interface) classes. Begin by importing the package: import java.awt.*; at the top of your main class. Now you can refer to classes like java.awt.Frame by their shorter names (e.g., Frame). Modify your main method so that you create a Frame. The Frame constructor optionally takes a String as an argument; it uses this String to label the Frame it creates. You may also want to use the Frame's setSize method, which takes two ints as arguments. You will definitely want to use the Frame's show method (no arguments), too. Try this and compile and run it. What happens if you forget to invoke the Frame's show method? What happens if you vary the arguments to setSize? Create a specialized Canvas.Now create another class and add it to your project. This class should extend java.awt.Canvas. (You can simply call it Canvas if you import java.awt.*; at the top of the file.) A Canvas is a Component that can live inside a Frame. You can add an instance of your extended Canvas class to the Frame you created in your main method. (Every Frame has an add method that takes a Component as an argument. Fortunately, a Canvas is a kind of Component.) If you extend Canvas and then add one of these extended Canvases to your Frame, you probably won't see much difference. The reason for putting the Canvas there in the first place is so that you can make it do something interesting. For this, we'll resort to the paint method. Every java.awt.Component (including java.awt.Canvas) has a paint method that takes one argument, a java.awt.Graphics. That Graphics is handed to the Component any time it's time to display the Component. The Graphics can be asked to do all sorts of cool stuff. To begin with, try writing a paint method that takes a Graphics as an argument (don't forget to give it a name!) and calls the fillOval method of that Graphics. fillOval takes four ints as arguments: public void paint( Graphics g ) { g.fillOval( 50, 100, 150, 200 ); } Try varying the ints to see what each one does. Where is the origin of this coordinate system? In which directions to the axes run? More paint tricksLook up the documentation for java.awt.Graphics in the Java api docs. Learn at least two more Graphics methods and get your Canvas to do something cool. Can you create other shapes? Change pen colors? (Check out java.awt.Color.) Funky bonus problem: Create the Olin O. (Don't spend too much time, though!) Painting behaviorIf you've followed our instructions, your class will look the same each time that it is painted. For example, if you drag another window over your Frame and then away, you'll see that the Canvas and Frame repaint themselves to look just the same as before. (Resizing your frame also causes them to repaint, but it doesn't change anything 'cause they paint just the same way.) But what about something that looks different at different times? Add a little bit of state to your Canvas class. (Hint: field.) Depending on the value of this state, make your paint method do something (slightly) different. Try resizing (or hiding and showing) your window repeatedly to see your Canvas change. Get the Point (and the Dimension)In addition to the classes you just played with, take a look at java.awt.Point and java.awt.Dimension. These are classes that merge the (x, y) coordinates or (length, width) coordinates, respectively. Although it is generally not nice style to reach in and inspect the fields of another class directly, in these two cases it can be necessary to do so. For example, a java.awt.Canvas has a getSize() method that returns a java.awt.Dimension: how big is the Canvas? You can directly access that Dimension's fields, e.g., to look at the Canvas' width: if c is a Canvas, c.getSize().width is an int representing c's width in pixels. Be sure that you know how to create a Point -- e.g., (40, 75) -- and to read its fields; Dimension, too. Then, use the getSize() method of the Canvas to make a simple shape (like an oval) stay in the middle of the Canvas as you resize it. Congratulations. You are now ready to move from the pre-lab station to the lab station. You should actually pick your stuff up and move to the next table, because we are going to try to keep track of what people are working on using your physical locations. Welcome to Lab.Get the code.At this point, you need to grab the project for today's lab. It should be waiting for you at \\stufps01\stufac\pi\breakout. Be sure to copy the whole breakout directory as there are some important configuration files there. See the docsJavadoc of code is located here for your perusal.Make it compile and run.When you load the project, you will discover that we have not given you a way to start it. That's because we've omitted the all-important target file. If you want to run the program, you will need to build one for yourself. You should create a file that contains a class with that one special method that Java will begin executing. The body of that method should create a breakout.GameFrame using GameFrame's no-args constructor. (Note that you can call breakout.GameFrame by its shorter name -- GameFrame -- if you are inside package breakout; or if you import breakout.*; ) Unlike the raw java.awt.Frame that you used earlier, you won't need to set the dimensions of the breakout.GameFrame or to tell it to show(). Be sure that you add your new class with its special method to your project and also be sure to tell Java that this class is its target. If you're not sure how to do this, ask one of your classmates or one of us. Once you run your code, you will have the opportunity to load a board. Click the load button and, when you see the dialog box, select the boards directory and choose the first board (default.brd). Play a quick game of breakout. Ahhh, that was nice. Build your own Brick type.Your next job is to create a class that can be a breakout brick. This class will need to implement the BreakoutComponent interface that we have provided. A breakout.BreakoutComponent is a bit like a java.awt.Component, although a BreakoutComponent is not actually a Component in the java.awt sense. Take a look at the BreakoutComponent interface now. The BreakoutComponent interface begins with some methods to manipulate shape and size. We will be giving you a rudimentary implementation of these methods, though you have the option of creating specialized BreakoutComponents that have more sophisticated versions of these methods. Next, the BreakoutComponent interface has three methods that deal with breakout game interactions: kill(), which is invoked whenever that component should simply die off; isDead(), which lets other objects find out whether this object has died off; and hitBy, which is invoked whenever another object (like the ball!) hits this one. You will be writing the hitBy behavior for (at least) the most basic brick type. Finally, the BreakoutComponent method has a paint method that is always invoked with a java.awt.Graphics, just as though it were a java.awt.Component. You will also be responsible for providing the paint behavior of your BreakoutComponent. (Strictly speaking, there's one more method -- isTransient() -- but you can ignore this one too.) To make your job easier, we've provided you with a partial implementation of BreakoutComponent. This is an abstract class called DefaultBreakoutComponent. DefaultBreakoutComponent provides implementations of all BreakoutComponent methods except hitBy and paint. Your task is to implement a SimpleBrick. A SimpleBrick should be destroyed when hit, and render itself as something brick-like. You will probably want to subclass DefaultBreakoutComponent to implement it. In order to operate with the provided code, your constructor takes a Point for location and a Dimension for size in that order.
Before you can implement these two methods, you should know about the constructors for your class. DefaultBreakoutComponent provides two constructors. One takes a java.awt.Point (representing the upper left-hand corner location of this component inside the GameFrame) and a java.awt.Dimension (reflecting the length and width of the component). The other constructor takes those two arguments as well as a World, should someone happen to want to set this component up with one. You should implement these same two constructors as well, making appropriate use of the superclass constructor. (If you are not sure how to do this, ask someone else at the table or one of us.) Now that you've got constructors, think about the two methods you need to implement: To paint yourself, you need to tell the Graphics what to do. The origins of the coordinate system for your DefaultBreakoutComponent will be in the upper left-hand corner of the component itself. To start with, you probably just want to fill this component in. (Note: In contrast to the Canvas's paint method, which got called only when the Frame it was sitting in got resized, your DefaultBreakoutComponent's paint method will be called on a regular basis even if nothing much is happening, so if your paint method changes its behavior, you should see action on your screen.) Finally, decide what this component should do when it has been hit. Do something really simple (and visibly obvious) now. You can come up with all sorts of interesting ideas for later. Add your component.There are two ways to get a BreakoutComponent (like the one you've just implemented) into your GameFrame. You are going to use the simpler one now, but there's a nicer one that we'll get to in a bit. The simplest way thing to do is to modify your main method. In that method, create an instance of your BreakoutComponent class. Construct it with a location (Point) and a size (Dimension) that make it visible in the GameFrame. (You will want to make these components positive and smaller than World.MAXX and World.MAXY, respectively.) Finally, use the GameFrame's add method, which takes a BreakoutComponent as an argument, to add your component to the game. Try this. Compile it and run it. Play the game. Then try again, this time creating multiple instances of your class. Cool! This is today's target exercise, and you can now move to the post-lab table if you'd like. But of course, there's more. Even if you're planning to keep hacking on your breakout game, please move to the Gravy table so that we know you've finished the target exercise. GravyHandy dandy hint: Using the loaderYou may remember that when you started the application originally, you were able to load a board configuration file -- and a whole bunch of bricks -- directly. Take a look at that file now. The purpose of that file is to let you load a board configuration. See if you can adapt it to load your brick type. If you're not sure how, ask Ben, who promised to make everything clear. Going to townFirst, you may want to take a look at the World. The World is the container in which the BreakoutComponents live. It also understands how to detect overlapping components and rebound components off each other. In order to implement a simple brick, you didn't need to use World at all, but more complicated bricks (like those that release balls when they are destroyed) will need to interact with it. Ooh, there's lots you can do. Make a few more different kinds of bricks, like one that can be hit multiple times before it dies or one that changes color each time it's hit before finally dying. Make a ball that can only hit one thing before it dies. Or build a better paddle. (You may have noticed that the paddle we gave you shoots balls, but the one whose code we gave you doesn't. You can build the shooter paddle, but you'll also need the ball that can only hit something once before it dies.) But these are just our ideas, and we're sure yours will be much cooler. Post-LabThere's no explicit post-lab writeup today, but we do want you to get checked out and you will need to make sure your code is in pretty good shape. Please join us at the checkout table to discuss where you got and how you feel about your code. Be sure to save this code as, well, you do remember that code is never done, right? | |
Questions, comments, gripes and other communication to pi-staff@lists.cognition.olin.edu | |
This course is a part of Lynn Andrea Stein's Rethinking CS101 project at the Computers and Cognition Laboratory and the Electrical and Computer Engineering area at Franklin W. Olin College of Engineering. |