Created
September 20, 2025 16:13
-
-
Save ranjanistic/dd39bdae19b306649f9b482a1be73284 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
| 'use strict'; | |
| /** | |
| * The backbone of all games. The Engine. | |
| * Provides, manages, executes & handles objects, attributes, methods and state of every level of every game. | |
| * @author Priyanshu Ranjan (ranjanistic) | |
| */ | |
| class Engine { | |
| canvas = getElement("canvas"); | |
| context = this.canvas.getContext("2d"); | |
| block = new Block(); | |
| asset = { | |
| background: new Image(), | |
| pathlayer: new Image(), | |
| hurdle: new Image(), | |
| // stickerimg: new Image(), | |
| destination: new Image(), | |
| object: { | |
| right: new Image(), | |
| up: new Image(), | |
| left: new Image(), | |
| down: new Image(), | |
| won: new Image(), | |
| lost: new Image(), | |
| }, | |
| }; | |
| totalassets = | |
| Object.keys(this.asset).length + Object.keys(this.asset.object).length - 1; | |
| loadedassets = 0; | |
| faceObjects = []; | |
| face = { | |
| right: 0, | |
| up: 1, | |
| left: 2, | |
| down: 3, | |
| }; | |
| eachStep = 2; | |
| rows = 0; | |
| cols = 0; | |
| spacing = 0; | |
| object = {}; | |
| acheivedDestinations = 0; | |
| newWinCoordinates = []; | |
| /** | |
| * @param dimen The dimensions related to game platfrom, rows, cols, spacing b/w boxes, height & width of each box, | |
| * coordinates of grid in pixels. | |
| * @param coord The arrays of coordinates of game for start, win & lose. | |
| * @param {Workspace} workspace The object of workspace class. | |
| * @param method (Optional) The methods to be executed in gamplay, reframe: for each frame, | |
| * reset: method to execute on game reset, isAtBox method to check if object is at box, isAtwin to check if won, | |
| * onRun to execute when run is clicked. | |
| * @param hideassets (Optional) the assets that are not required in current game can be hidden by setting them as true in this param. | |
| */ | |
| constructor( | |
| dimen = { | |
| rows, | |
| cols, | |
| spacing: 0, | |
| boxwidth: 0, | |
| boxheight: 0, | |
| startx: 0, | |
| starty: 0, | |
| }, | |
| coord = { | |
| start, | |
| win, | |
| lose, | |
| }, | |
| workspace, | |
| method = { | |
| reframe: this.reframe, | |
| reset: this.reset, | |
| isAtBox: this.isAtBoxCoordinate, | |
| isAtWin: this.isAtWinCoordinate, | |
| onRun: this.onRun, | |
| onStep: this.onStep, | |
| }, | |
| hideassets = { | |
| background: false, | |
| pathlayer: false, | |
| hurdle: false, | |
| destination: false, | |
| stickerimg: false, | |
| object: { | |
| right: false, | |
| up: false, | |
| left: false, | |
| down: false, | |
| won: false, | |
| lost: false, | |
| }, | |
| } | |
| ) { | |
| this.initGameAssets(hideassets); | |
| this.rows = dimen.rows; | |
| this.cols = dimen.cols; | |
| this.spacing = dimen.spacing || 0; | |
| this.box = { | |
| width: dimen.boxwidth + this.spacing, | |
| height: dimen.boxheight + this.spacing, | |
| }; | |
| this.box00 = { | |
| width: this.box.width, | |
| height: this.box.height, | |
| startx: dimen.startx - this.spacing / 2, | |
| starty: dimen.starty - this.spacing / 2, | |
| endx: dimen.startx - this.spacing / 2 + this.box.width, | |
| endy: dimen.starty - this.spacing / 2 + this.box.height, | |
| }; | |
| this.coord = coord; | |
| this.matrix = { | |
| box: new Array(this.rows).fill(0).map(() => new Array(this.cols).fill(0)), //[y][x] | |
| win: new Array(this.rows).fill(0).map(() => new Array(this.cols).fill(0)), //[y][x] | |
| lose: new Array(this.rows) | |
| .fill(0) | |
| .map(() => new Array(this.cols).fill(0)), //[y][x] | |
| }; | |
| this.workspace = workspace; | |
| this.method = { | |
| reframe: method.reframe || this.reframe, | |
| reset: method.reset || this.reset, | |
| isAtBoxCoord: method.isAtBox || this.isAtBoxCoordinate, | |
| isAtWinCoord: method.isAtWin || this.isAtWinCoordinate, | |
| onRun: method.onRun || this.onRun, | |
| onStep: method.onStep || this.onStep, | |
| }; | |
| if (method.isAtWin) { | |
| this.isAtWinCoordinate = this.method.isAtWinCoord; | |
| } | |
| if (method.isAtBox) { | |
| this.isAtBoxCoordinate = this.method.isAtBoxCoord; | |
| } | |
| this.newWinCoordinates = this.coord.win[data.level]; | |
| this.workspace.onClear((_) => { | |
| this.method.reset(); | |
| }); | |
| this.initVirtualMatrix(); | |
| } | |
| changeBackground(onMoved=_=>{this.onRun()}){ | |
| this.asset.pathlayer.src=img.game.pathlayer1; | |
| onMoved(); | |
| } | |
| /** | |
| * Initializes image object sources as required by the game. | |
| * @param hideassets The assets to be hidden are set as true. | |
| */ | |
| initGameAssets(hideassets) { | |
| this.canvas["x"] = 0; | |
| this.canvas["y"] = 0; | |
| // this.asset.stickerimg.src = hideassets.stickerimg?"":img.stickerimg; | |
| this.asset.background.src = hideassets.background | |
| ? "" | |
| : img.game.background; | |
| this.asset.pathlayer.src = hideassets.pathlayer ? "" : img.game.pathlayer; | |
| this.asset.hurdle.src = hideassets.hurdle ? "" : img.game.hurdle; | |
| this.asset.destination.src = hideassets.destination | |
| ? "" | |
| : img.game.destination; | |
| this.asset.object.right.src = hideassets.object.right | |
| ? "" | |
| : img.game.object; | |
| this.asset.object.up.src = hideassets.object.up ? "" : img.game.objectup; | |
| this.asset.object.left.src = hideassets.object.left | |
| ? "" | |
| : img.game.objectmirror; | |
| this.asset.object.down.src = hideassets.object.down | |
| ? "" | |
| : img.game.objectdown; | |
| this.asset.object.won.src = hideassets.object.won ? "" : img.game.objectwon; | |
| this.asset.object.lost.src = hideassets.object.lost | |
| ? "" | |
| : img.game.objectlost; | |
| this.faceObjects.push( | |
| hideassets.object.right ? 0 : this.asset.object.right, | |
| hideassets.object.up ? 0 : this.asset.object.up, | |
| hideassets.object.left ? 0 : this.asset.object.left, | |
| hideassets.object.down ? 0 : this.asset.object.down | |
| ); | |
| this.trackAssetsLoad(hideassets); | |
| } | |
| trackAssetsLoad(hideassets) { | |
| Object.keys(this.asset).forEach((asset) => { | |
| if (asset === "object") { | |
| Object.keys(this.asset[asset]).forEach((object) => { | |
| if (hideassets[asset][object]) { | |
| --this.totalassets; | |
| } | |
| }); | |
| } else { | |
| if (hideassets[asset]) --this.totalassets; | |
| } | |
| }); | |
| Object.keys(this.asset).map((asset) => { | |
| if (asset === "object") { | |
| Object.keys(this.asset[asset]).map((object) => { | |
| this.asset[asset][object].addEventListener("load", (_) => { | |
| ++this.loadedassets; | |
| }); | |
| }); | |
| } else { | |
| this.asset[asset].addEventListener("load", (_) => { | |
| ++this.loadedassets; | |
| }); | |
| } | |
| }); | |
| } | |
| /** | |
| * Creates & assignes attributes to each box of the virtual grid matrices, given the win & lose coordinates. | |
| */ | |
| initVirtualMatrix() { | |
| let y = 0; | |
| while (y < this.rows) { | |
| let x = 0; | |
| while (x < this.cols) { | |
| if (this.method.isAtBoxCoord(x, y)) { | |
| this.matrix.box[y][x] = { | |
| width: this.box.width, | |
| height: this.box.height, | |
| startx: this.box00.startx + x * this.box.width, | |
| endx: this.box00.endx + x * this.box.width, | |
| starty: this.box00.starty + y * this.box.height, | |
| endy: this.box00.endy + y * this.box.height, | |
| }; | |
| } else if (this.method.isAtWinCoord(x, y)) { | |
| this.matrix.win[y][x] = { | |
| width: this.box.width, | |
| height: this.box.height, | |
| startx: this.box00.startx + x * this.box.width, | |
| endx: this.box00.endx + x * this.box.width, | |
| starty: this.box00.starty + y * this.box.height, | |
| endy: this.box00.endy + y * this.box.height, | |
| }; | |
| } else { | |
| this.matrix.lose[y][x] = { | |
| width: this.box.width, | |
| height: this.box.height, | |
| startx: this.box00.startx + x * this.box.width, | |
| endx: this.box00.endx + x * this.box.width, | |
| starty: this.box00.starty + y * this.box.height, | |
| endy: this.box00.endy + y * this.box.height, | |
| }; | |
| } | |
| x++; | |
| } | |
| y++; | |
| } | |
| } | |
| /** | |
| * Checks if given coordinates are at neutral box coordinates. | |
| * @param {Number} x The x coordinate (w.r.t box coordinates) | |
| * @param {Number} y The y coordinate (w.r.t box coordinates) | |
| * Takes x & y of the object as default parameters. | |
| */ | |
| isAtBoxCoordinate = (x = this.object.x, y = this.object.y) => { | |
| return ( | |
| ((_) => { | |
| if (this.coord.lose && this.coord.lose.length) { | |
| return this.coord.lose[data.level].some((coord) => { | |
| return coordsEqual(coord, [x, y]); | |
| }) | |
| ? false | |
| : true; | |
| } | |
| return true; | |
| })() && | |
| (this.newWinCoordinates.some((coord) => { | |
| return coordsEqual(coord, [x, y]); | |
| }) | |
| ? false | |
| : true) && | |
| x >= 0 && | |
| y >= 0 && | |
| x < this.cols && | |
| y < this.rows | |
| ); | |
| }; | |
| /** | |
| * Checks if given coordinates are at the destination coordinates. | |
| * @param {Number} x The x coordinate (w.r.t box coordinates) | |
| * @param {Number} y The y coordinate (w.r.t box coordinates) | |
| * Takes x & y of the object as default parameters. | |
| */ | |
| isAtWinCoordinate = (x = this.object.x, y = this.object.y) => { | |
| return this.newWinCoordinates.some((coord) => { | |
| return coordsEqual(coord, [x, y]); | |
| }); | |
| }; | |
| onRun = () => { | |
| eval(this.workspace.getRunnerCode(this)); | |
| } | |
| onStep = () => eval(this.workspace.getStepCode(this)); | |
| /** | |
| * @return {Boolean} If all destinations have been acheived by object, true or otherwise. | |
| */ | |
| allDestinationsAcheived = () => | |
| this.acheivedDestinations == this.coord.win[data.level].length; | |
| /** | |
| * Clears the canvas. | |
| */ | |
| clearCanvas() { | |
| this.context.clearRect( | |
| this.canvas.x, | |
| this.canvas.y, | |
| this.canvas.width, | |
| this.canvas.height | |
| ); | |
| } | |
| /** | |
| * Sets background image on canvas | |
| */ | |
| setBackground() { | |
| try { | |
| const draw = (_) => { | |
| this.context.drawImage( | |
| this.asset.background, | |
| this.canvas.x, | |
| this.canvas.y, | |
| this.canvas.width, | |
| this.canvas.height | |
| ); | |
| }; | |
| if (this.loadedassets === this.totalassets) { | |
| draw(); | |
| } else { | |
| let intv = setInterval(() => { | |
| if (this.loadedassets === this.totalassets) { | |
| clearInterval(intv); | |
| draw(); | |
| } | |
| }, 10); | |
| } | |
| } catch {} | |
| } | |
| afterspeech(onMoved=_=>{this.enableRun()}){ | |
| onMoved(); | |
| } | |
| // sticker(onMoved=_=>{this.enableRun()}){ | |
| // const newcoords = [this.object.x, this.object.y + 1]; | |
| // const draw = (_) => { | |
| // this.context.drawImage( | |
| // this.stickerimg.object.x, | |
| // this.stickerimg.object.y, | |
| // ); | |
| // }; | |
| // draw(); | |
| // this.sudoMoveForward( | |
| // !this.isAtBoxCoordinate(newcoords[0], newcoords[1]) | |
| // ? (_) => { | |
| // if (!this.isAtWinCoordinate(newcoords[0], newcoords[1])) { | |
| // this.handleLoss(); | |
| // } else { | |
| // this.handleWin(onMoved); | |
| // } | |
| // } | |
| // : (_) => { | |
| // onMoved(); | |
| // } | |
| // ); | |
| // onMoved(); | |
| // } | |
| /** | |
| * Sets movement grid for game on canvas | |
| */ | |
| setPathLayer() { | |
| try { | |
| const draw = (_) => { | |
| this.context.drawImage( | |
| this.asset.pathlayer, | |
| this.canvas.x, | |
| this.canvas.y, | |
| this.canvas.width, | |
| this.canvas.height | |
| ); | |
| }; | |
| if (this.loadedassets === this.totalassets) { | |
| draw(); | |
| } else { | |
| let intv = setInterval(() => { | |
| if (this.loadedassets === this.totalassets) { | |
| clearInterval(intv); | |
| setTimeout(() => { | |
| draw(); | |
| }, 50); | |
| } | |
| }, 10); | |
| } | |
| } catch {} | |
| } | |
| /** | |
| * Sets the object in canvas according to its current coodinate attributes. | |
| */ | |
| setObject() { | |
| try { | |
| const draw = (_) => { | |
| try { | |
| this.context.drawImage( | |
| this.faceObjects[this.object.face], | |
| this.object.startx, | |
| this.object.starty | |
| ); | |
| } catch { | |
| this.context.drawImage( | |
| this.faceObjects[this.face.right], | |
| this.object.startx, | |
| this.object.starty | |
| ); | |
| } | |
| }; | |
| if (this.loadedassets === this.totalassets) { | |
| draw(); | |
| } else { | |
| let intv = setInterval(() => { | |
| if (this.loadedassets === this.totalassets) { | |
| clearInterval(intv); | |
| setTimeout(() => { | |
| draw(); | |
| }, 100); | |
| } | |
| }, 10); | |
| } | |
| } catch {} | |
| } | |
| setMirrorObject() { | |
| try { | |
| this.context.drawImage( | |
| this.faceObjects[this.face.left], | |
| this.object.startx, | |
| this.object.starty | |
| ); | |
| } catch {} | |
| } | |
| /** | |
| * Sets destinations according to losecoordinates of current level of game. | |
| */ | |
| setDestinations() { | |
| try { | |
| this.newWinCoordinates.map((coord) => { | |
| const draw = (_) => { | |
| this.context.drawImage( | |
| this.asset.destination, | |
| this.matrix.win[coord[1]][coord[0]].startx, | |
| this.matrix.win[coord[1]][coord[0]].starty | |
| ); | |
| }; | |
| if (this.loadedassets === this.totalassets) { | |
| draw(); | |
| } else { | |
| let intv = setInterval(() => { | |
| if (this.loadedassets === this.totalassets) { | |
| clearInterval(intv); | |
| setTimeout(() => { | |
| draw(); | |
| }, 80); | |
| } | |
| }, 10); | |
| } | |
| }); | |
| } catch {} | |
| } | |
| /** | |
| * Sets hurdles according to losecoordinates of current level of game. | |
| */ | |
| setHurdles() { | |
| try { | |
| this.coord.lose[data.level].forEach((coord) => { | |
| const draw = (_) => { | |
| this.context.drawImage( | |
| this.asset.hurdle, | |
| this.matrix.lose[coord[1]][coord[0]].startx, | |
| this.matrix.lose[coord[1]][coord[0]].starty | |
| ); | |
| }; | |
| if (this.loadedassets === this.totalassets) { | |
| draw(); | |
| } else { | |
| let intv = setInterval(() => { | |
| if (this.loadedassets === this.totalassets) { | |
| clearInterval(intv); | |
| setTimeout(() => { | |
| draw(); | |
| }, 80); | |
| } | |
| }, 10); | |
| } | |
| }); | |
| } catch {} | |
| } | |
| /** | |
| * Processes to be executed at each frame of the game canvas. | |
| */ | |
| reframe = () => { | |
| this.setPathLayer(); | |
| this.setHurdles(); | |
| this.setDestinations(); | |
| this.setObject(); | |
| }; | |
| /** | |
| * Resets the game canvas by setting the default positions of all graphics, and values. | |
| */ | |
| reset = () => { | |
| this.resetWinCoords(); | |
| this.clearCanvas(); | |
| this.setBackground(); | |
| this.setPathLayer(); | |
| this.setHurdles(); | |
| this.setDestinations(); | |
| this.resetObject(); | |
| this.workspace.setDebugBlocks(); | |
| this.workspace.updateRemainingBlocks(); | |
| this.enableRun(true); | |
| this.enableStep(true); | |
| }; | |
| /** | |
| * Enables 'Run' button, sets click action. Shows win if all destinations covered, lose if not & is debug level. | |
| * @param {Boolean} notReset If true, then this indicates that this method is being called after a game reset. | |
| */ | |
| enableRun(isReset = false) { | |
| if (this.allDestinationsAcheived()) { | |
| sound.playWin(); | |
| return new Dialog().showLevelWon( | |
| (_) => { | |
| this.afterWin(); | |
| }, | |
| (_) => { | |
| this.method.reset(); | |
| },this.workspace.getShowableCode() | |
| ); | |
| } | |
| opacityOf(this.workspace.run, 1); | |
| this.workspace.onRun((_) => { | |
| this.method.onRun(); | |
| }); | |
| if (!isReset) { | |
| sound.playLose(); | |
| new Dialog().showLevelBuggy((_) => { | |
| this.method.reset(); | |
| sound.playBGM(); | |
| }); | |
| } | |
| } | |
| enableStep(isReset = false) { | |
| if (this.allDestinationsAcheived()) { | |
| return new Dialog().showLevelWon( | |
| (_) => { | |
| this.afterWin(); | |
| }, | |
| (_) => { | |
| this.method.reset(); | |
| },this.workspace.getShowableCode() | |
| ); | |
| } | |
| opacityOf(this.workspace.step, 1); | |
| this.workspace.onStep((_) => { | |
| this.method.onStep(); | |
| }); | |
| if(isReset){ | |
| this.workspace.stepcount = 0; | |
| } | |
| } | |
| /** | |
| * Returns start coordinates for current level of game. | |
| * @param {Number} pos 0 for x coordinate, 1 for y coordinate. | |
| */ | |
| getStartCoordinate(pos) { | |
| return this.coord.start[data.level][pos]; | |
| } | |
| /** | |
| * Resets the object's attributes to default values, placing it at the starting position according to start coordinate of current level. | |
| */ | |
| resetObject() { | |
| const initBox = this.matrix.box[this.getStartCoordinate(1)][ | |
| this.getStartCoordinate(0) | |
| ]; | |
| this.object = { | |
| width: this.asset.object.right.width, | |
| height: this.asset.object.right.height, | |
| startx: initBox.startx, | |
| endx: initBox.endx, | |
| starty: initBox.starty, | |
| endy: initBox.endy, | |
| x: this.getStartCoordinate(0), | |
| y: this.getStartCoordinate(1), | |
| face: this.face.right, //facing forward | |
| }; | |
| this.setObject(); | |
| } | |
| /** | |
| * Lose the object at its current game postion | |
| */ | |
| loseObject() { | |
| this.setPathLayer(); | |
| try { | |
| this.context.drawImage( | |
| this.asset.object.lost, | |
| this.object.startx, | |
| this.object.starty | |
| ); | |
| } catch {} | |
| } | |
| /** | |
| * Wins the object at its current game postion | |
| */ | |
| winObject() { | |
| this.setPathLayer(); | |
| try { | |
| this.context.drawImage( | |
| this.asset.object.won, | |
| this.object.startx, | |
| this.object.starty | |
| ); | |
| } catch {} | |
| } | |
| /** | |
| * Handles process to be executed after user wins the level. | |
| */ | |
| afterWin() { | |
| if (data.level + 1 < data.totalLevels) { | |
| refer( | |
| `${data.lessonurl ? data.url : data.levelstep}?l=${data.level + 2}` | |
| ); | |
| } else { | |
| localStorage.removeItem(key.intro); | |
| new Dialog().showGameFinished(data.lessonurl); | |
| } | |
| } | |
| /** | |
| * Returns upcoming coordinates of object according to the direction of object's face. | |
| * @param {Boolean} opposite If true, returns the upcoming coordinates at the opposite side of face. | |
| */ | |
| nextCoordinates(opposite = false) { | |
| switch (this.object.face) { | |
| case 0: | |
| return opposite | |
| ? [this.object.x - 1, this.object.y] | |
| : [this.object.x + 1, this.object.y]; | |
| case 1: | |
| return opposite | |
| ? [this.object.x, this.object.y + 1] | |
| : [this.object.x, this.object.y - 1]; | |
| case 2: | |
| return opposite | |
| ? [this.object.x + 1, this.object.y] | |
| : [this.object.x - 1, this.object.y]; | |
| case 3: | |
| return opposite | |
| ? [this.object.x, this.object.y - 1] | |
| : [this.object.x, this.object.y + 1]; | |
| } | |
| } | |
| /** | |
| * Moves the object in direction as per moveFaceDirection value, which can be provided from face property of engine class, and changes its coordinates accordingly. | |
| * @param {Function} afterMove The method to be executed after movement is done. | |
| * @param {Number} moveFaceDirection The integral value as per the direction in which to be moved. | |
| * @param {FunctionStringCallback} done Callback provides boolean whether the movement is completed or not. | |
| * @note This method doesn't restrict movement of object in any condition. Therefore must be called around proper conditions. | |
| */ | |
| sudoMove(moveFaceDirection, done) { | |
| let i = 0; | |
| this.object.face = moveFaceDirection; | |
| let mover = () => { | |
| ((_) => { | |
| switch (moveFaceDirection) { | |
| case this.face.right: | |
| { | |
| this.object.startx += this.eachStep; | |
| this.object.endx += this.eachStep; | |
| } | |
| break; | |
| case this.face.up: | |
| { | |
| this.object.starty -= this.eachStep; | |
| this.object.endy -= this.eachStep; | |
| } | |
| break; | |
| case this.face.left: | |
| { | |
| this.object.startx -= this.eachStep; | |
| this.object.endx -= this.eachStep; | |
| } | |
| break; | |
| case this.face.down: | |
| { | |
| this.object.starty += this.eachStep; | |
| this.object.endy += this.eachStep; | |
| } | |
| break; | |
| } | |
| })(); | |
| this.method.reframe(); | |
| i += this.eachStep; | |
| let animFrame = requestAnimationFrame(mover); | |
| if ( | |
| ((_) => { | |
| switch (moveFaceDirection) { | |
| case this.face.left: | |
| case this.face.right: | |
| return i >= this.box.width; | |
| case this.face.down: | |
| case this.face.up: | |
| return i >= this.box.height; | |
| } | |
| })() | |
| ) { | |
| ((_) => { | |
| switch (moveFaceDirection) { | |
| case this.face.left: | |
| this.object.x -= 1; | |
| break; | |
| case this.face.right: | |
| this.object.x += 1; | |
| break; | |
| case this.face.down: | |
| this.object.y += 1; | |
| break; | |
| case this.face.up: | |
| this.object.y -= 1; | |
| break; | |
| } | |
| })(); | |
| cancelAnimationFrame(animFrame); | |
| done(true); | |
| } else { | |
| this.workspace.onClear((_) => { | |
| cancelAnimationFrame(animFrame); | |
| this.method.reset(); | |
| }); | |
| done(false); | |
| } | |
| this.workspace.onClear((_) => { | |
| cancelAnimationFrame(animFrame); | |
| this.method.reset(); | |
| }); | |
| }; | |
| mover(); | |
| } | |
| /** | |
| * Moves the object in negative y direction (upwards), and changes its coordinates accordingly. | |
| * @param {Function} afterMove The method to be executed after movement is done. | |
| * @note This method doesn't restrict movement of object in any condition. Therefore must be called around proper conditions. | |
| */ | |
| sudoMoveUp(afterMove = (_) => {}) { | |
| let blockWorkspaceid; | |
| this.sudoMove(this.face.up, (done) => { | |
| if (!done) { | |
| this.workspace.remainingBlocks.some((b) => { | |
| if (b.type === this.workspace.block.up().name) { | |
| blockWorkspaceid = b.id; | |
| this.workspace.workspace.highlightBlock(blockWorkspaceid); | |
| return true; | |
| } | |
| }); | |
| } else { | |
| this.workspace.updateRemainingBlocks(blockWorkspaceid); | |
| this.workspace.workspace.highlightBlock(); | |
| afterMove(); | |
| } | |
| }); | |
| } | |
| /** | |
| * Moves the object in positive x direction (forwards), and changes its coordinates accordingly. | |
| * @param {Function} afterMove The method to be executed after movement is done. | |
| * @note This method doesn't restrict movement of object in any condition. Therefore must be called around proper conditions. | |
| */ | |
| sudoMoveForward(afterMove = (_) => {}) { | |
| let blockWorkspaceid; | |
| this.sudoMove(this.face.right, (done) => { | |
| if (!done) { | |
| this.workspace.remainingBlocks.some((b) => { | |
| if (b.type === this.workspace.block.forward().name) { | |
| blockWorkspaceid = b.id; | |
| this.workspace.workspace.highlightBlock(blockWorkspaceid); | |
| return true; | |
| } | |
| }); | |
| } else { | |
| this.workspace.updateRemainingBlocks(blockWorkspaceid); | |
| this.workspace.workspace.highlightBlock(); | |
| afterMove(); | |
| } | |
| }); | |
| } | |
| /** | |
| * Moves the object in positive y direction (downwards), and changes its coordinates accordingly. | |
| * @param {Function} afterMove The method to be executed after movement is done. | |
| * @note This method doesn't restrict movement of object in any condition. Therefore must be called around proper conditions. | |
| */ | |
| sudoMoveDown(afterMove = (_) => {}) { | |
| let blockWorkspaceid; | |
| this.sudoMove(this.face.down, (done) => { | |
| if (!done) { | |
| this.workspace.remainingBlocks.some((b) => { | |
| if (b.type === this.workspace.block.down().name) { | |
| blockWorkspaceid = b.id; | |
| this.workspace.workspace.highlightBlock(blockWorkspaceid); | |
| return true; | |
| } | |
| }); | |
| } else { | |
| this.workspace.updateRemainingBlocks(blockWorkspaceid); | |
| this.workspace.workspace.highlightBlock(); | |
| afterMove(); | |
| } | |
| }); | |
| } | |
| /** | |
| * Moves the object in negative x direction (backwards), and changes its coordinates accordingly. | |
| * @param {Function} afterMove The method to be executed after movement is done. | |
| * @note This method doesn't restrict movement of object in any condition. Therefore must be called around proper conditions. | |
| */ | |
| sudoMoveBack(afterMove = (_) => {}) { | |
| let blockWorkspaceid; | |
| this.sudoMove(this.face.left, (done) => { | |
| if (!done) { | |
| this.workspace.remainingBlocks.some((b) => { | |
| if (b.type === this.workspace.block.back().name) { | |
| blockWorkspaceid = b.id; | |
| this.workspace.workspace.highlightBlock(blockWorkspaceid); | |
| return true; | |
| } | |
| }); | |
| } else { | |
| this.workspace.updateRemainingBlocks(blockWorkspaceid); | |
| this.workspace.workspace.highlightBlock(); | |
| afterMove(); | |
| } | |
| }); | |
| } | |
| /** | |
| * Moves the object in the direction of its face, and changes its coordinates accordingly. | |
| * @param {Function} afterMove The method to be executed after movement is done. | |
| * @note This method doesn't restrict movement of object in any condition. Therefore must be called around proper conditions. | |
| */ | |
| sudoMoveFace = (afterMove = (_) => {}) => { | |
| let blockWorkspaceid; | |
| this.sudoMove(this.object.face,(done)=>{ | |
| if (!done) { | |
| this.workspace.remainingBlocks.some((b) => { | |
| if (b.type === this.workspace.block.faceMove().name) { | |
| blockWorkspaceid = b.id; | |
| this.workspace.workspace.highlightBlock(blockWorkspaceid); | |
| return true; | |
| } | |
| }); | |
| } else { | |
| this.workspace.updateRemainingBlocks(blockWorkspaceid); | |
| this.workspace.workspace.highlightBlock(); | |
| afterMove(); | |
| } | |
| }) | |
| }; | |
| /** | |
| * Moves the object in the direction opposite to its face, and changes its coordinates accordingly. | |
| * @param {Function} afterMove The method to be executed after movement is done. | |
| * @note This method doesn't restrict movement of object in any condition. Therefore must be called around proper conditions. | |
| */ | |
| sudoMoveOppositeFace = (afterMove = (_) => {}) => { | |
| let blockWorkspaceid; | |
| this.sudoMove(this.object.face<2?this.object.face+2:this.object.face-2,(done)=>{ | |
| if (!done) { | |
| this.workspace.remainingBlocks.some((b) => { | |
| if (b.type === this.workspace.block.faceOppositeMove().name) { | |
| blockWorkspaceid = b.id; | |
| this.workspace.workspace.highlightBlock(blockWorkspaceid); | |
| return true; | |
| } | |
| }); | |
| } else { | |
| this.workspace.updateRemainingBlocks(blockWorkspaceid); | |
| this.workspace.workspace.highlightBlock(); | |
| afterMove(); | |
| } | |
| }) | |
| }; | |
| /** | |
| * Turns the object left (right, for user) at its own coordinate, and changes its face value accordingly. | |
| * @param {Function} afterTurn The method to be executed after object is turned. | |
| */ | |
| sudoTurnLeft(afterTurn = (_) => {}) { | |
| this.object.face = | |
| this.object.face < this.face.down | |
| ? this.object.face + 1 | |
| : this.face.right; | |
| this.method.reframe(); | |
| afterTurn(); | |
| } | |
| /** | |
| * Turns the object right (left, for user) at its own coordinate, and changes its face value accordingly. | |
| * @param {Function} afterTurn The method to be executed after object is turned. | |
| */ | |
| sudoTurnRight(afterTurn = (_) => {}) { | |
| this.object.face = | |
| this.object.face > this.face.right | |
| ? this.object.face - 1 | |
| : this.face.down; | |
| this.method.reframe(); | |
| afterTurn(); | |
| } | |
| /** | |
| * Moves object upwards, with checking invalid coordinates and showing alerts. | |
| * @param {Function} onMoved The method to be executed after succesfull movement. Will not execute if error occurs. | |
| */ | |
| moveUp(onMoved=_=>{this.enableRun()}) { | |
| const newcoords = [this.object.x, this.object.y - 1]; | |
| this.sudoMoveUp( | |
| !this.isAtBoxCoordinate(newcoords[0], newcoords[1]) | |
| ? (_) => { | |
| if (!this.isAtWinCoordinate(newcoords[0], newcoords[1])) { | |
| this.handleLoss(); | |
| } else { | |
| this.handleWin(onMoved); | |
| } | |
| } | |
| : (_) => { | |
| onMoved(); | |
| } | |
| ); | |
| } | |
| /** | |
| * Moves object rightwards, with checking invalid coordinates and showing alerts. | |
| * @param {Function} onMoved The method to be executed after succesfull movement. Will not execute if error occurs. | |
| */ | |
| moveForward(onMoved=_=>{this.enableRun()}) { | |
| const newcoords = [this.object.x + 1, this.object.y]; | |
| this.sudoMoveForward( | |
| !this.isAtBoxCoordinate(newcoords[0], newcoords[1]) | |
| ? (_) => { | |
| if (!this.isAtWinCoordinate(newcoords[0], newcoords[1])) { | |
| this.handleLoss(); | |
| } else { | |
| this.handleWin(onMoved); | |
| } | |
| } | |
| : (_) => { | |
| onMoved(); | |
| } | |
| ); | |
| } | |
| /** | |
| * Moves object downwards, with checking invalid coordinates and showing alerts. | |
| * @param {Function} onMoved The method to be executed after succesfull movement. Will not execute if error occurs. | |
| */ | |
| moveDown(onMoved=_=>{this.enableRun()}) { | |
| const newcoords = [this.object.x, this.object.y + 1]; | |
| this.sudoMoveDown( | |
| !this.isAtBoxCoordinate(newcoords[0], newcoords[1]) | |
| ? (_) => { | |
| if (!this.isAtWinCoordinate(newcoords[0], newcoords[1])) { | |
| this.handleLoss(); | |
| } else { | |
| this.handleWin(onMoved); | |
| } | |
| } | |
| : (_) => { | |
| onMoved(); | |
| } | |
| ); | |
| } | |
| /** | |
| * Moves object leftwards, with checking invalid coordinates and showing alerts. | |
| * @param {Function} onMoved The method to be executed after succesfull movement. Will not execute if error occurs. | |
| */ | |
| moveBackward(onMoved=_=>{this.enableRun()}) { | |
| const newcoords = [this.object.x - 1, this.object.y]; | |
| this.sudoMoveBack( | |
| !this.isAtBoxCoordinate(newcoords[0], newcoords[1]) | |
| ? (_) => { | |
| if (!this.isAtWinCoordinate(newcoords[0], newcoords[1])) { | |
| this.handleLoss(); | |
| } else { | |
| this.handleWin(onMoved); | |
| } | |
| } | |
| : (_) => { | |
| onMoved(); | |
| } | |
| ); | |
| } | |
| collect(onMoved=_=>{this.onRun()}){ | |
| onMoved(); | |
| } | |
| repeatFollowing(times, steps, onRepeated=_=>{this.enableRun()}) { | |
| const repeatCode = this.workspace.getRepeatCode(times,steps,onRepeated) | |
| if(repeatCode) eval(repeatCode); | |
| else this.enableRun(); | |
| } | |
| turnLeft( | |
| onMoved = (_) => { | |
| this.enableRun(); | |
| } | |
| ) { | |
| this.sudoTurnLeft(onMoved); | |
| } | |
| turnRight( | |
| onMoved = (_) => { | |
| this.enableRun(); | |
| } | |
| ) { | |
| this.sudoTurnRight(onMoved); | |
| } | |
| moveFaceward( | |
| onMoved = (_) => { | |
| this.enableRun(); | |
| } | |
| ) { | |
| const newcoords = this.nextCoordinates(); | |
| this.sudoMoveFace( | |
| !this.isAtBoxCoordinate(newcoords[0], newcoords[1]) | |
| ? (_) => { | |
| if (!this.isAtWinCoordinate(newcoords[0], newcoords[1])) { | |
| this.handleLoss(); | |
| } else { | |
| this.handleWin(onMoved); | |
| } | |
| } | |
| : (_) => { | |
| onMoved(); | |
| } | |
| ); | |
| } | |
| moveOppositeFace(onMoved = (_) => { | |
| this.enableRun(); | |
| }) { | |
| const newcoords = this.nextCoordinates(true); | |
| this.sudoMoveOppositeFace( | |
| !this.isAtBoxCoordinate(newcoords[0],newcoords[1]) | |
| ? (_) => { | |
| if (!this.isAtWinCoordinate(newcoords[0],newcoords[1])){ | |
| this.handleLoss(); | |
| } else { | |
| this.handleWin(onMoved); | |
| } | |
| } | |
| : (_) => { | |
| onMoved(); | |
| } | |
| ); | |
| } | |
| resetWinCoords() { | |
| this.acheivedDestinations = 0; | |
| this.newWinCoordinates = this.coord.win[data.level]; | |
| } | |
| /** | |
| * Updates the elements in array of wincoordinates if current coordinates of object match any of them. | |
| */ | |
| updateWinCoords() { | |
| let newcoords = []; | |
| this.newWinCoordinates.forEach((coord) => { | |
| if (!coordsEqual(coord, [this.object.x, this.object.y])) { | |
| newcoords.push(coord); | |
| } | |
| }); | |
| this.newWinCoordinates = newcoords; | |
| } | |
| handleLoss() { | |
| sound.stopObject(); | |
| this.loseObject(); | |
| sound.playLose(); | |
| new Dialog().showLevelLost((_) => { | |
| this.method.reset(); | |
| sound.playBGM(); | |
| }); | |
| } | |
| handleWin( | |
| onMoved, | |
| finalWinCondition = this.allDestinationsAcheived() || | |
| onMoved === `_=>{this.engine.enableRun()}` | |
| ) { | |
| this.acheivedDestinations++; | |
| if (finalWinCondition) { | |
| sound.stopObject(); | |
| this.winObject(); | |
| sound.playWin(); | |
| sendWin(); | |
| new Dialog().showLevelWon( | |
| (_) => { | |
| this.afterWin(); | |
| }, | |
| (_) => { | |
| this.method.reset(); | |
| },this.workspace.getShowableCode() | |
| ); | |
| } else { | |
| this.updateWinCoords(); | |
| onMoved(); | |
| } | |
| } | |
| downloadCanvas() { | |
| let link = document.createElement("a"); | |
| link.download = `${data.gamename} Level${data.level + 1}.png`; | |
| link.href = this.canvas.toDataURL(); | |
| link.click(); | |
| link.delete; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment