Gaming: Battle on the High Seas, Part 4
Last week, our gaming series dug deeper into SeaBattle’s architecture by discussing the SeaBattle
object’s update()
function along with its makeShip(x, y, bound1, bound2)
constructor. This is the fourth article in our five-part series, and continues to explore this architecture by covering the constructors for submarines, depth charges, torpedoes, and explosions. It also discusses intersects(r1, r2)
and collision detection.
Making a Submarine
The update()
function is responsible for creating the submarine and other game objects. It accomplishes submarine creation with help from the makeSub(x, y, bound1, bound2)
constructor. Listing 1 presents this constructor’s implementation.
makeSub: function(x, y, bound1, bound2) {
this.x = x;
this.y = y;
this.bound1 = bound1;
this.bound2 = bound2;
this.bbox = { left: 0, top: 0, right: 0, bottom: 0 };
this.LEFT = 0;
this.RIGHT = 1;
this.dir = (x >= SeaBattle.width) ? this.LEFT : this.RIGHT;
this.exploded = false;
this.height = SeaBattle.imgSubLeft.height;
this.vx = SeaBattle.rnd(5)+2;
this.width = SeaBattle.imgSubLeft.width;
this.draw = function() {
SeaBattle.ctx.drawImage((this.dir == this.LEFT)?
SeaBattle.imgSubLeft :
SeaBattle.imgSubRight,
this.x-this.width/2,
this.y-this.height/2);
}
this.getBBox = function() {
this.bbox.left = this.x-this.width/2;
this.bbox.top = this.y-this.height/2;
this.bbox.right = this.x+this.width/2;
this.bbox.bottom = this.y+this.height/2;
return this.bbox;
}
this.move = function() {
if (this.dir == this.LEFT)
{
this.x -= this.vx;
if (this.x-this.width/2 < this.bound1)
{
this.x += this.vx;
this.vx = SeaBattle.rnd(3)+1;
this.dir = this.RIGHT;
}
}
else
{
this.x += this.vx;
if (this.x+this.width/2 > this.bound2)
{
this.x -= this.vx;
this.vx = SeaBattle.rnd(3)+1;
this.dir = this.LEFT;
}
}
}
}
Listing 1: The move()
function automatically switches the submarine’s direction after it passes the left or right edge.
Listing 1 first saves its arguments in submarine object properties, and then introduces 11 more object properties:
bbox
references a rectangle object that serves as a bounding box for collision detection. This object is passed as an argument to theintersects(r1, r2)
function.LEFT
is a pseudo-constant used in conjunction with thedir
property.RIGHT
is a pseudo-constant used in conjunction with thedir
property.dir
specifies the submarine’s current direction.exploded
indicates whether or not the submarine has exploded.height
specifies the height of the submarine image in pixels.vx
specifies the submarine’s horizontal velocity in terms of the number of pixels the submarine moves.width
specifies the width of the submarine image in pixels.draw()
draws the submarine image coinciding with the submarine’sx
andy
properties.getBBox()
returns an updatedbbox
object. This object is updated to accommodate a change in the submarine’s horizontal position.move()
moves the submarine left or right.
Making a Depth Charge
When the spacebar is pressed, update()
attempts to create a depth charge object (only two depth charges can be in play at any one time). Listing 2’s makeDepthCharge(bound)
constructor is used to create the depth charge.
makeDepthCharge: function(bound) {
this.bound = bound;
this.bbox = { left: 0, top: 0, right: 0, bottom: 0 };
this.height = SeaBattle.imgDC.width;
this.width = SeaBattle.imgDC.height;
this.draw = function() {
SeaBattle.ctx.drawImage(SeaBattle.imgDC, this.x-this.width/2, this.y-this.height/2);
}
this.getBBox = function() {
this.bbox.left = this.x-this.width/2;
this.bbox.top = this.y-this.height/2;
this.bbox.right = this.x+this.width/2;
this.bbox.bottom = this.y+this.height/2;
return this.bbox;
}
this.move = function move() {
this.y++;
if (this.y+this.height/2 > this.bound)
return false;
return true;
}
this.setLocation = function(x, y) {
this.x = x;
this.y = y;
}
}
Listing 2: The depth charge’s current location coincides with the center of its image.
Listing 2 first saves the argument passed to its bound
parameter in a depth charge object property, and then introduces seven more object properties:
bbox
references a rectangle object that serves as a bounding box for collision detection.height
specifies the height of the depth charge image in pixels.width
specifies the width of the depth charge image in pixels.draw()
draws the depth charge image.getBBox()
returns an updatedbbox
object centered on the object’s currentx
andy
values.move()
advances the depth charge downward by a single pixel until the lower bound is passed.setLocation(x, y)
specifies the depth charge’s location, which coincides with the center of the depth charge image.
Making a Torpedo
When the center of the submarine is visible, a randomly generated integer equals a certain value, and less than 15 torpedoes are in play, update()
creates a torpedo object. The actual work of creating this object is performed by Listing 3’s makeTorpedo(bound)
constructor.
makeTorpedo: function(bound) {
this.bound = bound;
this.bbox = { left: 0, top: 0, right: 0, bottom: 0 };
this.height = SeaBattle.imgTorpedo.height;
this.width = SeaBattle.imgTorpedo.width;
this.draw = function() {
SeaBattle.ctx.drawImage(SeaBattle.imgTorpedo, this.x-this.width/2, this.y);
}
this.getBBox = function() {
this.bbox.left = this.x-this.width/2;
this.bbox.top = this.y;
this.bbox.right = this.x+this.width/2;
this.bbox.bottom = this.y+this.height;
return this.bbox;
}
this.move = function move() {
this.y--;
if (this.y < this.bound)
return false;
return true;
}
this.setLocation = function(x, y) {
this.x = x;
this.y = y;
}
}
Listing 3: The torpedo’s current location coincides with the top-center of its image.
Listing 3 first saves the argument passed to its bound
parameter in a same-named torpedo object property, and then introduces seven more object properties:
bbox
references a rectangle object that serves as a bounding box for collision detection.height
specifies the height of the torpedo image in pixels.width
specifies the width of the torpedo image in pixels.draw()
draws the torpedo image.getBBox()
returns an updatedbbox
object centered around the object’s currentx
value.move()
advances the torpedo upward by a single pixel. This function returns true until the top of the torpedo’s image passes its upper bound, at which point it returns false.setLocation(x, y)
specifies the torpedo’s location, which coincides with the top-center of the torpedo image. Its arguments are stored in thex
andy
properties of the torpedo object.
Detecting a Collision
Part 3’s update()
function relies on an intersects(r1, r2)
function to determine whether or not a collision between a torpedo and the ship or between a depth charge and the submarine has occurred. Listing 4 presents this function’s implementation.
intersects: function(r1, r2) {
return !(r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top);
}
Listing 4: Two rectangles are tested for intersection.
Listing 4 determines if its two rectangle arguments (returned from getBBox()
calls) intersect by first determining if the second rectangle (r2
) lies completely to the right or left of, below, or above the first rectangle (r1
) and then negating the result.
If you recall from Part 3, the ship’s bounding box is not fully vertically centered around the object’s current y location. Although the top part is vertically centered, the bottom is not because I assign this.y+2
instead of this.y+this.height/2
to this.bbox.bottom
.

