Gaming: Battle on the High Seas, Part 1

Jeff Friesen
Tweet
This entry is part 1 of 5 in the series Battle on the High Seas

Battle on the High Seas

Web browsers supporting HTML5’s Audio, Canvas, and Web Storage APIs are an exciting gaming platform. These APIs can be used to create interesting games with money-making potential. As a demonstration, this article begins a five-part HTML5 game development series focused on a simple SeaBattle game. Part 1 introduces SeaBattle, shows how to embed it in a Web page, and overviews its architecture. The game presented here has been tested in Chrome, Firefox, Internet Explorer 9, Opera 12, and Safari 5 desktop browsers.

Introducing SeaBattle

Years ago, I played a game in which a destroyer and multiple submarines engage in battle. Because this game was lots of fun to play, I implemented a simpler form of the game for this series. Figure 1 presents a snapshot of my SeaBattle game’s title screen. The destroyer photo was obtained from Wikimedia Commons.

SeaBattle Title Screen

Figure 1: The title screen introduces SeaBattle.

Figure 1’s title screen introduces you to SeaBattle and tells you to press the Return key to begin this game. When you press this key, you are greeted by a screen that’s similar to the screen shown in Figure 2.

SeaBattle Gameplay

Figure 2: A destroyer battles a submarine. Depth charge and torpedo sizes are exaggerated to improve the visibility of these game objects.

Figure 2 reveals a scene where you, the destroyer, appear in front of a starry background. The current score and the most recent high score (in parentheses) appear in the upper-left corner. The high score is retrieved from local storage. The number of destroyer images in the lower-right corner indicates the number of lives left.

Somewhere below you, a submarine enters the scene and starts firing torpedoes. You can try to evade a torpedo by using the left and right arrow keys. The destroyer image changes to reflect the new direction. Its speed changes when it reaches either canvas edge.

You can press the spacebar to fire up to two (at any one time) depth charges. When a depth charge hits the submarine, the submarine is destroyed and your score advances by 100 points. If the high score is exceeded, it is updated and saved in local storage.

The current round of gaming continues until the submarine is destroyed by a depth charge or the destroyer is destroyed by a torpedo. At this point, a message appears stating whether you won or lost and whether or not the game is over. When you restart an ended game, the score is reset to zero.

Embedding SeaBattle in a Web Page

SeaBattle consists of a SeaBattle.js JavaScript file that relies on jQuery and the jQuery HotKeys plugin (discussed in Part 2 of this series). To embed this game in a Web page, include these files as shown in Listing 1.

<script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script><script type="text/javascript" language="javascript" src="jquery.hotkeys.js"></script>
<script type="text/javascript" src="SeaBattle.js"></script>

Listing 1: SeaBattle relies on three external JavaScript files. SeaBattle.js must be included last. Next, embed a <script>element in the page’s body that initializes SeaBattle, and repeatedly executes a function that updates game state and redraws the canvas to reflect the new state. Listing 2 shows you one way to accomplish this task.

<script type="text/javascript">// <![CDATA[
  SeaBattle.init(800, 480);

  // The following function is courtesy of Opera Engineer Erik Mіller -- see
  // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
  (function()
   {
     var lastTime = 0;
     var vendors = ['ms', 'moz', 'webkit', 'o'];
     for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x)
     {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelRequestAnimationFrame = window[vendors[x]+'CancelRequestAnimationFrame'];
     }

     if (!window.requestAnimationFrame)
     {
       var f = function(callback, element)
               {
                 var currTime = new Date().getTime();
                 var timeToCall = Math.max(0, 16-(currTime-lastTime));
                 var id = window.setTimeout(function()
                                            {
                                              callback(currTime+timeToCall);
                                            }, timeToCall);
                 lastTime = currTime+timeToCall;
                 return id;
               };
       window.requestAnimationFrame = f;
     }

     if (!window.cancelAnimationFrame)
       window.cancelAnimationFrame = function(id)
                                     {
                                       clearTimeout(id);
                                     };
  }());

  (function gameloop()
  {
    SeaBattle.update();
    requestAnimationFrame(gameloop);
    SeaBattle.draw();
  })();
// ]]></script>

Listing 2: SeaBattle initializes and then enters an infinite update-then-draw loop.

Listing 2 first initializes the pre-created SeaBattle object by calling its init(width, height) function, which creates a <canvas> element of the specified width (800 pixels) and height (480 pixels), loads game resources, and performs other tasks.

Next, a cross-browser requestAnimationFrame() function that delegates to a browser-specific function is installed. The browser function produces smoother animation by scheduling a pixel-painting callback function for invocation just before the next browser window repaint.

Browsers that provide their own request animation frame functions (such as Mozilla’s mozRequestAnimationFrame() function) can automatically throttle back the frame rate when you switch to another tab. After all, there’s no point in the game running at top speed when its output isn’t visible. However, not all browsers support this feature: Internet Explorer 9 is an example. For these browsers, setInterval() is used to invoke the callback function. Regardless of which function is called, rendering occurs at up to 60 frames per second.

