Created
May 28, 2019 12:26
-
-
Save amcdnl/d4e37601c7398c3f6acb9c6076b49dc6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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