Building A Simple Working Memory Game App With Web API And SignalR

Introduction

This article will walk you through how to build a simple data-driven mobile game application using the power of Xamarin and Web API. We will also build a real-time leaderboard page using ASP.NET SignalR.

Background

In the past year, I was tasked to create proof-of-concept application about Working Memory in the form of a game. I was a bit excited about it since building a game application isn’t really my area of expertise and I was very curious about it. I decided to use Xamarin and Visual Studio for the following reasons:

  • Xamarin is now fully integrated with the latest Visual Studio release (VS 2017 as of this time of writing).
  • Xamarin allows you to build cross-platform apps (iOS, Andriod, and UWP) using C#.
  • I am an experienced C# developer.
  • I am more familiar with Visual Studio development tools.
  • I don't need to learn how to use other frameworks, editors, tools and other programming languages to build native apps.
  • I can take advantage of the cool features provided by Xamarin such as cloud testing and app monitoring.
  • Xamarin and Visual Studio are quite popular and stable platform for building real-world apps.
  • Xamarin has its own dedicated support site. So when you encounter any problem during your development, you can easily post your query to their dedicated forums.

I'm writing this article so anyone interested in mobile app development can reference this if they need a simple working game app that requires some kind of features that connects data from a mobile app to other services such as a REST application or web application. This article will walk you through on how to build a simple Working Memory game application using the power of Xamarin and ASP.NET

Before we dig down further, let’s take about a bit of Working Memory.

What is a Working Memory?

According to the documentation, a Working memory is a cognitive system with a limited capacity that is responsible for temporarily holding information available for processing. Working memory is important for reasoning and the guidance of decision-making and behavior. We can say that Working Memory is a crucial brain function that we use to focus our attention and control our thinking.

What You Will Learn

This article is targeted for beginners to intermediate .NET developers who want to build a data-driven mobile application that connects to other services from scratch and get their hands dirty with a practical example. I've written this book in such a way that it’s easy to follow and understand. As you go along and until such time you finished following the book, you will learn how to:

  • Set up an SQL Server database from scratch.
  • Build a simple Working Memory game application using Xamarin.Forms that targets both, iOS and Android platform.
  • Create an ASP.NET Web API project.
  • Integrate Entity Framework as our data access mechanism.
  • Create an ASP.NET MVC 5 project.
  • Integrate ASP.NET SignalR within ASP.NET MVC.
Prerequisites

Before you go any further, make sure that you have the necessary requirements for your system and your development environment is properly configured.  This particular demo uses the following tools and frameworks:

  • Visual Studio 2015
  • SQL Server Management Studio Express 2014
  • Xamarin 4.1
  • NET Web API 2
  • NET MVC 5
  • NET SignalR 2.2
  • Entity Framework 6

A basic knowledge on the following language and concept is also required.

  • C#
  • JavaScript
  • AJAX
  • CSS
  • HTML
  • XAML
  • HTTP Request and Response
  • OOP
Five Players, One Goal

As you can see from the prerequisites section, we are going to use various technologies to build this whole game application to fulfil a goal. The diagram below illustrates the high-level process on how each technologies connects to each other.
Xamarin
Based on the illustration above, we are going to need to the following projects,

  • A Mobile app
  • A Web API app
  • A Web app

To summarize that, we are going to build a mobile application using (1) Xamarin.Forms that can target both iOS and Android platform. The mobile app is where the actual game is implemented. We will build a (2) Web API project to handle CRUD operations using (3) Entity Framework. The Web API project will serve as the central gateway to handle data request that comes from the mobile app and web app. We will also build a web application to display the real-time dashboard for ranking using (4) ASP.NET MVC and (5) ASP.NET SignalR. Finally, we are going to create a database for storing the player information and their scores in SQL Server.

Game Objective

The objective of this game is very simple; you just need to count how many times: The light will blink on, the speaker will beep and the device will vibrate within a span of time.  The higher your level is, the faster it blinks, beeps and vibrates. This would test how great your memory is.

Game Views

This section will give some visual reference about the outputs of the applications that we are going to build.

Xamarin

The very first time you open the app, it will bring the Registration page wherein you could register using your name and email. This page also allows you to log-in using your existing account.

Here’s running view of the Registration page,

Xamarin

Once you registered or after you successfully logon to the system, it will bring the following page below,

Xamarin

Clicking the “START” button will start playing the game within a short period of time as shown in the figure below,

Xamarin

After the time has elapsed, it will bring you to the result page wherein you can input your answer of how many times each event happened.

Xamarin

Clicking the “SUBMIT” button will validate your answers whether you got them right and proceed to the next level or restart the game to your current level. Note that your score will automatically synced to the database once you surpass your current best score.

Here are some of the screenshots of the results:

Xamarin

Xamarin

And here’s a screenshot of the real-time web app dashboard for the rankings which triggered by ASP.NET SignalR.
Xamarin

That’s it. Now that you already have some visual reference how the app will look like, it’s time for us to build the app.

Let’s Begin!

Let’s go ahead and fire up Visual Studio 2015 and then create a new Blank XAML App (Xamarin.Forms Portable) project as shown in the figure below,

Xamarin
Figure 1: New Project

For this demo, we will name the project as “MemoryGame.App”. Click OK to let Visual Studio generate the default project templates for you.  You should now be presented with this,

Xamarin

Note that the solution only contains the .Droid and .iOS projects. This is because I omitted the .Windows project for specific reasons. This means that, we will be focusing on Android and iOS apps instead.

The Required NuGet Packages

The first thing we need is to add the required packages that are needed for our application. Now go ahead and install the following packages in all projects:

  • Plugins.Settings
  • Plugin.Connectivity

We'll be using the Xam.Plugins.Settings to provide us a consistent, cross platform settings/preferences across all projects (Portable library, Android and iOS projects). The Xam.Plugin.Connectivity will be used to get network connectivity information such as network type, speeds, and if connection is available. We'll see how each of these references is used in action later.

You can install them via PM Console or via NPM GUI just like in the figure below,

Xamarin

We also need to install the following package under the MemoryGame.App project:

  • Json

We will be using Newtonsoft.Json later in our code to serialize and deserialize object from an API request.

Once you’ve installed them all, you should be able to see them added in your project references just like in the figure below,

Xamarin

Setting Up a New Database

The next step is setup a database for storing the challenger and rank data. Now go ahead and fire-up SQL Server Management Studio and execute the following SQL script below:

 

  1. CREATE Database MemoryGame  
  2. GO  
  3.   
  4. USE [MemoryGame]  
  5. GO  
  6.   
  7. CREATE TABLE [dbo].[Challenger](  
  8.     [ChallengerID] [int] IDENTITY(1,1) NOT NULL,  
  9.     [FirstName] [varchar](50) NOT NULL,  
  10.     [LastName] [varchar](50) NOT NULL,  
  11.     [Email] [varchar](50) NULL,  
  12. CONSTRAINT [PK_Challenger] PRIMARY KEY CLUSTERED   
  13. (  
  14.     [ChallengerID] ASC  
  15. )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF,   
  16.             IGNORE_DUP_KEY = OFF,   
  17.             ALLOW_ROW_LOCKS  = ON,   
  18.             ALLOW_PAGE_LOCKS  = ON)   
  19.             ON [PRIMARY]  
  20. ON [PRIMARY]  
  21.   
  22. GO  
  23.   
  24. CREATE TABLE [dbo].[Rank](  
  25.     [RankID] [int] IDENTITY(1,1) NOT NULL,  
  26.     [ChallengerID] [intNOT NULL,  
  27.     [Best] [tinyint] NOT NULL,  
  28.     [DateAchieved] [datetime] NOT NULL,  
  29.  CONSTRAINT [PK_Rank] PRIMARY KEY CLUSTERED   
  30. (  
  31.     [RankID] ASC  
  32. )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF,   
  33.             IGNORE_DUP_KEY = OFF,   
  34.             ALLOW_ROW_LOCKS  = ON,   
  35.             ALLOW_PAGE_LOCKS  = ONON [PRIMARY]  
  36. ON [PRIMARY]  
  37.   
  38. GO  

The SQL script above should create the “MemoryGame” database with the following table,

Xamarin

The database tables that we’ve created earlier are very plain and simple. The dbo.Challenger table just contains some basic properties for us to identify a user who plays the game. The dbo.Rank table holds some basic property to help us identify which user has the highest rank.

Creating the Web API Project

Now that we’ve done setting up our database, it’s time for us to build a REST service to handle database calls and CRUD operations. We are choosing Web API because it’s a perfect fit to build RESTful services in the context of .NET. It also allows other apps (Mobile, Web Apps and even Desktop Apps) to consume our API via EndPoints. This would enable our application to allow clients to access data in any form of application for as long as it supports HTTP services.

Ok let’s proceed to our work. Switch back to Visual Studio and then create a new project by right-clicking on the main Solution and then select Add > New Project > Visual C# > Web.  Select ASP.NET Web Application(.NET Framework) and name the project as “MemoryGame.API” just like in the figure below,

Xamarin

Click OK and then select “Empty” from the ASP.NET project template. Check the “Web API” option only and then click Ok to let Visual Studio generate the project for you just like in the figure below,

Xamarin

Integrating Entity Framework

What is EF?

…………..

Now that we have our Web API project ready, let’s continue by implementing our Data Access to work with data from database. For this demo, we are going to use Entity Framework as our Data Access mechanism.

In the MemoryGame.API project, create a new folder called “DB” under the Models folder. Within the “DB” folder, add an ADO.NET Entity Data Model. To do this, just follow these steps:

  1. Right click on the “DB” folder and then select Add > New Item > ADO.NET Entity Data Model.
  2. Name the file as “MemoryGameDB” and then click Add.
  3. In the next wizard, select EF Designer from Database.
  4. Click the “New Connection…” button.
  5. Supply the Database Server Name to where you created the database in the previous section.
  6. Select or enter the database name. In this case, the database name for this example is “MemoryGame”.
  7. Click the Test Connection button to see if it’s successful just like in the figure below,

    Xamarin

  8. Click OK to generate the connection string that will be used for our application.
  9. In the next wizard, Click “Next”
  10. Select Entity Framework 6.x and then click “Next”
  11. Select the “Challenger” and “Rank” tables and then click “Finish”.

The .EDMX file should now be added under the “DB” folder just like in the figure below,

Xamarin

Implementing Data Access for CRUD Operations

The next step is to create a central class for handling Create, Read Update and Delete (CRUD) operations. Now, create a new folder called “DataManager” under the “Models” folder. Create a new class called “GameManager” and then copy the following code below:

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using MemoryGame.API.Models.DB;  
  5.   
  6. namespace MemoryGame.API.Models.DataManager  
  7. {  
  8.     #region DTO  
  9.     public class ChallengerViewModel  
  10.     {  
  11.         public int ChallengerID { get; set; }  
  12.         public string FirstName { get; set; }  
  13.         public string LastName { get; set; }  
  14.         public byte Best { get; set; }  
  15.         public DateTime DateAchieved { get; set; }  
  16.     }  
  17.     #endregion  
  18.  
  19.     #region HTTP Response Object  
  20.     public class HTTPApiResponse  
  21.     {  
  22.         public enum StatusResponse  
  23.         {  
  24.             Success = 1,  
  25.             Fail = 2  
  26.         }  
  27.   
  28.         public StatusResponse Status { get; set; }  
  29.         public string StatusDescription { get; set; }  
  30.     }  
  31.     #endregion  
  32.  
  33.     #region Data Access  
  34.     public class GameManager  
  35.     {  
  36.         public IEnumerable<ChallengerViewModel> GetAll { get { return GetAllChallengerRank(); } }  
  37.   
  38.         public List<ChallengerViewModel> GetAllChallengerRank()  
  39.         {  
  40.   
  41.             using (MemoryGameEntities db = new MemoryGameEntities())  
  42.             {  
  43.                 var result = (from c in db.Challengers  
  44.                               join r in db.Ranks on c.ChallengerID equals r.ChallengerID  
  45.                               select new ChallengerViewModel  
  46.                               {  
  47.                                   ChallengerID = c.ChallengerID,  
  48.                                   FirstName = c.FirstName,  
  49.                                   LastName = c.LastName,  
  50.                                   Best = r.Best,  
  51.                                   DateAchieved = r.DateAchieved  
  52.                               }).OrderByDescending(o => o.Best).ThenBy(o => o.DateAchieved);  
  53.   
  54.                 return result.ToList();  
  55.             }  
  56.         }  
  57.   
  58.   
  59.         public HTTPApiResponse UpdateCurrentBest(DB.Rank user)  
  60.         {  
  61.             using (MemoryGameEntities db = new MemoryGameEntities())  
  62.             {  
  63.                 var data = db.Ranks.Where(o => o.ChallengerID == user.ChallengerID);  
  64.                 if (data.Any())  
  65.                 {  
  66.                     Rank rank = data.FirstOrDefault();  
  67.                     rank.Best = user.Best;  
  68.                     rank.DateAchieved = user.DateAchieved;  
  69.                     db.SaveChanges();  
  70.                 }  
  71.                 else  
  72.                 {  
  73.                     db.Ranks.Add(user);  
  74.                     db.SaveChanges();  
  75.                 }  
  76.             }  
  77.   
  78.             return new HTTPApiResponse  
  79.             {  
  80.                 Status = HTTPApiResponse.StatusResponse.Success,  
  81.                 StatusDescription = "Operation successful."  
  82.             };  
  83.         }  
  84.   
  85.         public int GetChallengerID(string email)  
  86.         {  
  87.             using (MemoryGameEntities db = new MemoryGameEntities())  
  88.             {  
  89.                 var data = db.Challengers.Where(o => o.Email.ToLower().Equals(email.ToLower()));  
  90.                 if (data.Any())  
  91.                 {  
  92.                     return data.FirstOrDefault().ChallengerID;  
  93.                 }  
  94.   
  95.                 return 0;  
  96.             }  
  97.         }  
  98.   
  99.         public HTTPApiResponse AddChallenger(DB.Challenger c)  
  100.         {  
  101.             HTTPApiResponse response = null;  
  102.             using (MemoryGameEntities db = new MemoryGameEntities())  
  103.             {  
  104.                 var data = db.Challengers.Where(o => o.Email.ToLower().Equals(c.Email.ToLower()));  
  105.                 if (data.Any())  
  106.                 {  
  107.                     response =  new HTTPApiResponse  
  108.                     {  
  109.                         Status = HTTPApiResponse.StatusResponse.Fail,  
  110.                         StatusDescription = "User with associated email already exist."  
  111.                     };   
  112.                 }  
  113.                 else  
  114.                 {  
  115.                     db.Challengers.Add(c);  
  116.                     db.SaveChanges();  
  117.   
  118.                     response = new HTTPApiResponse  
  119.                     {  
  120.                         Status = HTTPApiResponse.StatusResponse.Success,  
  121.                         StatusDescription = "Operation successful."  
  122.                     };  
  123.                 }  
  124.   
  125.                 return response;  
  126.             }  
  127.         }  
  128.   
  129.         public ChallengerViewModel GetChallengerByEmail(string email)  
  130.         {  
  131.             using (MemoryGameEntities db = new MemoryGameEntities())  
  132.             {  
  133.                 var result = (from c in db.Challengers  
  134.                               join r in db.Ranks on c.ChallengerID equals r.ChallengerID  
  135.                               where c.Email.ToLower().Equals(email.ToLower())  
  136.                               select new ChallengerViewModel  
  137.                               {  
  138.                                   ChallengerID = c.ChallengerID,  
  139.                                   FirstName = c.FirstName,  
  140.                                   LastName = c.LastName,  
  141.                                   Best = r.Best,  
  142.                                   DateAchieved = r.DateAchieved  
  143.                               });  
  144.                 if (result.Any())  
  145.                     return result.SingleOrDefault();  
  146.             }  
  147.             return new ChallengerViewModel();  
  148.         }  
  149.   
  150.         public HTTPApiResponse DeleteChallenger(int id)  
  151.         {  
  152.             HTTPApiResponse response = null;  
  153.             using (MemoryGameEntities db = new MemoryGameEntities())  
  154.             {  
  155.                 var data = db.Challengers.Where(o => o.ChallengerID == id);  
  156.                 if (data.Any())  
  157.                 {  
  158.                     try  
  159.                     {  
  160.                         var rankData = db.Ranks.Where(o => o.ChallengerID == id);  
  161.                         if (rankData.Any())  
  162.                         {  
  163.                             db.Ranks.Remove(rankData.FirstOrDefault());  
  164.                             db.SaveChanges();  
  165.                         }  
  166.   
  167.                         db.Challengers.Remove(data.FirstOrDefault());  
  168.                         db.SaveChanges();  
  169.   
  170.                         response = new HTTPApiResponse  
  171.                         {  
  172.                             Status = HTTPApiResponse.StatusResponse.Success,  
  173.                             StatusDescription = "Operation successful."  
  174.                         };  
  175.                     }  
  176.                     catch (System.Data.Entity.Validation.DbUnexpectedValidationException)  
  177.                     {  
  178.                         //do stuff  
  179.                         response = new HTTPApiResponse  
  180.                         {  
  181.                             Status = HTTPApiResponse.StatusResponse.Fail,  
  182.                             StatusDescription = "An unexpected error occured."  
  183.                         };  
  184.                     }  
  185.                 }  
  186.                 else  
  187.                 {  
  188.                     response = new HTTPApiResponse  
  189.                     {  
  190.                         Status = HTTPApiResponse.StatusResponse.Fail,  
  191.                         StatusDescription = "Associated ID not found."  
  192.                     };  
  193.                 }  
  194.   
  195.                 return response;  
  196.             }  
  197.         }  
  198.     }  
  199.     #endregion  
  200. }  

Let’s take a look of what we just did there.

The code above is composed of three (3) main regions: The Data Transfer Object (DTO), the HTTP response object and the GameMananger class.

The DTO is nothing but just a plain class that houses some properties that will be used in the View or any client that consumes the API.

The HTTPApiResponse object is class that holds 2 main basic properties: Status and StatusDescription. This object will be used in the GameManager class methods as a response or return object.

The GameManager class is the central class where we handle the actual CRUD operations. This is where we use Entity Framework to communicate with the database by working with conceptual data entity instead of real SQL query. Entity Framework enables us to work with a database using .NET objects and eliminates the need for most of the data-access code that developers usually need to write.

The GameManager class is composed of the following methods

  • GetAll() – A short method that calls the GetAllChallengerRank() method.
  • GetAllChallengerRank() - Gets all the challenger names and it’s rank. It uses LINQ to query the model and sort the data.
  • GetChallengerByEmail(string email) – Gets the challenger information and rank by email.
  • GetChallengerID(string email) – Gets the challenger id by passing an email address as parameter.
  • AddChallenger(DB.Challenger c) – Adds a new challenger to the database.
  • UpdateCurrentBest(DB.Rank user) – Updates the rank of a challenger to then newly achieved high score.
  • DeleteChallenger(int id) – Deletes a challenger from the database.
The Web API EndPoints

Now that we have our data access ready, we can now start creating the API endpoints to serve data.

Create a new folder called “API” within the root of the application. Create a new Web API 2 Controller – Empty class and name it as “GameController” and then copy the following code below

  1. using MemoryGame.API.Models.DataManager;  
  2. using MemoryGame.API.Models.DB;  
  3. using System.Collections.Generic;  
  4. using System.Web.Http;  
  5.   
  6. namespace MemoryGame.API.API  
  7. {  
  8.     public class GameController : ApiController  
  9.     {  
  10.         GameManager _gm;   
  11.         public GameController()  
  12.         {  
  13.             _gm = new GameManager();  
  14.         }  
  15.   
  16.         public IEnumerable<ChallengerViewModel> Get()  
  17.         {  
  18.             return _gm.GetAll;  
  19.         }  
  20.   
  21.         [HttpPost]  
  22.         public HTTPApiResponse AddPlayer(Challenger user)  
  23.         {  
  24.             return _gm.AddChallenger(user);  
  25.         }  
  26.   
  27.         [HttpPost]  
  28.         public void AddScore(Rank user)  
  29.         {  
  30.             _gm.UpdateCurrentBest(user);  
  31.         }  
  32.   
  33.         [HttpPost]  
  34.         public HTTPApiResponse DeletePlayer(int id)  
  35.         {  
  36.             return _gm.DeleteChallenger(id);  
  37.         }  
  38.   
  39.         public int GetPlayerID(string email)  
  40.         {  
  41.             return _gm.GetChallengerID(email);  
  42.         }  
  43.   
  44.         public ChallengerViewModel GetPlayerProfile(string email)  
  45.         {  
  46.             return _gm.GetChallengerByEmail(email);  
  47.         }  
  48.     }  
  49. }  

Just like in the GameManager class, the GameController API is composed of the following methods:

Method

EndPoint

Description

Get()

 

Get all the challenger and rank data

AddPlayer(Challenger user)

 

Adds a new challenger

AddScore(Rank user)

 

Adds or updates a challenger score

DeletePlayer(int id)

 

Removes a player

GetPlayerID(string email)

 

Get the challenger id based on email

GetPlayerProfile(string email)

 

Get challenger information based on email


Enabling Cors

Now that we have our API ready, the final step that we are going to do on this project is to enable Cross-Orgin Resource Sharing (a.k.a CORS). We need this because this API will be consumed in other application that probably has a difference domain.

To enable CORS in ASP.NET Web API, do,

  1. Install AspNet.WebApi.Cors via nugget
  2. Open the file App_Start/WebApiConfig.cs. Add the following code to the WebApiConfig.Register method:

    config.EnableCors(); 
  1. Finally add the [EnableCors] attribute to the GameController class,

    [EnableCors(origins: "http://localhost:60273", headers: "*", methods: "*")]
    public class GameController : ApiController

Note that you’ll have to replace the value of origins based on the URI of the consuming client. Otherwise you can use the “*” wild-card to allow any domain to access your API.

Building the Mobile Application with Xamarin Forms

MemoryGame.App(Portable) Project

Now that we have the API ready, we can now start implementing the Memory Game app and start consuming the Web API that we’ve just created earlier. Switch back to MemoryGame.App(Portable) project and then create the following folders:

  • REST
  • Services
  • Classes
  • Pages
The GameAPI Class

Since we’ve done creating out API endpoints earlier, let’s start by creating the class for consuming the API. Create a new class called “GameAPI.cs” under the REST folder and then copy the following code below

  1. using System;  
  2. using System.Text;  
  3. using System.Threading.Tasks;  
  4. using Newtonsoft.Json;  
  5. using MemoryGame.App.Classes;  
  6. using System.Net.Http;  
  7.   
  8. namespace MemoryGame.App.REST  
  9. {  
  10.     public class GameAPI  
  11.     {  
  12.         private const string APIUri = "<YOUR API URI>"//replace this value with the published URI to where your API is hosted. E.g http://yourdomain.com/yourappname/api/game  
  13.         HttpClient client;  
  14.         public GameAPI()  
  15.         {  
  16.             client = new HttpClient();  
  17.             client.DefaultRequestHeaders.Host = "yourdomain.com"; >"; //replace this value with the actual domain  
  18.             client.MaxResponseContentBufferSize = 256000;  
  19.         }  
  20.   
  21.         public async Task<bool> SavePlayerProfile(PlayerProfile data, bool isNew = false)  
  22.         {  
  23.             var uri = new Uri($"{APIUri}/AddPlayer");  
  24.   
  25.             var json = JsonConvert.SerializeObject(data);  
  26.             var content = new StringContent(json, Encoding.UTF8, "application/json");  
  27.   
  28.             HttpResponseMessage response = null;  
  29.             if (isNew)  
  30.                 response = await ProcessPostAsync(uri, content);  
  31.   
  32.   
  33.             if (response.IsSuccessStatusCode)  
  34.             {  
  35.                 Settings.IsProfileSync = true;  
  36.                 return true;  
  37.             }  
  38.   
  39.             return false;  
  40.         }  
  41.   
  42.         public async Task<bool> SavePlayerScore(PlayerScore data)  
  43.         {  
  44.             var uri = new Uri($"{APIUri}/AddScore");  
  45.   
  46.             var json = JsonConvert.SerializeObject(data);  
  47.             var content = new StringContent(json, Encoding.UTF8, "application/json");  
  48.             var response = await ProcessPostAsync(uri, content);  
  49.   
  50.             if (response.IsSuccessStatusCode)  
  51.                 return true;  
  52.   
  53.             return false;  
  54.         }  
  55.   
  56.         public async Task<int> GetPlayerID(string email)  
  57.         {  
  58.             var uri = new Uri($"{APIUri}/GetPlayerID?email={email}");  
  59.             int id = 0;  
  60.   
  61.             var response = await ProcessGetAsync(uri);  
  62.             if (response.IsSuccessStatusCode)  
  63.             {  
  64.                 var content = await response.Content.ReadAsStringAsync();  
  65.                 id = JsonConvert.DeserializeObject<int>(content);  
  66.             }  
  67.   
  68.             return id;  
  69.         }  
  70.   
  71.         public async Task<PlayerData> GetPlayerData(string email)  
  72.         {  
  73.             var uri = new Uri($"{APIUri}/GetPlayerProfile?email={email}");  
  74.             PlayerData player = null;  
  75.   
  76.             var response = await ProcessGetAsync(uri);  
  77.             if (response.IsSuccessStatusCode)  
  78.             {  
  79.                 player = new PlayerData();  
  80.                 var content = await response.Content.ReadAsStringAsync();  
  81.                 player = JsonConvert.DeserializeObject<PlayerData>(content);  
  82.             }  
  83.   
  84.             return player;  
  85.         }  
  86.   
  87.         private async Task<HttpResponseMessage> ProcessPostAsync(Uri uri, StringContent content)  
  88.         {  
  89.             return await client.PostAsync(uri, content); ;  
  90.         }  
  91.   
  92.         private async Task<HttpResponseMessage> ProcessGetAsync(Uri uri)  
  93.         {  
  94.             return await client.GetAsync(uri);  
  95.         }  
  96.     }  
  97. }  

The code above is pretty much self-explanatory as you could probably guess by its method name. The class just contains some methods that call the API endpoints that we have created in the previous section.

Implementing the Service Interfaces

Next, we will create a few services that our app will need. Add the following class/interface files under the Services folder:

  • cs
  • cs
  • cs

Now copy the corresponding code below for each file.

The IHaptic Interface
  1. namespace MemoryGame.App.Services  
  2. {  
  3.     public interface IHaptic  
  4.     {  
  5.         void ActivateHaptic();  
  6.     }  
  7. }  
The ILocalDataStore Interface
  1. namespace MemoryGame.App.Services  
  2. {  
  3.     public interface ILocalDataStore  
  4.     {  
  5.         void SaveSettings(string fileName, string text);  
  6.         string LoadSettings(string fileName);  
  7.     }  
  8. }  
The ISound Interface
  1. namespace MemoryGame.App.Services  
  2. {  
  3.     public interface ISound  
  4.     {  
  5.         bool PlayMp3File(string fileName);  
  6.         bool PlayWavFile(string fileName);  
  7.     }  
  8. }  

The reason why we are creating the services above is because iOS and Android have different code implementation on setting the device vibration and sound. That’s why we are defining interfaces so both platform can just inherit from it and implement code specific logic.

Let’s move on by creating the following objects within the Classes folder

  • cs
  • cs
  • cs
  • cs

Before we start, let’s do a bit of clean-up first. Remember we’ve installed the Xam.Plugins.Settings in the previous section right? You will notice that after we installed that library, the Settings.cs file was automatically generated within the Helpers folder. Leaving the Settings.cs file there is fine but we wanted to have clean separation of objects so we will move the Settings.cs file within the Classes folder. Once you’ve done moving the file that, it’s safe to delete the Helper folder. To summarize the project structure should now look similar to this,

Xamarin

The Settings Class

Now open the Settings.cs file and then replace the default generated code with the following

  1. using Plugin.Settings;  
  2. using Plugin.Settings.Abstractions;  
  3. using System;  
  4.   
  5. namespace MemoryGame.App.Classes  
  6. {  
  7.     public static class Settings  
  8.     {  
  9.         private static ISettings AppSettings => CrossSettings.Current;  
  10.   
  11.         public static string PlayerFirstName  
  12.         {  
  13.             get => AppSettings.GetValueOrDefault(nameof(PlayerFirstName), string.Empty);  
  14.             set => AppSettings.AddOrUpdateValue(nameof(PlayerFirstName), value);  
  15.         }  
  16.   
  17.         public static string PlayerLastName  
  18.         {  
  19.             get => AppSettings.GetValueOrDefault(nameof(PlayerLastName), string.Empty);  
  20.             set => AppSettings.AddOrUpdateValue(nameof(PlayerLastName), value);  
  21.         }  
  22.   
  23.         public static string PlayerEmail  
  24.         {  
  25.             get => AppSettings.GetValueOrDefault(nameof(PlayerEmail), string.Empty);  
  26.             set => AppSettings.AddOrUpdateValue(nameof(PlayerEmail), value);  
  27.         }  
  28.   
  29.         public static int TopScore  
  30.         {  
  31.             get => AppSettings.GetValueOrDefault(nameof(TopScore), 1);  
  32.             set => AppSettings.AddOrUpdateValue(nameof(TopScore), value);  
  33.         }  
  34.   
  35.         public static DateTime DateAchieved  
  36.         {  
  37.             get => AppSettings.GetValueOrDefault(nameof(DateAchieved), DateTime.UtcNow);  
  38.             set => AppSettings.AddOrUpdateValue(nameof(DateAchieved), value);  
  39.         }  
  40.   
  41.         public static bool IsProfileSync  
  42.         {  
  43.             get => AppSettings.GetValueOrDefault(nameof(IsProfileSync), false);  
  44.             set => AppSettings.AddOrUpdateValue(nameof(IsProfileSync), value);  
  45.         }  
  46.   
  47.         public static int PlayerID  
  48.         {  
  49.             get => AppSettings.GetValueOrDefault(nameof(PlayerID), 0);  
  50.             set => AppSettings.AddOrUpdateValue(nameof(PlayerID), value);  
  51.         }  
  52.   
  53.     }  
  54. }  

If you notice from the code above, we have defined some basic properties that our app needs.  We are defining them in Settings.cs file so that we can access properties from shared code across all our applications, thus having a central location for shared properties.

The Helper Class

Let’s proceed by creating a new class called “Helper.cs” and then copy the following code below

  1. using Plugin.Connectivity;  
  2.   
  3. namespace MemoryGame.App.Helper  
  4. {  
  5.     public static class StringExtensions  
  6.     {  
  7.         public static int ToInteger(this string numberString)  
  8.         {  
  9.             int result = 0;  
  10.             if (int.TryParse(numberString, out result))  
  11.                 return result;  
  12.             return 0;  
  13.         }  
  14.     }  
  15.   
  16.   
  17.     public static class Utils  
  18.     {  
  19.         public static bool IsConnectedToInternet()  
  20.         {  
  21.             return CrossConnectivity.Current.IsConnected;  
  22.         }  
  23.     }  
  24. }  

The Helper.cs file is composed of two classes: StringExtension and Utils. The StringExtension class contains a ToIntenger() extension method to convert a valid numerical string value into an integer type. The Utils class on the other hand contains an IsConnectedToInternet() method to verify internet connectivity. We will be using these methods later in our application.

The PlayerManager Class

Now let’s create the class for managing the player data and score. Create a new class called “PlayerManager.cs” and then copy the following code below

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace MemoryGame.App.Classes  
  8. {  
  9.     #region API DTO's  
  10.     public class PlayerProfile  
  11.     {  
  12.         public string FirstName { get; set; }  
  13.         public string LastName { get; set; }  
  14.         public string Email { get; set; }  
  15.     }  
  16.   
  17.     public class PlayerScore  
  18.     {  
  19.         public int ChallengerID { get; set; }  
  20.         public byte Best { get; set; }  
  21.         public DateTime DateAchieved { get; set; }  
  22.     }  
  23.   
  24.     public class PlayerData  
  25.     {  
  26.         public string FirstName { get; set; }  
  27.         public string LastName { get; set; }  
  28.         public byte Best { get; set; }  
  29.         public DateTime DateAchieved { get; set; }  
  30.     }  
  31.  
  32.     #endregion  
  33.   
  34.     public static class PlayerManager  
  35.     {  
  36.         public static void Save(PlayerProfile player)  
  37.         {  
  38.             Settings.PlayerFirstName = player.FirstName;  
  39.             Settings.PlayerLastName = player.LastName;  
  40.             Settings.PlayerEmail = player.Email;  
  41.         }  
  42.   
  43.         public static PlayerProfile GetPlayerProfileFromLocal()  
  44.         {  
  45.             return new PlayerProfile  
  46.             {  
  47.                 FirstName = Settings.PlayerFirstName,  
  48.                 LastName = Settings.PlayerLastName,  
  49.                 Email = Settings.PlayerEmail  
  50.             };  
  51.         }  
  52.   
  53.         public static PlayerScore GetPlayerScoreFromLocal()  
  54.         {  
  55.             return new PlayerScore  
  56.             {  
  57.                 ChallengerID = Settings.PlayerID,  
  58.                 Best = Convert.ToByte(Settings.TopScore),  
  59.                 DateAchieved = Settings.DateAchieved  
  60.             };  
  61.         }  
  62.   
  63.         public static void UpdateBest(int score)  
  64.         {  
  65.             if (Settings.TopScore < score)  
  66.             {  
  67.                 Settings.TopScore = score;  
  68.                 Settings.DateAchieved = DateTime.UtcNow;  
  69.             }  
  70.         }  
  71.   
  72.         public static int GetBestScore(int currentLevel)  
  73.         {  
  74.             if (Settings.TopScore > currentLevel)  
  75.                 return Settings.TopScore;  
  76.             else  
  77.                 return currentLevel;  
  78.         }  
  79.   
  80.         public async static Task<bool> Sync()  
  81.         {  
  82.             REST.GameAPI api = new REST.GameAPI();  
  83.             bool result = false;  
  84.   
  85.             try  
  86.             {  
  87.                 if (!Settings.IsProfileSync)  
  88.                     result = await api.SavePlayerProfile(PlayerManager.GetPlayerProfileFromLocal(), true);  
  89.   
  90.                 if (Settings.PlayerID == 0)  
  91.                     Settings.PlayerID = await api.GetPlayerID(Settings.PlayerEmail);  
  92.   
  93.                 result = await api.SavePlayerScore(PlayerManager.GetPlayerScoreFromLocal());  
  94.   
  95.             }  
  96.             catch  
  97.             {  
  98.                 return result;  
  99.             }  
  100.   
  101.             return result;  
  102.         }  
  103.   
  104.         public async static Task<bool> CheckScoreAndSync(int score)  
  105.         {  
  106.             if (Settings.TopScore < score)  
  107.             {  
  108.                 UpdateBest(score);  
  109.                 if (Utils.IsConnectedToInternet())  
  110.                 {  
  111.                     var response = await Sync();  
  112.                     return response == true ? true : false;  
  113.                 }  
  114.                 else  
  115.                     return false;  
  116.             }  
  117.             else  
  118.                 return false;  
  119.         }  
  120.   
  121.         public async static Task<PlayerData> CheckExistingPlayer(string email)  
  122.         {  
  123.             REST.GameAPI api = new REST.GameAPI();  
  124.             PlayerData player = new PlayerData();  
  125.   
  126.             if (Utils.IsConnectedToInternet())  
  127.             {  
  128.                 player = await api.GetPlayerData(email);  
  129.             }  
  130.   
  131.             return player;  
  132.         }  
  133.     }  
  134. }  

The code above is composed of a few methods for handling CRUD and syncing data. Notice that each method calls the method defined in the GameAPI class. We did it like this so we can separate the actual code logic for the ease of maintenance and separation of concerns.

The Required XAML Pages

Now that we are all set, it’s time for us to create the following pages for the app:

  • Register
  • Home
  • Result

Let’s start building the Register page. Right click on the Pages folder and then select Add > New Item > Cross-Platform > Forms Xaml Page. Name the page as “Register” and then copy the following code below:

The Register Page
  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"  
  3.              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"  
  4.              x:Class="MemoryGame.App.Pages.Register">  
  5.   
  6.   <StackLayout VerticalOptions="CenterAndExpand">  
  7.     <Label Text="Working Memory Game"  
  8.           FontSize="30"  
  9.           HorizontalOptions="Center"  
  10.           VerticalOptions="CenterAndExpand" />  
  11.   
  12.     <Label x:Name="lblWelcome" Text="Register to start the fun, or Log-on to continue the challenge!"  
  13.           FontSize="20"  
  14.           HorizontalOptions="Center"  
  15.           VerticalOptions="CenterAndExpand" />  
  16.     <StackLayout x:Name="layoutChoose" Orientation="Horizontal" Spacing="5" VerticalOptions="CenterAndExpand" HorizontalOptions="Center">  
  17.       <Button x:Name="btnNew"  
  18.            Text="Register"  
  19.            FontSize="20"  
  20.            HorizontalOptions="Center"  
  21.            VerticalOptions="CenterAndExpand"  
  22.            Clicked="OnbtnNewClicked"/>  
  23.       <Button x:Name="btnReturn"  
  24.            Text="Log-on"  
  25.            FontSize="20"  
  26.            HorizontalOptions="Center"  
  27.            VerticalOptions="CenterAndExpand"  
  28.            Clicked="OnbtnReturnClicked"/>  
  29.     </StackLayout>  
  30.   
  31.     <StackLayout x:Name="layoutRegister" VerticalOptions="CenterAndExpand" IsVisible="False">  
  32.       <Label Text="First Name" />  
  33.       <Entry  x:Name="entryFirstName" />  
  34.       <Label Text="Last Name" />  
  35.       <Entry  x:Name="entryLastName" />  
  36.       <Label Text="Email" />  
  37.       <Entry  x:Name="entryEmail" />  
  38.   
  39.       <StackLayout  Orientation="Horizontal" Spacing="3" HorizontalOptions="Center">  
  40.         <Button x:Name="btnRegister"  
  41.                Text="Let's Do This!"  
  42.                HorizontalOptions="Center"  
  43.                VerticalOptions="CenterAndExpand"  
  44.                Clicked="OnbtnRegisterClicked"/>  
  45.         <Button x:Name="btnCancelRegister"  
  46.                Text="Cancel"  
  47.                HorizontalOptions="Center"  
  48.                VerticalOptions="CenterAndExpand"  
  49.                Clicked="OnbtnCancelRegisterClicked"/>  
  50.       </StackLayout>  
  51.     </StackLayout>  
  52.   
  53.     <StackLayout x:Name="layoutLogin" VerticalOptions="CenterAndExpand" IsVisible="False">  
  54.       <Label Text="Email" />  
  55.       <Entry  x:Name="entryExistingEmail" />  
  56.   
  57.       <StackLayout  Orientation="Horizontal" Spacing="3" HorizontalOptions="Center">  
  58.         <Button x:Name="btnLogin"  
  59.                Text="Let me in!"  
  60.                HorizontalOptions="Center"  
  61.                VerticalOptions="CenterAndExpand"  
  62.                Clicked="OnbtnLoginClicked"/>  
  63.         <Button x:Name="btnCancelLogin"  
  64.                Text="Cancel"  
  65.                HorizontalOptions="Center"  
  66.                VerticalOptions="CenterAndExpand"  
  67.                Clicked="OnbtnCancelLoginClicked"/>  
  68.       </StackLayout>  
  69.     </StackLayout>  
  70.   </StackLayout>  
  71. </ContentPage>  

Here’s the corresponding code behind for the Register page

  1. using System;  
  2. using Xamarin.Forms;  
  3. using MemoryGame.App.Classes;  
  4. using System.Threading.Tasks;  
  5. using MemoryGame.App.Helper;  
  6.   
  7. namespace MemoryGame.App.Pages  
  8. {  
  9.     public partial class Register : ContentPage  
  10.     {  
  11.         public Register()  
  12.         {  
  13.             InitializeComponent();  
  14.         }  
  15.   
  16.         enum EntryOption  
  17.         {  
  18.             Register = 0,  
  19.             Returning = 1,  
  20.             Cancel = 2  
  21.         }  
  22.   
  23.         protected override void OnAppearing()  
  24.         {  
  25.             base.OnAppearing();  
  26.             NavigationPage.SetHasBackButton(thisfalse);  
  27.         }  
  28.         async void CheckExistingProfileAndSave(string email)  
  29.         {  
  30.             if (Utils.IsConnectedToInternet())  
  31.             {  
  32.                 try  
  33.                 {  
  34.                     PlayerData player = await PlayerManager.CheckExistingPlayer(email);  
  35.                     if (string.IsNullOrEmpty(player.FirstName) && string.IsNullOrEmpty(player.LastName))  
  36.                     {  
  37.                         await App.Current.MainPage.DisplayAlert("Error""Email does not exist.""OK");  
  38.                     }  
  39.                     else  
  40.                     {  
  41.                         Settings.PlayerFirstName = player.FirstName.Trim();  
  42.                         Settings.PlayerLastName = player.LastName.Trim();  
  43.                         Settings.PlayerEmail = email.Trim();  
  44.                         Settings.TopScore = player.Best;  
  45.                         Settings.DateAchieved = player.DateAchieved;  
  46.   
  47.                         await App._navPage.PopAsync();  
  48.                     }  
  49.                 }  
  50.                 catch  
  51.                 {  
  52.                     await App.Current.MainPage.DisplayAlert("Oops""An error occured while connecting to the server. Please check your connection.""OK");  
  53.                 }  
  54.             }  
  55.             else  
  56.             {  
  57.                 await App.Current.MainPage.DisplayAlert("Error""No internet connection.""OK");  
  58.             }  
  59.   
  60.             btnLogin.IsEnabled = true;  
  61.         }  
  62.   
  63.         void Save()  
  64.         {  
  65.             Settings.PlayerFirstName = entryFirstName.Text.Trim();  
  66.             Settings.PlayerLastName = entryLastName.Text.Trim();  
  67.             Settings.PlayerEmail = entryEmail.Text.Trim();  
  68.             App._navPage.PopAsync();  
  69.         }  
  70.   
  71.         void ToggleEntryView(EntryOption option)  
  72.         {  
  73.             switch (option)  
  74.             {  
  75.                 case EntryOption.Register:  
  76.                     {  
  77.                         lblWelcome.IsVisible = false;  
  78.                         layoutChoose.IsVisible = false;  
  79.                         layoutLogin.IsVisible = false;  
  80.                         layoutRegister.IsVisible = true;  
  81.                         break;  
  82.                     }  
  83.                 case EntryOption.Returning:  
  84.                     {  
  85.                         lblWelcome.IsVisible = false;  
  86.                         layoutChoose.IsVisible = false;  
  87.                         layoutRegister.IsVisible = false;  
  88.                         layoutLogin.IsVisible = true;  
  89.                         break;  
  90.                     }  
  91.                 case EntryOption.Cancel:  
  92.                     {  
  93.                         lblWelcome.IsVisible = true;  
  94.                         layoutChoose.IsVisible = true;  
  95.                         layoutRegister.IsVisible = false;  
  96.                         layoutLogin.IsVisible = false;  
  97.                         break;  
  98.                     }  
  99.             }  
  100.         }  
  101.         void OnbtnNewClicked(object sender, EventArgs args)  
  102.         {  
  103.             ToggleEntryView(EntryOption.Register);  
  104.         }  
  105.         void OnbtnReturnClicked(object sender, EventArgs args)  
  106.         {  
  107.             ToggleEntryView(EntryOption.Returning);  
  108.         }  
  109.         void OnbtnCancelLoginClicked(object sender, EventArgs args)  
  110.         {  
  111.             ToggleEntryView(EntryOption.Cancel);  
  112.         }  
  113.         void OnbtnCancelRegisterClicked(object sender, EventArgs args)  
  114.         {  
  115.             ToggleEntryView(EntryOption.Cancel);  
  116.         }  
  117.   
  118.         void OnbtnRegisterClicked(object sender, EventArgs args)  
  119.         {  
  120.             if (string.IsNullOrEmpty(entryFirstName.Text) || string.IsNullOrEmpty(entryLastName.Text) || string.IsNullOrEmpty(entryEmail.Text))  
  121.                 App.Current.MainPage.DisplayAlert("Error""Please supply the required fields.""Got it");  
  122.             else  
  123.                 Save();  
  124.         }  
  125.   
  126.         void OnbtnLoginClicked(object sender, EventArgs args)  
  127.         {  
  128.             if (string.IsNullOrEmpty(entryExistingEmail.Text))  
  129.                 App.Current.MainPage.DisplayAlert("Error""Please supply your email.""Got it");  
  130.             else  
  131.             {  
  132.                 btnLogin.IsEnabled = false;  
  133.                 CheckExistingProfileAndSave(entryExistingEmail.Text);  
  134.             }  
  135.         }  
  136.     }  
  137. }  

Let’s continue by building the Home page. Right click on the Pages folder and then select Add > New Item > Cross-Platform > Forms Xaml Page. Name the page as “Home” and then copy the following code below:

The Home Page
  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"  
  3.              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"  
  4.              x:Class="MemoryGame.App.Pages.Home">  
  5.   
  6.   <StackLayout Padding="10">  
  7.     <StackLayout>  
  8.       <StackLayout Orientation="Horizontal">  
  9.         <Label x:Name="lblBest" FontSize="20" HorizontalOptions="StartAndExpand" />  
  10.         <Button x:Name="btnSync" Text="Sync" Clicked="OnbtnSyncClicked" HorizontalOptions="EndAndExpand" VerticalOptions="CenterAndExpand" />  
  11.       </StackLayout>  
  12.   
  13.       <Label x:Name="lblTime"  
  14.            FontSize="30"  
  15.            HorizontalOptions="Center"  
  16.            VerticalOptions="CenterAndExpand" />  
  17.     </StackLayout>  
  18.   
  19.     <Label x:Name="lblLevel"  
  20.            FontSize="30"  
  21.            HorizontalOptions="Center"  
  22.            VerticalOptions="CenterAndExpand" />  
  23.     <StackLayout Orientation="Horizontal" Spacing="3" HorizontalOptions="Center" BackgroundColor="White">  
  24.       <Image x:Name="imgLightOff" Source="lightoff.png" WidthRequest="100" HeightRequest="40" />  
  25.       <Image x:Name="imgLightOff2" Source="lightoff.png" IsVisible="False" WidthRequest="100" HeightRequest="40" />  
  26.       <Image x:Name="imgLightOn" Source="lighton.png" IsVisible="False" WidthRequest="100" HeightRequest="40" />  
  27.       <Image x:Name="imgSpeaker" Source="speakeron.png" WidthRequest="100" HeightRequest="40" />  
  28.       <Image x:Name="imgHaptic" Source="vibration.png" WidthRequest="100" HeightRequest="40" />  
  29.     </StackLayout>  
  30.     <Label Text="The light will blink on, the speaker will beep and the device will vibrate at different times. Try to count how many times each one happens."  
  31.            HorizontalOptions="Center"  
  32.            VerticalOptions="CenterAndExpand" />  
  33.   
  34.     <Button x:Name="btnStart"  
  35.             Text="Start"  
  36.             HorizontalOptions="Center"  
  37.             VerticalOptions="CenterAndExpand"  
  38.             Clicked="OnButtonClicked"/>  
  39.   </StackLayout>  
  40.   
  41. </ContentPage>  

Here’s the corresponding code behind that you will see in Home.xaml.cs

  1. using System;  
  2. using System.Threading.Tasks;  
  3. using MemoryGame.App.Classes;  
  4. using Xamarin.Forms;  
  5. using MemoryGame.App.Services;  
  6. using MemoryGame.App.Helper;  
  7.   
  8. namespace MemoryGame.App.Pages  
  9. {  
  10.     public partial class Home : ContentPage  
  11.     {  
  12.         private static int _blinkCount = 0;  
  13.         private static int _soundCount = 0;  
  14.         private static int _hapticCount = 0;  
  15.         private static int _level = 1;  
  16.   
  17.         public int _cycleStartInMS = 0;  
  18.         public int _cycleMaxInMS = 7000; // 7 seconds  
  19.         private const int CycleIntervalInMS = 2000; // 2 seconds  
  20.         private const int PlayTimeCount = 3; // 3 types default  
  21.   
  22.         enum PlayType  
  23.         {  
  24.             Blink = 0,  
  25.             Sound = 1,  
  26.             Haptic = 2  
  27.         }  
  28.   
  29.         public static int CurrentGameBlinkCount  
  30.         {  
  31.             get { return _blinkCount; }  
  32.         }  
  33.   
  34.         public static int CurrentGameSoundCount  
  35.         {  
  36.             get { return _soundCount; }  
  37.         }  
  38.   
  39.         public static int CurrentGameHapticCount  
  40.         {  
  41.             get { return _hapticCount; }  
  42.         }  
  43.   
  44.         public static int CurrentGameLevel  
  45.         {  
  46.             get { return _level; }  
  47.         }  
  48.   
  49.         public Home()  
  50.         {  
  51.             InitializeComponent();  
  52.         }  
  53.   
  54.         protected async override void OnAppearing()  
  55.         {  
  56.             base.OnAppearing();  
  57.   
  58.             if (string.IsNullOrEmpty(Settings.PlayerFirstName))  
  59.                 await App._navPage.PushAsync(App._registerPage);  
  60.             else  
  61.             {  
  62.   
  63.                 PlayerManager.UpdateBest(_level);  
  64.   
  65.                 if (Result._answered)  
  66.                     LevelUp();  
  67.                 else  
  68.                     ResetLevel();  
  69.   
  70.                 lblBest.Text = $"Best: Level {PlayerManager.GetBestScore(_level)}";  
  71.                 lblLevel.Text = $"Level {_level}";  
  72.             }  
  73.         }  
  74.   
  75.         static void IncrementPlayCount(PlayType play)  
  76.         {  
  77.             switch (play)  
  78.             {  
  79.                 case PlayType.Blink:  
  80.                     {  
  81.                         _blinkCount++;  
  82.                         break;  
  83.                     }  
  84.                 case PlayType.Sound:  
  85.                     {  
  86.                         _soundCount++;  
  87.                         break;  
  88.                     }  
  89.                 case PlayType.Haptic:  
  90.                     {  
  91.                         _hapticCount++;  
  92.                         break;  
  93.                     }  
  94.             }  
  95.         }  
  96.   
  97.         public static void IncrementGameLevel()  
  98.         {  
  99.             _level++;  
  100.         }  
  101.   
  102.         void ResetLevel()  
  103.         {  
  104.             _level = 1;  
  105.             _cycleStartInMS = CycleIntervalInMS;  
  106.             lblTime.Text = string.Empty;  
  107.         }  
  108.   
  109.         async void StartRandomPlay()  
  110.         {  
  111.             await Task.Run(() =>  
  112.             {  
  113.   
  114.                 Random rnd = new Random(Guid.NewGuid().GetHashCode());  
  115.                 int choice = rnd.Next(0, PlayTimeCount);  
  116.   
  117.                 switch (choice)  
  118.                 {  
  119.                     case (int)PlayType.Blink:  
  120.                         {  
  121.                             Device.BeginInvokeOnMainThread(async () =>  
  122.                             {  
  123.   
  124.                                 await imgLightOff.FadeTo(0, 200);  
  125.                                 imgLightOff2.IsVisible = false;  
  126.                                 imgLightOff.IsVisible = true;  
  127.                                 imgLightOff.Source = ImageSource.FromFile("lighton.png");  
  128.                                 await imgLightOff.FadeTo(1, 200);  
  129.   
  130.                             });  
  131.                             IncrementPlayCount(PlayType.Blink);  
  132.                             break;  
  133.                         }  
  134.                     case (int)PlayType.Sound:  
  135.                         {  
  136.                             DependencyService.Get<ISound>().PlayMp3File("beep.mp3");  
  137.                             IncrementPlayCount(PlayType.Sound);  
  138.                             break;  
  139.                         }  
  140.                     case (int)PlayType.Haptic:  
  141.                         {  
  142.                             DependencyService.Get<IHaptic>().ActivateHaptic();  
  143.                             IncrementPlayCount(PlayType.Haptic);  
  144.                             break;  
  145.                         }  
  146.                 }  
  147.             });  
  148.         }  
  149.   
  150.         void ResetGameCount()  
  151.         {  
  152.             _blinkCount = 0;  
  153.             _soundCount = 0;  
  154.             _hapticCount = 0;  
  155.         }  
  156.   
  157.         void LevelUp()  
  158.         {  
  159.             _cycleStartInMS = _cycleStartInMS - 200; //minus 200 ms  
  160.         }  
  161.   
  162.         void Play()  
  163.         {  
  164.             int timeLapsed = 0;  
  165.             int duration = 0;  
  166.             Device.StartTimer(TimeSpan.FromSeconds(1), () =>  
  167.             {  
  168.                 duration++;  
  169.                 lblTime.Text = $"Timer: { TimeSpan.FromSeconds(duration).ToString("ss")}";  
  170.   
  171.                 if (duration < 7)  
  172.                     return true;  
  173.                 else  
  174.                     return false;  
  175.             });  
  176.   
  177.             Device.StartTimer(TimeSpan.FromMilliseconds(_cycleStartInMS), () => {  
  178.                 timeLapsed = timeLapsed + _cycleStartInMS;  
  179.   
  180.                 Device.BeginInvokeOnMainThread(async () =>  
  181.                 {  
  182.                     imgLightOff2.IsVisible = true;  
  183.                     imgLightOff.IsVisible = false;  
  184.                     await Task.Delay(200);  
  185.   
  186.                 });  
  187.   
  188.                 if (timeLapsed <= _cycleMaxInMS)  
  189.                 {  
  190.                     StartRandomPlay();  
  191.                     return true//continue  
  192.                 }  
  193.   
  194.                 btnStart.Text = "Start";  
  195.                 btnStart.IsEnabled = true;  
  196.   
  197.                 App._navPage.PushAsync(App._resultPage);  
  198.                 return false//not continue  
  199.             });  
  200.         }  
  201.   
  202.         void OnButtonClicked(object sender, EventArgs args)  
  203.         {  
  204.             btnStart.Text = "Game Started...";  
  205.             btnStart.IsEnabled = false;  
  206.   
  207.             ResetGameCount();  
  208.             Play();  
  209.         }  
  210.   
  211.         async void OnbtnSyncClicked(object sender, EventArgs args)  
  212.         {  
  213.   
  214.             if (Utils.IsConnectedToInternet())  
  215.             {  
  216.                 btnSync.Text = "Syncing...";  
  217.                 btnSync.IsEnabled = false;  
  218.                 btnStart.IsEnabled = false;  
  219.   
  220.                 var response = await PlayerManager.Sync();  
  221.                 if (!response)  
  222.                     await App.Current.MainPage.DisplayAlert("Oops""An error occured while connecting to the server. Please check your connection.""OK");  
  223.                 else  
  224.                     await App.Current.MainPage.DisplayAlert("Sync""Data synced!""OK");  
  225.   
  226.                 btnSync.Text = "Sync";  
  227.                 btnSync.IsEnabled = true;  
  228.                 btnStart.IsEnabled = true;  
  229.             }  
  230.             else  
  231.             {  
  232.                 await App.Current.MainPage.DisplayAlert("Error""No internet connection.""OK");  
  233.             }  
  234.         }  
  235.     }  
  236. }  

The StartRandomPlay() method above is the core method of the class above. The method is responsible for playing different criteria on random, whether invoke a sound, vibration or just blink an image. Notice that we’ve used the DependencyService class to inject the Interface that we’ve defined in the previous steps. These allow us to call platform specific methods for playing a sound or vibration.

Now right click on the Pages folder and then select Add > New Item > Cross-Platform > Forms Xaml Page. Name the page as “Result” and then copy the following code below:

The Result Page
  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"  
  3.              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"  
  4.              x:Class="MemoryGame.App.Pages.Result">  
  5.   
  6.   <StackLayout>  
  7.     <Label Text="How many times did the light blink, the speaker beep and the device vibrate?"  
  8.             HorizontalOptions="Center"  
  9.             VerticalOptions="CenterAndExpand" />  
  10.   
  11.     <StackLayout Orientation="Horizontal" Spacing="3" HorizontalOptions="Center" BackgroundColor="White">  
  12.       <Image x:Name="imgLight" Source="lightoff.png" WidthRequest="100" HeightRequest="40" />  
  13.       <Image x:Name="imgSpeaker" Source="speakeron.png" WidthRequest="100" HeightRequest="40" />  
  14.       <Image x:Name="imgHaptic" Source="vibration.png" WidthRequest="100" HeightRequest="40" />  
  15.     </StackLayout>  
  16.   
  17.     <StackLayout Orientation="Horizontal" HorizontalOptions="Center" Spacing="5">  
  18.       <Picker x:Name="pickerLight" HorizontalOptions="FillAndExpand" WidthRequest="100">  
  19.         <Picker.Items>  
  20.           <x:String>0</x:String>  
  21.           <x:String>1</x:String>  
  22.           <x:String>2</x:String>  
  23.           <x:String>3</x:String>  
  24.           <x:String>4</x:String>  
  25.           <x:String>5</x:String>  
  26.           <x:String>6</x:String>  
  27.           <x:String>7</x:String>  
  28.           <x:String>8</x:String>  
  29.           <x:String>9</x:String>  
  30.           <x:String>10</x:String>  
  31.         </Picker.Items>  
  32.       </Picker>  
  33.       <Picker x:Name="pickerSpeaker" HorizontalOptions="FillAndExpand" WidthRequest="100">  
  34.         <Picker.Items>  
  35.           <x:String>0</x:String>  
  36.           <x:String>1</x:String>  
  37.           <x:String>2</x:String>  
  38.           <x:String>3</x:String>  
  39.           <x:String>4</x:String>  
  40.           <x:String>5</x:String>  
  41.           <x:String>6</x:String>  
  42.           <x:String>7</x:String>  
  43.           <x:String>8</x:String>  
  44.           <x:String>9</x:String>  
  45.           <x:String>10</x:String>  
  46.         </Picker.Items>  
  47.       </Picker>  
  48.       <Picker x:Name="pickerHaptic" HorizontalOptions="FillAndExpand" WidthRequest="100">  
  49.         <Picker.Items>  
  50.           <x:String>0</x:String>  
  51.           <x:String>1</x:String>  
  52.           <x:String>2</x:String>  
  53.           <x:String>3</x:String>  
  54.           <x:String>4</x:String>  
  55.           <x:String>5</x:String>  
  56.           <x:String>6</x:String>  
  57.           <x:String>7</x:String>  
  58.           <x:String>8</x:String>  
  59.           <x:String>9</x:String>  
  60.           <x:String>10</x:String>  
  61.         </Picker.Items>  
  62.       </Picker>  
  63.     </StackLayout>  
  64.     <Label x:Name="lblText" FontSize="20"  
  65.           HorizontalOptions="Center"  
  66.           VerticalOptions="CenterAndExpand" />  
  67.     <StackLayout Orientation="Horizontal" HorizontalOptions="Center" Spacing="40">  
  68.       <Label x:Name="lblBlinkCount"  
  69.           HorizontalOptions="Center"  
  70.           VerticalOptions="CenterAndExpand" />  
  71.       <Label x:Name="lblBeepCount"  
  72.           HorizontalOptions="Center"  
  73.           VerticalOptions="CenterAndExpand" />  
  74.       <Label x:Name="lblHapticCount"  
  75.           HorizontalOptions="Center"  
  76.           VerticalOptions="CenterAndExpand" />  
  77.     </StackLayout>  
  78.     <Button x:Name="btnSubmit"  
  79.             Text="Submit"  
  80.             HorizontalOptions="Center"  
  81.             VerticalOptions="CenterAndExpand"  
  82.             Clicked="OnButtonClicked"/>  
  83.     <Button x:Name="btnRetry"  
  84.            Text="Retry"  
  85.            IsVisible="False"  
  86.            HorizontalOptions="Center"  
  87.            VerticalOptions="CenterAndExpand"  
  88.            Clicked="OnRetryButtonClicked"/>  
  89.   </StackLayout>  
  90.   
  91. </ContentPage>  

Here’s the corresponding code behind

  1. using System;  
  2. using Xamarin.Forms;  
  3. using MemoryGame.App.Classes;  
  4.   
  5. namespace MemoryGame.App.Pages  
  6. {  
  7.     public partial class Result : ContentPage  
  8.     {  
  9.         public static bool _answered = false;  
  10.         public Result()  
  11.         {  
  12.             InitializeComponent();  
  13.             ClearResult();  
  14.   
  15.         }  
  16.   
  17.         protected override void OnAppearing()  
  18.         {  
  19.             base.OnAppearing();  
  20.             ClearResult();  
  21.             NavigationPage.SetHasBackButton(thisfalse);  
  22.         }  
  23.         void ClearResult()  
  24.         {  
  25.             lblText.Text = string.Empty;  
  26.             lblBlinkCount.Text = string.Empty;  
  27.             lblBeepCount.Text = string.Empty;  
  28.             lblHapticCount.Text = string.Empty;  
  29.             pickerLight.SelectedIndex = 0;  
  30.             pickerSpeaker.SelectedIndex = 0;  
  31.             pickerHaptic.SelectedIndex = 0;  
  32.             btnSubmit.IsVisible = true;  
  33.             btnRetry.IsVisible = false;  
  34.             _answered = false;  
  35.         }  
  36.   
  37.         bool CheckAnswer(int actualAnswer, int selectedAnswer)  
  38.         {  
  39.             if (selectedAnswer == actualAnswer)  
  40.                 return true;  
  41.             else  
  42.                 return false;  
  43.         }  
  44.   
  45.         void Retry()  
  46.         {  
  47.             btnSubmit.IsVisible = false;  
  48.             btnRetry.IsVisible = true;  
  49.         }  
  50.         async void OnButtonClicked(object sender, EventArgs args)  
  51.         {  
  52.             if (pickerLight.SelectedIndex >= 0 && pickerSpeaker.SelectedIndex >= 0 && pickerHaptic.SelectedIndex >= 0)  
  53.             {  
  54.                 lblText.Text = "The actual answers are:";  
  55.                 lblBlinkCount.Text = Home.CurrentGameBlinkCount.ToString();  
  56.                 lblBeepCount.Text = Home.CurrentGameSoundCount.ToString();  
  57.                 lblHapticCount.Text = Home.CurrentGameHapticCount.ToString();  
  58.   
  59.                 if (CheckAnswer(Home.CurrentGameBlinkCount, Convert.ToInt32(pickerLight.Items[pickerLight.SelectedIndex])))  
  60.                     if (CheckAnswer(Home.CurrentGameSoundCount, Convert.ToInt32(pickerSpeaker.Items[pickerSpeaker.SelectedIndex])))  
  61.                         if (CheckAnswer(Home.CurrentGameHapticCount, Convert.ToInt32(pickerHaptic.Items[pickerHaptic.SelectedIndex])))  
  62.                         {  
  63.                             _answered = true;  
  64.                             Home.IncrementGameLevel();  
  65.   
  66.                             var isSynced = PlayerManager.CheckScoreAndSync(Home.CurrentGameLevel);  
  67.   
  68.                             var answer = await App.Current.MainPage.DisplayAlert("Congrats!", $"You've got it all right and made it to level {Home.CurrentGameLevel}. Continue?""Yes""No");  
  69.   
  70.                             if (answer)  
  71.                                 await App._navPage.PopAsync();  
  72.                             else  
  73.                                 Retry();  
  74.                         }  
  75.   
  76.                 if (!_answered)  
  77.                 {  
  78.                     var isSynced = PlayerManager.CheckScoreAndSync(Home.CurrentGameLevel);  
  79.   
  80.                     var answer = await App.Current.MainPage.DisplayAlert("Game Over!", $"Your current best is at level {Home.CurrentGameLevel}. Retry?""Yes""No");  
  81.                     if (answer)  
  82.                         await App._navPage.PopAsync();  
  83.                     else  
  84.                         Retry();  
  85.                 }  
  86.             }  
  87.         }  
  88.   
  89.         void OnRetryButtonClicked(object sender, EventArgs args)  
  90.         {  
  91.             App._navPage.PopAsync();  
  92.         }  
  93.     }  
  94. }  

The code above handles the logic for validating your answers against the actual count of each play type occurred. If all your answers are correct then it will get you the next level, otherwise it resets back.

Implementing Haptic and Sound Service iOS and Android Platform

Now it’s time for us to implement an actual implementation for each interface that we have defined in the previous steps. Let’s start with Android. Add a new folder called “Services” in the MemoryGame.App.Droid project and then copy the following code for each class:

The Haptic Service (Droid)
  1. using Android.Content;  
  2. using Android.OS;  
  3. using Xamarin.Forms;  
  4. using MemoryGame.App.Droid.Services;  
  5. using MemoryGame.App.Services;  
  6.   
  7. [assembly: Dependency(typeof(HapticService))]  
  8. namespace MemoryGame.App.Droid.Services  
  9. {  
  10.     public class HapticService : IHaptic  
  11.     {  
  12.         public HapticService() { }  
  13.         public void ActivateHaptic()  
  14.         {  
  15.             Vibrator vibrator = (Vibrator)global::Android.App.Application.Context.GetSystemService(Context.VibratorService);  
  16.             vibrator.Vibrate(100);  
  17.         }  
  18.     }  
  19. }  
The Sound Service (Droid)
  1. using Xamarin.Forms;  
  2. using Android.Media;  
  3. using MemoryGame.App.Droid.Services;  
  4. using MemoryGame.App.Services;  
  5.   
  6. [assembly: Dependency(typeof(SoundService))]  
  7.   
  8. namespace MemoryGame.App.Droid.Services  
  9. {  
  10.     public class SoundService : ISound  
  11.     {  
  12.         public SoundService() { }  
  13.   
  14.         private MediaPlayer _mediaPlayer;  
  15.   
  16.         public bool PlayMp3File(string fileName)  
  17.         {  
  18.             _mediaPlayer = new MediaPlayer();  
  19.             var fd = global::Android.App.Application.Context.Assets.OpenFd(fileName);  
  20.             _mediaPlayer.Prepared += (s, e) =>  
  21.             {  
  22.                 _mediaPlayer.Start();  
  23.             };  
  24.             _mediaPlayer.SetDataSource(fd.FileDescriptor, fd.StartOffset, fd.Length);  
  25.             _mediaPlayer.Prepare();  
  26.             return true;  
  27.         }  
  28.   
  29.         public bool PlayWavFile(string fileName)  
  30.         {  
  31.             //TO DO: Own implementation here  
  32.             return true;  
  33.         }  
  34.     }  
  35. }  

Note

For Android add the required images under the “drawable” folder and add the sound file under the “raw” folder.

Now, do the same for the MemoryGame.App.iOS project. Copy the following code below for each service,

The Haptic Service (iOS)
  1. using Xamarin.Forms;  
  2. using AudioToolbox;  
  3. using MemoryGame.App.iOS.Services;  
  4. using MemoryGame.App.Services;  
  5.   
  6. [assembly: Dependency(typeof(HapticService))]  
  7. namespace MemoryGame.App.iOS.Services  
  8. {  
  9.     public class HapticService : IHaptic  
  10.     {  
  11.         public HapticService() { }  
  12.         public void ActivateHaptic()  
  13.         {  
  14.             SystemSound.Vibrate.PlaySystemSound();  
  15.         }  
  16.     }  
  17. }  
The Sound Service (iOS)
  1. using Xamarin.Forms;  
  2. using MemoryGame.App.iOS.Services;  
  3. using System.IO;  
  4. using Foundation;  
  5. using AVFoundation;  
  6. using MemoryGame.App.Services;  
  7.   
  8. [assembly: Dependency(typeof(SoundService))]  
  9.   
  10. namespace MemoryGame.App.iOS.Services  
  11. {  
  12.     public class SoundService : NSObject, ISound, IAVAudioPlayerDelegate  
  13.     {  
  14.         #region IDisposable implementation  
  15.   
  16.         public void Dispose()  
  17.         {  
  18.   
  19.         }  
  20.  
  21.         #endregion  
  22.   
  23.         public SoundService()  
  24.         {  
  25.         }  
  26.   
  27.         public bool PlayWavFile(string fileName)  
  28.         {  
  29.             return true;  
  30.         }  
  31.   
  32.         public bool PlayMp3File(string fileName)  
  33.         {  
  34.   
  35.             var played = false;  
  36.   
  37.             NSError error = null;  
  38.             AVAudioSession.SharedInstance().SetCategory(AVAudioSession.CategoryPlayback, out error);  
  39.   
  40.             string sFilePath = NSBundle.MainBundle.PathForResource(Path.GetFileNameWithoutExtension(fileName), "mp3");  
  41.             var url = NSUrl.FromString(sFilePath);  
  42.             var _player = AVAudioPlayer.FromUrl(url);  
  43.             _player.Delegate = this;  
  44.             _player.Volume = 100f;  
  45.             played = _player.PrepareToPlay();  
  46.             _player.FinishedPlaying += (object sender, AVStatusEventArgs e) => {  
  47.                 _player = null;  
  48.             };  
  49.             played = _player.Play();  
  50.   
  51.             return played;  
  52.         }  
  53.     }  
  54.   
  55. }  

Note

For IOS add the required images and sound file under the Resource folder.

Setting Permissions

For android, add the following configuration under AndroidManifest.xml 

  1. <uses-permission android:name="android.permission.VIBRATE" />  
  2. <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  
  3. <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />  
  4. <uses-permission android:name="android.permission.INTERNET" />  
The Output

Here’s an actual screenshot of the output when deploying and running the app in real device,

Xamarin

Xamarin

Building a Simple Real-time Leaderboard Web App with ASP.NET SignalR and MVC

Create a new ASP.NET Web Application project and name it as “MemoryGame.Web” just like in the figure below,

Xamarin

Click “Ok” and then select the “ Empty MVC” template in the next wizard and then hit “OK” again to generate the project for you.

Integrating ASP.NET SignalR

Install “Microsoft.Asp.Net.SignalR” in your project via NuGet. The version used for this project is “2.2”. Once installed, you should be able to see them added under the references folder,

Xamarin

Adding a Middleware for SignalR

Create a new class and name it as “Startup.cs”. Copy the following code below,

  1. using Microsoft.Owin;  
  2. using Owin;  
  3.   
  4. [assembly: OwinStartup(typeof(MemoryGame.Web.Startup))]  
  5. namespace MemoryGame.Web  
  6. {  
  7.     public class Startup  
  8.     {  
  9.         public void Configuration(IAppBuilder app)  
  10.         {  
  11.             app.MapSignalR();  
  12.         }  
  13.     }  
  14. }  

The configuration above will add the SignalR service to the pipeline and enable us to use ASP.NET SignalR in our application.

Adding a Hub

Next is to add an ASP.NET SignalR Hub. Add a new class and then copy the following code below, 

  1. using Microsoft.AspNet.SignalR;  
  2.   
  3. namespace MemoryGame.Web  
  4. {  
  5.     public class LeaderboardHub : Hub  
  6.     {  
  7.         public static void Show()  
  8.         {  
  9.             IHubContext context = GlobalHost.ConnectionManager.GetHubContext<LeaderboardHub>();  
  10.             context.Clients.All.displayLeaderBoard();  
  11.         }  
  12.     }  
  13. }  

To provide you a quick overview, the Hub is the center piece of the SignalR. Similar to the Controller in ASP.NET MVC, a Hub is responsible for receiving input and generating the output to the client. The methods within the Hub can be invoked from the server or from the client.

Adding an MVC Controller

Now add a new “Empty MVC 5 Controller” class within the “Controllers” folder and then copy the following code below,

  1. using System.Web.Mvc;  
  2.   
  3. namespace MemoryGame.Web.Controllers  
  4. {  
  5.     public class HomeController : Controller  
  6.     {  
  7.         public ActionResult Index()  
  8.         {  
  9.             return View();  
  10.         }  
  11.     }  
  12. }  

The code above is just an action method that throws an Index View.

Adding a View

Add a new Index view within the “Views/Home” folder and then copy the following code below,

  1. <div id="body">  
  2.     <section class="featured">  
  3.         <div class="content-wrapper">  
  4.             <hgroup class="title">  
  5.                 <strong>Leader Board</strong>  
  6.             </hgroup>  
  7.         </div>  
  8.     </section>  
  9.     <section class="content-wrapper main-content clear-fix">  
  10.         <strong>  
  11.             <span>  
  12.                 Top Challengers  
  13.                 <img src="Images/goals_256.png" style="width:40px; height:60px;" />  
  14.             </span>  
  15.         </strong>  
  16.         <table id="tblRank" class="table table-striped table-condensed table-hover">  
  17.         </table>  
  18.     </section>  
  19. </div>  
  20.   
  21. @section scripts{  
  22.     @Scripts.Render("~/Scripts/jquery.signalR-2.2.2.min.js")  
  23.     @Scripts.Render("~/signalr/hubs")  
  24.   
  25.     <script type="text/javascript">  
  26.         $(function () {  
  27.             var rank = $.connection.leaderboardHub;  
  28.             rank.client.displayLeaderBoard = function () {  
  29.                 LoadResult();  
  30.             };  
  31.   
  32.             $.connection.hub.start();  
  33.             LoadResult();  
  34.         });  
  35.   
  36.         function LoadResult() {  
  37.             var $tbl = $("#tblRank");  
  38.             $.ajax({  
  39.                 url: 'http://localhost:61309/api/game/get',  
  40.                 type: 'GET',  
  41.                 datatype: 'json',  
  42.                 success: function (data) {  
  43.                     if (data.length > 0) {  
  44.                         $tbl.empty();  
  45.                         $tbl.append('<thead><tr><th>Rank</th><th></th><th></th><th>Best</th><th>Achieved</th></tr></thead>');  
  46.                         var rows = [];  
  47.                         for (var i = 0; i < data.length; i++) {  
  48.                             rows.push('<tbody><tr><td>' + (i +1).toString() + '</td><td>' + data[i].FirstName + '</td><td>' + data[i].LastName + '</td><td>' + data[i].Best + '</td><td>' + data[i].DateAchieved + '</td></tr></tbody>');  
  49.                         }  
  50.                         $tbl.append(rows.join(''));  
  51.                     }  
  52.                 }  
  53.             });  
  54.         }  
  55.     </script>  
  56. }  

Take note of the sequence for adding the client Scripts references,

  • jQuery
  • signalR
  • /signalr/hub

jQuery should be added first, then the SignalR Core JavaScript and finally the SignalR Hubs script.

The LoadResult() function uses a jQuery AJAX to invoke a Web API call through AJAX GET request. If there’s any data from the response, it will generate an HTML by looping through the rows. The LoadResult () function will be invoked when the page is loaded or when the displayLeaderboard() method from the Hub is invoked. By subscribing to the Hub, ASP.NET SignalR will do the entire complex plumbing for us to do real-time updates without any extra work needed in our side.

Output

Here’s the final output when you deploy and run the project.

Xamarin

The data above will automatically update without refreshing the page once a user from the mobile app syncs their best score.

GitHub Repo

You can view and fork the source code here: https://github.com/proudmonkey/Xamarin.MemoryGameApp

Summary

In this article we've learned the following, 

  • Setting up a SQL Server database from scratch
  • Building a simple Working Memory game application using Xamarin.Forms that targets both iOS and Android platform.
  • Creating an ASP.NET Web API project
  • Integrating Entity Framework as our data access mechanism
  • Creating an ASP.NET MVC 5 project
  • Integrating ASP.NET SignalR within ASP.NET MVC
References 

  • https://en.wikipedia.org/wiki/Working_memory
  • https://docs.microsoft.com/en-us/aspnet/web-api/overview/security/enabling-cross-origin-requests-in-web-api
  • https://docs.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-server

 


Similar Articles