SOLVING THE ROBOT APPLET PROBLEM

So the first thing I do is to follow the basic 6 step method that the book outlines on page 27 and that we talked about in the first class:

UNDERSTAND THE PROBLEM

Here is the robot applet problem statement from the homework:

ROBOT APPLET

We will design a simple robot program that could form the basis for a more complex program later. Your job is to write an applet which displays and controls two robot teams.

For this you will first need to develop a Robot class. A Robot has a name and each robot instance should have a unique id number that is generated by the class. Robots know their current location on the screen (x,y). The robot constructor should take a name and a start location as parameters. The constructor should then be able to assign the robot its unique id number.  Robots need to also know what team they are on.

If a robot is given a graphics context (Graphics object) and it can draw itself at its current location. A robot should draw its shape and then print its name inside the shape.

Robots also move.  The command to move is given by cardinal direction.  To move up the screen, the command is robot1.move('n');  Likewise, 's' moves  down the screen, 'e' moves right, and 'w' moves left.  If a robot reaches the edge of the screen, then it should ignore any commands to continue in that direction.  Thus if a robot is all the way at the top of the screen and it receives a move('n') message, it should do nothing.   The movement distance on each turn is the robot's width (thus if the robot is a 50 pixel circle, it will move 50 pixels for each move messge received).

A Robot team is a collection of 3 robots. Each robot team has a team color. The team can draw itself on the current Graphics context. A team constructor takes the references to 3 robots as its team members, its movement method, and a color as its  team color.  The movement method should be coded as publicly available constants (RANDOM=1, LOCK_STEP=2);  If no movement method is provided, the team should move randomly.

Teams can move in one of two ways, random or lock-step.  If the team movement is random, each time a move message is received, each robot moves independently n, e, s or w.  If the team movement is lock-step, all the robots on the team will move in the same random direction (n,e,s,w).

The RobotApplet should create two teams of three robots each.  Each team should have a different color.   One robot team should move in lock-step, one should move random.  Every time the applet is started, the teams should move.  The robots should be created so that they are initially lined up vertically, one team on the left edge of the screen and one team on the right edge.

For this problem, you probably want to review the start/stop/init methods in section 4.6 of the text and the Diner problem we did in recitation last week.

For more advanced students (If you want to explore some more advanced things).:

You probably notice that sometimes a robot will overlap another because a team member hits a boundary  and can't move, but the second robot can and then gets hidden.  Add some logic to detect and prevent this condition.

TURN-IN

Robot.java

RobotApplet.java

RobotTeam.java

Break the Problem into Pieces

The next step is to break this big problem into smaller problems.  In object-oriented (OO) programming we use classes and methods to break down (or decompose) the problem.  Our first step is to think about what specific classes we need in order to be able to solve the problem.  Generally in OO, nouns give us possible classes that we can use.  So we have candidate classes from the problem statement like: Robot, Team, Applet, Program, Name, Color, Direction, Method, etc.  From the description , the major classes seem to be the Robot, the Robot Team and the Robot Applet.  (In 1322 of course you can check the turn-in list and it usually contains the major classes needed.)  The other nouns all refer to characteristics of these particular classes.  Sometimes it helps to visualize the problem as a real-world physical thing.  Picture robots on a team moving around in an arena.  Those tangible, physical things are usually your primary classes.

Design a Solution

Now that we have a set of classes we can start designing our solution.  The first step is to decide what behavior and what data are needed by each class.  The data can be derived by thinking about "what is it that each class needs to know?"  Let's start with the Robot class.  We look back at the problem description.  We see right away that Robots have a name and a unique id number.  They also know their current x,y position on the screen.  They also know what team they are on.  Again, sometimes it helps to visualize a real robot.  It is standing somewhere in the field wearing its team jersey with its name on it.  All that data needs to be encapsulated in the robot class.  Let's sketch out the code we have so far for the Robot class, turning our required data into Java types.  We know robot's have a shape, but there is no other specification.  We decide to use circles as an easy solution.  Because of this we will also need a constant representing the diameter of the circle.

/**

 * The robot class represents a single robot in the game

 */

public class Robot {

  /** the width of the robot shape */

  public static final int WIDTH = 50;

 

  /** the robots name */

  private String name;

 

  /** the robots id */

  private int id;

 

  /** the robots team */

  private RobotTeam myTeam;

 

 

  /** the robots current x location */

  private int x;

 

  /** the robots current y location */

  private int y;

 

}//end class Robot

 

Now that we know what instance data a robot needs, we need to concentrate on what a robot needs to be able to do.  Again, we refer back to the problem statement, this time looking for verbs.  It looks like there are two different key behaviors that a robot has, it can draw itself and it can move.  Movement is specified by a character input and drawing uses a Graphics context.  Knowing this we can stub out the two instance methods that all robots need to be able to do.  That gives us these two methods (which we would of course type inside the Robot class definition above):

 

/**

 * This method supports movement of the Robot.

 * When passed in the direction, the robot changes

 * its x and y location based upon the input data.

 * It only moves if it can remain on the screen.

 * It moves in increments of its width.

 *

 * @param dir the direction, given as a character n, e, s, w

 * @return nothing

 */

public void move(char dir) {

 

}//end method move

 

/**

 * This method draws the robot at its current x,y location

 * It uses a simple shape to render itself on the graphics

 * context.

 *

 * @param g the graphics context to draw on

 * @return nothing

 */

public void draw(Graphics g) {

 

}//end method draw

 

We also need at least one constructor.  What information do we know as soon as a robot is created?  We will know its name for sure.  The id is assigned uniquely within the constructor so that won't be a parameter.  We can have a robot outside a team, so we can't take in a team reference yet.  All robots have to be located somewhere, so we can take in the initial position, thus the constructor should take in 3 parameters, the name and the x and y location.  Of course an alternative design might say that the robots have no location (or they are all at (0,0)) until they get picked for a team.  This alternative  design would allow us to make the robot with just a name.  The problem however specifies that the robots know their initial position, so we decide to go with the 3 parameter constructor.  This gives us the constructor shell:

/**

 * Constructor for a robot

 * @param name the robots name

 * @param xloc the x coordinate of the robots initial location

 * @param yloc the y coordinate of the robots initial location

 */

public Robot(String n, int x, int y) {

 

}//end constructor

 

Now we are ready to design the robot team class.  Again, we peruse the problem statement for things a team needs to know about itself so we can establish what its instance data should be.  A team has 3 robots, so it needs to know who its robot team members are, there is the idea of a team color, and a special movement technique for each team.

 

Now we can stub out the RobotTeam class with the instance data we know about so far:

 

/**

 * The robot team represents a single team of 3 robots.

 */

public class RobotTeam {

 

  /** the three robots on the team */

  private Robot r1, r2, r3;

 

  /** the team color */

  private Color myColor;

 

  /** the movement method the team uses */

  private int myMoveMethod;

 

 

}// end class RobotTeam

 

Now we think about the behavior of a robot team. We return to the problem description and look for verbs that are associated with teams.  We see that like robots, teams draw and move.  This allows us to stub out the following methods:

 

/**

 * This method moves an entire robot team based upon its

 * correct movement method.

 *

 * @param no parameters

 * @return no return

 */

public void move( ) {

 

}//end move method

 

/**

 * This method draws the team on the current graphics context.

 *

 * @param g the graphics context to draw to

 * @return nothing

 */

public void draw(Graphics g) {

 

}//end draw method

 

Now we need to decide what the constructor for a RobotTeam would look like.  We look back at the problem description and see that when a team is created, it takes in the three robots, a movement method(technique) and a color.  This gives us the stub for the constructor as:

 

/**

 * Constructor for Robot Teams

 * @param r1 reference to robot 1

 * @param r2 reference to robot 2

 * @param r3 reference to robot 3

 * @param m integer code for team movement technique

 * @param c color of team

 */

public RobotTeam ( Robot r1, Robot r2, Robot r3, int m, Color c) {

 

} //end constructor

 

Now we are left with the RobotApplet.  Even though this is a top-level class, we still go through the same procedure as the other two classes.  The only difference is that the applet has less ties to the physical world and more ties to the computer itself.  We revisit the problem description looking for descriptive terms that might tell us what data a RobotApplet needs to know.  At this point we can pretty much ascertain that the applet only needs to know its two teams and not much else.  So we can stub out the applet as:

 

