CSE 283 Introduction to Object Oriented Design

Barbara Nostrand, Ph.D.


CSE 283 Labs
Welcome to the site for CSE 283 Labs!

 

Lab 9: Basic Graphics

Prev | Next | Lab 9

 

Introduction

In today's exercise, we will convert a text based program into one that uses a GUI. The application we will change simulates a random walk in two dimensions. There is a famous physics thought experiment that goes like this:

Suppose you have a drunk person that is standing next to a lamp post. He is very unsteady, but decides to head home. Fortunately, he is constrained to walk along the street (one dimension). Unfortunately, he has an equal probability of going forwards or backwards.

Given this situation, a physicist will want to know the answers to questions like:

  • What is the probability that the person will return to the lamp post?
  • After n steps, where is the person likely to be?
  • After n steps, on average how many times did the person pass the lamp post?
  • After n steps, what is the average distance of the person from the lamp post?

One approach to this kind of problem is to look for a mathematical solution. This is fine for a theoretician, but what about an experimentalist? Their approach would be to create an experiment and test the formulas that the theoretician comes up with. Now to actually run such an experiment would take time and who knows what governmental agencies would complain about the use of a human (let alone a drunk human) in our experiment. Fortunately, as computer scientists, we can write a simple program and save ourselves from having to deal with actual people.

In today's code, we will allow our person more freedom than in the stated problem. Instead of being on a road, you can imagine our person as standing next to a tree in the middle of a field. Now he can take a step in any direction. This results in a two dimensional random walk.

 

The Original Program

The program that we will be converting is contained in the files RandomWalker.java and RandomDemo.java. Lets take a look at these two classes and see how they solve the problem.

RandomWalker

We see that this class has five attributes:

  • myXPosition
  • myYPosition
  • myXExtent
  • myYExtent
  • myStepCount

These attributes record the position in the 2-D plane of the walker, the maximum change (extent) in x and y for a single step, and the number of steps taken so far.

The constructor forces us to initialize the extent and sets the other values to zero.

We have an accessor for each of the attributes. The ones we will be concerned with are:

  • double xPosition()
  • double yPosition()
  • int stepCount()

The one mutator is

  • void step()

which causes the walker to take a step in a random direction. The method Math.random() is used to generate a random value between 0.0 and 1.0. We convert that into a random value between -1.0 and +1.0,  and then multiply by the appropriate extent.

RandomDemo

It uses the following algorithm to generate a printout of the positions visited by a random 2-D walk:

0. Read in the maximum number of steps to take.
   1. Read in the maximum change in x and y per step.
   2. Read in the maximum value for x and y.
   3. Create a new RandomWalker.
   4. Print the walker (original position).
   5. Loop at most maxSteps times
      a. Take a step.
      b. Break out of the loop if out of bounds.
      c. Print the walker.

Take a moment and make sure that you understand how this solves the problem.

Make a project named RandomWalk and add RandomDemo.java, RandomWalker.java and the ann package to it. Compile and run the code with some different input values. Notice that even if you give the code the same input values, the output will be different.

Design issues

Why do we have two classes instead of just one? We could have solved the problem with just a single class holding just the main function. The answer to why goes back to the design. As the original code was being developed, the author considered what the objects in the simulation were. The obvious one is the random walker. The not so obvious one is the simulation itself. Once we have the objects in the simulation, we need to know what the responsibilities of each object are.

Responsibilities of walker:

  • It has to know its position.
  • It has to know the number of steps taken so far.
  • It has to be able to take a step.

Responsibilities of the simulation:

  • It has to know the maximum number of steps to take.
  • It has to know the boundaries of the simulation.
  • It has to get input from the user.
  • It has to step the walker an appropriate number of times.
  • It has to produce output.

 

Note that this is not the only possible design. For example, we could have put the responsibility for knowing the boundaries on the walker.

One benefit of the split in responsibilities we have is that all of the interface responsibilities are localized in one class. This kind of a split is very common in OOP design. It is typical of a two layer design which has a problem domain and an interface. Notice that our current design is not a pure design, since we have mixed the interface (getting input from user and doing output) with the simulation (knowing how many steps one can take, etc.) The benefit of this kind of design is that we can change the interface without having to change the domain.

Designing the GUI

As in the last interlude, we need to decide what our GUI should look like. For the input values, we will use a JOptionPane. The output, however, is another matter. We could print all the data values, but that would not be very informative. Instead, we would like to have some kind of graphical representation. Fortunately, we can draw pictures on components in Java.

In our design we would like to make sure that we have:

  • A representation of the random walk. Could be points or lines.
  • A message to the user letting them know what they are seeing.
  • A message informing the user about the data values used.

One possible design would look like this.

There is still a lot of flexibility in this design. Should the text be in a JTextArea or four JLabels? In fact, we will choose to do neither and draw the string information directly onto the graphics area.

