diff --git a/submissions/franchukv/js-oop-frogger/app.js b/submissions/franchukv/js-oop-frogger/app.js new file mode 100644 index 0000000000..29a41dc320 --- /dev/null +++ b/submissions/franchukv/js-oop-frogger/app.js @@ -0,0 +1,145 @@ +window.gameRowsNumber = prompt('How many rows do you want to play?', 6); +window.gameColsNumber = prompt('How many columns do you want to play?', 5); + +const TILE_HEIGHT = 83; +const TILE_WIDTH = 101; +const ROWS_NUMBER = window.gameRowsNumber; +const COLS_NUMBER = window.gameColsNumber; +const CANVAS_HEIGHT = TILE_HEIGHT * ROWS_NUMBER; +const CANVAS_WIDTH = TILE_WIDTH * COLS_NUMBER; + +const PLAYER_CONF = { + size: { + height: 40, + width: 40, + }, + position: { + x: COLS_NUMBER % 2 != 0 ? TILE_WIDTH * Math.floor(COLS_NUMBER / 2) : CANVAS_WIDTH - TILE_WIDTH, + y: TILE_HEIGHT * (ROWS_NUMBER - 1.5), + }, +}; + +const ENEMIES_CONF = { + size: { + height: 40, + width: 80, + }, + enemiesNumber: ROWS_NUMBER - 3, + firstEnemyPositionY: 55, + minSpeed: 100, + maxSpeed: 300, + enemies: [], +}; + +const Player = function ({ position, size }) { + this.sprite = "images/char-boy.png"; + this.x = position.x; + this.y = position.y; + this.height = size.height; + this.width = size.width; +}; + +Player.prototype.resetPlayerPosition = function ({ position }) { + this.x = position.x; + this.y = position.y; +}; + +Player.prototype.handleInput = function (key) { + switch (key) { + case "up": + if (this.y > 0) this.y -= TILE_HEIGHT; + break; + case "down": + if (this.y <= CANVAS_HEIGHT - TILE_HEIGHT * 2) this.y += TILE_HEIGHT; + break; + case "left": + if (this.x > 0) this.x -= TILE_WIDTH; + break; + case "right": + if (this.x < CANVAS_WIDTH - TILE_WIDTH) this.x += TILE_WIDTH; + break; + default: + break; + } +}; + +Player.prototype.update = function () { + if (this.y < 0) { + setTimeout(() => { + this.resetPlayerPosition(PLAYER_CONF); + }, 200); + } +}; + +Player.prototype.render = function () { + ctx.drawImage(Resources.get(this.sprite), this.x, this.y); +}; + +const Enemy = function (size, enemyStartPositionX, enemyProperty, player) { + this.sprite = "images/enemy-bug.png"; + this.height = size.height; + this.width = size.width; + this.x = enemyStartPositionX; + this.y = enemyProperty.y; + this.speed = enemyProperty.speed; + this.player = player; +}; + +Enemy.prototype.checkCollision = function () { + if ( + this.player.y > this.y - this.height && + this.player.y - this.player.height < this.y && + this.player.x + this.player.width > this.x && + this.player.x < this.x + this.width + ) { + this.player.resetPlayerPosition(PLAYER_CONF); + } +}; + +Enemy.prototype.update = function (dt) { + this.checkCollision(); + + if (this.x >= CANVAS_WIDTH) { + this.x = -TILE_WIDTH; + } + + this.x += this.speed * dt; +}; + +Enemy.prototype.render = function () { + ctx.drawImage(Resources.get(this.sprite), this.x, this.y); +}; + +const createEnemies = ( + { enemiesNumber, firstEnemyPositionY, minSpeed, maxSpeed }, + tileHeight, + tileWidth +) => { + for (let i = 0; i < enemiesNumber; i++) { + ENEMIES_CONF.enemies.push({}); + ENEMIES_CONF.enemies[i].y = firstEnemyPositionY + tileHeight * i; + ENEMIES_CONF.enemies[i].speed = + Math.random() * (maxSpeed - minSpeed) + minSpeed; + } + + ENEMIES_CONF.enemies.forEach((enemyProperty) => { + allEnemies.push( + new Enemy(ENEMIES_CONF.size, -tileWidth, enemyProperty, player) + ); + }); +}; + +const player = new Player(PLAYER_CONF); +const allEnemies = []; +createEnemies(ENEMIES_CONF, TILE_HEIGHT, TILE_WIDTH); + +document.addEventListener("keyup", function (e) { + var allowedKeys = { + 37: "left", + 38: "up", + 39: "right", + 40: "down", + }; + + player.handleInput(allowedKeys[e.keyCode]); +}); diff --git a/submissions/franchukv/js-oop-frogger/engine.js b/submissions/franchukv/js-oop-frogger/engine.js new file mode 100644 index 0000000000..616323c4e8 --- /dev/null +++ b/submissions/franchukv/js-oop-frogger/engine.js @@ -0,0 +1,188 @@ +/* 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 = window.gameColsNumber * 101; + canvas.height = window.gameRowsNumber * 101; + 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', + ], + numRows = window.gameRowsNumber, + numCols = window.gameColsNumber, + row, col; + + for (let i = 0; i < window.gameRowsNumber - 3; i++) { + rowImages.push('images/stone-block.png'); + } + + rowImages.push('images/grass-block.png', 'images/grass-block.png'); + + // 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);