Click here to Skip to main content
16,018,353 members
Articles / Programming Languages / C#

The SpaceShoot server

Rate me:
Please Sign up or sign in to vote.
4.97/5 (32 votes)
27 Mar 2012CPOL28 min read 53.2K   1.9K   56   16
Providing a stable and powerful server for the JavaScript / HTML5 browser game SpaceShoot with some gimmicks.

A sample multiplayer battle including a lot of bots

Introduction

This is the third article in the SpaceShoot series. The first one (which can be found here) discussed the principle of the game with a very easy and not very robust implementation. The second one (which can be found here) showed one possible way of integrating a fun single-player using JavaScript with APIs provided by the upcoming HTML5 standard. In this article we will discuss the final implementation of the multiplayer server in C# using Fleck build by Jason Staten.

We will have a look at the basic concepts behind the server's structure, the object oriented design of the match and the ability to give the server commands over the WebSocket protocol. We will also have a short look at the modified JavaScript and the lessons from the whole project. There is still the possibility of a fourth article in this series, where the topic would be a mobile implementation of SpaceShoot. That one would be most concerned about the changed UI and possible performance tweaks. However, a release date for that article is at the moment hard to predict, since the right ideas as well as the need for the mobile implementation are quite limited.

The game can be found (and played) at html5.florian-rappl.de/SpaceShootMulti/.

Video and Background

We made a short video of one of our battles. Even though it is a little bit over 12 minutes long only a short look should be sufficient in order to grasp the basic concept of the game itself. The video is hosted on YouTube:

Watch the video at YouTube

The so-called HTML5 standard is generating more and more attention. This is incredible considering that the official standard will not enter the final status in the next years. This is also quite amazing due to the fact that some of words or techniques often mentioned in the context of HTML5 are not included in the official W3C standard. Regardless of those two valid points against any HTML5 article, we will discuss the option of creating a game server in C# that builds the basis for a game that will be viewed in a web browser with a JSON compatible scripting engine. Therefore we will be on the edge of current technology.

It is possible to include other systems as well. The web browser is just an obvious pick, since it provides platform independence and a very rich API. It is also a system that includes a lot of debugging tools. Those tools are not as excellent as the ones provided by Visual Studio, however, they are sufficient in order to know where the problems occur and to get an idea about of to fix them. One of the problems in a seperated client/server application is always the debugging of the whole application. Since client and server are separated usually two debugging procedures have to be done. One way around this mess is the usage of test driven design for at least the server application. There are some JavaScript framework that help building up test driven design in JavaScript. Those frameworks are (in my opinion) quite well written, but too limited due from the JavaScript side or the current JavaScript IDEs. This article will not focus on test driven design.

We will focus on the techniques used with C# as a language and the .NET-Framework. We will investigate some of the most interesting methods and classes and explain the basic construct of client-server-interaction for this game. We will also have a look at the basic server construct including an advanced command pattern to execute methods over the command line or other ways (web pages, GUIs, ...). Finally we will analyze the changes in the JavaScript files for the SpaceShoot website.

Browser compatibility

The game has been tested with the latest versions of the major browser vendors. Excluding Microsoft's IE 9 every major browser is capable of the WebSocket technology. However, Mozilla's Firefox and the Opera web browser do require further steps in order to activate this technology.

Information for activating WebSockets in the latest version of Opera can be found at techdows.com/2010/12/enable-websockets-in-opera-11.html. For (some versions of) Mozilla Firefox, a similar documentation can be found like the one available at techdows.com/2010/12/turn-on-websockets-in-firefox-4.html.

Technologies

This project did already contain quite a lot of different technologies. Right now it is just the time to mention the technologies used when designing the server:

  • Fleck for driving the WebSocket with C# and the .NET-Framework.
  • JsonValue by Microsoft for giving us a simple JSON API.
  • A lot of reflection (commands, properties, collision, ...)
  • Some .NET 4.0 features like the Complex class
  • A little bit of test driven design to implement some methods correctly without headaches and provide some tests for future changes

During the development some problems with Fleck did come up. Therefore I lookup up the source code available at GitHub. Recognizing no mistake there I tried an update with Nuget, realizing I was working with an old version. This update was therefore essential, because it alleviated me from most of my troubles.

Interfaces

A lot of interfaces have been used in order to distinguish between different abilities of the objects available in the game. There are five central interfaces:

  • IExplodable requires that the inheriting object implements an explosion (sprite) number.
  • IOwner marks the inheriting object as ownable including a property that stores the owner.
  • IVelocity flags the object as moveable and gives the object properties that contain the velocity in x and y direction.
  • IParticleUpgrade for creating identifiable particle upgrades and implementing a method to perform the upgrade.
  • IShipUpgrade is used by atomic ship upgrades for being identifiable and implementing methods for loading and unloading upgrades.

Three out of the six core interfaces deal with upgrades. Let's have a look at the infrastructure of those upgrade-lists:

The class diagram for the upgrade lists

We see that every upgrade list inherits directly from the abstract upgrade list class Upgrades<T>. This class gives the freshly created concrete upgrades class a specialized list and some useful methods like a virtual Logic() method as well as Add() and Clear(). The generic type T that has to be set by inheriting from Upgrades<T> has a constraint to be of type IUpgrade. This interface is the parent of both, IShipUpgrade and IParticleUpgrade, and will be the parent of other upgrade interfaces to come.

Next to the distinguishing and extending interfaces are those for characterizing possible collision partners. One of the problems of the JavaScript code was to extend the logic() method of every new object in order to cover possible collisions. With C# and the possibility to manage this with reflection and globally (by inheritance) we are much more productive. All in all every object just has to inherit the right interfaces to mark itself collidable with certain other types of objects:

C#
public class Ship : GameObject, IAsteroidCollision, /* ... */
{
    // Code of the Ship class
}

public class Asteroid : GameObject, IShipCollision, /* ... */
{
    // Code of the Asteroid class
}

So here we are not only marking the ship as collidable with an asteroid, but we have to mark the asteroid as collidable with a ship as well. This is necessary since we are not performing n2 - n collision detections with our n objects, but just (n2 - n) / 2 collision detections, i. e. every collision is checked only once. By inheriting from the corresponding interface (like IAsteroidCollision) the class has to implemented one method: OnCollision(). The method name is the same for all collision interfaces, however, the argument is different and related to the kind of object the class is colliding with. By implementing IAsteroidCollision the method's signature would be void OnCollision(Asteroid asteroid).

The method to check for possible collisions is coded in the Match class. Here we have the Next() method to perform all logic steps. It contains the following code snippet:

C#
// Some code

Parallel.For(0, N, i =>
{
    for (int j = i + 1; j < N; j++)
        if (Objects[i].CheckCollision(Objects[j]))
            PerformCollision(Objects[i], Objects[j]);
});

//More code

We do use the parallel-for loop brought into gameplay with the .NET-Framework 4. The usage is quite straightforward. All in all we are able to easily transform any for-loop into parallel-for if we have no dependencies at all. Otherwise it gets a little bit more complicated. The magic is then performed by using a lambda expression. The CheckCollision() method is available in the abstract GameObject class. Some special objects like the bomb override this method, just to wrap it including a more specialized condition for checking collisions.

The PerformCollision() method is being called once a collision seems possible. The possibility is calculated using just coordinates and sizes of the two corresponding objects. Now we need to know if a collision is also possible from the types of objects:

C#
public bool PerformCollision(GameObject source, GameObject target)
{
    var t_source = source.GetType();
    var t_target = target.GetType();
    var coll_source = t_source.GetMethod("OnCollision", new Type[]{ t_target });

    if (coll_source == null)
        return false;
    
    coll_source.Invoke(source, new object[] { target });
    return true;
}

So here we use reflection to check if the source object contains a method called OnCollision() which accepts the specific target as parameter. If so then we invoke this method using the target.

Another area where reflection is really useful is the generation of upgrades. We use a static method Generate() in the UpgradePack class. The method does not know any upgrades it can generate. However, it knows that any upgrade has to implement the interface IUpgrade. Knowing this we can generate a random number that represents a specific class to use. Then an instance of that class is created.

C#
public static UpgradePack Generate(Match game)
{
    var ran = game.Random;
    var up = new UpgradePack();
    up.X = ran.Next(0, game.Width);
    up.Y = ran.Next(0, game.Height);
    var types = Assembly.GetExecutingAssembly().GetTypes().Where(
        m => !m.IsInterface && m.GetInterfaces().Contains(typeof(IUpgrade))).ToList();
    var idx = ran.Next(0, types.Count);
    var o = types[idx].GetConstructor(System.Type.EmptyTypes).Invoke(null) as IUpgrade;
    up.Upgrade = o;
    up.Type = o.ToString();
    return up;
}

The advantage of using reflection here is the reduced amount of code maintenance. Without reflection we would need to register any acceptable class in the UpgradePack's instance(s) or singleton instance. Therefore we would need to write code on multiple places instead of just adding or removing classes with the appropriate inheritances.

Classes

Different classes have been written in order to construct a nicely object oriented approach. Every game object inherits from the abstract base class GameObject, which already contains quite a lot of useful implementations. Every class has to implement its own Logic() method. The class diagram looks like the following:

The class diagram for the different game objects

We can recognize some of the (known) objects from the game. With Particle and Bomb we have the two types of weapons. The ships are represented by the Ship class. Asteroids have their own class. Every upgradepack has to inherit from the abstract class Pack. This is used by reflection to dynamically register and generate such packages. The packs can only collide with ships. Therefore the interfaces shown in the diagram do have a crucial role.

It is also interesting to note that not only real objects inherit directly from the abstract class GameObject, but also another type of object: GameInfoObject. Classes that inherit directly from this layer are used only for information. Here we have the InfoText (for displaying texts) and Explosion (for displaying explosions). There is one big difference to the other game objects: While normal game objects perform the logic steps on the server, the game info objects perform their logic on the client.

This means that game info objects are only created on the server. They are removed after they've been sent out to the clients. The clients will do the logic steps for those informative (but non-interacting) objects. This trick helps us reducing the amount of work-load on the server, while still being able to produce a game that includes texts, explosions and other objects. We will now have a deeper look at on of the game objects shown above:

The class diagram for the upgrade lists

