// This example is from _Java Examples in a Nutshell_. (http://www.oreilly.com)
// Copyright (c) 1997 by David Flanagan
// This example is provided WITHOUT ANY WARRANTY either expressed or implied.
// You may study, use, modify, and distribute it for non-commercial purposes.
// For any commercial use, see http://www.davidflanagan.com/javaexamples
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.io.*;
import java.util.*;
import 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 a 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 within a MUD has a unique name
if (args.length > 2) placename = args[2];
// Set the RMI security manager so that untrusted stub objects loaded
// over the network can't cause havoc.
System.setSecurityManager(new RMISecurityManager());
// 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(new OutputStreamWriter(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(e);
System.out.println("Usage: java MudClient <host> <mud> [<place>]");
System.exit(1);
}
}
/**
* 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
look(location);
}
catch (Exception e) {
System.out.println(e);
System.exit(1);
}
// 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"))
System.out.println(location.examineThing(arg));
// 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();
look(location);
}
// 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"))
me.setDescription(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.println("Bye.");
System.out.flush();
System.exit(0);
}
// HELP: Print out a big help message
else if (cmd.equals("help")) {
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";
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:");
System.out.println(e);
}
// Handle everything else that could go wrong.
catch (Exception e) {
System.out.println("Syntax or other error:");
System.out.println(e);
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.println(description);
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(things.elementAt(i));
}
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(names.elementAt(i));
}
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.print(exits.elementAt(i));
}
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 << line as part of the input
}
else text += line + "\n"; // Add the line to the collected input
}
// 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); }
}
}
}
|