Chapter 10. Going Mobile!

Nowadays, it seems that everyone is making, planning to make, or thinking of making applications for mobile devices. Mobile is the next great (or maybe actually the current) place to make money by selling applications. While HTML5 applications can be packaged up with tools such as Cordova Phone Gap and other systems, we are going to focus this chapter on targeting the mobile browser from a website to play games. We will first build our applications to work in a desktop web browser, and then we will modify them to scale full screen on iOS devices.

The First Application

The application we are going to create is a simple BS Bingo game. BS Bingo was designed on paper well before mobile devices were available. This cynical game concept is based on the feeling (by some) that the typical business workplace has been overtaken with Dilbert- or Office Space-esque annoying corporate jargon and doublespeak. This doublespeak seems to have deeply rooted itself in the workplace over the last 20 years, mostly to the annoyance of software developers (such as ourselves).

In the pen-and-paper version of the game, each player brings a “bingo card” to a meeting where he expects to hear a lot of this corporate doublespeak. The bingo card is a 5×5 grid, and each of the 25 squares is filled with one of the annoying words or jargon phrases. During the meeting, each player marks off squares as the words or phrases are said aloud by the unsuspecting (and not playing) members of the meeting. When a player has a full column or row of his card marked off, he is supposed to jump up from the meeting table and yell “BS!”

Whether this game was ever widely played (or even played at all) is a debatable urban legend, but the simple concept of clicking squares to highlight them makes for a useful piece of code that we can build easily and then port to the iPhone. We are not even going to build the entire game here; we will leave extending it into a full application (possibly adding multiplayer, which is discussed in Chapter 11) for you, the reader.

The Code

Example 10-1 gives the code for our game. We’ll discuss the various functions in the next section before we move on to testing it in a desktop browser and then modifying it to run full screen on an iOS device. This version of the game will work fine on a Safari desktop browser. We will highlight the modifications necessary to port it to the iPhone/iPad in the next section.

