Reputation: 854
I'm trying to make a p then have a delay, then make another p my battle function works until I try to add setTimeout. I've tried using setTimeout just about everywhere to no effect. Sometimes it creates an infinite loop, and in some places it can make a delay before it creates every p at the same time.
The effect should be similar to this Click Attack Button
I tried using the same method as this codepen's creator but I don't understand how his code works.
function timeout() {
setTimeout(function() {
}, 500);
}
var battle = function() {
while (monsterHP > 0) {
var playerDam = Math.floor(Math.random() * ((playerAtk - monsterAtk) + 2));
var newP = $('#battle').append("<p>You have hit the monster for " + playerDam + " damage. The monster has " + (monsterHP - playerDam) + "HP left</p>");
monsterHP -= playerDam;
timeout();
if (monsterHP <= 0) {
$('#battle').append("<p>You have defeated the monster</p>");
}
}
}
$('#battleButton').click(function() {
battle();
});
Upvotes: 0
Views: 91
Reputation: 28870
The accepted solution starts a number of setTimeout()
calls in parallel. In your case it will tend to be less than a dozen concurrent timers - although there's no guarantee how many timers it will start because of the random numbers.
In most cases it's better to have each timeout start the next one, instead of firing them up all at once like this. For example, imagine that you need to report periodic progress on a much longer running process than this, or a process that keeps running until the program exits? You would need a very large number of concurrent timeouts.
Here's a version of your code with minimal changes from your original and uses only a single setTimeout()
at any moment, to avoid this problem.
The way it works is simple: it doesn't use a loop at all, and instead battle()
calls timeout()
if it needs another run. timeout()
in turn passes battle
as the parameter to setTimeout()
, so battle()
will be called when the time elapses.
I marked the changes with //**
for you:
var playerAtk = 5;
var playerDef = 5;
var playerHP = 10;
var monsterAtk = 4;
var monsterDef = 4;
var monsterHP = 8;
function timeout() {
//** Your original setTimeout call didn't do anything at all.
//** setTimeout isn't a "delay and then return when the delay is done".
//** Instead, it returns immediately, and the function you pass into it
//** will be run after the time elapses.
//** Here we pass battle, so that function is called after the timeout.
setTimeout(battle, 500);
}
var battle = function() {
//** This is now just an if instead of a while. It doesn't loop at all here.
//** Instead, when we want to run another iteration, the setTimeout() call
//** takes care of that by calling battle again.
//** In fact, if monsterHP always starts positive, you don't need this
//** if statement at all.
if (monsterHP > 0) {
var playerDam = Math.floor(Math.random() * ((playerAtk - monsterAtk) + 2));
var newP = $('#battle').append("<p>You have hit the monster for " + playerDam + " damage. The monster has " + (monsterHP - playerDam) + "HP left</p>");
monsterHP -= playerDam;
if (monsterHP <= 0) {
$('#battle').append("<p>You have defeated the monster</p>");
} else {
//** The final change: if monsterHP is still > 0, we call timeout()
//** here to start the next battle call after the interval elapses.
timeout();
}
}
}
$('#battleButton').click(function() {
battle();
});
Upvotes: 0
Reputation: 3487
You could alter your battle function a bit and instead of using a while-loop, simply let the setTimeout()
call loop it for you.
The following is an example of my modifications to your linked fiddle:
var playerAtk = 5;
var playerDef = 5;
var playerHP = 10;
var monsterAtk = 4;
var monsterDef = 4;
var monsterHP = 8;
var battle = function() {
if (monsterHP > 0)
{
var playerDam = Math.floor(Math.random() * ((playerAtk - monsterAtk) + 2));
var newP = $('#battle').append("<p>You have hit the monster for " + playerDam + " damage. The monster has " + (monsterHP - playerDam) + "HP left</p>");
monsterHP -= playerDam;
setTimeout(function() {
battle();
}, 1000);
}
else {
$('#battle').append("<p>You have defeated the monster</p>");
}
}
$('#battleButton').click(function() {
battle();
});
You can see my JSFiddle here
Upvotes: 1
Reputation: 4346
The problem you are having is with your implementation of timeout
. As tymeJV mentioned in the comments, setTimeout
is async. Your code will not stop what it is doing to complete the timeout. Instead you have to pass a callback to be completed upon the time being elapsed (in your case appending the element).
Try this code snippet in your console to better illustrate this:
setTimeout(function() {
console.log('hello from callback');
}, 3000)
console.log('hello from outside');
As you can see the later code gets run first even though it is after the timeout.
Upvotes: 0
Reputation: 16726
i tried explaining in a comment, but it won't fit because there's a couple things going on here. it's easier just to fix it and talk about the new code:
var battle = function() {
var i=0;
while (monsterHP > 0) {
(function(hp){
var playerDam = Math.floor(Math.random() * ((playerAtk - monsterAtk) + 2));
monsterHP-= playerDam;
hp= monsterHP; // this might or might not be needed.
setTimeout(function(){
var newP = $('#battle').append("<p>You have hit the monster for " + playerDam + " damage. The monster has " + (hp - playerDam) + "HP left</p>");
if (hp<= 0)$('#battle').append("<p>You have defeated the monster</p>");
}, i++ * 500);
}(monsterHP));
}
}
here i've moved the computations to the top of the loop, then defered the visible action of the computations behind a setTimeout for 1/2 second.
you needed private copies of the vars altered by the loop, and you needed a timeout that could use those values.
Upvotes: 1