Exercise 3: Adding Tombstoning

In this exercise, we will utilize additional Windows Phone capabilities, which will enrich the game with the new features. The most prominent of those capabilities, at least by its name, is the Tombstoning.

The complete list of the capabilities that we will use in this exercise is the following:

  • Tombstoning, which allows the game remember its state when player launches another application and then comes back to the game.
  • Isolated storage, which allows persist the game state and restore it on the next execution of this game.
  • Choosers and Launchers, which allows launch another application from the running game.

Those capabilities are redundant when we are writing Windows PC application, but they are critical when we are writing Windows Phone applications. The reasons for this are the following:

  • Due to the performance and the battery saving considerations, Windows Phone allows only one application to run at a time. This is why we need a special means, the Tombstoning, to come back to our game.
  • For the same reason we need Choosers and Launchers, to launch other applications from our running application. Note that our application will not run when another application should start. Moreover, the phone should know that we did not exit our application but just switched to another one and still may come back. The Choosers and Launchers address all those problems.
  • Due to security considerations, Windows Phone isolates applications from each other and does not provide means like shared file system to persist data from applications. This is why we need a special means, the Isolated storage, to persist the state of our game.

Task 1 – Utilizing Tombstoning and Isolated Storage to Store the Game State

Though Tombstoning and Isolated storage have similar capabilities, they both can store the game state, we use them in different scenarios.

We use Tombstoning to store the state of the game when we launch another application, e.g. via the "Start" button or using the above-mentioned Choosers and Launchers.

We use Isolated storage to store the state of the game when we are going to exit the game, but want to continue next time from the same place. We exit the game, e.g. via "Back" button when we go before the first page of the game or when we explicitly call the "Exit" method of the Game class.

Fortunately, Windows Phone itself recognizes those scenarios and raises appropriate events, which we may handle in our game.

