Introduction to (Web) Programming

Lab 11: Game on!

Instructions


Goals


Console exercises

> Date.now()
> let t0 = Date.now();
[wait five seconds]
> let t1 = Date.now();
> t1 - t0

> let wizard = {name: 'Harry', house: 'Gryffindor'};
> wizard
> typeof wizard
> wizard.name
> wizard[house]
> wizard['house']
> wizard['name'] = 'Draco';
> wizard
> wizard.house = 'Slytherin';
> wizard
> wizard.age = 11;
> let p = 'age';
> wizard['p']
> wizard[p]
> Object.keys(wizard)
> wizard.hasOwnProperty('house')
> wizard.hasOwnProperty('House')

Experiment with the demo version of lab. Download and expand the starter archive; explore the files, particularly game.js (where most of your work will take place) and game.html (where you will need to make a few modifications and additions). Right "out of the box", the "player" (a green square) should be rendered on the canvas and can be controlled using the arrow keys. The canvas remains otherwise blank, and movement is unimpeded by the edges of the canvas.

Borders.

  1. Implement border cells around the edge of the canvas. First, complete drawBorder so that it draws filled-in cells on the perimeter of the canvas. Use at least two distinct loops to draw the four parts of the border (top, right, bottom and left). Use paintCell(pt,color) which draws a square of color and dimension CELL_SIZE by CELL_SIZE at the game-board position indicated by the coordinate-pair pt (which is an object containing an x and y property). Choose a color for the border. I recommend following the model of defining an (all caps!) const parameter such as BORDER_COLOR to represent that color. You can test this by simply reloading the page: the border should appear.

  2. Now modify playerMoveTo(pt) (which is called by playerMove which is called by keydownHandler) so that the game-board coordinates of the player are only modified (and set to the x, y values of pt) if the changed values would not place the player on or outside the border of the game grid. You can test this by reloading and trying to move the player into the border.


    Winning: Make the game winnable by adding an achievable goal.

  3. Add code to placeGoal so that the position of the game's "goal" (an imaginary flag to capture?) is randomly selected within the borders of the game board. Record the selected position (an object representing the column-row coordinate) as a property in the global object g. Choose a color for the goal. Modify render so that the goal is displayed (using paintCell) at the appropriate location with the color (e.g., GOAL_COLOR). Test by reloading.

  4. Modify gameStep so that if the player is in the same position as the goal, the game ends and a status message is displayed indicating victory. Change the value of g.gameOn. Test by moving the player onto the same cell where the goal appears.

  5. Optional. Add a game parameter MINIMUM_DISTANCE and modify the way the goal location is chosen so that it is at least that distance (in "Manhattan" game-board units) away from the player. (Use the supplied manhattanDistance function.)


    Losing. Make it possible to lose by running out of time. If the player has not won the game before a predetermined amount of time has elapsed, the game ends and a status message is displayed indicating the the player has lost the game by running out of time. There are numerous ways to accomplish this. I recommend against using a separate JavaScript timer. Instead, I suggest:

  6. Add a game parameter of the form TIME_LIMIT that represents (presumably in milliseconds) the amount of time allowed to lapse before the game ends. Modify initialize to add a global property (e.g., g.startTime) that marks a timestamp for when the game begins. Modify gameStep so that it checks if the amount of time lapsed (e.g., Date.now()-g.startTime) is more than the limit, in which case a similar course to winning should be taken (modify g.gameOn, update the status element). Test by setting the limit to be just a few seconds, reloading, and letting that much time pass.

  7. Add a small piece of HTML (perhaps using a paragraph containing a span element) to game.html that indicates how many seconds (not milliseconds) are remaining. Write a new function (e.g., showTime) that displays how much time remains, call that function from gameStep.


    Obstacles. Make the game more challenging by adding randomly placed interior walls. Again, there are many ways to do this. I suggest:

  8. Define a game parameter such as WALL_DENSITY that represents the probability that any non-border cell is occupied with a "wall". Add code to createWalls so it adds a property to the global object such as g.walls that is a two-dimensional array representing row-column positions on the board (as we have used for the green blocks exercises on the previous two labs). Use the supplied (and now familiar) makeMatrix function to initialize a two-dimensional array of false values. Use a loop within a loop and Math.random() to set some of the entries to be true to represent the presence of a wall. For example:

        ...
         while (i < ...) {   // loop over game-board rows
            ...
            while (j < ...) {   // loop over game-board columns
                if ( ... Math.random() ...) {
                    ....g.walls[...][...] = true;
                }
                ...
            }
            ...
        }
  9. Choose a color to represent interior walls (I recommend something distinct from the color of the border cells). Complete drawWalls so that it uses nested loops over g.walls and calls paintCell wherever a wall should be placed. Test by reloading.

  10. Modify playerMoveTo method so that the player cannot move into spaces occupied by a wall.

  11. Recommended: assure that the randomly generated walls do not sit on the starting locations for the player and the goal. This can be achieved easily by after creating walls, setting the values of the two-dimensional array to be false for the corresponding positions of the player and the goal.


    Invisible goals and cheating.

  12. Add a property to the global object (in placeGoal) that indicates that the goal has not yet been revealed. Modify render so the goal is only displayed if that property indicates that the goal has been revealed. At this point the game should be much harder to win since the player has no idea where the goal is.

  13. Add a case to keydownHandler so that if the player can "cheat" by forcing the goal's location to be revealed. You can use any key you like. (I use "r" for "reveal".)

  14. Optional: You may want to create a disincentive to revealing the goal (for example, by subtracting points from a score - if you are using one; or by reducing the amount of time remaining).


    Teleportation. Both because it is fun and because it gets around the problem of a randomly generated set of walls otherwise blocking access to the goal, implement the ability for the player to teleport by clicking on another cell.

  15. Add a mouseup (or mousedown) handler to your canvas (in initialize). Write the corresponding function which should look something like:

    function mouseupHandler(event) {
        let boundingRect = G.canvas.getBoundingClientRect();
        let pixelX = event.clientX - boundingRect.left;
        let pixelY = event.clientY - boundingRect.top;
        let column = ... ;
        let row = ... ;
        ... playerMoveTo(...) ...
    }

    To make this work you will need to compute the column and row coordinates in the grid that correspond to the clicked pixel coordinates on the canvas. (Hint: use division by CELL_SIZE.)

  16. Optional: As with "cheating", you may want to create a disincentive to teleporting.


Extensions