Sunday, 9 June 2013

HTML5 and MVC4 Board Game (Uckers)

Initial Thoughts

I was asked some time ago if I could make a game for some old Royal Naval friends. Uckers is a popular board across many services but appears to be more popular in the Naval Services around he would. Although the origins are somewhat unknown, it is thought to be based on an old Indian game called Patrisi. Essentially it's a pumped up game of Ludo, but the fun in playing the game is not just the game, it's the interaction of the people.
Initially I created the game in Flash as a single player version, using some very simple AI. Now it's time to look at a multiplayer version using HTML5 Canvas. Again building in stages and using C# MVC and updates via JSON. The reason for using C# web service is to separate the game engine from the presentation layer, allowing a more complex AI for single player usage later on, removing the requirement for one user to run their game as a server, opening sockets so client can connect.

Building the Game


Setting up the frameworks

First things first, I want to make sure that this goes as smoothly as possible so I opted for VS2010 and MVC 4.  Although I was thinking of installing Html5 Boilerplate, I opted to just use the standard Html5 and use the latest JQuery (at the time was 2.0).

Setting up the initial connectivity to social media is already part of the framework, but I did make a few enhancements with thanks to Pranav Rastogi blog post
Integrate OpenAuth/OpenID with your existing ASP.NET application using Universal Providers

Unit testing was done using NUnit and Rhino Mocks.

Board Layout

I like the use of JavaScript Closure but there is no reason why you can't just write this out in a script. This is not a live or commercial version, just demo code.

  
var Uckers = Uckers || {};
Uckers.Gameboard = Uckers.Gameboard || {};
Uckers.Gameboard.Common = $.extend(Uckers.Gameboard.Common || {}, function () {

....

    return {
        Initialise: initialise
    };
} ());  

  
Defining the co-ordinate system which will be used throughout the code was an important first step.

       var coordinate = function (x, y) {
        this.x = x;
        this.y = y;
    };  
Next was to define what a ucker was, although this will change in the next few parts we can still define the basic structure.


      var ucker = function (coord, target, radius, color) {
        this.x = coord.x;
        this.y = coord.y;
        this.targetx = target.x;
        this.targety = target.y;
        this.radius = radius;
        this.color = color[0];
        this.shadow = color[1];
        this.colors = color;
    };  

I also wanted to define the start locations for each of the uckers, the colours and it's shadows


   var uckers = [];

    var uckerRadius = 15;


 
    //Colour - Shadow colour

    var green = ['#397D02', '#45623C'];
    var blue = ['#42C0FB', '#35586C'];
    var red = ['#F08080', '#6F4242'];
    var yellow = ['#FCB514', '#8E6B23'];

    var uckerColours = [green, blue, red, yellow];
    //Ucker Starting Positions  

    var greenStart = [new coordinate(90, 100), new coordinate(90, 165), new coordinate(165, 100), new coordinate(165, 165)];  

    var blueStart = [new coordinate(445, 445), new coordinate(445, 515), new coordinate(515, 445), new coordinate(515, 515)];  

    var redStart = [new coordinate(90, 445), new coordinate(90, 515), new coordinate(165, 445), new coordinate(165, 515)];  

    var yellowStart = [new coordinate(445, 100), new coordinate(445, 165), new coordinate(515, 100), new coordinate(515, 165)];  


    var uckerStart = [greenStart, blueStart, redStart, yellowStart];  
 

 
 
 
By using arrays we can then loop through and create the complete set of uckers and store them in a new array.


 
  var uckerSetup = function () {  
         for (var j = 0; j < uckerStart.length; j++) {  
             for (var i = 0; i < uckerStart[j].length; i++) {  
                 uckers.push(new ucker(uckerStart[j][i], uckerStart[j][i], uckerRadius, uckerColours[j]));  
             }  
         }  
     };  

 
 
When laying out the board we can then draw the uckers using their own object information.


  for (var i = 0; i < uckers.length; i++) {  
    drawUckers(context, uckers[i]);  
 }  
  
var drawUckers = function (ctx, u) { // draw circle function  
         ctx.fillStyle = u.color;  
         ctx.shadowOffsetX = 5;  
         ctx.shadowOffsetY = 5;  
         ctx.shadowBlur = 4;  
         ctx.shadowColor = u.shadow;
         ctx.beginPath();  
         ctx.arc(u.x, u.y, u.radius, 0, Math.PI * 2, true);  
         ctx.closePath();  
         ctx.fill();  
     };  

The background is a little simpler, we just add an image of an uckers board.


     var boardImage = new Image();  
     var bgReady = false;  

 
     boardImage.onload = function () {  
         bgReady = true;  
     };  

 
     boardImage.src = "/Images/uckersboard.png";  


     var loadBoard = function () {  
         theCanvas = $("#gameboard").get(0);  
         var context = theCanvas.getContext("2d");
        if (bgReady) {
            context.drawImage(boardImage, 5, 5); 
        } 
   }; 
 

Facebook, Twitter, Google+ and Microsoft Authentication.

Using MVC 4 makes this a lot simpler than it used to be.  But to ensure that it goes smoothly we need to make some simple changes.  The default MVC4 EF5 setup means that when we first run the program several files are used to initialise the database.  We can change this later using the package manager, but initially I created a blank database and changed the connection string.



Old


