Are you ready to level up your coding skills and create an unforgettable gaming experience?
In this tutorial, we’re diving deep into the world of React.js development to build a Tic Tac Toe game that’s computer vs player.
We’ll be taking our already nifty state management to a whole other level while creating something portfolio-worthy!
Related: How To Make A Really Good Shopping Cart With React
Not that all our other projects don’t deserve some portfolio limelight. If anything, every single THT project will make your skills shine bright like a diamond 💎
It’s just that Tic Tac Toe is a classic developer test. The logical workflow it requires is a perfect way to gauge our ability to craft programming logic.
For all my coding girl gurus out there, it’s the “no-makeup” makeup look of the programming world 😉
For anyone like DM who doesn’t understand a word of that, think about the difficulty of making something simple. The skill it requires. The workarounds it demands.
Hey!
Checkout the live demo here! Design inspo drawn from Crypto Wallet App I Ofspace on dribbble.
No more talking, get ready to unleash your creativity and take your gaming prowess to new heights!
Beginning the adventure with a start page
Picture this: You’re greeted by a sleek start page that sets the stage for an epic gaming journey.
The start page consists of an app title, logo, and action button that initializes gameplay.
With its captivating design and intuitive interface, you can’t help but feel a surge of excitement as you hit that Start button.
I use box-shadow
to create a background color slide visual effect.
.Btn-active:hover {
box-shadow: inset 400px 0 0 0 var(--lightblue-tint);
}
The inset
keyword specifies that the shadow should be inset inside the element, creating an inner shadow rather than an outer shadow, extending the stated 400px.
Note that usually, I call instance-specific styles added on a custom component className
. However, doing so won’t work in this case because we’re spreading the props other than className, {...props}
.
So any className
prop will overwrite the default button styles that should apply to the Button
component. To avoid the overwrite, we need to rename the instance styles which I call cs
(clearly creative 🙄).
I, also, use React PropTypes to define the display style of the button as either active or regular.
Button.propTypes = {
active: PropTypes.bool.isRequired
}
Setting up the selection screens
Customize a user’s gaming experience through personalization!
The selection screens guide users to choose a game symbol and display name, both required. If a user tries to move to the next screen without making a selection, an error alert will appear.
However, I’m not super rigid, I provide the option to navigate among the selections screens so a user can always go back to change the symbol or display name before gameplay.
From the name selection screen, you can go back to the symbol selection screen (the first screen).
Tip: Casually remind a user what symbol they chose on name selection for improved user experience!
On the game-on screen, there’s the option to back to name selection to change your name. Here, again, the user can see their selection of the prior screen through the displaying content.
Hint: The computer name is dynamically chosen, the user has nothing to do on their end.
There’s, also, the option to go back to the start page by exiting the selection screens altogether from any screen. A full navigation experience is going on despite the simplicity of these screens!
Game Screen
Finally, the game screen where we’re presented with the gaming board and all of the game action.
First, notice how the click of “Game On” on the last selection screen smoothly guides directly to the game board. Here I employ the useRef
and useEffect
hooks to scroll into view the game board.
It all comes down to:
useEffect( ()=>{
navWrapRef.current.scrollIntoView({ behavior: 'smooth' });
}, [startGame]);
Second, note the game nav. This is separate from the board itself and it provides that crucial navigation component for the user to (a) reset the game board by clearing out any existing gameplay OR (b) end the game and be redirected to the start page.
There’s a lot of state management. Any sort of game will require lots of dexterity when it comes to managing the states of a variety of variables other than the gameplay itself.
Since this is a relatively low-impact game, I opt to use useContext
to manage the states.
For more
useContext
see How To Build A Really Stunning Quote App With React.
const GameContext = createContext({
isGameOn: false,
setIsGameOn: () => { },
players: {},
setPlayers: () => { },
showBoard: false,
setShowBoard: () => { },
startGame: false,
setStartGame: () => { },
resetBoard: false,
setResetBoard: () => { }
});
I use useContext
in conjunction with useState for cases like managing the endGame state to display the scoreboard.
Time to show your skills with gameplay
Ready to play? Let’s begin by setting up the game board which displays the players’ info, like their names and symbols, along the Tic Tac Toe grid.
Who goes first is determined randomly by the system and a turn is indicated above the game board. An active player is outlined when taking a turn.
Most of the core game logic rests in the computerMove
function. The code creates an array (emptyCells
) that contains the indices of all empty cells in the currentBoard
array. It uses the reduce()
method to iterate over the currentBoard
array, pushing the index of each empty cell to the accumulator array if the cell is empty. Finally, it returns the accumulator array containing the indices of all empty cells.
const emptyCells = currentBoard.reduce((acc, cell, index) => {
if (!cell) acc.push(index);
return acc;
}, []);
Tip 👇
We’re checking if
!cell
is true, which means the cell is falsy (eithernull
,undefined
,0
,''
,false
, orNaN
).
Now, computerMove
takes a copy of the board. By using the spread operator [...board]
, we create a new array with the same elements as board
, ensuring that the computerMove
function always receives the most up-to-date version of the board state.
However, note that in the checkWinner
function, we don’t need to create a copy of the board using [...board]
because we’re only reading the board state and not modifying it. Therefore, we can directly pass board
to the checkWinner
function.
Moving on, let’s take a look at the behind-the-scenes of the reset/rematch logic.
const resetGameBoard = useCallback(() => {
// console.log('Resetting board...');
setResetBoard(false);
resetGame();
}, []);
I’m using the useCallback
hook in tandem with useEffect
to ensure the resetGame
callback is only triggered when the resetBoard
flag is set to true.
useEffect(() => {
// console.log('Running reset')
if (resetBoard) {
resetGameBoard();
setIsCellActive(false);
cell.forEach(item=>item.classList.remove(s.active));
setCell([]);
} else {
return;
}
}, [resetBoard, resetGameBoard]);
When only using useEffect
, the state change of resetBoard
, regardless of true or false, triggers a component re-render resulting in a “glitch”.
In using useCallback
, I memoize the resetGame
function and use the useEffect
to trigger the reset logic only when the reset
state changes to true
.
As said previously, gameplay involves lots of logic and state management which is to be expected considering it’s what makes a game a game 💁♀️
It’s a wrap
And there you have it, fellow THT mates, one futuristic Tic Tac Toe game with React.js is done! Honestly, building a game is the best way to thoroughly put your state management skills to the test.
As a quick overview, in this tutorial, we built a Tic Tac Toe game that’s computer vs player with React.js. The project consists of a start page, selection screens (for player info), and a game screen. On the game screen, we have:
- a navigation bar with reset and done action buttons
- display of player and gameplay status
- the game board with keyboard navigation enabled
It’s a simple game with plenty of challenges for any developer. The result? An awesome game you can share and showcase.
All source code is available on GitHub. See ya on the next one 🕶️