Revisit the boulder dodge program from before, expanding it to include up to 10 falling boulders, and allowing the player to shoot (and destroy) boulders. It would be nice to have the player shoot bullets or something else that makes sense, though you can just use the boulder image set. The player should be able to have up to 10 projectiles active at once. Spawn the boulders randomly, and make sure that it's fast enough to have several on screen at once.

You can use the code below to get started. This uses a javascript class to make the code organized.

var spriteType = {
    NONE:             0,
    USERCONTROLLED:   1,
    BULLET:           2,
    ROCK:             3,
  };

// Code is from the HW14 assignment, csc4821
class Sprite {
  constructor(name, t) {
    this.name = name;
    this.active = false;
    this.type = t; //spriteType.NONE;
    this.X = 0;
    this.Y = 0;
    this.DX = 0;
    this.DY = 0;
    this.height = SpriteHeight;  // default
    this.width = SpriteWidth;    // default
  }
  SetType(t) {
    this.type = t;
  }
  SetXY(newX, newY) {
    this.X = newX;
    this.Y = newY;
  }
  SetDXDY(newDX, newDY) {
    this.DX = newDX;
    this.DY = newDY;
  }
  SetHeightWidth(h, w) {
    this.height = h;
    this.width = w;
  }
  UpdateXY(tempX, tempY) {
    this.X = tempX;
    this.Y = tempY;
  }
}

// Make the sprites as objects
let sprites = new Array();

sprites.push(new Sprite("player1", spriteType.USERCONTROLLED));
sprites[0].X = defaultPlayerX;
sprites[0].Y = defaultPlayerY;
sprites[0].active = true;
// Set up 10 bullets
for (var i=0; i<10; i++) {
  sprites.push(new Sprite("bullet", spriteType.BULLET));
}
// Set up 10 boulders
for (var i=0; i<10; i++) {
  sprites.push(new Sprite("boulder", spriteType.ROCK));
}

You will need to spawn a boulder, and the code below will help you use the class for this. Remember to set the X,Y and DX, DY.

    // Spawn a boulder
    // First, find an unused boulder
    var i = 1;  // start at 1 because 0 is player
    var found = false;
    while ((i<sprites.length) && (!found)) {
      if ((!sprites[i].active) && (sprites[i].type == spriteType.ROCK)) {
        found = true;
        // Set the X, Y
        // Set the DX, DY
        // Turn it on
        sprites[i].active = true;
        if (DEBUG)
          console.log("Set sprite " + i + " Active; spawn a boulder");
      }
      i++;
    }

Also, here is a function that will draw everything.

function drawEverything() {
  // The drawImage command lets you copy a rectangle from an image
  // to the canvas.
  // It has the pattern
  //   ctx.drawImage(a, b, c, d, e, f, g);
  // which means
  //   ctx = the context (on the canvas)
  //   a = the image draw
  //   b = the X offset into image a
  //   c = the Y offset into image a
  //   d = the width of the subimage
  //   e = the height of the subimage
  //   f = the X offset into the canvas, where you want the rectangle
  //   g = the Y offset into the canvas, where you want the rectangle
  //   h = the width of the rectangle on the canvas
  //   i = the height of the rectangle on the canvas
  // -MCW, Fall 2021
  // b,c,d,e define the source rectangle
  // f,g,h,i define the destination rectangle
  //
  // Often, h = d and i = e, that is, the image you show on the canvas
  // is the same size as the image on the sprite/tile sheet.
  // However, if h<d and i<e, it will appear smaller.
  //

  // "stars" is a long image, so only show WIDTHxHEIGHT at a time.

  // show mountains
  ctx.drawImage(stars, 0, Math.floor(stars_offset),
    WIDTH, HEIGHT, 0, 0, WIDTH, HEIGHT);
  // console.log('Showed stars from ' + stars_offset);

  // show the player
  for (var i=0; i<sprites.length; i++) {
    if (sprites[i].active) {
      switch (sprites[i].type) {
        case spriteType.USERCONTROLLED:
          SpriteCol = drawSprite(SpriteImage, sprites[i].X, sprites[i].Y,
            SpriteCol, SpriteRow, MaxSpriteCol);
          break;
        case spriteType.BULLET:
          // Could use something besides "boulderImage" here.
          boulderCol = drawSprite(boulderImage, sprites[i].X, sprites[i].Y,
            boulderCol, boulderRow, MaxBoulderCol);
          break;
        case spriteType.ROCK:
          // Could save the boulderCol to the sprites object...
          // currently, it is not used individually.
          boulderCol = drawSprite(boulderImage, sprites[i].X, sprites[i].Y,
            boulderCol, boulderRow, MaxBoulderCol);
          break;
      }
    }
  }
}

On the gamedev server, you can find the rolling boulders.png graphic, along with the white curve graphic, and the man in blue graphic. You can use these, or use other graphics. Here is a link to the original boulder dodge.