Why is clearTimeout not working for this code? - javascript

I call the following code on mouse click:
clearTimeouts();
var inMotion = true, x = 0;
var shuffleTimer = setTimeout(function(){inMotion = false}, 3500);
var shuffleStart = setTimeout(oneShuffle, x);
function oneShuffle(){
x+=5;
if(inMotion === true){
console.log('Shuffling again');
//shuffle again
shuffleStart = setTimeout(oneShuffle, x);
} else {
//increment spins
spins++;
//reset spins if loadOrder been exhausted
spins === loadOrder.length ? spins = 0 : 0;
console.log(spins);
}
}
function clearTimeouts(){
console.log('Clearing timeouts')
clearTimeout(shuffleTimer);
clearTimeout(shuffleStart);
}
What should be happening is if I click the elment while inMotion is true the two timeouts should reset and my spins counter should not increase for the previous click. However, what happens instead is that spins gets incremented for the previous timeouts anyway. Why?

What timers should reset? If the code you posted is in a click handler, then each click produces new timers.
The lines:
var shuffleTimer = setTimeout(function(){inMotion = false}, 3500);
var shuffleStart = setTimeout(oneShuffle, x);
create new timers each time, so the first line (clearTimeouts();) makes no sense since the timers don't exist until next two lines.
You should put both timers outside the scope of a click handler, so all click invocations would reference same timers. Also all state (inMotin, spins, etc.) should exist outside the function scope, otherwise each click produces new, unrelated variables.

You should put clearTimeouts() inside your if, like this:
if(inMotion === true){
clearTimeouts();
console.log('Shuffling again');
//shuffle again
shuffleStart = setTimeout(oneShuffle, x);
}

Related

Why are old variables reused when using event handler functions with Javascript?

I'm trying to create a Simon game (project from a Udemy course) where you have to memorize a pattern sequence and click the right buttons. At each level, the pattern sequence increases by one. You have to repeat the entire sequence each time. If you lose, you have to press a key to restart the game. Here is an example:
https://londonappbrewery.github.io/Simon-Game/
However, when my game is restarted with my current code, everything falls apart. I don't know if old variables from a previous game continue into the new one, or maybe my code is just written really poorly.
I've implemented the entire game within a keypress event handler. Doing this initiates all the functions and variables of the game. The sequence is stored within an array, and each subsequent click on a box compares its colour value against the array. If it doesn't match - its game over. If it does match, it will either wait for you to finish the sequence correctly, or go to the next level.
Can anyone give me pointers on what I might be doing wrong? And whether it's at all possible to make this work the way I have it set up?
$(document).on("keypress", function(){
let counter = 0;
let level = 1;
let colours = ["green","red","yellow","blue"];
let nodeList = [];
function nextNode(){
randomNumberGen = Math.floor(Math.random()*4);
currentNode = colours[randomNumberGen];
nodeList.push(currentNode);
$("."+currentNode).fadeOut(200).fadeIn(200);
}
$("h1").text("Level "+level);
setTimeout(function() {
nextNode();
},500);
$(document).on("click", ".box", function(){
selectedNode = $(this).attr("id");
$("#"+selectedNode).addClass("pressed");
setTimeout(function(){
$("#"+selectedNode).removeClass("pressed");
},100);
if (nodeList[counter] != selectedNode){
gameSounds("wrong");
$("body").addClass("game-over");
setTimeout(function(){
$("body").removeClass("game-over");
},100);
$("h1").text("Game Over, Press Any Key to Restart");
}
else{
gameSounds(selectedNode);
counter++;
if (counter>= nodeList.length){
level++;
setTimeout(function(){
$("h1").text("Level "+(level));
}, 1000);
counter = 0;
setTimeout(function() {
nextNode();
},1000);
}
}
});
});
function gameSounds(key){
var soundPlay = new Audio("sounds/"+key+".mp3");
soundPlay.play();
}

How to trigger a var after x actions

I have the following code. I want to trigger the action in function activityDetected(eventName) only after 100 click. How to do this ?
I know I have to put let a = 1; ++a but not sure where...
https://pastebin.com/SMsJsikE
const intervalTimeout = 2000;
//here is where code should be added. let a = 1; ++a...
function activityDetected(eventName) {
console.log(`Activity detected with the event name: ${eventName}!`);
clearInterval(activityTimeout);
activityTimeout = setInterval(recordNoActivity, intervalTimeout);
}
document.addEventListener('click', _ => {
activityDetected('click');
});
You need to declare a counter outside the function and up it by 1 when the eventName is 'click'. After that check for a % 100 and put whatever action you want to call every 100 clicks in there.
Look at the code example:
// For ease, change this to a smaller value to detect more often, higher to detect less often!
const intervalTimeout = 2000;
let a = 0;
// Here's our interval, setting up the initial capture of no activity
let activityTimeout = setInterval(recordNoActivity, intervalTimeout);
// A single function to handle the events we're listening to.
// clears the interval and restarts it, also tells us which event has cleared the interval!
//here is where code should be added. let a = 1; ++a...
function activityDetected(eventName) {
if(eventName == 'click'){
a++;
if(a%100 == 0){
// Trigger whatever you want to trigger after every 100 clicks
}
}
console.log(`Activity detected with the event name: ${eventName}!`);
clearInterval(activityTimeout);
activityTimeout = setInterval(recordNoActivity, intervalTimeout);
}
// Set listening events
document.addEventListener('keydown', _ => {
activityDetected('keydown');
});
document.addEventListener('click', _ => {
activityDetected('click');
});
As correctly pointed by others on this thread the variable a should be declared and defined outside the function but the reason why this approach would work is because of Closure
So when the function is getting invoked an execution context is created which contains
scopeChain - it contains variableObject + all parent execution context's variableObject
variableObject - it contains function arguments / parameters, inner variable and function declarations
this - the this context.
Thus the variable a values would be saved before invoking the function and the variable will keep incrementing.

Unity javascript infinite spawning

So I'm trying to set up a system in a project where these spawn points will spawn targets that move towards the player and have to be destroyed before reaching a certain point, or its game over. Everything seems to be working fine except for one issue. The spawners don't stop spawning. They're supposed to do waves, spawning more enemies after each wave has been finished. I'm totally lost as to where the error might be.
Small note, originally I had the spawn count be 3 times the enemyspawncount, and spawnCount would count down to 0, then jump to 2 and remain there.
Spawner script:
var targetPrefab:Transform;
var spawnCount = deathcounter.enemySpawnCount;
function Start()
{
StartCoroutine("CoStart");
}
function CoStart() : IEnumerator
{
while (true)
yield CoUpdate();
}
function CoUpdate(){
spawnCount = deathcounter.enemySpawnCount;
while(spawnCount > 0)
{
var target= Instantiate(targetPrefab, transform.position, transform.rotation);
target.rigidbody.AddForce(Vector3.right * (deathcounter.enemySpawnCount *0.5 * 100));
spawnCount = spawnCount - 1;
Debug.Log("Spawn" + spawnCount);
yield WaitForSeconds (5);
}
deathcounter.timeToSpawn = false;
}
Target script:
var spawnCount = deathcounter.enemyDeathCount;
function OnTriggerEnter() {
Destroy (gameObject);
deathcounter.enemyDeathCount = deathcounter.enemyDeathCount + 1;
}
Death Counter script:
static var enemyDeathCount = 0;
static var enemySpawnCount = 1;
static var timeToSpawn : boolean = true;
function Update () {
if(enemyDeathCount % 3 == 0 && enemyDeathCount != 0){
timeToSpawn = true;
enemySpawnCount = enemySpawnCount + 1;
}
}
The issue could be in function CoUpdate(). The value of deathcounter.enemySpawnCount never gets reduced in that function. So if CoUpdate() gets called again, deathcounter.enemySpawnCount will still have the same value, and more enemy prefabs will get instantiated.
If that is the issue, and I'm not just misreading your code, you can solve that easily by setting deathcounter.enemySpawnCount after you set spawnCount:
spawnCount = spawnCount - 1;
deathcounter.enemySpawnCount = spawnCount;
Debug.Log("Spawn" + spawnCount);
Debug.Log("Spawn (double-check) " + deathcounter.enemySpawnCount);
With much more mature eyes, I can look back and correct myself.
First I have to flip the order of these commands so they both trigger.
function OnTriggerEnter() {
deathcounter.enemyDeathCount = deathcounter.enemyDeathCount + 1;
Destroy (gameObject);
}
Second I have to redo how spawnCount is handled. It should be removed from the 'target' script, and given a set initial value, not set to another variable value. It should only be changed in the while loop with each iteration, and in the death counter script, inside the if statement, so it is set to be equal to the new enemySpawnCount value only when that if statement is true.

Understanding JavaScript setTimeout and setInterval