connectionstring="Data Source=(LocalDb)\v11.0;Initial Catalog=aspnet-Uckers-20130606091131;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnet-Jannersoft.Demos.Uckers-20130606091131.mdf" name="DefaultConnection" providername="System.Data.SqlClient"

New

connectionstring="Data Source=.\sqlexpress;Initial Catalog=Uckers-Dev;Integrated Security=True" name="DefaultConnection" providername="System.Data.SqlClient"



The next stage is to add our keys to the Facebook auth config. In App_Start edit the AuthConfig.cs file and enable the FAcebook OAuth.


            OAuthWebSecurity.RegisterFacebookClient(
                            appId: "169767096406585",
                            appSecret: "c180be059c784d8cba0eb44379b34c11");

A large amount of creating an initial database and creating the new tables is done by the InitializeSimpleMembershipAttribute.cs class in the filters folder, with the AccountController.cs taking a lead role in handling the authentication either via site registration or via facebook.

It is also worthwhile before doing anything to have a look at the AccountModel.cs file.  This is where the UserContext is initialised and the stucture of the UserProfile.  It is worthwhile noting that changing the UserProfile class will have no effect on the table.  It will not add any extra colums nor will it work correctly if altered.  It is tied directly into the InitializeSimpleMembershipAttribute.cs file.  We will cover how to change this file later, but it involves playing with the package migration manager and adding code to the seed method.

Run this now and trying to login via facebook will fail, we need to tell facebook to allow the url we will be using.  I am using iis8 express, so I have changed the properties of the program.


Once you login via facebook new tables are created in the database.

Later on we are going to do more with the social media connection, but for now I want to concentrate on the game.  

The next stage would be to store details of the games and the players, to do this we are going to use the Code First approach to creating the tables.

Models And Enums


   [Table("Games")]  
   public class GameModel  
   {  
     [Key]  
     [DatabaseGeneratedAttribute(DatabaseGeneratedOption.None)]  
     public Guid GameId { get; set; }  
     public List<Player> Players { get; set; }  
     public DateTime StartDate { get; set; }  
     public DateTime? EndDate { get; set; }  
   }  
   [Table("Players")]  
   public class Player  
   {  
     [Key]  
     [DatabaseGenerated(DatabaseGeneratedOption.None)]  
     public Guid PlayerId { get; set; }  
     public UserProfile User { get; set; }  
     #region EF4 method of Enum Handling  
     //public int PlayerSelectedColourEnumValue { get; set; }  
     //public SelectedColorEnum PlayerSelectedColor  
     //{  
     //  get { return (SelectedColorEnum)PlayerSelectedColourEnumValue; }  
     //  set { PlayerSelectedColourEnumValue = (int)value; }  
     //}  
     #endregion  
     public SelectedColorEnum PlayerSelectedColor { get; set; }  
   }  

Generating the Table (Code First)


This provided a basic initial setup, with a tweak or two, and it does build the tables

   public class GameDbContextInitializer : DropCreateDatabaseIfModelChanges<GamesContext>  
   {  
     protected override void Seed(GamesContext context)  
     {  
       var db = new UsersContext();  
       var userProfile = db.UserProfiles.FirstOrDefault();  
       var players = new List<Player>{ new Player{  
          PlayerSelectedColor = SelectedColorEnum.Blue, PlayerUser = userProfile}};  
       var games = new List<GameModel>  
       {  
         new GameModel{ StartDate = DateTime.UtcNow,  
            GameUser = userProfile, Players = players}  
       };  
       players.ForEach(p => context.Players.Add(p));  
       games.ForEach(g=> context.Games.Add(g));  
       context.SaveChanges();  
     }  
   }  

This does work if you are only using a single context that doesnt have complex joins to other databases.  In his example we are using the defaultConnnection to set up the database for UserProfile.

To get this to work on a single context just put the following code in the Glbal.asax Application_Start;

       Database.SetInitializer<GamesContext>(new GameDbContextInitializer());  

Using Package Manager Migration - Cleaner and recommended

http://msdn.microsoft.com/en-us/data/jj193542.aspx
Open the Package Manager Console.


run > Enable-Migrations -ContextTypeName Jannersoft.Demos.Uckers.Models.GamesContext.

This creates a new file configuration and folder migration.  This forms the basic structure required to allow the migration code and the update.  It also aids in the moving of the database from dev to prod.

run > Add-Migration InitialGameTables

Creates a migration class, which we need to make a few changes. Firstly, we need to remove the UserProfile table creattion as this has alread been created when we did the facebook part.

       CreateTable(  
         "dbo.Games",  
         c => new  
           {  
             GameId = c.Int(nullable: false, identity: true),  
             StartDate = c.DateTime(nullable: false),  
             EndDate = c.DateTime(),  
             GameUser_UserId = c.Int(),  
           })  
         .PrimaryKey(t => t.GameId)  
         .ForeignKey("dbo.UserProfile", t => t.GameUser_UserId)  
         .Index(t => t.GameUser_UserId);  
      // CreateTable(  
      //   "dbo.UserProfile",  
      //   c => new  
      //     {  
      //       UserId = c.Int(nullable: false, identity: true),  
      //       UserName = c.String(),  
      //     })  
      //   .PrimaryKey(t => t.UserId);  
       CreateTable(  
         "dbo.Players",  
         c => new  
           {  
             PlayerId = c.Int(nullable: false, identity: true),  
             PlayerSelectedColor = c.Int(nullable: false),  
             PlayerUser_UserId = c.Int(),  
             GameModel_GameId = c.Int(),  
           })  
         .PrimaryKey(t => t.PlayerId)  
         .ForeignKey("dbo.UserProfile", t => t.PlayerUser_UserId)  
         .ForeignKey("dbo.Games", t => t.GameModel_GameId)  
         .Index(t => t.PlayerUser_UserId)  
         .Index(t => t.GameModel_GameId);  

