Session 3: Networking

Develop a program in which users on different computers can interact over the network.
  1. Sending and receiving messages
  2. Getting information from a variable
  3. Converting numbers and text
  4. Writing a constructor method
  5. Making decisions
  6. Creating the Tic-Tac-Toe game

1. Sending and receiving messages

Open up the program "Chatty.java" in the "S3-Networking" project. Before you run it, scroll down to the onEnterPress() method. This method, as you can tell, sends a message across the network. You can change what is written in quotes as a method parameter to change the message you will send for everyone else to see. (Something written in quotes is an object of the String class, which is Java's way of representing text)

Now, run the program. You will notice a new menu, called "Network". I'm going to "host" a game on my computer; then you'll be able to "join" that game. Once you have joined, press the enter key to send your message. All the messages sent are shown on everyone's screen.

Lets look back at the program and see if we can figure out how it works. You should be able to figure out most of the code based on what we have done so far. The program has two instance variable: outputPoint remembers where on the screen the next message should appear, and network is the Client object we will use to send messages. You can see that when a "client connected" event happens, I store the client in that instance variable so that the other methods can use it. You can also see that when a "message received" event occurs, I make a Text object to display it, and then move the output point down on the screen. Finally, there is the onEnterPress() method that we already noticed.

There are only two new things that show up in this program. The first is a method call to Network.configure() in begin(). Here, Network is the name of a class, not a variable; the class, rather than any obect that is an instance of it, is what is being asked to configure(). If you press control-space after the "(" to see what the parameters are, you'll see this:



So, "Chatty" must be a String object giving the name of the game, and something called ServerListener.ECHO is handling the server side of things. But the last parameter is supposed to be of type ClientListener, and I passed this - that is, the program itself - which is a Chatty object. Is my program somehow also a ClientListener object at the same time?

ServerListener is not actually a class at all; it is something called an interface. An interface is a list of methods that a class needs to have if its objects are to be used in a particular way. The definition of ClientListener looks like this:

public interface ClientListener {
    public void clientConnected(Client client);
    public void messageReceived(Client client, String message);
    public void clientDisconnected(Client client);
}

As you can see, it is just a list of method signatures, with no code at all. In order for Chatty to be able to be used as a ClientListener I had to do two things. First, I had to add all three of those methods to the Chatty class. This is why I made the clientDisconnected() method, even though it does nothing at all. Then, in order to tell Java that I meant for Chatty to act as a ClientListener, I changed the class signature:

public class Chatty extends FrameWindowController implements ClientListener {

Here "implements ClientListener" can be read as "has all the methods needed to be considered a ClientListener."

Quick exercise:
In addition to methods like onSpacePress() and onSpaceRelease(), there is a method that is called when a key event resulted in text being produced:

public void onKeyTyped(String typed)

As you can see, what was typed is represented as a String, which makes it perfect to send out over the network. Change Chatty so that whenever a key is typed, it is sent out as a message.

If you want more of a challenge, store the message you are writing in a String instance variable. That variable can start equal to "", and then when a character is typed, you can add it to the end. If my instance variable is called text, I can add typed to the end of it by just doing this:

text = text + typed;

Have the program send the message when enter is pressed.
Give me a hint?
Sending every key as a message should be as simple as remembering what method to use to do that. If you want to compose a message and send it with "enter," you'll need to do three things:

2. Getting information from a variable

Our next goal will be to write a program in which clicking sends a message that makes a spot appear where you clicked on all the connected computers. In order to do this, I have to be able to somehow extract the x and y coordinates from a Location and turn them into a String. How do I get information out of a variable?

The methods you've seen so far all have a signature that starts with "public void..." The word void means that no information comes back from that method; you just send off the parameters to it and it does its thing. Some methods, however, will return a value.

An example of this is the method getLocation() in FramedRect (and all the Rect and Oval classes). It has a signature that looks like this:

public Location getLocation()

If I write some code calling that method, the whole method call - rect.getLocation(), in the example below - will be replaced by a Location object that is returned when the method runs. So, for example, this code will create an oval with the same top left corner as

FilledRect rect = new FilledRect(30, 70, 120, 80);
System.out.println(rect.getLocation());

This code prints out "new Location(30, 70)". It is printing the Location object returned from rect.getLocation().

You can see a more complex example of retrieving information from variables in the RectangleDecorator program. It starts out working like the LineDrawing program you have seen before, but when the mouse is released, it replaces the line with a rectangle, using the line.getStart() and line.getEnd() method calls to retrieve the start and end points of the line. Then, it passes along that rectangle to a method called decorate(), that automatically puts ovals at each corner of whatever FilledRect it is given. Can you see how it does this?

Exercise: RotateColors
The method getColor(), which all ObjectDraw objects have, returns the object's Color. You can change an object's color using setColor(), passing the color as a parameter. So, for example, this code would swap the colors of two objects object1 and object2:

Color color1 = object1.getColor();
Color color2 = object2.getColor();
object1.setColor(color1);
object2.setColor(color2);

What I would like you to do in this assignment is a little bit more complicated: I would like you to alter the color of object, changing it to a new color whose red component is equal to the old blue component, whose green is equal to the old red, and whose blue is equal to the old green. In the program RotateColors, you will be filling in the onMousePress() method to rotate the color of oval in this way.

You know from before that a Color object is made with three numbers: the amount of red, green, and blue that go into it. So, for example, new Color(100, 0, 150) is a purplish color, made up of red and blue. You can retrieve these components with getRed(), getGreen(), and getBlue(). They are integer values, so if you keep them in a variable, it should be of type int. However, it is probably easier to pass off the values directly as parameters in creating a new color.
Give me a hint?
I would suggest that you do this in three instructions:
  1. Retrieve oval's color and store it in a Color oldColor.
  2. Create a new color, using oldColor.getRed(), etc, as the parameter values. Store it in a Color newColor.
  3. Set the color of oval to the newColor.
    OK, just tell me.
    My code looks like this:

    Color oldColor = oval.getColor();
    Color newColor = new Color(oldColor.getBlue(), oldColor.getRed(), oldColor.getGreen());
    oval.setColor(newColor);

3. Converting numbers and text

Now that we know how to find the actual number that describe a shape, we are halfway to being able to send that information over the network. However, I still have the problem that the number is an int and I can only send a String.

The integer class will help us out here. It has methods to convert an int into a String, and a String back into an int:

public String toString(int n)
public int parseInt(String s)

So, if I wanted to send out the x coordinate of the mouse location whenever the mouse is pressed, and have each client draw a vertical line there, I could do this:

public void onMousePress(Location point) {
    String message = Integer.toString(point.getX());
    network.sendMessage(message);
}

public void messageReceived(Client client, String message) {
    int x = Integer.parseInt(message);
    new Line(x, 0, x, 400, canvas);
}

If I want to send the whole location of the mouse press, things get a bit trickier, because I need to pack multiple numbers into one String message. Creating the string is easy:

String x = Integer.toString(point.getX());
String y = Integer.toString(point.getY());
String message = x + "," + y;

Because I am adding String objects, they are just joined end to end, not added mathematically. So, if I point was equal to new Location(184, 90), then this code would set x equal to "184", set y equal to "90", and then create a message equal to "184,90".

The tricky part is how to extract the parts of this message. Operating with Strings is a complicated area with a lot of tricks to learn, but I can give you just one pattern to use that will serve for everything you do in this course:

int commaPosition = message.indexOf(",");
String start = message.substring(0, commaPosition);
String end = message.substring(commaPosition + 1);

Briefly, what this does it to figure out where the comma shows up in the string, and then cut out two "substrings:" one containing all of the string from the start up to the comma, and one containing everything after the comma.

Here's how I would use this idiom to get the x and y back out of my message:

public void messageReceived(Client client, String message) {
    int pos = message.indexOf(",");
    int x = Integer.parseInt(message.substring(0, pos));
    int y = Integer.parseInt(message.substring(pos + 1));
    new FilledOval(x - 5, y - 5, 10, 10, canvas);
}

Again, this code looks complicated, but remember what I am doing: I am finding where the comma is, and then chopping off the bit of the string before it, and the bit after.

The program Spotty.java encodes and decodes locations in the way I showed here in order to send the location of a mouse press across the network and create an oval at that location on everyone's screens. So, if I run the server on my computer, we can all connect to it and create spots on a shared screen.

Take a careful look at the methods encodeLocation() and decodeLocation(), since these are the first example you've seen of writing a method that returns a value. All that I have done is to change the void in the method signature to the type of object the method will return, and then have the last line of the method say return followed by an object to return.

Exercise: Encoding a line
Suppose that I want to be able to send a Line in a message, so that new Line(new Location(10, 20), new Location(50, 80), canvas) appears in the format "10,20;50,80". Notice that this is just the format for a Location, followed by a semicolon, followed by another Location.

The program NetLineDrawing has methods encodeLine() and decodeLine() that need to be filled in in order for it to work properly. You will find that they end up looking very much like encodeLocation() and decodeLocation(), which I have copied over because you will probably find them useful. Fill in what is missing in these methods, changing the return values from null - a placeholder meaning "no object" - into whatever really ought to be returned.
Give me a hint?
Model encodeLine() after encodeLocation(), changing x and y to be start and end. Of course, you won;t want to use Integer.toString() to encode the start and end, since they are Locations; what method do you have to encode a Location?

Likewise, model decodeLine() after decodeLocation(). You will decode the two parts of the message and store them in Location start and Location end. Then, make a Line out of them and return it.
OK, just tell me.
My code looks like this:

public String encodeLine(Line line) {
    String start = encodeLocation(line.getStart());
    String end = encodeLocation(line.getEnd());
    return start + ";" + end;
}

public Line decodeLine(String message) {
    int pos = message.indexOf(";");
    Location start = decodeLocation(message.substring(0, pos));
    Location end = decodeLocation(message.substring(pos + 1));
    return new Line(start, end, canvas);
}

4. Writing a constructor method

In order to make the TicTacToe game in the next section, there is one more problem we need to solve, because we will want that game to be played in a window that is 600 by 600, and so far all our programs automatically run in a window 400 by 400. Based on how we make other objects, it might be reasonable to expect that I could accomplish this by code like:

new Spotty(600, 600);

However, this does not work. The problem is that I have not defined a method to be used to construct Spotty objects.

When you write code like new Location(33, 195), you are actually calling a method. In this case, the method involved has this signature:

public Location(double x, double y)

Two things make it clear that this method has a special purpose: first, it has no return type (not even void), and second, it has the same name as its class.

Suppose that I want to modify Spotty so that I can set the background color when I create the program. I could create a constructor method that would look like this:

public Spotty(Color background) {
    canvas.setBackground(background);
}

Then, when I create the program in main(), I would do this:

public static void main(String[] args) {
    new Spotty(Color.GRAY);
}

Notice that, now that I have a constructor method, and it requires a Color parameter, it is no longer allowed to just do new Spotty().

Still, I haven't figured out how to make my Spotty a different size. I could create a constructor with width and height parameters, but what would I do with them. The problem here is that the width and height of the window - in fact, the window itself - are not in my part of the program at all. They are properties of a class called FrameWindowController. My program Spotty "inherits" all properties of FrameWindowController, because in the class signature I wrote that Spotty extends FrameWindowController:

public class Spotty extends FrameWindowController implements ClientListener {

You can think of a Spotty object as being a FrameWindowController object with just a few extra features tacked on to it. The window that you see - and the canvas that you paint on - all belong to the FrameWindowController; all that is new is the extra methods you have added.

You can think of it this way: When someone makes a police car, they start out with an ordinary car, and just add a radio and a siren. In Java, we would say that Car is the superclass of PoliceCar - a police car is just a car with more features added.

It's important to know what your superclass is, because FrameWindowController does have a way to set its width and height. There is a constructor method that looks like this:

public FrameWindowController(int width, int height, String title)

So, I could create a FrameWindowController that was 600 wide and 400 high like this:

new FrameWindowController(600, 400, "My new window");

The problem is, how do I get Spotty to tell the part of itself that is a FrameWindowController to construct itself that way? It turns out that there is a special keyword super that can be used to refer to the part of an object that is its superclass:

public Spotty() {
    super(600, 400, "Spotty");
}

You can think of that line as being a shorthand way of saying "super = new FrameWindowController(600, 400, "My new window");", which is in fact how it is done in some other programming languages. The super() call has to be the first things in a constructor method, since you can't start building on to your superclass before it has been made.

Quick exercise:
Create a constructor for TicTacToe that will call the constructor of the superclass, specifying a window 600 by 600 with the title "Tic-Tac-Toe".
Give me a hint?
This should be just a matter of copying the constructor I wrote for Spotty, above, and changing the parameters a bit. Just remember that a constructor method needs to have the same name as the class.
OK, just tell me.
My constructor looks like this:

public TicTacToe() {
    super(600, 600, "Tic-Tac-Toe");
}

5. Making decisions

So far, all of the programs we have written always make the same response when a particular method is called. We've used the parameters to our mouse methods to adapt the response to the point where the even occurred, but the method is still always doing the same thing there.

The method below is somewhat different - when the mouse is clicked, it creates an oval of one of two colors, depending on whether the click was on the top or bottom of the screen:

public void onMousePress(Location point) {
    Color color;
    if(point.getY() < 200) {
        color = Color.RED;
    } else {
        color = Color.BLUE;
    }
    point.translate(-5, -5);
    new FilledOval(point, 10, 10, color, canvas);
}

The new magical word here is if. When the computer is running the method and hits that if instruction, it checks whether the thing in parentheses is true or not. If it is true, it runs the block of code right after the if. If not, it skips on to the block of code after the else. Either way, after it is done running that block of code, it continues with the next instruction after the else block.

So, here, for example, we start by creating a variable color that has no value yet. Then, we check the y coordinate of point, and we set color to either red or blue based on where it is. We then continue on with translating point and making a FilledOval with the stored color.

You can see in "DecisionMaker.java" a slightly more complicated example, where I use an instance variable to count how many shapes have been made, and I draw either an oval or a rectangle depending on whether the count is even or odd:

if(count % 2 == 0) {
    new FilledOval(point, 10, 10, color, canvas);
} else {
    new FilledRect(point, 10, 10, color, canvas);
}
count = count + 1;

The operator "%" gets the remainder when dividing one number by another. So, if I divide count by 2 and the remainder is zero, it is even. Notice that I use "==" to check if two things are equal; "=" is already used for assigning a value to a variable. The other comparisons you can make are "<=", ">=", and "!=" (not equal).

The value of a comparison operation has a type: it is called a boolean. So, this is valid code:

boolean tooBig = (x >= 100);
if(tooBig) ...

If you want to make more than one comparison at once, you can string them together with && and ||, which stand for "and" and "or." So, this will test if x is between 1 and 100:

if(x >= 1 && x <= 100) ...

The opposite of that would be testing if x is either less than 1 or greater than 100:

if(x < 1 || x > 100) ...

6. Creating the Tic-Tac-Toe game

Now it is time to put together everything you've learned to create a full game. We'll start with a very simple one: Tic-Tac-Toe.

You've already created a constructor for TicTacToe to make its window be 600 by 600. This will end up being convenient because now each of the squares on the board will be exactly 200 by 200, instead of 133.33 by 133.33 as it would be in a 400 by 400 window.

Part 1: Getting the graphics working
The first part is very much a review of what we did in the last session. When the program starts, it should draw a tic-tac-toe board on the screen. Then, write methods to place X's and O's on the screen:

public void markX(Location center)
public void markO(Location center)

Remember that you can set the line width of a Line or FramedOval to make it more substantial-looking. Test out your markX() and markO() by calling them from begin() or by attaching them to mouse events (for example, pressing the mouse might make an X and releasing it might make an O).

Instead of translating center around to all the points you want to draw at, it might be easier to use center.offset(), which returns a new location instead of moving center.
Give me a hint?
If you're confused about the X, remember that it's just two crossed lines. I chose to make my grid lines 20 wide, and the X and O have lines 50 wide, with their corners 60 away from center. Remember that you can turn the grid on to help you figure things out, and don't be afraid to try something, see how it looks, and then change it if you have to.
OK, just tell me.
My code looks like this:

public void begin() {
    boardLine(new Location(200, 50), new Location(200, 550));
    boardLine(new Location(400, 50), new Location(400, 550));
    boardLine(new Location(50, 200), new Location(550, 200));
    boardLine(new Location(50, 400), new Location(550, 400));
}

public void boardLine(Location start, Location end) {
    Line line = new Line(start, end, canvas);
    line.setLineWidth(20);
}

public void markX(Location point) {
    Line line1 = new Line(point.offset(-60, -60), point.offset(60, 60), Color.RED, canvas);
    line1.setLineWidth(50);
    
    Line line2 = new Line(point.offset(60, -60), point.offset(-60, 60), Color.RED, canvas);
    line2.setLineWidth(50);
}

public void markO(Location point) {
    FramedOval oval = new FramedOval(point.offset(-60, -60), 120, 120, Color.BLUE, canvas);
    oval.setLineWidth(50);
}

Notice that I decided to make a separate method to create a board line, since each one has to have its width set.


Part 2: Placing your marks
Write a method called mark() that will place a mark in a particular (x, y) grid location:

public void mark(int x, int y)

The x and y here are not screen locations; they are row and column numbers in the grid. (0, 0) is the top left grid square. So, (2, 1) would be the middle of the right side. Remember that you can do math with a number variable.

The mark placed should be either an X or an O, depending on whose turn it is. So, you will have to keep track of the turn number in an instance variable, and draw one mark or the other depending on whether it is even or odd. You've seen me do something like this with count in DecisionMaker.

You can test out your mark() method by calling it a few times from begin() to make sure it works as intended.
Give me a hint?
I would suggest that you start out by creating and storing a Location center. Notice that for x = 0, 1, 2 the screen location of the center is x = 100, 300, 500; there should be a pattern there that you can exploit.

To add in your turn variable, look at what I did with count in DecisionMaker: I declared it at the top of the class block, gave it a starting value in begin(), used if(count % 2 == 0) to check if it was even, and then added one to it once I had checked it.
OK, just tell me.
My code looks like this:

public class TicTacToe extends FrameWindowController implements ClientListener {
    int turn;
    
    ...
    
    public void begin() {
        turn = 0;
        
        ...
    }
    
    public void mark(int x, int y) {
        Location center = new Location(x * 200 + 100, y * 200 + 100);
        
        if(turn % 2 == 0) {
            markX(center);
        } else {
            markO(center);
        }
        turn = turn + 1;
    }
    
    ...
}


Part 3: Networking
At this point, all we have left to do is to:I've given you copies of the encodeLocation() and decodeLocation() methods that we wrote before, so sending and decoding the message should be simple. The main tricky bit is converting the x and y on the screen into the x and y of the appropriate grid.

Before you start writing a massive set of if instructions to decide what grid was hit, let me point out that when dividing an integer by another integer in Java, I get only the whole number part of the result, not the remainder. So, if I divide 78 by 10, I get 7, because 10 goes 7 times into 78; the remainder of 8 is discarded. A little thought should show you how to use this to convert 0-199 into 0, 200-399 into 1, and 400-599 into 2.
Give me a hint?
Just dividing the screen x by 200 gives the grid x, and the same is true for the y. For encoding and decoding, just look back at how we did it in Spotty.
OK, just tell me.
My code looks like this:

public void messageReceived(Client client, String message) {
    Location point = decodeLocation(message);
    mark(point.getX() / 200, point.getY() / 200);
}

public void onMousePress(Location point) {
    if(network != null) {
        network.sendMessage(encodeLocation(point));
    }
}

The check if(network != null) solves a problem you might have noticed before, where an error (a NullPointerException) occurs if I try to call a method of network before the variable has been given a value.


We have now successfully made a network game in Java. Using these techniques, you can make much more elaborate games as you learn more about how to write code in Java. In the next session, we will learn how to implement the server to do something more than simply echo messages to all the clients. We will also learn how to animate the game by using timers.

back to Creating Network Games with ObjectDraw.