In recreating the Simon game, I am trying to push a click event to an array and then immediately test that Array in a nested function. On the first pass it seems to work.
However, on the third run the array does not seem to clear.
The screen shot below also shows that each input is printed multiple times to the console.
Full code pen here - https://codepen.io/jhc1982/pen/NwQZRw?editors=1010
Quick example:
function userMoves() {
var userInput = [];
document.getElementById("red").addEventListener("click", function(){
userInput.push("red");
testington();
});
$(".red").mousedown(function(event){
redAudio.play();
$(".red").css("background-color", "red");
});
$(".red").mouseup(function(){
$(".red").css("background-color", "#990000");
});
function testington(){
if (userInput.length == pattern.length) {
for (var i = 0; i < userInput.length; i++) {
if (userInput[i] !== pattern[i]) {
alert("Game Over");
} else if (i === userInput.length -1 && userInput[i] === pattern[i]) {
userInput = emptyArr;
simonMoves();
console.log("user input is ",userInput);
} else {
continue;
}
}
}
}
}
I am sure it is something really obvious but have been stuck for hours.
I think the problem may be that you are assigning the click events every time the userMoves execute. That means every time the function is called the event is added to the elements, so after two calls to userMoves() when you click on red the event is executed twice, after three calls it is executed three times, etc.
The code that adds the event listener should be out of the userMoves function. The testington function should also be out of userMoves, which would get much simpler:
function userMoves() {
$("#score-text").text(level);
userInput = [];
}
Here's a Pen with working code: https://codepen.io/anon/pen/ppzqyY
You need to add the break; keyword to after alert("Game over");
function testington(){
if (userInput.length == pattern.length) {
for (var i = 0; i < userInput.length; i++) {
if (userInput[i] !== pattern[i]) {
alert("Game Over");
break; // Break the loop
} else if (i === userInput.length -1 && userInput[i] === pattern[i]) {
userInput = emptyArr;
simonMoves();
console.log("user input is ",userInput);
} else {
continue;
}
}
}
}
Related
I have to create a slideshow, using an array of images and have that set on a timer. There is a drop-down menu with slow, medium, and fast options and the pictures need to transition with accordance to the drop down option selected. Whenever I execute this code in a web browser the code repeats itself, while doubling, as I read the value of i in the console.
I have tried using a while and a do-while loop to have the images on a rotation.
I have also tried putting the if-statements outside and below/above the function.
<script>
var i = 0;
function changeImg(){
if (x == 'slow'){
setInterval("changeImg()", 5000);
} else if (x == 'medium'){
setInterval("changeImg()", 3000);
} else if (x == 'fast') {
setInterval("changeImg()", 1000);
} else {}
while (i < 3){
console.log(i);
document.slide.src = sportsArray[i];
i++;
}
console.log(i);
console.log(sportsArray);
}
</sctipt>
First, I would read up on MDN's docs on setInterval() and clearInterval to fill in the knowledge gaps that lead you to approach the problem this way.
You are recursively calling changeImg() in your code which I believe is causing the issue you describe as:
the code repeats itself, while doubling, as I read the value of i in the console
Also, your while loop will run immediately when calling changeImg() which also does not appear to be desired in this situation.
setInterval() mimics a while loop by nature. There is no need for a while loop in this code. Below is a solution that I hope you can use as a reference. I separated the code to determine the interval into a function the getIntervalSpeed.
function changeImg(x) {
var getIntervalSpeed = function(x) {
if (x === 'slow') {
return 5000;
} else if (x === 'medium') {
return 3000;
} else if (x === 'fast') {
return 1000;
} else {
return 3000;
// return a default interval if x is not defined
}
};
var i = 0;
var slideShow = setInterval(function() {
if (i >= sportsArray.length) {
i = 0; // reset index
}
document.slide.src = sportsArray[i]; // set the slide.src to the current index
i++; // increment index
}, getIntervalSpeed(x));
// on click of ANY button on the page, stop the slideshow
document.querySelector('button').addEventListener('click', function() {
clearInterval(slideShow);
});
}
I am new to javascript, my background is python and ruby and I am having issues dealing with javascript anonymous functions, my problem is the following:
I have an element in the page which has an attribute value (true/false), I need to keep performing an action until this attribute changes value.
the code that I tried out is below, as you could guess, the break won't quit the loop if result.value == true... Any idea if I am in the right direction?
var counter = 0;
while (counter < 5) {
this
.click('#someelementid')
counter++;
this
.getAttribute('#someelementid', 'disabled', function(result) {
if (result.value == 'true') {
this.break;
}
}.bind(this));
this.api.pause(1000);
};
My assumption is that by binding this, I have access to the while block? Please correct me if I am wrong.
Have you tried changing your code to something like below ?
var counter = 0;
var arr = [1, 2, 3, 4, 5];
while (counter < 5) {
arr.forEach(
(element) => {
if (counter < 5) { // if you don't have this it will print all 5, else it prints only 3
console.log('Element value:', element);
}
if (element == 3) {
counter = 5;
}
}
);
}
What happens is this:
When you enter the while loop it will enter the forEach loop and start iterating, when it reaches element == 3 it will set counter to 5, but it still has to finish the current forEach loop, once it does it wont perform another while loop therefore exiting it.
So changing your code to:
var counter = 0;
while (counter < 5) {
this
.click('#someelementid')
counter++;
this
.getAttribute('#someelementid', 'disabled', function(result) {
if (result.value == 'true') {
counter = 5; // This should do the trick (without 'this')
}
}.bind(this));
this.api.pause(1000);
};
The break inside a function would not break the loop outside the function.
Through getAttribute() get the result inline instead of having a callback. then you can break the function.
Or
Have a global variable. breakLoop = false; set it to true; inside the callback function. And put a check on this variable in the while loop. while(!breakLoop ..)
Another approach.
Don't have a loop altogether.
var counter = 0;
function doSomething(){
this.click('#someelementid');
counter++;
this.getAttribute('#someelementid', 'disabled', function(result) {
if(result.value != 'true' && counter<5){
this.api.pause(1000);
doSomething();
}
}.bind(this);
};
I'm writing a simple card game, but for some reason this code below behaves very strangely...The turn functions is first called using theTurn(0)
players is an array of objects with player name and hand etc.
function theTurn(playerNumber) {
if(play == 1) {
$('#player').text(players[playerNumber].Name);
$('#submit').click(function() {
nextPlayer(playerNumber);
})
}
}
function nextPlayer(playerNumber) {
if(playerNumber == players.length - 1) {
theTurn(0);
} else {
theTurn(playerNumber + 1);
}
}
For some reason I get player 0 then 1 then 1 again and then 0.
I've left out some of the stuff in theTurn...but this is the gist of it and the problem shows up in this simplified version too.
Any help with my faulty logic would be greatly appreciated.
This actually makes a little more sense... just add the click handler once, then set the player number as a data property so the nextPlayer function knows what it is without an argument.
$('#player').data('activePlayer', 0);
$('#submit').click(function() {
nextPlayer();
});
function theTurn(playerNumber) {
if(play == 1) {
$('#player').text(players[playerNumber].Name);
$('#player').data('activePlayer', playerNumber);
}
}
function nextPlayer() {
var playerNumber = $('#player').data('activePlayer');
if(playerNumber == players.length - 1) {
theTurn(0);
} else {
theTurn(playerNumber + 1);
}
}
I have a long list of else if statements.
Each one does a document.getElementById to check for existence of some element.
In one of of the else if statements to the bottom i need to not only do getElementById but I need to check also if that element has a certain attribute. This made me do getElementById twice, which i was hoping to avoid.
This is my code:
if (doc.getElementById('blah')) {
} else if (doc.getElementById('blah2')) {
} else if (doc.getElementById('js_blob') && doc.getElementById('js_blob').hasAttribute('action')) {
//here
} else if (doc.getElementById('blah3')) {
} else if (doc.getElementById('blah4')) {
} else {
console.warn('none of them');
}
Notice the line: } else if (doc.getElementById('js_blob') && doc.getElementById('js_blob').hasAttribute('action')) {
I had tried something like this and it didnt work: } else if (var myBlobL = doc.getElementById('js_blob') && myBlobL.hasAttribute('action')) { this would give syntax error
anyway to avoid doing double getElementById in this else if statement?
Thanks
Use a temporary variable:
var tmp;
// ...
} else if ((tmp = doc.getElementById('js_blob')) && tmp.hasAttribute('action')) {
You can save the result in a variable, in this case you only once will call getElementById
var js_blob = doc.getElementById('js_blob')
if (js_blob && js_blob.hasAttribute('action')) {
}
I see that you have logic with many "else if", I think you can replace it on something like this
function fn() {
var ids = ['elem1', 'elem2', 'elem3'],
len = ids.length,
i, el;
for (i = 0; i < len; i++) {
el = document.getElementById(ids[i]);
if (el) {
break;
}
}
if (!el) {
return;
}
// work with el which has action
// or add logic for specific ID
if (el.hasAttribute('action')) {
el.innerHTML = 'action';
} else if (el.getAttribute('id') === 'elem2') {
el.innerHTML = 'ELEM2';
}
}
fn();
Need decrease count appeals to the DOM, in this case you just once will appeal to each element, and further will work only with copy.
Thanks to the help of you fine Overflowians, I fixed up my silly little RNG Addition game and got it working. Now, at one user's suggestion, I'm trying to change the scope of the addition game's code from global to local before I code up the next game; I want each game to be completely contained within its own scope, as I understand that learning to not thoughtlessly contaminate the global scope is a good idea. However, I'm a bit stuck on how to achieve that.
Here's the code for the currently functional addition game:
function beginAdditionChallenge() {
var x = Math.ceil(Math.random()*100);
alert(x);
for (var i = 0; i < 3; i++) {
var a = Number(prompt("Provide the first addend.", ""));
var b = Number(prompt("Provide the second addend.", ""));
if (a + b === x) {
alert("Well done!");
break;
}
else if (a + b !== x && i < 2) {
alert("Please try again.");
}
else {
alert("Derp.");
}
}
}
function initChallenge() {
var button = document.getElementById("challengeButton");
button.addEventListener("click", beginAdditionChallenge);
}
window.addEventListener("load", initChallenge);
And here's my attempt to wrap it, which only succeeds in breaking the game, such that the button doesn't even respond:
window.addEventListener("load", function() {
function beginAdditionChallenge() {
var x = Math.ceil(Math.random()*100);
alert(x);
for (var i = 0; i < 3; i++) {
var a = Number(prompt("Provide the first addend.", ""));
var b = Number(prompt("Provide the second addend.", ""));
if (a + b === x) {
alert("Well done!");
break;
}
else if (a + b !== x && i < 2) {
alert("Please try again.");
}
else {
alert("Derp.");
}
}
}
function initChallenge() {
var button = document.getElementById("challengeButton");
button.addEventListener("click", beginAdditionChallenge);
}
window.addEventListener("load", initChallenge);
});
Functional code is available on JSFiddle here.
Note that the onLoad option in JSFiddle does the same as your 2nd snippet. You'll want to choose one of the No wrap options when binding to 'load' yourself.
And, the issue stems from attempting to bind to 'load' within a 'load' handler:
window.addEventListener("load", function() {
// ...
window.addEventListener("load", initChallenge);
});
When the event is already firing and handling the outer binding, it's too late to add more handlers to it. They won't be cycled through and the event shouldn't occur again.
You'll either want to call initChallenge within the outer event binding:
window.addEventListener("load", function() {
// ...
initChallenge();
});
Or, you can use an IIFE with the inner binding:
(function () {
// ...
window.addEventListener("load", initChallenge);
})();