jQuery Memory Skill game setTimeout() issues - javascript

I am making a little game of Simon with jQuery. I have the functionality I want; start on page load, score, round numbers, etc, and the game works to an extent.
However, I still have a problem that I can't get my head around. I want to be able to prevent the user from being able to select the panels during the computer's turn. Currently, the user can trigger a sequence during the computer displaying its output, which causes havoc with buttons flashing and sounds going off.
The issue lies in setTimeout(). I tried to implement a variable 'cpuLoop' which turns to true when it's the computer's turn, and then back to false, but the implementation of setTimeout() means that there are still events on the event loop even after cpuLoop has been changed to false. The change to false changes immediately when of course it should wait until the setTimeout() has completed.
A similar problem is encountered when the reset button is clicked. When clicked, it should interrupt the setTimeout() events and restart the game. As it is, it continues outputting the computer's turn.
To get around this, I have attached the setTimeout() functions in the global scope and attempted to cut them off with clearInterval(var) but this seems to have no effect at the moment.
Here is my jQuery:
$(function(){
var counter = 0;
var cpuArray = [];
var cpuSlice = [];
var numArray = [];
var userArray = [];
var num = 1;
var wins = 0;
var losses = 0;
var cpuLoop = false;
// Initialise the game
function init(){
$('#roundNumber').html('1');
counter = 0;
cpuArray = [];
numArray = [];
userArray = [];
cpuLoop = false;
num = 1;
// Create cpuArray
function generateRandomNum(min, max){
return Math.floor(Math.random() * (max - min) + min);
}
for(var i = 1; i <= 20; i++){
numArray.push(generateRandomNum(0, 4));
}
for(var i = 0; i < numArray.length; i++){
switch(numArray[i]){
case 0:
cpuArray.push('a');
break;
case 1:
cpuArray.push('b');
break;
case 2:
cpuArray.push('c');
break;
case 3:
cpuArray.push('d');
break;
}
}
console.log('cpuArray: ' + cpuArray);
// Create a subset of the array for comparing the user's choices
cpuSlice = cpuArray.slice(0, num);
goUpToPoint(cpuSlice);
}
init();
var looperA, looperB, looperC, looperD;
// Cpu plays sounds and lights up depending on cpuArray
function cpuPlayList(input, time){
setTimeout(function(){
if(input === 'a'){
looperA = setTimeout(function(){
aSoundCpu.play();
$('#a').fadeOut(1).fadeIn(500);
}, time * 500);
} else if(input === 'b'){
looperB = setTimeout(function(){
bSoundCpu.play();
$('#b').fadeOut(1).fadeIn(500);
}, time * 500);
} else if(input === 'c'){
looperC = setTimeout(function(){
cSoundCpu.play();
$('#c').fadeOut(1).fadeIn(500);
}, time * 500);
} else if(input === 'd'){
looperD = setTimeout(function(){
dSoundCpu.play();
$('#d').fadeOut(1).fadeIn(500);
}, time * 500);
}
}, 1750);
};
// CPU takes its turn
function goUpToPoint(arr){
cpuLoop = true;
console.log('cpuLoop: ' + cpuLoop);
for(var i = 0; i < arr.length; i++){
cpuPlayList(arr[i], i);
}
cpuLoop = false;
console.log('cpuLoop: ' + cpuLoop);
}
// User presses restart button
$('.btn-warning').click(function(){
clearTimeout(looperA);
clearTimeout(looperB);
clearTimeout(looperC);
clearTimeout(looperD);
init();
});
// Array comparison helper
Array.prototype.equals = function (array) {
// if the other array is a falsy value, return
if (!array)
return false;
// compare lengths - can save a lot of time
if (this.length != array.length)
return false;
for (var i = 0, l=this.length; i < l; i++) {
// Check if we have nested arrays
if (this[i] instanceof Array && array[i] instanceof Array) {
// recurse into the nested arrays
if (!this[i].equals(array[i]))
return false;
}
else if (this[i] != array[i]) {
// Warning - two different object instances will never be equal: {x:20} != {x:20}
return false;
}
}
return true;
}
// User presses one of the four main buttons
function buttonPress(val){
console.log('strict?: ' + $('#strict').prop('checked'));
console.log('cpuSlice: ' + cpuSlice);
userArray.push(val);
console.log('userArray: ' + userArray);
if(val === 'a'){ aSoundCpu.play(); }
if(val === 'b'){ bSoundCpu.play(); }
if(val === 'c'){ cSoundCpu.play(); }
if(val === 'd'){ dSoundCpu.play(); }
// If the user selected an incorrect option
if(val !== cpuSlice[counter])
//Strict mode off
if(!$('#strict').prop('checked')){
// Strict mode off
alert('WRONG! I\'ll show you again...');
userArray = [];
console.log('cpuSlice: ' + cpuSlice);
goUpToPoint(cpuSlice);
counter = 0;
} else {
//Strict mode on
losses++;
$('#lossCount').html(losses);
ui_alert('You lose! New Game?');
return;
} else {
// User guessed correctly
counter++;
}
if(counter === cpuSlice.length){
$('#roundNumber').html(counter + 1);
}
if(counter === 5){
ui_alert('YOU WIN!');
$('#winCount').html(++wins);
return;
}
console.log('counter: ' + counter);
if(counter === cpuSlice.length){
console.log('num: ' + num);
cpuSlice = cpuArray.slice(0, ++num);
console.log('userArray:' + userArray);
userArray = [];
console.log('cpuSlice: ' + cpuSlice);
goUpToPoint(cpuSlice);
counter = 0;
}
}
// Button presses
$('#a').mousedown(function(){
if(!cpuLoop){
buttonPress('a');
}
});
$('#b').mousedown(function(){
if(!cpuLoop) {
buttonPress('b');
}
});
$('#c').mousedown(function(){
if(!cpuLoop){
buttonPress('c');
}
});
$('#d').mousedown(function(){
if(!cpuLoop){
buttonPress('d');
}
});
// jQuery-UI alert for when the user has either won or lost
function ui_alert(output_msg) {
$("<div></div>").html(output_msg).dialog({
height: 150,
width: 240,
resizable: false,
modal: true,
position: { my: "top", at: "center", of: window },
buttons: [
{
text: "Ok",
click: function () {
$(this).dialog("close");
init();
}
}
]
});
}
// Sound links
var aSoundCpu = new Howl({
urls: ['https://s3.amazonaws.com/freecodecamp/simonSound1.mp3'],
loop: false
});
var bSoundCpu = new Howl({
urls: ['https://s3.amazonaws.com/freecodecamp/simonSound2.mp3'],
loop: false
});
var cSoundCpu = new Howl({
urls: ['https://s3.amazonaws.com/freecodecamp/simonSound3.mp3'],
loop: false
});
var dSoundCpu = new Howl({
urls: ['https://s3.amazonaws.com/freecodecamp/simonSound4.mp3'],
loop: false
});
});
and here is a link to the app on codepen. Many thanks

