Exercise: Space Fleet

Finish the classes behind a space shooter's fleet, a Ship with private shields, a Fighter that extends it and a Fleet that reports who can fly, and let the tests check each one.

Journeyman · JavaScript · OOP · Updated

Red Squadron is the player's fleet in a space shooter game, and its code isn't finished. The ships' shields can be changed by anyone, a tank can be "refueled" with negative fuel, the fighters can't fire, and the fleet's roll call crashes. You'll finish three classes, Ship, Fighter and Fleet, using everything in this chapter: methods and this, a class with a private field and a getter, a child class that extends its parent, and an arrow function that keeps this. Tests check every piece as you go.

Get the exercise code

In the Exercise box beside the lesson, choose Get the starter on GitHub to open its GitHub repository. Click Code, then Download ZIP, and unzip it into a folder for this course. Or clone it with Git.

The folder holds:

  • ship.js: the Ship class (problems 1 to 4), a finished Freighter class to learn from, and the start of Fighter (problem 5).
  • fleet.js: the Fleet class (problems 6 and 7).
  • mission.js: Red Squadron's mission log. It builds a fleet from your classes and prints what happens. Ready to use.
  • ship.test.js and fleet.test.js: the tests. You don't need to change these.

Open the folder in VS Code. All you need is Node.js; there's nothing to install.

What to do

Every problem sits in a comment, with what the code should do and examples. Some methods are already written and return a placeholder for now, such as takeHit:

  /*
   * 3. Take a hit: lower the shields by the damage, but never below 0.
   *    Return the shields that are left.
   *
   *    A ship with 100 shields: takeHit(30) -> 70, then takeHit(50) -> 20, then takeHit(50) -> 0
   */
  takeHit(damage) {
    return 0;
  }
  1. Make the shields private (problem 1). Declare #shields at the top of Ship and store the shields in it.
  2. Give the shields a getter (problem 2), so other code can read ship.shields but not change it.
  3. Finish takeHit and refuel (problems 3 and 4). refuel refuses an amount less than 0, and never fills past Ship.MAX_FUEL.
  4. Finish Fighter (problem 5): a constructor that passes the ship's details to Ship, a fire() method, and a describe() that adds the missiles to Ship's line.
  5. Finish readyToFly (problem 6): the names of the ships that can still fly.
  6. Fix rollCall (problem 7). Run node mission.js first and read the error it gives.

Check your work

Open VS Code's terminal with Terminal > New Terminal, and run the tests:

node --test

At first every test fails. That's expected: each problem you solve turns its tests green. When a test fails, its message says which problem it is and what it expected, for example Problem 4: refuel(-10) should return false.

To watch your classes at work, run the mission log:

node mission.js

At first it stops at the roll call with a TypeError. That's the bug in problem 7, and once problems 5 and 7 are done, the whole log prints.

Hints

  • Read Freighter before you write Fighter. It's a finished class that extends Ship, and Fighter follows the same pattern: super(...) first, then its own property, then an override of describe that starts from super.describe(). Inheritance with extends and super walks through it.
  • Two methods already read this.shields. After problem 1, canFly and describe would break, until the getter from problem 2 gives shields back to them. Private Fields, Getters and Setters shows a getter beside a private field.
  • A limit is a job for Math.max or Math.min. Math.max(0, number) never goes below 0, and Math.min(Ship.MAX_FUEL, number) never goes above the top.
  • Can it fire? A fighter fires only when it can fly and has a missile. Every ship already has a canFly getter you can ask, through this.
  • Who's before the dot? In rollCall, map calls the inner function with no object before the dot. Losing this, and How Arrow Functions Help shows the kind of function that takes this from the code around it, where this is the fleet.

When you're done

Every test is green, and node mission.js prints:

Ships in the fleet: 3
--- Roll call ---
Red Squadron: Comet
Red Squadron: Vega
Red Squadron: Mule One
--- Battle ---
Comet fires! Missiles left: 2
Vega fires! Missiles left: 0
Vega can't fire
Vega takes a hit. Shields left: 0
Comet takes a hit. Shields left: 70
--- Supply run ---
Mule One unloads 15 crates
Mule One refuels -10: refused
Mule One refuels 90: done
--- Status ---
Comet: shields 70, fuel 40, missiles 2
Vega: shields 0, fuel 10, missiles 0
Mule One: shields 150, fuel 100, cargo 25 crates
Ready to fly: Comet, Mule One

Now look back at your Fighter. If Ship's describe added the ship's speed tomorrow, would your fighter's line pick it up without a change? If it would, you reused the parent's work the way inheritance intends.

So what

A class that guards its own data, a child class that adds only what's new, and a group object that asks each member a question: that's the shape of most game code, and of shopping carts, playlists and dashboards too. The Virtual Pet project at the end of this module is built the same way.

Key terms

Related