Lastly, Listing 2 specifies and invokes a gameloop() function, which defines SeaBattle’s game loop. This function performs the following tasks:

  1. Execute SeaBattle‘s update() function to compute new game state based on user input and other factors.
  2. Execute requestAnimationFrame(gameloop) to schedule gameloop() for invocation before painting the browser window (if “requestAnimationFrame()” is supported) or at the next time point (via setTimeout()).
  3. Execute SeaBattle‘s draw() function to redraw the canvas with the updated game state.

Overviewing SeaBattle’s JavaScript Architecture

At some point, you’ll want to enhance SeaBattle, so you’ll need to understand how it works. The first step in gaining this knowledge is to grasp the object’s overall JavaScript architecture. See Listing 3.

var SeaBattle =
{
  init: function(width, height)
        {
        },

  update: function()
          {
          },

  draw: function()
        {
        },

  MAX_DC: 2,
  MAX_TORP: 15,
  STATE_INIT: 0,
  STATE_TITLE: 1,
  STATE_PLAY: 2,
  STATE_WINLOSE: 3,
  STATE_RESTART: 4,

  allResourcesLoaded: function()
                      {
                      },

  intersects: function(r1, r2)
              {
              },

  makeDepthCharge: function(bound)
                   {
                   },

  makeExplosion: function(isShip)
                 {
                 },

  makeShip: function(x, y, bound1, bound2)
            {
            },

  makeSub: function(x, y, bound1, bound2)
           {
           },

  makeTorpedo: function(bound)
               {
               },

  rnd: function(limit)
       {
       },

  supports_html5_storage: function()
                          {
                          }
}

Listing 3: SeaBattle defines 19 static properties. Additional properties are dynamically added to this object.

Listing 3’s global SeaBattle object first presents a public API consisting of init(width, height), update(), and draw(). It then presents a private API that defines the following pseudo-constant (a variable pretending to be a constant) properties:

  • MAX_DC specifies the maximum number of depth charges that can be in play at any given time. A small value makes it harder to destroy the submarine and results in more interesting gaming. This pseudo-constant appears in init(width, height), update(), and draw().
  • MAX_TORP specifies the maximum number of torpedoes that can be in play at any given time. A larger value than the number of depth charges results in a more interesting game. This pseudo-constant appears in init(width, height), update(), and draw().
  • STATE_INIT identifies the game’s initial state. SeaBattle loads image and audio resources and displays an initialization message. The state changes to STATE_TITLE after all resources have loaded. This pseudo-constant appears in init(width, height), update(), and draw().
  • STATE_TITLE identifies the game’s title state. SeaBattle displays a message telling you to press Return to play the game. This pseudo-constant appears in update() and draw().
  • STATE_PLAY identifies the game’s play state. You interact with the game by pressing the left arrow, right arrow, and spacebar keys while SeaBattle remains in this state. This pseudo-constant appears in update() only.
  • STATE_WINLOSE identifies the game’s win/lose state. The game is set to this state after an explosion has finished, and is used to ensure that a win/lose message is displayed. This pseudo-constant appears in update() and draw().
  • STATE_RESTART identifies the game’s restart state. The game is set to this state after an explosion has finished and there are no lives left. It’s used to ensure that a “Game Over” message is displayed, to reset the score to zero, and to reset the total number of lives to four. This pseudo-constant appears in update() and draw().

The private API also defines the following function properties:

  • allResourcesLoaded() returns true when all image and audio resources have loaded; otherwise, it returns false.
  • intersects(r1, r2) returns true when the rectangle defined by r1 intersects the rectangle defined by r2; otherwise, it returns false.
  • makeDepthCharge(bound) creates a depth charge object with the specified lower bound. Depth charges disappear once they reach this bound.
  • makeExplosion(isShip) creates an explosion where isShip determines whether the ship or submarine is exploding.
  • makeShip(x, y, bound1, bound2) creates a new ship where the center location of its image is passed to x and y, and whose horizontal movement is bounded by bound1 on the left and bound2 on the right.
  • makeSub(x, y, bound1, bound2) creates a new submarine object where the center location of its image is passed to x and y, and whose horizontal movement is bounded by bound1 on the left and bound2 on the right.
  • makeTorpedo(bound) creates a torpedo with the specified upper bound. Torpedoes disappear once they reach this bound.
  • rnd(limit) returns a random integer from zero through limit-1.
  • supports_html5_storage() returns true when the browser supports the local aspect of Web storage; otherwise, it returns false.

Conclusion

SeaBattle is an example HTML5 game that leverages the Audio, Canvas, and Web Storage APIs. Now that you’ve been introduced to this game, have learned how to embed it in a Web page, and have received an architectural overview, you’re ready to move deeper. Next Friday, Part 2 begins this task by exploring the init(width, height), rnd(limit) and supports_html5_storage() functions.

Battle on the High Seas

Gaming: Battle on the High Seas, Part 2 >>

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

No Reader comments