This seemed to work OK for me for disabling user input during the computer's turn:
function goUpToPoint(arr){
cpuLoop = true;
console.log('cpuLoop: ' + cpuLoop);
for(var i = 0; i < arr.length; i++){
cpuPlayList(arr[i], i);
}
//cpuLoop = false;
setTimeout(function() {
cpuLoop = false;
}, arr.length * 500 + 1750);
console.log('cpuLoop: ' + cpuLoop);
}
Then for the reset button, put this with your globals above function init()
timeoutsArray = [];
and make these function edits:
// Cpu plays sounds and lights up depending on cpuArray
function cpuPlayList(input, time){
timeoutsArray.push(setTimeout(function(){
if(input === 'a'){
timeoutsArray.push(setTimeout(function(){
aSoundCpu.play();
$('#a').fadeOut(1).fadeIn(500);
}, time * 500));
} else if(input === 'b'){
timeoutsArray.push(setTimeout(function(){
bSoundCpu.play();
$('#b').fadeOut(1).fadeIn(500);
}, time * 500));
} else if(input === 'c'){
timeoutsArray.push(setTimeout(function(){
cSoundCpu.play();
$('#c').fadeOut(1).fadeIn(500);
}, time * 500));
} else if(input === 'd'){
timeoutsArray.push(setTimeout(function(){
dSoundCpu.play();
$('#d').fadeOut(1).fadeIn(500);
}, time * 500));
}
}, 1750));
};
// User presses restart button
$('.btn-warning').click(function(){
for(var i = 0; i < timeoutsArray.length; i++) {
clearTimeout(timeoutsArray[i]);
}
timeoutsArray = [];
init();
});
I think you were replacing some of your looperX variable values. Using an array to store all of your setTimeout functions guarantees that they all get cleared.