Figure 1: The ship image is outlined with a red border to clearly show the extent of the empty vertical space.
Why the difference? Each of the left and right ship images reveals a lot of empty vertical space below the ship. Figure 1 shows the image of the ship facing left.
If I specified this.y+this.height/2
as the bottom bound, an intersecting torpedo would explode too far from the ship’s bottom to look believable. This problem is not present with the submarine, whose images don’t have an excessive amount of empty vertical space.
Making an Explosion
The update()
function responds to a collision by calling the makeExplosion(isShip)
constructor to create an explosion object. The passed Boolean argument is true when the ship is exploding and false otherwise. Listing 5 shows how this constructor is implemented.
makeExplosion: function(isShip) {
this.isShip = isShip;
this.counter = 0;
this.height = SeaBattle.imgExplosion[0].height;
this.imageIndex = 0;
this.width = SeaBattle.imgExplosion[0].width;
this.advance = function() {
if (++this.counter < 4)
return true;
this.counter = 0;
if (++this.imageIndex == 8)
{
if (this.isShip)
SeaBattle.ship.exploded = true;
else
SeaBattle.sub.exploded = true;
}
else
if (this.imageIndex > 16)
{
this.imageIndex = 0;
return false;
}
return true;
}
this.draw = function() {
SeaBattle.ctx.drawImage(SeaBattle.imgExplosion[this.imageIndex],
this.x-this.width/2, this.y-this.height/2);
}
this.setLocation = function(x, y) {
this.x = x;
this.y = y;
try
{
SeaBattle.audBomb.play();
}
catch (e)
{
// Safari without QuickTime results in an exception
}
}
}
Listing 5: An explosion starts to play its audio as soon as its location is specified.
Listing 5’s makeExplosion(isShip)
constructor first saves the argument passed to parameter isShip
in the explosion object’s isShip
property, and then introduces seven additional object properties:
counter
is used to slow down the explosion’s advance so that it doesn’t disappear too quickly.height
specifies the height of each explosion image in pixels.imageIndex
specifies the zero-based index of the next explosion image to display.width
specifies the width of each explosion image in pixels.advance()
advances the explosion each timecounter
equals four. WhenimageIndex
equals eight, almost half of the explosion is finished, and the exploding ship or submarine is removed.draw()
draws the next explosion image.setLocation(x, y)
specifies the explosion’s location, which coincides with the center of each explosion image. Its arguments are stored in thex
andy
properties of the explosion object.
After setting the explosion’s location, an explosion sound effect is played via SeaBattle.audBomb.play();
. If you’re using the Safari browser without Quicktime, this browser throws an exception. An exception handler could display a message or take some other action. Currently, we ignore the exception.
Conclusion
Our exploration of SeaBattle’s architecture is nearly complete. Next Friday, Part 5 completes this exploration by first showing you how the game’s scene is drawn on the canvas. Next, it briefly reviews HTML5’s Audio, Canvas, and Web Storage APIs to help newcomers to these APIs better understand SeaBattle. After providing ideas for enhancing this game, Part 5 ends this series by taking SeaBattle beyond the desktop.