![]() |
![]() 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:
It's aimport 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 { }
Frame
that exports some methods, and processes
key(board) events. Let's see what the client exports (at this stage):
We'd like a client to be updated on the position (import java.rmi.*; public interface ClientExports extends Remote { public void update(int player, int x, int y) throws RemoteException; }
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:
So we have a constructor and the instance variables defined.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 } }
We need to clarify three things:
ServerExports
)
main
)
KeyListener
?
First, let's see what the server looks like from far away.
It allows clients to as for two things: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; }
register
with the server, and
broadcast
a change in position
For now let's answer the remaining two questions:
So the client is almost finished.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... }
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:
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 { }
Let's be a bit more precise aboutimport 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 } }
broadcast
ing.
Now let's define aimport 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? } } } }
main
method for the server.
We only need to defineimport 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) { } } }
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.
Now the client and the server are done.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) { } }
But what does a Player
look like?
How do you play this?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 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:
This will ensure the necessary permissions.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%
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.)