How to code Canyon Bomber (1977) – 6

I’ll be honest, 1977 was a pretty poor year for games. Most released seemed to be copies of already existing games with just a bit of a makeover. In 1977 alone there were 3 copies of Blockade. I made a tutorial on creating blockade last week!

The most unique game created in 1977 is Canyon Bomber, a somewhat strange idea for a game where two players battle it out to destroy more of a canyon than the other by releasing strategically placed bombs.

The Canyon

The first thing we’re gonna code up is the canyon, this is the thing the planes bomb. It is just simply going to be an array of blocks.

Block

Let’s create a class called Block. This is going to take an x and a y location and also a colour object (which we’re going to create).

class Block {
    constructor(x, y, colour) {
        this.x = x;
        this.y = y;

        this.r = colour.r;
        this.g = colour.g;
        this.b = colour.b;

        this.endY = y;
    }

    draw() {
      	// moves the block down 
        if (this.y < this.endY) {
            this.y += 2;
        }

        noStroke();
        fill(this.r, this.g, this.b);
        rect(this.x, this.y, 20, 20);
    }

  	// when there are no blocks underneath, we must move the block down
    moveDown() {

        if (this.y == this.endY) {
            this.endY += 20;
        }
    }

    // for when we want to switch the colour
    setRgb(rgb) {
        this.r = rgb.r;
        this.g = rgb.g;
        this.b = rgb.b;
    }
}

Block colour

Let’s create a BlockColour class which will hold the different types of colours for a block, we can pass this into the constructor for when we build a Block.

class BlockColour {
    constructor() {
        this.yellow = this.createYellow();
        this.blue = this.createBlue();
        this.red = this.createRed();
        this.rgbs = this.intialiseRgbs();
    }
    
    createYellow() {
        return {
            r: 255,
            g: 255,
            b: 0
        }
    }

    createBlue() {
        return {
            r: 8,
            g: 4,
            b: 191
        }


    } 

    createRed() {
        return {
            r: 255,
            g: 100,
            b: 100
        }
    }

    intialiseRgbs() {
        let rgbs = [];
        rgbs.push(this.yellow, this.red, this.blue);

        return rgbs;

    }

}

Canyon

The canyon holds this list of blocks, it also manages when a block gets removed, any blocks that are left above will shift down into that position. So every frame each block checks to see whether there is any space below it, if there is then it shifts it. Of course, it doesn’t check the bottom blocks because they should never have anything underneath them!

class Canyon {
    constructor() {
        this.blocks = [];
        this.blockColour = new BlockColour();
        this.initaliseBlocks();
    }

  	
    update() {
        this.updateColours();
        for (let i = this.blocks.length - 1; i >= 0; i--) {
            if (this.isSpaceBelow(this.blocks[i], i)) {
                this.blocks[i].moveDown();
            };
        }
    }

  	// when a block moves down it changes colour accordingly
    updateColours() {
        for (let block of this.blocks) {
            if (block.y >= height - 40) {
                block.setRgb(this.blockColour.blue);
            } else if (block.y >= height - 80) {
                block.setRgb(this.blockColour.red);
            } else if (block.y >= height - 120) {
                block.setRgb(this.blockColour.yellow);
            }
        }
    }
    
  	
    isSpaceBelow(block, i) {
        if (this.isBottomBlock(block)) {
            return false;
        }

        let isBlockBelow = true;
        for (let i = 0; i < this.blocks.length; i++) {
            if (block.x === this.blocks[i].x && this.blocks[i].y === block.y + 20) {
                return false;
            } 
        }
        return isBlockBelow;
    }

  	
    isBottomBlock(block) {
        if (block.y === height - 20) {
            return true;
        }
        return false;
    }

    draw() {
        for (let block of this.blocks) {
            block.draw();
        }
    }

    initaliseBlocks() {
        let startingY = height - 120;
        for (let i = 0; i < 6; i++) {
            for (let x = 0; x < width; x += 20) {
                let rgbPosition = floor(i / 2);
                this.blocks.push(new Block(x, startingY, this.blockColour.rgbs[rgbPosition]));
            }
            startingY += 20;
        }
    }

   

    removeBlock(x, y) {

        for (let i = this.blocks.length - 1; i >= 0; i--) {
        
            let xPos = this.blocks[i].x + 10;
            let yPos = this.blocks[i].y + 10;
            if (dist(x, y, xPos, yPos) < 10) {
                this.blocks.splice(i, 1);
            }
        }
    }


}

Sketch.js

What we can do to test the removal of blocks is to add a handler for clicking on them. So if we click a block, it get’s destroyed.

let canyon;
function setup() {
  createCanvas(400, 400);
  canyon = new Canyon();
}

function draw() {
  background(115,113, 236);
  frameRate(25);
  canyon.update();
  canyon.draw();
}


function mouseClicked() {
  canyon.removeBlock(mouseX, mouseY);
}

Now we have the basic mechanics for the game, let’s add the players!

Player/Bomber

These are the two images I created, I scaled them up so don’t worry about the size!

Go ahead and copy these into your game

As this is a two player game, the Bomber can be either going left or right, if you’re going left you are one player and if you’re going right you’re the other player. The bomber constructor takes a direction as an argument, as JavaScript doesn’t have enums I’ve used 0 to represent left to right and 1 to represent right to left!

class Bomber {

    constructor(direction, bomberImage) {
        this.direction = direction;
        
        // player starting position
        if (this.direction == 0) {
            this.x = -100;
        } else if (this.direction == 1) {
            this.x = width + 25;
        }
        this.bomberImage = bomberImage;
        this.y = random(0, 200);
        this.bombs = [];
        this.r = 2.5;
        this.speed = 3;

        this.timeTillNextShot = 0;
        this.score = 0;
    }

