JS die roll simulation with 6 die images - javascript

in Js, I want to try to simulate a die roll by showing images of die 1 to 6, but when I try to display these in a for loop, it only displays image dice6. I tried putting in a nested for loop to slow down the outer loop but that didnt work. Does the page need to refresh after changing "src" attribute?
const dieImage = function (num) {
return "images/dice" + String(num).trim() + ".png";
};
function dieRoll(num) {
for (let i = 1; i < 7; i++) {
for (let z = 0; z < 44444; z++) {} // attempt to slow
if (num === 1) {
img1.setAttribute("src", dieImage(i));
} else {
img2.setAttribute("src", dieImage(i));
}
}
}

As mentioned in the comments you can use setTimeout. You can introduce a delay and give the browser a chance to redraw by using setTimeout, promise, and await, for example like this:
const DELAY = 300; // 300ms
async function dieRoll(num) {
for (let i = 1; i < 7; i++) {
if (num === 1) {
img1.setAttribute("src", dieImage(i));
} else {
img2.setAttribute("src", dieImage(i));
}
await new Promise((resolve) => setTimeout(() => resolve(), DELAY));
}
}
The loop execution will stop until the promise is resolved, but it will let the browser to continue to respond and redraw. When the promise is resolved after the timeout callback is run after the given DELAY milliseconds, the next iteration of the loop will take place.

What you are missing (roughly) is that the browser paints the screen when the JavaScript code has finished running. Even though you are setting the src attribute to a different image in a loop, the JavaScript code finishes only when the loop ends. The browser paints only once, i.e. the last image you set in the loop. This explanation may be oversimplified, but it gives you an idea.
The solution is to return from the JavaScript code after setting the src and repeating after a suitable delay, giving the user the opportunity to sense the change. setTimeout is probably good enough for your case; in other use cases where you want really smooth animation, there would be other solutions (e.g. requestAnimationFrame()). An untested implementation to demonstrate the intent:
function dieRoll(selectedNum) {
var counter = 8; // how many times to change the die
function repeat() {
if (counter === 1) {
// on the last iteration, set the image representing the selected number
img1.setAttribute("src", dieImage(selectedNum));
} else {
// else decrement the counter, set a random image and repeat after a timeout
counter--;
img1.setAttribute("src", dieImage(Math.floor(6 * Math.random()) + 1));
setTimeout(repeat, 300);
}
}
repeat();
}

Related

How do I perform a simple toggle operation in JavaScript with the help of setInterval()?

This is what my code looks like:
var fnInterval = setInterval(function() {
let b = true
if (b) {
console.log("hi")
} else {
console.log("bye")
}
b = !b
}, 1000);
clearTimeout(fnInterval, 10000)
I am a newbie to JavaScript and my aim here is to console log a message every 1 second for a total duration of 10 seconds, but during each interval I want my message to toggle its value between "hi" and "bye" . How can I do it? (as of now it displays the value for the default boolean and doesn't change later)
Move the flag variable out of the function:
let b = true;
const fnInterval = setInterval(function() {
if (b) {
console.log("hi");
} else {
console.log("bye");
}
b = !b
}, 1000);
To stop the timer after 10000 milliseconds, wrap the call to clearInterval in a setTimeout:
setTimeout(() => clearInterval(fnInterval), 10000);
Meanwhile, note that the return value of setInterval is not a function but a number, so it may be misleading to call it fnInterval.
First of all, declare let b = true outside of the callback function. It's re-initialized on each call otherwise.
Secondly, the 10000 in clearTimeout(fnInterval, 10000) isn't a valid parameter. clearTimeout(timeoutId) accepts only the first parameter and clears the timeout passed in immediately. You'd need a setTimeout to trigger this after 10 seconds, if that's your goal. But that causes a race condition between the two timeouts -- imprecision can mean you'll miss some of the logs or wind up with extra logs.
Using a counter is one solution, as other answers show, but usually when I'm using complex timing with setInterval that requires clearing it after some number of iterations, I refactor to a generic promisified sleep function based on setTimeout. This keeps the calling code much cleaner (no callbacks) and avoids messing with clearTimeout.
Instead of a boolean to flip a flag back and forth between two messages, a better solution is to use an array and modulus the current index by the messages array length. This makes it much easier to add more items to cycle through and the code is easier to understand since the state is implicit in the counter.
const sleep = ms => new Promise(res => setInterval(res, ms));
(async () => {
const messages = ["hi", "bye"];
for (let i = 0; i < 10; i++) {
console.log(messages[i%messages.length]);
await sleep(1000);
}
})();
setInterval() is stopped by clearInterval() not clearTimeout(). Details are commented in code below.
// Define a counter
let i = 0;
// Define interval function
const fnCount = setInterval(fnSwitch, 1000);
function fnSwitch() {
// Increment counter
i++;
// if counter / 2 is 0 log 'HI'
if (i % 2 === 0) {
console.log(i + ' HI');
// Otherwise log 'BYE'
} else {
console.log(i + ' BYE');
}
// If counter is 10 or greater run fnStop()
if (i >= 10) {
fnStop();
}
};
function fnStop() {
// Stop the interval function fnCount()
clearInterval(fnCount);
};

Slowing down 2 nested loops

I'm trying to build one of my first web apps that would visualize sorting algorithms. I'm currently working on a bubble sort algorithm but I'm having trouble slowing the bubble sort loops execution down.
I tried using setTimeout() function but apparently, it is not that easy to use it on nested loops.
The piece of code below works well but if I add setTimeout within the nested loop then it only does the first iteration of the outer while loop. It is understandable as an entire while loop is executed instantly and isn't really aware that someone has desynchronized its' body execution.
I tried putting the outer loop in a setTimeout function but I couldn't find the way to retrieve the actual time that it is supposed to be delayed by as I tried using j value to calculate it but it is only available within the for loop.
Any idea on how to slow down execution of both loops in a relevant way?
bubbleSort(){
var len = this.numberOfElements;
var sorted = false;
var i = 0;
while(!sorted){
sorted = true;
for(let j = 0 ; j < len - i - 1; j++){
setTimeout( () => {
if (this.valuesAndPillarsObject[j].value > this.valuesAndPillarsObject[j + 1].value) {
//swap graphical representation of the value
this.swapPillars(this.valuesAndPillarsObject[j].pillar, this.valuesAndPillarsObject[j+1].pillar);
// swap values
var temp = this.valuesAndPillarsObject[j].value;
this.valuesAndPillarsObject[j].value = this.valuesAndPillarsObject[j+1].value;
this.valuesAndPillarsObject[j + 1].value = temp;
sorted = false;
}
}, j*40);
}
i++;
}
return this.valuesAndPillarsObject;
}
You don't want to just schedule a bunch of timeouts in a loop. They'll all finish after just one delay. You want to have each timeout start the next one. To keep using a loop to control the flow, you'll need to make use of async and await.
Wrong way
for (let i = 0; i < 5; ++i) {
setTimeout(() => { console.log(`step ${i}`) }, 1000);
}
console.log('finished');
Right way
const delay = time_ms => new Promise(resolve => setTimeout(resolve, time_ms));
const main = async () => {
for (let i = 0; i < 5; ++i) {
await delay(1000);
console.log(`step ${i}`);
}
}
main().then(() => console.log('finished'));
The for loop will finish before the first setTimeout callback is called !
even if the timeout is 1ms or 0ms.
read some about the EventLoop to understand how things works in JavaScript.
UPDATE: the simplest solution is using delay inside the for loop.
just like how Wyck did.
but here i will keep the other solutions
You can use function and call it again to demonstrate a loop instead of normal for loop.
// change the code as your needs
const sleep = (t) => ({ then: (r) => setTimeout(r, t) })
const len =10
// change slowFor for your needs
const slowFor= async (i,j)=>{
// do what you want here
await sleep(5000)
if(j == len) return ;
console.log("after sleeping call next loop");
slowFor(i,j+1)
}
slowFor(0,0)
be aware of maximum call stack when running on big arrays.
or you can convert the bubbleSort function to a generator function so you will be able to stop it whenever and as much time you want.
change the code to fit your need
function* bubble() {
for(let i = 1 ; i<10 ; i++ ){
for(let j=0 ; j<i ; j++, yield){
console.log("hellow from for");
}
}
}
const genBubble = bubble()
genBubble.next()
setInterval(() => {
genBubble.next()
}, 5000);

Setting timeout inside loop with javascript

I'm making a puzzle solving function which uses an array of puzzle pieces in their current shuffled order. Each piece has an id which points to the correct position in the array. I set overlay colors on the pieces that are about to be swapped. I want for there to be a delay between the pieces being colored and then swapped.
function solvePuzzle() {
while (rezolvat == false) // while all pieces are not in correct position
for (var i = 0; i < _piese.length; i++) { // iterates through array of puzzle pieces
if (checkPiesa(i) == false) {
_piesaCurentaDrop = _piese[i];
_piesaCurenta = getPiesaDestinatie(_piesaCurentaDrop.id); // destination piece
_context.save();
_context.globalAlpha = .4;
_context.fillStyle = PUZZLE_HOVER_TINT;
_context.fillRect(_piesaCurentaDrop.xPos, _piesaCurentaDrop.yPos, _latimePiesa, _inaltimePiesa);
_context.fillStyle = PUZZLE_DESTINATION_HOVER_TINT;
_context.fillRect(_piesaCurenta.xPos, _piesaCurenta.yPos, _latimePiesa, _inaltimePiesa);
_context.restore();
// here I want to have some kind of 'sleep' for 2000 ms so you can see the pieces about to be swapped
dropPiece(); // function for swapping puzzle pieces
}
}
}
You can use javascript's setTimeout() functions which will execute the function after specified milliseconds, you can learn more about it here
function solvePuzzle() {
while (rezolvat == false) // while all pieces are not in correct position
for (var i = 0; i < _piese.length; i++) { // iterates through array of puzzle pieces
(function (i) {
setTimeout(function () {
if (checkPiesa(i) == false) {
_piesaCurentaDrop = _piese[i];
_piesaCurenta = getPiesaDestinatie(_piesaCurentaDrop.id); // destination piece
_context.save();
_context.globalAlpha = .4;
_context.fillStyle = PUZZLE_HOVER_TINT;
_context.fillRect(_piesaCurentaDrop.xPos, _piesaCurentaDrop.yPos, _latimePiesa, _inaltimePiesa);
_context.fillStyle = PUZZLE_DESTINATION_HOVER_TINT;
_context.fillRect(_piesaCurenta.xPos, _piesaCurenta.yPos, _latimePiesa, _inaltimePiesa);
_context.restore();
// here I want to have some kind of 'sleep' for 2000 ms so you can see the pieces about to be swapped
// setTimeout in side task execution
setTimeout(() => dropPiece(), 1000); // function for swapping puzzle pieces
}
}, 2000 * i); // schedules excution increasingly for each iteration
})(i);
}
}
In the code above for loop finishes immediately, however, it schedules the execution of each iteration after a specified increased time(i*2000) using setTimeout
So the execution of the, (Despite for loop's immediate completion)
first iteration will begin immediately at 0*2000=0 mili-seconds,
the task for second execution will be executed after (1*2000),
the task for third execution will be executed after (2*2000),
and so on..
Just for a simple understanding look at the sample code below
Working Code Sample
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
setTimeout(() => console.log(i + 0.5), 1000); // setTimeout in side task execution in case needed
}, 2000 * i); // schedules excution increasingly for each iteration
})(i);
}

Too much recursion on simple JS slider

I am trying to build a simple slider by changing the background image, but i am not sure why I am getting an error that says too much recursion.
document.addEventListener("DOMContentLoaded", function(event) {
let headerImages = [];
let header = document.querySelector('.header');
let i = 0;
let time = 3000;
headerImages[0] = 'img/header1.jpg';
headerImages[1] = 'img/header2.jpg';
function changeHeaderImg() {
header.style.backgroundImage = "url(" + headerImages[i] + ")";
if(i < headerImages.length - 1){
i++;
} else {
i = 0;
}
setTimeout(changeHeaderImg(), time);
}
changeHeaderImg();
});
You are calling changeHeaderImg and passing it's result to setTimeout instead of passing changeHeaderImg itself.
So you are getting endless recursion which results in so-called "stack overflow" classic error.
Try setTimeout(changeHeaderImg, time);
A function that calls itself is called a recursive function. Once a condition is met, the function stops calling itself. This is called a base case.
In some ways, recursion is analogous to a loop. Both execute the same code multiple times, and both require a condition (to avoid an infinite loop, or rather, infinite recursion in this case). When there are too many function calls, or a function is missing a base case, JavaScript will throw this error.
function loop(x) {
if (x >= 10) // "x >= 10" is the exit condition
return;
// do stuff
loop(x + 1); // the recursive call
}
loop(0);
Setting this condition to an extremely high value, won't work:
function loop(x) {
if (x >= 1000000000000)
return;
// do stuff
loop(x + 1);
}
loop(0);
// InternalError: too much recursion
This recursive function is missing a base case. As there is no exit condition, the function will call itself infinitely.
function loop(x) {
// The base case is missinng
loop(x + 1); // Recursive call
}
loop(0);
// InternalError: too much recursion

Using setTimeout to update progress bar when looping over multiple variables