We can use the following components to implement this design.

Component

Type

Name

input maximum number of steps

JOptionPane

input maximum change in x

JOptionPane

input maximum change in y

JOptionPane

graphics drawing area

WalkDisplay

myPicture

the whole thing

JApplet

WalkDisplay

We will follow tradition and create a new class for the purpose of drawing. It will extend JPanel and therefore can be drawn on. This class will have a constructor and it will have one method which must be

   public void paintComponent(Graphics pen){  

which will do all the drawing. The method paintComponent() will be called anytime the window needs to be redrawn. For example, suppose that we change the size of our application. The application will ask each of its components to redraw themselves. This method is how it will be accomplished.

To do the painting, we will send messages to the pen that is passed into us. Some of the common methods are:

  • setColor(Color c)
  • setFont(Font font)
  • drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)
  • drawOval(int x, int y, int width, int height)
  • drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
  • drawRect(int x, int y, int width, int height)
  • fillArc(int x, int y, int width, int height, int startAngle, int arcAngle)
  • fillOval(int x, int y, int width, int height)
  • fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
  • fillRect(int x, int y, int width, int height)
  • drawString(String str, int x, int y)
  • drawLine(int x1, int y1, int x2, int y2)

 

The fill methods will use the current pen color to fill the specified shape. Notice that all of the coordinates are integer values. This will have consequences for our program.

Another thing to keep in mind is that the origin of the coordinate system is in the upper left hand corner. X values increase going to the right. Y values increase going down.

For our design, we will use the fillOval() method to plot our points and the drawString() method to display the text information.

Converting the Code

Making the Application

The first thing that we want to do is to make a copy of RandomDemo.java and call it RandomGUI.java. We will make all our changes in RandomGUI.java.

We need to import the appropriate packages.

   import java.awt.*;
   import javax.swing.*;
   import ann.gui.*;

We need to change the name of the class to RandomGUI and make it extend ClosableFrame which is a convenience class in ann.gui. It is for creating an application with a frame that appropriately handles a window close event.

Making the WalkDisplay Stub

We will use a private helper class to do the actual work of drawing. The first thing to do is to create the new class. At the very end of the file RandomGUI.java after the class RandomGUI add in the following stub for a class:

class WalkDisplay extends JPanel 
{
   
}

Since this class is in the same file we will be able to use it, but it is invisible to all other code.

Inside this class lets create stubs for the constructor and the paintComponent methods:

     public WalkDisplay()
     {

	}
	
	
	public void paintComponent(Graphics pen) 
     {

	}

What are the responsibilities of the WalkDisplay class?

  • It has to know the points to be drawn.
  • It has to be able to compose the output strings.
  • It has to know the size of the oval to draw.
  • It has to know the size of the area to be drawn on.

One possibility is that our main program will create the random walker and then call step() an appropriate number of times. The points visited would be stored in an array. This array could then be passed to the WalkDisplay class. We will take a different approach. The main function will create the random walker and then will pass that to the WalkDisplay when it is constructed. This results in new responsibilities for the WalkDisplay class.

  • It has to know the random walker.
  • It has to know the maximum number of steps.
  • It has to step the walker an appropriate number of times.

 

Changing main()

After we have decided on the responsibilities of WalkDisplay, we need to record the responsibilities of the main function:

  1. It has to know the size of the window.
  2. It has to create the instance of RandomGUI.
  3. It has to set its size.
  4. It has to get the input.
  5. It has to create the instance of RandomWalker.
  6. It has to create the instance of WalkDisplay.
  7. It has to associate the instance of WalkDisplay with the GUI.
  8. It has to make itself visible.

The first four are new responsibilities related to being a GUI.

 

Know the size of the window

Just before the main(), declare three static constants.

  • WIDTH = 300
  • HEIGHT = 300
  • MESS_HEIGHT = 100

These will be the width of the area we will draw in, the height of the area we will plot points in, and the height of the area we will draw our message in. Since we had four lines to display in our design and each line should be about 20 high, this should give us enough space.

 

Create the instance of RandomGUI; Set the size; Make it visible

The graphics system distinguishes between visible objects and invisible objects.  Setting an object visible will trigger a paint and will also mark the objects currently in the content pain as visible.  For this reason,  you need to invoke setVisible after you have added all of your components to the content pane.  To begin,  add the following three lines to your main method: 

    RandomGUI myGUI = new RandomGUI();        // Must appear early in main
    myGUI.setSize(WIDTH, HEIGHT+MESS_HEIGHT); // Must appear early in main
    myGUI.setVisible(true);                   // Must be last statement in main

Get the input

Use JOptionPane to get maxSteps, maxXChange, and maxYChange as we have done in the past.

We don't need to get maxX and maxY because we will use the constants WIDTH and HEIGHT instead.

 

Create the RandomWalker

This should be the same as in RandomDemo.java.

 

Create the WalkDisplay

Before we can do this, we need to know what information to send to the display. Lets review the responsibilities of the WalkDisplay:

  1. It has to know the points to be drawn.
  2. It has to be able to compose the output strings.
  3. It has to know the size of the oval to draw.
  4. It has to know the size of the area to be drawn on.
  5. It has to know the random walker.
  6. It has to know the maximum number of steps.
  7. It has to step the walker an appropriate number of times.

To satisfy 4, we need to send it the three constant values. To satisfy 5, we need to send it fred (our instance of RandomWalker.) To satisfy 6, we need to send it maxSteps.

Add the following line of code to main():

   WalkDisplay myPicture = 
      new WalkDisplay(WIDTH, HEIGHT, MESS_HEIGHT, maxSteps, fred);

Then go and change the constructor for WalkDisplay to match. Don't forget to create attributes to store these values.

Associate the instance of WalkDisplay with the GUI

The final line in main() should be:

   myGUI.setContentPane(myPicture); 

Now we should be able to see our WalkDisplay.

Compile and Test

You should compile your program. If you have syntactical errors, review the above steps and find your errors. Once it compiles, run the code. It should get three values and then display a gray rectangular window with the title RandomGUI. Continue when you have achieved this.

 

Building up WalkDisplay

Since this is where most of the new material is, lets take it in steps. After each step, compile and fix any syntax or logic bugs.

Changing the background color to white.

Gray is an unappealing color. Lets make the background white.

We can use the setBackground() method. One can specify a color using RGB format, but we will use one of the predefined colors (black, blue, cyan, darkGray, gray, green, lightGray, magenta, orange, pink, red, white, yellow). Add the following line to the constructor:

   setBackground(Color.white);

To observe this change we need to add the following line to paintComponent().

   super.paintComponent(pen);

We must do this because there are many things that need to be done to paint on our pane, including taking care of the background color. The super.paintComponent() ensures that they are done. (You'll see more precisely what this means when you cover the material on inheritence in chapter 11.)

Drawing Strings.

We would like to display appropriate information on our pane. Lets make the color of our text blue. Add the following line to paintComponent() after the super call.

   pen.setColor(Color.blue);

This sends a message to our pen to change its color to blue for any subsequent drawing.

To draw a string, we need to provide 3 arguments. The first is the string that we want to display. Then give the x and y value of the start of our string. The x value is the left edge of the first character. The y value is the baseline of the string. Something like the following should put a message in our window.

   pen.drawString("This program demonstrates a 2D random walk.",
      0, myDrawAreaHeight + 20);

Plotting the Points.

First change the color of the pen to green.

Second send a reset() message to your walker. Since paintComponent() may be called more than once, we would like to start our walker over in the center each time.

Copy the loop from RandomDemo and make appropriate changes to variable names. Since we would like our points to be in our drawing area, the bound on X (maxX in RandomDemo) will be half of myDrawAreaWidth. Similarly the bound on Y will be half of myDrawAreaHeight.

Make these changes and run the program. You should see a list of points on the console.

Now we need to take the values that are produced by the RandomWalker and plot them in our drawing area. Unfortunately, the data that we have does not match the coordinate system we must do our drawing in.

The first transformation we need to do is to convert from double to int.

   xPos = (int) Math.round(myWalker.xPosition());

The next transformation is to find the upper left corner of the oval we want to plot. Consider the following picture:



The arrow shows the x and y value we want to use as arguments. Lets find the center of the oval first. Consider these three values of xPos and the corresponding coordinates in the drawing area:

xPos

x coordinate of center in drawing area

-myDrawAreaWidth/2

0

0

myDrawAreaWidth/2

myDrawAreaWidth/2

myDrawAreaWidth

So we need to add in myDrawAreaWidth/2 . Once we have the center, we need to subtract half of the size of the oval. Putting it all together results in:

   xPos = myDrawAreaWidth/2 + xPos - OVALSIZE/2;

where OVALSIZE is a constant we've defined. (Choose a small value like 2 or 3 for now.)

Do a similar analysis and compute the appropriate yPos.

 

The final step is to actually plot the point using drawOval(). The last statement in the loop should be

   pen.fillOval(xPos, yPos, OVALSIZE, OVALSIZE);

 

Test the code and do any needed debugging.

Phrases you should now understand:

Random walk, Responsibility, Two layer design, Problem domain, Graphics, paintComponent(), drawOval(), drawString().

 

Prev | Next | Lab 9

 
  Home

Schedule

Help

Lab 0

Lab 1

Lab 2

Lab 3

Lab 4

Lab 5

Lab 6

Lab 7-8

Lab 9

Lab 10

Lab 11

Lab 12

Lab 13






Last modified: 2007 NOV 07
bnostran@syr.edu