Campus Shuttle

This assignment is (with some modifications) the Campus Shuttle assignment developed by David J. Malan for Harvard University's CS 50 course as presented in the Nifty Assignments session at SIGCSE 2013.

You may use two days (Jan 21 and 22) to work on this assignment. When you finish, it will be time to propose your final project. Remember that you may do any assignment in this course with a partner, if you'd like.

Summary

Campus Shuttle challenges students to implement a web-based driving game that mashes together the Google Earth and Google Maps APIs. Once implemented, students can drive around Harvard's campus virtually. (I wanted to modify the assignment for the Williams campus, but our buildings have apparently not yet been mapped by Google Earth. The game's objective is to pick up some of Harvard's notable alums who appear as 2D avatars within Google Earth and drop them off at their respective dormitories.

The topics touched on in this exercise include HTML, CSS, JavaScript (but not jQuery), loops, conditions, variables, functions, arrays, and objects.

This assignment requires Internet access (for Google Earth and Google Maps APIs). It also requires installation of the (free) Google Earth browser plugin. Try running the demo below. If you're missing the plugin, you'll get a message telling you so. Loading the APIs requires a key, which I've obtained and set to work from cs.williams.edu IP addresses. If you want to work elsewhere, you'll need your own key.

Demo

Your Assignment

Your mission for this assignment is to implement your own shuttle service that picks up passengers all over the Harvard campus and drops them off at their houses. Your shuttle is already equipped with an engine, a steering wheel, and 12 seats. Shall we go for a spin? Here's how to drive with your keyboard:

Move Forward: W
Move Backward: S
Turn Left: left arrow
Turn Right: right arrow
Slide Left: A
Slide Right: D
Look Downward: down arrow
Look Upward: up arrow

The original description from which this assignment is taken says the following: Note that your mouse is not used for driving in this 3D world. In fact, don't even click on it, else you'll take away "focus" from our (and, soon, your) JavaScript code, the result of which is that the shuttle will no longer respond to your keystrokes. If that does happen, simply click the 2D map or anything above it (within the same window) to give focus back to the code; the shuttle should then respond to your keystrokes again.

I haven't found this to be a problem, but if you do, the instructions above should help you recover.

Now go for a ride! As you approach buildings, you may find that they get prettier and prettier as more satellite imagery is automatically downloaded. Try not to take any shortcuts through buildings, though you'll certainly find that you can do so. Familiarize yourself with the Harvard campus and surrounding area. Along the way, you'll likely seem some famous faces. Those will soon be your passengers.

In fact, go ahead and click the minus (-) sign in the top-left corner of the application's 2D map. You should see more and more people markers as you zoom out, each of which represents a passenger waiting for pickup. If you hover over each marker with your mouse, you'll see each passenger's name and origin. If you hover over a yellow house, you'll see the name of that dormitory. The blue bus, of course, represents you! You're welcome to click and drag the 2D map to see more of campus; as soon as you start driving again, the map will be re-centered around you.

Above the 2D map is a list of your 12 seats, each of which is currently empty. Above that, in the application's top-right corner, are two buttons: Pick Up and Drop Off. Neither works yet, but both will before long!

If you happen to get lost, just reload the page, and you'll be returned to a default location. Your soon-to-be passengers will also be pseudorandomly repositioned throughout campus.


Alright, let's take a stroll through this assignment's distribution code. Open up index.html first and read over the lines of HTML within. Notice first how this file references several others in its head, namely service.css, math3d.js, buildings.js, houses.js, passengers.js, shuttle.js, and service.js, as well as the URL of Google's JavaScript API. Notice next how the body tag has a few event handlers, each of whose implementations lives, as we'll soon see, in service.js. And notice how each of the div elements is uniquely identified with an id attribute so that we can stylize it with CSS or access it via JavaScript.