So let's take the class to be used for particles. We see that all corresponding constants are now (compared to the JavaScript version) in the correct place. We have numerous properties and important methods. The most important method is of course the Logic() method, which is called at every logic step. There are actually to methods to create a new instance of this class. One possibility is quite standard over the constructor. Another (most often more useful) possibility is provided with a static Create() method. The last one accepts some parameters and performs a lot of important tasks. The ArtificalIntelligence() method is only used when the particle is in the boomerang mode. This is the case with the help of a special upgrade. Instead of a linear route another more focused and specialized path is followed. In order to determine this path the ArtificalIntelligence() method is required.

Next to the (required due to interface implementation or due to the logic) properties we have the very important ToJson() method. This one must be implemented in any class which inherits from GameObject. While the classic ToString() method can be implemented in any object (giving a proper string representation of the instance back), this one has to be (giving back a proper JSON representation). We will call this method in any included object right before sending out the whole game state to every client.

Commands

This interface has to be implemented by every command

Every command has to implement the interface shown above. This guarantees invocation as well as other required methods. With the implementation of the shown methods it can be determined if a command can be executed. It is also possible to undo previously executed commands. It is worth noticing that the commands are completely independent of the console. They do not write any output to the console, i.e. they can be used with a variety of possible views, e.g. webpages, forms or the already mentioned console.

The singleton class Commands contains the commands

All commands are brought together in the singleton class Commands. In order to talk to the current instance we just have to call Commands.Instance. The private constructor of the Commands class looks like the following:

C#
private Commands()
{
    // Create a stack of previously commands for undoing them
    undoList = new Stack<ICommand>();
    // Also save the last typed commands that can be reverted
    undoCallings = new List<string>();
    // Here we use reflection to dynamically register the commands
    var icmd = typeof(ICommand); // Every command has to implement this interface
    // Get all commmands in this interface
    var cmds = Assembly.GetExecutingAssembly().GetTypes().Where(m => icmd.IsAssignableFrom(m) && !m.IsInterface).ToList();
    // Now we will create an array with instances of all commands
    commands = new ICommand[cmds.Count];
    for(int i = 0; i < cmds.Count; i++) // Here the empty (standard) constructor is very important
        commands[i] = cmds[i].GetConstructor(System.Type.EmptyTypes).Invoke(null) as ICommand;
}

In order to invoke any command (from the command line, or any other text-based interface) we can use the Invoke() method of the Commands class. Let's have a look at the concrete implementation of this method:

C#
public bool Invoke(string command)
{
    string _origcmd = command;
    //Normalize command
    var cs = command.Trim().Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
    command = cs.Length > 0 ? cs[0] : string.Empty;
    var args = new string[0];

    if (cs.Length > 1)
        args = cs.Skip(1).ToArray();

    foreach (var cmd in List)
    {
        if (CheckForCall(cmd, command))
        {
            if (cmd.Invoke(command, args))
            {
                if (cmd.CanUndo)
                {
                    undoList.Push(cmd);
                    undoCallings.Add(_origcmd);
                }

                last = cmd;
                return true;
            }

            return false;
        }
    }

    return false;
}

So what is actually going on here? First any text command is normalized so that additional (non-required) spacings are removed. Afterwards the first element from the list of items or an empty string is taken as the actual command. The rest is then saved as optional arguments. Finally we iterate through our list with commands and look for a calling match. If we found one we invoke the corresponding command. The command is then evaluated for being undo-able and saved as last called command. We return true to show the calling method that the command has been successfully executed. Otherwise we return false to signal a failure in either the command itself or the arguments.

How is it possible to use the Commands class from the web? By connecting to the running server via the WebSockets protocol we can not only play games with the server, but also post commands to it. The commands will be redirected to the Commands.Instance object and invoke the Invoke() method. To achieve this the following code snippet is used (located in the MatchCollection class):

C#
public void UpdatePlayer(IWebSocketConnection socket, string message)
{
    var j = JsonObject.Parse(message);

    if (j.ContainsKey("cmd"))
    {
        switch (ParseString(j["cmd"]))
        {
            /* Not interesting for commands */

            //Everything in order to control the server
            case "console":
                //Ensures that the server can only be controlled from the localhost (127.0.0.1)
                if (socket.ConnectionInfo.ClientIpAddress.Equals(IPAddress.Loopback.ToString()))
                {
                    //Was it a valid command ?
                    if (Commands.Instance.Invoke(ParseString(j["msg"])))
                        socket.Send(ReportConsole(Commands.Instance.Last.FlushOutput()));
                    else //Send error if not
                        socket.Send(ReportConsole("Command not found or wrong arguments."));
                }
                else //Obviously not enough rights (connection from outside 127.0.0.1)
                    socket.Send(ReportConsole("Not enough privileges for sending commands."));
                break;
        }
    }
}

So the only thing one has to be sensitive about is the question who should be able to send commands to the server. Right now only locally connected players (we can also call them clients) can send those commands. This seems like a big restriction. However, this gives us the possibility to write a web page which performs the communication (locally) and shows us the results. In order to ensure security such a website should have a proper login with a secure authentication process.

Implementing AI

The idea of including some nice bots is usually not a bad one. In this case we will follow an interesting approach, which is a solid basis for further upgrades. The bots from this server will work over the Keyboard class. Every Ship has an instance of this class in its portfolio. The difference between a bot and a human is given over the IsBot property of the Ship class. In case of a bot the Logic() method of the current Ship will call the Automatic() method of the corresponding Keyboard instance. This method looks like the following:

C#
public void Automatic()
{
	//Resets previous keyboard commands
	Reset();

	//Instantly respawn if necessary
	if (Ship.Respawn)
	{
		Shoot = true;
		return;
	}

	switch (Ship.BotLevel)
	{
		case 1:
			//Monte Carlo is for Bot Level 1 and 2 only
			MonteCarlo();
			break;
		case 2:
			MonteCarlo();
			//ForceBots depend on some force by other objects
			ForceBot();
			break;
		case 3:
		case 4:
			//The goodbots is a potential based method -- level 3 is 3/2 and level 4 is squared potential
			GoodBots();
			DeployBomb();
			break;
		case 5:
			//Ultra bots are not as strong as their name suggests - they just take information and do something with it
			UltraBot();
			DeployBomb();
			break;
	}
}

The first thing we notice is that any previously set command (or keyboard key) will be unset and that bots will always directly respawn (not waiting for a better moment or having a coffee break). We also see that some methods are being used in order to distinguish between different kinds of bots. This is kind of interesting and needs to explained a little bit further.

In a usual game a lot of different players attend. We have some really bad ones, some really good ones, and a lot of average players. Of those average players the majority is somewhat OK, while a few of them are quite good. Others might be mediocre. So all in all we have a distribution as shown in the next figure.

The distribution of bot classifications

In order to set a fixed level for every bot the level has to be determined at creation. Since the .NET random generator does only give us uniformly distributed pseudo random numbers we have to use an external library or write our own method. In this case we can do the last one, since it is not that much of a problem and we can control any overhead. There are actually more possible solutions in order to create a normal pseudo random number generator from a uniform one.

One of the most used solutions is the coordinate transformation, where two uniform random numbers will be created in order to create (actually two and we could and should store the second one for further usage, but this has been omitted until now) a normal random number. In order for this to work we use that exp(x2)exp(y2)=exp(x2+y2) and substitute x2+y2 by r2. The full code looks like this:

C#
double GaussianRandom()
{
	var u = 0.0;
	var v = 0.0;
	var r = 0.0;

	do
	{
		u = 2.0 * Game.Random.NextDouble() - 1.0;
		v = 2.0 * Game.Random.NextDouble() - 1.0;
		r = u * u + v * v;
	}
	while (r == 0 || r > 1);

	var c = Math.Sqrt(-2 * Math.Log(r) / r);
	return u * c;
}

There are faster implementations out there. However, this is one of the easiest to implement and since it is not used that often (for every bot just once), we can easily afford the overhead.

The concept

The basic concept of having a secure server to prevent cheating while making the code readable for anyone is displayed below:

The concept of the client-server-interaction

The solution to the security (cheating) issue comes with a price tag: We have to do all the hard work on the server. Otherwise we could never know if everyone still sees the same game. Another point is that this construction gives us some robustness regarding synchronization and latency. Right now we are doing the following:

  1. If a player joins the game (or hosts the game) he gets the full information of the game, i.e. every object with all information. The client then constructs the game based on this information.
  2. If a player presses or releases a key the updated keyboard information will be send to the server.
  3. Every 40ms a logic step is executed on the server. This logic step uses the current keyboard information from every user.
  4. After the logic step is completed the changed game state will be sent to every client. The clients have to remove objects that will be removed from the next round on, as well as add new objects. New objects can be either new players (ships), asteroids, (upgrade-) packages etc.
  5. If a player leaves the game his ship is being marked to be removed. It will then be removed after the next logic step.

This means that we do not care if a player sent us current keyboard information or not. We do also not care if a player received the current status. In general every player is responsible for his own connection. If the connection is not good (fast) enough he has to suffer from it -- not the other players. This also means that most of the work is done on the server. Only a few tasks can be done on the client. One of those tasks is the fading of information messages. The fading is not controlled by the server. Here the server is just responsible for creating the information texts. After the text has been sent to the client it is immediately removed from the server's memory. The client is then responsible for not only drawing it - but also for it's logic steps. Those logic steps are synchronized with the message receiving from the server.

The reason for this is simply that the decay of the message should be bound to the game or the drawing of the game. The drawing as well as the game itself is bound to the game's logic, which takes place on the server. Therefore we can say that we just replaced the client's logic loop with a server's logic send-loop. If we bind the client's (small) logic routines to the network receive method, we indirectly bind those routines to the original logic. To say it short: It just looks more consistent.

Some JavaScript modifications

In order to support the modified server completely a different structure was required for the client's JavaScript code.

Next to obvious methods like the ones that wire up the menu (host, join, ...) we have to include different methods for starting the game. The singleplayer is focused on a game loop which is then called all 40ms. Now this loop is sitting on our server. We only receive the message sent by the server in an interval of about 40ms. Right after joining (or hosting) a multiplayer game we receive a big package from the server containing the game's current state. This message is used in order to initialize the game:

Java
game.startMulti = function(data) {
    // Reset game 
    game.reset();
    // Set the appropriate game mode
    game.multiplayer = true;
    // Set the document's title
    document.title = 'Multiplayer - ' + DOC_TITLE;
    
    // Initialize the corresponding arrays

    for(var i = 0, n = data.ships.length; i < n; i++)
        ships.push(new ship(data.ships[i]));

    myship = ships[ships.length - 1];

    for(var i = 0, n = data.asteroids.length; i < n; i++)
        asteroids.push(new asteroid(data.asteroids[i]));

    for(var i = 0, n = data.particles.length; i < n; i++)
        particles.push(new particle(data.particles[i]));

    for(var i = 0, n = data.packs.length; i < n; i++)
        packs.push(new pack(data.packs[i]));

    for(var i = 0, n = data.bombs.length; i < n; i++)
        bombs.push(new bomb(data.bombs[i]));

    game.running = true;
    loop = true;
};

The variable loop is just set to be true. This is just to prevent any errors. Before this variable was set to the pointer of the interval that has been executed with the GameLoop. In cases of the game being stopped, the loop was set to null (equivalent to false) right after the interval was cleared. We see that only initialization is done in this method. There is no drawing method whatsoever being invoked.

Along with the method to initialize the multiplayer game we require another one that is being called on receiving a data package. This one looks like this:

Java
network.receive = function(e) {
    var obj = JSON.parse(e.data);
    
    switch(obj.cmd) {
        case 'next':        
            game.continueMulti(obj);
            break;
        case 'chat':
            chat.append(obj);
            break;
        case 'info':
            infoTexts.push(new infoText(obj.info));
            break;
        case 'list':
            network.setList(obj.list);
            break;
        case 'current':
            game.startMulti(obj);
            break;
        case 'error':
            network.error(obj.error);
            break;
        case 'console':
            console.log(obj.msg);
            break;
    }
};

So we basically just decide what kind of message is being sent by the server and execute the appropriate method. We have already seen the corresponding method in the case of current. Now we are interested in the method that is mostly called, i.e. when we receive a package that is of type next:

Java
game.continueMulti = function(data) {
    // If this has been entered accidentally then leave! 
    if(!loop || !game.multiplayer)
        return;
        
    //OK measure time for ping gauge
    network.gauge.value = network.measureTime();
    
    //Use some (local) variables
    var length, i;

    //Perform some client logic with the local explosions
    length = explosions.length;
    for(i = length; i--; )
        if(!explosions[i].logic())
            explosions.splice(i, 1);

    //Append the new explosions (from the server)
    for(i = data.explosions.length; i--;)
        explosions.push(new explosion(data.explosions[i]));
        
    /* Do same for info texts */

    //Updating the local array with values from the server
    length = ships.length;
    for(i = length; i--;) {
        if(!data.ships[i] || data.ships[i].remove)
            ships.splice(i, 1);
        else
            ships[i].update(data.ships[i]);
    }
    //Append the (rest) of the server's array to the client
    for(var j = length, n = data.ships.length; j < n; j++)
        ships.push(new ship(data.ships[j]));

    /* Same for rest [ Asteroids, Particles, Packs, ... ] */

    // Set gauges
    gauge.speed.value = myship.boost;
    
    // Perform the (client) logic
    chat.logic();
    statistic();
    
    // Draw the game
    draw();
    network.gauge.draw();
};

Here most of the work is to iterate over all the arrays, either updating, deleting or adding entries. After the incoming data was analyzed we have to do a little bit of logic on the client as well as perform some drawing. The last thing is quite important - after all it is the only location where drawing is performed in the case of a multiplayer game. The only question is now how interactivity is achieved. The answer lies in a small modification of the manageKeyboard() method as displayed here:

Java
var manageKeyboard = function(key, status) {
    //Do not use this method if the chat is currently in use
    if(chat.shown)
        return false;
        
    /* Code as before, returns in case of unimportant key */
    
    //Here is the important part: send to server in case of multiplayer
    if(game.multiplayer)
        keyboard.send();
        
    return true;
};

The keyboard.send() method is one of the required network methods. It is just a wrapper for the network.send() method including the keyboard object and the right identifier (cmd). The code snippet should be enough in order to fully understand what is going on:

Java
keyboard.send = function() {
    // Uses the network.send with the included object
    network.send({
        keyboard : keyboard,
        cmd : 'keys',
    });
};

network.send = function(obj) {
    // Sending the given object as a (JSON-) string
    network.socket.send(JSON.stringify(obj));
};

The Server class

The class which creates the actual binding of our program with Fleck (which gives us a nice access layer to the WebSocket technology by using TCP/IP) is called SpaceShootServer. Here we create our instance of the MatchCollection class to give us a lobby and a lot of possibilities. We also save the startup time. The server is then controlled by the methods Start(), Stop() and Restart(). The code has the following layout:

C#
public class SpaceShootServer
{
	#region Members

	MatchCollection matches;
	WebSocketServer server;
	DateTime startTime;

	#endregion

	#region Properties

	/// <summary>
	/// Gets the DateTime of the server's startup
	/// </summary>
	public DateTime StartTime
	{
		get { return startTime; }
	}

