diff --git a/README.md b/README.md index 8f831f5..b6413bb 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,49 @@

- Welcome to the official repository for the IESD Hack Day event. + August 2019 IESD Hack Day event.

## **Getting Started** -Look under branches and find the appropriate month and year for the Hack Day you're participating in. Each branch has its own documentation for getting started. +This repository has the resources to get you prepared for the Hack Day event. + +```Starter code has been update - 8/31/19 @ 1:55 PM.``` If you have any questions, please ask on our [Slack](https://ie-sd.slack.com). We welcome everyone to our Slack, so don't be afraid to join! Have fun coding! **Join our Slack!** + +## **Concepts to Review** +Resources section below has links for these concepts. +- Canvas +- RequestAnimationFrame (JS Method) +- Object Oriented JavaScript +- Prototypal Inheritance +- Sprites +- Keyboard Events + +## **Resources** +List will be updated up until the day of the Hack Day event. +- Making Sprite Based Games with Canvas (2013 article - still relevant) +- HTML5 Canvas Game: 2D Collision Detection +- Adding collision detection to images drawn on canvas on StackOverflow +- Modals in Pure ES6 JavaScript +- KeyboardEvent Value (keyCodes, metaKey, etc) via CSS-Tricks + +### **RequestAnimationFrame** + +The `window.requestAnimationFrame()` method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint. The method takes a callback as an argument to be invoked before the repaint. Read more about this here. + +### **Object-Oriented JavaScript** +Make sure you are comfortable with Object-Oriented JavaScript: + +- What is an object? +- How is an object different from a primitive in JavaScript (e.g., can a primitive have methods?) +- What are some ways to create or instantiate a new object? +- How do you modify properties, or add/remove properties from an object? +- What is a constructor function (or class)? +- What is `this`? What does it refer to and how is it used in different contexts (i.e., in a constructor function, a method, etc.)? +- How do you add a property or method to a constructor's prototype? +- What is prototypal inheritance and how is it implemented? diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..34d6872 --- /dev/null +++ b/css/style.css @@ -0,0 +1,78 @@ +body { + text-align: center; +} + +#game-info { + border: 1px solid red; + display: flex; + width: 100%; + flex-direction: row; +} + +#life-counter-container, #score-counter-container, #character-profile-container { + border: 1px solid black; + flex: 1; + font-family: Arial, Helvetica, sans-serif; + font-size: 24px; +} + +#score-counter-container { + border: 1px solid green; + flex: 1; +} + +#character-profile-container { + border: 1px solid blue; + flex: 1; +} + +.is-hidden { + display: none !important; + } + + .button-close { + display: inline-block; + width: 16px; + height: 16px; + position: absolute; + top: 10px; + right: 10px; + cursor: pointer; + } + + .modal-overlay { + position: absolute; + top: 0; + left: 0; + width: 505px; + height: 606px; + background: rgba(0, 0, 0, 0.6); + z-index: 9999; + display: flex; + justify-content: center; + } + + .modal { + padding: 20px 30px; + width: 90%; + max-height: calc(100% - 150px); + position: relative; + margin: 5% auto 0; + z-index: 9999; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: #fff; + } + + .game-over { + font-size: 48px; + } + + #retry-btn { + background-color: #8888ff; + color: #fff; + max-width: 160px; + padding: 16px; + } \ No newline at end of file diff --git a/engine.js b/engine.js deleted file mode 100644 index 7fb7b09..0000000 --- a/engine.js +++ /dev/null @@ -1,185 +0,0 @@ -/* Engine.js - * This file provides the game loop functionality (update entities and render), - * draws the initial game board on the screen, and then calls the update and - * render methods on your player and enemy objects (defined in your app.js). - * - * A game engine works by drawing the entire game screen over and over, kind of - * like a flipbook you may have created as a kid. When your player moves across - * the screen, it may look like just that image/character is moving or being - * drawn but that is not the case. What's really happening is the entire "scene" - * is being drawn over and over, presenting the illusion of animation. - * - * This engine makes the canvas' context (ctx) object globally available to make - * writing app.js a little simpler to work with. - */ - -var Engine = (function(global) { - /* Predefine the variables we'll be using within this scope, - * create the canvas element, grab the 2D context for that canvas - * set the canvas element's height/width and add it to the DOM. - */ - var doc = global.document, - win = global.window, - canvas = doc.createElement('canvas'), - ctx = canvas.getContext('2d'), - lastTime; - - canvas.width = 505; - canvas.height = 606; - doc.body.appendChild(canvas); - - /* This function serves as the kickoff point for the game loop itself - * and handles properly calling the update and render methods. - */ - function main() { - /* Get our time delta information which is required if your game - * requires smooth animation. Because everyone's computer processes - * instructions at different speeds we need a constant value that - * would be the same for everyone (regardless of how fast their - * computer is) - hurray time! - */ - var now = Date.now(), - dt = (now - lastTime) / 1000.0; - - /* Call our update/render functions, pass along the time delta to - * our update function since it may be used for smooth animation. - */ - update(dt); - render(); - - /* Set our lastTime variable which is used to determine the time delta - * for the next time this function is called. - */ - lastTime = now; - - /* Use the browser's requestAnimationFrame function to call this - * function again as soon as the browser is able to draw another frame. - */ - win.requestAnimationFrame(main); - } - - /* This function does some initial setup that should only occur once, - * particularly setting the lastTime variable that is required for the - * game loop. - */ - function init() { - reset(); - lastTime = Date.now(); - main(); - } - - /* This function is called by main (our game loop) and itself calls all - * of the functions which may need to update entity's data. Based on how - * you implement your collision detection (when two entities occupy the - * same space, for instance when your character should die), you may find - * the need to add an additional function call here. For now, we've left - * it commented out - you may or may not want to implement this - * functionality this way (you could just implement collision detection - * on the entities themselves within your app.js file). - */ - function update(dt) { - updateEntities(dt); - // checkCollisions(); - } - - /* This is called by the update function and loops through all of the - * objects within your allEnemies array as defined in app.js and calls - * their update() methods. It will then call the update function for your - * player object. These update methods should focus purely on updating - * the data/properties related to the object. Do your drawing in your - * render methods. - */ - function updateEntities(dt) { - allEnemies.forEach(function(enemy) { - enemy.update(dt); - }); - player.update(); - } - - /* This function initially draws the "game level", it will then call - * the renderEntities function. Remember, this function is called every - * game tick (or loop of the game engine) because that's how games work - - * they are flipbooks creating the illusion of animation but in reality - * they are just drawing the entire screen over and over. - */ - function render() { - /* This array holds the relative URL to the image used - * for that particular row of the game level. - */ - var rowImages = [ - 'images/water-block.png', // Top row is water - 'images/stone-block.png', // Row 1 of 3 of stone - 'images/stone-block.png', // Row 2 of 3 of stone - 'images/stone-block.png', // Row 3 of 3 of stone - 'images/grass-block.png', // Row 1 of 2 of grass - 'images/grass-block.png' // Row 2 of 2 of grass - ], - numRows = 6, - numCols = 5, - row, col; - - // Before drawing, clear existing canvas - ctx.clearRect(0,0,canvas.width,canvas.height); - - /* Loop through the number of rows and columns we've defined above - * and, using the rowImages array, draw the correct image for that - * portion of the "grid" - */ - for (row = 0; row < numRows; row++) { - for (col = 0; col < numCols; col++) { - /* The drawImage function of the canvas' context element - * requires 3 parameters: the image to draw, the x coordinate - * to start drawing and the y coordinate to start drawing. - * We're using our Resources helpers to refer to our images - * so that we get the benefits of caching these images, since - * we're using them over and over. - */ - ctx.drawImage(Resources.get(rowImages[row]), col * 101, row * 83); - } - } - - renderEntities(); - } - - /* This function is called by the render function and is called on each game - * tick. Its purpose is to then call the render functions you have defined - * on your enemy and player entities within app.js - */ - function renderEntities() { - /* Loop through all of the objects within the allEnemies array and call - * the render function you have defined. - */ - allEnemies.forEach(function(enemy) { - enemy.render(); - }); - - player.render(); - } - - /* This function does nothing but it could have been a good place to - * handle game reset states - maybe a new game menu or a game over screen - * those sorts of things. It's only called once by the init() method. - */ - function reset() { - // noop - } - - /* Go ahead and load all of the images we know we're going to need to - * draw our game level. Then set init as the callback method, so that when - * all of these images are properly loaded our game will start. - */ - Resources.load([ - 'images/stone-block.png', - 'images/water-block.png', - 'images/grass-block.png', - 'images/enemy-bug.png', - 'images/char-boy.png' - ]); - Resources.onReady(init); - - /* Assign the canvas' context object to the global variable (the window - * object when run in a browser) so that developers can use it more easily - * from within their app.js files. - */ - global.ctx = ctx; -})(this); diff --git a/images/Gem Blue.png b/images/Gem Blue.png new file mode 100644 index 0000000..5808a7d Binary files /dev/null and b/images/Gem Blue.png differ diff --git a/images/Gem Green.png b/images/Gem Green.png new file mode 100644 index 0000000..e971d14 Binary files /dev/null and b/images/Gem Green.png differ diff --git a/images/Gem Orange.png b/images/Gem Orange.png new file mode 100644 index 0000000..f8faf7b Binary files /dev/null and b/images/Gem Orange.png differ diff --git a/images/Heart.png b/images/Heart.png new file mode 100644 index 0000000..aa49b5c Binary files /dev/null and b/images/Heart.png differ diff --git a/images/Key.png b/images/Key.png new file mode 100644 index 0000000..7602326 Binary files /dev/null and b/images/Key.png differ diff --git a/images/Rock.png b/images/Rock.png new file mode 100644 index 0000000..29c4e20 Binary files /dev/null and b/images/Rock.png differ diff --git a/images/Selector.png b/images/Selector.png new file mode 100644 index 0000000..e7c5475 Binary files /dev/null and b/images/Selector.png differ diff --git a/images/Star.png b/images/Star.png new file mode 100644 index 0000000..17c0af5 Binary files /dev/null and b/images/Star.png differ diff --git a/images/char-boy.png b/images/char-boy.png new file mode 100644 index 0000000..3dc7c29 Binary files /dev/null and b/images/char-boy.png differ diff --git a/images/char-cat-girl.png b/images/char-cat-girl.png new file mode 100644 index 0000000..dc0538b Binary files /dev/null and b/images/char-cat-girl.png differ diff --git a/images/char-horn-girl.png b/images/char-horn-girl.png new file mode 100644 index 0000000..90f2278 Binary files /dev/null and b/images/char-horn-girl.png differ diff --git a/images/char-pink-girl.png b/images/char-pink-girl.png new file mode 100644 index 0000000..baef177 Binary files /dev/null and b/images/char-pink-girl.png differ diff --git a/images/char-princess-girl.png b/images/char-princess-girl.png new file mode 100644 index 0000000..9d9f958 Binary files /dev/null and b/images/char-princess-girl.png differ diff --git a/images/enemy-bug.png b/images/enemy-bug.png new file mode 100644 index 0000000..191587b Binary files /dev/null and b/images/enemy-bug.png differ diff --git a/images/grass-block.png b/images/grass-block.png new file mode 100644 index 0000000..eb04ba9 Binary files /dev/null and b/images/grass-block.png differ diff --git a/images/stone-block.png b/images/stone-block.png new file mode 100644 index 0000000..01113cf Binary files /dev/null and b/images/stone-block.png differ diff --git a/images/water-block.png b/images/water-block.png new file mode 100644 index 0000000..0383ed8 Binary files /dev/null and b/images/water-block.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..605c303 --- /dev/null +++ b/index.html @@ -0,0 +1,35 @@ + + + + + Hack Day - Frogger Clone Starter Code + + + +
+
+ Lives: 5 +
+
+ Rounds: 1 +
+
+ Games Won: 0 +
+
+ + + + + + + + + + + + diff --git a/js/app.js b/js/app.js new file mode 100644 index 0000000..66e9c20 --- /dev/null +++ b/js/app.js @@ -0,0 +1,55 @@ +const occupied = []; +const sprites = [ + "images/char-boy.png", + "images/char-cat-girl.png", + "images/char-horn-girl.png", + "images/char-pink-girl.png", + "images/char-princess-girl.png" +]; + + +const game = new Game(); + +let player = new Player(); + +//Array to hold Enemy objects +const allEnemies = []; + + +const enemy1 = new Enemy(101, 83, 150); +const enemy2 = new Enemy(404, 166, 350); +const enemy3 = new Enemy(0, 249, 375); +const enemy4 = new Enemy(0, 83, 100); + +allEnemies.push(enemy1, enemy2, enemy3, enemy4); + +// Player.handleInput() method. You don't need to modify this. +document.addEventListener("keyup", function (e) { + var allowedKeys = { + 65: "left", + 37: "left", + + 87: "up", + 38: "up", + + 68: "right", + 39: "right", + + 83: "down", + 40: "down", + + 13: "enter" + }; + + player.handleInput(allowedKeys[e.keyCode]); + game.handleInput(allowedKeys[e.keyCode]); +}); + +document.addEventListener("click", function (e) { + e.preventDefault(); + if(e.target && e.target.id== 'retry-btn'){ + game.modal.close(); + occupied.splice(0, occupied.length); + player = new Player(); + } +}); \ No newline at end of file diff --git a/js/enemy.js b/js/enemy.js new file mode 100644 index 0000000..c3e1b6c --- /dev/null +++ b/js/enemy.js @@ -0,0 +1,20 @@ +//Enemy class +class Enemy extends Populate { + constructor (x, y, speed) { + super(); + this.x = x; + this.y = y; + this.speed = speed; + this.sprite = "images/enemy-bug.png"; + this.enemySprite = this.sprite; + } + + //Smooth movement of Enemy objects across gameboard + update (dt) { + if (this.x < this.sideways * 5) { + this.x += this.speed * dt; + } else { + this.x = -100; + } + } +} \ No newline at end of file diff --git a/js/engine.js b/js/engine.js new file mode 100644 index 0000000..269abbc --- /dev/null +++ b/js/engine.js @@ -0,0 +1,166 @@ +/* Engine.js + * This file provides the game loop functionality (update entities and render), + * draws the initial game board on the screen, and then calls the update and + * render methods on your player and enemy objects (defined in your app.js). + * This engine makes the canvas' context (ctx) object globally available to make + * writing app.js a little simpler to work with. + */ + +var Engine = (function (global) { + var doc = global.document, + win = global.window, + canvas = doc.createElement("canvas"), + ctx = canvas.getContext("2d"), + lastTime; + + canvas.width = 505; + canvas.height = 606; + + doc.body.appendChild(canvas); + + + var modal = new Modal(document.querySelector('.modal-overlay'), canvas); + game.setModal(modal); + modal.open(screenTemplates.titleScreen); + + + /* This function serves as the kickoff point for the game loop itself + * and handles properly calling the update and render methods. + */ + function main () { + /* Get our time delta information which is required if your game + * requires smooth animation. + */ + var now = Date.now(), + dt = (now - lastTime) / 1000.0; + + /* Call our update/render functions, pass along the time delta to + * our update function + */ + update(dt); + render(); + + /* Set our lastTime variable which is used to determine the time delta + */ + lastTime = now; + + /* Use the browser's requestAnimationFrame function to call this + * function again as soon as the browser is able to draw another frame. + */ + if (player.winAStar === true) { + modal.style.display = "block"; + win.cancelAnimationFrame; + + } else { + win.requestAnimationFrame(main); + + } + } + + /* This function does some initial setup that should only occur once, + * particularly setting the lastTime variable that is required for the + * game loop. + */ + function init () { + lastTime = Date.now(); + main(); + } + + /* This function is called by main (our game loop) and itself calls all + * of the functions which may need to update entity's data. */ + + function update (dt) { + updateEntities(dt); + } + + /* This is called by the update function and loops through all of the + * objects within your allEnemies array as defined in app.js + */ + function updateEntities (dt) { + allEnemies.forEach(function (enemy) { + enemy.update(dt); + }); + player.update(); + } + + /* This function initially draws the "game level", it will then call + * the renderEntities function. + */ + function render () { + /* This array holds the relative URL to the image used + * for that particular row of the game level. + */ + var rowImages = [ + "images/water-block.png", // Top row is water + "images/stone-block.png", // Row 1 of 3 of stone + "images/stone-block.png", // Row 2 of 3 of stone + "images/stone-block.png", // Row 3 of 3 of stone + "images/grass-block.png", // Row 1 of 2 of grass + "images/grass-block.png", // Row 2 of 2 of grass + "images/Selector.png" // Special occupied space :) + ], + numRows = 6, + numCols = 5, + row, col; + + // Before drawing, clear existing canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); + + /* Loop through the number of rows and columns we've defined above + * and, using the rowImages array, draw the correct image for that + * portion of the "grid" + */ + for (row = 0; row < numRows; row++) { + for (col = 0; col < numCols; col++) { + /* The drawImage function of the canvas' context element + * requires 3 parameters: the image to draw, the x coordinate + * to start drawing and the y coordinate to start drawing. + * We're using our Resources helpers to refer to our images + * so that we get the benefits of caching these images, since + * we're using them over and over. + */ + ctx.drawImage(Resources.get(rowImages[row]), col * 101, row * 83); + + if((occupied.indexOf(101 * col) < 0) && row === 0) { + ctx.drawImage(Resources.get(rowImages[rowImages.length - 1]), col * 101, row * 83); + } + } + } + + renderEntities(); + } + + /* This function is called by the render function and is called on each game + * tick. Its purpose is to then call the render functions you have defined + * on your enemy and player entities within app.js + */ + function renderEntities () { + allEnemies.forEach(function (enemy) { + enemy.render(); + }); + + player.render(); + } + + //Images used in game + Resources.load([ + "images/stone-block.png", + "images/water-block.png", + "images/grass-block.png", + "images/enemy-bug.png", + "images/char-boy.png", + "images/char-cat-girl.png", + "images/char-horn-girl.png", + "images/char-pink-girl.png", + "images/char-princess-girl.png", + "images/Star.png", + "images/Selector.png" + ]); + Resources.onReady(init); + + /* Assign the canvas' context object to the global variable (the window + * object when run in a browser) so that developers can use it more easily + * from within their app.js files. + */ + global.ctx = ctx; +})(this); diff --git a/js/game.js b/js/game.js new file mode 100644 index 0000000..e7420c9 --- /dev/null +++ b/js/game.js @@ -0,0 +1,27 @@ +//Game State Manager +class Game { + state = { + hasBegun: false, + levelComplete: false + }; + + setModal(modal) { + this.modal = modal; + } +//key input for whole game + handleInput (input) { + if(input === "enter") { + if (!this.state.hasBegun) { + this.modal.close(); + this.state.hasBegun = true; + } + + if (this.state.levelComplete) { + this.modal.close(); + this.state.levelComplete = false; + occupied.splice(0, occupied.length); + } + } + + } +} \ No newline at end of file diff --git a/js/modal.js b/js/modal.js new file mode 100644 index 0000000..49b8dd0 --- /dev/null +++ b/js/modal.js @@ -0,0 +1,18 @@ +function Modal(overlay, anchorElement) { + var modalContent = document.querySelector(".modal-content"); + this.overlay = overlay; + this.anchorElement = anchorElement; + this.open = function(content) { + modalContent.innerHTML = content; + this.overlay.classList.remove('is-hidden'); + this.overlay.style.top = this.anchorElement.offsetTop + 'px'; + this.overlay.style.left = this.anchorElement.offsetLeft + 'px'; + this.overlay.style.width = this.anchorElement.width + 'px'; + this.overlay.style.height = this.anchorElement.height + 'px'; + } + + this.close = function() { + this.overlay.classList.add('is-hidden'); + modalContent.innerHTML = ""; + } +} diff --git a/js/player.js b/js/player.js new file mode 100644 index 0000000..5b6e348 --- /dev/null +++ b/js/player.js @@ -0,0 +1,79 @@ +//Player class +class Player extends Populate { + constructor () { + super(); + this.x = 0; + this.y = 415; + this.round = 0; + this.sprite = sprites[this.round % sprites.length]; + this.lives = 5; + } + + //key input for Player + handleInput (input) { + if(this.lives > 0) { + switch (input) { + case "left": + if ((this.x >= this.sideways) && !(this.occupiedSpace((this.x - this.sideways), this.y))) { + this.x -= this.sideways; + } + break; + case "right": + if ((this.x <= this.sideways * 3) && !(this.occupiedSpace((this.x + this.sideways), this.y))) { + this.x += this.sideways; + } + break; + case "up": + if ((this.y >= 83) && !(this.occupiedSpace(this.x, (this.y - this.upDown)))) { + this.y -= this.upDown; + } + break; + case "down": + if (this.y <= this.upDown * 4) { + this.y += this.upDown; + } + break; + } + + this.handleWin(); + } + } + + //updates player and sets condition for collision & win + update () { + for (let enemy of allEnemies) { + if (this.y === enemy.y && (enemy.x + enemy.sideways / 2 > this.x && enemy.x < this.x + this.sideways / 2)) { + this.lives--; + document.getElementById("life-counter").innerText = this.lives; + this.reset(); + + if (this.lives === 0) { + game.modal.open(screenTemplates.gameOver); + } + } + } + } + + //check if occupied + occupiedSpace (x, y) { + return ((occupied.indexOf(x) >= 0) && y === 0); + } + + //runs the win condition + handleWin () { + if (occupied.indexOf(this.x) < 0 && this.y === 0) { + occupied.push(this.x); + this.round++; + document.getElementById("round-counter").innerText = (this.round + 1); + document.getElementById("games-won").innerText = (this.round / sprites.length); + + this.sprite = sprites[this.round % sprites.length]; + this.reset(); + + if(occupied.length === sprites.length) { + game.state.levelComplete = true; + game.modal.open(screenTemplates.newLevel); + } + } + } +} diff --git a/js/populate.js b/js/populate.js new file mode 100644 index 0000000..69a10c4 --- /dev/null +++ b/js/populate.js @@ -0,0 +1,20 @@ +//Parent object for sprites +class Populate { + constructor () { + this.x = 0; + this.y = 0; + this.speed = 0; + this.sprite = ""; + this.sideways = 101; + this.upDown = 83; + } + + render () { + ctx.drawImage(Resources.get(this.sprite), this.x, this.y); + } + + reset () { + this.x = 0; + this.y = 415; + } +} \ No newline at end of file diff --git a/resources.js b/js/resources.js similarity index 98% rename from resources.js rename to js/resources.js index f2fdb6a..cd267b5 100644 --- a/resources.js +++ b/js/resources.js @@ -6,6 +6,7 @@ */ (function() { var resourceCache = {}; + // var loading = []; var readyCallbacks = []; /* This is the publicly accessible image loading function. It accepts @@ -36,7 +37,7 @@ function _load(url) { if(resourceCache[url]) { /* If this URL has been previously loaded it will exist within - * our resourceCache array. Just return that image rather than + * our resourceCache array. Just return that image rather * re-loading the image. */ return resourceCache[url]; @@ -57,13 +58,14 @@ */ if(isReady()) { readyCallbacks.forEach(function(func) { func(); }); - } + } }; /* Set the initial cache value to false, this will change when * the image's onload event handler is called. Finally, point * the image's src attribute to the passed in URL. */ + resourceCache[url] = false; img.src = url; } diff --git a/js/screens.js b/js/screens.js new file mode 100644 index 0000000..fbf84cf --- /dev/null +++ b/js/screens.js @@ -0,0 +1,4 @@ +var screenTemplates = { gameOver: '

Game Over

', +newLevel: '

Level Complete!

Press ENTER To Continue

', +titleScreen: '

Welcome to notFROGGER!

Press ENTER to Start

' +}; \ No newline at end of file