Skip to content

Instantly share code, notes, and snippets.

@ranjanistic
Created September 20, 2025 16:13
Show Gist options
  • Select an option

  • Save ranjanistic/dd39bdae19b306649f9b482a1be73284 to your computer and use it in GitHub Desktop.

Select an option

Save ranjanistic/dd39bdae19b306649f9b482a1be73284 to your computer and use it in GitHub Desktop.
'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