You are here:   Blog
Register   |  Login

 

Oct 29

Written by: Richard Waddell
10/29/2013 4:00 PM  RssIcon

Part I – Bare Bones Mechanics

Part II – Basic Classes and Interface Implementations

Part III - Game to Demonstrate Derived Classes and More Sophisticated Interface Implementations

The End Result

The end result is a multi-player game that can be played across the internet by users logged into the same page. Unfortunately it’s not a very interesting game, and the question is still open as to whether I can in fact design an interesting game. What I have done, I hope, is to create a framework that can be used to design all sorts of interesting games. As such, a very simple game actually serves the purpose better as it exposes the details of using the framework to create alternative games and simulations and avoids obscuring them with details about the particular game. Maybe there will be a fourth post with an interesting game, but first we have to come up with a game lobby for this one, so who knows?

The large graphic below shows a game in progress, so you can’t see the buttons used to enter the game, shown immediately below. Game play starts as soon as there are at least two players. You can see below that the game is in progress when Red player enters the game and gets the initial Notification “Click button to enter game”.

image

The goal is to click on an opponent’s player so that your player moves on top. For this particular game, a point is awarded when you are successful, so, back on the Server, the behavior of the particular ooNaThing subtype, in conjunction with ooBerLink and ooBer, is to perform collision detection and increment its score if warranted. A different ooBerLink and ooNaThing might do something completely different, from handling the left mouse click to the consequences of collisions. There’s also nothing to prevent multiple ooNaThing subtypes, all interacting with each other. That’s the part you can plug in.

image

Try It Out

You can try the game online at http://sinalrgame1.lightswitchhelpwebsite.com/HTMLClient/

Background

If you’ve been following this series, then this will be a review, with some enhancements. If not, then it’s a description of how the connection starts up when the screen is initialized

The ooNaWorld Screen

We start out with a screen named ooNaWorld that has a Custom Control named ooNaWorldView:

image

Connecting to the SignalR Hub

Attach the Server-Callable functions when the screen object is available, in the Screen created event handler, ooNaWorldScreen.created

Although this is a description of the connection process, the essential first step is to provide, on the Client, the functions that the Server will call by attaching them to the Hub Client. I chose to do that in the screen created event handler because it gets the screen object as a parameter and some of the Hub Client functions will need it. As you can see, I‘m also creating a _screen global variable that I’m referencing in some other local functions, but I hope to eliminate it. Either way, I still prefer to place the screen object in the scope of the Hub Client functions by making it a local variable of the function that attaches them to the Hub Client proxy, which is defineHubClientFunctions().

myapp.ooNaWorldScreen.created = function (screen) {
    // We'll need access to screen controls from JavaScript functions
    _screen = screen;
    // Define the client functions that will be called from the Server 
    defineHubClientFunctions(screen);
};

The point here is the startup process, so I’m going to defer the details of creating the Hub Client functions.

Start the connection when the Canvas is ready, in the Custom Control render event handler, ooNaWorldView_render

You might expect that I’d immediately start the connection and even be wondering why I didn’t do it from the Screen created event handler.  There’s another step in being prepared for the connection to start and we can’t do it in ooNaWorldScreen.created because we can’t prepare the ooNaWorldView Custom Control from here. The graphical representation of ooNaWorld, which I’m calling the View, is drawn on a Canvas hosted in the ooNaWorldView Custom Control. We can’t start the server until that Canvas is ready, which is right after we’ve created and initialized the Canvas in the ooNaWorldView render event handler.

Make the first call to the Server from a function chained to the connection.start.done() function

The start.done() function solves the problem of knowing when the server can be called.

myapp.ooNaWorldScreen.ooNaWorldView_render = function (element, contentItem) {
    renderOoNaWorldCanvas(element);
    initializeCanvas(LIGHT_YELLOW_LITERAL);
    startHubConnection();
};
// Append the HTML to define the canvas upon which ooNaWorld is drawn
function renderOoNaWorldCanvas(element) {
    var HTML_STRING = $("<canvas id='ooNaWorld' width='500' height='300'></canvas>");
    $(element).append(HTML_STRING);
    $(element).css({
        "border": "black 1px solid"
    });
}
// Initialize the ooNaWorldView canvas
function initializeCanvas(color) {
    // Save the Canvas control reference in a global variable
    _ooNaWorldCanvas = $("#ooNaWorld")[0];
    // Also the canvas color
    _ooNaWorldColor = color;
    $(_ooNaWorldCanvas).css("backgroundColor", _ooNaWorldColor);
    _ooNaWorldCanvas.addEventListener("mousedown", onCanvasMouseDown, false);
}
// Starts the hub and attaches a function to be run when start is complete (done)
function startHubConnection() {
    $.connection.hub.start()
    // The following function is run when start completes successfully
    .done(function () {
        // Update the UI to match the model
        $.connection.ooBerHub.server.newUser();
    })
    .fail(function () {
        alert("Could not Connect! - ensure EnableCrossDomain = true");
    });
}

ooNaWorldView_render() steps to prepare for and start Hub

image

ooBerHub – ooNaWorld Talks to the Server

Everything that happens in the ooNaVerse, let alone ooNaWorld, is controlled by ooBer, so all calls to the Server are calls to ooBer. As a practical matter, we need to implement some mechanism for the calls to work. In this case, we’re using the SignalR Hub Class, which ooBerHub inherits from.

Each method on the ooBerHub called by the Client should map to a method of the same name on ooBerLink

What’s an ooBerLink? ooBerLink is a class which can be considered a component of ooBer. ooBer provides an API of functions that ooBerLink can call and ooBerLink provides an API of functions that the Client can call.  But first the calls have to pass through ooBerHub, which is an implementation detail that should not affect ooBerLink. In other words, ooBerLink should not have to inherit from the SignalR Hub class, which would be necessary for ooBerLink to receive calls on the ooBerLink API directly from the Client.

image

There may be exceptions to that name matching rule, but I can’t think of any. I’d even extend it to say each public method, because ooBerHub’s only concern should be calls from the Client (and what to do with them). As far as ooBerLink knows the calls are coming directly from the View, so ooBerHub should support the same methods as if the calls actually were coming directly from the View.You could define an IooBerLink interface and have ooBerHub implement it, but who would use it? All the calls from the Client are from JavaScript and so resolved (or not) at runtime. It still makes some sense as it allows you to enforce the existence of ooBerLink API methods on ooBerHub, but you would have to create a new interface for each version of ooBerLink, so is it really worth it? Maybe when the APIs become larger it will.

Here’s a code fragment that shows the essentials for the one method we know about so far, NewUser(). Notice that the call from the Client is to ooBerHub.server.newUser() but the method name is capitalized as NewUser on ooBerHub. The translation occurs automatically so that the JavaScript call can conform to the JavaScript convention of camel-casing and the hub C# method name can conform to the C# convention of Pascal-casing.

using Microsoft.AspNet.SignalR;

...

public class ooBerHub : Hub

{
...
    public void NewUser()
    {
        _ooBerLink.NewUser(Context.ConnectionId);
    }
...
}

 

ooBerLink – ooBerHub Talks to ooBer

As you can see below, ooBerLink determines that a call to newUser() means the View should be updated with the current Model. This is where we break away from the direct mapping of methods.   A different ooBerLink subtype might decide on different results, or this ooBerLink might decide on a different result under different circumstances. ooBerLink is the class that allows the rules of the simulation to change by providing different implementations of ooBerLink with different APIs of methods to be called from the Client / View. ooBer never changes but ooBerLink nearly always does.

public class ooBerLink
{
    ...
    // ooBerLink communicates with ooBer through this reference
    protected readonly ooBer _ooBer;
    // ooBerLink communicates with the view through this reference
    protected IOoNaView _ooNaView;
    ...
    // A new user has logged in. Push the model to the view
    public virtual void NewUser(string userId)
    {
        _ooNaView.ShowOoNaWorld(userId, CoreOoNaThingList);
    }
    ...
    // Generate a list of CoreOoNaThings generated from all the ooNaThings in ooNaWorld. This property is used by NewUser to return the model to
    // the caller. ooBerGameLink would return a list of CoreGameThing instead
    private List<CoreOoNaThing> CoreOoNaThingList
    {
        get { return _ooBer.ooNaThings.Select(o => new CoreOoNaThing(o)).ToList(); }
    }
    ...
}

CoreOoNaThingList gets the list of ooNaThings from ooBer and strips each one down to the information that must go over the wire.

You’ll notice that instead of calling a function on the Client to update the View, ooBerLink makes a call to a method on an IooNaView implementation. This insulates ooBerLink from the details of how the View is updated, and in this case, that there are multiple copies to be updated.

ooBer – The Model

Here’s just enough of ooBer to show the underlying code referenced by the CoreOoNaThingList property in ooBerLink

public class ooBer
{
,,,
    // This list of all existing ooNaThing objects comprises the model
    public IEnumerable<ooNaThing> ooNaThings
    {
        // oonaThings returns a copy of the list rather than a reference
        // The lock allows multiple concurrent readers
        get 
        {
            _ooNaThingsLock.EnterReadLock();
            try
            {
                return _ooNaThings.ToList();
            }
            finally
            {
                _ooNaThingsLock.ExitReadLock();
            }
        }
    }
,,,
}

