All players play through the network protocol, over sockets.  The main
difference between local and network players is initialization.  Local
players have their clients initialized by the server in the server's JVM,
while remote players initialize their own clients using StartClient.  Both
types of clients then connect to the server.  (Using "localhost" for local
clients.)

There is one ServerSocket, attached to Server.  It waits for maxClients (equal
to the total number of players in the game, of all types) to connect.  Each
time a client connects, it spawns a SocketServerThread which is associated with
that client.  The SocketServerThread has an infinite while() loop that listens
for traffic on the socket, parses it, and calls appropriate methods on Server.
SocketServerThread also implements IClient, so the Server can call Client
methods on the appropriate SocketServerThread, which then stringifies them and
sends them over the socket to the correct client.

The way this is managed is that each SocketServerThread has its thread name set
to that client's player name.  (They must be unique -- we add numbers to the
end of non-unique names to force this.)  Server keeps a list and map of
clients, and so when the server asks for a client by name it actually gets the
appropriate SocketServerThread.  And any code inside Server is running in a
thread, so Thread.currentThread().getName() says which client called this
method. (Useful for authenticating that the correct player is acting.)

Each Client has a SocketClientThread, which opens a socket to the server at the
passed host and port.  (The default is localhost:26567)  SocketClientThread
implements IServer and handles sending outbound traffic to the server.

The protocol is a straight stringification of the remote methods in Client and
Server.  We join the method name and all its arguments into a String, and then
split them back apart on the other side.  Compound arguments (Set, List, etc.)
are themselves joined and split, using a different joining sequence.  The
grunt work is done in the Glob and Split classes in util.  The parsing is just
a giant case statement. Yes, this is ugly and hard to maintain.  (If a remote
method changes in Client or Server, we also need to change IClient or IServer,
SocketClientThread, and SocketServerThread.)  But the network interface
shouldn't need to change much.