run > Update-Database

Then check the database for the tables.

Now that we have been able to generate the tables we can make changes to increase the usefulness of the UserProfile object..

Add new columns to the UserProfile object then run > Add-Migration UserProfileUpdate

  [Table("UserProfile")]  
   public class UserProfile  
   {  
     [Key]  
     [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]  
     public int UserId { get; set; }  
     public string UserName { get; set; }  
     public string FirstName { get; set; }  
     public string Surname { get; set; }  
     public string Email { get; set; }  
   }  
   public class RegisterExternalLoginModel  
   {  
     [Required]  
     [Display(Name = "User name")]  
     public string UserName { get; set; }  
     public string ExternalLoginData { get; set; }  
     public string FirstName { get; set; }  
     public string Surname { get; set; }  
     public string Email { get; set; }  
   }  


Making a dice

Making and handling the dice can be carried out with a separate JavaScript file, this separation allows later re-usage of the dice.

One of the important parts of this is understanding the business logic surrounding it.  The dice will become part of the canvas animation and as such the animation should take care of the final number to display.  the dice should only care about its own properties, dimensions and the number it should display.

It is also worthwhile noting that the click, and mouse are not events but true or false returns, perhaps I need to rename these.  With HTML5 the event is handled by the canvas.

 var Uckers = Uckers || {};  
 Uckers.Gameboard = Uckers.Gameboard || {};  
 Uckers.Gameboard.Dice = $.extend(Uckers.Gameboard.Dice || {}, function () {  
   var dx;  
   var dy;  
   var diceArray = [];  
   var horizontalLayout;  
   var diceGap = 100;  
   var radius;  
   var dheight;  
   var dwidth;  
   var dcanvas;  
   var faceStyle;  
   var die = function (posx, posy, num) {  
     this.dicex = posx;  
     this.dicey = posy;  
     this.num = num;  
   };  
   // posx = 630 posy = 50 height = 60 dotradius = 5 width = 60 face = #009966  
   var loadDice = function (canvas, posx, posy, width, height, dotradius, face, dieCount) {  
     for (var i = 0; i < dieCount; i++) {  
       diceArray.push(new die(posx, posy, 1));  
     }  
     radius = dotradius;  
     dheight = height;  
     dwidth = width;  
     dcanvas = canvas;  
     faceStyle = face;  
     setLayout(true);  
     drawDice();  
   };  
   var setLayout = function(isHorizontal) {  
     horizontalLayout = isHorizontal;  
   };  
   var drawface = function (n) {  
     var ctx = dcanvas.getContext("2d");  
     ctx.lineWidth = 5;  
     ctx.clearRect(dx, dy, dwidth, dheight);  
     ctx.strokeRect(dx, dy, dwidth, dheight);  
     ctx.fillStyle = faceStyle;  
     switch (n) {  
       case 1:  
         draw1(ctx);  
         break;  
       case 2:  
         draw2(ctx);  
         break;  
       case 3:  
         draw2(ctx);  
         draw1(ctx);  
         break;  
       case 4:  
         draw4(ctx);  
         break;  
       case 5:  
         draw4(ctx);  
         draw1(ctx);  
         break;  
       case 6:  
         draw4(ctx);  
         draw2Mid(ctx);  
         break;  
     }  
   };  
   var drawDice = function () {  
     for (var i = 0; i < diceArray.length; i++) {  
       //Get random dice number  
       diceArray[i].num = 1 + Math.floor(Math.random() * 6);  
       //check if vertical or horizontal layout  
       if (horizontalLayout) {  
         dx = diceArray[i].dicex + (diceGap * i);  
         dy = diceArray[i].dicey;  
       } else {  
         dx = diceArray[i].dicex;  
         dy = diceArray[i].dicey + (diceGap * i);  
       }  
       drawface(diceArray[i].num);  
     }  
     return diceArray;  
   };  
   var isMouseOnDice = function(e) {  
     var mouseX = e.offsetX || 0;  
     var mouseY = e.offsetY || 0;  
     for (var i = 0; i < diceArray.length; i++) {  
       if (mouseX > diceArray[i].minX && mouseX < diceArray[i].maxX && mouseY > diceArray[i].minY && mouseY < diceArray[i].maxY) {  
         return true;  
       }  
     }  
     return false;  
   };  
   var draw1 = function (ctx) {  
     var dotx;  
     var doty;  
     ctx.beginPath();  
     dotx = dx + .5 * dwidth;  
     doty = dy + .5 * dheight;  
     ctx.arc(dotx, doty, radius, 0, Math.PI * 2, true);  
     ctx.closePath();  
     ctx.fill();  
   };  
   var draw2 = function (ctx) {  
     var dotx;  
     var doty;  
     ctx.beginPath();  
     dotx = dx + 3 * radius;  
     doty = dy + 3 * radius;  
     ctx.arc(dotx, doty, radius, 0, Math.PI * 2, true);  
     dotx = dx + dwidth - 3 * radius;  
     doty = dy + dheight - 3 * radius;  
     ctx.arc(dotx, doty, radius, 0, Math.PI * 2, true);  
     ctx.closePath();  
     ctx.fill();  
   };  
   var draw4 = function (ctx) {  
     var dotx;  
     var doty;  
     ctx.beginPath();  
     dotx = dx + 3 * radius;  
     doty = dy + 3 * radius;  
     ctx.arc(dotx, doty, radius, 0, Math.PI * 2, true);  
     dotx = dx + dwidth - 3 * radius;  
     doty = dy + dheight - 3 * radius;  
     ctx.arc(dotx, doty, radius, 0, Math.PI * 2, true);  
     ctx.closePath();  
     ctx.fill();  
     ctx.beginPath();  
     dotx = dx + 3 * radius;  
     doty = dy + dheight - 3 * radius; //no change  
     ctx.arc(dotx, doty, radius, 0, Math.PI * 2, true);  
     dotx = dx + dwidth - 3 * radius;  
     doty = dy + 3 * radius;  
     ctx.arc(dotx, doty, radius, 0, Math.PI * 2, true);  
     ctx.closePath();  
     ctx.fill();  
   };  
   var draw2Mid = function (ctx) {  
     var dotx;  
     var doty;  
     ctx.beginPath();  
     dotx = dx + 3 * radius;  
     doty = dy + .5 * dheight;  
     ctx.arc(dotx, doty, radius, 0, Math.PI * 2, true);  
     dotx = dx + dwidth - 3 * radius;  
     doty = dy + .5 * dheight; //no change  
     ctx.arc(dotx, doty, radius, 0, Math.PI * 2, true);  
     ctx.closePath();  
     ctx.fill();  
   };  
   return {  
     Initialize: loadDice,  
     Roll: drawDice,  
     DiceGap: diceGap,  
     IsHorizontalLayout: setLayout,  
     Click: isMouseOnDice,  
     MouseOver: isMouseOnDice,  
     MouseDown: isMouseOnDice,  
     MouseUp: isMouseOnDice,  
     Dice:diceArray  
   };  
 }());  

calling a dice

dice = Uckers.Gameboard.Dice;
        dice.Initialize(theCanvas, 630, 50, 60, 60, 4, '#009966', 2);

Adding a Dice Button

We could easily set up a small area to test for a mouse event but to be able to extend this button we can be a little more adventurous.

 Uckers.Gameboard.CanvasButton = $.extend(Uckers.Gameboard.Button || {}, function() {  
   var ctx;  
   var minX;  
   var minY;  
   var boxX;  
   var boxY;  
   var text;  
   var textColor;  
   var fillColor;  
   var lineColor;  
   var lineWidth;  
   var font;  
   var width;  
   var height;  
   var btn = function() {  
     this.canvas = arguments[0].canvas || $("canvas").get(0);  
     this.ctx = arguments[0].ctx || this.canvas.getContext("2d");  
     this.text = arguments[0].text || "Submit";  
     this.textColor = arguments[0].color || "#5E5E5E";  
     this.fontSize = arguments[0].fontSize || 18;  
     this.fontTypeFace = arguments[0].fontTypeFace || "sans-serif";  
     this.font = arguments[0].font || this.fontSize + "px " + this.fontface;  
     this.ctx.font = this.font;  
     this.width = arguments[0].width || this.ctx.measureText(this.text).width;  
     this.height = arguments[0].height || this.fontSize;  
     this.minX = arguments[0].posx || this.canvas.width;  
     // this.maxX = this.minX + this.width ;  
     this.minY = arguments[0].posy || this.canvas.height;  
     // this.maxY = this.minY + this.height ;  
     this.id = arguments[0].id || "undefined";  
     this.active = true;  
     this.lineColor = arguments[0].linecolor || "#FFFF00";  
     this.lineWidth = arguments[0].linewidth || 1;  
     this.fillColor = arguments[0].fillcolor || "00FFFF";  
     this.borderWidth = arguments[0].boarderwidth || 2;      
     ctx = this.ctx;  
     minX = this.minX;  
     minY = this.minY;  
     text = this.text;  
     textColor = this.textColor;  
     fillColor = this.fillColor;  
     lineColor = this.lineColor;  
     lineWidth = this.lineWidth;  
     font = this.font;  
     width = this.width + 15;  
     height = this.height + 4;  
     drawLabel();  
     this.click = function(e) {  
       var mouseX = e.offsetX || 0;  
       var mouseY = e.offsetY || 0;  
       return mouseX > boxX && mouseX < (boxX + width) && mouseY > boxY && mouseY < (boxY + height) && this.active;  
     };  
   };  
   var settext = function() {  
     text = arguments[0].text || "Submit";  
     textColor = arguments[0].color || "#5E5E5E";  
     this.fontSize = arguments[0].fontSize || 18;  
     this.fontTypeFace = arguments[0].fontTypeFace || "sans-serif";  
     font = arguments[0].font || this.fontSize + "px " + this.fontface;  
     ctx.font = font;  
     width = (arguments[0].width || ctx.measureText(this.text).width) + 15;  
     height = (arguments[0].height || this.fontSize) + 4;  
     drawLabel();  
   };  
   var drawLabel = function() {  
     ctx.lineWidth = 2;  
     boxX = minX - 5;  
     boxY = minY - 15;  
     ctx.clearRect(boxX, boxY, width, height);  
     ctx.strokeRect(boxX,boxY, width, height);  
     ctx.fillStyle = fillColor;  
     ctx.lineWidth = lineWidth;  
     ctx.fillStyle = textColor;  
     ctx.lineStyle = lineColor;  
     //calculate the center position for the text  
     // width of the box minus the width of the text divide by two then add to the posx  
     var textPos = (width - ctx.measureText(text).width) / 2;  
     ctx.fillText(text, minX + textPos, minY);  
   };  
   return {  
     Button: btn,  
     Text : settext  
   };  
 }());  