Suppose you have 3 arrays you want to loop over, with lengths x, y, and z, and for each loop, you want to update a progress bar. For example:
function run() {
x = 100;
y = 100;
z = 10;
count = 0;
for (i=0; i<x; i++) {
//some code
for (j=0; j<y; j++) {
// some code
for (k=0; k<z; k++) {
//some code
$("#progressbar").reportprogress(100*++count/(x*y*z));
}
}
}
}
However, in this example, the progress bar doesn't update until the function completes. Therefore, I believe I need to use setTimeout to make the progress bar update while the function runs, although I'm not sure how to do that when you have nested for loops.
Do I need to break each loop up into its own function, or can I leave them as nested for loops?
I created a jsfiddle page in case you'd like to run the current function: http://jsfiddle.net/jrenfree/6V4Xp/
Thanks!
TL;DR: Use CPS: http://jsfiddle.net/christophercurrie/DHqeR/
The problem with the code in the accepted answer (as of Jun 26 '12) is that it creates a queue of timeout events that don't fire until the triple loop has already exited. You're not actually seeing the progress bar update in real-time, but seeing a late report of what the values of the variables were at the time they were captured in the inner closure.
I'd expect that your 'recursive' solution looks a bit like using continuation-passing style to ensure that your loop doesn't continue until after you've yielded control via setTimeout. You might not know you were using CPS, but if you're using setTimeout to implement a loop, you're probably pretty close to it.
I've spelled out this approach for future reference, because it's useful to know, and the resulting demo performs better than the ones presented. With triple nested loops it looks a bit convoluted, so it may be overkill for your use case, but can be useful in other applications.
(function($){
function run() {
var x = 100,
y = 100,
z = 10,
count = 0;
/*
This helper function implements a for loop using CPS. 'c' is
the continuation that the loop runs after completion. Each
'body' function must take a continuation parameter that it
runs after doing its work; failure to run the continuation
will prevent the loop from completing.
*/
function foreach(init, max, body, c) {
doLoop(init);
function doLoop(i) {
if (i < max) {
body(function(){doLoop(i+1);});
}
else {
c();
}
}
}
/*
Note that each loop body has is own continuation parameter (named 'cx',
'cy', and 'cz', for clarity). Each loop passes the continuation of the
outer loop as the termination continuation for the inner loop.
*/
foreach(0, x, function(cx) {
foreach(0, y, function(cy) {
foreach(0, z, function(cz) {
count += 1;
$('#progressbar').reportprogress((100*(count))/(x*y*z));
if (count * 100 % (x*y*z) === 0) {
/*
This is where the magic happens. It yields
control to the javascript event loop, which calls
the "next step of the foreach" continuation after
allowing UI updates. This is only done every 100
iterations because setTimeout can actually take a lot
longer than the specified 1 ms. Tune the iterations
for your specific use case.
*/
setTimeout(cz, 1);
} else {
cz();
}
}, cy);
}, cx);
}, function () {});
}
$('#start').click(run);
})(jQuery);
You can see on jsFiddle that this version updates quite smoothly.
If you want to use setTimeout you could capture the x, y, z and count variables into a closure:
function run() {
var x = 100,
y = 100,
z = 10,
count = 0;
for (var i=0; i<x; i++) {
for (var j=0; j<y; j++) {
for (var k=0; k<z; k++) {
(function(x, y, z, count) {
window.setTimeout(function() {
$('#progressbar').reportprogress((100*count)/(x*y*z));
}, 100);
})(x, y, z, ++count);
}
}
}
}
Live demo.
Probably a jquery function in reportprogress plugin uses a setTimeout. For example if you use setTimeout and make it run after 0 milliseconds it doesn't mean that this will be run immediately. The script will be executed when no other javascript is executed.
Here you can see that i try to log count when its equal to 0. If i do it in setTimeout callback function then that is executed after all cycles and you will get 100000 no 0. This explains why progress-bar shows only 100%. js Fiddle link to this script
function run() {
x = 100;
y = 100;
z = 10;
count = 0;
for (i=0; i<x; i++) {
//some code
for (j=0; j<y; j++) {
// some code
for (k=0; k<z; k++) {
//some code
if(count===0) {
console.log('log emidiatelly ' + count);
setTimeout(function(){
console.log('log delayed ' + count);
},0);
}
count++;
}
}
}
}
console.log('started');
run();
console.log('finished');
wrapping everything after for(i) in setTimeout callback function made the progress-bar work. js Fiddle link
Edit:
Just checked that style setting code for item is actually executed all the time. I think that it might be a browser priority to execute javascript first and then display CSS changes.
I wrote a another example where i replaced first for loop with a setInterval function. It's a bit wrong to use it like this but maybe you can solve this with this hack.
var i=0;
var interval_i = setInterval(function (){
for (j=0; j<y; j++) {
for (k=0; k<z; k++) {
$("#progressbar").reportprogress(100*++count/(x*y*z));
}
}
i++;
if((i<x)===false) {
clearInterval(interval_i);
}
},0);
JS Fiddle
I've found a solution based on the last reply but changing the interval time to one. This solution show a loader while the main thread is doing an intensive task.
Define this function:
loading = function( runme ) {
$('div.loader').show();
var interval = window.setInterval( function() {
runme.call();
$('div.loader').hide();
window.clearInterval(interval);
}, 1 );
};
And call it like this:
loading( function() {
// This take long time...
data.sortColsByLabel(!data.cols.sort.asc);
data.paint(obj);
});

Categories