I would like to add a small dice rolling effect to my Javascript code. I think a good ways is to use the setInterval()
method. My idea was following code (just
Like callbacks you can use Promises to pass a function that is called when the program is done running, if you use reject
you can also handle errors with Promises.
function rollDice() {
return new Promise((resolve, reject) => {
const dice = document.getElementById("dice")
let i = Math.floor((Math.random() * 25) + 5)
const intervalId = setInterval(() => {
const diceValue = Math.floor((Math.random() * 6) + 1)
dice.src = `./images/dice/dice${diceValue}.png`
if (--i < 1) {
clearInterval(intervalId)
resolve(diceValue)
}
}, 50)
})
}
Then use it like this:
rollDice().then(value => alert(`Dice rolled: ${value}`))
Orginally your code was all sequential. Here is a basic dice game where two players roll one and they see who has a bigger number. [If a tie, second person wins!]
function roleDice() {
return Math.floor(Math.random() * 6) + 1;
}
function game(){
var player1 = roleDice(),
player2 = roleDice(),
p1Win = player1 > player2;
alert( "Player " + (p1Win ? "1":"2") + " wins!" );
}
game();
The code above is really simple since it just flows. When you put in a asynchronous method like that rolling the die, you need to break up things into chunks to do processing.
function roleDice(callback) {
var i = Math.floor((Math.random() * 25) + 5);
var j = i;
var test = setInterval(function(){
i--;
var die = Math.floor((Math.random() * 6) + 1);
document.getElementById("dice").src = "./images/dice/dice" + die + ".png";
if(i < 1) {
clearInterval(test);
callback(die); //Return the die value back to a function to process it
}
}, 50);
}
function game(){
var gameInfo = { //defaults
"p1" : null,
"p2" : null
},
playerRolls = function (playerNumber) { //Start off the rolling
var callbackFnc = function(value){ //Create a callback that will
playerFinishes(playerNumber, value);
};
roleDice( callbackFnc );
},
playerFinishes = function (playerNumber, value) { //called via the callback that role dice fires
gameInfo["p" + playerNumber] = value;
if (gameInfo.p1 !== null && gameInfo.p2 !== null ) { //checks to see if both rolls were completed, if so finish game
giveResult();
}
},
giveResult = function(){ //called when both rolls are done
var p1Win = gameInfo.p1 > gameInfo.p2;
alert( "Player " + (p1Win ? "1":"2") + " wins!" );
};
playerRolls("1"); //start player 1
playerRolls("2"); //start player 2
}
game();
The above code could be better in more of an OOP type of way, but it works.
There are a few issues for the above solutions to work. Running the program doesn't (at least not in my preferred browser) show any images, so these has to be loaded before running the game.
Also, by experience I find the best way to initiate the callback method in cases like preloading N images or having N players throw a dice is to let each timeout function do a countdown to zero and at that point execute the callback. This works like a charm and does not rely on how many items needing to be processed.
<html><head><script>
var game = function(images){
var nbPlayers = 2, winnerValue = -1, winnerPlayer = -1;
var rollDice = function(player,callbackFinish){
var playerDice = document.getElementById("dice"+player);
var facesToShow = Math.floor((Math.random() * 25) + 5);
var intervalID = setInterval(function(){
var face = Math.floor(Math.random() * 6);
playerDice.src = images[face].src;
if (--facesToShow<=0) {
clearInterval(intervalID);
if (face>winnerValue){winnerValue=face;winnerPlayer=player}
if (--nbPlayers<=0) finish();
}
}, 50);
}
var finish = function(){
alert("Player "+winnerPlayer+" wins!");
}
setTimeout(function(){rollDice(0)},10);
setTimeout(function(){rollDice(1)},10);
}
var preloadImages = function(images,callback){
var preloads = [], imagesToLoad = images.length;
for (var i=0;i<images.length;++i){
var img=new Image();
preloads.push(img);
img.onload=function(){if(--imagesToLoad<=0)callback(preloads)}
img.src = images[i];
}
}
preloadImages(["dice1.png","dice2.png","dice3.png","dice4.png","dice5.png","dice6.png"],game);
</script></head><body>
<img src="" id="dice0" /><img src="" id="dice1" /></body></html>
You stumbled into the pitfall most people hit at some point when they get in touch with asynchronous programming.
You cannot "wait" for an timeout/interval to finish - trying to do so would not work or block the whole page/browser. Any code that should run after the delay needs to be called from the callback you passed to setInterval
when it's "done".
function rollDice(callback) {
var i = Math.floor((Math.random() * 25) + 5);
var j = i;
var test = setInterval(function() {
i--;
var value = Math.floor((Math.random() * 6) + 1);
document.getElementById("dice").src = "./images/dice/dice" + value + ".png";
if(i < 1) {
clearInterval(test);
callback(value);
}
}, 50);
}
You then use it like this:
rollDice(function(value) {
// code that should run when the dice has been rolled
});