Setup the button

diceButton = new Uckers.Gameboard.CanvasButton.Button({ ctx: context, posx: 660, posy: 135, text: "Stop!" });

Placing the Uckers

The initail placement of the uckers is a two stage process.  Trying to keep this to the client model update and instantiate the uckers from the javascript.  We could make a call to the server and request the locations and update from a Json response, but we need to think about the number of server calls during the initial setup.  We could lso make it part of the model and load this onto the page, but again it come to decide on where the balance of saving this information is kept.  In this case we are going to save this as part of the javascript and do all the work in a single hit.

  var uckers = [];  
   var uckerRadius = 15;  
   //Colour - Shadow colour  
   var green = ['#397D02', '#45623C', "green"];  
   var blue = ['#42C0FB', '#35586C', "blue"];  
   var red = ['#F08080', '#6F4242', "red"];  
   var yellow = ['#FCB514', '#8E6B23', "yellow"];  
   var uckerColours = [green, blue, red, yellow];  

In the section above we layout the basic information about an ucker, we then send this to an ucker object and save this in an array of uckers.
   var ucker = function (coord, target, radius, color, name) {  
     this.uckerId = 0;  
     this.name = name;  
     this.x = coord.x;  
     this.y = coord.y;  
     //target [4,1] target A = [0,0] = x, [0,1] = y, Target B = [1,0] = x, [1,1] = y  
     this.target = target;  
     this.selectedtargetx;  
     this.selectedtargety;  
     this.radius = radius;  
     this.color = color[0];  
     this.shadow = color[1];  
     this.colors = color;  
     this.boardPosition = 0;  
     this.IsActive = false;  
   };  

We use a couple of extra object in this, but one of the things that spring out is the Target.  This is a set of co-ordinates for this ucker.  As there are two dice, an ucker could have the potential to move to either.  Once an ucker is selected (after the dice is selected) the it is moved to the selected target.

Here is the JavaScript in it's full.

 var Uckers = Uckers || {};  
 Uckers.Gameboard = Uckers.Gameboard || {};  
   
 Uckers.Gameboard.Ucker = $.extend(Uckers.Gameboard.Ucker || {}, function () {  
   
   var coordinate = function (x, y) {  
     this.x = x;  
     this.y = y;  
   };  
   
   //Ucker Starting Positions  
   var greenStart = [new coordinate(90, 100), new coordinate(90, 165), new coordinate(165, 100), new coordinate(165, 165)];  
   var blueStart = [new coordinate(445, 445), new coordinate(445, 515), new coordinate(515, 445), new coordinate(515, 515)];  
   var redStart = [new coordinate(90, 445), new coordinate(90, 515), new coordinate(165, 445), new coordinate(165, 515)];  
   var yellowStart = [new coordinate(445, 100), new coordinate(445, 165), new coordinate(515, 100), new coordinate(515, 165)];  
   
   //Colour - Shadow colour  
   var green = ['#397D02', '#45623C', "green"];  
   var blue = ['#42C0FB', '#35586C', "blue"];  
   var red = ['#F08080', '#6F4242', "red"];  
   var yellow = ['#FCB514', '#8E6B23', "yellow"];  
   
   var uckerStart = [greenStart, blueStart, redStart, yellowStart];  
   
   var uckerRadius = 15;  
   
   var uckers = [];  
   
   var ucker = function (coord, target, radius, color) {  
     this.uckerId = 0;  
   
     this.radius = radius;  
     this.x = coord.x;  
     this.y = coord.y;  
   
     this.boardPosition = 0;  
     //target [4,1] target A = [0,0] = x, [0,1] = y, Target B = [1,0] = x, [1,1] = y  
     this.target = target;  
     this.selectedtargetx;  
     this.selectedtargety;  
   
     this.colors = color;  
     this.color = color[0];  
     this.shadow = color[1];  
     this.name = color[2];  
   
     this.IsActive = false;  
   };  
   
   var uckerSetup = function () {  
     for (var j = 0; j < uckerStart.length; j++) {  
       for (var i = 0; i < uckerStart[j].length; i++) {  
         uckers.push(new ucker(uckerStart[j][i], uckerStart[j][i], uckerRadius, uckerColours[j]));  
       }  
     }  
   };  
   
   var drawUckers = function (ctx, u) { // draw circle function  
     ctx.fillStyle = u.color;  
     ctx.shadowOffsetX = 5;  
     ctx.shadowOffsetY = 5;  
     ctx.shadowBlur = 4;  
     ctx.shadowColor = u.shadow;  
     ctx.beginPath();  
     ctx.arc(u.x, u.y, u.radius, 0, Math.PI * 2, true);  
     ctx.closePath();  
     ctx.fill();  
   
     //reset shaddows so it doesnt affect teh background  
     ctx.shadowOffsetX = 0;  
     ctx.shadowOffsetY = 0;  
     ctx.shadowBlur = 0;  
   };  
   
   var getUckerColor = function (c) {  
     for (var i = 0; i < uckerColours.length; i++) {  
       if (uckerColours[i][2] == c) {  
         return uckerColours[i];  
       }  
     }  
     return null;  
   };  
   
   var uckerEvent = function (mouseX, mouseY) {  
     for (var j = 0; j < Ucker.uckers.length; j++) { // checking through all circles - are mouse down inside circle or not  
       var uckerX = Ucker.uckers[j].x;  
       var uckerY = uckers[j].y;  
       var radius = uckers[j].radius;  
       if (Math.pow(mouseX - uckerX, 2) + Math.pow(mouseY - uckerY, 2) < Math.pow(radius, 2) && uckers[j].IsActive) {  
         //Check to see if the selected ucker is home  
   
         var coord = new coordinate(uckerX, uckerY);  
         uckers[j] = new ucker(coord, target, radius, uckers[j].colors);  
         break;  
       }  
     }  
   };  
   
   var getAllUckers = function () {  
     return uckers;  
   };  
   
   var drawUckers = function (ctx) {  
     for (var i = 0; i < uckers.length; i++) {  
       drawUckers(ctx, uckers[i]);  
     }  
   };  
   
   var initialise = function () {  
     uckerSetup();  
   };  
   
   return {  
     Initailize: initialise,  
     GetUckerColor: getUckerColor,  
     Uckers: getAllUckers,  
     DrawUckers: drawUckers,  
     Ucker: ucker,  
     UckerEvent: uckerEvent  
   }  
   
 }());  