	/// <summary>
	/// Gets the corresponding MatchCollection with all matches and players
	/// </summary>
	public MatchCollection Matches
	{
		get { return matches; }
	}

	/// <summary>
	/// Gets the current status if the server is running or not
	/// </summary>
	public bool Running
	{
		get { return server != null; }
	}

	#endregion

	#region ctor

	public SpaceShootServer()
	{
		Construct();
	}

	#endregion

	#region Methods

	private void Construct()
	{
		matches = new MatchCollection();
		server = new WebSocketServer("ws://localhost:8081");
	}

	/// <summary>
	/// Starts the server's execution
	/// </summary>
	public void Start()
	{
		startTime = DateTime.Now;

		server.Start(socket =>
		{
			socket.OnOpen = () =>
			{
				matches.AddPlayer(socket);
			};

			socket.OnClose = () =>
			{
				matches.RemovePlayer(socket);
			};

			socket.OnMessage = message =>
			{
				matches.UpdatePlayer(socket, message);
			};
		});
	}

	/// <summary>
	/// Stops the server's execution and closes the server
	/// </summary>
	public void Stop()
	{
		server.Dispose();
		server = null;
	}

	/// <summary>
	/// Restarts the server :-)
	/// </summary>
	public void Restart()
	{
		matches = null;
		server.Dispose();
		GC.Collect();
		Construct();
		Start();
	}

	#endregion
}

All those public methods are controlled by the commands as presented above. It is possible to restart the server or stop the execution at all. The Start() method has been invoked by our program in the beginning. The most important part in the SpaceShootServer class is certainly the event binding. Here we use some lambda expressions in order to set the properties appropriately.

Another important issue when using the Fleck library as the responsible layer for the Websocket access is the restriction to strings as valid arguments for sending data. It is true that the specification just allows strings (which are serialized to bytes for transport anyway). However, we are only interested in a certain kind of strings: strings build out of JSON objects. Therefore we implement the following extension method:

C#
public static void Send(this IWebSocketConnection socket, JsonObject msg)
{
   socket.Send(msg.ToString());
}

Using this extension method we can invoke the Send() method with JsonObjects as with strings. The advantage lies in the readability. It is now much cleaner without the unnecessary ToString() call.

Using the code

The server should be easily adjustable for other games and projects. With the basic commands already implemented and an existing MatchCollection the server can be used to create a solid basis for a variety of different games. This section discusses the parts of the code which should be changed, the ones which could be changed and the ones which should not be changed by taking this code as a basis for some WebSocket games.

The following image shows a screenshot of the server's command line interface. The advanced command pattern (including the already built-in commands) should be part of any project building up on this one.

The command line interface of the server

Basically the folders Structures, Interfaces, and Game/Upgrades are only SpaceShoot related. So they could be removed for other projects. The Game folder contains some files which might be good as a starting point. MatchHelpers, MatchCollection, and Match do contain some SpaceShoot related code, however, the related part can be easily cut out of the more generic rest. Also GameObject and GameInfoObject are more or less generic. Some methods or properties might be close related to SpaceShoot, but they can be modified or removed easily. The other classes of the folder Game are fully related to SpaceShoot and could be removed.

The files in the folder Server are as generic as the code in the Program class with the Main() entry function. Changes here should not be applied.

Some of the commands being found in the Commands folder will have to be modified regarding the changes in the MathCollection class. If the class is fully removed then a lot more changes are about to come. There could have been ways around it involving more interfaces and dependency injection, however, it is obvious that some things can be over-engineered. In this case the server is somehow specialized to the game, i.e. it would be kind of waste to build generic interfaces and such when we actually want to specialize the commands to this specific kind of server (SpaceShoot). It is therefore obvious that the same (specialized commands for a specialized server) applies for future projects. Therefore the commands rely on the MatchCollection (and other stuff like Match and Ship) by intention.

The test project is not part of test driven design, but part of just including some fixed points for testing. The included list of methods is neither complete nor very clever. The list grew and shrunk due to demand. Therefore the test should probably be the first thing that is being removed or ignored from this project, since the tests are really focused on problems regarding SpaceShoot.

A quick way to find a good spawning position for a player has been included in the Match class. We see the following code:

C#
public Point StartingPosition()
{
    //As default point -- the middle of the plane
    var pt = new Point { X = Width / 2.0, Y = Height / 2.0 };
    var dist = 0.0;
    var monitor = new Object();

    //If there are other objects included -- determine position
    if (Objects.Count > 0)
    {
        //Check 10 positions in parallel
        Parallel.For(0, 10, i =>
        {
            //Generate a position (x and y within a certain rectangle)
            var x = (Random.NextDouble() * 0.6 + 0.2) * Width;
            var y = (Random.NextDouble() * 0.6 + 0.2) * Height;
            //Find minimum position; first position will always be smaller than this
            var min = double.MaxValue;

            //Loop over all objects
            for (int j = 0; j < Objects.Count; j++)
            {
                //Perform measurement only if object is "alive"
                if (Objects[j].Remove)
                    continue;

                //Calculate euclidean distance
                var dx = x - Objects[j].X;
                var dy = y - Objects[j].Y;
                //OK is this object closer than the closest one before?
                min = Math.Min(Math.Sqrt(dx * dx + dy * dy) - 10.0 - Objects[j].Size / 2.0, min);
            }
            
            //Now with a locking (global update)
            lock (monitor)
            {
                //Of the minimum now is bigger than the minimum before - GREAT!
                if (min > dist)
                {
                    dist = min;
                    //Lets take this is current starting point
                    pt.X = x;
                    pt.Y = y;
                }
            }
        });
    }

    return pt;
}

