CS 302 Lab 12

Lab 12: Designing Classes: Processing Applets

CS302, UW-Madison

Today We'll be:

Overview

You've learned how to organize a program's statements into task-oriented modules using methods. You've also learned how to organize data into lists and tables using arrays. Combining data and methods into a single module, is called an instantiable class, which is one of the key ideas of object-oriented programming (OOP)!

In this lab, you will design and define classes that are a composite of primitive types and instances of other classes. Each instance has some data, and a draw(PApplet) method so that it can draw itself on a Processing Applet (PApplet) window frame.  

Once, you get the basics of displaying text, lines, rectangles, and ellipse on the applet, we'll guide you in designing new data types that can display themselves on a PApplet.  Then, you'll add some functionality so that the instances of your class(es) can do things like: float(), move(), grow(), race(), land(), anything thing you want.

Getting Started

  1. Create a new project in Eclipse named ProcessingLab
  2. Download and add core.jar to your project.
    The core.jar file is an external java archive (library) of class files that can be used to create Processing Applets (PApplet class). 
  3. Add the core.jar file to the build path of your project. 

Tip: After lab, see processing.org for tutorials, language reference, and many more tips on using the Processing Language to draw and animate your programs. 

Task 1: View OrangePApplet Demo

  1. Download OrangePApplet.java and save it in your ProcessingLab project.
  2. Click Eclipse's Run button (or Run->Run from the menu) to launch the Applet viewer and start your applet. You should see an orange background with some yellow stuff drawn on it and a moving circle.
  3. Click the close button on the applet frame to stop and close the applet.
  4. View the source code to see the basic methods that Processing Applet's use.
          public void settings() -- set size of PApplet
          public void setup() -- initialize data members (fields)
          public void draw() -- draw current state of the applet
  5. Try editing the source and running to see what pieces you can understand and change.
  6. Don't get stuck playing here though!  We have other ideas for you... continue with the next tasks to see how to make more interactive and object-oriented applets.

Show a TA where the points (0,0) and (550,200) are located on your applet window.

Task 2: Create a Snow Storm

Many different snow flakes

Simulate a snow storm by drawing snowflakes that float to the bottom of the window (and magically appear at the top to fall again). Using the Processing library of classes and a few simple drawing methods, it's easy.

2.a) Get the applet going with one snowflake

  1. Download SnowStormApplet.java file and add it to your project.
  2. If there are any compile-time errors, check that you have correctly downloaded the core.jar file and added it to your build path. Your SnowStormApplet class extends the processing.core.PApplet class so that it inherits the public fields and methods of the PApplet class. The import statement is required because the PApplet class is not part of your project or the built-injava.lang package. The main method calls the PApplet.main(String[]) method with an array that contains the name of your newly defined PApplet.
  3. gray square box correct sizeClick run to launch the Applet viewer and start your applet. You should see a blue background with one small snow flake.  Each time you run this applet, the snowflake is in a different location.  Close the applet and continue.

  4. Change the values of x and y and rerun the applet. Can you set x and y to place the snowflake right where you want it?  What do you notice about the coordinate system of applets (and graphics in Java in general)? 

2.b) Define a SnowFlake class

A simple snowflake can be drawn with four short line segments.  But, how can we easily create dozens (hundreds) of snowflakes that can each move independent of each other? The answer is to define a new data type, an instantiable class SnowFlake and then use that to create lots of SnowFlake instances.  First, define a new instantiable class.

  1. Create a new class named SnowFlake.  (File->New -> Java Class)
  2. Add a draw(PApplet) method to your SnowFlake class.
        public void draw(processing.core.PApplet applet) {
            // TODO add code to draw this snow flake here
    
        }

    Tip: Add an import statement import processing.core.*; so that you don't have to type the full name of the PApplet class.

  3. Move (cut and paste) the code to draw a snowflake from the draw() method of the SnowStormApplet to the draw(PApplet) method of your SnowFlake class.

    If you have compiler errors (you should), you will need to resolve them to continue.

    1. The code in the draw(PApplet) method requires that x, y, and size exist as instance fields in your SnowFlake class.   Move the fields for x, y, and size. from the SnowStormApplet class to the SnowFlake class.
    2. Add a constructor to initialize the SnowFlake fields. For example:
          public SnowFlake( int x, int y, int size ) {
              this.x = x ;
              this.y = y ;
              this.size = size ;
          }
    3. Change the object reference to each drawing instruction in your draw(PApplet) method to the reference passed in the applet parameter.

      Add the object name applet. before each call to a PApplet instruction.

          applet.stroke(255); // white lines
          applet.fill(255);   // white solids
          applet.ellipse(x-1,y-1,size,size); // small filled in circle
          ... // TODO: fix the other commands too

      Explanation:

      Calling the PApplet methods stroke(),fill(),ellipse(),rect(),... methods from within the draw() method in SnowStormApplet was possible since the SnowStormApplet class extends (inherits from) class PApplet.  However, in our SnowFlake class, we are not extending PApplet but we are passing in the instance of PApplet as a parameter.

      Since we want to call those methods from within the SnowFlake to do our snowflake drawing, we need to pass in an instance of PApplet.  We did this.  Now, all we have to do is call each drawing method using the draw method's parameter (named applet and shown below).