At the moment, the HTML within two of those div elements (#announcements and #chart) is meant to be temporary. Eventually, there will be some announcements, and there will be passengers in seats!

Next open up service.css, which stylizes that HTML. You don't need to understand all of the CSS within, but do know in general that div#foo, refers to the div element whose id is foo.

Now take a peek at buildings.js. Declared in that file is BUILDINGS, which is an array of objects, each of which represents a building on the Harvard campus. Associated with each building is a name and address, and a pair of coordinates that represents an edge of that building. The authors could have declared this array in service.js, but because it's so big, they decided to isolate it in its own file.

Similarly, passengers have their own file. In passengers.js is PASSENGERS, another array of objects, each of which this time represents a passenger on campus. Associated with each passenger is a username (i.e., a unique identifier), a name, and a house. Incidentally, each of those usernames maps to a similarly named JPEG in passengers. Note, though, that some houses comprise multiple buildings, and so even though Mather House appears in both passengers.js and buildings.js, Quincy House appears only in passengers.js. In buildings.js, meanwhile, are objects for Quincy House New Residence Hall, Quincy House Library, and (nice and confusingly) Mather Hall, Quincy House. And so you will find in houses.js a third (and final!) data structure, this one an object whose keys are houses' names and whose values are objects representing houses' coordinates. And so you can access the best house's coordinates with something like:

var lat = HOUSES["Mather House"].lat;
var lng = HOUSES["Mather House"].lng;

You may assume that the coordinates in houses.js are the official coordinates for passengers' destinations. In fact, you'll see yellow markers planted at those very coordinates on the 2D map to help out the driver. The authors did not plant placemarks at those coordinates on the 3D Earth, since they're not as easy to spot.

Now take a peek at shuttle.js. This file's a bit fancy, inasmuch as it effectively implements a data structure called Shuttle. You don't need to understand this file's entirety, but know that inside of a Shuttle are two properties: position, which encapsulates a shuttle's position (including its longitude and latitude), and states, which encapsulates a shuttle's various states.

Okay, now turn your attention to service.js, where you'll do most of your work, though you're welcome to modify any of this assignment's files, except for math3d.js (which is just a library), buildings.js, houses.js, and passengers.js. Atop service.js are a bunch of constants and globals. Just below those lines are two calls to google.load, which loads the two APIs on which this application relies:

You won't need to read through the entirety of that documentation; odds are you'll explore it as needed. But do read the first page or so of each to get a sense of what each API does.

Implemented in service.js are all of those event handlers you first saw in index.html. Scroll down to the implementation of load first. It's this function that embeds the 2D map and 3D Earth map in your browser. Next scroll back up to the implementation of initCB; this function gets called as soon as the Google Earth Plugin has loaded, and so it's in this function that the authors finish initializing the app. (If something goes wrong, it's failureCB that's instead called.) Notice, in particular, that initCB "instantiates" an object, shuttle, of type Shuttle.

Next take a look at the function called keystroke. It's this function that handles your keystrokes. In response to your keystrokes, this function changes the state of the shuttle simply by updating the appropriate property in shuttle.states with a value of true or false.

Now take a peek at the function called populate. It's this function that plants notable faces all over campus. Each face is implemented as a "placemark" on the 3D earth and as a "marker" on the 2D map.

Finally, take a look at the function called chart. That function renders a seating chart (in the div whose id is chart) as an ordered HTML list (0-indexed for your debugging convenience) for the shuttle's 12 seats. Notice how it iterates over shuttle.seats, an array that's initialized to be of size SEATS (i.e., 12) in shuttle.js. It looks like null represents an empty seat!


Alright, it's time start picking up passengers. Recall from index.html that, when the button labeled Pick Up is clicked, the function called pickup in service.js is called. Implement pick-ups as follows.

If the button is clicked when the shuttle is within 15.0 meters of a passenger and at least one seat is empty, that passenger should be given a seat on the shuttle and removed from both the 2D map and 3D earth. (If multiple passengers are within 15.0 meters, you should pick up as many of them as there are empty seats.) If the shuttle is not within 15.0 meters of any passenger, make an announcement to that effect. If the shuttle is within range of some passenger but no seats are free, make an announcement to that effect (and don't pick up the passenger). Any such announcements should be cleared (or replaced with some default text) as soon as the shuttle starts moving.

Not sure how to proceed? Whenever in doubt, peruse the APIs' documentation, and turn to Google (the search engine) for tips. But here are some hints to get you started on pick-ups.

To calculate the shuttle's distance, d, from some point (lat, lng), you'll probably want something like:

var d = shuttle.distance(lat, lng);

To remove a placemark, p, from the 3D Earth, you'll probably want something like:

var features = earth.getFeatures();
features.removeChild(p);

To remove a marker, m, from the 2D map, you'll probably want something like:

m.setMap(null);

Unfortunately, populate, at the moment, doesn't remember the placemarks or markers that it plants, so you may need to tweak populate as well, per its TODO. Perhaps you could store those placemarks and markers in some global array(s) and/or object(s) so that you can remove them from the 3D Earth and 2D map, respectively, later?

And how to give a picked-up passenger a seat? Odds are you'll want to update shuttle.seats, but be sure not to add extra seats. And odds are you'll want to update div#seats any time that array is updated by calling chart. But, at the moment, chart only knows how to display empty seats; be sure to replace its TODO with some code that displays picked-up passengers' names and houses, so that you know who and where to drop off.

As for making announcements, recall that code like that below will get the job done:

document.getElementById("announcements").innerHTML = "hello, world";

Alright, it's now time to implement drop-offs! Recall from buildings.js that each passenger lives in a house. And recall from index.html that, when the button labeled Drop Off is clicked, the function called dropoff in service.js is called. Implement drop-offs as follows.

If the button is clicked when the shuttle is within 30.0 meters of an on-board passenger's house, that passenger should be dropped of by emptying their seat. (If there are multiple passengers on board that live in that house, all should be dropped off in this manner.) If the shuttle is not within 30.0 meters of any passenger's house, make an announcement to that effect. Any such announcement should be cleared (or replaced with some default text) as soon as the shuttle starts moving.

No need to re-plant a placemark or marker for dropped-off passengers; assume they're going to head straight inside. As for passengers who are still on board after a drop-off, be sure not to make them change seats just because some other seat is now empty.


Nice work! Suffice it to say this shuttle service is starting to feel like a game. If you'd like, you can implement additional features. For example: