Skip to content

Instantly share code, notes, and snippets.

@amcdnl
Created May 28, 2019 12:26
Show Gist options
  • Save amcdnl/d4e37601c7398c3f6acb9c6076b49dc6 to your computer and use it in GitHub Desktop.
Save amcdnl/d4e37601c7398c3f6acb9c6076b49dc6 to your computer and use it in GitHub Desktop.
/**
* Space invaders canvas game
* Inspiration: https://codepen.io/amcdaniel2/pen/MdQZpL
*/
export const spaceInvaders = canvas => {
let screen;
let gameSize;
let game;
let invaderCanvas;
let invaderMultiplier;
let invaderSize = 20;
let invaderAttackRate;
let invaderSpeed;
let invaderSpawnDelay = 250;
let i = 0;
let kills = 0;
let spawnDelayCounter = invaderSpawnDelay;
let invaderDownTimer;
var blocks = [
[3, 4, 8, 9, 10, 15, 16],
[2, 4, 7, 11, 14, 16],
[1, 4, 7, 11, 13, 16],
[1, 2, 3, 4, 5, 7, 11, 13, 14, 15, 16, 17],
[4, 7, 11, 16],
[4, 8, 9, 10, 16]
];
class Game {
invaders: any[] = [];
level = -1;
lost = false;
player = new Player();
invaderShots: any[] = [];
constructor() {
if (invaderDownTimer === undefined) {
invaderDownTimer = setInterval(function() {
for (i = 0; i < game.invaders.length; i++) {
game.invaders[i].move();
}
}, 1000 - this.level * 1.8);
}
}
destroy() {
this.player.destroy();
}
update() {
// Next level
if (game.invaders.length === 0) {
spawnDelayCounter += 1;
if (spawnDelayCounter < invaderSpawnDelay) {
return;
}
this.level += 1;
invaderAttackRate -= 0.002;
invaderSpeed += 10;
game.invaders = createInvaders();
spawnDelayCounter = 0;
}
if (!this.lost) {
// Collision
game.player.projectile.forEach(function(projectile) {
game.invaders.forEach(function(invader) {
if (collides(projectile, invader)) {
invader.end();
projectile.active = false;
}
});
});
this.invaderShots.forEach(function(invaderShots) {
if (collides(invaderShots, game.player)) {
game.player.end();
}
});
for (i = 0; i < game.invaders.length; i++) {
game.invaders[i].update();
}
}
// Don't stop player & projectiles.. they look nice
game.player.update();
for (i = 0; i < game.invaderShots.length; i++) {
game.invaderShots[i].update();
}
this.invaders = game.invaders.filter(invader => invader.active);
}
draw() {
if (this.lost) {
screen.fillStyle = 'rgba(0, 105, 255, .01)';
screen.fillRect(0, 0, gameSize.width, gameSize.height);
screen.font = '55px Blender Pro';
screen.textAlign = 'center';
screen.fillStyle = 'white';
screen.fillText('You lost', gameSize.width / 2, gameSize.height / 2);
screen.font = '20px Blender Pro';
screen.fillText(
'Points: ' + kills,
gameSize.width / 2,
gameSize.height / 2 + 30
);
} else {
screen.clearRect(0, 0, gameSize.width, gameSize.height);
screen.font = '10px Blender Pro';
screen.textAlign = 'right';
screen.fillText(
'Points: ' + kills,
gameSize.width,
gameSize.height - 12
);
}
screen.beginPath();
let i;
this.player.draw();
if (!this.lost) {
for (i = 0; i < this.invaders.length; i++) {
this.invaders[i].draw();
}
}
for (i = 0; i < this.invaderShots.length; i++) {
this.invaderShots[i].draw();
}
screen.fill();
}
invadersBelow(invader) {
return (
this.invaders.filter(b => {
return (
Math.abs(invader.coordinates.x - b.coordinates.x) === 0 &&
b.coordinates.y > invader.coordinates.y
);
}).length > 0
);
}
}
class Invader {
active = true;
coordinates: any;
size: any;
patrolX = 0;
speedX: any;
constructor(coordinates) {
this.coordinates = coordinates;
this.size = {
width: invaderSize,
height: invaderSize
};
this.speedX = invaderSpeed;
}
update() {
if (Math.random() > invaderAttackRate && !game.invadersBelow(this)) {
const projectile = new Projectile(
{
x: this.coordinates.x + this.size.width / 2,
y: this.coordinates.y + this.size.height - 5
},
{
x: 0,
y: 2
}
);
game.invaderShots.push(projectile);
}
}
draw() {
if (this.active) {
screen.drawImage(invaderCanvas, this.coordinates.x, this.coordinates.y);
}
}
move() {
if (this.patrolX < 0 || this.patrolX > 100) {
this.speedX = -this.speedX;
this.patrolX += this.speedX;
this.coordinates.y += this.size.height;
if (this.coordinates.y + this.size.height * 2 > gameSize.height) {
game.lost = true;
}
} else {
this.coordinates.x += this.speedX;
this.patrolX += this.speedX;
}
}
end() {
this.active = false;
kills += 1;
}
}
class Player {
projectile: any[] = [];
keyboarder = new KeyController();
shooterHeat = -3;
active = true;
size = {
width: 16,
height: 8
};
coordinates = {
x: (gameSize.width / 2 - this.size.width / 2) | 0,
y: gameSize.height - this.size.height * 2
};
destroy() {
this.keyboarder.destroy();
}
update() {
for (var i = 0; i < this.projectile.length; i++) {
this.projectile[i].update();
}
this.projectile = this.projectile.filter(projectile => projectile.active);
if (!this.active) {
return;
}
if (
this.keyboarder.isDown(this.keyboarder.KEYS.LEFT) &&
this.coordinates.x > 0
) {
this.coordinates.x -= 2;
} else if (
this.keyboarder.isDown(this.keyboarder.KEYS.RIGHT) &&
this.coordinates.x < gameSize.width - this.size.width
) {
this.coordinates.x += 2;
}
if (this.keyboarder.isDown(this.keyboarder.KEYS.Space)) {
this.shooterHeat += 1;
if (this.shooterHeat < 0) {
const projectile = new Projectile(
{
x: this.coordinates.x + this.size.width / 2 - 1,
y: this.coordinates.y - 1
},
{
x: 0,
y: -7
}
);
this.projectile.push(projectile);
} else if (this.shooterHeat > 12) {
this.shooterHeat = -3;
}
} else {
this.shooterHeat = -3;
}
}
draw() {
if (this.active) {
screen.rect(
this.coordinates.x,
this.coordinates.y,
this.size.width,
this.size.height
);
screen.rect(this.coordinates.x - 2, this.coordinates.y + 2, 20, 6);
screen.rect(this.coordinates.x + 6, this.coordinates.y - 4, 4, 4);
screen.fillStyle = '#CE003E';
screen.fill();
}
for (var i = 0; i < this.projectile.length; i++) {
this.projectile[i].draw();
}
}
end() {
this.active = false;
game.lost = true;
}
}
class Projectile {
active: any;
coordinates: any;
size: any;
velocity: any;
constructor(coordinates, velocity) {
this.active = true;
this.coordinates = coordinates;
this.size = {
width: 3,
height: 3
};
this.velocity = velocity;
}
update() {
this.coordinates.x += this.velocity.x;
this.coordinates.y += this.velocity.y;
if (this.coordinates.y > gameSize.height || this.coordinates.y < 0) {
this.active = false;
}
}
draw() {
if (this.active) {
screen.rect(
this.coordinates.x,
this.coordinates.y,
this.size.width,
this.size.height
);
}
}
}
class KeyController {
KEYS = {
LEFT: 37,
RIGHT: 39,
Space: 32
};
keyState: any = {};
keyCode = [37, 39, 32];
counter;
constructor() {
window.addEventListener('keydown', this.onKeydown);
window.addEventListener('keyup', this.onKeyUp);
}
destroy() {
window.removeEventListener('keydown', this.onKeydown);
window.removeEventListener('keyup', this.onKeyUp);
}
onKeydown = e => {
let counter = this.counter;
let keyCode = this.keyCode;
for (counter = 0; counter < keyCode.length; counter++) {
if (keyCode[counter] === e.keyCode) {
this.keyState[e.keyCode] = true;
e.preventDefault();
}
}
};
onKeyUp = e => {
let counter = this.counter;
let keyCode = this.keyCode;
for (counter = 0; counter < keyCode.length; counter++) {
if (keyCode[counter] === e.keyCode) {
this.keyState[e.keyCode] = false;
e.preventDefault();
}
}
};
isDown(keyCode) {
return this.keyState[keyCode] === true;
}
}
const collides = (a, b) => {
return (
a.coordinates.x < b.coordinates.x + b.size.width &&
a.coordinates.x + a.size.width > b.coordinates.x &&
a.coordinates.y < b.coordinates.y + b.size.height &&
a.coordinates.y + a.size.height > b.coordinates.y
);
};
const getPixelRow = rowRaw => {
let textRow: any[] = [];
let placer = 0;
let row = Math.floor(rowRaw / invaderMultiplier);
if (row >= blocks.length) {
return [];
}
for (var i = 0; i < blocks[row].length; i++) {
let tmpContent = blocks[row][i] * invaderMultiplier;
for (var j = 0; j < invaderMultiplier; j++) {
textRow[placer + j] = tmpContent + j;
}
placer += invaderMultiplier;
}
return textRow;
};
// Write Text
// -----------
const createInvaders = () => {
let invaders: any[] = [];
let i = blocks.length * invaderMultiplier;
while (i--) {
let j = getPixelRow(i);
for (var k = 0; k < j.length; k++) {
invaders.push(
new Invader({
x: j[k] * invaderSize,
y: i * invaderSize
})
);
}
}
return invaders;
};
const loop = () => {
game.update();
game.draw();
requestAnimationFrame(loop);
};
const invaderAsset = new Image();
invaderAsset.src = '//avatars1.githubusercontent.com/u/15002684?s=15&v=4';
invaderAsset.onload = function() {
invaderCanvas = document.createElement('canvas');
invaderCanvas.width = invaderSize;
invaderCanvas.height = invaderSize;
invaderCanvas.getContext('2d').drawImage(invaderAsset, 0, 0);
loop();
};
screen = canvas.getContext('2d');
if (window.innerWidth > 1200) {
screen.canvas.width = 1200;
screen.canvas.height = 500;
gameSize = {
width: 1200,
height: 500
};
invaderMultiplier = 3;
} else if (window.innerWidth > 800) {
screen.canvas.width = 900;
screen.canvas.height = 600;
gameSize = {
width: 900,
height: 600
};
invaderMultiplier = 2;
} else {
screen.canvas.width = 600;
screen.canvas.height = 300;
gameSize = {
width: 600,
height: 300
};
invaderMultiplier = 1;
}
kills = 0;
invaderAttackRate = 0.999;
invaderSpeed = 20;
spawnDelayCounter = invaderSpawnDelay;
game = new Game();
return game;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment