CS4513 Project

Cloud Saucer Shoot - an Implementation of a Cloud-based Game

Due date: Friday, May 2nd, 2014 at 11:59pm


Description | Hints | Implementation | Hand In | Grading

Description

You will take a game with a traditional client-server architecture and create a working "cloud" version. In the cloud, a server will do the heavy-weight game computations, sending the game screen frame by frame as a sort of "video" to the client. The thin client ("thin" because it does not do game computations) receives and displays the game-video. The client also captures player input (keystrokes) and sends it to the server for processing.

Cloud Saucer Shoot

Before undertaking your cloud-game implementation, work through the Saucer Shoot tutorial, making a simple game using Dragonfly. The tutorial helps better understand a game engine by developing a game from a game programmer's perspective, and familiarizes you with some of the knowledge needed for extending Dragonfly to a cloud-based system. Your new game will be a direct port of Saucer Shoot, only to the cloud.

Server

The server starts up the game engine and gets ready for a client to connect by waiting on a well-known port. Once the client connects, the server does all the game computations - keeping track of objects, determining object collisions and even rendering frames on the screen. However, unlike in a traditional game, there is no player viewing the game and providing input where the game is running. Instead, the server "scrapes" the screen every frame it renders, sending the frames down the network socket to the client. Player input, which usually arrives by keyboard where the game is running, arrives by the network socket, sent from the thin client. All input thus received is applied to the game as if they player were at the keyboard of the server.

The server only has to handle one client for one game. When the one game ends, the server closes the network connection and then exits. Subsequent games would restart the server (and client).

Client

The client starts up and immediately connects to the server using the hostname provided by the player and the well-known port. Once connected, the client receives input over the network socket in the form of the game frames to be displayed, displaying them one at a time on the screen. The client also captures keyboard input from the player, sending all key presses to the server.

When the game is over and the network connection closes, the client exits.


Implementation

You will do the tutorial first, as this will help set up your Dragonfly development environment and understand the relationship between the game you are porting and the Dragonfly game engine. Once done with the tutorial, you can begin your design and implementation. Your client and server must use the pre-compiled Dragonfly game engine, obtained from the Dragonfly Web Page.

You must do your coding in C/C++ in Linux, such that it runs on the WPI CCC machines or in Cygwin in Windows. The CCC machines all run Linux and Cygwin is installed on all the Zoo lab computers.

You will work alone for this project. While you can discuss the project with other students and even help each other debug code, you must write all code yourself.

You will need to include a Makefile that allows your program to be built and a README explaining how to run your game system. See Hand In for details.

There are many ways to design and implement the functionality necessary for this project. However, a strong suggestion is to adopt/use three main elements from Dragonfly: 1) managers, 2) events, and 3) curses.

NetworkManager

In Dragonfly, major game engine functionality is done by individual managers (e.g., the WorldManager that handles the game world state, and the GraphicsManager that handles game graphics). These managers all inherit from the base Manager class and implement the singleton pattern (the latter limits the engine to one instance of the class and also provides global access to the manager).

You are encouraged to make a NetworkManager that handles the network activity, akin to the other Dragonfly managers. Like other managers, the NetworkManager inherits from the Manager base class, implementing the singleton design pattern. A possible header file is below:

//
// The network manager
// 

#ifndef __NETWORK_MANAGER_H__
#define __NETWORK_MANAGER_H__

#include 

// Engine includes.
#include "Manager.h"

#define DRAGONFLY_PORT "9876"	// Default port.

class NetworkManager : public Manager {

 private:
  NetworkManager();                       // Private since a singleton.
  NetworkManager(NetworkManager const&);  // Don't allow copy.
  void operator=(NetworkManager const&);  // Don't allow assignment.
  int sock;				  // Connected network socket.

 public:

  // Get the one and only instance of the NetworkManager.
  static NetworkManager &getInstance();

  // Start up NetworkManager.
  int startUp();

  // Shut down NetworkManager.
  void shutDown();

  // Accept only network events.
  // Returns false for other engine events.
  bool isValid(string event_type);

  // Block, waiting to accept network connection.
  int accept(string port = DRAGONFLY_PORT);

  // Make network connection.
  // Return 0 if success, else -1.
  int connect(string host, string port = DRAGONFLY_PORT);

  // Close network connection.
  // Return 0 if success, else -1.
  int close();

  // Send buffer to connected network.
  // Return 0 if success, else -1.
  int send(void *buffer, int bytes);

  // Receive from connected network (no more than nbytes).
  // Return number of bytes received, else -1 if error.
  int receive(void *buffer, int nbytes);

  // Check if network data.
  // Return amount of data (0 if no data), -1 if not connected or error.
  int isData();

  // Return true if network connected, else false.
  bool isConnected();
};

#endif // __NETWORK_MANAGER_H__

Unlike other Managers that are started by the GameManager and are typically invoked by the engine in the game loop, your NetworkManager, not being officially part of the engine, needs to be started and executed in game programmer code. To do so, you create a game object (inherited from Object), that upon creation, starts up the NetworkManager and registers for the step event. Each step, it checks the NetworkManager for network data received (i.e., calling isData()), generating network events when there is data (via onEvent()).

EventNetwork

Objects that express interest in network data register for EventNetwork events with the NetworkManager. EventNetwork is inherited from the base Event class. A possible header file for EventNetwork is below:

//
// A "network" event, generated when a network packet arrives.
//

#ifndef __EVENT_NETWORK_H__
#define __EVENT_NETWORK_H__