/**

 * The robot applet displays and controls two robot teams

 */

public class RobotApplet extends Applet {

 

  /** the two robot teams */

  private RobotTeam rt1, rt2;

 

}//end class RobotApplet

 

We now have to determine what behavior is required of the RobotApplet.  It is our "top-level" class, so it doesn't have to provide any services like move and draw that we saw in the other two classes. Instead, we have to use our knowledge of how applets work to figure out what methods we need.  First we have to get the robot teams created.  We do all our one-time initialization in the init method, so we know we will need that one.  We also know that each time the applet starts, we need to move the teams.  This signals that we have to implement the start method in the applet.  Finally of course, we know we actually have to draw stuff, so we know we have to have the paint method.  This gives us the standard applet methods :

 

/**

 * This method creates our two robot team and initializes the applet

 */

public void init() {

 

}//end init

/**

 * This method moves the robots on each start

 */

public void start() {

 

}//end start

/**

 * This methods paints the applet

 * @param g the graphics context to draw to

 * @return nothing

 */

public void paint(Graphics g) {

 

}//end paint

 

Remember that for applets, we don't have any leeway about what goes in the method signature.  They have to look just like this in order to get called automagically from the appletviewer or web browser environment.

 

Whew, that's alot, but now we have some stubbed out classes that define our major classes, their behavior and their instance data.  Now we are ready to actually implement some stuff.

 

Consider alternatives

This step is important for more advanced programs where there may be many alternative designs that we need to choose from.  We have some design alternatives to make decisions about, but some of the design decisions have been made for us.  We have to decide how to represent the movement techniques.  There are a bunch of ways: as strings, characters, int codes, special classes.  This design decision is made for us though.  They are constants.  Our only real decision is which class do they belong to?  The movement techniques most affect the teams.  An individual robot only has to know how to move in a specific direction.  Only a team has to know about group movement, so they probably belong in the RobotTeam class.  We also need to decide if they are public or private constants--do any other classes outside the RobotTeam need to know about the different movement techniques?  Yes we see that the constructors have to know the technique, so that means any class that creates a robot team has to have access to the constants so they need to be public.  Thus we add the following class data to the the RobotTeam:

 

/** define the movement method where all robots move together */

public static final int LOCK_STEP = 2;

 

/** define the movement method where all robots move individually */

public static final int RANDOM = 1;

 

We also have to decide how we should handle the screen limits.  A robot cannot be allowed to move off the screen, therefore we have to know what the maximum width and height are of the applet.  We can get these dynamically or we can set them statically.  Since the program does not require dynamic resizing I am going to make it easy on myself.  I will code some constants that represent the size of the movement area, no matter what the user does.  Now we have to decide where these constants should be located.  Although they directly affect the Robot class, they are really attributes of the applet class. I then decide I like the dimensions 400x300.  There being no other information in the problem to stop me from doing this, I add the following two statements to the RobotApplet class as class constants:

 

/** set the applet width for robot moves  */

public static final int MAXX = 400;

 

/** set the applet height for robot moves */

public static final int MAXY = 300;

 

Robots need to have a unique id assigned during their construction.  There are several ways we could handle this.  One way is to use random numbers.  Another is to use sequential numbers assigned from a static class variable.  I decide to use the static method.  This requires a new class variable in the Robot class and a new static method.

 

/** the next unique id number to be assigned to a robot */

private static int nextId = 0;

 

/**

 * A static method that gets the next unique robot id and then

 * increments the id.  Thus all robot ids are sequential and

 * in order of their creation.

 */

private static int getNextID() {

  nextId++;

  return nextId;

}

Implement Solution

Now we are ready to start filling in the code and fleshing out our application into something that can actually run.  Let's start at the top with the RobotApplet. 