I need a bit of help understanding and learning how to control these functions to do what I intend for them to do
So basically I'm coming from a Java background and diving into JavaScript with a "Pong game" project. I have managed to get the game running with setInteval calling my main game loop every 20ms, so that's all ok. However I'm trying to implement a "countdown-to-begin-round" type of feature that basically makes a hidden div visible between rounds, sets it's innerHTML = "3" // then "2" then "1" then "GO!".
I initially attempted to do this by putting setTimeout in a 4-iteration for-loop (3,2,1,go) but always only displayed the last iteration. I tried tinkering for a bit but I keep coming back to the feeling that I'm missing a fundamental concept about how the control flows.
I'll post the relevant code from my program, and my question would be basically how is it that I'm writing my code wrong, and what do I need to know about setTimeout and setInterval to be able to fix it up to execute the way I intend it to. I'm interested in learning how to understand and master these calls, so although code examples would be awesome to help me understand and are obviously not unwelcome, but I just want to make it clear that I'm NOT looking for you to just "fix my code". Also, please no jQuery.
The whole program would be a big wall of code, so I'll try to keep it trimmed and relevant:
//this function is called from the html via onclick="initGame();"
function initGame(){
usrScore = 0;
compScore = 0;
isInPlay = true;
//in code not shown here, these objects all have tracking variables
//(xPos, yPos, upperBound, etc) to update the CSS
board = new Board("board");
ball = new Ball("ball");
lPaddle = new LPaddle("lPaddle");
rPaddle = new RPaddle("rPaddle");
renderRate = setInterval(function(){play();}, 20);
}
.
function initNewRound(){
/*
* a bunch of code to reset the pieces and their tracking variables(xPos, etc)
*/
//make my hidden div pop into visibility to display countdown (in center of board)
count = document.getElementById("countdown");
count.style.visibility = "visible";
//*****!!!! Here's my issue !!!!*****//
//somehow i ends up as -1 and that's what is displayed on screen
//nothing else gets displayed except -1
for(var i = 3; i >= 0; i--){
setInterval(function(){transition(i);}, 1000);
}
}
.
//takes initNewRound() for-loop var i and is intended to display 3, 2, 1, GO!
function transition(i){
count.innerHTML = (i === 0) ? "Go" : i;
}
.
//and lastly my main game loop "play()" just for context
function play(){
if(usrScore < 5 && compScore < 5){
isInPlay = true;
checkCollision();
moveBall();
moveRPaddle();
if(goalScored()){
isInPlay = false;
initNewRound();
}
}
}
Thanks a bunch for your advise, I'm pretty new to JavaScript so I really appreciate it.
Expanding on cookie monster's comment, when you use setInterval in a loop, you are queueing up method executions that will run after the base code flow has completed. Rather than queue up multiple setInterval executions, you can queue up a single execution and use a variable closure or global counter to track the current count. In the example below, I used a global variable:
var i = 3 // global counter;
var counterInterval = null; // this will be the id of the interval so we can stop it
function initNewRound() {
// do reset stuff
counterInterval = setInterval(function () { transition() }, 1000); // set interval returns a ID number
}
// we don't need to worry about passing i, because it is global
function transition() {
if (i > 0) {
count.innerHTML = i;
}
else if (i === 0) {
count.innerHTML = "Go!";
}
else {
i = 4; // set it to 4, so we can do i-- as one line
clearInterval(counterInterval); // this stops execution of the interval; we have to specify the id, so you don't kill the main game loop
}
i--;
}
Here is a Fiddle Demo
The problem is in this code:
for(var i = 3; i >= 0; i--){
setInterval(function(){transition(i);}, 1000);
}
When the code runs, it creates a new function 3 times, once for each loop, and then passes that function to setInterval. Each of these new functions refers to the variable i.
When the first new function runs it first looks for a local variable (in it's own scope) called i. When it does not find it, it looks in the enclosing scope, and finds i has the value -1.
In Javascript, variables are lexically scoped; an inner function may access the variables defined in the scope enclosing it. This concept is also known as "closure". This is probably the most confusing aspect of the language to learn, but is incredibly powerful once you understand it.
There is no need to resort to global variables, as you can keep i safely inside the enclosing scope:
function initNewRound(){
var i = 3;
var count = document.getElementById("countdown");
count.style.visibility = "visible";
var interval = setInterval(function(){
//this function can see variables declared by the function that created it
count.innerHTML = i || "Go"; //another good trick
i-=1;
i || clearInterval(interval); //stop the interval when i is 0
},1000);
}
Each call to this function will create a new i, count and interval.

why doesn't JavaScript's setTimeout slow down keydown rate?

Why doesn't the following keydown event slow down by 3000 mil's, when I am continuously pressing the keydown event (letter k)? If I keep my finger down, the count rapidly adds up as through there is no setTimeout on mcount. why is that? There should be a delay between each count but I can't get it to work...
var mcount = 0;
function playershoot() {
if(!game.playerHit){
$(document).keydown(function(e){
switch(e.keyCode){
case 75:
clearTimeout();
setTimeout(console.log(mcount++), 3000);
break;
}
});
}
}
playershoot();
Any advice will be appreciated!
Thanks
1.: setTimeout() returns a timeoutId which can be cleared with clearTimeout(timeoutId). You're not doing that... so, after your 3 second delay, all those timeouts are called back-to-back.
2.: your console.log is executed immediately because you didn't wrap it in a function like so:
setTimeout(function() { console.log(mcount++) }, 3000);
setTimeout does not cause a delay, it starts a timer that fires an event after the specified amount of time.
You cannot "sleep" in Javascript, you need to refactor your code so it can work with events. For your code, it looks like you will need to set a flag at first keypress. Then return, and only allow new keypresses (ie. only respond to), when the flag is cleared. The flag can then be cleared automatically after a time with setTimeout.
To go with what #Norguard said, here's an implementation: http://jsfiddle.net/apu3P/
this.fire = function(){
var cFire = new Date();
if ((cFire - lastFire) / 1000 > 1/me.fireRate){
// code to fire the projectile
lastFire = cFire;
}
};
I have fireRate set up as an integer indicating how many times per second the player can fire.
In the demo, I set up 3 players each with different fire rates. If you hold the spacebar down, you can see this in action.
While everyone here is right, what they're missing is that you need to put a delay on the firing, not on the event being called...
Inside of your keydown event, set a timestamp, have a previous-time and a current-time for the event.
Inside of the function, have a time_limit.
So when you press the key (or it fires repeatedly), check for:
current_time - last_fired >= rate_limit;
If the current time is more than 3000ms since the last shot, then set the last_fired timestamp to the current time, and fire your weapon.
EDIT
Consider this trivial example:
var Keyboard = {};
var player = (function () {
var gun = {
charging : false,
lastFired : 0,
rateLimit : 3000
},
controls = { shoot : 75 },
isHit = false,
public_interface;
function shoot () {
var currentTime = Date.now();
if (gun.rateLimit > currentTime - gun.lastFired) { return; }
/* make bullet, et cetera */
gun.lastFired = currentTime;
}
function update () {
if (Keyboard[controls.shoot] || gun.charging) { this.shoot(); }
// if key was released before this update, then the key is gone...
// but if the gun was charging, that means that it's ready to be fired
// do other updates
}
function draw (ctx) { /* draw player */ }
public_interface = {
shoot : shoot,
damage : function (amt) { isHurt = true; /* rest of your logic */ }
draw : draw,
update : update
};
return public_interface;
}());
document.addEventListener("keydown", function (e) {
// if key is already down, exit
if (!!Keyboard[e.keyCode]) { return; }
// else, set the key to the time the key was pressed
// (think of "charging-up" guns, based on how long you've held the button down)
Keyboard[e.keyCode] = e.timeStamp;
});
document.addEventListener("keyup", function (e) { delete Keyboard[e.keyCode]; });
Inside of your gameloop, you're now going to do things a little differently:
Your player is going to update itself.
Inside of that update, it's asking the Keyboard if it's got the shoot key pressed down.
If it is, then it will call the shoot method.
This still isn't 100% correct, as Player shouldn't care about or know about Keyboard.
It should be handled through a service of some kind, rather than asking for window.Keyboard.
Regardless...
Your controls are now wrapped inside of the player -- so you can define what those controls are, rather than asking by keyCode all over the place.
Your events are now doing what they should: setting the key and going away.
In your current iteration, every time the browser fires keydown, which might be 300x/sec, if it wanted to, that event ALSO has to call all of your player logic... 300x/sec...
In larger games, you could then take this a step further, and make components out of Controls and Health, each having all of the properties and all of the methods that they need to do their own job, and nothing else.
Breaking the code up this way would also make it dirt-simple to have different guns.
Imagine an Inventory component:
The inventory contains different guns.
Each gun has its own rateLimit, has its own lastFired, has its own bulletCount, does its own damage, and fires its own bulletType.
So then you'd call player.shoot();, and inside, it would call inventory.equipped.shoot();.
That inner function would take care of all of the logic for firing the equipped gun (because you'd inventory.add(Gun); to your guns, and inventory.equip(id); the gun you want.
You have to pass returned value of setTimeout to clearTimeout . to cancel it.
var mcount = 0,timeout;
function playershoot() {
if(!game.playerHit){
$(document).keydown(function(e){
switch(e.keyCode){
case 75:
clearTimeout(timeout );
timeout = setTimeout(function(){
console.log(mcount++);
}, 3000);
break;
}
});
}
}
playershoot();

Categories

Resources