Commit b8b50f66 authored by Matthew Odle's avatar Matthew Odle

move common functionality to canvas-libs

parent 057ae3e8
/*jslint white: true */
var collisions = {
// TODO abstract non-centipede functionality and move to canvas-libs
check : function() {
Object.keys(lasers.lasers).forEach(key => {
let targets = this.getLaserTargets();
......@@ -9,7 +10,7 @@ var collisions = {
Object.keys(players.players).forEach(player =>
this.checkPlayerVsEnemies(players.players[player], this.getPlayerEnemies())
);
this.removeDestroyedTargets();
this.removeDestroyedTargets(targets);
},
getLaserTargets : function() {
let targets = [];
......
/*jslint white: true */
function Component(args) {
this.remove = false;
this.speedX = 0;
this.speedY = 0;
this.x = args.x;
this.y = args.y;
this.width = args.width;
this.height = args.height;
if (args.background) {
this.background = args.background;
};
this.color = args.color;
if (args.fontSize) {
this.fontSize = args.fontSize;
};
if (args.constructorFunctions) {
Object.keys(args.constructorFunctions)
.forEach(theFunction => args.constructorFunctions[theFunction](this));
};
this.loadImages = function(images) {
if (!images) {return;};
Object.keys(images).forEach(key => images[key].image.src = knobsAndLevers.mediaPath + images[key].filename);
};
this.name = args.name;
if (args.extraArgs) {
this.type = args.extraArgs.type;
this.images = args.extraArgs.images;
this.loadImages(this.images);
if (args.extraArgs.speed) {
this.speedX = args.extraArgs.speed.x;
this.speedY = args.extraArgs.speed.y;
};
};
this.update = function() {
if (this.background) {
this.background.update();
};
let ctx = game.gameArea.context;
ctx.fillStyle = this.color;
if (this.type == 'text') {
this.makeText(ctx);
} else if (['background', 'laser'].includes(this.type)) {
this.makeARectangle(ctx);
} else if (knobsAndLevers.components.imageTypes.includes(this.type)) {
customComponents.drawComponent(ctx, this);
};
};
this.stop = function() {
this.speedX = 0;
this.speedY = 0;
},
this.makeText = function(ctx) {
ctx.font = this.fontSize + " " + knobsAndLevers.text.font;
ctx.fillText(this.text, this.x, this.y);
};
this.makeARectangle = function(ctx) {
ctx.fillRect(this.x, this.y, this.width, this.height);
};
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
};
this.crashWith = function(otherObject) {
let crash = true;
if (this.getBottom() < otherObject.getTop() || this.getTop() > otherObject.getBottom() || this.getRight() < otherObject.getLeft() || this.getLeft() > otherObject.getRight()) {
crash = false;
};
return crash;
};
this.crashWithXOnly = function(otherObject) {
let crash = true;
// delay collision slightly by allowing objects to overlap by 1 pixel
if (this.getRight() < otherObject.getLeft() + 1 || this.getLeft() > otherObject.getRight() - 1) {
crash = false;
};
return crash;
};
this.crashWithYOnly = function(otherObject) {
let crash = true;
if (this.getBottom() < otherObject.getTop() + 1 || this.getTop() > otherObject.getBottom() - 1) {
crash = false;
};
return crash;
};
this.crashWithMiddle = function(otherObject) {
let crash = false;
if (this.crashWithMiddleX(otherObject) && this.crashWithYOnly(otherObject)) {
crash = true;
};
return crash;
};
this.crashWithMiddleX = function(otherObject) {
let thisMiddleX = this.getMiddleX();
let otherMiddleX = otherObject.getMiddleX();
return thisMiddleX < otherMiddleX + 5 && thisMiddleX > otherMiddleX - 5 && this.crashWithYOnly(otherObject);
};
this.crashWithMiddleY = function(otherObject) {
let thisMiddleY = this.getMiddleY();
let otherMiddleY = otherObject.getMiddleY();
return thisMiddleY < otherMiddleY + 5 && thisMiddleY > otherMiddleY - 5;
}
this.getMiddleX = function() {
return this.x + this.width / 2;
};
this.getMiddleY = function() {
return this.y + this.height / 2;
};
this.getTop = function() {
return this.y;
};
this.getBottom = function() {
return this.y + this.height;
};
this.getLeft = function() {
return this.x;
};
this.getRight = function() {
return this.x + this.width;
};
};
var customComponents = {
// TODO move drawComponent to canvas-libs
drawComponent : function(ctx, obj) {
let key = this.imageKeys[obj.type](obj);
if (!obj.images) {
......
/*jslint white: true */
var playerConstants = {
watchPositions : {
'up' : ['belowTop'],
'right' : ['insideRight'],
'down' : ['aboveBottom'],
'left' : ['insideLeft'],
'upRight' : ['belowTop', 'insideRight'],
'downRight' : ['aboveBottom', 'insideRight'],
'downLeft' : ['aboveBottom', 'insideLeft'],
'upLeft' : ['belowTop', 'insideLeft'],
},
eligibleDirections : {
'up' : true,
'right' : true,
'down' : true,
'left' : true,
'upRight' : true,
'downRight' : true,
'downLeft' : true,
'upLeft' : true,
},
};
var players = {
players : {},
activeDirection : undefined,
boundaries : {},
died : false,
players = {
currentSelection : undefined,
init : function() {
while (Object.keys(this.players).length < game.numberOfPlayers) {
let player = new Component(knobsAndLevers.player.args);
player.name = 'player' + (Object.keys(this.players).length + 1);
player.eligibleDirections = supporting.clone(playerConstants.eligibleDirections);
this.players[player.name] = player;
};
console.log('players initialized', this.players);
},
manage : function() {
Object.keys(this.players).forEach(key => {
let player = this.players[key];
this.move(player);
this.update(player);
});
},
update : function(player) {
player.update();
},
reset : function() {
Object.keys(this.players).forEach(key => {
let player = this.players[key];
player.x = knobsAndLevers.player.startX[0];
player.y = knobsAndLevers.player.startY;
});
},
move : function(player) {
this.stop(player);
this.setBoundaries(player);
this.determineEligibleDirections(player);
this.moveTheThing(player);
},
stop : function(player) {
player.speedX = 0;
player.speedY = 0;
},
setBoundaries : function(player) {
this.boundaries.belowTop = player.getTop() > knobsAndLevers.player.topLimit;
this.boundaries.insideRight = player.getRight() < game.gameArea.canvas.width;
this.boundaries.aboveBottom = player.getBottom() < game.gameArea.canvas.height;
this.boundaries.insideLeft = player.getLeft() > 0;
},
determineEligibleDirections : function(player) {
player.eligibleDirections = supporting.clone(playerConstants.eligibleDirections);
Object.keys(playerConstants.watchPositions).forEach(direction => {
playerConstants.watchPositions[direction].forEach(playerPosition =>
player.eligibleDirections[direction] = this.boundaries[playerPosition] && player.eligibleDirections[direction]
);
Object.assign(this, playersBase);
this.buildPlayers(game.numberOfPlayers, knobsAndLevers.player.args);
Object.keys(this.functionOverrides).forEach(element => {
this[element] = this.functionOverrides[element];
});
},
moveTheThing : function(player) {
let speed = this.determineSpeed(player);
if (!speed) {
return;
};
this.updatePosition(player, speed);
if (collisions.withMushrooms(player)) {
this.revertPosition(player, speed);
};
},
determineSpeed : function(player) {
return controls.getPositionModifiers(this.boundaries, knobsAndLevers.player.speed.value, player)
},
updatePosition : function(player, modifier) {
player.speedX = modifier.x ? modifier.x : player.speedX;
player.speedY = modifier.y ? modifier.y : player.speedY;
player.newPos();
},
revertPosition : function(player, modifier) {
player.speedX = -1 * (modifier.x ? modifier.x : player.speedX);
player.speedY = -1 * (modifier.y ? modifier.y : player.speedY);
player.newPos();
console.log('players initialized');
},
functionOverrides : {
setBoundaries : function(player) {
this.boundaries.belowTop = player.getTop() > knobsAndLevers.player.topLimit;
this.boundaries.insideRight = player.getRight() < game.gameArea.canvas.width;
this.boundaries.aboveBottom = player.getBottom() < game.gameArea.canvas.height;
this.boundaries.insideLeft = player.getLeft() > 0;
},
},
};
var templates = {
marker : {},
baseMarkerParams : {},
init : function() {
this.baseMarkerParams = {
width : 15,
height : 15,
extraArgs : {type : 'player', images : knobsAndLevers.player.args.extraArgs.images},
};
this.marker = new Component(
Object.assign(this.baseMarkerParams, {
x : 700,
y : knobsAndLevers.text.gameInfoHeight - 15,
})
);
console.log('templates initialized');
},
};
var dom = {
links : {
devblog : {text : 'dev blog.', url : 'http://blog.matthewodle.com/category/centipede/'},
source : {text : 'gitlab.', url : 'https://gitlab.com/taciturn-pachyderm/centipede'},
otherGames : {text : 'other games.', url : 'http://blog.matthewodle.com/games/'},
},
instructions : '<strong>Centipede!</strong><br><strong>WASD</strong> : move<br><strong><strong>',
mobileWarning : "Mobile is not supported.<br><br>" +
"The use of a keyboard is required.<br><br>" +
"Sorry!<br><br>" +
"To show how bad we feel, here's a gif so you can see what you're missing (that's not rude at all, we promise!)<br><br>" +
"<img src='app/static/media/images/centipede.gif' style='width: 100%;'></img>" +
"<br><br><br><br><br><br><br><br>.",
init : function() {
this.addElement(this.getLinksElement());
if (supporting.isMobile()) {
this.addElement(this.getMobileMessageElement());
return;
};
console.log("dom initialized");
},
addElement : function(element) {
document.body.insertBefore(element, document.body.childNodes[0]);
},
getLinksElement : function() {
let element = document.createElement('div');
element.className = 'linkButtonWrapper';
Object.keys(this.links).forEach( link => {
let aLink = document.createElement('div');
aLink.className = 'linkButton';
aLink.onclick = function() { window.open(dom.links[link].url) };
aLink.innerHTML = this.links[link].text;
element.appendChild(aLink);
});
return element;
},
getMobileMessageElement : function() {
let element = document.createElement('div');
element.innerHTML = this.mobileWarning;
return element;
},
};
/*jslint white: true */
function GameArea() {
this.canvas = document.createElement("canvas"),
this.xVertices = [],
this.yVertices = [],
this.canvas.width = knobsAndLevers.canvas.width;
this.canvas.height = knobsAndLevers.canvas.height;
this.gridSquareSideLength = knobsAndLevers.general.gridSquareSideLength;
this.firstMushroomLayer = knobsAndLevers.general.gridSquareSideLength * 2;
this.start = function() {
this.loadCanvas();
this.loadFrame();
this.loadFont();
this.loadBackground();
};
this.loadCanvas = function() {
this.context = this.canvas.getContext("2d");
document.getElementById("canvas-wrapper").appendChild(this.canvas);
};
this.loadFrame = function() {
this.frameNo = 0;
this.interval = setInterval(main.updateGameState, supporting.intervalDivisor);
};
this.loadFont = function() {
let theLoadedFont = new FontFace('press-start', 'url(./app/static/css/fonts/prstartk.ttf)');
theLoadedFont.load().then((font) => {
document.fonts.add(font);
console.log('Font added', font);
});
};
this.loadBackground = function() {
let backgroundId = 'canvas-background';
if (!document.getElementById(backgroundId)) {
let canvasBackground = new Image();
canvasBackground.src = 'app/static/media/images/centipede.gif';
canvasBackground.id = backgroundId;
document.getElementById("canvas-wrapper").appendChild(canvasBackground);
};
};
this.removeBackground = function() {
let background = document.getElementById('canvas-background');
if (!background) {
return;
};
background.parentNode.removeChild(background);
};
this.setGridVertices = function() {
this.xVertices = this.getXVertices();
this.yVertices = this.getYVertices();
};
this.getXVertices = function() {
let x = 0;
let vertices = [];
while (x < this.canvas.width) {
vertices.push(Math.ceil(x));
x += this.gridSquareSideLength;
};
return vertices;
};
this.getYVertices = function() {
let y = this.firstMushroomLayer;
let vertices = [];
while (y < this.canvas.height) {
vertices.push(Math.ceil(y));
y += this.gridSquareSideLength;
};
return vertices;
};
this.setGridVertices();
this.clear = function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
};
this.stop = function() {
clearInterval(this.interval);
};
};
var game = {
paused : true,
running : false,
gameOver : false,
timeSinceGameOver : 0,
delayed : 0,
delayEndTime : 300,
keysDown : {},
activeCheats : {},
numberOfPlayers : 1,
activePlayers : 1,
init : function() {
this.gameArea = new GameArea();
Object.assign(this, gameBase);
this.gameArea.init(knobsAndLevers);
console.log('game initialized');
},
start : function() {
if (supporting.isMobile()) {
this.gameArea.stop();
return;
};
menus.reset();
this.paused = true;
this.gameArea.start();
},
levelIsOver : function() {
return centipedes.numberSpawned === centipedes.numberKilled && this.gameArea.frameNo !== 0;
},
startNextFrame : function() {
this.gameArea.clear();
this.gameArea.frameNo += 1;
},
getFrameNo : function() {
return this.gameArea.frameNo;
},
cheatsAreActive : function() {
return supporting.getFirstTruthy(game.activeCheats);
},
manageLevel : function() {
this.resetSomeThings();
this.levelOver = false;
metrics.currentLevel += 1;
},
setDiedText : function() {
texts.diedText.text = "You died.";
texts.diedText.update();
},
managePause : function() {
texts.pausedMessage.text = "Paused";
texts.pausedMessage.update();
sounds.stopAllSounds();
},
manageDeath : function() {
this.resetMoreThings();
texts.diedText.text = "";
players.died = false;
},
manageGameOver : function() {
if (this.gameOver) {
this.timeSinceGameOver += 1;
sounds.stopAllSounds();
this.showGameOver();
if (this.timeSinceGameOver > knobsAndLevers.game.gameOverDelay) {
this.resetTheWholeTamale();
};
};
},
showGameOver : function() {
texts.gameOver.text = "Game Over";
texts.gameOver.update();
},
resetSomeThings : function() {
this.gameArea.frameNo = 0;
centipedes.clear();
lasers.clear();
},
resetMoreThings : function() {
this.resetSomeThings();
intervalCreatures.clear();
spiders.clear();
players.reset();
},
resetTheWholeTamale : function() {
this.gameOver = false;
this.timeSinceGameOver = 0;
metrics.lastScore = metrics.score.player1.value;
mushrooms.clear();
init.afterGameOver();
menus.reset();
},
};
/*jslint white: true */
var hud = {
update : function() {
this.updateScore();
this.updateLives();
// this.updateLevel();
},
updateLives : function() {
metrics.livesMarker.update();
texts.livesDisplay.text = metrics.lives.player1;
texts.livesDisplay.update();
},
updateLevel : function() {
texts.level.text = "Level: " + metrics.currentLevel;
texts.level.update();
},
updateScore : function() {
metrics.score.player1.text = metrics.score.player1.value;
metrics.score.player1.update();
},
};
var init = {
run : function() {
console.log('init the things, yo');
knobsAndLevers.init();
leaderboard.init();
templates.init();
dom.init();
game.init();
initials.init();
intervalCreatures.init();
leaderboard.init();
main.init();
mainMenu.init();
menus.init();
game.init();
metrics.init();
spiders.init();
dom.init();
players.init();
sounds.init();
spiders.init();
texts.init();
intervalCreatures.init();
console.log("game initialized");
},
afterGameOver : function() {
......@@ -28,6 +32,6 @@ var init = {
game.running = false;
console.log("game reset");
},
}
};
init.run();
// abstract common knobsAndLevers and move them to canvas-libs
var knobsAndLevers = {
init : function() {
this.general.init(this);
......
var leaderboard = {
// TODO leaderboard functions only work on chrome
// either disable the leaderboard menu option when not in chrome
// or figure out firefox storage usage
saveScore : function(initials, score) {
console.log('saving score');
try {
let currentLeaderboard = this.readLeaderboard(this.targetLeaderboard);
let entry = {initials : initials, score : score, when : Date.now()};
if (currentLeaderboard) {
currentLeaderboard.push(entry);
} else {
currentLeaderboard = [entry];
};
localStorage.setItem(this.targetLeaderboard, JSON.stringify(currentLeaderboard));
} catch(e) {
console.log('could not save leaderboard to localStorage', e);
};
},
readLeaderboard : function() {
try {
return JSON.parse(localStorage.getItem(this.targetLeaderboard));
} catch(e) {
console.log('could not load leaderBoard from localStorage', e);
};
},
clearLeaderboard : function() {
try {
localStorage.removeItem(this.targetLeaderboard);
console.log(this.targetLeaderboard, 'removed from local storage');
} catch(e) {
console.log(this.targetLeaderboard, 'not found in localStorage', e);
};
},
init : function() {
this.targetLeaderboard = knobsAndLevers.targetLeaderboard;
},
};
/*jslint white: true */
var main = {
framesToWaitToPauseAgain : 0,
updateGameState : function() {
main.updateGamepad();
if (!game.running) {
game.gameArea.loadBackground();
controls.gamepad.checkState(game.numberOfPlayers);
menus.processMenus();
return;
};
if (Object.keys(players.players).length < game.numberOfPlayers) {
players.init();
};
game.gameArea.removeBackground();
main.handleGamePause();
if (main.processTriggers()) {
return;
};
main.prepTheCanvas();
main.manageGameObjects();
},
updateGamepad : function() {
if (controls.gamepad.enabledGamepadIndices.size <= 0) {
return;
};
controls.gamepad.refreshGamepadData();
},
handleGamePause : function() {
if (this.framesToWaitToPauseAgain > 0) {
this.framesToWaitToPauseAgain--;
return;
};
if (controls.pausedIsPressed()) {
game.paused = !game.paused;
this.framesToWaitToPauseAgain = 50;
};
},
processTriggers : function() {
let triggered = (
this.checkPlayerDied()
|| this.checkLevelOver()
|| this.checkGameOver()
|| this.checkPause()
);
return triggered;
},
checkPlayerDied : function() {
if (players.died) {
if (game.delayed === 0) {
game.setDiedText();
sounds.playDiedSound();
game.delayed++;
return true;
} else if (game.delayed < game.delayEndTime) {
game.delayed++;
return true;
} else {
game.delayed = 0;
game.manageDeath();
return true;
};
};
return false;
},
checkLevelOver : function() {
if (game.levelIsOver()) {
game.manageLevel();
return true;
};
return false;
},
checkGameOver : function() {
if (game.gameOver) {
game.manageGameOver();
return true;
};
return false;
},
checkPause : function() {
if (game.paused) {
game.managePause();
return true;
};
return false;
},
prepTheCanvas : function() {
game.startNextFrame();
sounds.manageSounds();
if (game.running) {
hud.update();
};
},
manageGameObjects : function() {
metrics.manage();
mushrooms.manage();
centipedes.manage();
intervalCreatures.manage();
spiders.manage();
lasers.manage();
players.manage();
collisions.check();
init : function() {
Object.assign(this, mainBase);