    // takes a list of blocks because we want to see if we have shot them
    update(blocks) {
        this.timeTillNextShot--;
        if (this.direction == 0) {
            this.x += this.speed;
        } else if (this.direction == 1) {
            this.x -= this.speed;
        }

        this.constrain();

        this.updateBombs(blocks);
    }
  
  	// Checks against the blocks to see if a bomb has hit them, if so give us a point
    updateBombs(blocks) {
        for (let bomb of this.bombs) {
            bomb.y += 3;

            if (this.direction == 0) {
                bomb.x += this.speed;
            } else if (this.direction == 1) {
                bomb.x -= this.speed;
            }
        }

        this.checkBombCollision(blocks);

    }
		
    checkBombCollision(blocks) {
        for (let j = this.bombs.length - 1; j >= 0; j--) {
            for (let i = blocks.length - 1; i >= 0; i--) {
                let bomb = this.bombs[j];
                let blockX = blocks[i].x + 10;
                let blockY = blocks[i].y + 10;
                if (dist(bomb.x, bomb.y, blockX, blockY) < this.r + 10) {
                    blocks.splice(i, 1);
                    bomb.resistance--;
                    this.score++;
                    if (bomb.resistance <= 0) {
                        this.bombs.splice(j, 1);
                        break;
                    }
                }
            }
        }
    }



    constrain() {
        if (this.direction == 0 && this.x > width) {
            this.x = -100;
            this.y = random(0, 200);
        } else if (this.direction == 1 && this.x <= -75) {
            this.x = width + 25;
            this.y = random(0, 200);
        }
    }

    draw() {
        this.drawBombs();
        image(this.bomberImage, this.x, this.y, this.bomberImage.width*3, this.bomberImage.height*3);

        this.drawScore();
    }

    drawBombs() {
        for (let bomb of this.bombs) {
            push();
            fill(255);
            ellipse(bomb.x, bomb.y, this.r * 2, this.r * 2);
            pop();
        }
    }

    drawScore() {

        push();

        let x;

        if (this.direction == 0) {
            fill(255, 58, 58);
            x = 100;
        } else if (this.direction == 1) {
            fill(195, 178, 52);
            x = 300;
        }

        textAlign(CENTER);
        textSize(50);
        text(this.score, x, 45);

        pop();
    }

    bomb() {
        if (this.timeTillNextShot <= 0 ) {
            let bomb = {
                x: this.x + 35,
                y: this.y + 20,
                resistance: 4
            }
            this.bombs.push(bomb);

            this.timeTillNextShot = 25;

        }
        
    }
}

I have added some comments to the Bomber.js class to try explain things in a little more detail. There is however, one function that I’d really like to take apart and explain.

   checkBombCollision(blocks) {
        for (let j = this.bombs.length - 1; j >= 0; j--) {
            for (let i = blocks.length - 1; i >= 0; i--) {
                let bomb = this.bombs[j];
                let blockX = blocks[i].x + 10;
                let blockY = blocks[i].y + 10;
                if (dist(bomb.x, bomb.y, blockX, blockY) < this.r + 10) {
                    blocks.splice(i, 1);
                    bomb.resistance--;
                    this.score++;
                    if (bomb.resistance <= 0) {
                        this.bombs.splice(j, 1);
                        break;
                    }
                }
            }
        }
    }

So this checkBombCollision function loops through all of the bombs backwards, we do this because if it has exceeded it’s resistance (So a bomb can only hit X amount of blocks) we want to remove this block from the array, looping through it backwards means we won’t get issues with the removal, thisexplains the reason for the backward looping in more detail. Notice that when we remove a bomb we use the key word break, this means we break out of the loop, so we no longer check that bomb against the rest of the blocks because it has been removed!

Sketch.js

In sketch, we want to add handlers for when the user presses a button to fire off a bomb. In the example below, player 1 uses space-bar and player 2 uses L. Also notice how the player objects take all the blocks on every update, this is so they can check to see if the bombs have hit them.

let canyon;
let bomber;
let bomber2;

let playerOneImage;

let bomberImage;
let bomber2Image;

function preload() { 
  bomberImage = loadImage("playerOne.png");
  bomber2Image = loadImage("playerTwo.png");
}


function setup() {
  createCanvas(400, 400);
  canyon = new Canyon();
  bomber = new Bomber(0, bomberImage);
  bomber2 = new Bomber(1, bomber2Image);
}

function draw() {
  background(115,113, 236);
  frameRate(25);
  canyon.update();
  canyon.draw();
  
  bomber.update(canyon.blocks);
  bomber.draw();
  
  bomber2.update(canyon.blocks);
  bomber2.draw();
}


function mouseClicked() {
  canyon.removeBlock(mouseX, mouseY);
}

function keyPressed() {
  if (keyCode === 32) { 
    bomber.bomb();
  } 
  // L for bomb
  if (keyCode === 76) {
    bomber2.bomb();
  }
}


Icing on the cake

The last thing to add which is also probably the easiest is the grey rectangle at the top of the screen which the scores are supposed to sit on.

in the draw function of sketch:

  push();
  noStroke();
  fill(112,110, 112);
  rect(0, 0, width, 60 );
  pop();

Some ideas for stuff you can add!

  • The actual game has some blocks which you can’t hit, add them!
  • Add a multiplier for the type of block you hit, the deeper the rock the higher the score.
  • Add more planes, in the game I think they rotate 3 different vehicles.
  • Add more players, there is no reason why this game has to be limited to two players!

Leave a Reply