2.c) Add an instance of your SnowFlake class to your SnowStormApplet

  1. Declare a field of type SnowFlake in your SnowStormApplet class (remove fields x,y,size).
  2. Initialize the SnowFlake instance in the setup() method (instead of initializing x,y,size).
  3. Replace the code in your SnowStormApplet's draw() method with code that calls the draw(PApplet) method of your SnowFlake

    Tip: Pass a reference to this applet using the self-referencing pointer this.  (See example below):
       // in SnowStormApplet class
       // call draw method of the SnowFlake instance
       public void draw() {
          background( 0, 0, 63 );
          snowFlake.draw( this ); 
       }
  4. Run your program to see one random snow flake.

2.d) Make the snowflake float down

To make the snowflake move, we must change the location (x,y) before (or after) drawing it.

  1. Add a public void move(int dx, int dy) method to your SnowFlake class.  The values dx and dy represent the relative change along the x and y axes, respectively.  This method can than be called to move your snowflake as desired. 
  2. In the draw(applet) method of your SnowFlake class:
    1. Add code to randomly select a dx that will move the flake one pixel left or right. (randomly choose dx from set {-1,0,1})
    2. Add a call to the move(dx,dy) method before you draw the lines of the snow flake.
    3. If the flake reaches the bottom, place it at a random location along the x-axis and at the top (y=0).
  3. Run your SnowStormApplet to see your snow flake fall to the bottom and start at top again.

2.e) Add dozens (100) snow flakes to your applet.

  1. Add an instance field to your SnowStormApplet to store an array or arraylist of SnowFlake instances. 
  2. Add code to the setup() method to create (instantiate) a few (or many) SnowFlake instances at random locations and random sizes = 1,2, or 3 pixels (or whatever you want). Add each snowflake created to your array or arraylist.
  3. Edit the the code in your SnowStormApplet's draw() method so that it draws each SnowFlake. 


       public void draw() {
          background( 0, 0, 63 );
    
          // FOR EACH SNOWFLAKE ...
          // call to the draw() method (of the SnowFlake class) 
          snowFlakeList[i].draw( this ); 
       }
  4. Run your applet to see the many snowflakes on display. Fix any random location (x,y) or size problems.

Show a TA your SnowStorm with the flakes floating down and try the next tasks.

Task 3: Make a SnowStudent

  1. Save your SnowStormApplet.java class as SnowStudentInASnowStorm.java
  2. Edit the class name.
  3. Edit the applet name's string in the main method to "SnowStudentInASnowStorm".
  4. Download this SnowBall.java class to your project folder and refresh the project. The SnowBall class has been defined for you and it has a draw(PApplet) method so it can draw itself on a Processing applet.  Each SnowStudent instance will draw three SnowBall instances.

  5. Dark blue background, snowflakes, and a SnowStudent Download the start to a SnowStudent (SnowStudent.java) and add the bold lines of code to your class. 
    import processing.core.*; 
     public class SnowStudent {
    
        private float BASE_WIDTH = 20;
        private float BASE_RADIUS = BASE_WIDTH / 2;
    
        public SnowStudent( String name, float x, float y ) {
            this.name = name; this.x = x; this.y = y;
            base = new SnowBall( x, y-BASE_RADIUS, BASE_RADIUS );
            // TODO: add code to initialize body SnowBall here
            // TODO: add code to initialize head SnowBall here
        }
        
        public void draw( processing.core.PApplet applet ) {
    
            // draw the base of this SnowStudent on the applet
            this.base.draw( applet );
            // TODO: add code to draw body here
            // TODO: add code to draw head here
            
        }
    
    }
  6. Place a SnowStudent in your SnowStormApplet. Tip: Add a SnowStudent field, initialize it in the setup() method, and place a call to your SnowStudent's draw() method in the applet's draw() method.
  7. Play with the size and position until your SnowStudent looks right in the picture.
  8. Add eyes, buttons, a nose, a hat, and a scarf to your SnowStudent.