Example 10-1. BSBingo.html full source listing
<!doctype html>
 <html lang="en">
 <head>
    <meta charset="UTF-8">

 <title>BS Bingo</title>
 <script src="modernizr-min.js"></script>
 <script src="TextButton.js"></script>
 <script src="ConsoleLog.js"></script>
 <script type="text/javascript">

 window.addEventListener('load', eventWindowLoaded, false);
 function eventWindowLoaded() {

    canvasApp();

 }

 function canvasSupport () {
     return Modernizr.canvas;
 }

 function canvasApp(){

    if (!canvasSupport()) {
           return;
      }else{
       theCanvas = document.getElementById("canvas");
        context = theCanvas.getContext("2d");
    }

    var bingoCard = [];
    var buttons = [];

    var standardJargonList = [];
    var tempButton = {};
    var clickSound;

    function initLists(){

       standardJargonList=[
        "Actionable", "Assessment" ,"Bandwidth", "Benchmark",
        "Best\nPractices", "Bottle neck" , "Change\nManagement",  "Coach",
        "Competitive\nAdvantage", "Constraints", "Core\nCompetencies",
        "Core values", "Critical\nthinking", "Cutting\nedge",
        "Dashboard", "Deliverables", "Enterprise","Gatekeeper",
        "Individual\nContributor", "Leadership", "Matrix\norganisation",
        "Metrics", "Milestones", "Momentum", "Moving target",
        "Initiative","Partnership", "Process", "Process\nmanagement",
        "Re-engineer", "Requirements", "Rightsize", "Seat at\nthe table",
        "Tentpole", " Silo", "Standards", "State of the art",
        "Supply chain", "Synergy","Teamwork", "Thought\nleader",
        "Touchpoints", "Value\nadded", "Drink the\nKool Aid",
        "Baked In", "Champion", "Circle Back", "Dialogue", "Emerge",
        "Enhance", "Evolve", "Execute", "Facilitate" ,"Incentivise",
        "Leverage", "Partner", "Spearhead", "Strategize","Synergise",
        "Throw\na\nCurve", "Touch Base", "Outside\nthe\nBox",
        "Opportunity", "Open Door\nPolicy","Win-Win\n(Anything)",
        "Risk\n(Anything)","Proactive","Reactive","Buy-In",
        "Paradigm\nShift","Task-Oriented","Empower","Team\nPlayer",
        "Enterprise\nWide","Globalization","Localization",
        "Mission-critical", "Magic\nQuadrant","Agile\n(Anything)",
        "Waterfall","Outsourcing","Off-Shoring","Blue Sky",
        "20/20\hindsight","Low\nHanging\nFruit","10,000\nFoot View",
        "Take\nOwnership","Ramp up", "Out of\nthe Box", "24x7",
        "Fast Track", "Out of\nthe Loop", "In the\nLoop","Touch Base",
        "Mindset", "Game Plan", "Bring to \nthe Table", "Drill Down",
        "Elevator\nSpeech", "Level the\nPlaying field",
        "Ping\n(Someone)","Pushback","Retool", "Take Away",
        "Life-Time\nValue", "Thought\nLeadership", "Up Sell"
         ];

    }

    function initButtons(){
       buttons = [
          [

          new TextButton(0,0,"Button
              0,0",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(92,0,"Button
              0,1",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(184,0,"Button
              0,2",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(276,0,"Button
              0,3",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(368,0,"Button
              0,4",85,50,gr,"#000000","#ffff00","#000000")

         ],

         [

          new TextButton(0,57,"Button
              1,0",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(92,57,"Button
              1,1",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(184,57,"Button
              1,2",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(276,57,"Button
              1,3",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(368,57,"Button
              1,4",85,50,gr,"#000000","#ffff00","#000000")

         ],

         [

          new TextButton(0,114,"Button
              2,0",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(92,114,"Button
              2,1",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(184,114,"Button
              2,2",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(276,114,"Button
              2,3",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(368,114,"Button
              2,4",85,50,gr,"#000000","#ffff00","#000000")

         ],

         [

          new TextButton(0,171,"Button
              3,0",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(92,171,"Button
              3,1",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(184,171,"Button
              3,2",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(276,171,"Button
              3,3",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(368,171,"Button
              3,4",85,50,gr,"#000000","#ffff00","#000000")

         ],

         [

          new TextButton(0,228,"Button
              4,0",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(92,228,"Button
              4,1",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(184,228,"Button
              4,2",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(276,228,"Button
              4,3",85,50,gr,"#000000","#ffff00","#000000"),

          new TextButton(368,228,"Button
              4,4",85,50,gr,"#000000","#ffff00","#000000")

         ]
       ];
    }

    function initSounds(){
       clickSound = document.getElementById('clicksound');
    }

    function chooseButtonsForCard(){
       //copy jargon into temp array
       var tempArray = [];
       for (var arrayctr=0;arrayctr<standardJargonList.length;arrayctr++){
          tempArray.push(standardJargonList[arrayctr]);
       }

       for (var ctr1=0;ctr1<buttons.length;ctr1++){

          for (var ctr2=0; ctr2<buttons[ctr1].length;ctr2++){
             var randInt = Math.floor(Math.random()*tempArray.length);
             buttons[ctr1][ctr2].text = tempArray[randInt];
             tempArray.splice(randInt,1);
          }
       }

    }



    function drawScreen() {
       //ConsoleLog.log("standardAcronymList="+standardAcronymList.length);
       //ConsoleLog.log("standardJargonList="+standardJargonList.length);
       for (var ctr1=0;ctr1<buttons.length;ctr1++){
          ConsoleLog.log("ctr1="+ctr1)
          for (var ctr2=0; ctr2<buttons[ctr1].length;ctr2++){
             buttons[ctr1][ctr2].draw(context);
          }
       }

    }

    function onMouseClick(e) {

       //select case through states and then the locations of
       //buttons in those states
       mouseX = e.clientX-theCanvas.offsetLeft;
       mouseY = e.clientY-theCanvas.offsetTop;
       ConsoleLog.log("click " + mouseX + "," + mouseY);
       //find the button clicked

       var col = Math.floor(mouseX/92);
       var row = Math.floor(mouseY/57);

       console.log("row",row,"col", col);
       tempButton = buttons[row][col];
       clickSound.play();
       tempButton.pressDown();
       tempButton.draw(context);

    }

    function onMouseMove(e) {
       mouseX = e.clientX-theCanvas.offsetLeft;
       mouseY = e.clientY-theCanvas.offsetTop;
    }

       //**** start application
   var gr = context.createLinearGradient(0, 0, 85, 50);

    // Add the color stops.
    gr.addColorStop(0,'#ffffff');
         gr.addColorStop(.5,'#bbbbbb');
    gr.addColorStop(1,'#777777');

    theCanvas.addEventListener("mousemove", onMouseMove, false);
    theCanvas.addEventListener("click", onMouseClick, false);

    initSounds();
    initButtons();
    initLists();
    chooseButtonsForCard();
    drawScreen();

 }

 </script>
 </head>
 <body>
 <div style="position: absolute; top: 0px; left: 0px;">
 <canvas id="canvas" width="570" height="418">
  Your browser does not support HTML5 Canvas.
 </canvas>
 <audio id ="clicksound"  preload="auto">
    <source src="click.mp3" type="audio/mpeg" />

 Your browser does not support the audio element.
 </audio>
 </div>
 </body>
 </html>

Name this file bsbingo.html, and save it in a folder. If you are going to follow along and create the example project, you will also want to create a folder to hold the project files.

Examining the Code for BSBingo.html

When designing an application for the iOS platform, we are actually targeting the Safari Mobile browser. This means that we can make concessions rather than having to target all available HTML5-compatible devices. You will notice this especially when we discuss <audio> tag usage.

The TextButton.js file

Our BS Bingo game will be played on a grid of 25 squares. We have created a class (an object prototype, actually) called TextButton.js to help us create buttons with the text, as well as a “press” state that we can use to show that the button has been clicked. You will want to save this file in the project folder along with the BSBingo.html file. Here is the code for this file:

function TextButton(x,y,text, width, height, backColor, strokeColor,
 overColor, textColor){
    this.x = x;
    this.y = y;
    this.text = text;
    this.width = width;
    this.height = height;
    this.backColor = backColor;
    this.strokeColor = strokeColor;
    this.overColor = overColor;
    this.textColor = textColor;
    this.press = false;
}

TextButton.prototype.pressDown=function() {
    if (this.press==true){
        this.press = false;
    }else{
        this.press = true;
    }
}

TextButton.prototype.draw = function(context){

    context.save();
    context.setTransform(1,0,0,1,0,0); // reset to identity
    context.translate(this.x, this.y);

    context.shadowOffsetX = 3;
    context.shadowOffsetY = 3;
    context.shadowBlur = 3;
    context.shadowColor = "#222222";

    context.lineWidth = 4;
    context.lineJoin = 'round';
    context.strokeStyle = this.strokeColor;

    if (this.press==true){
        context.fillStyle = this.overColor;
    }else{
       context.fillStyle = this.backColor;
    }

    context.strokeRect(0, 0, this.width,this.height);
    context.fillRect(0, 0, this.width,this.height);

    //text
    context.shadowOffsetX = 1;
    context.shadowOffsetY = 1;
    context.shadowBlur = 1;
    context.shadowColor = "#ffffff";
    context.font = "14px serif"
    context.fillStyle = this.textColor;
    context.textAlign = "center";
    context.textBaseline = "middle";
    var metrics = context.measureText(this.text);
    var textWidth = metrics.width;
    var xPosition = this.width/2;
    var yPosition = (this.height/2);

    var splitText = this.text.split('\n');
    var verticalSpacing = 14;

    for (var ctr1=0; ctr1<splitText.length;ctr1++) {
        context.fillText  ( splitText[ctr1],  xPosition,
        yPosition+ (ctr1*verticalSpacing));
    }

    context.restore();
}

This object prototype contains functions for creating, drawing, and clicking a gray square button with black text on it. When clicked, the button will be drawn with a yellow background. We have covered all these drawing functions earlier in this book, so they will look familiar to you if you have read those chapters. If you have not, it’s especially a good idea to read Chapter 2, which covers drawing and shading objects drawn with paths.

Let’s now take a quick look at the functions we have created in bsbingo.html.

The initLists() function

The first game-related function you will encounter is initLists(). For our simple game implementation, we have created a single list of words based on some common business jargon. The standardJargonList application scope variable will contain a single-dimension array of words that will be placed randomly on the player’s bingo card. We can add more types of lists if we would like to target other types of jargon-speak, such as pure IT process-speak, marketing-speak, or even sports- or geek-speak.

The initButtons() function

This function creates a grid of 25 TextButton instances, 85 pixels in width and 25 in height. These are stored in the application scope buttons two-dimensional array so that they can be accessed via the [row][column] syntax.

The initSounds() function

The initSounds() function needs to initialize only a single sound referenced in an HTML5 <audio> tag. Because we are targeting the iOS platform, we need to provide only a single .mp3-formatted sound. We do not need .ogg or .wav because we are not targeting any other browsers. Here is the HTML5 <audio> tag:

<audio id="clicksound"  preload="auto">
    <source src="click.mp3" type="audio/mpeg" />
 Your browser does not support the audio element.
 </audio>

The chooseButtonsForCard() function

This function creates a local array called tempArray and fills it with the contents of the standardJargonList. Next, it randomly chooses an element from the tempArray for each of the 25 row/column combinations on the bingo card. As it selects a word, it splices it from the tempArray so that it cannot be selected again, leaving the card with no duplicates.

The drawScreen() function

This function loops through the buttons two-dimensional array and draws the initial 25 buttons with text onto the canvas.

The onMouseClick() function

When the user clicks the mouse on the game screen, this event listener function determines which of the 25 squares was clicked. It calls the appropriate TextButton instance’s pressDown() function and then its draw() function, passing in the context.

The onMouseMove() function

When the mouse is moved, this event listener function will set the mouseX and mouseY values to the current mouse position on the canvas.

The Application Code

After all the functions and the TextButton object prototype are created, the actual application code is very simple. Because this is a completely event-based application, we don’t need a main loop. We also have not put in any other states or buttons, such as a title screen or a reset button. This makes the app less user-friendly, but it is fine for this simple example. It also makes the application code very simple:

 //**** start application
    var gr = context.createLinearGradient(0, 0, 100, 100);

    // Add the color stops.
    gr.addColorStop(0,'#ffffff');
    gr.addColorStop(.5,'#bbbbbb');
    gr.addColorStop(1,'#777777');

    theCanvas.addEventListener("mousemove", onMouseMove, false);
    theCanvas.addEventListener("click", onMouseClick, false);
    initSounds();
    initButtons();
    initLists();
    chooseButtonsForCard();
    drawScreen();

First, we create a shared linear gradient that can be used by all the TextButton instances. Next we add the mouse event listeners for click and move. Finally, we run through our functions to set up the card, and then we simply wait for the user to click a button. That’s all there is to it. We haven’t even added a way to announce that the player has won. Extending this into a full-fledged application would be very simple, so we leave this task up to the reader if you have the desire to do so.

Figure 10-1 shows the screen for the finished application.

BS Bingo in Safari Desktop Edition
Figure 10-1. BS Bingo in Safari Desktop Edition

Next we will look at how to scale the game as a web-based, full-screen iOS application.

Scaling the Game for the Browser

The cool thing about HTML5 Canvas, especially when it comes to Mobile Safari, is that we can easily create applications that scale to the full screen of an iOS device and allow the user to add an icon to their Home screen interface. This icon will play the application in full screen (without the top browser bar) and will function just like an app downloaded from iTunes. The only caveat us that the app is actually on a web page, hosted on a web server, and the user will need Internet access to use it.

Starting to create a full screen mobile version of BS Bingo

We now have a game that is shoved up into the top-left corner of the browser window, and while it plays fine, it will be too small on a phone browser and will be shoved up into the corner on a tablet browser. Scaling the game to work on the desktop and mobile devices is not difficult, but it will require some changes and additions to the current code in the bsbingo.html file.

The game’s aspect ratio is not ideal for a mobile Safari full screen application, so we will see soon enough that the buttons and text will look a little pixilated and not fit perfectly on the iOS screens. However, this is only the first of two examples, and it will be good to see it in action to educate ourselves about what changes we should make for the second application in the chapter so that it will fit better on an iOS screen.

Changing the Canvas style properties

We will need to add some new styles to the file to help position and scale the .html file. I am going to call this version bsbingo_scaled.html. Here are the styles we will need to add to take the scaled Canvas full screen:

<style>
<style type="text/css">
        html, body {
            background-color: #2f9acc;
            margin: 0px;
            padding: 0px;
            color: #fff;
              height: 100%;
              overflow: hidden;
        }

        #canvas {

            overflow: hidden;
            image-rendering: optimizeSpeed;
            -ms-interpolation-mode: nearest-neighbor;
            -webkit-optimize-contrast;
              width:100%;
              height:100%;

        }

         :webkit-full-screen {
            width: 100%;
            height: 100%;
        }


</style>

What these styles do is force both the HTML <BODY> tag and the canvas to scale to fit the entire screen and limit the size of the scroll bars.

See Figure 10-2 for an example of bsbingo_scaled.html in a desktop browser.

BS Bingo scaled to fit the Chrome browser
Figure 10-2. BS Bingo scaled to fit the Chrome browser

Updating the mouse listening code

Now that the game has been scaled, the current mouse listener code that determines clicks will not work. We need to add in the following code to determine the new scale of the Canvas and translate that into mouse clicks:

function onMouseClick(e) {

    var mouseX;
    var mouseY;

    var xFactor = theCanvas.width / window.innerWidth;
    var yFactor = theCanvas.height / window.innerHeight;

    var mouseX1 = event.clientX - theCanvas.offsetLeft;
    var mouseY1 = event.clientY - theCanvas.offsetTop;
    mouseX = mouseX1 * xFactor;
    mouseY = mouseY1 * yFactor;
    //find the button clicked

    var col = Math.floor(mouseX/(92));
    var row = Math.floor(mouseY/(57));

    console.log("row",row,"col", col);
    tempButton=buttons[row][col];
    clickSound.play();
    tempButton.pressDown();
    tempButton.draw(context);

}

The xFactor and yFactor variables make up the secret sauce that creates values that are multiplied against the mouseX and mouseY values. This allows the col and row values to be easily determined now that that the scale factor has been applied.

These are determined by dividing the current canvas width and height by the current actual size of the web browser (window.innerWidth and window.innerHeight), respectively. This will provide us with a scale factor in each dimension that we apply to the mouse position to find the canvas pixel rather than the browser pixel. This takes the scaling out of the equation and allows the application to use its original math rather than worry about the actual scaled size of the Canvas.

One problem you will notice is that if the screen is scrolled vertically, this code will not work. This is why we will create a full screen version of the game that can be played from the Home screen of the iOS device and eliminate as much scrolling as possible.

If you encounter a mouseX and mouseY position problem using the event.clientX and event.clientY values on a scrolling browser window, you can substitute them with event.pageX and event.page. In most browsers, this substitution will help if the screen is scrolled.

Adding meta-tags for iOS devices

Meta-tags are added to help the iOS devices determine the correct size of the viewport in a scaled mode and also to define an icon. With the icon, the user can add a link from the web-published game to their iOS device that can be clicked just like any other application.

<meta name="viewport" content="initial-scale=1 maximum-scale=1
      user-scalable=0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-touch-fullscreen" content="yes">
<link rel="apple-touch-icon" href="bsicon.png" >

Figures 10-3 and 10-4 show the icon and banner that we will use for the app.

57x57 icon (scaled up for viewing)
Figure 10-3. 57x57 icon (scaled up for viewing)
Add to Home Screen button at top
Figure 10-4. Add to Home Screen button at top

Testing the Game on an Actual Device

We have placed a folder with the live files at this site.

You can find also these files in the bsbingo_scaled folder, inside the Chapter 10 directory in the book’s download files at O’Reilly’s website.

The folder contains the following files:

  1. bsbingo_scaled.html

  2. bsicon.png

  3. click.mp3

  4. ConsoleLog.js

  5. modernizr-min.js

  6. TextButton.js

To add this page (or your own live page) to the iPad or iPhone desktop, simply navigate to the page and then click the Add to Home Screen button next to the browser address bar. Figure 10-4 shows bsbingo_scaled.html running in mobile Safari on an iPad with the Add to Home Screen button circled at the top, next to the address bar.

When the Add to Home Screen button is pressed, it will ask you if you want to use the bsicon.png image that we designated. It will also ask for a name to be displayed on the home screen under the icon. Click the icon, name it, and then it will be added to the home screen of your device just like any other application. When you run it this time, the browser address bar will not be present and the game will fit nicely (as nicely as possible given the aspect ratio we chose) on the iPad or iPhone Screen. See Figure 10-5 for the game running in this mode.

You will notice that we did not choose an aspect ratio that looks really pretty on the scale screen, but this first example simply serves to get our feet wet. While were are not going to do it here, it would be pretty trivial to change the code to fit into a 640×480 aspect ratio with borders around the application to make it look nicer on the screen. If you do choose to make your game on a 640×480 or 1024×768 canvas, you will get much better results. Next, we will create a new version of the GeoBlaster Extended game from Chapter 9 and extend it to a full screen app, first in the browser and then as a mobile Safari application. The game will use a better aspect ratio.

Game launched from the Home screen
Figure 10-5. Game launched from the Home screen

Retro Blaster Touch

We are going to use the guts of the GeoBlaster Extended game from Chapter 9, change it up a bit graphically, and then modify it for touch controls. Mouse movement and touch movement are handled in different manners between the desktop and mobile Safari browsers, so most of our code changes will be in this area. We will also be adding in auto-fire to the game, because we don’t want the user to have to press any complicated on-screen buttons to fire missiles.

The original Retro Blaster was one of the first indie Flash games that we ever completed. If you search for “8bitrocket Retro Blaster” on the Internet, you will find it in a number of places. It is a much more complicated and intricate game than we are going to create here. What we have done is replace the tile sheets from GeoBlaster Extended with game graphics from Retro Blaster, and we have added a background graphic and title screen.

Figures 10-6 and 10-7 show the new player ship tile sheets that we will be using.

Retro Blaster ship tiles 1
Figure 10-6. Retro Blaster ship tiles 1
Retro Blaster ship tiles 2 (with thrust)
Figure 10-7. Retro Blaster ship tiles 2 (with thrust)

Figures 10-8, 10-9, and 10-10 show examples of the large, medium, and small rock tile sheets that we will use in this version of the Retro Blaster Touch game.

Retro Blaster large rock tiles
Figure 10-8. Retro Blaster large rock tiles
Retro Blaster medium rock tiles
Figure 10-9. Retro Blaster medium rock tiles
Retro Blaster small rock tiles
Figure 10-10. Retro Blaster small rock tiles

We also have a set of tiles for the particle explosions and a single image that will be that saucer that attacks the player. Figures 10-11 and 10-12 show these two files, respectively.

10x enlarged version of the Retro Blaster particle explosion tiles
Figure 10-11. 10x enlarged version of the Retro Blaster particle explosion tiles
Enlarged version of the Retro Blaster enemy saucer
Figure 10-12. Enlarged version of the Retro Blaster enemy saucer

Aside from these game-play elements, we have created a title screen, background screen, and home screen icon for the game. These are shown in Figures 10-13, 10-14, and 10-15.

Retro Blaster title screen
Figure 10-13. Retro Blaster title screen
Retro Blaster game background screen
Figure 10-14. Retro Blaster game background screen
64x64 Home screen icon (enlarged for print)
Figure 10-15. 64x64 Home screen icon (enlarged for print)

Now that we have taken a good look at the new assets, let’s go through the code necessary to scale the game to the browser window and then add both the mouse and the touch events.

Mobilizing Retro Blaster Touch

The full code for the Retro Blaster Touch game is in a folder called retroblaster_touch in the Chapter 10 files downloaded from the O’Reilly website. We are not going to go through the full set of changes or provide the entire code listing here (one giant code listing in Chapter 9 was quite enough). Retro Blaster Touch is a modification of that full code listing for GeoBlaster Extended game from Chapter 9. We will go though the most important changes in the next few sections.

Jumping to Full Screen

The changes to HTML and CSS necessary to jump to full screen are almost identical to those for the BSBingo game. The one change we have made is to create this game at a 480×320 aspect ratio that will scale more evenly to various device sizes in landscape mode.

Canvas element

We will be using the following HTML Canvas element code:

<div style="top: 0px; left: 0px; height: 100%; width: 100%;">
<canvas id="canvas" width="480" height="320" >
 Your browser does not support the HTML 5 Canvas.
</canvas>

Notice that we have moved the DIV element that holds the game from the GeoBlaster Extended 50x, 50y starting point to 0,0, using the top and left style attributes.

Meta-tags

We also need to add in a set of meta-tags for Mobile Safari that will aid in informing the device to run the application at full screen when the Home Screen icon is clicked.

<meta name="viewport" content="initial-scale=1 maximum-scale=1
      user-scalable=0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-touch-fullscreen" content="yes">
<link rel="apple-touch-icon" href="icon.png" >

Also note that we have added the icon.png image as the referenced Home Screen icon when the user adds the game to their Home screen from the website.

Style sheets

Next we need to add in the same style attributes for the canvas that we did with the BSBingo game:

<style type="text/css">
        html, body {
            background-color: #2f9acc;
            margin: 0px;
            padding: 0px;
            color: #fff;
              height: 100%;
              overflow: hidden;
        }

        #canvas {

            overflow: hidden;
            image-rendering: optimizeSpeed;
            -ms-interpolation-mode: nearest-neighbor;
            -webkit-optimize-contrast;
              width:100%;
              height:100%;

          }

        :webkit-full-screen {
            width: 100%;
            height: 100%;
        }
    </style>

These changes will cosmetically put the game into full screen, but we still have not added in the mouse movement and touch controls to allow the game to be played full screen in either a desktop or a mobile Safari browser. Let’s look at those now.

Touch Move Events

The one difference between a desktop mouse and a mobile device is the finger touch. The finger-touch movement when “tapped” is identical to the mouse-click, so we were able to use the same basic code for each in the bsbingo_scaled game. For Retro Blaster Touch, we will need to have two separate events set up. One will be for the finger-touch events, and one will be for the mouse events. The same scale factor algorithm as in bsbingo_scaled can be applied to each set of events to determine the correct x and y coordinate for the mouse for whichever device (Safari Desktop or Safari Mobile) the game is played on.

New global variables

We will be defining a new set of global variables that will be used to move the player ship on the game screen:

//touch
var mouseX;
var mouseY;
var touchX;
var touchY;

The touch and mouse listener functions will translate the position of the finger or mouse on the game screen to the mouseX and mouseY variables. touchX and touchY will be caught and used to set mouseX and mouseY when using a mobile device, while mouseX and mouseY will be used directly when using a desktop browser. We will demonstrate this code in the following section.

New listener functions

When the player ship starts, we will add these new functions to the gameStatePlayerStart() function from GeoBlaster Extended.

theCanvas.addEventListener("mousemove", onMouseMove, false);
theCanvas.addEventListener("touchmove", onTouchMove, false);

We are going to add a listener function for each of these and also a separate function that will translate the touchX and touchY values into mouseX and mouseY. This way, the game doesn’t need to know what type of device it is running on; it will work with both mouse and touch events in the same manner.

function onMouseMove(e) {
    var xFactor = theCanvas.width / window.innerWidth;
    var yFactor = theCanvas.height / window.innerHeight;

    var mouseX1 = event.clientX - theCanvas.offsetLeft;
    var mouseY1 = event.clientY - theCanvas.offsetTop;
    mouseX = mouseX1 * xFactor;
    mouseY = mouseY1 * yFactor;

    allMoveHandler(mouseX,mouseY);

}


function onTouchMove(e) {
    if (e.touches.item(0)) {
        targetEvent =  e.touches.item(0);
    }else{
        targetEvent =  e;
    }

    touchX1=targetEvent.clientX-theCanvas.offsetLeft;
    touchY1=targetEvent.clientY-theCanvas.offsetTop;
    xFactor =  theCanvas.width/window.innerWidth;
    yFactor = theCanvas.height/window.innerHeight;
    touchX=touchX1*xFactor;
    touchY=touchY1*yFactor;

    allMoveHandler(touchX,touchY);

    e.preventDefault();

}


function allMoveHandler(x, y) {
    mouseX=x;
    mouseY=y;
}

The onMouseMove() function creates the xFactor and yFactor values by using the current size of the canvas and browser window. It does this on each event just in case the window has changed sizes since the last event. These are translated into mouseX and mouseY coordinates that are passed into the allMoveHandler() function.

The allMoveHandler function takes whatever is passed in and sets the mouseX and mouseY values. This is not 100% necessary here, because they are global values, but the next function, onTouchMove, will set the touchX and touchY values and pass those in. Just in case we wanted to do more in the allMoveHander() function, we made it accept in the parameters and be called from both functions. This might be a little redundant in this example, but it could prove useful in a larger game as a reusable function.

The onTouchMove function looks a little strange. This is because not all browsers give off the same touch events. Some give a touch event off as an array, and some give it off as the actual event. To make sure we cover as many devices as possible, we first look to see whether the first element in the e.touches array exists. If it does, we use its attributes in the algorithm to find the current touch location. If not, we use the attributes of the event passed in directly (e).

Beyond that, the touchX and touchY values are calculated in the same manner as the mouseX and mouseY values for onMouseMove. We also need to make sure that the finger move event is not passed to the mobile Safari web browser. This would result in the browser window moving rather than the ship moving. We do this with the e.preventDefault() function call.

If you encounter mouseX and mouseY position problems when using the event.clientX and event.clientY values on a scrolling browser window, you can substitute them with event.pageX and event.page. In most browsers, these will help if the screen is scrolled.

Auto-fire

One other change we’ve made is to remove the need for the player to press any keys or tap the screen to fire bullets. We have added this code to the updatePlayer() function.

player.missileFrameCount++;
    if (player.missileFrameCount>player.missileFrameDelay){
        playSound(SOUND_SHOOT,.5);
        firePlayerMissile();
        player.missileFrameCount=0;

    }

The player.missileFrameCount and player.missileFrameDelay attributes were added to the player object in the gameStateNewgame() function from GeoBlaster Extended:

player.missileFrameDelay=5;
player,missileFrameCount=0;

Player movement

The player ship must now follow the mouse rather than respond to the arrow keys pressed by the user. We have removed checkKeys() function from the GeoBlaster Extended code base as well as all references to it. In its place, we have added the following code to the updatePlayer() function:

var radians=Math.atan2((mouseY)-player.y, (mouseX)-player.x)
var degrees=(radians * (180/ Math.PI));
var yChange=(mouseY-player.y)
var xChange=(mouseX-player.x)
var delay=16;
var yMove=(yChange/delay)*frameRateCounter.step;
var xMove=(xChange/delay)*frameRateCounter.step;

player.x=player.x+xMove;
player.y=player.y+yMove;


if (degrees <0) {
    player.rotation=359+degrees;
}else{
    player.rotation = degrees;
}

First, we find the radians value for the direction the player needs to point to follow the mouse. Next, we use the yChange and xChange values to find the difference in screen pixel location between the player position and the mouse position. Finally, we create the actual delta for the player movement (xMove and yMove). We have put in a delay value of 16. This value acts like a smooth easing function so that the player doesn’t zoom straight to the mouse (or finger) on each click. This value can be changed to easily modify the easing look and feel.

Finally we check to make sure the degrees value is not less than 0. If it is, we add 359 to the value. If it is not, we simply use the degree value as calculated. This keeps the player rotation between 0 and 359 and doesn’t allow any negative values.

Checking out the game

Now it’s time to check out how this all comes together. I have placed the live files at this site.

You can place them at any location you like. The game will be fully playable from a local folder, but to play it from a mobile device, you will need to go to the link above or place the files on the web server of your choice.

Let’s take a look at the three different versions of the game. First, Figure 10-16 shows the game being played in the desktop Safari Browser. (It will also work fine in Chrome as of this writing.)

Retro Blaster Touch scaled in the Safari desktop browser
Figure 10-16. Retro Blaster Touch scaled in the Safari desktop browser

Next, Figure 10-17 shows the mobile Safari version before it has been added the Home screen.

Retro Blaster Touch scaled in the Safari Mobile browser
Figure 10-17. Retro Blaster Touch scaled in the Safari Mobile browser

Finally, Figure 10-18 shows the game being played from the Home Screen icon. Notice that the browser bar and other navigation elements are eliminated from this version.

Retro Blaster Touch played from the iOS Home screen icon
Figure 10-18. Retro Blaster Touch played from the iOS Home screen icon

That’s as far as we are going to go with creating scaled, mobile versions of your applications. Both of these games and the ideas presented can be fully fleshed out to create much more elaborate applications. The goal was to demonstrate that HTML5 Canvas applications can easily be turned into apps that work on both the desktop and mobile Safari browsers in a similar manner and that it is very easy to turn them into apps that can be played right from the Home screen of an iOS device, just like an application downloaded from the iTunes store.

Sound does not work the same in all current browsers and platforms. For example, sounds in the Mobile Safari browser need to be triggered on an event, such as a button click. We could have added an HTML “fire” button to the screen, and this would have allowed us to play a shooting sound when the player’s missiles are fired.

Retro Blaster Touch Complete Game Code

The full source code and assets for Retro Blaster Touch are located at this site.

Beyond the Canvas

A nice set of tools and frameworks are available (with more emerging every day) that can help transform the look and feel of an HTML or an HTML5 application (not necessarily just on Canvas) into an iPhone-like application. These can be used in conjunction with a canvas app to provide a seamless iPhone look and feel for the user.

If you would like to explore mobile functionality further, we recommend the following technologies, which can be combined with other technologies, such as Cordova PhoneGap, to create very powerful mobile applications:

jQT

jQT is a framework that makes use of jQuery to target mobile-device-specific features across platforms that use WebKit (iOS, Palm, Nexus, and so on).

jQuery Mobile Framework

The jQuery Mobile Framework is another jQuery-based mobile framework for building cross-platform applications. It can be used to create a unified user interface across mobile platforms.

What’s Next?

As you can see, HTML5 Canvas is a powerful and easy way to target the iOS Safari browser. In this chapter, we built a small game to run in the Safari browser and then modified the application to run on the iPhone in full screen. After the simulation was successful, we modified the GeoBlaster Extended game from Chapter 9 to create a new game called Retro Blaster Touch. Finally, we were able to see this completed application running on an actual iOS device.

In Chapter 11, we will look at applying multiplayer capabilities to a canvas application using ElectroServer. We’ll also take a small tour of 3D in Canvas. We will continue to explore the Canvas by creating a framework for a drag-and-drop application, and finally, we will take a look at HTML5 in Microsoft Windows 8.