In this task, we extend the CatapultGame class with event handlers that save and restore the application state using Tombstoning and Isolated storage capabilities and assign those handlers to the correspondent events raised by the Windows Phone:

  1. Open the starter solution located in the Source\Ex3-Persistency\Begin folder.
  2. Open the CatapultGame.cs file.
  3. Add the following using statements

    C#

    using Microsoft.Phone.Shell; using System.IO.IsolatedStorage; using System.Xml.Serialization; using System.IO;

  4. Add the following field

    C#

    stringfileName ="Catapult.dat";

  5. Add the highlighted line to the class constructor:

    C#

    public CatapultGame()
    FakePre-8ad0facf067d4208a897ee9064777f0d-a33b8dbe36d64acbb8683fc7b250ef6aFakePre-4741c63fce254fa09763942450522999-4e70256241ff4dbb97e3b438c3bf4253FakePre-abd90dcd28734dd1bff11eace76ad592-ca20acd3416142c28f72536a03612536FakePre-277834ee750f45d984a41eca8bd272b9-2de20561d32d423e91c5867333b1d2cfInitializePhoneServices();FakePre-05303e70494f4c8ab6c180583807964f-659d6af421ac4a3bab7e19a0cc7c76ee

  6. Locate the "Initialization Methods" region and add there the following method:

    C#

    private void InitializePhoneServices() { PhoneApplicationService.Current.Activated += new EventHandler<ActivatedEventArgs>(GameActivated); PhoneApplicationService.Current.Deactivated += new EventHandler<DeactivatedEventArgs>(GameDeactivated); PhoneApplicationService.Current.Closing += new EventHandler<ClosingEventArgs>(GameClosing); PhoneApplicationService.Current.Launching += new EventHandler<LaunchingEventArgs>(GameLaunching); }

    This function assigns state saving and restoring handlers to the appropriate events represented by the application level object PhoneApplicationService.Current. Windows Phone provides this object to its applications and raises events in appropriate conditions.

  7. Add to the class the following code fragment that defines event handlers:

    C#

    #region Event Handlers /// <summary> /// Occurs when the game class (and application) launched /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void GameLaunching(object sender, LaunchingEventArgs e) { ReloadLastGameState(false); } /// <summary> /// Occurs when the game class (and application) exiting during exit cycle /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void GameClosing(object sender, ClosingEventArgs e) { SaveActiveGameState(false); } /// <summary> /// Occurs when the game class (and application) deactivated and tombstoned /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void GameDeactivated(object sender, DeactivatedEventArgs e) { SaveActiveGameState(true); } /// <summary> /// Occurs when the game class (and application) activated during return from tombstoned state /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void GameActivated(object sender, ActivatedEventArgs e) { ReloadLastGameState(true); } #endregion

    In the event handlers, we use the following functions SaveActiveGameState that saves the application state and ReloadLastGameState that restores the application state. We pass those functions a Boolean argument that indicates what capability to use: if the argument is true, we use Tombstoning, else, we use Isolation Storage.

  8. Add to the class the following code fragment that implements functions that we call from event handlers:

    C#

    #region Private functionality /// <summary> /// Reload last active game state from Isolated Storage or State object /// </summary> /// <param name="isTombstoning"></param> private void ReloadLastGameState(bool isTombstoning) { int playerScore = 0; int computerScore = 0; bool isHumanTurn = false; bool isLoaded = false; if (isTombstoning) isLoaded = LoadFromStateObject(out playerScore, out computerScore, out isHumanTurn); else { isLoaded = LoadFromIsolatedStorage(ref playerScore, ref computerScore, ref isHumanTurn); if (isLoaded) { if (System.Windows.MessageBox.Show("Old game available.\nDo you want to continue last game?", "Load Game", System.Windows.MessageBoxButton.OKCancel) == System.Windows.MessageBoxResult.OK) isLoaded |= true; else isLoaded = false; } } if (isLoaded) { PhoneApplicationService.Current.State.Add("IsReload", true); PhoneApplicationService.Current.State.Add("playerScore", playerScore); PhoneApplicationService.Current.State.Add("computerScore", computerScore); PhoneApplicationService.Current.State.Add("isHumanTurn", isHumanTurn); screenManager.AddScreen(new GameplayScreen(), null); } } private bool LoadFromIsolatedStorage(ref int playerScore, ref int computerScore, ref bool isHumanTurn) { bool res = true; // Assume success // Load from Isolated Storage file using (IsolatedStorageFile isolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()) { if (isolatedStorageFile.FileExists(fileName)) { //If user choose to save, create a new file using (IsolatedStorageFileStream fileStream = isolatedStorageFile.OpenFile(fileName, FileMode.Open)) { using (StreamReader streamReader = new StreamReader(fileStream)) { playerScore = int.Parse(streamReader.ReadLine(), System.Globalization.NumberStyles.Integer); computerScore = int.Parse(streamReader.ReadLine(), System.Globalization.NumberStyles.Integer); isHumanTurn = bool.Parse(streamReader.ReadLine()); streamReader.Close(); } } res = true; isolatedStorageFile.DeleteFile(fileName); } else res = false; } return res; } private bool LoadFromStateObject(out int playerScore, out int computerScore, out bool isHumanTurn) { playerScore = -1; computerScore = -1; isHumanTurn = false; bool res = true; // Assume success if (PhoneApplicationService.Current.State.ContainsKey("HumanScore")) { playerScore = (int)PhoneApplicationService.Current.State["HumanScore"]; PhoneApplicationService.Current.State.Remove("HumanScore"); } else res = false; if (PhoneApplicationService.Current.State.ContainsKey("PhoneScore")) { computerScore = (int)PhoneApplicationService.Current.State["PhoneScore"]; PhoneApplicationService.Current.State.Remove("PhoneScore"); } else res = false; if (PhoneApplicationService.Current.State.ContainsKey("isHumanTurn")) { isHumanTurn = (bool)PhoneApplicationService.Current.State["isHumanTurn"]; PhoneApplicationService.Current.State.Remove("isHumanTurn"); } else res = false; return res; } /// <summary> /// Saves the current game state (if game is running) to Isolated Storage or State object /// </summary> /// <param name="isTombstoning"></param> private void SaveActiveGameState(bool isTombstoning) { // Try finding the running game instance var res = from screen in screenManager.GetScreens() where screen.GetType() == typeof(GameplayScreen) select screen; GameplayScreen gameplayScreen = res.FirstOrDefault() as GameplayScreen; if (null != gameplayScreen) { if (isTombstoning) { SaveToStateObject(gameplayScreen); } else { SaveToIsolatedStorageFile(gameplayScreen, fileName); } } } /// <summary> /// Saves the gameplay screen data to Isolated storage file /// </summary> /// <param name="gameplayScreen"></param> /// <param name="fileName"></param> private void SaveToIsolatedStorageFile(GameplayScreen gameplayScreen, string fileName) { // Save to Isolated Storage file using (IsolatedStorageFile isolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()) { // If user choose to save, create a new file using (IsolatedStorageFileStream fileStream = isolatedStorageFile.CreateFile(fileName)) { using (StreamWriter streamWriter = new StreamWriter(fileStream)) { // Write date to the file streamWriter.WriteLine(gameplayScreen.player.Score); streamWriter.WriteLine(gameplayScreen.computer.Score); streamWriter.WriteLine(gameplayScreen.isHumanTurn); streamWriter.Close(); } } } } /// <summary> /// Saves the gamepleay screen data to State Object /// </summary> /// <param name="gameplayScreen"></param> private static void SaveToStateObject(GameplayScreen gameplayScreen) { // Save data to the State object PhoneApplicationService.Current.State.Add("HumanScore", gameplayScreen.player.Score); PhoneApplicationService.Current.State.Add("PhoneScore", gameplayScreen.computer.Score); PhoneApplicationService.Current.State.Add("isHumanTurn", gameplayScreen.isHumanTurn); } #endregion

    Note that when the application state is reloaded from the Isolated storage, the player is asked if continue the saved game or start the new one.

  9. Open file GameplayScreen.cs located in “Screens” folder and locate function LoadContent and replace it with the following code:

    C#

    public override void LoadContent()
    FakePre-b755e01f42c04308a29e6036fd13a696-64f3cd9e7fc4486ba081168167d6c13cbase.LoadContent();FakePre-536ac7ad06574fb5809a71e7a4106c82-51727da272d944d0ad0543d412e91a5eFakePre-033f615bb6c7407a853608b18b18b179-4ef944f5ab54418e996355c841f40401FakePre-94237d820991426a80d7809044dccb9d-83dbf05540544df3b5b80a26ebb0808a

  10. Add the following using statements

    C#

    usingMicrosoft.Devices.Sensors; usingGameStateManagement; usingMicrosoft.Phone.Shell;

  11. Locate the LoadAssets and add the highlighted code at the bottom, as below:

    C#

    public void LoadAssets()
    FakePre-3b31bb432963450cb2c211f61b5e1c78-273a7e2292554268aae97bd6a149b12dFakePre-df2cc009f55b402dae647a2785a40a04-5051a976894845f6bcd9a21b9681c326FakePre-3ea300cfedb4450991b5e28f803bc7fc-1f698d0b0a1448128edfa84c3758d61bFakePre-d22bf32f17294655a7447db8d137d2a2-031319aba38448cf968e9e25caf73cc0FakePre-8f7d7611546e4c31bb72cd89b4b9fc1c-d28253315b294456a8a365f17dbc0921FakePre-df97439abb694ab7b27088d7a74e819c-14380f51d4054d8cb5bda748a97ac6bbFakePre-cc391b26f7c647ffb5498c343588d437-bebd83454713443198f403b31199d4ebFakePre-20ef1175e8e14558b32b06c7b636040a-8bb143fbfd294d94a286bb239a940ae8FakePre-a7d6057bc68749b7a0282c0a715d2b3a-220fe2c1467f4f31a36d47f9da4b5ba5FakePre-d35cbd1d735e42858eb7224a91eff694-c533fa998e464219abaa9b512366b279FakePre-70817a473c1c4a6990477060b25ab890-68a6a2c5fa294d368d6e5710c2a7de22FakePre-61cb6272e8e84841a476cf1d2f473716-8fabaecb49ac43d1a911d6ebf47ba307FakePre-8c452b4633f14e1780c60ec47c155f1e-6cec1ac26a404cafb4614ce3e8ceb919FakePre-6a62972ab9704f2aa655299cef28a0db-3bfe5b82b71044d3ad5c304da3977e86FakePre-435fdab2e55041a6a16d9233e61e4ed8-d63f2abd830540a29dfc23b2fe986e7cFakePre-e3d9f6040fb942ccb24a224d0e8d147e-1b1a8365c6354cbfa5aef76138e69e84FakePre-acf75f1b7dcc49509dfb24a2acb38aa4-fef531de9ae0496b9711610fc6627479FakePre-d8ce2dcebd814428bc3c9cdcf99bc706-17a01021620e4430b38ba2b976e52867FakePre-111c5d697a264ea4983897ba7784f090-206d6bc31055408388a7afd165d628e4FakePre-da72cf26a3d245b9ae8bb0e0129d7fea-22b881d0c9cf4c7bbe326b63ffc671e0FakePre-72b8b581ef6648d88b2a69fdab15fc99-b3b09e44601e43f4ac2eb30816a64659FakePre-091eda1457fa48a38bb9cade33b21c96-a1411acc3ffd46cab14e0156a4a5b33dFakePre-ccf4d587dd1540a3a7657c64dec320af-ab27081357114e3d86a4277ed0953f29FakePre-31c14dfb377f4070a033131b7e6be7bd-38618b157e4f49b197a8a413c1c8d830FakePre-57013a53e4f84bdc8797ab45e555990f-48d4fbc7154b445a87e980437c9b214bFakePre-7f82af87400d479c8281d67069ff3bdf-77dee66459bd44fe9d074de23702c5b2FakePre-0803996ab11d4d8c8456e16a9e1dd0ca-c1c88174677e4811b6fe2bba72170600FakePre-74d278ac135045528271fa87386e199f-8fea6dea3dcc48b2807a895ddff3695bFakePre-a1e3b322ffff40b698fee130729192e9-89a278ff30124b00951932e9da67726cFakePre-4a43815745694aa4952d327092993965-0a3615389d274d5e861f07e626ccebfeFakePre-681cd1769b5646c8975bc0e305f9a01f-6c21198de8344e35a0f5b760483e56e7FakePre-d3aae25a3e9c40fe9ed7bb90192d69c7-a6b900866c5f49379bb8ca402200176fFakePre-18ac939d392c4e539300d6a7b4cbd42c-1e2e176a4c154f8c88abeeb4568752b7FakePre-3eaeda94ac6a4514a821db68f926a569-f8e44ae220604941b3ee335e92137bb4FakePre-d7132247c273474f94d40f1b148d81fa-869f146dd4864f46aaccf0c34b4ff910FakePre-69dc3919fcb24edfa94349b1edb8a263-b9fba422a8df4881a3ad5986d4569712FakePre-2a668a9de00b45f9b936f10b2ad5627a-9be658a8a3674502a5ab52fc78d29cfdFakePre-3a7f977ab01d46ab882b5681544debda-798db8e659014cf39b8a6b3213b7baf7FakePre-12e07345df1e433fb6c854216821dc5c-f43b55c8b20846bd87d9d42998b711fbFakePre-a984a769997b4e97acca54a953cb1360-82bb3005b3bc49f1ad3cd8a67397b068FakePre-c591fda7fbdc4af286a87b1627074bfa-a335b60e27814fa783805f0f9fe73981FakePre-6ad87063912048298d0a554ffc54100b-3d66d31b4e6c434a97054de697c50304FakePre-5e626a126cc240d48dbf390a04743315-5ac4ce8896f94564b61676d3a3750a0bFakePre-f57a2182a5b74a1d9c4e3f25736e2c26-c8f5c1f7f5644b80a9d3a1a19739c02dFakePre-83fb429835c2482fbb2836bbac5f668e-a9382d79ac7841ebb08b4ceb9aab3037

    Note the highlighted statements at the end of the function. Those statements utilize the Tombstoning capabilities to reload the application state, where the first highlighted statement checks if any state data is available.