In Part two we will be doing a lot more with the following:

Setting up Players

Sending JSON

Responding to JSON

Storing Data

End of Turn


Saturday, 26 March 2011

UpdatePanel and JQuery Tabs CSS losing StyleSheet on update

This problems always seems to happen when you least expect it, or when you think that browsers would have come to realise this is an issue.  Anyway, I came across this problem last week and not just in IE, but also Chrome and Firefox.

I have a default.aspx page with a user controle, unit1.ascx.  Unit1.ascx has a user control  ImageList.ascx inside that.  This has a jQuery Tabs control inside that.


First clue on solving this was UpdatePanel Css StyleSheet upon partial-refresh bug in IE

However, the solution was fairly simple in the end.

replace the following




This captures the partial load and makes sure the jQuery is fired, although the state has been lost (We'll deal with that in a minute).  But looking at the memory leaks using sIEve more worryingly is that each time the updatepanel refreshes the amount of memory used by IE is increased. (thanks to http://www.codeproject.com/KB/ajax/jqmemleak.aspx  for pointing this out).

Adding the following disposable method in the jQuery helps to resolve this.


Now to the previous issue, the state of which tab was clicked.

All we need to do is capture the number of the tab selected, place this in a control that isn't affected by the update panel, then pas it back after the update.

To do this we need to make sure we get the value as an int but also the correct asp.net control from its id.  To do this we use ('[id$=hfSelectedTAB]') instead of $('#hfSelectedTAB').

below is the final code block






Many thanks to http://www.dotnetfunda.com and Stackoverflow

Thursday, 27 January 2011

Project Management and Influence Charts

For the past three years I have been theorising the use of Influence Charts as part of the decision analysis of Project Management.  Original I was trained in the use of Prince 2 methodology, but the past three years I have used and introduced Agile(scrum) into several companies I have contracted with.  The constant with all IT Projects is the number of differing influence there are in scooping and managing these projects.  This doesn’t matter if it was using Prince 2 or Agile methodology.

One of the most common influence people think of is the personnel link, such as the Forrester Social
Influence Chart (http://chainringaction.blogspot.com/2010/03/forresters-social-influence-chart.html , Last Accessed 27 Jan 2011).  There are other influence charts, such as Newark’s soil impact chart (http://en.wikipedia.org/wiki/Newmark's_influence_chart , last accessed 27 Jan 2011). However, there are many other influences that impact the development of software and what complicates it even more is that many of these influences change over time, often referred to in Agile as Impediments.

To help me plan and control software development I developed a method of charting these influences as a way of understanding the whole picture. Now I have seen it work, although I am still developing it, I have
decided to share my experiences so far.

Typical influence types:    

  • Fixed – Never changes.  Such as the core operating system
  • Static – Not intended to change, but can be extended.  Such as the expected browser in web applications 
  • Growing – Amount of influence can increase depending on external changes.  Such as the addition of a new page means changes to menu structure 
  • Reducing – Amount of influence depending on internal changes.  Such as a reduction on the number of Browsers being supported (Static) 
  • External – An influence that is external to the development but must be aware of.  Such as Press release dates, advertising etc. 
  • Crossover – This is an external influence that has direct impact on project.  This can alter state and become a Static or External influence depending on interaction of Growing or Reducing influence.  Such as expected number of site visitors or external api connection 
Influence type graphical representation:

Fixed

Static

Growing

Reducing

External

Crossover


Full Graphical Representation:

As you can see these can grow very large, but it does give you a larger picture on what is happening during a project.

Over the next few weeks I will show how these charts can help with controlling the backlog and  impediments in an Agile scrum methodology.

Thursday, 11 November 2010

Word 2007 Fluent Ribbon Addin

I recently came across an interesting issue when our Office was recently upgraded from 2003 to 2007. Previously the company Word templates (.dot) were loaded into the menu system for easy selection. With 2007 this has been replaced with the Fluent Ribbon.

It should have been easy to find examples of transferring these macro based functions to the new ribbon. However, the articles were mainly based on the XML with very little written to help with a C# solution.

Here is how I solved some of the issues in using the Visual Studio tools.

First, open a new project.

 This creates a soulution with a C# file called "ThisAddIn".
We now add a new Ribbon using Visual Designer and NOT the XML.
 
Now we can get to the code as we would with any other Winform or Webform application.

The visual design mode allows us to remove the existing tab, which would show as the "Add In" tab in Office and replace with our own Custom Tab.  We add two groups, one for the list of templates using the Menu, and the second for a print button and insert footer function.  All the icons are part of the Office Gallaery, a list of which can be downloaded here.



Next we add the code to load the templates.  These can be anywhere on the system, including a share, but for this demo I am using the standard templates in C:\Program Files\Microsoft Office\Templates\1033.

I am using a recusive method to loop the folders and sub folders to build the list.
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Tools.Ribbon;
using Word =  Microsoft.Office.Interop.Word;
using Office = Microsoft.Office.Core;
using System.IO;
using System.Windows.Forms;

namespace CustomRibbon
{
    public partial class CustomRibbon : OfficeRibbon
    {
        public CustomRibbon()
        {
            InitializeComponent();
        }

        private void CustomRibbon_Load(object sender, RibbonUIEventArgs e)
        {
            LoadDirectories();
        }


    private int count = 0;

    #region Template Loader

    private void LoadDirectories()
    {
      tpltMenu.Dynamic = true;
      RibbonMenu m = new Microsoft.Office.Tools.Ribbon.RibbonMenu(); // For VS 2010 Use this.Factory.CreateRibbonMenu();
      DirectoryInfo di = new DirectoryInfo(Path.GetDirectoryName(@"C:\Program Files\Microsoft Office\Templates\1033"));
      foreach (DirectoryInfo d in di.GetDirectories())
      {
          RibbonMenu subM = new Microsoft.Office.Tools.Ribbon.RibbonMenu(); // For VS2010 use this.Factory.CreateRibbonMenu();
          BuildFolders(d, ref tpltMenu, subM);
      }
    }

    private void BuildFolders(DirectoryInfo di,ref RibbonMenu parentM , RibbonMenu m)
    {
      count++;
      try
      {

        //if (di.Name != "1033")
        //{
          m.Dynamic = true;
          m.Name = "dir" + count;
          m.Label = di.Name;
          m.ShowLabel = true;

          foreach (DirectoryInfo d in di.GetDirectories())
          {
              RibbonMenu subM = new Microsoft.Office.Tools.Ribbon.RibbonMenu();
              BuildFolders(d, ref m, subM);
          }


          FileInfo[] files = di.GetFiles();
          foreach (FileInfo f in files)
          {
              if (f.Name.ToLower().EndsWith(".dot") || f.Name.ToLower().EndsWith(".dotx"))
              {
                  count++;
                  string FileName = f.Name.Trim();
                  FileName = FileName.Replace(".Dotx", "");

                  RibbonButton b = new Microsoft.Office.Tools.Ribbon.RibbonButton();
                  b.Name = "cmd" + count;
                  b.Label = FileName;

                  b.SuperTip = f.FullName;
                  b.Click += new EventHandler(button1_Click); // For VS 2010 use Microsoft.Office.Tools.Ribbon.RibbonControlEventHandler(this.button1_Click);
                  m.Items.Add(b);
              }
          }
          parentM.Items.Add(m);
       // }
      }
      catch (UnauthorizedAccessException e){}
      catch { }
    }


    private void button1_Click(object sender, RibbonControlEventArgs e)
    {
      RibbonButton btn = (RibbonButton)sender;
      object nullobj = System.Reflection.Missing.Value;
      Object oTemplatePath = btn.SuperTip;
      Object SaveOption = Word.WdSaveOptions.wdDoNotSaveChanges;
      Word._Application wordApp = new Word.Application();
      Word.Document doc = new Word.Document();
      wordApp.Documents.Add(ref oTemplatePath, ref nullobj, ref nullobj, ref nullobj);
      wordApp.Visible = true;
      doc = wordApp.Documents.Add(ref oTemplatePath, ref nullobj, ref nullobj, ref nullobj);
    }
    #endregion



    private void cmdPrintX2_Click(object sender, RibbonControlEventArgs e)
    {

      object nullobj = System.Reflection.Missing.Value;
      object copies = "1";
      object pages = "1";
      object range = Word.WdPrintOutRange.wdPrintAllDocument;
      object items = Word.WdPrintOutItem.wdPrintDocumentContent;
      object pageType = Word.WdPrintOutPages.wdPrintAllPages;
      object oTrue = true;
      object oFalse = false;

      Word.Application applicationObj = Globals.ThisAddIn.Application;
      string CurrentPrinter = applicationObj.ActivePrinter;
      applicationObj.ActivePrinter = @"\\SERVER\PRINTER";

      Word.WdPaperTray wdHeaded = (Word.WdPaperTray)260; //Different for other printers
      Word.WdPaperTray wdAllPages = (Word.WdPaperTray)261;//Different for other printers

      Word.Document document = applicationObj.ActiveDocument;
      document.PageSetup.FirstPageTray = wdHeaded;
      document.PageSetup.OtherPagesTray = wdAllPages;

      try
      {
        document.PrintOut(
            ref oTrue, ref oFalse, ref nullobj, ref nullobj, ref nullobj, ref nullobj,
            ref items, ref copies, ref pages, ref pageType, ref oFalse, ref oTrue,
            ref nullobj, ref oFalse, ref nullobj, ref nullobj, ref nullobj, ref nullobj);

      }
      catch (Exception ex)
      {
        MessageBox.Show(ex.Message);
      }

     //Return back to Original Printer
      applicationObj.ActivePrinter = CurrentPrinter;
    }

    private void cmdInsertFooter_Click(object sender, RibbonControlEventArgs e)
    {
      Word.Application applicationObj = Globals.ThisAddIn.Application;
      Word.Document doc = applicationObj.ActiveDocument;

      applicationObj.ActiveWindow.View.SeekView = Word.WdSeekView.wdSeekCurrentPageFooter;
      applicationObj.Selection.TypeParagraph();

      applicationObj.ActiveWindow.Selection.TypeText("\t");
      applicationObj.ActiveWindow.Selection.TypeText(doc.FullName);

      applicationObj.ActiveWindow.View.SeekView = Word.WdSeekView.wdSeekMainDocument;

    }
  }
}
 
 

Friday, 25 June 2010

Location Based Content - Database


Designing the Database

As this is just an example of how this is going to work, I have decided to take a very simple approach of a single table.  This holds the location information, which I am going to restrict to Plymouth City Centre.

CREATE TABLE dbo.lobacos
      (
      lobaco_id int NOT NULL IDENTITY (1, 1) PRIMARY KEY,
      lobaco_name nvarchar(50) NOT NULL,
      Description nvarchar(MAX) NOT NULL,
      CreationDate datetime NOT NULL DEFAULT (getDate()),
      longitude float(53) NOT NULL,
      latitude float(53) NOT NULL,
      WebUrl nvarchar(50) NULL,
      OfferUrl nvarchar(50) NULL,
      Icon nvarchar(50)NOT NULL DEFAULT 'marker.png',
      Image nvarchar(50) NULL,
      Category nvarchar(50) NOT NULL DEFAULT 'Other'
      )

The main key is the Longitude and Latitude.  To enable to fine granularity required these two are using the Float value and not the Northings and Eastings values.

We need to build a couple of functions to return a number of items when given the location co-ordinates.  I am going use and tweak the code found on NerdDinner (http://nerddinnerbook.s3.amazonaws.com/Part11.htm )


CREATE FUNCTION [dbo].[DistanceBetween](@Lat1 as real,
                @Long1 as real, @Lat2 as real, @Long2 as real)
RETURNS real
AS
BEGIN

DECLARE @dLat1InRad as float(53);
SET @dLat1InRad = @Lat1 * (PI()/180.0);
DECLARE @dLong1InRad as float(53);
SET @dLong1InRad = @Long1 * (PI()/180.0);
DECLARE @dLat2InRad as float(53);
SET @dLat2InRad = @Lat2 * (PI()/180.0);
DECLARE @dLong2InRad as float(53);
SET @dLong2InRad = @Long2 * (PI()/180.0);

DECLARE @dLongitude as float(53);
SET @dLongitude = @dLong2InRad - @dLong1InRad;
DECLARE @dLatitude as float(53);
SET @dLatitude = @dLat2InRad - @dLat1InRad;
/* Intermediate result a. */
DECLARE @a as float(53);
SET @a = SQUARE (SIN (@dLatitude / 2.0)) + COS (@dLat1InRad)
                 * COS (@dLat2InRad)
                 * SQUARE(SIN (@dLongitude / 2.0));
/* Intermediate result c (great circle distance in Radians). */
DECLARE @c as real;
SET @c = 2.0 * ATN2 (SQRT (@a), SQRT (1.0 - @a));
DECLARE @kEarthRadius as real;
/* SET kEarthRadius = 3956.0 miles */
SET @kEarthRadius = 6376.5;        /* kms */

DECLARE @dDistance as real;
SET @dDistance = @kEarthRadius * @c;
return (@dDistance);
END

This “NearestItems” function returns all the items located in a radius of 0.1 miles of the co-ordinates we have given it.


CREATE FUNCTION [dbo].[NearestItems]
      @lat real,
      @long real,
        @num int,
        @distance float
      )
RETURNS  TABLE
AS
      RETURN
      SELECT top (@num) Lobacos.lobaco_id
      FROM   Lobacos
      WHERE  dbo.DistanceBetween(@lat, @long, Latitude, Longitude) < @distance