Dec
1
Written by:
Michael Washington
12/1/2013 3:33 PM
NOTE: You can play the game at this link: https://manipulyte.lightswitchhelpwebsite.com/HTMLClient (use your username and password from LightSwitchHelpWebsite.com)
Thomas Capps and I sat out to make a HTML SPA turn based game using Visual Studio LightSwitch. We wanted to make a game that can be played on all devices, phone, tablet, and desktop web browser. It had to be a SPA application because that technology is a must for decent performance on a phone or tablet unless you make a native application. Visual Studio LightSwitch HTML Client creates SPA applications, so we chose LightSwitch because development is faster and easier than other options.
Before we began working on the real game, we decided to make a proof of concept that demonstrated the key functionality we would need. This version is not a real game, it is just a simple example that allows two players to take turns rolling the die and drawing cards to see who can move to the end of the small game board first.
However, it demonstrates that with LightSwitch we can implement the following things:
- A turn based game that works on all HTML devices including mobile phones
- Implements methods that prevent users from cheating
- Allows users to design games and play their games with others
- Allow users to upload their own photos to use in their games
- Quick codes to allow users to easily log into the application
- Game lobby to allow users to connect for games
- Email notifications
Walk-Thru
When users first logs into the application they are presented with a list of games to join (if any) and the ability to start their own game.
They are also presented with the menu:
- Picture Manager - Upload and manage their own pictures
- Game Designer - Create and manage their own games
- Set Quick Code – Create a code that will facilitate fast login that wont require them to type their full username and password
- Set Email Address – Manage the email address that they will use for game notifications
Manage Pictures allows users to upload and manage pictures that only they will be able to use in games they create.
The game designer allows users to change the picture and text of each of the games spaces.
It also allows them to indicate what spaces will require a user to draw a card, what that card will say, and if it will require a user to move forward or back a space.
Any user can start a game session using a game that was created by themselves or another user.
When a user starts a game session it will show them that they are waiting for another player to join.
When any other player logs in they will see games waiting for a player that they can join.
Players receive email notifications notifying them when it is their turn.
Play proceeds until there is a winner of the game.
The Command Table Pattern
The most important thing that was learned in creating the proof of concept, was the need for, and the implementation of, the Command Table Pattern. Essentially it means that:
You need a method to allow a user to perform an action that you cannot simply give them the ability to do directly.
Even if you are not creating a video game you will find you may have this need. It is frequently required in business applications. For example, you may not want to allow a user to cancel an order directly.
You can see a detailed explanation at the following link: OData Security Using The Command Table Pattern And Permission Elevation.
An alternative to the Command Table Pattern is using WCF RIA Services to perform the same functionality as described here: LightSwitch Survey: Handling Complex Business Logic Using WCF RIA Services.
In Manipulyte, the access to most tables like the GameSessions table is restricted:
partial void GameSessions_CanDelete(ref bool result)
{
result = this.Application.User.HasPermission(Permissions.CanWriteToTables);
}
partial void GameSessions_CanInsert(ref bool result)
{
result = this.Application.User.HasPermission(Permissions.CanWriteToTables);
}
partial void GameSessions_CanUpdate(ref bool result)
{
result = this.Application.User.HasPermission(Permissions.CanWriteToTables);
}
The GameSessionsCommands table is used to create and modify records in the GameSessions table.
When a user clicks a button to perform an action they insert a record into the GameSessionsCommands table.
The _inserting method for the table is called, and if then logic is used to determine if the identity of the user, and the current state of the GameSession record they are attempting to create or modify, should allow the change to proceed. If it is to proceed, the user’s security level is temporally “elevated” to allow the GameSession table to be modified.
For example, the following code is used to determine if a user can join a game:
if (entity.CommandAction == "Join")
{
// Must not already be in a Game
GameSession currentSession = GetCurrentSession();
if (currentSession == null)
{
int intGameSessionID = Convert.ToInt32(entity.CommandParameter);
var SelectedGameSession = (from objGameSessions in this.GameSessions
where objGameSessions.Id == intGameSessionID
where objGameSessions.GameState == "Created"
select objGameSessions).FirstOrDefault();
if (SelectedGameSession != null)
{
// Elevate the user's permission to allow the Entity to be updated
this.Application.User.AddPermissions(Permissions.CanWriteToTables);
// Add currentuser as creator & playerone
SelectedGameSession.PlayerTwoUserName = this.Application.User.Name;
SelectedGameSession.GameState = "Started";
// SEND EMAIL TO PLAYER ONE (TAKE TURN)
SendEmail(SelectedGameSession);
}
}
}
Uploading and Managing Pictures
Manipulyte allows users to upload and manage their own pictures.
You can find a detailed explanation of the code at the following link:
LightSwitch HTML Picture Manager Using WCF RIA Services
Game Designer
The Edit Game screen, located in the AddEditGame page is the most complex part of the application other than the Play Game screen. It demonstrates that Visual Studio LightSwitch implements fast performing SPA JavaScript that allows complex functionality that even performs well on a cell phone with a slow connection.
It also contains the second largest amount of JavaScript for any single page, at nearly 500 lines of code.
Some code is simple, for example the following method is used to delete a game:
myapp.AddEditGame.DeleteGame_execute = function (screen) {
// delete game
screen.Game.deleteEntity();
return myapp.commitChanges().then(null, function fail(e) {
myapp.cancelChanges();
alert("Cannot delete game when it has been played!");
throw e;
});
};
Other code is more complex, such as this code to change a picture for a space:
myapp.AddEditGame.ChangePicture05_Tap_execute = function (screen) {
myapp.showBrowseGetUserAndDefaultPictures({
beforeShown: function (addEditScreen) {
// Set the text to the current Space Text value
addEditScreen.SpaceText = screen.Game.Space05;
// Set the flipswitch to yes/no for optional Card Draw
addEditScreen.IsDrawCard = screen.Game.IsSpace05Card;
},
afterClosed: function (addEditScreen, navigationAction) {
// Always update DrawCard boolean and Space Text
screen.Game.IsSpace05Card = addEditScreen.IsDrawCard;
screen.Game.Space05 = addEditScreen.SpaceText;
// Get SelectedPictureName
var ActualFileName = addEditScreen.SelectedPictureName;
// Only set picture if one were selected
if (ActualFileName !== undefined && ActualFileName !== null) {
// update the PictureSpace value
screen.Game.PictureSpace05 = ActualFileName;
}
}
});
};
To understand the code fully, it is recommended that you read my book:
Creating Web Pages Using the LightSwitch HTML Client In Visual Studio 2012
Play Game
The Play Game screen is the largest and most complex of any screen in the application.
It is comprised of over 500 lines of code.
The following code sets up the game board:
myapp.PlayGame.created = function (screen) {
// Get game
screen.GameSession.getGame().then(function (result) {
// Set Game name
screen.details.displayName = "Play Game: " + result.GameName;
// Get Pictures
screen.getIndividualGamePicture();
});
// set default die image
screen.imgDice = "Content/DieOne.png";
// Set CurrentPlayers UserName
msls.promiseOperation(CallGetUserName).then(function PromiseSuccess(GetUserNameResult) {
var CurrentPlayerUserName = GetUserNameResult
// Call CallGetGameStatus to get latest game session info
msls.promiseOperation(CallGetGameStatus).then(function PromiseSuccess(GetGameStatusResult) {
// Parse the JSON returned
var objGameStatus = jQuery.parseJSON(GetGameStatusResult);
if (objGameStatus.CurrentPlayerUserName !== GetUserNameResult || objGameStatus.GameState == "Created") {
// It is not the current players turn
screen.findContentItem("btnRollDie").isVisible = false;
screen.findContentItem("btnDrawCard").isVisible = false;
screen.lblMessage = '...waiting for other player...';
} else {
// It is the current players turn
if (objGameStatus.PlayerNeedsToDrawCard) {
// draw card needed; hide Roll Die and display Draw Card
screen.findContentItem("btnRollDie").isVisible = false;
screen.findContentItem("btnDrawCard").isVisible = true;
screen.lblMessage = 'Your Turn! Click "Draw Card" to continue...';
}
else {
// current turn; display Roll Die and hide Draw Card
screen.findContentItem("btnRollDie").isVisible = true;
screen.findContentItem("btnDrawCard").isVisible = false;
screen.lblMessage = 'Your Turn! Click "Roll Die" to continue...';
}
}
// Call SetGamePieces
SetGamePieces(screen, objGameStatus.PlayerOneCurrentSpace, objGameStatus.PlayerTwoCurrentSpace);
});
});
};
The following code is used to process a Die roll:
myapp.PlayGame.RollDie_execute = function (screen) {
// Create a new Game Session Command
var GameSessionCommand = new myapp.GameSessionCommand();
GameSessionCommand.CommandAction = "RollDie";
GameSessionCommand.CommandActionTime = new Date();
GameSessionCommand.CommandParameter = " ";
GameSessionCommand.UserName = " ";
// Save the GameSessionCommand to execute a die roll
return myapp.activeDataWorkspace.ApplicationData.saveChanges().then(function () {
// Call CallGetGameStatus to get latest game session info
msls.promiseOperation(CallGetGameStatus).then(function PromiseSuccess(GetGameStatusResult) {
// Parse the JSON returned
var objGameStatus = jQuery.parseJSON(GetGameStatusResult);
// Set lblMessage
screen.lblMessage = objGameStatus.PlayerLastStatusMessage;
// show correct Die Image to player
SetDieImage(objGameStatus.PlayerLastDieRoll);
// no matter what, cannot roll again, so hide that button
screen.findContentItem("btnRollDie").isVisible = false;
// determine if Card still needs to be drawn, or if turn over
if (objGameStatus.PlayerNeedsToDrawCard) {
// draw card needed; display Draw Card button
screen.findContentItem("btnDrawCard").isVisible = true;
}
else {
screen.lblMessage = screen.lblMessage + " ...waiting for other player...";
}
// call SetGamePieces
SetGamePieces(screen, objGameStatus.PlayerOneCurrentSpace, objGameStatus.PlayerTwoCurrentSpace);
// if "null" then game is over; redirecting
if (objGameStatus.PlayerLastStatusMessage == "null") {
myapp.showMain();
}
});
});
};
WCF RIA Services
You will find that WCF RIA Services are useful in the following situations:
- Combine more than one entity into a single entity.
- Eliminate unneeded columns in an entity to improve performance (otherwise large amounts of data, for example pictures, will be transmitted even when they are not shown).
- Implement calculated fields that allow the resulting values to be searchable and sortable.
WCF RIA Services are used in Manipulyte for the following functionality:
- Retrieving a Quick Code only when the secret string is also passed
- Creating thumbnails of uploaded pictures
- Retrieving pictures from the server hard drive
Quick Codes
When using a mobile device, it takes a considerable amount of time to enter a username and password. Quick codes are implemented to allow a user to create a short code that can be used from a specific device to quickly log in.
See the following article for a full explanation: LightSwitch lsQuickCode: Fast Login For Mobile Applications.
Email Notifications
Sending emails is an important part of the application. Users are notified when it is their turn in the game, and are notified as to who the winner of the game is.
See the following article: Sending Asynchronous Emails Using LightSwitch HTML Client for more information and sample code on sending emails using Visual Studio LightSwitch.
Just a Demo
While care was taken to test the goals of the proof of concept, the code is still considered demonstration code and has not been tested for production level security.
Special Note
Game concept, design and much of the coding is by Thomas Capps. The final game will be much more extensive with the ability to buy and sell spaces.
Download Code
The LightSwitch project is available at http://lightswitchhelpwebsite.com/Downloads.aspx
(you must have Visual Studio 2012 Update 2 installed to run the code – it will not run in Visual Studio 2013)
1 comment(s) so far...
I can't remember a blog post of yours that I have enjoyed as well!
Now let's get to work on the full version ;)
(And my own thanks to you, Michael, for your assistance so far! Without you this idea would still be on the back burner, with no hope of being made. And Lightswitch certainly helped as well :D )
By Tom Capps on
12/2/2013 12:38 PM
|