|
Lecture Notes Five: Anatomy of a Simple Networked Game |
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:
ServerExports)
main)
KeyListener?
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:
register with the server, and
broadcast a change in position
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:
register (which gives us a registration number), and
broadcast (which we don't know yet what it does, but we are about to find out)
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?
But there are 4 (four) errors injavac *.java
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:
Now you can play the game.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%
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):
That is, implement collisions (which are simple reflections off orthogonal walls).
Then restart the game.
(Implement a similar type of collisions as with the puck.)
(Collisions with the puck are tough now.)