So what is happening here? We are generating 10 possible starting positions and checking which of them maximizes the minimum distance to all other objects. It will rarely happen that the obtained solution is the optimum solution, however, it will usually happen that the obtained position will be a good solution. And we did get this with just a few calculations (depending on the number of objects). Overall we had to generate 20 random numbers and perform at maximum 10 * N measurements (where N is the number of objects in the game). Compare this to obtaining the best solution which can be done by checking every pixel in that rectangle. This could involve W * H * N measurement (where W and H are the width and height of the rectangle). In practice we were quite amazed with the performance this way. We had no measureable performance impact but obtained solutions that were close to perfect at a first glance.

Points of Interest

Already quite a few cheats have been found. One of the earliest was that someone could pick black as primary and secondary colors, nearly cloaking him. This was not the main problem with the color choice. The biggest problem was that particles have also the color of the player, resulting in cloaked particles. This was way too strong, so it had to be prevented on the server side. Any color choice is evaluated there and adjusted to a color which is bright enough for the any opponent.

By using some of the real useful .NET-Framework 4, as well as C# 4 properties and methods we can boost our productivity. A good example for this is the new Complex structure from the System.Numerics namespace. In order to use it we have to include the corresponding assembly reference. It gives us a very easy possibility to calculate angles between two or more points in two dimensions. All we need to do is represent the points in a complex number where X would be the real part and Y would be the imaginary part - depending on the coordinate system we pick. Every complex point has a certain absolute value (named Magnitude by the developer team) and a certain angle (named Phase). This is because it is possible to represent any complex number z = x + iy as z = r * exp(i * f), with r = (x2 + y2)1/2.

Also the usage of parallel-for loops and other features of the task library as well as LINQ and lambda expressions did help us a lot. By including the parallel task library we made our server scalable. By using LINQ we did produce a more readable code - write less, do more. The same can be said for lambda expressions, which are already essential for Fleck.

The game can be found (and played) at html5.florian-rappl.de/SpaceShootMulti/.

Avoiding dark colors

For picking a suitable primary and secondary color a color-picker control has been included with the free JSColor script. The drawback is that players can also pick quite dark colors, which make them hard to distinguish. The main problem with this is that not the ship itself, but the particles (shots) from such a ship cannot be seen or is really hard to detect.

The only way to safely prevent abuse of darkish colors is to introduce such a routine on the server. Every color has to be set in the Colors class. This class contains properties for the primary and the secondary color. By setting one of these properties a routine is called with the passed value. The called method is this:

C#
public string CheckDarkness(string value, string[] colors)
{
	var rgb = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

	if (rgb.Length != 3)
		return colors[0];

	var r = 0;
	var g = 0;
	var b = 0;

	if (!int.TryParse(rgb[0], out r) || !int.TryParse(rgb[1], out g) || !int.TryParse(rgb[2], out b))
		return colors[0];

	var h = new HSV(r, g, b);
	h.Value = Math.Max(h.Value, 0.5);
	return h.ToString();
}

We only get (comma separated RGB) strings for colors. So the first task is to see if the passed string is really a color. If not then we return a predefined color from an array, which is either primaryColors or secondaryColors. If everything seems ok then we create a new HSV class instance. This is a nested class. It has the following code:

C#
class HSV
{
	public double Hue { get; set; }
	public double Saturation { get; set; }
	public double Value { get; set; }

	public HSV(Color rgb)
	{
		int max = Math.Max(rgb.R, Math.Max(rgb.G, rgb.B));
		int min = Math.Min(rgb.R, Math.Min(rgb.G, rgb.B));

		Hue = rgb.GetHue();
		Saturation = (max == 0) ? 0 : 1.0 - (1.0 * min / max);
		Value = max / 255.0;
	}

	public HSV(int r, int g, int b) : this(Color.FromArgb(r, g, b))
	{ }

	public Color ToRgb()
	{
		var hi = Convert.ToInt32(Math.Floor(Hue / 60)) % 6;
		var f = Hue / 60 - Math.Floor(Hue / 60);

		var value = Value * 255;
		int v = Convert.ToInt32(value);
		int p = Convert.ToInt32(value * (1 - Saturation));
		int q = Convert.ToInt32(value * (1 - f * Saturation));
		int t = Convert.ToInt32(value * (1 - (1 - f) * Saturation));

		if (hi == 0)
			return Color.FromArgb(255, v, t, p);
		else if (hi == 1)
			return Color.FromArgb(255, q, v, p);
		else if (hi == 2)
			return Color.FromArgb(255, p, v, t);
		else if (hi == 3)
			return Color.FromArgb(255, p, q, v);
		else if (hi == 4)
			return Color.FromArgb(255, t, p, v);
		
		return Color.FromArgb(255, v, p, q);
	}

