I'm trying to make bomberman using vanilla JS, for my examination project.
I am a little stuck right now with how to take out the bombs in the array and push them into the array again after they explode.
They need to explode after 2 seconds.
My code for bombs:
function bombPlayerOne() {
let ss = new createjs.SpriteSheet(game.q.getResult('bomb'))
let temp = new createjs.Sprite(ss, "bombIt");
temp.x = playerOne.x;
temp.y = playerOne.y;
game.stage.addChild(temp);
powerUps.bombs.push(temp);
console.log("player one placed a bomb");
for (var i = powerUps.bombs.length - 1; i > 0; i--) {
powerUps.bombs.splice;
// TODO : tween bomber ud...
powerUps.bombs.push;
}
}
function bombPlayerTwo() {
let ss = new createjs.SpriteSheet(game.q.getResult('bomb'))
let temp = new createjs.Sprite(ss, "bombIt");
temp.x = playerTwo.x;
temp.y = playerTwo.y;
game.stage.addChild(temp);
powerUps.bombs.push(temp);
console.log("player two placed a bomb");
for (var i = powerUps.bombs.length - 1; i > 0; i--) {
powerUps.bombs.splice;
// TODO : tween bomber ud...
powerUps.bombs.push;
}
}
So you have a few options, and FYI this isn't necessarily a javascript question so much as how do you handle game logic/code design type of question.
1) A bomb when placed contains a reference back to it's owner. ie
bomb.owner = playerOne
2) You have a manager that controls the state of a level, which keeps track of bombs
LevelManager.player1Bombs = ....
3) You have an array of bombs placed belonging to each player, which you then update during your logic update calls.
function gameUpdate(long milliSecondsSinceLastFrame){
for(bomb in playerOne.placedBombs){
if(bomb.isExploded){
//do other cleanup
playerOne.availableBombs ++;
}
}
//... do same for player 2 etc
}
All of them have their own advantages/disadvantages.
Related
I'm sorry if this has been asked before,
I've searched through Stackoverflow but couldn't find anything that answered my problem.
I'm building a simple memory game, an online version of Simon, when you click the "Start" button it runs the code below to create a random array (of length 4) out of the four colour buttons.
But when you click "Start" again for the next round it doesn't clear the array, and instead creates a second one, and then checks your input against both telling you you're both right and wrong, or right and right (depending on the random array created out of the buttons).
I've tried buttonsToClick = [] in the else section, but it doesn't reset.
I don't know what I'm missing, I've only been learning JavaScript/jQuery for about a month but I wanted to test my knowledge.
The code snipped:
var score = 0;
$("#score").html(`${score}`);
$("#button5").on("click", function() {
var buttons = document.getElementsByClassName("js-button");
var buttonsToClick = chooseRandomButtons(buttons);
currentButtons = buttonsToClick;
flashButtons(buttonsToClick, 0);
var currentOrder = 0;
$(".js-button").on("click", function() {
var selectedButton = $(this)[0];
var button = currentButtons[0];
if (selectedButton === button) {
currentButtons.splice(button,1);
/*alert("Correct");*/
score += 1;
$("#score").html(`${score}`);
} else {
currentButtons = buttonsToClick;
alert("Wrong. Click 'Start' to try again");
score = 0;
$("#score").html(`${score}`);
}
});
})
function chooseRandomButtons(buttons) {
var buttonsToClick = [];
var maxRandomNumber = buttons.length - 1;
for (var i = 0; i < 4; i++) {
buttonsToClick.push(buttons[randomIntFromInterval(0, maxRandomNumber)]);
}
return buttonsToClick;
}
function randomIntFromInterval(min, max) { // min and max included
return Math.floor(Math.random() * (max - min + 1) + min);
}
function flashButtons(buttonsToClick, index) {
setTimeout(function() {
$(buttonsToClick[index]).fadeOut(500).fadeIn(500);
if (index === buttonsToClick.length - 1) {
return;
}
flashButtons(buttonsToClick, index = index + 1);
}, 1000);
}
welcome to SO.
In general you're doing anything correct with your arrays.
The issue is your event handler.
Every time you click the #button5, which I guess is the start button, you register on all your .js-button a new listener. Since you're not unbinding the old event listeners they're still active.
Since the old event listeners have a reference to your old array, you're basically checking the button against the old game and the new game.
Your solution would be to unregister the old one before registering the new one.
This could be done for example by the .off method.
Your code should then look like this:
var currentOrder = 0;
$(".js-button").off("click").on("click", function() {
var selectedButton = $(this)[0];
var button = currentButtons[0];
if (selectedButton === button) {
currentButtons.splice(button,1);
/*alert("Correct");*/
score += 1;
$("#score").html(`${score}`);
} else {
currentButtons = buttonsToClick;
alert("Wrong. Click 'Start' to try again");
score = 0;
$("#score").html(`${score}`);
}
});
Notice the .off() there.
The documentation about the method could be found here: https://api.jquery.com/off/
I am writing a simulation for bunny survival in a meadow and have to detect the minimal plant growth rate for the bunny to survive. I decided to go with OOP. Hence, tried to design my "classes" in js. I haven't done much OOP in JS, so I am stuck. I don't understand why I keep getting "this.checkElementExists" is not a function.
I tried to follow OOP that was shown in Mozilla MDN for JS and here I am stuck. I updated to ES6 classes.
class Meadow{
constructor(){
this.grid = this.makeGrid();
//console.log(this.grid);
}
makeGrid(){
let grid = new Array(30);
for(var i=0; i < 30; i++){
grid[i] = new Array(30).fill(null);
}
return grid;
}
checkElementExists(coordinates){
if(this.grid[coordinates[0]][coordinates[1]] != null){
return true;
}else{
return false;
}
}
growAPlant(timeRate){
if(timeRate == null){
clearInterval(this.growAPlant);
}
let plant = new Plant();
let coord = plant.generateCoordinateInMeadow();
//console.log(coord);
// add a plant to the 2d array, but check if the that spot is free
// otherwise use the generateCOordinate in the meadow function
//console.log(this.grid[coord[0]][coord[1]]);
//var that = this;
var ans = checkElementExists(coord).bind(this);
console.log(ans);
while(!checkElementExists(coord)){
coord = plant.generateCoordinateInMeadow();
}
//console.log(coord);
//console.log(this.grid[coord[0]] == undefined);
this.grid[coord[0]][coord[1]] = plant;
//console.log(this.grid);
}
}
class Simulation{
constructor(){
this.passRateArray = []; // this array will be used to plot the data
this.failureRateArray = []; // array that will hold failure growth rates
this.timeToEnergyData = []; // an example would be [{0: 1000, 1: 999, 2: 998, ....., 10000: 0}]
this.rateToEnergyTimeData = {};
this.timeCounter = 100; // 10000
this.growthTimeRate = 1000; // 1 second
this.gap = 0.05;
this.meadow = new Meadow();
this.bunny = new Bunny();
}
timeToEnergyDataPopulator(currTime, energy){
var relation = {currTime : energy};
this.timeToEnergyData.push(relation);
}
simulation(){
// HERE I MAKE A CALL TO MEADOW.GROWAPLANT
setInterval(this.meadow.growAPlant.bind(this.meadow), this.growthTimeRate);
//meadow.growAPlant(this.growthTimeRate);
let bunnyMove = this.bunny.move();
// not enough energy, bunny did not survive
if(bunnyMove == false){
this.timeToEnergyDataPopulator(this.timeCounter, bunny.getBunnyEnergy());
let rate = this.growthTimeRate / 1000;
this.rateToEnergyTimeData = {rate : this.timeToEnergyData};
// add the failure rate to the failureRateArray
this.failureRateArray.push(this.growthTimeRate);
// increase the rate of plant growth
if(this.passRateArray.length < 1){
this.growthTimeRate = this.growthTimeRate + this.growthTimeRate * 0.5;
}else{
let lastSurvivalRate = this.passRateArray[this.passRateArray.length - 1];
this.growthTimeRate = lastSurvivalRate - ((lastSurvivalRate - this.growthTimeRate)*0.5);
}
// stop the meadow from growing a plant
meadow.growAPlant(null);
// stop the simulation
clearInterval(this.simulation);
}
while(!this.meadow.checkValidBunnyMove(bunnyMove).bind(this.meadow)){
bunnyMove = bunny.move();
}
if(meadow.checkIfBunnyEats(bunnyMove)){
// since bunny made still a move, -1 energy
bunny.decreaseEnergyByOne();
// and since the meadow at that coordinate had food, we add +10 to energy via eatPlant method
bunny.eatPlant();
// track the time to energy data
this.timeToEnergyDataPopulator(this.timeCounter, bunny.getBunnyEnergy);
}else{
// no food, -1 energy
bunny.decreaseEnergyByOne();
// track the time to energy data
this.timeToEnergyDataPopulator(this.timeCounter, bunny.getBunnyEnergy);
}
// decrement the timeCounter
this.timeCounter -= 1;
if(this.timeCounter <= 0){
this.timeToEnergyDataPopulator(this.timeCounter, bunny.getBunnyEnergy());
let rate = this.growthTimeRate / 1000;
this.rateToEnergyTimeData = {rate : this.timeToEnergyData};
this.passRateArray.push(this.growthTimeRate);
// bunny survived, adjust the growth rate
if(this.failureRateArray.length < 1){
this.growthTimeRate = this.growthTimeRate - (this.growthTimeRate * 0.5);
}else{
let lastFailureRate = this.failureRateArray[this.failureRateArray.length - 1];
this.growthTimeRate = this.growthTimeRate - ((this.growthTimeRate - lastFailureRate) * 0.5);
}
clearInterval(this.simulation);
}
}
runner(){
while(this.passRateArray[this.passRateArray.length - 1] - this.failureRateArray[this.failureRateArray.length - 1] > this.gap || this.passRateArray.length == 0 || this.failureRateArray.length == 0){
setInterval(this.simulation(), 1000);
}
console.log("The minimum plant regeneration rate required to sustain the bunny for 10000 units of time is " +
this.growthTimeRate + " regenerations/unit time");
}
}
Errors that I get:
1) simulation.js:62 Uncaught TypeError: this.meadow.checkValidBunnyMove is not a function
at Simulation.simulation (simulation.js:62)
at Simulation.runner (simulation.js:101)
at (index):24
2) meadow.js:1 Uncaught SyntaxError: Identifier 'Meadow' has already been declared
at VM16689 meadow.js:1
3) VM16689 meadow.js:37 Uncaught ReferenceError: checkElementExists is not defined
at Meadow.growAPlant (VM16689 meadow.js:37)
My question is why the number 1 and 3 errors persist?
clearInterval(this.growAPlant);
This clearInterval isn’t correct, because you need pass the return value of setInterval to it, not a function. It does helpfully imply that you have a setInterval(someMeadow.growAPlant, …) somewhere, though, and that’s where the issue is. When you reference a function without calling it – like when you pass it to setInterval – the object it belonged to doesn’t come with it. Then, when the timer fires, it calls the function without a this value.
In JavaScript, the value of this inside a non-arrow function is determined entirely by how the function is called, not where it’s declared. You can read about how this works in various other questions and pieces of documentation. Fixing the problem involves giving growAPlant the correct this somehow, either by:
placing a reference to it in a containing scope (i.e. moving your var that = this out one level and using that instead of this throughout)
wrapping the function in one that’ll preserve the correct value, as in
setInterval(someMeadow.growAPlant.bind(someMeadow), …);
(Function.prototype.bind) or
setInterval(function () {
someMeadow.growAPlant();
}, …);
(the someMeadow.growAPlant reference is now part of a call, so someMeadow becomes the this value for the call)
changing it into an arrow function, which doesn’t have its own this and uses the one from the containing scope
Only (2) will work when you convert to the simplest form of an ES6 class, so it’s the approach I recommend.
Explanation
First, this always refers to the first parent function. In your case it is:
this.growAPlant = function(timeRate){
//content
var that = this; // this is growAPlant
}
And
this.checkElementExists = function(coordinates){ }
Is accessible with Meadow object. However, your var that is referring to this.growAPlant = function(timeRate) not Meadow.
Solution
Create that in the beginning
function Meadow(){
var that = this;
that.growAPlant = function(timeRate){
}
that.checkElementExists = function(coordinates){
if(this.grid[coordinates[0]][coordinates[1]] != null){
return true;
}else{
return false;
}
};
return that;
}
Well, I'm making a function to update player's hp over time.
On my NodeJS side, I'm passing the players array to require('./addHp')(players); Inside addHp.js I have this fully working function:
module.exports = function (players) {
var addHp = setInterval ( function () {
for (var player in players) {
var thisPlayer = players[player];
(thisPlayer.hp < thisPlayer.maxHp) ? //Uggly
thisPlayer.hp += 10 : //Uggly
thisPlayer.hp = thisPlayer.maxHp; //Uggly
}
}, 2000);
}
But it's really not too easy to read so I've made some changes:
module.exports = function (players) {
var addHp = setInterval ( function () {
for (var player in players) {
var thisPlayer = players[player];
var hp = thisPlayer.hp;
var maxHp = thisPlayer.maxHp;
(hp < maxHp) ? hp += 10 : hp = maxHp; //Beautiful
}
}, 2000);
}
All the proper values are reaching both hp and maxHp, but when I change the hp to 5000, the value of players[player].hp won't change. Why? And how can I use it this way?
This doesn't work because hp (which is probably a numeric value, and not an object at all) is not attached to any outside object. Your object is a value in the players array (or players object, doesn't really matter), and you need to mutate that. There's really no way around this.
With that said, I'd write this a little different:
for (var player in players) {
var p = players[player];
p.hp = Math.max(p.maxHp, p.hp + 10);
}
If players is an Array, you can even do:
for (var player of players) {
player .hp = Math.max(player .maxHp, player .hp + 10);
}
I am attempting to create an app in Javascript/HTML/CSS to run board games, so I can teacher my 11th and 12th grades students to do the same. Mostly I've got it working, including a dice roll, but I have a counter that determines whose turn it is that is returning NaN for one of the turns. It does return all other turns, but adds in the NaN as well.
I'm not using a for() loop because it is inside a given function that starts a player turn. All of the answers I've found online and here at StackOverflow refer to issues in counters using the for() loop.
Here's my code:
var p1="Name1";
var p2="Name2";
var p3="Name3";
var playerList=new Array(p1, p2, p3);
var pTurn=0;
var currentPlayer=playerList[pTurn];
function nextPlayer() {
pTurn++;
if(pTurn<playerList.length) {
pTurn=0;
}
currentPlayer=playerList[pTurn];
/* the rest of the function sends the data to the html page and works */
}
You need to reset pTurn when it is too large for the list. That is:
if (pTurn >= playerList.length) {
pTurn = 0;
}
var p1 = "Name1";
var p2 = "Name2";
var p3 = "Name3";
var playerList = new Array(p1, p2, p3);
var pTurn = 0;
var currentPlayer = playerList[pTurn];
function nextPlayer() {
pTurn++;
if (pTurn >= playerList.length) {
pTurn = 0;
}
currentPlayer = playerList[pTurn];
}
for (var i = 0; i < 10; ++i) {
console.log(currentPlayer);
nextPlayer();
}
I'm trying to pick a random film from an object containing film objects. I need to be able to call the function repeatedly getting distinct results until every film has been used.
I have this function, but it doesn't work because the outer function returns with nothing even if the inner function calls itself because the result is not unique.
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var moviesLength = Object.keys(movies).length;
function doPick() {
var pick = pickRandomProperty(movies);
var distinct = true;
for (var i = 0;i < watchedFilms.length; i += 1) {
if (watchedFilms[i]===pick.title) {
distinct = false;
if (watchedFilms.length === moviesLength) {
watchedFilms = [];
}
}
}
if (distinct === true) {
watchedFilms.push(pick.title);
return pick;
}
if (distinct === false) {
console.log(pick.title+' has already been picked');
doPick();
}
};
return doPick();
}
T.J. Crowder already gave a great answer, however I wanted to show an alternative way of solving the problem using OO.
You could create an object that wraps over an array and makes sure that a random unused item is returned everytime. The version I created is cyclic, which means that it infinitely loops over the collection, but if you want to stop the cycle, you can just track how many movies were chosen and stop once you reached the total number of movies.
function CyclicRandomIterator(list) {
this.list = list;
this.usedIndexes = {};
this.displayedCount = 0;
}
CyclicRandomIterator.prototype.next = function () {
var len = this.list.length,
usedIndexes = this.usedIndexes,
lastBatchIndex = this.lastBatchIndex,
denyLastBatchIndex = this.displayedCount !== len - 1,
index;
if (this.displayedCount === len) {
lastBatchIndex = this.lastBatchIndex = this.lastIndex;
usedIndexes = this.usedIndexes = {};
this.displayedCount = 0;
}
do index = Math.floor(Math.random() * len);
while (usedIndexes[index] || (lastBatchIndex === index && denyLastBatchIndex));
this.displayedCount++;
usedIndexes[this.lastIndex = index] = true;
return this.list[index];
};
Then you can simply do something like:
var randomMovies = new CyclicRandomIterator(Object.keys(movies));
var randomMovie = movies[randomMovies.next()];
Note that the advantage of my implementation if you are cycling through items is that the same item will never be returned twice in a row, even at the beginning of a new cycle.
Update: You've said you can modify the film objects, so that simplifies things:
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
var counter = keyCount * 2;
// Try a random pick
while (--counter) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
// We've done two full count loops and not found one, find the
// *first* one we haven't watched, or of course return null if
// they've all been watched
for (counter = 0; counter < keyCount; ++counter) {
candidate = movies[keys[counter]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
return null;
}
This has the advantage that it doesn't matter if you call it with the same movies object or not.
Note the safety valve. Basically, as the number of watched films approaches the total number of films, our odds of picking a candidate at random get smaller. So if we've failed to do that after looping for twice as many iterations as there are films, we give up and just pick the first, if any.
Original (which doesn't modify film objects)
If you can't modify the film objects, you do still need the watchedFilms array, but it's fairly simple:
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
if (watchedFilms.length >= keyCount) {
return null;
}
while (true) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (watchedFilms.indexOf(candidate) === -1) {
watchedFilms.push(candidate);
return candidate;
}
}
}
Note that like your code, this assumes getRandomFilm is called with the same movies object each time.