Let's start with initialization and the init method.  When our applet starts up, we need to create our two robot teams.  This can be done fairly easily since we already have our constructors stubbed out.  We have to decide how to line up the robots.  Other than being vertical on opposite sides, we have no other guidance.  We decide to line them up with team 1 on the right edge and team 2 on the left edge.  We can easily compute a start location so that they are lined up like we want.   We also need one team of each movement type.  There is not other specification so we can choose which we like.  We decide to make team 1 random and team 2  lock_step  We also have no guidance on color, so we decide to make team 1 red and team 2 blue.  In the init method we fill in the code:

/**

 * This method creates our two robot team and initializes the applet

 */

public void init() {

 /* make a robot team.  we don't need to

    keep any references to the robots so

    we can make them anonymous objects.

    We decide to have team 1 be on the

    right edge of the screen and team 2

    be on the left since there are no

    instructions to the contrary.

 */

  rt1 = new RobotTeam(new Robot("Bob",MAXX-Robot.WIDTH-10,50),

                     new Robot("Fred", MAXX-Robot.WIDTH-10,125),

                     new Robot("Paul",MAXX-Robot.WIDTH-10,200),

                     RobotTeam.RANDOM, Color.red); 

 rt2 = new RobotTeam(new Robot("Alice",Robot.WIDTH+10,50),

                     new Robot("Sally",Robot.WIDTH+10,125),

                     new Robot("Becky",Robot.WIDTH+10,200),

                     RobotTeam.LOCK_STEP,Color.blue);

}//end init

 

We will now do the start method implementation.  We know every time the applet starts the robots have to move.  I know the RobotTeam knows how to move and I know the applet has a reference to the two robot teams, so I can just directly call their move methods in the applet start method.  The only tricky part is that the teams must line up on the side of the screen the first time the applet loads.  That means I need to have a way to tell whether this the first time that start has been called.  There are several ways we might think of, but the simplest is to add a new instance variable to the applet that is boolean.

/** flag to mark first time applet starts */

boolean bFirstTime;

 

Then we go back to our init method above and add the following line of code:

 

bFirstTime = true;

 

Now we are ready to fill out the implementation of the start method.  It is actually trivial since teams know how to move already.   We end up with:

/**

 * This method moves the robots on each start

 */

public void start() {

  if (!bFirstTime) {

    rt1.move();

    rt2.move();

  } else

    bFirstTime=false;

}//end start

 

Whew, almost done with the whole RobotApplet class implementation.  Only thing left is paint, which just asks the teams to draw themselves.  Its implementation is trivial.

 

/**

 * This methods paints the applet.  We just ask the robot teams

 * to draw themselves, passing them the graphics context

 * @param g the graphics context to draw to

 * @return nothing

 */

public void paint(Graphics g) {

  rt1.draw(g);

  rt2.draw(g);

}//end paint

 

So there is all the RobotApplet implementation done and ready for test.  We can't start testing till we get the other classes implementations done however. So let's go to RobotTeam.

 

First let's fill in the implementation for the constructor of RobotTeam.  We set all the instance variables to their correct values, but wait.  There is a flaw in our design.  There is no way to tell a robot what team they are on.  We go back to design for a minute and recognize that we need a method in Robot to set a team.  We decide on a signature (public void setTeam(RobotTeam)) so we can continue our design.  We just have to remember to add this method when we start work again on Robot.

 

/**

 * Constructor for Robot Teams

 * @param r1 reference to robot 1

 * @param r2 reference to robot 2

 * @param r3 reference to robot 3

 * @param m integer code for team movement method

 * @param c color of team

 */

public RobotTeam ( Robot r1, Robot r2, Robot r3, int m, Color c) {

  this.r1=r1;

  this.r2=r2;

  this.r3=r3;

 

  //set the robot's team to this team

  r1.setTeam(this);

  r2.setTeam(this);

  r3.setTeam(this);

 

  myMoveMethod=m;

 

  myColor=c;

 

} //end constructor

 

OK, so that was easy enough, except for our small design flaw.  It's important to note that even experts use an interative design-implement-design process.  No one is so good (unless the problem is trivial) that they can get the full design correct the first time.  Now lets tackle the draw method.  That one is fairly simple also.  We simply have to tell each of the robots on our team to draw itself.

 

/**

 * This method draws the team on the current graphics context.

 *

 * @param g the graphics context to draw to

 * @return nothing

 */