The ooBer class per se is a shadow of what it was. Other than returning a copy of the ooNaThings list, ooBer mostly just unites an individual ooNaThing with the ooNaThings List where they all reside. They work out the results between them without further input from ooBer. Not only is ooBerLink a replaceable part of ooBer, but so is ooNaThing. By providing alternative versions of ooBerLink and as many subtypes of ooNaThings as you like, hopefully it will be easy to create alternative ooNaWorlds. ooBerLink decides how events in ooNaWorld will affect the Model and the individual ooNaThings involved determine their own specific behavior in how the Model is changed.

Variations in IOoNaView are a little trickier as they will have to conform with the API of functions provided by the Client to update the View and ooBerLink will have to know what methods to expect on the interface. Hopefully creating interfaces that inherit from IOoNaView and adding the methods required to support new functions to update the View, and subtypes of ooBerLink that know about them will do the trick. As I describe later I try to use very generic names for IOoNaView methods to minimize the number of versions that have to be created to support various Models and Views.

IOoNaView – The Server Talks To ooNaWorld

In reality it’s the SignalR Hub that allows the Server to talk to ooNaWorld. Fortunately we can use a component of the Hub object that implements IHubConnectionContext. This enables ooNaSignalRView to make the call to _Clients.Client(userId).showOoNaWorld(allOoNaThings). This is a good time to point out that showOoNaWorld is dynamic, so if it’s not defined on the client you can call, but you’ll call in vain, and there won’t be any error message. The symptom will be that a new user will not get the current ooNaWorld when they log in. As other users move their players the new user will eventually be brought up to date. So be aware that it may not be obvious when something is broken

public class ooNaSignalRView : IOoNaGameView
{
    private readonly IHubConnectionContext _Clients;
...
    public ooNaSignalRView(IHubConnectionContext hubContext)
    {
        _Clients = hubContext;
    }
...
    public void ShowOoNaWorld(string userId, List<CoreOoNaThing> allOoNaThings)
    {
        _Clients.Client(userId).showOoNaWorld(allOoNaThings);
    }
...
}

We create both ooBerLink and ooNaSignalRView in the ooBerHub default constructor.

public ooBerHub()
    : this(new ooBerGameLink(new ooNaSignalRView(GlobalHost.ConnectionManager.GetHubContext<ooBerHub>().Clients)))
{
}
// This constructor accepts an ooBerLink, but only an ooBerGameLink
public ooBerHub(ooBerGameLink anOoBerLink)
{
    _ooBerLink = anOoBerLink;
}

This brings us back full circle as ooNaSignalRView is calling the functions attached to the Hub Client proxy before we started the connection. Here are the bare essentials of hooking up showOoNaWorld, it all follows the same pattern.

// All the client functions that can be called from the hub are defined here
function defineHubClientFunctions(screen) {
    var clientScreen = screen;
    // ooBerHub inherits from the SignalR Hub class, so the following call is getting
    // the hub client object / proxy. I'm giving it the name hubClient.
    var hubClient = $.connection.ooBerHub.client;
    // Now we can create references on hubClient to functions that the hub Server can call.
    // The names defined here must match the names called from the server. Make these names
    // as general as possible to keep the coupling as loose as possible. We want to use
    // generic low-level methods on the server to transmit back the model changes that
    // result from the higher level methods that process the game changes.
    // For instance the call from the server to moveOoNaThing results not in just a move, but
    // also a change to the game, so the more specific 'moveInGame' is called locally
...
    hubClient.showOoNaWorld = function (ooNaThings) {
        $.each(ooNaThings, function () {
            enterGame(this);
        });
    }
...
}

In this case, the Server is passing the entire model to the Client with the expectation that it would do whatever is required to update the View. How the Client goes about that is not the Server’s concern. In this case the Client processes each ooNaThing through the enterGame function, which not only draws the ooNaThing, but makes them players in the game. By keeping the names generic, you can keep the IOoNaView methods generic. ooNaLink and the Client know the specifics of any call, but there’s no reason IOoNaView has to, but using specific names, such as ShowAllThingsInGame instead of ShowOoNaWorld would defeat the purpose.

Now  ooBerHub can make calls on ooBerLink through the _ooBerLink reference, ooBerLink can update the View through the _ooNaView reference, and ooNaSignalRView can update the Client through the _Clients reference.

The Stage Is Set

So everything is in place. Each user has a picture of the Model on the Canvas that represents the ooNaWorldView. Each Canvas has a listener to detect a left mouse click and the Hub provides a LeftMouseClick method for the Client to call. So it’s time to make something happen. I’ve already described the very simple demonstration game. Here’s the implementation:

