From aee3d8b8df8668df04cd5c6a0e5682572522e278 Mon Sep 17 00:00:00 2001 From: Volodymyr Franchuk Date: Mon, 5 Sep 2022 21:22:58 +0300 Subject: [PATCH 1/8] Finished 'JS OOP Frogger Game' --- submissions/franchukv/js-oop-frogger/app.js | 116 ++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 submissions/franchukv/js-oop-frogger/app.js diff --git a/submissions/franchukv/js-oop-frogger/app.js b/submissions/franchukv/js-oop-frogger/app.js new file mode 100644 index 0000000000..1cb23025be --- /dev/null +++ b/submissions/franchukv/js-oop-frogger/app.js @@ -0,0 +1,116 @@ +const CANVAS_HEIGHT = 606; +const CANVAS_WIDTH = 505; +const TILE_HEIGHT = 83; +const TILE_WIDTH = 101; +const ENEMY_SPEED = 80; +const PLAYER_STAT = { + x: 202, + y: 380, + height: 40, + width: 40, +}; + +const Player = function () { + this.sprite = 'images/char-boy.png'; + this.x = PLAYER_STAT.x; + this.y = PLAYER_STAT.y; + this.height = PLAYER_STAT.height; + this.width = PLAYER_STAT.width; +}; + +Player.prototype.update = function () { + if (this.y < 0) { + this.resetPlayerPosition(); + } +}; + +Player.prototype.render = function () { + ctx.drawImage(Resources.get(this.sprite), this.x, this.y); +}; + +Player.prototype.resetPlayerPosition = function () { + this.x = PLAYER_STAT.x; + this.y = PLAYER_STAT.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 * 3) 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; + } +}; + +const Enemy = function (x, y) { + this.sprite = 'images/enemy-bug.png'; + this.x = x; + this.y = y; + this.width = 80; + this.height = 40; + this.speed = ENEMY_SPEED; +}; + + +Enemy.prototype.update = function (dt) { + this.checkCollision(); + + if (this.x >= CANVAS_WIDTH) { + this.x = 0; + } + + this.x += this.speed * dt; +}; + +Enemy.prototype.render = function () { + ctx.drawImage(Resources.get(this.sprite), this.x, this.y); +}; + +Enemy.prototype.setStartEnemiesPosition = function () { + for (let i = 0; i < 3; i++) { + let y = i * TILE_HEIGHT + 50; + let x = i * -TILE_WIDTH; + allEnemies.push(new Enemy(x, y)); + } +}; + +Enemy.prototype.checkCollision = function () { + if ( + ( + player.y > this.y - this.height && + this.x + this.width > player.x && + player.x + player.width > this.x && + player.y - player.height < this.y + ) + ) { + + player.resetPlayerPosition(); + } +}; + +const allEnemies = []; +const enemy = new Enemy(); +const player = new Player(); + +Enemy.prototype.setStartEnemiesPosition(); + +document.addEventListener('keyup', function (e) { + var allowedKeys = { + 37: 'left', + 38: 'up', + 39: 'right', + 40: 'down' + }; + + player.handleInput(allowedKeys[e.keyCode]); +}); From 4c26565eb884ac8dd03886c1f028a88de23521cb Mon Sep 17 00:00:00 2001 From: Volodymyr Franchuk Date: Thu, 29 Sep 2022 11:22:08 +0300 Subject: [PATCH 2/8] now classes don't refer to any global variables --- submissions/franchukv/js-oop-frogger/app.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/submissions/franchukv/js-oop-frogger/app.js b/submissions/franchukv/js-oop-frogger/app.js index 1cb23025be..887bfaa942 100644 --- a/submissions/franchukv/js-oop-frogger/app.js +++ b/submissions/franchukv/js-oop-frogger/app.js @@ -52,13 +52,14 @@ Player.prototype.handleInput = function (key) { } }; -const Enemy = function (x, y) { +const Enemy = function (x, y, player) { this.sprite = 'images/enemy-bug.png'; this.x = x; this.y = y; this.width = 80; this.height = 40; this.speed = ENEMY_SPEED; + this.player = player; }; @@ -80,17 +81,17 @@ Enemy.prototype.setStartEnemiesPosition = function () { for (let i = 0; i < 3; i++) { let y = i * TILE_HEIGHT + 50; let x = i * -TILE_WIDTH; - allEnemies.push(new Enemy(x, y)); + allEnemies.push(new Enemy(x, y, player)); } }; Enemy.prototype.checkCollision = function () { if ( ( - player.y > this.y - this.height && - this.x + this.width > player.x && - player.x + player.width > this.x && - player.y - player.height < this.y + this.player.y > this.y - this.height && + this.x + this.width > this.player.x && + this.player.x + this.player.width > this.x && + this.player.y - this.player.height < this.y ) ) { From 569160f85cf46f49d553de83f42b8f7e7554fc25 Mon Sep 17 00:00:00 2001 From: Volodymyr Franchuk Date: Fri, 30 Sep 2022 11:20:32 +0300 Subject: [PATCH 3/8] rewrote app --- submissions/franchukv/js-oop-frogger/app.js | 85 +++++++++------------ 1 file changed, 34 insertions(+), 51 deletions(-) diff --git a/submissions/franchukv/js-oop-frogger/app.js b/submissions/franchukv/js-oop-frogger/app.js index 887bfaa942..93a678d1d5 100644 --- a/submissions/franchukv/js-oop-frogger/app.js +++ b/submissions/franchukv/js-oop-frogger/app.js @@ -2,35 +2,18 @@ const CANVAS_HEIGHT = 606; const CANVAS_WIDTH = 505; const TILE_HEIGHT = 83; const TILE_WIDTH = 101; -const ENEMY_SPEED = 80; -const PLAYER_STAT = { - x: 202, - y: 380, - height: 40, - width: 40, -}; const Player = function () { this.sprite = 'images/char-boy.png'; - this.x = PLAYER_STAT.x; - this.y = PLAYER_STAT.y; - this.height = PLAYER_STAT.height; - this.width = PLAYER_STAT.width; -}; - -Player.prototype.update = function () { - if (this.y < 0) { - this.resetPlayerPosition(); - } -}; - -Player.prototype.render = function () { - ctx.drawImage(Resources.get(this.sprite), this.x, this.y); + this.x = 202; + this.y = 380; + this.height = 40; + this.width = 40; }; Player.prototype.resetPlayerPosition = function () { - this.x = PLAYER_STAT.x; - this.y = PLAYER_STAT.y; + this.x = 202; + this.y = 380; }; Player.prototype.handleInput = function (key) { @@ -52,16 +35,37 @@ Player.prototype.handleInput = function (key) { } }; -const Enemy = function (x, y, player) { +Player.prototype.update = function () { + if (this.y < 0) { + this.resetPlayerPosition(); + } +}; + +Player.prototype.render = function () { + ctx.drawImage(Resources.get(this.sprite), this.x, this.y); +}; + +const Enemy = function (x, y, speed, player) { this.sprite = 'images/enemy-bug.png'; this.x = x; this.y = y; this.width = 80; this.height = 40; - this.speed = ENEMY_SPEED; + this.speed = 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(); + } +}; Enemy.prototype.update = function (dt) { this.checkCollision(); @@ -77,33 +81,12 @@ Enemy.prototype.render = function () { ctx.drawImage(Resources.get(this.sprite), this.x, this.y); }; -Enemy.prototype.setStartEnemiesPosition = function () { - for (let i = 0; i < 3; i++) { - let y = i * TILE_HEIGHT + 50; - let x = i * -TILE_WIDTH; - allEnemies.push(new Enemy(x, y, player)); - } -}; - -Enemy.prototype.checkCollision = function () { - if ( - ( - this.player.y > this.y - this.height && - this.x + this.width > this.player.x && - this.player.x + this.player.width > this.x && - this.player.y - this.player.height < this.y - ) - ) { - - player.resetPlayerPosition(); - } -}; - -const allEnemies = []; -const enemy = new Enemy(); const player = new Player(); - -Enemy.prototype.setStartEnemiesPosition(); +const allEnemies = [ + new Enemy(0, 55, 280, player), + new Enemy(0, 135, 150, player), + new Enemy(0, 220, 200, player) +]; document.addEventListener('keyup', function (e) { var allowedKeys = { From 44c939dacbfd18a660c874bf62cd9ef08e2f343f Mon Sep 17 00:00:00 2001 From: Volodymyr Franchuk Date: Fri, 30 Sep 2022 14:57:04 +0300 Subject: [PATCH 4/8] rewrote app after repeated self-check --- submissions/franchukv/js-oop-frogger/app.js | 74 +++++++++++++++------ 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/submissions/franchukv/js-oop-frogger/app.js b/submissions/franchukv/js-oop-frogger/app.js index 93a678d1d5..2168e310d4 100644 --- a/submissions/franchukv/js-oop-frogger/app.js +++ b/submissions/franchukv/js-oop-frogger/app.js @@ -3,17 +3,50 @@ const CANVAS_WIDTH = 505; const TILE_HEIGHT = 83; const TILE_WIDTH = 101; -const Player = function () { +const PLAYER_CONF = { + size: { + height: 40, + width: 40 + }, + position: { + x: 202, + y: 380 + } +} + +const EMENIES_CONF = { + size: { + height: 40, + width: 80 + }, + enemy: [{ + x: 0, + y: 55, + speed: 280 + }, + { + x: 0, + y: 135, + speed: 150 + }, + { + x: 0, + y: 220, + speed: 200 + }] +} + +const Player = function ({ position, size }) { this.sprite = 'images/char-boy.png'; - this.x = 202; - this.y = 380; - this.height = 40; - this.width = 40; + this.x = position.x; + this.y = position.y; + this.height = size.height; + this.width = size.width; }; -Player.prototype.resetPlayerPosition = function () { - this.x = 202; - this.y = 380; +Player.prototype.resetPlayerPosition = function ({ position }) { + this.x = position.x; + this.y = position.y; }; Player.prototype.handleInput = function (key) { @@ -37,7 +70,7 @@ Player.prototype.handleInput = function (key) { Player.prototype.update = function () { if (this.y < 0) { - this.resetPlayerPosition(); + this.resetPlayerPosition(PLAYER_CONF); } }; @@ -45,13 +78,13 @@ Player.prototype.render = function () { ctx.drawImage(Resources.get(this.sprite), this.x, this.y); }; -const Enemy = function (x, y, speed, player) { +const Enemy = function ({ size }, enemy, player) { this.sprite = 'images/enemy-bug.png'; - this.x = x; - this.y = y; - this.width = 80; - this.height = 40; - this.speed = speed; + this.height = size.height; + this.width = size.width; + this.x = enemy.x; + this.y = enemy.y; + this.speed = enemy.speed; this.player = player; }; @@ -63,7 +96,7 @@ Enemy.prototype.checkCollision = function () { this.player.x < this.x + this.width ) { - this.player.resetPlayerPosition(); + this.player.resetPlayerPosition(PLAYER_CONF); } }; @@ -81,13 +114,14 @@ Enemy.prototype.render = function () { ctx.drawImage(Resources.get(this.sprite), this.x, this.y); }; -const player = new Player(); +const player = new Player(PLAYER_CONF); const allEnemies = [ - new Enemy(0, 55, 280, player), - new Enemy(0, 135, 150, player), - new Enemy(0, 220, 200, player) + new Enemy(EMENIES_CONF, EMENIES_CONF.enemy[0], player), + new Enemy(EMENIES_CONF, EMENIES_CONF.enemy[1], player), + new Enemy(EMENIES_CONF, EMENIES_CONF.enemy[2], player), ]; + document.addEventListener('keyup', function (e) { var allowedKeys = { 37: 'left', From feb2b0b071f145bc870e493a510a12ea4f9d381a Mon Sep 17 00:00:00 2001 From: Volodymyr Franchuk Date: Mon, 3 Oct 2022 08:36:07 +0300 Subject: [PATCH 5/8] 1. Renamed 'enemy' arguments to 'enemyProperties' 2. Added function 'addEnemiesToInitArray' --- submissions/franchukv/js-oop-frogger/app.js | 26 +++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/submissions/franchukv/js-oop-frogger/app.js b/submissions/franchukv/js-oop-frogger/app.js index 2168e310d4..22caf4d63a 100644 --- a/submissions/franchukv/js-oop-frogger/app.js +++ b/submissions/franchukv/js-oop-frogger/app.js @@ -14,12 +14,12 @@ const PLAYER_CONF = { } } -const EMENIES_CONF = { +const ENEMIES_CONF = { size: { height: 40, width: 80 }, - enemy: [{ + enemies: [{ x: 0, y: 55, speed: 280 @@ -78,13 +78,13 @@ Player.prototype.render = function () { ctx.drawImage(Resources.get(this.sprite), this.x, this.y); }; -const Enemy = function ({ size }, enemy, player) { +const Enemy = function ({ size }, enemyProperties, player) { this.sprite = 'images/enemy-bug.png'; this.height = size.height; this.width = size.width; - this.x = enemy.x; - this.y = enemy.y; - this.speed = enemy.speed; + this.x = enemyProperties.x; + this.y = enemyProperties.y; + this.speed = enemyProperties.speed; this.player = player; }; @@ -114,13 +114,15 @@ Enemy.prototype.render = function () { ctx.drawImage(Resources.get(this.sprite), this.x, this.y); }; -const player = new Player(PLAYER_CONF); -const allEnemies = [ - new Enemy(EMENIES_CONF, EMENIES_CONF.enemy[0], player), - new Enemy(EMENIES_CONF, EMENIES_CONF.enemy[1], player), - new Enemy(EMENIES_CONF, EMENIES_CONF.enemy[2], player), -]; +function addEnemiesToInitArray() { + ENEMIES_CONF.enemies.forEach((enemy) => { + allEnemies.push(new Enemy(ENEMIES_CONF, enemy, player)) + }) +} +const player = new Player(PLAYER_CONF); +const allEnemies = []; +addEnemiesToInitArray(); document.addEventListener('keyup', function (e) { var allowedKeys = { From dee91b112dfeb67e60090959c285764bcd96fda4 Mon Sep 17 00:00:00 2001 From: Volodymyr Franchuk Date: Mon, 3 Oct 2022 08:43:21 +0300 Subject: [PATCH 6/8] added a few semicolons --- submissions/franchukv/js-oop-frogger/app.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/submissions/franchukv/js-oop-frogger/app.js b/submissions/franchukv/js-oop-frogger/app.js index 22caf4d63a..42b737a9c5 100644 --- a/submissions/franchukv/js-oop-frogger/app.js +++ b/submissions/franchukv/js-oop-frogger/app.js @@ -12,7 +12,7 @@ const PLAYER_CONF = { x: 202, y: 380 } -} +}; const ENEMIES_CONF = { size: { @@ -34,7 +34,7 @@ const ENEMIES_CONF = { y: 220, speed: 200 }] -} +}; const Player = function ({ position, size }) { this.sprite = 'images/char-boy.png'; @@ -116,8 +116,8 @@ Enemy.prototype.render = function () { function addEnemiesToInitArray() { ENEMIES_CONF.enemies.forEach((enemy) => { - allEnemies.push(new Enemy(ENEMIES_CONF, enemy, player)) - }) + allEnemies.push(new Enemy(ENEMIES_CONF, enemy, player)); + }); } const player = new Player(PLAYER_CONF); From 74aafcc31b4f42a7f428c589b1d89b4a97752b75 Mon Sep 17 00:00:00 2001 From: Volodymyr Franchuk Date: Mon, 3 Oct 2022 12:14:47 +0300 Subject: [PATCH 7/8] 1. Now enemies don't 'blink' when creating. 2. Now user visually can see that he crossed the road successfully. --- submissions/franchukv/js-oop-frogger/app.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/submissions/franchukv/js-oop-frogger/app.js b/submissions/franchukv/js-oop-frogger/app.js index 42b737a9c5..16407d9326 100644 --- a/submissions/franchukv/js-oop-frogger/app.js +++ b/submissions/franchukv/js-oop-frogger/app.js @@ -20,17 +20,17 @@ const ENEMIES_CONF = { width: 80 }, enemies: [{ - x: 0, + x: -80, y: 55, speed: 280 }, { - x: 0, + x: -80, y: 135, speed: 150 }, { - x: 0, + x: -80, y: 220, speed: 200 }] @@ -70,7 +70,7 @@ Player.prototype.handleInput = function (key) { Player.prototype.update = function () { if (this.y < 0) { - this.resetPlayerPosition(PLAYER_CONF); + setTimeout(() => { this.resetPlayerPosition(PLAYER_CONF); }, 200); } }; @@ -104,7 +104,7 @@ Enemy.prototype.update = function (dt) { this.checkCollision(); if (this.x >= CANVAS_WIDTH) { - this.x = 0; + this.x = -80; } this.x += this.speed * dt; From ceb54fd75878a99dc66dbe99d329f22834100535 Mon Sep 17 00:00:00 2001 From: Volodymyr Franchuk Date: Fri, 7 Oct 2022 15:02:09 +0300 Subject: [PATCH 8/8] Yeap, rewrote the app again :D 1. Rewrote game engine for visual demonstration of new features (I'm not sure is it okay or not for this task). 2. Added simple functionality to choose size of game desk. 3. Now all math calculating by a computer. 4. Enemies added automatically into game based on game desk size. --- submissions/franchukv/js-oop-frogger/app.js | 101 +++++----- .../franchukv/js-oop-frogger/engine.js | 188 ++++++++++++++++++ 2 files changed, 243 insertions(+), 46 deletions(-) create mode 100644 submissions/franchukv/js-oop-frogger/engine.js diff --git a/submissions/franchukv/js-oop-frogger/app.js b/submissions/franchukv/js-oop-frogger/app.js index 16407d9326..29a41dc320 100644 --- a/submissions/franchukv/js-oop-frogger/app.js +++ b/submissions/franchukv/js-oop-frogger/app.js @@ -1,43 +1,38 @@ -const CANVAS_HEIGHT = 606; -const CANVAS_WIDTH = 505; +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 + width: 40, }, position: { - x: 202, - y: 380 - } + 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 - }, - enemies: [{ - x: -80, - y: 55, - speed: 280 - }, - { - x: -80, - y: 135, - speed: 150 + width: 80, }, - { - x: -80, - y: 220, - speed: 200 - }] + enemiesNumber: ROWS_NUMBER - 3, + firstEnemyPositionY: 55, + minSpeed: 100, + maxSpeed: 300, + enemies: [], }; const Player = function ({ position, size }) { - this.sprite = 'images/char-boy.png'; + this.sprite = "images/char-boy.png"; this.x = position.x; this.y = position.y; this.height = size.height; @@ -51,16 +46,16 @@ Player.prototype.resetPlayerPosition = function ({ position }) { Player.prototype.handleInput = function (key) { switch (key) { - case 'up': + case "up": if (this.y > 0) this.y -= TILE_HEIGHT; break; - case 'down': - if (this.y <= CANVAS_HEIGHT - TILE_HEIGHT * 3) this.y += TILE_HEIGHT; + case "down": + if (this.y <= CANVAS_HEIGHT - TILE_HEIGHT * 2) this.y += TILE_HEIGHT; break; - case 'left': + case "left": if (this.x > 0) this.x -= TILE_WIDTH; break; - case 'right': + case "right": if (this.x < CANVAS_WIDTH - TILE_WIDTH) this.x += TILE_WIDTH; break; default: @@ -70,7 +65,9 @@ Player.prototype.handleInput = function (key) { Player.prototype.update = function () { if (this.y < 0) { - setTimeout(() => { this.resetPlayerPosition(PLAYER_CONF); }, 200); + setTimeout(() => { + this.resetPlayerPosition(PLAYER_CONF); + }, 200); } }; @@ -78,13 +75,13 @@ Player.prototype.render = function () { ctx.drawImage(Resources.get(this.sprite), this.x, this.y); }; -const Enemy = function ({ size }, enemyProperties, player) { - this.sprite = 'images/enemy-bug.png'; +const Enemy = function (size, enemyStartPositionX, enemyProperty, player) { + this.sprite = "images/enemy-bug.png"; this.height = size.height; this.width = size.width; - this.x = enemyProperties.x; - this.y = enemyProperties.y; - this.speed = enemyProperties.speed; + this.x = enemyStartPositionX; + this.y = enemyProperty.y; + this.speed = enemyProperty.speed; this.player = player; }; @@ -95,7 +92,6 @@ Enemy.prototype.checkCollision = function () { this.player.x + this.player.width > this.x && this.player.x < this.x + this.width ) { - this.player.resetPlayerPosition(PLAYER_CONF); } }; @@ -104,7 +100,7 @@ Enemy.prototype.update = function (dt) { this.checkCollision(); if (this.x >= CANVAS_WIDTH) { - this.x = -80; + this.x = -TILE_WIDTH; } this.x += this.speed * dt; @@ -114,22 +110,35 @@ Enemy.prototype.render = function () { ctx.drawImage(Resources.get(this.sprite), this.x, this.y); }; -function addEnemiesToInitArray() { - ENEMIES_CONF.enemies.forEach((enemy) => { - allEnemies.push(new Enemy(ENEMIES_CONF, enemy, player)); +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 = []; -addEnemiesToInitArray(); +createEnemies(ENEMIES_CONF, TILE_HEIGHT, TILE_WIDTH); -document.addEventListener('keyup', function (e) { +document.addEventListener("keyup", function (e) { var allowedKeys = { - 37: 'left', - 38: 'up', - 39: 'right', - 40: 'down' + 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);