
With a just a small amount of additional software, we can run a multiplayer version in which several players can connect to a central server from their own machines and play together in the same game. This type of game is often called a MUD (for multi-user dungeon).
The game itself runs on the central server. A machine that connects to the server is called a client. The client software is built into Swindle and is very simple: it just connects to the server and reads and writes messages to and from the server. The client machine does not run any game software or know anything about game objects; it just acts as a terminal.
There is a server polar.csuglab.cornell.edu running the game that you can connect to from your machine at home or in the lab. Run Swindle (the console version, not the GUI version DrSwindle) and type (client). It will ask you for a server to connect to. Type polar.csuglab.cornell.edu. If there are not too many people already connected, your connection request will be accepted and you will be able to play the game along with other people who are currently connected.
The extra server software needed is contained in the file server.ss (less the pieces you have to implement for PS5).
The server software is multi-threaded. There are separate threads for each player, as well as some other general threads that are spawned to take care of various housekeeping tasks. Some of these threads access shared variables such as the event queue *event-queue* and the active players list *players*, which must be protected with semaphores to enforce mutual exclusion. This was not an issue in the single-player game, which was single-threaded.
Communication over the network is accomplished using TCP connections that are established using the Scheme commands tcp-listen, tcp-connect, and tcp-accept using the hard-coded TCP port number 12321. To the Scheme program, these connections appear as input and output ports that can be read from and written to using read, echo, display, etc. much as one would read and write from the user's console, the only difference being that the current port is reset with parameterize before the read, echo, or display command.
The thunk (server-game) is invoked to start the server. After a few small housekeeping details, it calls tcp-listen to declare to the underlying operating system networking support that it is ready to entertain connection requests from clients. The return value from tcp-listen is a #<tcp-listener> object that is needed later to accept new connections. It then calls (server-loop) in a separate thread to listen for new connections and create new players for each new connection. One of your exercises is to write server-loop; see the PS5 description for instructions on how to do this.
It then spawns a separate memory management thread to do garbage collection to reclaim unused storage every 10 seconds. The reason for this is that if we wait too long to do garbage collection, then it will take much longer when it does occur and cause more of a perceivable interruption in the game.
It then inserts some initial events and defines the game by calling (define-game) as in the single-player version. Finally, it calls (event-loop) in a separate thread to start up the game.
The original thread then returns from server-game to the Scheme top-level interpreter loop running on the server to listen for operator commands.
This is a subclass of <player>. It inherits all the functionality of <player>, except that it also has a #<thread> object and two ports (input and output) associated with it. There is one <player> for each player currently in the game. The #<thread> object represents the thread that is running on the server that is acting for that player. The two ports are the TCP ports to the client. The two methods tell and get-player-input specialize these generic functions for <net-player> to communicate via the TCP ports. Other communication primitives such as echo and echo-ns are redefined in server.ss to account for some of the idiosyncrasies of communication over the net.
The function make-net-player is called with the ports returned from tcp-accept to create a new player and start the thread for that player. (You will call tcp-accept to get the ports and call make-net-player with these ports from server-loop, which you will write for the problem set.) The player thead is actually started in the initialize procedure for <net-player> that is called automatically when the <net-player> object is created. The initialize procedure also enters the new player in the active players list *players*.
To debug your server code, you will have to run the server on your own machine. To do this, you will need to know the IP address or DNS name of your machine. You can find this out by typing either ipconfig or winipcfg in Windows or hostname and then nslookup <name> in Unix or Windows NT. Mac users: Apple Menu -> Control Panel -> TCP/IP. Copy the file server.ss to your collects/cs212 directory. Navigate to that directory in a command window, run Swindle, and type (load "server.ss") and then (server-game). This will start up the server in that window. Now in a different command window, start up Swindle and type (client). When it asks for a host name, type in the IP address of your own machine. You will then be connected as a client to the server running in the first window. You can connect as many clients as you like up to a maximum of 20.