public void draw(Graphics g) {

  r1.draw(g);

  r2.draw(g);

  r3.draw(g);

}//end draw method

 

Now for the hard one, move.  We have to use two separate algorithms based on the movement type.  We decide to have two separate methods that are private that will handle each of the two algorithms and the public move method will just choose which to use.  If we think about it, both methods will need to get a letter from a random number, so we need a short utility method that lets us get a random direction character.  We always keep in mind that methods in OO should be relatively short, and that helps us decompose the work into multiple methods.  If you have methods over 10 lines, you think about whether you are doing too much work in a single method.

 

/**

 * This method moves an entire robot team based upon its

 * correct movement method.

 *

 * @param no parameters

 * @return no return

 */

public void move( ) {

  switch(myMoveMethod) {

    case RANDOM:

       moveRandom();

       break;

    case LOCK_STEP:

       moveLockStep();

       break;

    default:

       //if we don't know what to do, do nothing

       System.out.println("ERROR--incorrect move method in RobotTeam");

  }//end switch

 

}//end move method

 

/**

 * For random moves, each robot goes a random direction

 */

private void moveRandom() {

  r1.move(getDir());

  r2.move(getDir());

  r3.move(getDir());

}//end method moveRandom

 

/**

 * for lock step moves, all robots go the same direction

 */

private void moveLockStep(){

  char ch = getDir();

  r1.move(ch);

  r2.move(ch);

  r3.move(ch);

}//end method moveLockStep

 

/**

 * generate a random direction

 */

public char getDir() {

  int dir = (int) (Math.random()*4);

  String dirChars = "news";

  return dirChars.charAt(dir);

}//end method getDir

 

OK, we are done with the implementation of RobotTeam and now we are ready for the Robot class itself.  We need to do the constructor so:

 

/**

 * Constructor for a robot

 * @param name the robots name

 * @param xloc the x coordinate of the robots initial location

 * @param yloc the y coordinate of the robots initial location

 */

public Robot(String n, int x, int y) {

  name=n;

  this.x=x;

  this.y=y;

}//end constructor

 

Now we add the method we forgot that we identified earlier:

 

/**

 * Assign the robot to a team

 */

public void setTeam(RobotTeam t) {

  myTeam = t;

}

 

Now for the draw method.  Oops, we realize that we need a way for the robot to ask a team what its color is.  Again we make a note we need to add a method to RobotTeam pubic Color getTeamColor() that will return a team color for us to use.

 

/**

 * This method draws the robot at its current x,y location

 * It uses a simple shape to render itself on the graphics

 * context.  It then draws its name in the center of the

 * shape.

 *

 * @param g the graphics context to draw on

 * @return nothing

 */

public void draw(Graphics g) {

  Color c = myTeam.getTeamColor();

  g.setColor(c);

  g.fillOval(x,y,WIDTH,WIDTH);

 

  g.setColor(Color.black);

  g.drawString(name,x+10,y+WIDTH/2);

 

}//end method draw

 

Now we need to flesh out the move method.  We basically know from the problem description 'n' makes the robot move up 1 width, 's' makes the robot move down one width, 'e' makes the robot move right 1 width and 'w' makes the robot move left 1 width.  The other specification limit on moves from the instructions is that the robot cannot move off the playfield.  We made the playfield coordinates as static constants in the applet, so we can use those constants to check that we are not going to move off the screen.

 

 

/**

 * This method moves the robot 1 unit based upon the input direction.

 * 'n' = WIDTH pixels up

 * 's' = WIDTH pixels down

 * 'w' = WIDTH pixels left

 * 'e' = WIDTH pixels right

 * We also do bounds checks to ensure we do not move off the screen.

 * If any move takes us off the screen, then leave at current position

 *

 * @param dir the direction to move as 'n','e','w' or 's'

 */

