Man, this class is pretty... tough!

Lecture Notes Five: Anatomy of a Simple Networked Game


In which we take things apart only to put them back on.

Let's describe this application in stages, OK?

There will be a client. (Actually many clients are allowed in this game).

The client looks like this:

import java.rmi.*;
import java.rmi.server;
import java.awt.*;
import java.awt.event.*; 
import java.awt.image; 

public class Client extends Frame implements ClientExports, KeyListener {

}
It's a Frame that exports some methods, and processes key(board) events.

Let's see what the client exports (at this stage):

import java.rmi.*; 

public interface ClientExports extends Remote {
    public void update(int player, int x, int y) throws RemoteException; 
}
We'd like a client to be updated on the position (x, y) of any player when the player moves. So we provide this hook, a method that can be invoked remotely, for the client to receive new information when the server has it.

Let's refine our Client definition:

import java.rmi.*;
import java.rmi.server;
import java.awt.*;
import java.awt.event.*; 
import java.awt.image; 

public class Client extends Frame implements ClientExports, KeyListener {

    static int WIDTH = 300, int HEIGHT = 200; // size for all clients  
    static int hInset = 30, vInset = 30; // insets when placing players 

    int x, y,          // where the client's player is positioned 
	width, height; // actual width and height of this frame 

    Player[] players = new Player[100]; // all the players in the game 

    int numb; // a number, the index of this client's player in the array

    ServerExports server; // the server to which we will connect 

    public Client(int x, int y, 
		  int width, int height, 
		  ServerExports farAway
		  ) 
    { 
	super("You are a client..."); // call the superclass constructor 

	this.width = width; this.height = height; // set frame's size 

	System.out.println("Starting up client..."); // keep user informed

	numb = -1; // this is an invalid index

	this.x = x; this.y = y; // initialize your player's position

	server = farAway; // make a note of where your server is 

	addKeyListener(this); // starting paying attention to the keyboard

    }
}
So we have a constructor and the instance variables defined.

We need to clarify three things:

First, let's see what the server looks like from far away.

import java.rmi.*; 

public interface ServerExports extends Remote {

    public int register(ClientExports client) throws RemoteException;

    public void broadcast(int name, int x, int y) throws RemoteException; 

}
It allows clients to as for two things:

How the server actually does all this, we'll see in a minute.

For now let's answer the remaining two questions:

import java.rmi.*;
import java.rmi.server;
import java.awt.*;
import java.awt.event.*; 
import java.awt.image; 

public class Client extends Frame implements ClientExports, KeyListener {
    static int WIDTH = 300, int HEIGHT = 200; 
    static int hInset = 30, vInset = 30; 
    int x, y,          
	width, height; 
    Player[] players = new Player[100]; 
    int numb; 
    ServerExports server; 
    public Client(int x, int y, 
		  int width, int height, 
		  ServerExports farAway
		  ) 
    { 
	super("You are a client..."); 
	this.width = width; this.height = height; 
	System.out.println("Starting up client..."); 
	numb = -1; 
	this.x = x; this.y = y; 
	server = farAway;
	addKeyListener(this); 
    }

    public static void main(String[] args) {

	try {

	    ServerExports farAway = 
		(ServerExports)Naming.lookup(
		    "rmi://" + args[0] + ":" + args[1] + "/Dirac"

	    // e.g., rmi://burrowww.cs.indiana.edu:37xxx/Dirac

		    ); // you need to know that Dirac's the name
	               // (some coordination with the server is assumed!)

            // now you have access to the server, let's create the client

	    int width = 20, height = 20; 

	    Client here = // create a client positioned randomly inside the 
		new Client ( // frame according to the values of h/v-Insets 
		    (int)(Math.random() * (WIDTH - 2 * hInset) + hInset),
		    (int)(Math.random() * (WIDTH - 2 * hInset) + hInset),
		    width, // width of the player on the screen 
		    height // height of the player on the screen 
		    ); 

	    here.resize(WIDTH, HEIGHT); // set the Frame's size
	    here.show();                // show the Frame so we can play 

	    UnicastRemoteObject.exportObject(here); // export the object 

	    here.numb = farAway.register(here); // ask server to register you 
	    // the server will reply by giving you an index in the array 

	    farAway.broadcast(here.numb, // broadcast through the server 
			      here.x,    // the number you received, as well 
			      here.y);   // as the position you currently have 

	} catch (Exception e) {
	    System.out.println("Error in client..."); 
	    e.printStackTrace(); 
	}

    }