#include "Event.h"

#define NETWORK_EVENT "__network__"

class EventNetwork : public Event {

 private:
  int bytes;  // number of bytes pending
  
 public:
  // Default constructor.
  EventNetwork();

  // Create EventNetwork with indicated bytes.
  EventNetwork(int initial_bytes);

  // Set number of network bytes available.
  void setBytes(int new_bytes);

  // Get number of network bytes available.
  int getBytes();
};

#endif // __EVENT_NETWORK_H__

Note, EventNetwork does not contain the network data itself. Instead, it merely indicates there is network data pending, leaving it up to the interested Objects once notified to pull the data from the NetworkManager.

Curses

Graphics in Dragonfly are managed by the GraphicsManager using Curses. Characters drawn to the screen on the server (running the game) can be "scraped" and sent from the server to the client, that will display the graphics. To do this, the Curses WINDOW buffer can be obtained from the GraphicsManager (see getPreviousBuffer() and getCurrentBuffer()) and the whole screen iterated over using the Curses call mvwinch(). All data obtained is then sent to the thin client. The client receives the network data, uses the GraphicsManager to get the WINDOW buffer for its display, then writes out the characters on the screen.

For the cloud server, the "scraping" should be done once every game frame, about 30 frames/second. This most easily done from a game object each step event.

On the thin client, Curses can be used to capture keyboard (and mouse) input, too, using the InputManager. This is most easily done by having a game object register for input events, sending any input to the cloud server.

Note, Curses characters and keystrokes are actually all of type int, not char.


Hints

Suggestions on the server and client tasks, and breakdown, are as follows:

  SERVER (main.cpp)
    Start GameManager
    Start NetworkManager
    Load game resources
    Start Host 
    Run game
    Cleanup and exit

  HOST (a game object)
    Register for step event
    Register for network event
    Each step event
      Scrape screen
      Send to client
      If network input
	Generate network event
    Each network event
      Apply network input to game


  CLIENT (main.cpp)
    Start GameManager
    Start NetworkManager
    Start Player
    Cleanup and exit

  PLAYER (a game object)
    Register for network event
    Register for step event
    Each step event
      Check network input
      If network input
	Generate network event
    Each network event
      Receive data
      Draw frame on screen
    Each input event
      Send input to server

Since TCP is a stream-oriented protocol, the client can receive part of a frame even though the server sends only complete frames. It may be easier to only display full sized frames by not taking data from the network socket until an entire frame is available. Note, if the client only plays one frame per step event, this will result in a client falling behind in the visual representation, which to the player will feel like lag. To avoid this, the client may want to read and display all complete frames in a buffer that are available.

Sockets are by default blocking, meaning if a process reads from them and there is no data available, the process blocks until there is data. Non-blocking behavior can be achieved with the MSG_DONTWAIT flag for a recv() call.

By default, a recv() call that retrieves data from a socket removes it from the OS buffer such that subsequent reads do not get the same data. The MSG_PEEK can be used to retrieve the data but leave it in the buffer for subsequent reads.

The ioctl() call can be used to see the number of bytes available in a socket before reading using the FIONREAD parameter as the second argument (e.g., ioctl(sock, FIONREAD, &bytes)).


Hand In

You must hand in the following:

  1. A source code package with all code necessary to build your game:
  2. A README file explaining: platform, files, code structure, how to compile, and anything else needed to understand (and grade) your game system.

If you do not have your files on the CCC machines, then copy your entire working directory to your CCC account. Then, login to the CCC machines (using slogin or putty). Use tar with gzip to archive your files. For example:

        mkdir lastname-project-cloud
        cp * lastname-project-cloud 
        tar czvf project-cloud-lastname.tgz lastname-project-cloud
Submit your assignment (project-cloud-lastname.tgz):
        /cs/bin/turnin submit cs4513 project5 project-cloud-lastname.tgz

If you need more information, see Using the turnin Program for additional help with turnin.


Grading

A general rubric follows:

100-90. The game system meets all specified requirements. Both client and server are robust in the face of possible errors, such as being unable to connect to the network or network errors in flight (TCP loss or broken connections). Game plays flawlessly with no visual glitches or input problems. All code builds and runs cleanly without errors or warnings. README has sufficient details to easily compile and run programs.

89-80. The game system meets most of the specified requirements. Both client and server have solid communication, but may have occasional problems due to the network or other system conditions. The game plays well and is definitely playable, but may have a few visual glitches. All code builds cleanly and runs. The README has sufficient details to compile and run the programs.

79-70. The game system meets some of the specified requirements. Both client and server can connect and communicate, but may have limited connection abilities. The game can be played, but may feel laggy or have considerable visual glitches. Code builds and runs but may have warnings. The README may be missing some details on how to run the programs.

69-60. The game system meets some of the specified requirements. Both client and server can connect but communication is limited. The game may start but is only somewhat playable, with input difficulties and visual glitches, and/or terminates prematurely. Code builds and runs but may have warnings. The README may be missing some details on how to run the programs.

59-0. The game system does not meet the specified requirements. Both client and server can be started but may fail to connect or stay connected. The game may start but is not displayed at the client and/or the client does not get input back to the server. Code may build, but has warnings or may not even compile. The README may be missing some details on how to run the programs.


Description | Hints | Implementation | Hand In | Grading

Return to 4513 Home Page

Send all questions to the TA mailing list (cs4513-staff at cs.wpi.edu).