I've been thinking about writing a game in JavaScript as a fun programming challenge, but I can't decide if I want to do a board game or a word game. So I figured I'd start by writing a set of functions that can be used for various game components. Today I'm working on a dice roll animation.
The goal is to write a function that returns an animated SVG of the dice dots. It should change rapidly at first to simulate the dice roll, then slow down and stop after a few seconds. Now, I could write this all as one function, but there's a lot of pieces to this logic that could be reused in other game components. Let's break it down.
Want to skip the article and go straight to the app? Check it out here!
Splitting into multiple functions
Instead of one big function that returns the SVG, I want to break this down into smaller components that can be reused in other games.
Something like this:
randBetween(min,max){
//returns random integer between min and max
}
diceSVG(num){
//returns svg of dice for num (1-6)
}
intervalRamp(timerName, start, stop, rate, callback){
//run the callback function with a slowing interval until stop is reached.
}
diceRoll(){
//wrapper for the previous 3 functions.
}
Generating a random number in a range
For this use case, we only need a random number between 1 and 6. But in the next game, we may have a 20 sided dice, or want a random number that includes zero. So the min and max should be input parameters.
randBetween(min=1, max=6)
Generates a random integer between the specified minimum and maximum values, inclusive.
randBetween(min=1, max=6) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
Converting the Number to a Dice SVG
Now that I have the random number, I want to display this as an SVG of a dice. I got a little help from ChatGPT on this one. The code defines an svgString
as a starting point for the dice. Then the dotPositions
const is used to lookup the correct dot map based on the input number, and adds that onto the SVG string.
diceSVG(num)
// Function to generate SVG of a dice with the given number (1-6) as a data URL
diceSVG(num=1) {
// Define SVG dimensions and radius for dots
const svgWidth = 100;
const svgHeight = 100;
const dotRadius = 10;
// Start the SVG string
let svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="${svgWidth}" height="${svgHeight}" viewBox="0 0 ${svgWidth} ${svgHeight}" style="border-radius: 10px; background-color: white;">`;
// Calculate dot positions based on the dice number
const dotPositions = {
1: [{ x: svgWidth / 2, y: svgHeight / 2 }],
2: [{ x: svgWidth / 3, y: svgHeight / 3 }, { x: (2 * svgWidth) / 3, y: (2 * svgHeight) / 3 }],
3: [
{ x: svgWidth / 3, y: svgHeight / 3 },
{ x: svgWidth / 2, y: svgHeight / 2 },
{ x: (2 * svgWidth) / 3, y: (2 * svgHeight) / 3 },
],
4: [
{ x: svgWidth / 3, y: svgHeight / 3 },
{ x: (2 * svgWidth) / 3, y: svgHeight / 3 },
{ x: svgWidth / 3, y: (2 * svgHeight) / 3 },
{ x: (2 * svgWidth) / 3, y: (2 * svgHeight) / 3 },
],
5: [
{ x: svgWidth / 3, y: svgHeight / 3 },
{ x: (2 * svgWidth) / 3, y: svgHeight / 3 },
{ x: svgWidth / 2, y: svgHeight / 2 },
{ x: svgWidth / 3, y: (2 * svgHeight) / 3 },
{ x: (2 * svgWidth) / 3, y: (2 * svgHeight) / 3 },
],
6: [
{ x: svgWidth / 3, y: svgHeight / 4 },
{ x: (2 * svgWidth) / 3, y: svgHeight / 4 },
{ x: svgWidth / 3, y: (2 * svgHeight) / 4 },
{ x: (2 * svgWidth) / 3, y: (2 * svgHeight) / 4 },
{ x: svgWidth / 3, y: (3 * svgHeight) / 4 },
{ x: (2 * svgWidth) / 3, y: (3 * svgHeight) / 4 },
],
};
// Add circles for each dot position to the SVG string
dotPositions[num].forEach((position) => {
svgString += `<circle cx="${position.x}" cy="${position.y}" r="${dotRadius}" fill="black" />`;
});
// Close the SVG string
svgString += `</svg>`;
// Convert SVG to data URL
return `data:image/svg+xml;base64,${btoa(svgString)}`;
}
Note: This could also be done with createElementNS(), but that requires access to the document. This string approach works without document access.
Animating the SVG
Ok, the diceSVG() combined with the randBetween() functions can now simulate a dice roll, but it's not animated. I could just run the diceSVG function a few times and then stop, but I want the rate to slow down before stopping on the last value, to make it more realistic. So instead of just using a for loop or setInterval(), I want the duration of each loop to get a little longer each time.
But wait! We might want to run other functions on a timer that changes rate, for a different game component later. So first we need a function that just does the timer part and calls another function each loop.
I gave ChatGPT the function requirements and input parameters, and here's what it came up with:
intervalRamp(timerName, start, stop, rate, callback)
// Function to create an interval with a callback, modifying interval length over time
intervalRamp(timerName, start, stop, rate, callback) {
let currentInterval = start;
const intervalHandler = () =>{
callback();
currentInterval += rate;
if ((rate > 0 && currentInterval >= stop) || (rate < 0 && currentInterval <= stop)) {
clearInterval(window[timerName]);
} else {
clearInterval(window[timerName]);
window[timerName] = setInterval(intervalHandler, currentInterval);
}
}
// Initialize the interval
window[timerName] = setInterval(intervalHandler, start);
}
And with this, we now have all the pieces for the final diceRoll() function.
diceRoll()
// Wrapper function to simulate a dice roll with realistic start, stop, and rate values
diceRoll() {
const startInterval = 100; // Initial interval duration in milliseconds
const stopInterval = 500; // Stop interval duration in milliseconds
const rampRate = 50; // Rate of interval change in milliseconds
// Use a unique timer name for each dice roll
const timerName = `diceRollTimer_${Date.now()}`;
// Roll the dice using the defined functions
this.intervalRamp(timerName, startInterval, stopInterval, rampRate, () => {
const rollResult = this.randBetween(1, 6);
const diceImageURL = this.diceSVG(rollResult);
return diceImageURL;
});
}
Using ChatGPT to Write Code
Working with ChatGPT to write code is a great way to learn new programming techniques and increase productivity. I went from having an idea, to a working dice roll function in about 30 minutes. Here's the original prompt I started with:
I want to build a dice roll svg animation with vanilla js. write the following functions:
randBetween(min,max){
//returns random integer between min and max, including the min and max values.
}
diceSVG(num){
//returns svg of dice for num (1-6) as data url
}
intervalRamp(timerName, start, stop, rate, callback){
//creates an interval with callback function at start interval. each time the interval is up, the interval length is modified by the rate (pos or neg) until the stop rate is reached.
If there is a better approach for this then use your own params.
}
diceRoll(){
//wrapper for the previous 3 functions. choose realistic start, stop and rate values to simulate a dice roll.
}
Conclusion
And here's the finished product!
Writing game logic can be a fun programming challenge, and a great way to hone your developer skills. I plan to write a few more of these game components before diving into a full game build. Got an idea for a component or game? Drop a comment below and let us know your ideas! Or better yet, fork this app and create your own game, then add it to the community template library!