    public void keyTyped(KeyEvent e) { }    // that's what any 
    public void keyPressed(KeyEvent e) { }  // KeyListener is
    public void keyReleased(KeyEvent e) { } // supposed to do...

}
So the client is almost finished.

We know that the server (farAway) has two methods we can call:

Let's see how the server defines these methods:

import java.rmi.*; 
import java.rmi.server.*; 
import java.rmi.registry.*; 
import java.util.*; 

public class Server extends UnicastRemoteObject implements ServerExports {

} 
So that's our server. Now let's see some definitions.

import java.rmi.*; 
import java.rmi.server.*; 
import java.rmi.registry.*; 
import java.util.*; 

public class Server extends UnicastRemoteObject implements ServerExports {

    ClientExports[] clients = new ClientExports[100]; // the clients

    Player[] players = new Player[100];  // the client's players 

    int howMany = -1;  // how many players you currently have 

    public Server() throws RemoteException {
	System.out.println("Server being initialized... "); 
    }

    public int register(ClientExports remote) throws RemoteException {
	int newNumber = ++howMany; 
	clients[newNumber] = remote; 
	// notice the difference between clients and players 
	System.out.println("Client " + newNumber + " has registered."); 
	return newNumber; 
    }  // this method is self-explanatory 

    public void broadcast(int name, // actually a number (that's "who")
			  int x, y  // the new position to be broadcast
	                 )  // this meth. used by clients to send updates
    {
	if (players[name] == null) {
	     // this is a new player, making its first broadcast 
	} else {
	     // we know this player, we've seen it before
	}

	// so players will need to be created with the first broadcast
	// of a client - while clients get created when they register

    }
} 
Let's be a bit more precise about broadcasting.

import java.rmi.*; 
import java.rmi.server.*; 
import java.rmi.registry.*; 
import java.util.*; 

public class Server extends UnicastRemoteObject implements ServerExports {

    ClientExports[] clients = new ClientExports[100]; 

    Player[] players = new Player[100]; 

    int howMany = -1; // actually the index of the last element 

    public Server() throws RemoteException {
	System.out.println("Server being initialized... "); 
    }

    public int register(ClientExports remote) throws RemoteException {
	int newNumber = ++howMany; 
	clients[newNumber] = remote; 

	System.out.println("Client " + newNumber + " has registered."); 
	return newNumber; 
    } 

    public void broadcast(int name, 
			  int x, y  
	                 ) 
    {
        int width = 20, height = 20; // will need to corroborate these  
	// values with those used by clients so the best would be to define  
	// a set of constants in a class (perhaps Player) and use them here 

	if (players[name] == null) { // no player for this client  

	    players[name] = new Player(x, y, width, height); // create one  

	} else { // we have the player already  

	}

	for (int i = 0; i <= howMany; i++) {
	    
	    try {

		clients[i].update(name, x, y);
		// client name updates all clients (including itself!)

	    } catch (Exception e) {

		System.out.println("Client unavailable..."); 
		// perhaps we should also remove the client? 

	    }
	    
	}
	
	
    }
} 
Now let's define a main method for the server.

import java.rmi.*; 
import java.rmi.server.*; 
import java.rmi.registry.*; 
import java.util.*; 

public class Server extends UnicastRemoteObject implements ServerExports {
    ClientExports[] clients = new ClientExports[100]; 
    Player[] players = new Player[100]; 
    int howMany = -1; 
    public Server() throws RemoteException {
	System.out.println("Server being initialized... "); 
    }
    public int register(ClientExports remote) throws RemoteException {
	int newNumber = ++howMany; 
	clients[newNumber] = remote; 
	System.out.println("Client " + newNumber + " has registered."); 
	return newNumber; 
    } 
    public void broadcast(int name, 
			  int x, y  
	                 ) 
    {
        int width = 20, height = 20; 
	if (players[name] == null) { 
	    players[name] = new Player(x, y, width, height); 
	} else { }
	for (int i = 0; i <= howMany; i++) {
	    try {
		clients[i].update(name, x, y);
	    } catch (Exception e) {
		System.out.println("Client unavailable..."); 
	    }
	}
    }

    public static void main(String[] args) {

	System.setSecurityManager(new RMISecurityManager()); // required 

	try {

	    int port = Integer.parseInt(args[0]); 

	    Server pam = new Server(); // get an instance of the server 

	    Registry cat = // set up a registry on specified port 

		LocateRegistry.createRegistry(port); 

	    cat.bind("Dirac", pam); // what the client should know 

	    // client can now connect to this server as follows  
	    // 
            //     rmi://burrowww.cs.indiana.edu:port/Dirac

	    System.out.println("Server is ready..."); 

	} catch (Exception e) {

	}
    }

} 
We only need to define update in the Client.

(The server is invoking it, but the client does not have it defined).

And we also need to provide the content of the key listener's methods.

import java.rmi.*;
import java.rmi.server;
import java.awt.*;
import java.awt.event.*; 
import java.awt.image; 

public class Client extends Frame implements ClientExports, KeyListener {
    static int WIDTH = 300, int HEIGHT = 200; 
    static int hInset = 30, vInset = 30; 
    int x, y,          
	width, height; 
    Player[] players = new Player[100]; 
    int numb; 
    ServerExports server; 
    public Client(int x, int y, 
		  int width, int height, 
		  ServerExports farAway
		  ) 
    { 
	super("You are a client..."); 
	this.width = width; this.height = height; 
	System.out.println("Starting up client..."); 
	numb = -1; 
	this.x = x; this.y = y; 
	server = farAway;
	addKeyListener(this); 
    }

    public static void main(String[] args) {
	try {
	    ServerExports farAway = 
		(ServerExports)Naming.lookup(
		    "rmi://" + args[0] + ":" + args[1] + "/Dirac"
		    ); 
	    int width = 20, height = 20; 
	    Client here = 
		new Client ( 
		    (int)(Math.random() * (WIDTH - 2 * hInset) + hInset),
		    (int)(Math.random() * (WIDTH - 2 * hInset) + hInset),
		    width, 
		    height 
		    ); 
	    here.resize(WIDTH, HEIGHT); 
	    here.show();                
	    UnicastRemoteObject.exportObject(here); 
	    here.numb = farAway.register(here); 
	    farAway.broadcast(here.numb, 
			      here.x,    
			      here.y);   
	} catch (Exception e) {
	    System.out.println("Error in client..."); 
	    e.printStackTrace(); 
	}
    }

    public void update(int player, int x, int y) {
	if (players[player] == null) // we mimic the server here 
	    players[player] = new Player(x, y, width, height); 
	else players[player].moveTo(x, y); // where is class Player? 
	repaint(); // need to update
    }

    public void paint(Graphics g) {
	for (int i = 0; i < players.length; i++) // draw
	    if (players[i] != null) // the existing players
		players[i].draw(g, i == numb); // indicating self 
    }

    public void keyTyped(KeyEvent e) { }    

    public void keyPressed(KeyEvent e) { 
	int val = e.getKeyCode(); 
	try {
	    if (val == 38)      server.broadcast(numb, x, --y); // up
	    else if (val == 39) server.broadcast(numb, ++x, y); // right
            else if (val == 40) server.broadcast(numb, x, ++y); // down
            else if (val == 41) server.broadcast(numb, --x, y); // left 
	    else { System.out.println(val); }

	} catch (Exception oops) {
	    System.out.println("Exception in key processed: " + oops); 
	}
    }  

    public void keyReleased(KeyEvent e) { } 

}
Now the client and the server are done.

But what does a Player look like?

import java.awt.*; 

public class Player {

    int x, y, // location 
	width, height; // size  

    public Player(int x, int y, int width, int height) { // constructor  
	this.x = x; 
	this.y = y;
	this.width = width;
	this.height = height; 
    }

    public void moveTo(int x, int y) { // change location through jump  
	this.x = x; 
	this.y = y; 
    }

    public void draw(Graphics g, boolean self) { // drawing all players 
 	                                         // able to identify own  

	g.drawRect(x, y, width, height); // basic drawing  

	if (self) { // identify the player for this client  

	    g.drawLine(x, y, x + width, y + height); // one diagonal 
	    g.drawLine(x + width, y, x, y + height); // another diagonal  

	}
    }
}
How do you play this?

How do you start it?

javac *.java
But there are 4 (four) errors in Client.java that need to be fixed.

Find them, fix them, and while doing that focus on (re)compiling Client.java only.

Try Server.java then, but that also needs to have one typo fixed.

Then we can compile everything as follows:

frilled.cs.indiana.edu%ls -l
total 8
-rw-------   1 dgerman      2544 Feb  7 14:58 Client.java
-rw-------   1 dgerman       145 Feb  7 11:33 ClientExports.java
-rw-------   1 dgerman       745 Feb  7 14:54 Player.java
-rw-------   1 dgerman      1640 Feb  7 15:01 Server.java
-rw-------   1 dgerman       219 Feb  7 12:09 ServerExports.java
frilled.cs.indiana.edu%javac *.java
Note: Client.java uses or overrides a deprecated API.
Note: Recompile with -deprecation for details.
frilled.cs.indiana.edu%ls -ld *.class
-rw-------   1 dgerman      2942 Feb  7 15:02 Client.class
-rw-------   1 dgerman       209 Feb  7 15:02 ClientExports.class
-rw-------   1 dgerman       649 Feb  7 15:02 Player.class
-rw-------   1 dgerman      1832 Feb  7 15:02 Server.class
-rw-------   1 dgerman       262 Feb  7 15:02 ServerExports.class
frilled.cs.indiana.edu%rmic Client
[5]  - Done                 emacs Server.java
frilled.cs.indiana.edu%ls -l
total 21
-rw-------   1 dgerman      2942 Feb  7 15:02 Client.class
-rw-------   1 dgerman      2544 Feb  7 14:58 Client.java
-rw-------   1 dgerman       209 Feb  7 15:02 ClientExports.class
-rw-------   1 dgerman       145 Feb  7 11:33 ClientExports.java
-rw-------   1 dgerman      1543 Feb  7 15:04 Client_Skel.class
-rw-------   1 dgerman      2897 Feb  7 15:04 Client_Stub.class
-rw-------   1 dgerman       649 Feb  7 15:02 Player.class
-rw-------   1 dgerman       745 Feb  7 14:54 Player.java
-rw-------   1 dgerman      1832 Feb  7 15:02 Server.class
-rw-------   1 dgerman      1640 Feb  7 15:01 Server.java
-rw-------   1 dgerman       262 Feb  7 15:02 ServerExports.class
-rw-------   1 dgerman       219 Feb  7 12:09 ServerExports.java
frilled.cs.indiana.edu%rmic Server
frilled.cs.indiana.edu%ls -l
total 27
-rw-------   1 dgerman      2942 Feb  7 15:02 Client.class
-rw-------   1 dgerman      2544 Feb  7 14:58 Client.java
-rw-------   1 dgerman       209 Feb  7 15:02 ClientExports.class
-rw-------   1 dgerman       145 Feb  7 11:33 ClientExports.java
-rw-------   1 dgerman      1543 Feb  7 15:04 Client_Skel.class
-rw-------   1 dgerman      2897 Feb  7 15:04 Client_Stub.class
-rw-------   1 dgerman       649 Feb  7 15:02 Player.class
-rw-------   1 dgerman       745 Feb  7 14:54 Player.java
-rw-------   1 dgerman      1832 Feb  7 15:02 Server.class
-rw-------   1 dgerman      1640 Feb  7 15:01 Server.java
-rw-------   1 dgerman       262 Feb  7 15:02 ServerExports.class
-rw-------   1 dgerman       219 Feb  7 12:09 ServerExports.java
-rw-------   1 dgerman      1973 Feb  7 15:04 Server_Skel.class
-rw-------   1 dgerman      3707 Feb  7 15:04 Server_Stub.class
frilled.cs.indiana.edu%
Now you can play the game.

Notice one thing: all networking is concentrated in the two main methods!

(Your objects have no idea this is happening over the network).

Please make sure you have this:

frilled.cs.indiana.edu% ls -ld ~/.java*
-rw-r--r--   1 dgerman  faculty        72 Nov 13 18:44 /u/dgerman/.java.policy
blesmol.cs.indiana.edu% cat ~/.java.policy
grant {
  permission java.net.SocketPermission "*", "connect,accept";
};frilled.cs.indiana.edu% 
This will ensure the necessary permissions.

Your task(s) for today (and next week):

We'll see you next time.


Last updated: Feb 7, 2002 by Adrian German for A348/A548