Here’s how the JumpGameThing maintains its own score:

public class JumpGameThing : ooNaGameThing
{
...
    public override void Move(double xCoordinate, double yCoordinate, List<ooNaThing> allThings)
    {
        // Call the base method and then set the score
        base.Move(xCoordinate, yCoordinate, allThings);
        this.Score += EnteringCollisionsWith.Count;
    }
...
}

The problem with this approach is that ooBer still has to know to call the ooNaThing.Move() method. I don’t want to modify ooBer whenever I create a new ooNaThing variant method or property.

The ooBerRequest

An ooBerRequest can only be created by passing a userId to the constructor. The userId identifies the ooNaThing instance in the Model.

The implementation of abstract method GrantRequest will do whatever is required to fulfill whatever the particular request is. UserThing is a utility function used to retrieve the ooNaThing that goes with the request. I haven’t really thought it out, but maybe you could do the same thing with ooNaThing extensions, but this seems easier to maintain and isn’t ooNaThing-specific. It just needs two essentials – the user id and the operation. As with most aspects of this framework it may become more complex. Right now everything is only as complex as it needs to be. Hopefully I’ve made it flexible enough to allow complexity to be added as the need develops.

public abstract class ooBerRequest
{
    protected string _userId;
    public ooBerRequest(string userId)
    {
        _userId = userId;
    }
    public abstract ooNaThing GrantRequest(List<ooNaThing> allThings);
    protected ooNaThing UserThing(List<ooNaThing> allThings)
    {
        return allThings.Where(o => o.UserId == _userId).FirstOrDefault();
    }
}

Here’s a move request

public class MoveOoNaThingRequest : ooBerRequest
{
    private Double _xCoordinate;
    private Double _yCoordinate;
    public MoveOoNaThingRequest(string userId, Double xCoordinate, Double yCoordinate) : base (userId)
    {
        _xCoordinate = xCoordinate;
        _yCoordinate = yCoordinate;
    }
    public override ooNaThing GrantRequest(List<ooNaThing> allThings)
    {
        var theThing = UserThing(allThings);
        if (theThing != null)
            theThing.Move(_xCoordinate, _yCoordinate, allThings);
        return theThing;
    }
}

The particular subtype of ooNaThing will carry out its own Move implementation. For instance JumpGameThing will do collision detection and give itself points for any things it landed on.

Across the ooNaVerse – the dispersal of ooBer

The ooBer class is further diminished.

public ooNaThing Request(ooBerRequest request)
{
    // The lock only allows one writer at a time
    _ooNaThingsLock.EnterWriteLock();
    try
    {
        return request.GrantRequest(_ooNaThings);
    }
    finally
    {
        _ooNaThingsLock.ExitWriteLock();
    }
}

As I’ve said before, the essence of ooBer exists in ooBerLink and the ooNaThings. ooBer per se just maintains the list of ooNaThings and makes sure the calls from the Client are processed in the correct order by using _ooNaThingsLock.EnterWriteLock(). This is one case where you don’t want write concurrency. You only want each request to be processed after the previous has been completed. Note that the individual ooNaThings in the list are not write-protected, so you can definitely cause problems if you get the list and start making calls against them.

Is This MVC?

Sure, if you hold your head just right and squint.

ooBerLink would be the Controller as it takes calls from the Client and makes calls on the Model.

IOoNaView is the View. As far as possible, what you see on the Client is just a picture of the View. The Client provides JavaScript functions to draw on the canvas, control buttons, and show messages to the user. Why? Because I like code that compiles, so I want to keep the amount and complexity of JavaScript code to a minimum.

ooBer and the ooNaThings make up the Model.

Caveats

I think this is a fairly complete implementation of SignalR, as far as it goes, but it’s also fairly naïve. Performance and timing may require that ooBer be a lot more sophisticated and the Client might need to do a lot more than just draw pictures and update controls.

Hopefully this can all be addressed by extending the current ooBer-ooNaWorld Framework. For instance JavaScript ooNaThing “classes” that can be programmed to mirror their server-side counterpart operations so as to reduce the calls required from Server to Client. I’ll avoid JavaScript when I can, but sometimes it’s Needs /Must. I’ll leave you with the sentiment I expressed in Part I, updated with a fourth anticipated ‘Must’ instance.

image

Download Code

This project is available at http://lightswitchhelpwebsite.com/Downloads.aspx

Tags:
Categories:

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
CAPTCHA image
Enter the code shown above in the box below
Add Comment   Cancel 
Microsoft Visual Studio is a registered trademark of Microsoft Corporation / LightSwitch is a registered trademark of Microsoft Corporation