The Color Match Game
Enough with the boring examples. Let’s build something fun.
Since this is the first game we will tackle, I picked a very simple one. I named it Color Match.
The game UI will present the player with two randomly selected colors. For example, green and red. The first color is always rendered with black ink while the second color is rendered with a third random color value. The challenge is for the player to determine if the meaning of the first color word matches the ink color of the second word. This is not as easy as it sounds.
Here’s a screenshot of the final game:

The bottom word RED in the screenshot above is rendered with a green ink color. The top word GREEN matches the ink color of the word RED. The player should click YES to win this round.
On every YES/NO click, the UI should indicate whether the click is correct or not and then start another color match challenge.
The focus of this game is on updating the state of a class component and styling things based on values from the state. I am going to build the entire game in a single Game
component to keep a focus on just the state-updates concept. We will also need to use a timer to delay the reset of the game after each button click so that the players get to see if their previous click was correct or not.
Let’s go through this game one step at a time. The key strategy is to find small increments and focus on them rather than getting overwhelmed with the whole picture. Don’t shoot for perfection from the first round. Have something that works and then iterate, improve, and optimize.
While building all the games in this book, I will sometimes implement things in not-very-ideal ways first, show you why they are not ideal, and then go through the process of replacing them with better versions. Give yourself credit if you can spot me doing something like that. |
Step 1 – Initial Markup and Style
I like to start with some mock markup and any styles that I can come up with for that markup. With simple games like this one, this is usually an easy task. Just put mock static content where the dynamic content will eventually be.
The UI of this game is super simple. We can put two static color words where the dynamic ones will be. We can build the whole markup before we even start doing any React code. However, it is always good to build your static markup with the React components that make sense for the app logic.
I’ve decided to use a single Game
component for this game. This decision will allow us to focus on mastering the skill of updating a component state without worrying about data flow. Don’t worry though, games #2, #3, and #4 will all have multiple components and plenty of data flow.
This book is about React, so to keep the examples short and focused on just React, I’ll give you a starting template for each game. The template will have a static mock version of the DOM and all the CSS that you need to style the game the way I did. Feel free to customize the CSS as you wish. I am not a CSS expert and I will probably do some questionable things in my styles.
I opted to do many features of the games, and especially this first one, through CSS styles. I love the simple power of CSS and I think that if something can be done adequately with CSS, you should consider that. For an example of that, I will use only CSS to indicate whether a YES/NO button click was correct or not. In the React component, we just need to use the right CSS className
. This game will also have an example of dynamically styling an element with an inline JavaScript object. I made some of these decisions so that you can learn how to work with both ways of dynamically styling elements in your React UIs.
The game’s starting HTML/CSS template jsdrops.com/rg-4.1 |
Here’s the template HTML that you can start with:
class Game extends React.Component { render() { return ( <div className="game"> <div className="help"> Does the meaning of the top word match the ink color of the bottom word? </div> <div className="body"> <div className="game-status status-playing" /> <div className="meaning">*Meaning*</div> <div className="ink">*Ink*</div> <div className="buttons"> <button>YES</button> <button>NO</button> </div> </div> </div> ); } } ReactDOM.render(<Game />, mountNode);
The bolded words above are the mock color word values that will eventually need to be generated at random.
As you can see, I am giving most elements a CSS className
with the intention of doing most of the styling with pure CSS. Here’s all the CSS that I used to style the game:
.game { max-width: 600px; margin: 0 auto; } .body { text-align: center; position: relative; background-color: #efefef; border: thin solid #ddd; } .meaning, .ink { font-size: 350%; font-weight: bold; margin: 10px; } .help { color: #666; margin: 10px; text-align: center; } .buttons button { margin: 20px; font-size: 200%; padding: 10px 20px; } button:focus { outline: none; } .game-status { position: absolute; width: 100%; height: 100%; opacity: 0.85; background-color: #eee; font-size: 125px; font-weight: bold; } .status-playing { display: none; } .status-correct:after { content: "\2713"; color: green; } .status-wrong:after { content: "\2717"; color: red; }
Here’s how the UI should look like when executing the HTML/CSS above:

Note how I have 2 bolded classes in the templates' CSS. These are the special classes that will cause the UI to indicate a correct or wrong answer. You can test them out by changing the "status-playing"
class in the mock HTML to status-correct
and status-wrong
.
When using status-correct
, the UI should show:

When using status-wrong
, the UI should show:

Change the class back to status-playing
to continue. This is the starting status of the game.
Step 2 – Picking Random Colors
We need to render three random colors (one for the meaning word, one for the ink word, and one for the actual color of the ink word).
To keep the game simple, let’s just use the primary colors of black
, blue
, red
, green
, and yellow
. We can put these values in an array and just sample the array to get a random color.
const colors = [ 'black', 'blue', 'red', 'green', 'yellow' ]; // To get a random color, use _.sample(colors)
Although picking a random value from an array can be easily done with vanilla JavaScript, it is not the focus of this book. Whenever possible, I’ll use methods from the lodash library to keep us focused on just the React API. The above example uses the sample
method from lodash. It’s a method that will return a random value from an array.
Now all we need to do is place three random value somewhere in variables so that we can use them. First, we have to come up with good names for these three variables, then, we need to decide if we need to place them on the state of the Game
component.
The names I came up for these three colors are meaningWord
, inkWord
, and inkColor
. Naming is hard, but I think these are pretty clear.
Since every time a player clicks YES or NO we have to reset the game and start a new one, those values will have to be changed between games. If we place all of the three values on the state of the Game
component, then changing them will reset the game:
// In the Game class state = { meaningWord: _.sample(colors), inkWord: _.sample(colors), inkColor: _.sample(colors), };
We can now use these three values in the rendered output of a Game
component. The first two are easy. They just replace the mock values that we have in the template. The inkColor is a special one. It has to drive the style of the inkWord
. For that, we can use React’s special style property to dynamically set the color of the ink word.
Here are the three changes that we need for the color div
elements in the markup:
<div className="meaning"> {this.state.meaningWord.toUpperCase()} </div> <div className="ink" style={{ color: this.state.inkColor }} > {this.state.inkWord.toUpperCase()} </div>
Since CSS accepts named colors, we can use the color words directly in the style object.
The game should now render two random color words and style the second one with a third random color value.
Step 3 – Planning UI Statuses
Think about the different UI statuses that this game could be in at any moment:
-
If the player has not clicked a button yet, the game would be in laying mode.
-
If the user clicks on a wrong answer, the game would be in a status to indicate that.
-
If the user clicks on a correct answer, the game would be in a status to indicate that.
Let’s officially name these three UI statuses playing
, correct
, and wrong
and collectively refer to them as gameStatus
.
Do we need to manage this gameStatus on the Game state object?
To answer any question like that, you need to answer the following question first:
Do we need React to re-render the Game component if the value of this gameStatus
changes?
Yes, we do. This probably means that this new gameStatus
belongs to the state of the Game
component. It should start as playing
.
Since we already have CSS classes designed to change the UI for the correct
/wrong
statuses, all we need to do in the component’s render
method is use the value of gameStatus
from the state object. We initialize this value on the state first:
// In the Game component state = { meaningWord: _.sample(colors), inkWord: _.sample(colors), inkColor: _.sample(colors), gameStatus: 'playing', // Possible other values: correct, wrong };
Then, in the render output, we use it
<div className={ `game-status status-${this.state.gameStatus}` }/>
You can test these UI statuses by testing all three possible values on the initial gameStatus
state.
Step 4 – Implementing User Interactions
Once the UI is prepared to honor all the values that we planned and initialized on a component’s state, implementing users' interactions will mostly be about updating that component’s state. This usually also involves some application-level logic.
For the Color Match game, on every button click:
-
We need to finish the game. This means changing the
gameStatus
field to eithercorrect
orwrong
. -
We need to compute whether the game has a meaning/ink match or not. This is as simple as comparing
meaningWord
withinkColor
. Let’s name this new fieldmeaningInkMatch
. -
Since
meaningInkMatch
can betrue
orfalse
and the player can click either yes or no, we have 4 possibilities about whether the click was correct or wrong. Let’s assume we have a Boolean flag for the button clicked as well. Let’s name this one yesClick. IfmeaningInkMatch
andyesClick
are bothtrue
or bothfalse
, that indicates a correct click. If they have different Boolean values, that indicates a wrong click.
The logic description above has many conditional branches. Think about a way to avoid having to use many if
statements for this logic.
The logic in the last point above can be computed with a logical XNOR gate, which is the logical compliment of the XOR gate. See the chart below for the four possibilities of the XNOR logical operator:

JavaScript does not have a logic XNOR operator, but we can use the JavaScript Bitwise XOR (^) operators to implement it.
In JavaScript, A ^ B
returns a 1
in each bit position for which the corresponding bits of either but not both operands are 1s
. Since false
is 0
and true
is 1
, the XNOR operation that we need to implement the logic in the last point above can just be (meaningInk ^ yesClick) === 0
.
Here are the code modifications that we need to implement all of the logic identified above:
// Implement the clicking logic. In the Game class handleClick = (yesClick) => { this.setState((prevState) => { if (prevState.gameStatus !== 'playing') { return null; // Do nothing. } const meaningInkMatch = prevState.meaningWord === prevState.inkColor; const correct = (meaningInkMatch ^ yesClick) === 0; return { gameStatus: correct ? 'correct' : 'wrong' }; }); }; // Register the click handlers on buttons <div className="buttons"> <button onClick={() => this.handleClick(true)}> YES </button> <button onClick={() => this.handleClick(false)}> NO </button> </div>
Note a couple of things about the code above:
-
We had to wrap the
handleClick
function with an inline function before we passed it as theonClick
reference function because we needed to invoke it with different arguments for each button. Keep in mind that this will create new inline functions every time theGame
component gets re-rendered. This is okay for infrequent renders that do not affect children components, which is the case we have here. Keep it simple. -
I used an updater function for
setState
because I needed to access values that are on the state of theGame
component to compute the new state. This might not be necessary because within one game run the colors values do not actually change.
Note how I added an edge-case check to handleClick to disable clicking on the buttons unless the game is in playing mode. Returning null from the setState updater function makes the setState call do nothing.
|
Have you identified a problem in the design of the game so far? If the colors values do not change in a single game, why do we have them on the state?
The golden React rule about this is: Only place on a component state what is absolutely needed to be on the state. Minimize state elements and use computations (and caching) otherwise.
If you think about the Game
state object, there is some value overlap among what we have so far on the state. Changing either gameStatus
or any of the color values will trigger the UI to render a new game session. We can minimize the state here.
However, before we refactor anything we wrote so far to minimize the game’s state object, let’s finish the minimum requirements of the game. We need to reset the game after every click. Let’s make that our next step.
Did you notice how most of this very important past step was not about React but rather about coming up with the JavaScript logic needed for the user interaction? I think 80% of your power as a React developer comes from your JavaScript skills. |
Step 5 – Resetting the Game
This one is straight forward. After every click, we need to reset the state object to the same object we used for the initial state. Let’s use a browser timer to give the players a bit of delay so that they can see the result of their last click. After experimenting with a few values for this timer, I settled on having that delay be half a second.
Here are the modifications that we need to implement this step:
handleClick = (yesClick) => { this.setState((prevState) => { if (prevState.gameStatus !== 'playing') { return null; // Do nothing. } const meaningInkMatch = prevState.meaningWord === prevState.inkColor; const correct = (meaningInkMatch ^ yesClick) === 0; return { gameStatus: correct ? 'correct' : 'wrong' }; }, this.resetGameAfterDelay); }; resetGameAfterDelay = () => { setTimeout(() => { this.setState({ meaningWord: _.sample(colors), inkWord: _.sample(colors), inkColor: _.sample(colors), gameStatus: 'playing', }); }, 500); };
Since we need to trigger the timer after the first setState
call is completed (and remember setState
is to be considered asynchronous), I used the second argument of setState
(inside handleClick
). This is the first time we are seeing this, so let me explain!
The second argument to setState
is a callback (function reference). React guarantees it will execute the function referenced as the second argument after the setState
operation is done. This is similar to the way Node.js functions work. The resetGameAfterDelay
function will be executed after the correct
/wrong
UI update is done.
Inside resetGameAfterDelay
, we just use a regular browser timer to update the state of the component once again (after 500 milliseconds). However, this time the state will be reset to its initial shape.
This change makes the minimum requirements of this game complete! Go ahead and play a few rounds.
You probably noticed that I just copied the initial state object above, which is considered a code smell (not following the DRY principle). I am aware of that, but I don’t want to optimize yet because I am still not sure that we need all these elements on the state. When possible, delay thinking about optimization until after you validate your design decisions. |
Step 6 – Refactoring the State
Although changing the three color values should change the UI, if you carefully analyze the code you’ll see that this operation happens only when we change the gameStatus
back to playing
(in the resetGameAfterDelay
function). That gameStatus change by itself will trigger React to re-render the UI. The three colors never change between clicks. We can simply store them as instance properties. Furthermore, the meaningInkMatch
computation does not have to be in the click handler.
That too is a constant value between clicks.
For this simple example, these optimizations will not make a big difference, but I want you to start getting into the habit of questioning what you put on the state. Don’t use the state as a dumping ground for anything your app needs.
Since we need these three colors when the game is first mounted and every time we click the buttons, let’s create a function that returns an object to represent them and place that object as an instance property in the Game
component. We can also include the meaningInkMatch
value as part of that object. I’ll name this instance object colorValues
.
The function that generates the three random colors does not have to be part of the Game
component at all. Let’s call in randomColors
:
// Top-level function const randomColors = () => { const meaningWord = _.sample(colors); const inkWord = _.sample(colors); const inkColor = _.sample(colors); return { meaningWord, inkWord, inkColor, meaningInkMatch: meaningWord === inkColor, }; };
Both the colors
array and this new randomColors
function can be extracted into their own module and tested separately. This is important because it simplifies the Game
component and reduces its dependencies. For example, if we do this extraction, the game stops directly depending on lodash (for the sample
method). It would only depend on a colors module. We can change the colors module without needing to change the Game
component.
To use the randomColors
function:
// The new Game state is now: state = { gameStatus: 'playing', }; // Use an instance property for the color values colorValues = randomColors();
This way, when the component is first mounted, it will get an object with 3 random colors and 1 Boolean flag.
We need to change the Game
component in a few places to use the colorValues
instance property:
// Change handleClick to use this.colorValues handleClick = (yesClick) => { this.setState(prevState => { if (prevState.gameStatus !== 'playing') { return null; // Do nothing. } const correctClick = (this.colorValues.*meaningInkMatch ^ yesClick) === 0; return { gameStatus: correctClick ? 'correct' : 'wrong', }; }, this.resetGameAfterDelay ); }; // Change resetGame to use reset this.colorValues resetGameAfterDelay = () => { setTimeout(() => { *this.colorValues = randomColors(); this.setState({ gameStatus: 'playing' }); }, 500); };
We will also need to change the render function to use the color values from the new instant property rather than from the state. Try to make that change on your own. The playground link below has all the changes we need, including the render function.
Step 7 – Getting Feedback and Improving
This step is not really about React, but before you label something done or MVP, stop testing it with your biased eyes and put it in-front of beta testers. Their fresh feedback is very valuable.
For example, you might have noticed that the game draws a lot more color-ink mismatches than it does matches. A beta tester would certainly notice that if you did not. Beta testing this game made me tune the UI and the help message as well.
Let’s fix this mismatch bios. We need the game to be a match at least 50% of the time. This is a simple change. We can use the Math.random() < 0.5
condition for that and for 50% of the game sessions we can force the colors to be a match.
Here is a possible implementation:
const randomColors = () => { const meaningWord = _.sample(colors); const inkWord = _.sample(colors); const inkColor = Math.random() < 0.4 ? meaningWord : _.sample(colors); return { meaningWord, inkWord, inkColor, meaningInkMatch: meaningWord === inkColor, }; };
Now test the game some more. There should be a good balance between match sessions and mismatch sessions. Make sure you iterate on users' feedback. I think we can now label this progress as MVP. #ShipIt.
Playground link for the final code of this game jsdrops.com/rg-4.7 |
Bonus Challenges
Maintain a score property for all played games. Give the player 1 point for each correct click and take 1 point for each wrong click. Display the current score somewhere in the game UI.
Note this new score property needs to be kept between game sessions. Where do you think you can store it?
Let me make the challenge a little bit interesting: You can’t use a global variable for this new score value.
If you feel like tackling a harder challenge, make this game a lot harder by swapping top/bottom logic. For every new game, change the help message to swap the top and bottom words:
-
First game help message: Does the meaning of the top word match the ink color of the bottom word?
-
Second game help message: Does the meaning of the bottom word match the ink color of the top word?
If your testers think this game is easy even after this change, maybe make the top/bottom swapping at random!