public void move(char dir) {

  switch (dir){

    case 'n':

      if(y>WIDTH)

        y-=WIDTH;

      break;

    case 's':

      if (y<RobotApplet.MAXY-WIDTH)

        y+=WIDTH;

      break;

    case 'w':

      if (x>WIDTH)

        x-=WIDTH;

      break;

    case 'e':

      if (x<RobotApplet.MAXX-WIDTH)

        x+=WIDTH;

      break;

    default:

      System.out.println("ERROR -- invalid move direction in Robot.move = "+dir);

      break;

  }//end switch

}//end move

 

Now we jump back to Robot Team and add our getTeamColor method which just returns a color.  If I just made blanket accessors and modifiers (getters and setters) we would already have all these methods, but as a personal preference, I like to add only the ones I need, which protects my class data even more.

 

/**

 * This method returns the current team color

 *

 * @return team color as a Color object

 */

public Color getTeamColor() {

  return myColor;

}//end method getTeamColor

 

 

Whew, and there we go.  All the code is written for the basic specification.  Now are we done?  No!!!!! The jobs not over until the code compiles cleanly and and is tested. 

 

Test the Solution

One technique that a lot of Java programmers use, is to create a main method for every class (even though we only need one) and use that main method to test the class as much as possible.  To use some CS buzzwords, testing our individual classes is known as unit testing, and testing the whole application at once is known as system testing.  We always start with unit testing.

Lets do unit testing on the Robot class first.  We ended up with the following methods:

public void move(char dir)

public Robot(String n, int x, int y)

public void draw (Graphics g)

public void setTeam(RobotTeam t)

 

It would be hard to test the draw method outside the system because we can't easily create the graphics context. Also to test setTeam would require a lot of overhead for a one line method, so that's not cost-effective.  The constructor and move method are somewhat complex and can be tested outside any other classes, so let's do that.  For the constructor we need to make a robot and then check that its data is properly set.  Likewise, for move we need to make some robots right on the edges and try to move them so we can see that the move method works correctly.

 

/**

 * This main method is for unit testing the Robot class

 */

public static void main(String[] args) {

  //first lets make a new robot in the upper left corner of the screen

  Robot rul = new Robot("rul",0,0);

  if (rul.id !=1)

    System.out.println("Error in assigning unique id");

 

  //Now we know that this robot should not move if given 'n' or 'w'

  //as a move dir, so we check that it does not move

  rul.move('n');

  rul.move('w');

  if (rul.x!=0 && rul.y!=0)

    System.out.println("Error, allowed robot to move off screen");

 

  //if we move 'e' x should change by WIDTH, but y remain same

  int oldx = rul.x;

  int oldy = rul.y;

  rul.move('e');

  if(rul.x!=WIDTH+oldx)

    System.out.println("Error, move e too much.  got= "+rul.x+" expected "+(oldx+WIDTH));

  if(rul.y!=oldy)

    System.out.println("Error, e move caused y to change");

 

  //if we move 's', y should change by WIDTH, but x remain same

  oldx=rul.x;

  oldy=rul.y;

  rul.move('s');

  if (rul.x != oldx)

    System.out.println("Error, x changed during s move");

  if (rul.y != oldy+WIDTH)

    System.out.println("Error, y move incorrect. got= "+rul.y+" expected "+(oldy+WIDTH));

 

  //we could also now create a robot in the lower right corner and do the same kind of tests

  //but I think you get the idea.

}//end main

 

When we run the test code above, we see that there is an error printed out "Error in assigning unique id".  If we look back at the constructor for Robot we see that we did not initialize the id.  We add the statement:

 

id=Robot.getNextID();

 

Now all our tests for Robot succeed.

 

Now we have done the unit testing, we need to test everything all together to make sure it meets the specification.  We need to start up the applet, and make sure we have three robots on a side lined up vertically.

 

Then we start and stop the applet to ensure the robots do in fact move correctly.  We move several times to ensure they do not go off the screen.

 

If we look at the lineup, the blue team on the left seems to line up too far to the right.  We go back and adjust the constructors for the three robots of the blue team to remove the Robot.WIDTH from the x expression of their intial location.  This will put them 10 pixels in and make the teams look more balanced at startup.  We make that correction and rerun the system.

 

The system tests now appear to be successful, so we are ready for submission.

 

The Final Code Submission

Here are the three final code files:

 

RobotApplet.java

Robot.java

RobotTeam.java