This concludes the task.

Now our game is enriched with tombstoning and state reloading features.

Note:
In order to check how tombstoning works, you can start the game and tap the hardware 'Start' button (). The application is now deactivated – it is possible to observe this in the Output window.

Tap the 'Back' button () to return to the application. If the application was tombstoned, the "Resuming" screen and a loading animation are displayed. Otherwise, the application should instantly resume.

Task 2 – Utilizing Chooser and Launcher to Run another Application from the Game

As we already mentioned, we use Choosers and Launchers to launch another application from our game. The difference between Choosers and Launchers is that Choosers provide results back to the game, while Launchers do not. The development platform provides a rich collection of Choosers and Launchers that launch standard Windows Phone tasks out-of-the box. In this exercise, we will use a Chooser of type PhoneNumberChooserTask and a Launcher of type SmsComposeTask.

PhoneNumberChooserTask launches the standard Phone Book application and returns the selected number back to the game.

SmsComposeTask sends SMS to the given number.

In this task, we will integrate those two capabilities into our game.

  1. Open the file CatapultGame.cs
  2. Locate the “Using Statements” region and add there the following statements:

    C#

    ... using Microsoft.Phone.Tasks; ...

  3. Locate the “Fields” region and add there the following statement:

    C#

    ... #region Fields PhoneNumberChooserTask phoneNumberChooserTask; SmsComposeTask smsComposeTask; #endregion ...

  4. Now add into the constructor the following highlighted code:

    C#

    public CatapultGame()
    FakePre-dc1b6abd680b402199b34cbca17e7275-22e4239b58b04906afe61f54558e5ec8FakePre-313859cbaa544e22859b6177fb020dc6-00968f04c0f24445b19a1e4e060a9006FakePre-b190a1c941a14ad5aecdf4a16d8bc1a9-905c24d2dfec4f78964e9e44443518efFakePre-5d49860595d443d19fa7b606b8b7ae36-6c836d6971b044e4b438b1fdc2e6ad23FakePre-d55f702c218b4221b8e3d21f36da038a-0087b0ebf1ee469d8c2bf88026a819c8//Create Chooser and LauncherFakePre-ab7e41cb7ad24e18855b977bdfc605d6-5a14115e620f41e888954543448f3b9dFakePre-13688c9b9d2b45f38b58fec507b24022-b558cdc2de9c4675917df9f0c841eb51FakePre-3fd7cdf76e074a93b8a7eb211a4bd60b-cbd7308159314aef89d089a8b6bf2da5

  5. Into Initialization Methods region, add the following function:

    C#

    public void InitializeChooserAndLauncher() { phoneNumberChooserTask = new PhoneNumberChooserTask(); smsComposeTask = new SmsComposeTask(); phoneNumberChooserTask.Completed += new EventHandler<PhoneNumberResult>(PhoneNumberChooserTaskCompleted); }

  6. Now locate the Loading region and add the function

    /// <summary> /// Occurs when the Phone Number Chooser task completes /// </summary> /// <param name="sender"></param> /// <param name="args"></param> void PhoneNumberChooserTaskCompleted(object sender, PhoneNumberResult args) { // If user provided the requested info if (args.TaskResult == TaskResult.OK) { // Create, initialize and show SMS composer launcher smsComposeTask.To = args.PhoneNumber; smsComposeTask.Body = "Hello! Just discovered very good game called Catapult Wars. Try it by yourself and see!"; smsComposeTask.Show(); } }

    This event handler completes the "Phone Number Chooser" task by launching the SMS Composer with the chosen number.

  7. Add the following code to the CatapultGame class:

    C#

    #region Public Interface public void ExecuteTask() { phoneNumberChooserTask.Show(); } #endregion

  8. Open file MainMenuScreen.cs located in “Screens” folder and locate the constructor and replace it with the following code:

    C#

    public MainMenuScreen()
    FakePre-7dadcea33cbc48579a4549b03f293888-9de0498e17994213a0c36a33baf04c07FakePre-e3a69839c1244b76b357a1372a74cfe3-fd222f0503be48478d88288822128ec3FakePre-e695274921314cc4938cab7222ed134d-703fd058b2b34948a4cc2a827d7cde52FakePre-9b43d4c67a5942fca40325389042c3dd-970fb78f801f4979a15c65ef90c5258bFakePre-ba6ccf176bc24b79a3a2097caf16afd6-b0f7b03ee28447938303534dd988569cFakePre-50e461dee3bd4fdfa2819715a9c2eb80-351f39536f644475adf0efc22b6edad4MenuEntry shareMenuEntry = new MenuEntry("Tell a Friend"); shareMenuEntry.Selected += ShareMenuEntrySelected; MenuEntries.Add(shareMenuEntry);FakePre-f0ca558a067248bda2035e7624435cbe-55b44a60546b4023a1c61cc9bad952fbFakePre-4dec25f03c0e4dcab20babb8653b2355-9d5ab0cb246440e49c553b0facefb24dFakePre-28fd7f420c0b4a12b35948f079a3394f-9637b53a9afd40c6bbcadab6f0bc3de6FakePre-0797b7b5762e434a9f5b856c466b2c3e-5356ef45e5f0461c86cbb3a4fbfa4291FakePre-e8bec043092b4053894f1576c27526c0-8c58ea4641d84aa885eebf595e4e5d0aMenuEntry shareMenuEntry = new MenuEntry("Tell a Friend"); shareMenuEntry.Selected += ShareMenuEntrySelected; MenuEntries.Add(shareMenuEntry);FakePre-f9b711f29682414cb438ca4442900917-0569402097374c55ba7e24c0b6c71738FakePre-c517099fa4384c6aafd804dd44665160-88a46b40ccdb454880f945d6ecde7bffFakePre-427417c614dd47fd8e9189dff22e4d61-5777e100037e4407a5c14d0b0570d5deFakePre-77fe938ea1554abfa36a541ec04a1e66-0a5c38ad40e749a38949286c66602a9bFakePre-29af7a729f334842a7818c839c232be9-95f7dac1f9da4628a3074ecd559738e3MenuEntry shareMenuEntry = new MenuEntry("Tell a Friend"); shareMenuEntry.Selected += ShareMenuEntrySelected; MenuEntries.Add(shareMenuEntry);FakePre-cea0cc17d8a046349e743171f99a94ae-d6cd33a9739f43bd9efb3b36e9c9335dFakePre-a7f954bb4dd84fb8aca8bff618b23e7c-62099bd0950d4181be8ad17eb981ab5cFakePre-2eae0dd763dd4264880e1c219b4a0816-4d92489e58e24c6a9246f92565f3f9ca

    Note the highlighted statements. Those statements introduce a new menu item, which will launch the Phone Book and send SMS to the chosen number.

  9. Locate “Event Handlers for Menu Items” and add there the following code:

    C#

    /// <summary> /// Handler "Share" menu item selection /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void ShareMenuEntrySelected(object sender, EventArgs e) { //Execute a task defined in the game class ((CatapultGame)(ScreenManager.Game)).ExecuteTask(); }

This concludes the task and the exercise.

Now our game is enriched with tombstoning and state reloading features and allows share its score with a friend via SMS.

  1. Note:
    You can check this new option by clicking on “Tell a Friend” new menu from the main page. It will launch the Phone Book application, get back the chosen number and send a SMS to this number.