Tic Tac Toe using MEF, SignalR, Ninject and ASP.NET MVC 4

The game lets two artificial players fight each other in an epic battle of whom will be the one with five horizontal, vertical or diagonal discs in a row. The board is 20 x 20 squares and the players take turn to add discs.

image

Preparations

The idea is to create two projects; one for each player. A player consists of a class implementing the interface IPlayer and attributed with [Export(typeof(IPlayer))].

The player assemblies are then dropped in the Players folder located on the web root.

The source code for the game engine can be found at https://github.com/tobiasnilsson/tictactoe. The solution includes the following projects:

  • Player1 and Player2 – the dummy player projects
  • TicTacToe.Common – the game engine
  • TicTacToe.WebUI – web UI and startup project
  • TicTacToe.Common.Tests – unit tests for the Common project
  • TicTacToe.WebUI.Tests – unit tests for the WebUI project

Play

Once the solution is downloaded from Github and your two player assemblies are dropped into the Players folder the game is ready to play (Use the dummy players from Player1 and Player2 projects to try out the solution). Open the game engine solution in Visual Studio 2012. Make sure the WebUI project is marked as startup project and hit F5 to run. A web browser window opens with the game board view. Hit Play! button to start the battle.

Technincal stuff

This section aims at describing some of my design and component choices.

MEF

To be able to load the players dynamically without referencing them from the Common project I use Managed Extensibility Framework. This is shipped with .Net 4.5 and is commonly used to build pluggable architectures.

See https://alertcoding.wordpress.com/2012/12/03/replace-your-reflection-code-with-mef/ for details.

Ninject

I used the nuget package Ninject.MVC3. This works with MVC 4 as well.

Ninject with Controllers

Everything is pretty straight-forward after installing the Ninject nuget package Ninject.MVC3. The package provides two classes below App_Start: NinjectWeb.cs and NinjectWebCommon.cs. The method RegisterServices in NinjectWebCommon is where you need to specify which concrete implementation to use for which interface.

private static void RegisterServices(IKernel kernel)
        {
            //Binding with parameterless constructor
            kernel.Bind<IDiscColorManager>().To<DiscColorManager>();
            kernel.Bind<IBoardFactory>().To<BoardFactory>();

            //Binding with constructor accepting a string parameter
            kernel.Bind<IPlayerRepository>().To<PlayerRepository>().WithConstructorArgument("playerAssemblyDir", System.Web.Hosting.HostingEnvironment.MapPath("~/Players"));
            kernel.Bind<IWinnerCheckerFactory>().To<WinnerCheckerFactory>();

            //Binding with constructor accepting three parameters of interface type
            kernel.Bind<IGameManager>().To<GameManager>()
                  .WithConstructorArgument("playerFactory", ctx => ctx.Kernel.Get<IPlayerRepository>())
                  .WithConstructorArgument("boardFactory", ctx => ctx.Kernel.Get<IBoardFactory>())
                  .WithConstructorArgument("winnerCheckerFactory", ctx => ctx.Kernel.Get<IWinnerCheckerFactory>());            
        }

Ninject with SignalR

I used nuget SignalR.Ninject which is a pre-release so make sure to select the option “Include Prerelease” when searching for the nuget package.

To be able to use dependency injection with Ninject for the SignalR hubs a dependency resolver must be added. The guidelines suggests to use the following:

RouteTable.Routes.MapHubs(new SignalR.Ninject.NinjectDependencyResolver(kernel));

However, this resulted in a compilation error stating:

Error 1 ‘System.Web.Routing.RouteCollection’ does not contain a definition for ‘MapHubs’ and the best extension method overload ‘Microsoft.AspNet.SignalR.RouteExtensions.MapHubs(System.Web.Routing.RouteCollection, Microsoft.AspNet.SignalR.IDependencyResolver)’ has some invalid arguments

So what I did was to implement my own resolver, basically by copying the contents of SignalR.Ninject.NinjectDependencyResolver to my own class called NinjectSignalRDependencyResolver.

public class NinjectSignalRDependencyResolver : DefaultDependencyResolver
    {
        private readonly IKernel _kernel;

        public NinjectSignalRDependencyResolver(IKernel kernel) 
        {
            if (kernel == null)
            {
                throw new ArgumentNullException("kernel");
            }

            _kernel = kernel;
        }

        public override object GetService(Type serviceType) 
        {
            return _kernel.TryGet(serviceType) ?? base.GetService(serviceType);
        }

        public override IEnumerable<object> GetServices(Type serviceType) 
        {
            return _kernel.GetAll(serviceType).Concat(base.GetServices(serviceType));
        }
    }

