Why are old variables reused when using event handler functions with Javascript? - 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();
}

Related

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 is clearTimeout not working for this code?

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);
}

stopping dynamically generated setInterval

I am generating multiple charts each with their own setInterval to refresh the data. I have it set to clearInterval when the dynamically generated container is removed - but if I reload and it has the same id the old setInterval continues to run. Is there a way to set a dynamically named setInterval that can be stopped when the replacement is generated?
Right now I'm using:
function generateChart(data, location){
var chart = new Highcharts.Chart({
// blah blah blah
}, function(chart){
setInterval(function(){
if($('#'+location).length){
// I'm doing stuff every minute
}else{
clearInterval();
}
},60000);
});
}
What happens is, the location is a randomly generated string that becomes the element ID for the container for the Highchart and if they user saves the chart it becomes the unique identifier. If the user updates the chart that's saved and reloads the chart, the old one gets .removed() and the new one generated in its place. Now the new one has the same element ID as the old one and since the old interval finds the container it wants it attempts to continue updating - which is can't since its chart went poof.
is there a way to set a dynamic variable I can use for setInterval so that I can clearInterval on it?
var blob+location = setInterval(function(){ ...
and then
clearInterval(blob+location);
You can just use an object:
var myObj = {};
var location = "somevalue";
myObj[location] = setInterval(...
clearInterval(myObj[location]);
ok - since I couldn't seem to wrap my head around some of your answers I decided to go low tech.
function genToken(){
var num = Math.floor(Math.random() * 10000);
var token = 't-' + num;
return token;
}
function genLocation(){
var chartToken = genToken();
var newChart = '<div id="'+location+'" data-token="'+chartToken+'"></div>';
$('#chartHome').append(newChart);
}
// inside my chart function
var token = $('#'+location).data('token');
setInterval(function(){
if( $('[data-token="'+token+'"]').length ){
// still there - keep going
}else{
// all gone - time to stop
clearInterval();
}
},60000);
now when I do:
$('#'+location).remove();
the token also vanishes and won't be the same if I generate a new chart with the same location id.
Stop using setInterval, use setTimeout instead (How do I execute a piece of code no more than every X minutes?):
function generateChart(data, location) {
var element = $('#'+location);
var chart = new Highcharts.Chart({
// blah blah blah
}, foo);
var foo = function() {
if(element){
// I'm doing stuff every minute
setTimeout(foo, 6000);
}
};
}
To stop it, just avoid the setTimeout or make element = null.
Maybe my code is a little bit wrong (I'm getting sleep right now), but the thing is to use setTimeout and closures.
If inside foo, something longs more than 6 seconds you will be in troubles since setTimeinterval will call it again, please watch http://www.youtube.com/watch?feature=player_detailpage&v=i_qE1iAmjFg#t=462s , so, this way you ensure that this will run 6 seconds after the last completed stuff.
I'll let this example here to posterity:
http://jsfiddle.net/coma/vECyv/2/
var closure = function(id) {
var n = 0;
var go = true;
$('#' + id).one('click', function(event) {
go = false;
});
var foo = function() {
if(go) {
console.log(id, n++);
setTimeout(foo, 1000);
}
};
foo();
};
closure('a');
closure('b');
Not sure if anyone is still looking for this solution but I ran into this problem and chose the following approach.
For anyone dynamically creating private/anonymous intervals that need to be stopped based on some event. You can simply save the interval in a variable, then transfer that variable into a data property in your html element.
// Outer scope
let pos = 1
let interval = setInterval(() => {
if (pos < 700) {
pos++;
}
htmlEl.style.top = pos + "px";
});
htmlEl.setAttribute("data-interval", interval)
This will save the numeric identifier of your interval, providing that html element is somewhere in your DOM.
Then, later you can simply extract this data attribute and use it to cancel an interval.
let intervalId = document.querySelector("#someElement").dataset.interval;
clearInterval(intervalId);

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