Your problem is that setTimeout is an asynchronous function, which means that once you called it, the code after it continue as if it is done.
If you want the code to wait until the end of your loop, you need to invoke it at the end of the setTimeout function.
You could split your function in two (in your case it's the goUpToPoint function), something like this:
function first_part() {
//Call setTimeout
setTimeout(function() { some_function(); }, time);
}
function second_part() {
// Rest of code...
}
function some_function() {
//Delayed code...
...
second_part();
}
Since you are calling your function a number of times, I would create a global counter that you can decrease at the end of each setTimeout call, and call the second_part function only if the counter is 0:
var global_counter = 0;
function first(num) {
//Call setTimeout
global_counter = num;
for (var i = 0; i < num; i++) {
setTimeout(function() { some_function(); }, time);
}
}
function second() {
// Rest of code...
}
function some_function() {
//Delayed code...
...
// Decrease counter
global_counter--;
if (global_counter == 0) {
second();
}
}

Related

function doesn't register variable change

I'm trying to make a small game with Javascript where the user has to enter the sum of two random numbers (a and b).When you click on a button or when you press enter, it calls a function which checks if the sum is the same as what you entered. If that's the case, the function play() is called again and a and b change. It works fine the first time, but the second time, unless the second sum is equal to the first one, it doesn't work. What does my answer() function acts as if a and b didn't change ?
let count = 0;
function play() {
if (count < 10) {
// setTimeout(loss, 30000);
count += 1;
document.getElementById("user-answer").value = "";
var a = Math.floor(Math.random() * 20) + 1;
var b = Math.floor(Math.random() * 20) + 1;
var question = document.getElementById("question");
question.textContent = a + " + " + b;
function answer() {
var result = a + b;
var userAnswer = document.getElementById("user-answer").value;
if (userAnswer == result) {
sound.play();
//clearTimeout();
play();
}
if (userAnswer != result) {
document.getElementById("user-answer").classList.add("wrong");
// document.getElementById("user-answer").disabled = true;
console.log(result);
console.log(userAnswer);
// setTimeout(remove, 1000);
}
}
window.addEventListener("keypress", function(event) {
if (event.key == "Enter") {
answer();
}
})
document.getElementById("send-answer").addEventListener("click", answer);
} else {
document.getElementById("win").textContent = "You won!";
}
}
Well, calling play repeatedly will create new local variables a and b and new instances of answer function closing over those a, b and will add new event listeners every time. You could:
move the declaration of answer outside play
share a,b across answer and play
add event listeners only once
Here is somewhat refactored version for a start:
(function () {
let count = 0;
let a = 0, b= 0;
function play() {
if (count < 10){
// setTimeout(loss, 30000);
count += 1;
document.getElementById("user-answer").value = "";
a = Math.floor(Math.random() * 20) + 1;
b = Math.floor(Math.random() * 20) + 1;
var question = document.getElementById("question");
question.textContent = a + " + " + b;
} else {
document.getElementById("win").textContent = "You won!";
}
}
function answer(){
var result = a + b;
var userAnswer = document.getElementById("user-answer").value;
if(userAnswer == result){
//sound.play();
//clearTimeout();
play();
}
if(userAnswer != result) {
document.getElementById("user-answer").classList.add("wrong");
// document.getElementById("user-answer").disabled = true;
console.log(result);
console.log(userAnswer);
// setTimeout(remove, 1000);
}
}
window.addEventListener("keypress", function(event){
if (event.key == "Enter"){
answer();
}
})
document.getElementById("send-answer").addEventListener("click", answer);
play();
})()
<div>
<div id="question">
</div>
<input type="text" id="user-answer"/>
<input type="button" id="send-answer" value="Send answer"></input>
<div id="win">
</div>
</div>

How to break a for-loop with a nested setTimeout() function in javascript?

The Situation
I have a function which is called on button click and which should draw an animated chart, updating the chart every 2 seconds. When the user clicks that button again, while the animation is still running, the animation should stop.
My current solution
Right now I have the following script which stops the animation visually, but the underlying for-loop continues until the end in the background:
var animRun = false;
$("#animateButton").on("click", function() {
if (animRun === false) {
redraw(data.slice(0,30))
//some CSS...
} else {
//Some CSS...
animRun = false;
}
});
function redraw(data) {
animRun = true;
for (var i=0; i<data.length;i++){
(function(i){
setTimeout(function(){
if(animRun === true) {
//draw the chart
return draw(data[i])
}
},2000*(i))
if (i === data.length -1) {
//reset animRun
if(animRun === true) {
//Some CSS things
//...
animRun = false;
}
}
})(i);
}
}
Question
What would be the correct way of stopping the for-loop when the user clicks the button again while the animation is still executing?
Did you try clearTimeout. Store your draw timeout to an array, and stop it when you done
var animRun = false;
var drawArr = [];
$("#animateButton").on("click", function() {
if (animRun === false) {
redraw(data.slice(0,30))
//some CSS...
} else {
//Some CSS...
animRun = false;
drawArr.forEach(d=>clearTimeout(d));
}
});
function redraw(data) {
animRun = true;
for (var i=0; i<data.length;i++){
(function(i){
drawArr[i] = setTimeout(function(){
if(animRun === true) {
//draw the chart
return draw(data[i])
}
},2000*(i))
if (i === data.length -1) {
//reset animRun
if(animRun === true) {
//Some CSS things
//...
animRun = false;
}
}
})(i);
}
}

JS How make that function could not be start earlier than 10 seconds after the previous launch?

function testworking(n){
if(n == 1)
testuser();
else
testconfig();
}
setInterval(function(){testworking(n)}, 1000);
How do I make that function testuser(); could not start earlier than 10 seconds after the previous launch?
P.S.:
an approximate algorithm:
if(n == 1){
if (first run function `testuser()` ||
time after previous run `testuser();` == 10 seound){
testuser();
}
}
Set a flag using a timer:
var is_waiting = false;
function testuser() {
if (!is_waiting) {
//do your stuff here
} else {
alert('You must wait ten seconds before doing this again');
}
is_waiting = true;
setTimeout(function() {is_waiting = false}, 10000);
}
You can do it like this
var i = 0;
function testworking(i){
if(i < 10) {
console.log(i);
} else {
console.log('Here is 10 second');
}
}
setInterval(function(){
i = (i == 10) ? 0 : i;
i++;
testworking(i);
}, 1000);
It's not entirely clear what you're looking for, but here's something that might give you an idea.
var n = 1;
var testUserInterval;
function testworking(n) {
if (n == 1)
testuser();
else
testconfig();
}
function testuser() {
var cnt = 0;
if (testUserInterval == null) {
testUserInterval = setInterval(function() {
document.getElementById("testusercnt").innerHTML = cnt;
cnt += 1;
if (cnt == 10) {
clearInterval(testUserInterval);
testUserInterval = null;
//DO SOMETHING ???
testuser();
}
}, 1000);
}
}
function testconfig() {
document.getElementById("testconfig").innerHTML = n;
}
setInterval(function() {
testworking(n++)
}, 1000);
testuser cnt:<span id="testusercnt"> </span>
<br/>testconfig n: <span id="testconfig"> </span>

Javascript - Check if key was pressed twice within 5 secs

I want to check if Enter key was pressed twice within 5 secs and perform some action.
How can I check if the key was pressed once or twice within a given time and perform different actions.
Here is my code:
<h1 id="log">0</h1>
<br/>
<span id="enteredTime">0</span>
<script>
$(document).keypress(function(e) {
if(e.which == 13){
var element = $("#log");
var timeDifference = 0;
//Log the timestamp after pressing Enter
$("#enteredTime").text(new Date().getTime());
//Check if enter was pressed earlier
if ($("#enteredTime").text() !== "0") {
var now = new Date().getTime();
var previous = $("#enteredTime").text();
difference = now - previous;
}
//Check if enter was pressed only once within 5 secs or more
if(){
$("#log").text("Once");
$("#enteredTime").text("0");
//Check if enter was pressed twice in 5 secs
}else{
$("#log").text("Twice in less than 5 secs");
$("#enteredTime").text("0");
}
}
});
</script>
http://jsfiddle.net/Rjr4g/
Thanks!
something like
var start=0;
$(document).keyup(function(e) {
if(e.keyCode == 13) {
elapsed = new Date().getTime();
if(elapsed-start<=5000){
//do something;
}
else{
//do something else;
}
start=elapsed;
}
});
Try a timer based solution like
var flag = false,
timer;
$(document).keypress(function (e) {
var element = $("#log");
var timeDifference = 0;
if (e.which == 13) {
if (flag) {
console.log('second');
clearTimeout(timer);
flag = false;
} else {
console.log('first');
flag = true;
timer = setTimeout(function () {
flag = false;
console.log('timeout')
}, 5000);
}
//Log the timestamp after pressing Enter
$("#enteredTime").text(new Date().getTime());
if ($("#enteredTime").text() !== "0") {
var now = new Date().getTime();
var previous = $("#enteredTime").text();
difference = now - previous;
}
}
});
Demo: Fiddle
Bacon.js seems like a good tool to express this.
$(document).asEventStream('keypress')
.filter(function (x) {
return x.keyCode == 13;
})
.map(function () {
return new Date().getTime();
})
.slidingWindow(2, 1)
.map(function (x) {
return (x.length == 1 || x[1] - x[0] > 5000) ? 1 : 2;
})
.onValue(function (x) {
$("#log").text(x == 1 ? "Once" : "Twice in less than 5 secs");
});
(fiddle)
here is my solutions, please check it if match your idea :)
(function($){
var element = $("#log");
var timeDifference = 0;
var count = 0;
$(document).keypress(function(e) {
if(e.which === 13){
//do what you want when enterpress 1st time
/*blah blah */
//after done 1st click
count++;
if(count === 2) {
//do what you want when enterpress 2nd time in 5 seconds
/* blah blah */
//after done
clearTimeout(watcher);
count = 0;
return;
}
//setTimeout to reset count if more than 5 seconds.
var watcher = setTimeout( function() {
count = 0;
},5000);
}
});
}(jQuery)
Check your Updated Fiddle
var count = 0;
$(document).keypress(function(e) {
var element = $("#log");
var timeDifference = 0;
if(e.which == 13){
count++;
console.log('enter pressed'+count);
if(count == 1){
startTimer();
}
else{
checkCount();
}
//Log the timestamp after pressing Enter
$("#enteredTime").text(new Date().getTime());
if ($("#enteredTime").text() !== "0") {
var now = new Date().getTime();
var previous = $("#enteredTime").text();
difference = now - previous;
}
}
});
function startTimer(){
setTimeout(checkCount,5000);
}
function checkCount(){
if(count == 1){
$("#log").text("Once");
$("#enteredTime").text("0");
//Check if enter was pressed twice in 5 secs
}else{
$("#log").text("Twice in less than 5 secs");
$("#enteredTime").text("0");
}
}
startTimer() starts counting on first enter press. And checkCount() contains your condition after 5secs.
setTimeout() lets you attach an event which occurs after a specific timespan.

JavaScript to delay execution of functions after page is loaded

I wrote this javascript to make an animation. It is working fine in the home page. I wrote a alert message in the last.
If I go other then home page, this alert message has to come, but I am getting alert message, if I remove the function, alert message working on all pages, any thing wrong in my code?
window.onload = function(){
var yellows = document.getElementById('magazine-brief').getElementsByTagName('h2');
var signUp = document.getElementById('signup-link');
if (yellows != 'undefined' && signUp != undefined){
function animeYellowBar(num){
setTimeout(function(){
yellows[num].style.left = "0";
if(num == yellows.length-1){
setTimeout(function(){
signUp.style.webkitTransform = "scale(1)";
},num*250);
}
}, num * 500);
}
for (var i = 0; i < yellows.length; i++){
animeYellowBar(i);
}
}
alert('hi');
}
DEMO: http://jsbin.com/enaqu5/2
var yellows,signUp;
window.onload = function() {
yellows = document.getElementById('magazine-brief').getElementsByTagName('h2');
signUp = document.getElementById('signup-link');
if (yellows !== undefined && signUp !== undefined) {
for (var i = 0; i < yellows.length; i++) {
animeYellowBar(i);
}
}
alert('hi')
}
function animeYellowBar(num) {
setTimeout(function() {
yellows[num].style.left = "0";
if (num == yellows.length - 1) {
setTimeout(function() {
signUp.style.webkitTransform = "scale(1)";
},
num * 250);
}
},
num * 500);
}
DEMO 2: http://jsbin.com/utixi4 (just for sake)
$(function() {
$("#magazine-brief h2").each(function(i,item) {
$(this).delay(i+'00').animate({'marginLeft': 0 }, 500 ,function(){
if ( i === ( $('#magazine-brief h2').length - 1 ) )
$('#signup-link')[0].style.webkitTransform = "rotate(-2deg)";
});
});
});
For starters you are not clearing your SetTimeout and what are you truly after here? You have 2 anonymous methods that one triggers after half a second and the other triggers a quarter of a second later.
So this is just 2 delayed function calls with horribly broken syntax.
Edited Two possibilities, one fixes your current code... the latter shows you how to do it using JQuery which I would recomend:
var yellows, signUp;
window.onload = function(){
yellows = document.getElementById('magazine-brief');
if(yellows != null){
yellows = yellows.getElementsByTagName('h2');
}else{
yellows = null;
}
signUp = document.getElementById('signup-link');
if (yellows != null && signUp != null && yellows.length > 0)
{
for(var i = 0; i < yellows.length; i++)
{
animeYellowBar(i);
}
}
alert('hi');
}
function animeYellowBar(num)
{
setTimeout(function(){
yellows[num].style.left = "0";
if(num == yellows.length-1){
setTimeout(function(){
signUp.style.webkitTransform = "scale(1)";
},num*250);
}
}, num * 500);
}
The below approach is a SUMMARY of how to use JQuery, if you want to use JQuery I'll actually test it out:
//Or using JQuery
//Onload equivelent
$(function(){
var iterCount = 0,
maxIter = $("#magazine-brief").filter("h2").length;
$("#magazine-brief").filter("h2").each(function(){
setTimeout(function(){
$(this).css({left: 0});
if(iterCount == (maxIter-1))
{
setTimeout(function(){
signUp.style.webkitTransform = "scale(1)";
},iterCount*250);
}
}, iterCount++ * num );
});
});

Categories