How I Built a Wheel of Fortune JavaScript Game for My Zoom Group

Share this article

How I Built a Wheel of Fortune JavaScript Game for My Zoom Group

In this article, I describe how I developed a JavaScript “Wheel of Fortune” game to make online meetings via Zoom a little more fun during the global pandemic.

The current pandemic has forced many social activities to go virtual. Our local Esperanto group, for example, now meets online (instead of in person) for our monthly language study meetups. And as the group’s organizer, I’ve had to to re-think many of our activities because of the coronavirus. Previously, I could add watching a film, or even a stroll through the park, to our mix of activities in an effort to avoid fatigue (constant grammar drills don’t encourage repeat attendance).

Our new Wheel of Fortune game was well received. Of course, SitePoint is a tech blog, so I’ll be presenting an overview of what went into building a rudimentary version of the game to screenshare in our online meetings. I’ll discuss some of the trade-offs I made along the way, as well as highlight some possibilities for improvement and things I should have done differently in hindsight.

First Things First

If you’re from the United States, you’re probably already familiar with Wheel of Fortune, as it’s the longest-running American game show in history. (Even if you’re not in the United States, you’re probably familiar with some variant of the show, as it’s been adapted and aired in over 40 international markets.) The game is essentially Hangman: contestants try to solve a hidden word or phrase by guessing its letters. Prize amounts for each correct letter is determined by spinning a large roulette-style wheel bearing dollar amounts — and the dreaded Bankrupt spots. A contestant spins the wheel, guesses a letter, and any instances of said letter in the puzzle are revealed. Correct guesses earn the contestant another chance to spin and guess, while incorrect guesses advance game play to the next contestant. The puzzle is solved when a contestant successfully guesses the word or phrase. The rules and various elements of the game have been tweaked over the years, and you can certainly adapt your own version to the needs of your players.

For me, the first order of business was to decide how we physically (virtually) would play the game. I only needed the game for one or two meetings, and I wasn’t willing to invest a lot of time building a full-fledged gaming platform, so building the app as a web page that I could load locally and screenshare with others was fine. I would emcee the activity and drive the gameplay with various keystrokes based on what the players wanted. I also decided to keep score using pencil and paper —something I’d later regret. But in the end, plain ol’ JavaScript, a little bit of canvas, and a handful of images and sound effect files was all I needed to build the game.

The Game Loop and Game State

Although I was envisioning this as a “quick and dirty” project rather than some brilliantly coded masterpiece following every known best practice, my first thought was still to start building a game loop. Generally speaking, gaming code is a state machine that maintains variables and such, representing the current state of the game with some extra code bolted on to handle user input, manage/update the state, and render the state with pretty graphics and sound effects. Code known as the game loop repeatedly executes, triggering the input checks, state updates, and rendering. If you’re going to build a game properly, you’ll most likely be following this pattern. But I soon realized I didn’t need constant state monitoring/updating/rendering, and so I forwent the game loop in favor of basic event handling.

In terms of maintaining state, the code needed to know the current puzzle, which letters have been guessed already, and which view to display (either the puzzle board or the spinning wheel). Those would be globally available to any callback logic. Any activities within the game would be triggered when handling a keypress.

Here’s what the core code started to look like:

(function (appId) {
  // canvas context
  const canvas = document.getElementById(appId);
  const ctx = canvas.getContext('2d');

  // state vars
  let puzzles = [];
  let currentPuzzle = -1;
  let guessedLetters = [];
  let isSpinning = false;

  // play game
  window.addEventListener('keypress', (evt) => {
    //... respond to inputs
  });
})('app');

The Game Board and Puzzles

Wheel of Fortune’s game board is essentially a grid, with each cell in one of three states:

  • empty: empty cells aren’t used in the puzzle (green)
  • blank: the cell represents a hidden letter in the puzzle (white)
  • visible: the cell reveals a letter in the puzzle

One approach to writing the game would be to use an array representing the game board, with each element as a cell in one of those states, and rendering that array could be accomplished several different ways. Here’s one example:

let puzzle = [...'########HELLO##WORLD########'];

const cols = 7;
const width = 30;
const height = 35;

puzzle.forEach((letter, index) => {
  // calculate position
  let x = width * (index % cols);
  let y = height * Math.floor(index / cols);

  // fill
  ctx.fillStyle = (letter === '#') ? 'green' : 'white';
  ctx.fillRect(x, y, width, height);

  // stroke
  ctx.strokeStyle = 'black';
  ctx.strokeRect(x, y, width, height);

  // reveal letter
  if (guessedLetters.includes(letter)) {
      ctx.fillStyle = 'black';
      ctx.fillText(letter, x + (width / 2), y + (height / 2));
  }
});

This approach iterates through each letter in a puzzle, calculating the starting coordinates, drawing a rectangle for the current cell based on the index and other details — such as the number of columns in a row and the width and height of each cell. It checks the character and colors the cell accordingly, assuming # is used to denote an empty cell and a letter denotes a blank. Guessed letters are then drawn on the cell to reveal them.

A potential game board rendered using the above code

Another approach would be to prepare a static image of the board for each puzzle beforehand, which would be drawn to the canvas. This approach can add a fair amount of effort to puzzle preparation, as you’ll need to create additional images, possibly determine the position of each letter to draw on the custom board, and encode all of that information into a data structure suitable for rendering. The trade-off would be better-looking graphics and perhaps better letter positioning.

This is what a puzzle might look like following this second approach:

let puzzle = {
  background: 'img/puzzle-01.png',
  letters: [
    {chr: 'H', x: 45,  y: 60},
    {chr: 'E', x: 75,  y: 60},
    {chr: 'L', x: 105, y: 60},
    {chr: 'L', x: 135, y: 60},
    {chr: 'O', x: 165, y: 60},
    {chr: 'W', x: 45,  y: 100},
    {chr: 'O', x: 75,  y: 100},
    {chr: 'R', x: 105, y: 100},
    {chr: 'L', x: 135, y: 100},
    {chr: 'D', x: 165, y: 100}
  ]
};

For the sake of efficiency, I’d recommend including another array to track matching letters. With only the guessedLetters array available, you’d need to scan the puzzle’s letters repeatedly for multiple matches. Instead, you can set up an array to track the solved letters and just copy the matching definitions to it when the player makes their guess, like so:

const solvedLetters = [];

puzzle.letters.forEach((letter) => {
  if (letter.chr === evt.key) {
    solvedLetters.push(letter);
  }
});

Rendering this puzzle then looks like this:

// draw background
const imgPuzzle = new Image();
imgPuzzle.onload = function () {
  ctx.drawImage(this, 0, 0);
};
imgPuzzle.src = puzzle.background;

// reveal letters
solvedLetters.forEach((letter) => {
  ctx.fillText(letter.chr, letter.x, letter.y);
});

A potential game board rendered using the alternative approach

For the record, I took the second approach when writing my game. But the important takeaway here is that there are often multiple solutions to the same problem. Each solution comes with its own pros and cons, and deciding on a particular solution will inevitably affect the design of your program.

Spinning the Wheel

At first blush, spinning the wheel appeared to be challenging: render a circle of colored segments with prize amounts, animate it spinning, and stop the animation on a random prize amount. But a little bit of creative thinking made this the easiest task in the entire project.

Regardless of your approach to encoding puzzles and rendering the game board, the wheel is probably something you’ll want to use a graphic for. It’s much easier to rotate an image than draw (and animate) a segmented circle with text; using an image does away with most of the complexity up front. Then, spinning the wheel becomes a matter of calculating a random number greater than 360 and repeatedly rotating the image that many degrees:

const maxPos = 360 + Math.floor(Math.random() * 360);
for (let i = 1; i < maxPos; i++) {
  setTimeout(() => {
    ctx.save();
    ctx.translate(640, 640);
    ctx.rotate(i * 0.01745); // radians
    ctx.translate(-640, -640);
    ctx.drawImage(imgWheel, 0, 0);
    ctx.restore();
  }, i * 10);
}

I created a crude animation effect by using setTimeout to schedule rotations, with each rotation scheduled further and further into the future. In the code above, the first 1 degree rotation is scheduled to be rendered after 10 milliseconds, the second is rendered after 20 milliseconds, etc. The net effect is a rotating wheel at approximately one rotation every 360 milliseconds. And ensuring the initial random number is greater than 360 guarantees I animate at least one full rotation.

A brief note worth mentioning is that you should feel free to play around with the “magic values” provided to set/reset the center point around which the canvas is rotated. Depending on the size of your image, and whether you want the the entire image or just the top portion of the wheel to be visible, the exact midpoint may not produce what you have in mind. It’s okay to tweak the values until you achieve a satisfactory result. The same goes for the timeout multiplier, which you can modify to change the animation speed of the rotation.

Going Bankrupt

I think we all experience a bit of schadenfreude when a player’s spin lands on Bankrupt. It’s fun to watch a greedy contestant spin the wheel to rack up a few more letters when it’s obvious they already know the puzzle’s solution — only to lose it all. And there’s the fun bankruptcy sound effect, too! No game of Wheel of Fortune would be complete without it.

For this, I used the Audio object, which gives us the ability to play sounds in JavaScript:

function playSound(sfx) {
  sfx.currentTime = 0;
  sfx.play();
}

const sfxBankrupt = new Audio('sfx/bankrupt.mp3');

// whenever a spin stops on bankrupt...
playSound(sfxBankrupt);

But what triggers the sound effect?

One solution would be to press a button to trigger the effect, since I’d be controlling the gameplay already, but it was more desirable for the game to automatically play the sound. Since Bankrupt wedges are the only black wedges on the wheel, it’s possible to know whether the wheel stops on Bankrupt simply by looking at the pixel color:

const maxPos = 360 + Math.floor(Math.random() * 360);
for (let i = 1; i < maxPos; i++) {
  setTimeout(() => {
    ctx.save();
    ctx.translate(640, 640);
    ctx.rotate(i * 0.01745); // radians
    ctx.translate(-640, -640);
    ctx.drawImage(imgWheel, 0, 0);
    ctx.restore();

    if (i === maxPos - 1) {
      // play bankrupt sound effect when spin stops on black
      const color = ctx.getImageData(640, 12, 1, 1).data;
      if (color[0] === 0 && color[1] === 0 && color[2] === 0) {
        playSound(sfxBankrupt);
      }
    }
  }, i * 10);
}

I only focused on bankruptcies in my code, but this approach could be expanded to determine prize amounts as well. Although multiple amounts share the same wedge color — for example $600, $700, and $800 all appear on red wedges — you could use slightly different shades to differentiate the amounts: rgb(255, 50, 50), rgb(255, 51, 50), and rgb(255, 50, 51) are indistinguishable to human eyes but are easily identified by the application. In hindsight, this is something I should have pursued further. I found it mentally taxing to manually keep score while pressing keys and running the game, and the extra effort to automate score keeping would definitely have been worth it.

The differences between these shades of red are indistinguishable to the human eye

Summary

If you’re curious, you can find my code on GitHub. It isn’t the epitome and best practices, and there’s lots of bugs (just like a lot of real-world code running in production environments!) but it served its purpose. But ultimately the goal of this article was to inspire you and invite you to think critically about your own trade-off choices.

If you were building a similar game, what trade-offs would you make? What features would you deem critical? Perhaps you’d want proper animations, score keeping, or perhaps you’d even use web sockets so contestants could play together in their own browsers rather than via screensharing the emcee’s screen.

Looking beyond this particular example, what choices are you faced with in your daily work? How do you balance business priorities, proper coding practices, and tech debt? When does the desire to make things perfect become an obstacle to shipping a product? Let me know on Twitter.

Frequently Asked Questions (FAQs) about JavaScript Game Zoom Group

How can I create a Wheel of Fortune game for Zoom?

Creating a Wheel of Fortune game for Zoom involves a combination of JavaScript and HTML. You’ll need to create a canvas element in your HTML where the wheel will be drawn. Then, in your JavaScript, you’ll create a function to draw the wheel and another function to spin it. You can use the Math.random() function to generate a random spin speed. You can also add event listeners to start the spin when a button is clicked. Once your game is ready, you can share your screen on Zoom to play it with others.

Can I customize the Wheel of Fortune game?

Yes, you can customize the Wheel of Fortune game to suit your needs. You can change the number of segments on the wheel, the colors, and the prizes. You can also adjust the spin speed and duration. All these changes can be made in the JavaScript code. If you’re not familiar with JavaScript, there are many online resources and tutorials that can help you learn.

How can I add sound effects to the game?

Adding sound effects to the game can enhance the gaming experience. You can use the HTML5 audio element to play sound effects. You’ll need to source sound files (in a format like .mp3 or .wav) for the spinning wheel and for when a player wins. Then, in your JavaScript, you can create a new Audio object and call the play() method at the appropriate times.

Can I use this game for other video conferencing platforms?

Yes, this game can be used on any platform where you can share your screen. This includes platforms like Microsoft Teams, Google Meet, and Skype. Just make sure that the platform you’re using supports screen sharing and that your game is displayed correctly on other participants’ screens.

How can I make the game interactive for all participants?

Making the game interactive for all participants can be a bit challenging, as you’ll need to find a way to let participants spin the wheel. One solution could be to use the Zoom reactions feature. You can ask participants to use a specific reaction when they want to spin the wheel, and then you can spin the wheel for them. Alternatively, you could use a third-party tool that allows participants to interact with your screen.

Can I host a Wheel of Fortune game without coding knowledge?

If you’re not comfortable with coding, there are other options available. There are many online platforms that offer pre-made Wheel of Fortune games that you can customize and play over Zoom. These platforms often have a user-friendly interface that allows you to easily change the prizes and colors of the wheel.

How can I troubleshoot issues with the game?

If you’re experiencing issues with the game, the first step is to check your code for errors. JavaScript has a built-in error handling mechanism that can help you identify and fix issues. If you’re still having trouble, you can search for solutions online or ask for help on coding forums.

Can I monetize my Wheel of Fortune game?

Monetizing your Wheel of Fortune game can be a bit tricky, as it depends on the platform you’re using and the rules they have in place. Some platforms may allow you to charge participants a fee to play, while others may not. It’s important to check the terms of service of the platform you’re using before attempting to monetize your game.

How can I make the game more engaging?

There are many ways to make the game more engaging. You can add sound effects, use vibrant colors, and include exciting prizes. You can also encourage interaction by asking participants to use reactions or chat to indicate when they want to spin the wheel.

Can I use this game for teaching or training purposes?

Yes, this game can be a fun and engaging way to teach or train. You can customize the wheel with questions or topics related to your lesson or training material. Participants can spin the wheel to select a question or topic, adding an element of surprise and excitement to the learning process.

Timothy BoronczykTimothy Boronczyk
View Author

Timothy Boronczyk is a native of Syracuse, New York, where he lives with no wife and no cats. He has a degree in Software Application Programming, is a Zend Certified Engineer, and a Certified Scrum Master. By day, Timothy works as a developer at ShoreGroup, Inc. By night, he freelances as a writer and editor. Timothy enjoys spending what little spare time he has left visiting friends, dabbling with Esperanto, and sleeping with his feet off the end of his bed.

canvasgamegame developmentjavascriptjavascript game
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week