Then I added the resolver to the route table like this (in NinjectWebCommon):

private static void RegisterServices(IKernel kernel) {

… RouteTable.Routes.MapHubs(new NinjectSignalRDependencyResolver(kernel));

}

Then, as Apple states, it just works! The resolver enables the SignalR hub classes to be injected according to the bindings described in Ninject with Controllers section above.

HTML5 localStorage

The results of previous games are stored in the web browsers local storage. I use Modernizr which is included in the default ASP.NET MVC 4 project template. Modernizr lets me check if the browser is compliant with localStorage without wrapping this in a try catch statement.

function storeResult(message) {
        if (Modernizr.localstorage) {
            // window.localStorage is available! Store the result
            localStorage.result = localStorage.result + "|" + message;
        }
    }

See http://diveintohtml5.info/detect.html#storage for details.

Architecture

The POCO entities on which the game is based is Board and DiscPosition:

public class Board
    {
        public List<DiscPosition> DiscsOnBoard { get; set; }
        public int BoundaryX { get; set; }
        public int BoundaryY { get; set; }
    }
public class DiscPosition
    {
        public int X { get; set; }
        public int Y { get; set; }
        public char PlayerInitialLetter { get; set; }
    }

The Board object is served by the class BoardFactory and contains the player discs on the board as well as the x and y boundaries.  The game is limited to 20 by 20 positions.

The DiscPosition class is a representation of a disc on the board. It has a x and y coordinate and an ID corresponding to the player who added the disc.

Each player is represented by an implementation of the interface IPlayer:

public interface IPlayer
    {
        DiscPosition Play(Board board);

        string Name { get; }
    }

The implemented player class must provide a property called Name and the method Play which accepts a parameter of type Board. The Board parameter holds the current state of the board. The WebUI project contains a decorator class which adds a color property to the Player class, like this:

public class ColoredPlayer : IPlayer
    {
        public ColoredPlayer(IPlayer player)
        {
            Player = player;
        }

        public IPlayer Player { get; set; }

        public DiscPosition Play(Board board)
        {
            return Player.Play(board);
        }

        public string Name
        {
            get { return Player.Name; }
        }

        public string RgbColor { get; set; }
    }

OMG! Why is ColoredPlayer not in Common project?!  you might think. The reason why ColoredPlayer is located in the WebUI project and not in the Common project is because it is specific to the WebUI project. The logic of the game is contained in the Common project. The color property just adds a web specific color.

The class TicTacToe.WebUI.Hubs.GameHub is the SignalR hub and controls the communication with the web browser. GameHub gets injected with an object implementing IGameManager on startup. GameHub subscribes to the events fired by GameManager and updates the board on the client whenever the event GameManager.OnBoardUpdated is fired.

The class TicTacToe.Common.GameManager handles the game play and implements interface IGameManager. The class utilizes the class PlayerRepository to get the players from the player assemblies located in the WebUI\Players directory. The GameManager class has two events: OnBoardUpdated and OnEnded. OnBoardUpdated is triggered whenever there is a change to the board, i.e. when a player added a disc to the board. GameManager gets injected with objects implementing the interfaces IPlayerRepository, IBoardFactory and IWinnerCheckerFactory at startup.

The class PlayerRepository implements interface IPlayerRepository and delivers instances of the two players to the GameManager. PlayerRepository uses MEF to instantiate the players from the assemblies located in the Players folder.

The class BoardFactory implements interface IBoardFactory and whose only responsibility is to return an instance of class Board.

The class WinnerCheckerFactory implements the interface IWinnerCheckerFactory . The only responsibility for WinnerCheckerFactory is to provide the rules/checker objects which checks if the board contains a winning disc combination. A winning combo is 5 sequential discs horizontal or vertical.

When locating a winning disc combo instances of classes implementing of IWinnerChecker interface is used. Right now there are only two checkers, vertical and horizontal. In the future there might be two more which checks the diagonal combos as well.

Bugs and improvements

· Sometimes five discs in a row is missed hence making the wrong combination victorious.

· Refactor game.js to make it object-oriented.

2 thoughts on “Tic Tac Toe using MEF, SignalR, Ninject and ASP.NET MVC 4

  1. Pingback: [RESOLVED]Game caro online 2 player using asp.net | ASP Questions & Answers

  2. Pingback: [RESOLVED]Game caro online 2 player using asp.net | ASP Web Form Data Control

Leave a comment