Created
January 24, 2018 17:28
-
-
Save David-Else/707458147b99593458db57202807ef7a to your computer and use it in GitHub Desktop.
This file contains hidden or 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
/* http://www.elsewebdevelopment.com/ */ | |
(function () { | |
'use strict'; | |
const utils = { | |
events: {}, | |
// utils.subscribe("nameofevent", callback); | |
// utils.publish("nameofevent", 4); | |
// utils.unsubscribe("nameofevent", callback); | |
subscribe(eventName, fn) { | |
this.events[eventName] = this.events[eventName] || []; | |
this.events[eventName].push(fn); | |
}, | |
unsubscribe(eventName, fn) { | |
if (this.events[eventName]) { | |
for (let i = 0; i < this.events[eventName].length; i += 1) { | |
if (this.events[eventName][i] === fn) { | |
this.events[eventName].splice(i, 1); | |
break; | |
} | |
} | |
} | |
}, | |
publish(eventName, data) { | |
if (this.events[eventName]) { | |
this.events[eventName].forEach((fn) => { | |
fn(data); | |
}); | |
} | |
}, | |
randomNumber(upperNumberLimit) { | |
return Math.random() * upperNumberLimit; | |
}, | |
randomBoolean() { | |
return Boolean(Math.floor(Math.random() * 2)); | |
}, | |
toRadians(angle) { | |
return (angle * Math.PI) / 180; | |
}, | |
}; | |
const canvas = document.getElementById('canvas'); | |
const ctx = canvas.getContext('2d'); | |
let rightPressed; | |
let leftPressed; | |
let firePressed; | |
function keyHandler(event) { | |
// event.preventDefault(); | |
switch (event.code) { | |
case 'KeyF': | |
if (event.type === 'keydown') { | |
firePressed = true; | |
} else { | |
firePressed = false; | |
} | |
break; | |
case 'ArrowLeft': | |
if (event.type === 'keydown') { | |
leftPressed = true; | |
} else { | |
leftPressed = false; | |
} | |
break; | |
case 'ArrowRight': | |
if (event.type === 'keydown') { | |
rightPressed = true; | |
} else { | |
rightPressed = false; | |
} | |
break; | |
// no default | |
} | |
} | |
addEventListener('keydown', keyHandler); | |
addEventListener('keyup', keyHandler); | |
function render({ | |
onScreenObjects, | |
remainingZombieCount, | |
remainingBulletCount, | |
}) { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); // clear the canvas | |
ctx.font = '25px Arial'; | |
ctx.fillText(`Zombies ${remainingZombieCount}`, 10, 35); | |
ctx.fillText(`Bullets ${remainingBulletCount}`, 10, 70); | |
onScreenObjects.forEach((onScreenObject) => { | |
ctx.save(); | |
ctx.translate(onScreenObject.xPosition, onScreenObject.yPosition); | |
ctx.rotate(utils.toRadians(onScreenObject.angle)); | |
ctx.fillStyle = onScreenObject.color; | |
ctx.fillRect( | |
onScreenObject.width / -2, onScreenObject.height / -2, | |
onScreenObject.width, onScreenObject.height, | |
); | |
ctx.restore(); | |
}); | |
} | |
function createHeroObject({ | |
xPosition, | |
yPosition, | |
}) { | |
return { | |
description: 'hero', | |
width: 25, | |
height: 50, | |
xPosition, | |
yPosition, | |
angle: 0, | |
color: 'red', | |
spin(direction) { | |
switch (direction) { | |
case 'left': | |
this.angle -= 1; | |
break; | |
case 'right': | |
this.angle += 1; | |
break; | |
// no default | |
} | |
}, | |
}; | |
} | |
function createZombieObject({ | |
xPosition, | |
yPosition, | |
speed, | |
geneticStupidity, | |
zombieStupidityThreshold, | |
}) { | |
return { | |
description: 'zombie', | |
width: 15, | |
height: 15, | |
xPosition, | |
yPosition, | |
speed, | |
geneticStupidity, | |
zombieStupidityThreshold, | |
angle: 0, | |
color: 'green', | |
move(direction) { | |
const envrStupidity = utils.randomBoolean(); | |
let zombieIsClever = false; | |
if (this.geneticStupidity && envrStupidity) { | |
zombieIsClever = true; | |
this.color = 'orange'; | |
} | |
switch (direction) { | |
case 'up': | |
if (zombieIsClever) { | |
this.yPosition -= this.speed; | |
} else { | |
this.yPosition += this.speed; | |
} | |
break; | |
case 'down': | |
if (zombieIsClever) { | |
this.yPosition += this.speed; | |
} else { | |
this.yPosition -= this.speed; | |
} | |
break; | |
case 'left': | |
if (zombieIsClever) { | |
this.xPosition -= this.speed; | |
} else { | |
this.xPosition += this.speed; | |
} | |
break; | |
case 'right': | |
if (zombieIsClever) { | |
this.xPosition += this.speed; | |
} else { | |
this.xPosition -= this.speed; | |
} | |
break; | |
default: | |
throw new Error(`move() there is no direction called ${direction}`); | |
} | |
}, | |
}; | |
} | |
function createBulletObject({ | |
xPosition, | |
yPosition, | |
angle, | |
}) { | |
return { | |
description: 'bullet', | |
width: 5, | |
height: 10, | |
xPosition, | |
yPosition, | |
angle, | |
color: 'black', | |
fly() { | |
this.xPosition += 1 * Math.sin(utils.toRadians(this.angle)); | |
this.yPosition -= 1 * Math.cos(utils.toRadians(this.angle)); | |
}, | |
}; | |
} | |
// for es6 modules in firefox turn on dom.module.scripts in about:config | |
let tempHeroXpos; // for the animate loop so zombies can know where to attack | |
let tempHeroYpos; // this can go, we can find a better solution! | |
let tempHeroAngle; | |
let tempHeroWidth; | |
let tempHeroHeight; | |
const onScreenObjects = []; | |
const numberOfZombies = 50; | |
const initialBulletCount = 500; | |
const zombieStupidityThreshold = 0.75; | |
const distanceFromHeroRequired = 3; | |
const maxZombieSpeed = 0.5; | |
let remainingZombieCount = numberOfZombies; | |
let remainingBulletCount = initialBulletCount; | |
const shootSound = new Audio('../assets/shoot.wav'); | |
const deadZombieSound = new Audio('../assets/invaderkilled.wav'); | |
const deadPlayerSound = new Audio('../assets/explosion.wav'); | |
function createHero() { | |
onScreenObjects.push(createHeroObject({ | |
xPosition: canvas.width / 2, | |
yPosition: canvas.height / 2, | |
})); | |
} | |
function createBullet({ | |
fireAngle, | |
}) { | |
utils.publish('bulletFired', 'fire'); | |
onScreenObjects.push(createBulletObject({ | |
xPosition: tempHeroXpos, | |
yPosition: tempHeroYpos, | |
angle: fireAngle, | |
})); | |
remainingBulletCount -= 1; | |
} | |
function generateRandomZombiePosition(axis) { | |
// create random screen position, if greater than the middle on either axis push it away | |
function randomPosition(axisLength) { | |
const randomPos = utils.randomNumber(axisLength); | |
return (randomPos > axisLength / 2) ? | |
randomPos + (axisLength / distanceFromHeroRequired) : | |
randomPos - (axisLength / distanceFromHeroRequired); | |
} | |
// return random number for chosen axis for zombie position | |
return (axis === 'xAxis') ? | |
Math.round(randomPosition(canvas.width)) : | |
Math.round(randomPosition(canvas.height)); | |
} | |
function createZombies() { | |
for (let i = 0; i < numberOfZombies; i += 1) { | |
onScreenObjects.push(createZombieObject({ | |
xPosition: generateRandomZombiePosition('xAxis'), | |
yPosition: generateRandomZombiePosition('yAxis'), | |
speed: utils.randomNumber(maxZombieSpeed), | |
zombieStupidityThreshold, | |
geneticStupidity: utils.randomBoolean(), | |
})); | |
} | |
} | |
/** | |
* Act on the variables sent from the key handler in the view | |
*/ | |
function actOnKeyPress() { | |
if (firePressed && remainingBulletCount > 0) { | |
createBullet({ | |
fireAngle: tempHeroAngle, | |
}); | |
} | |
} | |
/** | |
* Make things move on screen | |
*/ | |
function animate() { | |
onScreenObjects.forEach((onScreenObject) => { | |
// Assign a temp variable to hero's x and y for zombie to detect, spin if button pressed | |
if (onScreenObject.description === 'hero') { | |
tempHeroXpos = onScreenObject.xPosition; | |
tempHeroYpos = onScreenObject.yPosition; | |
tempHeroAngle = onScreenObject.angle; | |
tempHeroWidth = onScreenObject.width; | |
tempHeroHeight = onScreenObject.height; | |
if (rightPressed) { | |
onScreenObject.spin('right'); | |
} else if (leftPressed) { | |
onScreenObject.spin('left'); | |
} | |
} | |
// Make the bullets fly in the direction they were fired | |
if (onScreenObject.description === 'bullet') { | |
onScreenObject.fly(); | |
} | |
// Make each zombie attack the hero, one iteration at a time | |
if (onScreenObject.description === 'zombie') { | |
// if (onScreenObject.geneticStupidity < zombieStupidityThreshold) { | |
// zombieIsClever = true; | |
// } | |
const zombieIsClever = false; | |
// If zombie is right of hero | |
if (tempHeroXpos < onScreenObject.xPosition) { | |
if (zombieIsClever) { | |
onScreenObject.move('left'); | |
} else { | |
onScreenObject.move('right'); | |
} | |
} | |
// If zombie is left of hero | |
if (tempHeroXpos > onScreenObject.xPosition) { | |
if (zombieIsClever) { | |
onScreenObject.move('right'); | |
} else { | |
onScreenObject.move('left'); | |
} | |
} | |
// If zombie is below hero | |
if (tempHeroYpos < onScreenObject.yPosition) { | |
if (zombieIsClever) { | |
onScreenObject.move('up'); | |
} else { | |
onScreenObject.move('down'); | |
} | |
} | |
// If zombie is above hero | |
if (tempHeroYpos > onScreenObject.yPosition) { | |
if (zombieIsClever) { | |
onScreenObject.move('down'); | |
} else { | |
onScreenObject.move('up'); | |
} | |
} | |
} | |
}); | |
} | |
/** | |
* Detect on-screen collisions | |
*/ | |
function detectCollision() { | |
const edgeOfScreen = 0; | |
// Check every zombie | |
onScreenObjects.forEach((zombie, zombieIndex) => { | |
const zombieCollidesWithHero = | |
zombie.xPosition < tempHeroXpos + tempHeroWidth && | |
zombie.xPosition + zombie.width > tempHeroXpos && | |
zombie.yPosition < tempHeroYpos + tempHeroHeight && | |
zombie.height + zombie.yPosition > tempHeroYpos; | |
if (zombie.description === 'zombie' && zombieCollidesWithHero) { | |
utils.publish('playerDead', 'deadPlayer'); | |
throw new Error('You died!'); | |
} | |
if (zombie.description === 'zombie') { | |
// Check every bullet while iterating through each zombie | |
onScreenObjects.forEach((bullet, bulletIndex) => { | |
const bulletHitsScreenBoundary = | |
bullet.yPosition < edgeOfScreen || | |
bullet.yPosition > (canvas.height - edgeOfScreen) || | |
bullet.xPosition < edgeOfScreen || | |
bullet.xPosition > (canvas.width - edgeOfScreen); | |
const bulletHitsZombie = | |
zombie.xPosition < bullet.xPosition + bullet.width && | |
zombie.xPosition + zombie.width > bullet.xPosition && | |
zombie.yPosition < bullet.yPosition + bullet.height && | |
zombie.height + zombie.yPosition > bullet.yPosition; | |
if (bullet.description === 'bullet' && bulletHitsScreenBoundary) { | |
onScreenObjects.splice(bulletIndex, 1); | |
} | |
if (bullet.description === 'bullet' && bulletHitsZombie) { | |
onScreenObjects.splice(zombieIndex, 1); | |
onScreenObjects.splice(bulletIndex, 1); | |
remainingZombieCount -= 1; | |
utils.publish('zombieDead', 'zombieDead'); | |
} | |
}); | |
} | |
}); | |
} | |
/** | |
* Play a sound when pub-sub sends a signal to do so | |
*/ | |
function playSound(data) { | |
switch (data) { | |
case 'fire': | |
shootSound.play(); | |
break; | |
case 'zombieDead': | |
deadZombieSound.play(); | |
break; | |
case 'deadPlayer': | |
deadPlayerSound.play(); | |
break; | |
default: | |
throw new Error(`playSound() there is no sound called ${data}`); | |
} | |
} | |
/** | |
* The main loop that runs repeatedly | |
*/ | |
function mainLoop() { | |
render({ | |
onScreenObjects, | |
remainingZombieCount, | |
remainingBulletCount, | |
}); | |
actOnKeyPress(); | |
animate(); | |
detectCollision(); | |
requestAnimationFrame(mainLoop); | |
} | |
/** | |
* INIT in an IIFE | |
*/ | |
(function init() { | |
createZombies(); | |
createHero(); | |
utils.subscribe('bulletFired', playSound); | |
utils.subscribe('zombieDead', playSound); | |
utils.subscribe('playerDead', playSound); | |
mainLoop(); | |
}()); | |
}()); | |
//# sourceMappingURL=bundle.js.map |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment