[ Team LiB ] Previous Section Next Section

21.9 A MUD Client

Example 21-8 is a client program for the MUD system developed in the previous examples. It uses the Naming.lookup( ) method to look up the RemoteMudServer object that represents a named MUD on a specified host. The program then calls the getEntrance( ) or getNamedPlace( ) method of this RemoteMudServer object to obtain an initial MudPlace object into which to insert the user. Next, the program asks the user for a name and description of the MudPerson that will represent him in the MUD, creates a MudPerson object with that name and description, and then places it in the initial RemoteMudPlace. Finally, the program enters a loop that prompts the user to enter a command and then processes the command. Most of the commands that this client supports simply invoke one of the remote methods of the RemoteMudPlace that represents the user's current location in the MUD. The end of the command loop consists of a number of catch clauses that handle the large number of things that can go wrong.

In order to use the MudClient class, you must first have a MudServer up and running. You should be able to accomplish that with commands like the following:

% cd je3/rmi
% javac Mud*.java
% rmic -d ../../../../ je3.rmi.MudServer
% rmic -d ../../../../ je3.rmi.MudPlace
% rmic -d ../../../../ je3.rmi.MudPerson
% rmiregistry &
% java je3.rmi.MudServer MyMud muddy Lobby \
    'A large marble lobby with ficus trees'

Having started the server with these commands, you can then run the client with a command like this:

% java je3.rmi.MudClient localhost MyMud
Example 21-8. MudClient.java
package je3.rmi;
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.io.*;
import java.util.*;
import je3.rmi.Mud.*;

 * This class is a client program for the MUD.  The main( ) method sets up 
 * a connection to a RemoteMudServer, gets the initial RemoteMudPlace object,
 * and creates a MudPerson object to represent the user in the MUD.  Then it 
 * calls runMud( ) to put the person in the place, begins processing
 * user commands.  The getLine( ) and getMultiLine( ) methods are convenience
 * methods used throughout to get input from the user.
public class MudClient {
     * The main program.  It expects two or three arguments:
     *   0) the name of the host on which the mud server is running
     *   1) the name of the MUD on that host
     *   2) the name of a place within that MUD to start at (optional).
     * It uses the Naming.lookup( ) method to obtain a RemoteMudServer object
     * for the named MUD on the specified host.  Then it uses the getEntrance( )
     * or getNamedPlace( ) method of RemoteMudServer to obtain the starting
     * RemoteMudPlace object.  It prompts the user for their name and 
     * description, and creates a MudPerson object.  Finally, it passes
     * the person and the place to runMud( ) to begin interaction with the MUD.
    public static void main(String[  ] args) {
        try {
            String hostname = args[0]; // Each MUD is uniquely identified by a 
            String mudname = args[1];  //   host and a MUD name.
            String placename = null;   // Each place in a MUD has a unique name
            if (args.length > 2) placename = args[2];
            // Look up the RemoteMudServer object for the named MUD using
            // the default registry on the specified host.  Note the use of
            // the Mud.mudPrefix constant to help prevent naming conflicts
            // in the registry.
            RemoteMudServer server = 
                (RemoteMudServer)Naming.lookup("rmi://" + hostname + "/" +
                                               Mud.mudPrefix + mudname);

            // If the user did not specify a place in the mud, use
            // getEntrance( ) to get the initial place.  Otherwise, call
            // getNamedPlace( ) to find the initial place.
            RemoteMudPlace location = null;
            if (placename == null) location = server.getEntrance( );
            else location = (RemoteMudPlace) server.getNamedPlace(placename);
            // Greet the user and ask for their name and description.
            // This relies on getLine( ) and getMultiLine( ) defined below.
            System.out.println("Welcome to " + mudname);
            String name = getLine("Enter your name: ");
            String description = getMultiLine("Please describe what " +
                                          "people see when they look at you:");

            // Define an output stream that the MudPerson object will use to
            // display messages sent to it to the user.  We'll use the console.
            PrintWriter myout = new PrintWriter(System.out);
            // Create a MudPerson object to represent the user in the MUD.
            // Use the specified name and description, and the output stream.
            MudPerson me = new MudPerson(name, description, myout);
            // Lower this thread's priority one notch so that broadcast
            // messages can appear even when we're blocking for I/O.  This is
            // necessary on the Linux platform, but may not be necessary on all
            // platforms.
            int pri = Thread.currentThread( ).getPriority( );
            Thread.currentThread( ).setPriority(pri-1);
            // Finally, put the MudPerson into the RemoteMudPlace, and start
            // prompting the user for commands.
            runMud(location, me);
        // If anything goes wrong, print a message and exit.
        catch (Exception e) {
            System.out.println("Usage: java MudClient <host> <mud> [<place>]");

     * This method is the main loop of the MudClient.  It places the person
     * into the place (using the enter( ) method of RemoteMudPlace).  Then it
     * calls the look( ) method to describe the place to the user, and enters a
     * command loop to prompt the user for a command and process the command
    public static void runMud(RemoteMudPlace entrance, MudPerson me) 
        throws RemoteException
        RemoteMudPlace location = entrance;  // The current place
        String myname = me.getName( );        // The person's name
        String placename = null;             // The name of the current place
        String mudname = null;             // The name of the mud of that place

        try { 
            // Enter the MUD
            location.enter(me, myname, myname + " has entered the MUD."); 
            // Figure out where we are (for the prompt)
            mudname = location.getServer( ).getMudName( );
            placename = location.getPlaceName( );
            // Describe the place to the user
        catch (Exception e) {
        // Now that we've entered the MUD, begin a command loop to process
        // the user's commands.  Note that there is a huge block of catch
        // statements at the bottom of the loop to handle all the things that
        // could go wrong each time through the loop.
        for(;;) {  // Loop until the user types "quit"
            try {    // Catch any exceptions that occur in the loop
                // Pause just a bit before printing the prompt, to give output
                // generated indirectly by the last command a chance to appear.
                try { Thread.sleep(200); } catch (InterruptedException e) {  }

                // Display a prompt, and get the user's input
                String line = getLine(mudname + '.' + placename + "> ");
                // Break the input into a command and an argument that consists
                // of the rest of the line.  Convert the command to lowercase.
                String cmd, arg;
                int i = line.indexOf(' ');
                if (i == -1) { cmd = line; arg = null; }
                else {
                    cmd = line.substring(0, i).toLowerCase( );
                    arg = line.substring(i+1);
                if (arg == null) arg = "";
                // Now go process the command.  What follows is a huge repeated
                // if/else statement covering each of the commands supported by
                // this client.  Many of these commands simply invoke one of
                // the remote methods of the current RemoteMudPlace object.
                // Some have to do a bit of additional processing.

                // LOOK: Describe the place and its things, people, and exits
                if (cmd.equals("look")) look(location);
                // EXAMINE: Describe a named thing
                else if (cmd.equals("examine")) 
                // DESCRIBE: Describe a named person
                else if (cmd.equals("describe")) {
                    try { 
                        RemoteMudPerson p = location.getPerson(arg);
                        System.out.println(p.getDescription( )); 
                    catch(RemoteException e) {
                        System.out.println(arg + " is having technical " +
                                           "difficulties. No description " +
                                           "is available.");
                // GO: Go in a named direction
                else if (cmd.equals("go")) {
                    location = location.go(me, arg);
                    mudname = location.getServer( ).getMudName( );
                    placename = location.getPlaceName( );
                // SAY: Say something to everyone 
                else if (cmd.equals("say")) location.speak(me, arg);
                // DO: Do something that will be described to everyone
                else if (cmd.equals("do")) location.act(me, arg);
                // TALK: Say something to one named person
                else if (cmd.equals("talk")) {
                    try {
                        RemoteMudPerson p = location.getPerson(arg);
                        String msg = getLine("What do you want to say?: ");
                        p.tell(myname + " says \"" + msg + "\"");
                    catch (RemoteException e) {
                        System.out.println(arg + " is having technical " +
                                         "difficulties. Can't talk to them.");
                // CHANGE: Change my own description 
                else if (cmd.equals("change"))
                            getMultiLine("Describe yourself for others: "));
                // CREATE: Create a new thing in this place
                else if (cmd.equals("create")) {
                    if (arg.length( ) == 0)
                        throw new IllegalArgumentException("name expected");
                    String desc = getMultiLine("Please describe the " +
                                               arg + ": ");
                    location.createThing(me, arg, desc);
                // DESTROY: Destroy a named thing
                else if (cmd.equals("destroy")) location.destroyThing(me, arg);
                // OPEN: Create a new place and connect this place to it
                // through the exit specified in the argument.
                else if (cmd.equals("open")) {
                    if (arg.length( ) == 0) 
                      throw new IllegalArgumentException("direction expected");
                    String name = getLine("What is the name of place there?: ");
                    String back = getLine("What is the direction from " + 
                                          "there back to here?: ");
                    String desc = getMultiLine("Please describe " +
                                               name + ":");
                    location.createPlace(me, arg, back, name, desc);
                // CLOSE: Close a named exit.  Note: only closes an exit
                // uni-directionally, and does not destroy a place.
                else if (cmd.equals("close")) {
                    if (arg.length( ) == 0) 
                      throw new IllegalArgumentException("direction expected");
                    location.close(me, arg);
                // LINK: Create a new exit that connects to an existing place
                // that may be in another MUD running on another host
                else if (cmd.equals("link")) {
                    if (arg.length( ) == 0) 
                      throw new IllegalArgumentException("direction expected");
                    String host = getLine("What host are you linking to?: ");
                    String mud =
                        getLine("What is the name of the MUD on that host?: ");
                    String place =
                        getLine("What is the place name in that MUD?: ");
                    location.linkTo(me, arg, host, mud, place);
                    System.out.println("Don't forget to make a link from " +
                                       "there back to here!");
                // DUMP: Save the state of this MUD into the named file,
                // if the password is correct
                else if (cmd.equals("dump")) {
                    if (arg.length( ) == 0) 
                       throw new IllegalArgumentException("filename expected");
                    String password = getLine("Password: ");
                    location.getServer( ).dump(password, arg);
                // QUIT: Quit the game
                else if (cmd.equals("quit")) {
                    try { location.exit(me, myname + " has quit."); } 
                    catch (Exception e) {  }
                    System.out.flush( );
                // HELP: Print out a big help message
                else if (cmd.equals("help")) System.out.println(help);
                // Otherwise, this is an unrecognized command.
                else System.out.println("Unknown command.  Try 'help'.");
            // Handle the many possible types of MudException
            catch (MudException e) {
                if (e instanceof NoSuchThing) 
                    System.out.println("There isn't any such thing here."); 
                else if (e instanceof NoSuchPerson) 
                   System.out.println("There isn't anyone by that name here.");
                else if (e instanceof NoSuchExit) 
                  System.out.println("There isn't an exit in that direction.");
                else if (e instanceof NoSuchPlace) 
                    System.out.println("There isn't any such place."); 
                else if (e instanceof ExitAlreadyExists)
                    System.out.println("There is already an exit " +
                                       "in that direction.");
                else if (e instanceof PlaceAlreadyExists)
                    System.out.println("There is already a place " +
                                       "with that name.");
                else if (e instanceof LinkFailed)
                    System.out.println("That exit is not functioning.");
                else if (e instanceof BadPassword) 
                    System.out.println("Invalid password."); 
                else if (e instanceof NotThere)      // Shouldn't happen
                    System.out.println("You can't do that when " +
                                       "you're not there."); 
                else if (e instanceof AlreadyThere)  // Shouldn't happen
                    System.out.println("You can't go there; " +
                                       "you're already there.");
            // Handle RMI exceptions
            catch (RemoteException e) {
               System.out.println("The MUD is having technical difficulties.");
               System.out.println("Perhaps the server has crashed:");
            // Handle everything else that could go wrong.
            catch (Exception e) {
                System.out.println("Syntax or other error:");
                System.out.println("Try using the 'help' command.");
     * This convenience method is used in several places in the
     * runMud( ) method above.  It displays the name and description of
     * the current place (including the name of the mud the place is in), 
     * and also displays the list of things, people, and exits in
     * the current place.
    public static void look(RemoteMudPlace p) 
        throws RemoteException, MudException
        String mudname = p.getServer( ).getMudName( ); // Mud name
        String placename = p.getPlaceName( );         // Place name
        String description = p.getDescription( );     // Place description
        Vector things = p.getThings( );               // List of things here
        Vector names = p.getNames( );                 // List of people here
        Vector exits = p.getExits( );                 // List of exits from here

        // Print it all out
        System.out.println("You are in: " + placename +
                           " of the Mud: " + mudname);
        System.out.print("Things here: ");
        for(int i = 0; i < things.size( ); i++) {      // Display list of things
            if (i > 0) System.out.print(", ");
        System.out.print("\nPeople here: ");
        for(int i = 0; i < names.size( ); i++) {       // Display list of people
            if (i > 0) System.out.print(", ");
        System.out.print("\nExits are: ");
        for(int i = 0; i < exits.size( ); i++) {       // Display list of exits
            if (i > 0) System.out.print(", ");
        System.out.println( );                         // Blank line
        System.out.flush( );                           // Make it appear now!
    /** This static input stream reads lines from the console */
    static BufferedReader in =
        new BufferedReader(new InputStreamReader(System.in));
     * A convenience method for prompting the user and getting a line of 
     * input.  It guarantees that the line is not empty and strips off 
     * whitespace at the beginning and end of the line.
    public static String getLine(String prompt) {
        String line = null;
        do {                      // Loop until a non-empty line is entered
            try {
                System.out.print(prompt);             // Display prompt
                System.out.flush( );                   // Display it right away
                line = in.readLine( );                 // Get a line of input
                if (line != null) line = line.trim( ); // Strip off whitespace
            } catch (Exception e) {  }                // Ignore any errors
        } while((line == null) || (line.length( ) == 0));
        return line;
     * A convenience method for getting multi-line input from the user.
     * It prompts for the input, displays instructions, and guarantees that
     * the input is not empty.  It also allows the user to enter the name of
     * a file from which text will be read.
    public static String getMultiLine(String prompt) {
        String text = "";
        for(;;) {  // We'll break out of this loop when we get non-empty input
            try {
                BufferedReader br = in;       // The stream to read from 
                System.out.println(prompt);   // Display the prompt
                // Display some instructions
                System.out.println("You can enter multiple lines.  " + 
                                   "End with a '.' on a line by itself.\n" +
                                   "Or enter a '<<' followed by a filename");
                // Make the prompt and instructions appear now.
                System.out.flush( );
                // Read lines
                String line;
                while((line = br.readLine( )) != null) {    // Until EOF
                    if (line.equals(".")) break;  // Or until a dot by itself
                    // Or, if a file is specified, start reading from it 
                    // instead of from the console.
                    if (line.trim( ).startsWith("<<")) {      
                        String filename = line.trim( ).substring(2).trim( );
                        br = new BufferedReader(new FileReader(filename));
                        continue;  // Don't count the << as part of the input
                    // Add the line to the collected input
                    else text += line + "\n";  
                // If we got at least one line, return it.  Otherwise, chastise
                // the user and go back to the prompt and the instructions.
                if (text.length( ) > 0) return text;
                else System.out.println("Please enter at least one line.");
            // If there were errors, for example an IO error reading a file,
            // display the error and loop again, displaying prompt and
            // instructions
            catch(Exception e) { System.out.println(e); }

    /** This is the usage string that explains the available commands */
    static final String help = 
        "Commands are:\n" + 
        "look: Look around\n" +
        "examine <thing>: examine the named thing in more detail\n" +
        "describe <person>: describe the named person\n" +
        "go <direction>: go in the named direction (i.e. a named exit)\n" +
        "say <message>: say something to everyone\n" +
        "do <message>: tell everyone that you are doing something\n" +
        "talk <person>: talk to one person.  Will prompt for message\n" +
        "change: change how you are described.  Will prompt for input\n" +
        "create <thing>: create a new thing.  Prompts for description \n" +
        "destroy <thing>: destroy a thing.\n" + 
        "open <direction>: create an adjoining place. Prompts for input\n"+
        "close <direction>: close an exit from this place.\n" +
        "link <direction>: create an exit to an existing place,\n" +
        "     perhaps on another server.  Will prompt for input.\n" +
        "dump <filename>: save server state.  Prompts for password\n" +
        "quit: leave the Mud\n" +
        "help: display this message";
    [ Team LiB ] Previous Section Next Section