Challenge Tasks: Design and implement your own Processing Applets that use instantiable classes.

See the Processing Language Reference page for new ideas to try. Don't just download and run examples. Try creating your own simple objects that have the ability to do things.  

Here are some more suggestions until you imagine your own:

  1. Try adding wind to the SnowStormApplet
  2. Try making your a SnowStudent move as one object?
  3. Can you add the ability to throw SnowBalls at a target?
  4. Can you create a two player SnowBall fight?
  5. Try making a flower garden with flowers that sprout and grow randomly.
  6. Try making a car that moves across the window in a race.
  7. Try animating the GameOfLife code you wrote for Program 2.
  8. Try creating your own GUI for PortalSnake (Program 3).
  9. Try making the UFO, or Maze GUIs from earlier labs.
  10. Try making a family tree diagram with a Person class (or use your SnowStudent class).

Deb's completed examples for this lab.

Processing Applet Design Considerations

When designing instantiable classes for Processing Applets, there are two key questions you'll want to answer:

  1. What image will be displayed for your instances?
  2. What will your instances do on each iteration of the the loop() method?

Some other things to consider for your objects:

  1. What information do you want your objects to know or store?
  2. How will they move or change? 
      1. In response to key strokes?
      2. In response to mouse events?
      3. Every loop cycle?
  3. What happens when your objects (instances) encounter instances of other classes?
    • Do they move?
    • Do they grow?
    • Do they die?
    • Do they spawn new objects?

Design your classes so that your PApplet can draw and move your instances in an interesting way depending upon the class you design. 

For the best results, incrementally develop each applet and instantiable class.

General Instantiable Class Design

Check with your Lab TA to see if you are making good design choices.

Have you included the following items?

Design Considerations:

When designing an instantiable a class to represent a particular kind of object you need to ask yourself two questions:

  1. What do you want these objects to store? That is, what data is needed to represent these objects?
  2. What do you want these objects to be able to do? That is, what methods are needed to make use of the data?

The Data

Deciding what data to store is the first step in the design of a class. In general, for each field (data member) you'll need to answer these questions:

Write pseudocode for the data members by answering the questions for each item above (write your answers on paper to develop your design):

The Constructors

Once you've designed the data part of a class, you'll next design its constructors. Here are some questions you'll need to answer with your design:

The Methods

Once you've designed the fields and constructors of a class, you'll next design its methods. Here are some questions you'll need to answer with your design:

Lengthy Challenge Task (after class most likely): Host a Snow Student Race

Now, lets try something a little more interactive and challenging.  A race between you and Bucky to the finish line.  Your main applet class will create two instances of a SnowStudent class. Each execution of the applet's draw() method will randomly move and then draw the SnowStudent named Bucky.  The second SnowStudent instance will be controlled by the keys that the player types.  Each time the player types the correct letter (the one shown in the body of their SnowStudent avatar), their SnowStudent advances 1 pixel toward the finish line. 

When the applet is done, it will look something like these images.

Bucky and the player are both at starting line (x=20). The player has won and is at x=345

3.a) Get the race course showing

  1. Course showing instructions, start and finish lines Download a new class SnowStudentRace.java like your SnowStormApplet class with a main method that gets the applet started.  This is your main class and main controller for the race. Add the following to the draw() method of your applet class.
            // Draw the background color
            background(128, 196, 255);
    
            // Draw a green starting line
            this.stroke(0,255,0);         // green for starting line
            this.line(START_LINE, 0, START_LINE, this.height);
            
            // Draw a red finish line 
            this.stroke(255,0,0);
            this.line(FINISH_LINE, 0, FINISH_LINE, this.height);
            
            // Draw the race instructions
            this.text("It's a SnowStudent race between Bucky and you.",START_LINE+5,20);
            this.text("Type the letter in your Snow Student to start the race and advance.",START_LINE+5,40);
  2. Run the applet to see that the race course is ready for some racers. Close the applet and continue. 