	public override string ToString()
	{
		var c = ToRgb();
		return string.Format("{0},{1},{2}", c.R, c.G, c.B);
	}
}

Nothing to exciting here. It is basically a HSV (Hue, Saturation, Value) class which has two possible ways of being constructed. Either by a System.Drawing.Color structure or with three values (red, green, blue). We need this class, since it allows us to get the value of Value. This represents the lightness. We want the value to be 0.5 or higher. Therefore we had the following call before: h.Value = Math.Max(h.Value, 0.5).

Next we just use some wicked code to transform HSV back to RGB. Usually we will not call this method, since we did also override the ToString() method. This one will now just give us a comma separated string containing red, green and blue - a RGB string that can be read from any CSS parser. With this code no one is able to abuse the usage of darkish colors in order to get an unfair advantage in a match.

What does the whole project teach us?

This is a really interesting question I asked myself after finishing the server program. The big message is: Start with the server before you write any line of HTML, CSS, or JavaScript.

This sounds odd, however, I think my JavaScript design would have been significantly better with a really good and solid server already up and running. Writing the code like this I had to adjust the server first to the JavaScript needs (going from OOP to prototype / procedural programming is for me much easier than the other way), which was kind of messy and showed me that my JavaScript code could have been written a lot better by abusing the prototype pattern much more than I did.

Also the extensibility of the program would have been much better, because I was already aware of the connection to the server. Therefore I would have designed the JavaScript to embrace the server and not the other way around. I think that my awareness of these problems is somehow due to using a different language for the server than the client. A lot of the described problems could have been avoided by using node.js. Nevertheless I think using C# for the server gives you not only some coding fun, but also a kind of different perspective to the problem. Also you will get the best debugging tools available, which will help you a lot in building a robust and scalable server.

History

  • v1.0.0 | Initial release | 19.03.2012.
  • v1.0.1 | Minor update with some fixes | 21.03.2012.
  • v1.1.0 | Update (Including AI) | 23.03.2012.
  • v1.2.0 | Update (Including ColorCheck) | 24.03.2012.
  • v1.3.0 | Update (Including Server class, Fleck extensions) | 27.03.2012.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer
Germany Germany
Florian lives in Munich, Germany. He started his programming career with Perl. After programming C/C++ for some years he discovered his favorite programming language C#. He did work at Siemens as a programmer until he decided to study Physics.

During his studies he worked as an IT consultant for various companies. After graduating with a PhD in theoretical particle Physics he is working as a senior technical consultant in the field of home automation and IoT.

Florian has been giving lectures in C#, HTML5 with CSS3 and JavaScript, software design, and other topics. He is regularly giving talks at user groups, conferences, and companies. He is actively contributing to open-source projects. Florian is the maintainer of AngleSharp, a completely managed browser engine.

Comments and Discussions

 
GeneralMy Vote 5 Pin
Shemeemsha (ഷെമീംഷ)8-Oct-14 18:04
Shemeemsha (ഷെമീംഷ)8-Oct-14 18:04 
GeneralRe: My Vote 5 Pin
Florian Rappl8-Oct-14 21:47
professionalFlorian Rappl8-Oct-14 21:47 
GeneralMy vote of 5 Pin
aciura20-Jun-12 7:56
aciura20-Jun-12 7:56 
GeneralRe: My vote of 5 Pin
Florian Rappl20-Jun-12 23:09
professionalFlorian Rappl20-Jun-12 23:09 
GeneralMy vote of 5 Pin
freddyrock8-Jun-12 22:19
freddyrock8-Jun-12 22:19 
GeneralRe: My vote of 5 Pin
Florian Rappl20-Jun-12 23:09
professionalFlorian Rappl20-Jun-12 23:09 
QuestionThanks for sharing Pin
Patrick Kalkman6-Apr-12 21:44
Patrick Kalkman6-Apr-12 21:44 
AnswerRe: Thanks for sharing Pin
Florian Rappl7-Apr-12 2:03
professionalFlorian Rappl7-Apr-12 2:03 
QuestionGreat article there man Pin
Sacha Barber28-Mar-12 23:51
Sacha Barber28-Mar-12 23:51 
AnswerRe: Great article there man Pin
Florian Rappl29-Mar-12 3:56
professionalFlorian Rappl29-Mar-12 3:56 
QuestionNice! My 5 Pin
Rodion Fedechkin28-Mar-12 0:37
Rodion Fedechkin28-Mar-12 0:37 
AnswerRe: Nice! My 5 Pin
Florian Rappl28-Mar-12 4:12
professionalFlorian Rappl28-Mar-12 4:12 
QuestionGood article! Pin
Aamer Alduais22-Mar-12 4:21
Aamer Alduais22-Mar-12 4:21 
AnswerRe: Good article! Pin
Florian Rappl22-Mar-12 6:05
professionalFlorian Rappl22-Mar-12 6:05 
QuestionMy 5 Pin
Mohammad A Rahman20-Mar-12 14:22
Mohammad A Rahman20-Mar-12 14:22 
AnswerRe: My 5 Pin
Florian Rappl20-Mar-12 21:01
professionalFlorian Rappl20-Mar-12 21:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.