3.b) Add Bucky

  1. Add an instance of your SnowStudent class to the SnowStudentRace .  The "race" will draw SnowStudent instances and move them across the race course.

  2. Course showing instructions, start and finish lines, and Bucky at (20.0) In SnowStudentRace class:
    1. Add a SnowStudent instance field with the name bucky. In other words, declare a field of type SnowStudent with the name bucky as an instance field in class SnowStudentRace.
    2. Initialize bucky in the setup() method.
      Try different x and y values until you get bucky where you want (Hint: at the starting line nearer top of course).
    3. Add code to draw bucky. (Hint: in the draw() method of the applet class).
    4. Run your applet to see Bucky on the course.
    5. Change the initial positions of x,y to get Bucky where you want her.

3.c) Add Bucky's name and race position !

  1. Course showing instructions, start and finish lines, and Bucky with name at (20.0) Add code to the draw(PApplet) method of your SnowStudent class to draw (write) the name and position (along the x-axis) of the SnowStudent to the window.
        // draw the name of this SnowStudent (in middle of base)
        applet.fill(0); // fill text letters with black
        applet.text(name, x-5, y );
        applet.text("("+x+")", x-10, y+10 );
    

3.d) Get Bucky moving

  1. Complete the move(dx,dy) method in your SnowStudent class to make moving SnowStudent instances convenient. The parameters dx and dy represent the change along either the x or y axis from the current location.

  2. Add code to the draw(PApplet) method your SnowStudentRace class to move bucky a small amount to the right on each draw().  Hint: to move right, call the move method of your bucky instance with a small positive value for dx and the value 0 for dy.  To make it more interesting, pick a random number of pixels (0-2 is good) to move right on each draw.  Just make sure that your head and body move with the base of the SnowStudent.  If they don't, trouble-shoot your draw(applet) method in the SnowStudent class until bucky moves across the finish line.

3.e) Add a player that is controlled by key strokes

  1. Course showing bucky and player at the starting lineWhen bucky is moving correctly (with name and all parts moving too), add another SnowStudent instance to the race applet.  Construct this SnowStudent so that it starts with the name A.  (This is what the player will have to type to start the race).  The name of the instance field can be player or whatever you want to call the other racer in this game.

    Be sure that you declare this SnowStudent instance as a field of your SnowStudentRace applet, initialize the field in the setup() method, and draw the instance in the draw() method.   Hint: get this player SnowStudent on the starting line at a different height than Bucky.

  2. Run your applet to see both Bucky and the player at the starting line. Close applet and continue.

3.f) Get the player moving when you type the player's name.

For this game, the player's name is a single letter that changes as the player type.  It starts with the letter "A", and each name that is typed moves the SnowStudent player and changes its name.   To do this, we will need to handle events that the Graphic User Interface (GUI) sends to us.

  1. Complete the public float getRightEdge() method in your SnowStudent class.  This should return the x-coordinate of the right-most edge of the SnowStudent. Hint: x-coord + radius of the base snowball of the SnowStudent:

  2. Add this public boolean checkForWin(SnowStudent) method to your SnowStudentRace applet class:
        private boolean checkForWin(SnowStudent ss) {
            if ( ss.getRightEdge() >= FINISH_LINE ) {
                ss.setName("WINNER");
                return true;
            }
            return false;
        }
  3. Add this public void keyPressed() method to your SnowStudentRace applet class.  This overrides the keyPressed() method that is inherited and becomes the key-press event handler for your SnowStudentRace applet. An "event handler" is a method that gets called automatically when an "event" occurs. In this case, the event is the player typing a key on the keyboard.
        public void keyPressed() {
    //uncomment for development and debugging //println("typed " + (int)key + " " + keyCode);
    String k = ""+key; // get key (char) that player typed String K = k.toUpperCase();

    // If the race has not started and the player typed 'A',
    // START RACE! if ( !started ) { if ( K.equalsIgnoreCase("A") ) started = true; // START THE RACE } else if ( K.equals( player.getName() ) ) { // MOVE PLAYER player.move(10,0); // moves 10 pixels on each letter if ( ! checkForWin(player) ) {
    // Randomly pick a new letter for player to type player.setName(""+(char)(Math.random()*26+'A')); } } }
    You will also need to declare an instance variable started and add logic to make Bucky start moving after the player types A.

  4. Run your applet, and type A to get the race started. (Note that you may need to click inside the window before your program will respond to key presses.)  Hurry, type quickly to beat Bucky to the finish line.

Deb's solution