Day 1: Building a Customizable Tic Tac Toe with Socket.io - Set Your Own Board Size and Play Online!
Welcome to Day 1 of building customizable Tic Tac Toe game! Today, I’m focusing on setting up the basics with an Pass & Play mode, where players can select their own board size and play on the same device. My goal for Day 1 is to create a smooth, flexible gaming experience that lets users play with different grid sizes, moving beyond the classic 3x3 setup to something even more challenging, like a 5x5 board. By the end of this post, you’ll see how I built a fully functional, Pass & Play version of the game using JavaScript and React. In Day 2, I’ll take things to the next level by adding Connect & Play with Socket.io!
This is the folder structure of the src
directory in this Tic Tac Toe React app, designed to keep the project modular, organized, and scalable.
Components
: This folder contains the building blocks of the UI, like reusable buttons, grid cells, and other elements that appear throughout the game. By organizing components here, I can easily reuse them across different parts of the app, keeping the code DRY (Don't Repeat Yourself).hooks
: This folder stores custom React hooks, which help handle specific logic or state management separately from the main components. By managing complex logic with hooks, I can keep the main components focused on rendering and UI, while hooks take care of specific behavior or side effects, like managing the game state.layout
: Thelayout
folder is where layout components reside, such as RootLayout and any structural components that give shape to the app. These elements ensure that each page has a consistent look and feel.Page
: Each main screen or view of the app, such as the home screen or game board, can be found in thePage
folder. By storing larger, page-specific components here, it keeps them separate from smaller, reusable components, making it easier to work on individual pages without interference.store
: This folder is dedicated to global state management, using tools like Redux. The centralizedstore
allows data and state to be easily shared across the app, helping manage the game’s state efficiently.utils
: Utility functions or helper methods are organized inutils
. These includes mathematical functions, constants, and other logic needed across the app. This folder makes it easy to access shared functions without duplicating code.
Each folder in src
serves a specific purpose, keeping the app’s structure clear and organized as the project grows. This setup makes development faster and maintenance easier, as each part of the app is logically separated and easy to navigate.
import { useMemo, useState } from "react"
import intializeBoard from "../utils/initializeBoard"
import generateWinningPatterns from "../utils/generateWinningPatterns"
function useTicTacToe(boardSize){
const [board,setBoard]=useState(intializeBoard(boardSize))
const [isX,setIsX]=useState(true)
const winningPatterns=useMemo(()=>generateWinningPatterns(boardSize),[])
function calculateWinner(){
for(let i=0;i<winningPatterns.length;i++){
let winnerFlag=true
for(let j=0;j<winningPatterns[i].length-1;j++){
if(!board[winningPatterns[i][j]]){
winnerFlag=false
break
}
if(board[winningPatterns[i][j]]!==board[winningPatterns[i][j+1]]){
winnerFlag=false
break
}
}
if(winnerFlag)return board[winningPatterns[i][0]]
}
return null
}
function statusMessage(){
const winner=calculateWinner()
if(winner)return `Player ${winner} is winner`
if(!board.includes(null))return "Game is draw"
return `Player ${isX ? "X" : "O"} turn`
}
function handleClick(index){
if(calculateWinner())return
if(board[index])return
const newBoard=[...board]
newBoard[index]=isX ? "X" : "O"
setBoard(newBoard)
setIsX(!isX)
}
return {board,statusMessage,handleClick}
}
export default useTicTacToe
The useTicTacToe
hook is designed to manage the state and logic of Tic Tac Toe game. Here's a breakdown of its functionality:
State Initialization:
board
holds the current state of the game (array of X, O, or null).isX
indicates whose turn it is (true for X, false for O).
Winning Patterns:
winningPatterns
is generated usinggenerateWinningPatterns
, which defines the possible ways to win based on the board size.
calculateWinner Function:
- This checks if there’s a winner by iterating through the
winningPatterns
. If all positions in a pattern are filled with the same player's mark (X or O), it returns the winner. If not, it returnsnull
.
- This checks if there’s a winner by iterating through the
statusMessage Function:
- This function generates a message based on the game's state. It announces the winner, indicates a draw if the board is full, or prompts the current player’s turn.
handleClick Function:
- This function handles player moves. If the game is over or the clicked cell is already filled, it returns early. Otherwise, it updates the board and toggles the turn between "X" and "O".
Overall, this hook encapsulates the game logic and state management for Tic Tac Toe game.
function generateWinningPatterns(boardSize){
const winningPattern=[]
let count=0
let tempSize=boardSize
for(let i=0;i<boardSize;i++){
let j=tempSize-boardSize
const pattern1Arr=[]
const pattern2Arr=[]
while(j<tempSize){
pattern1Arr.push(j)
j++
}
let k=count
while(k<=(boardSize*boardSize+count)-boardSize){
pattern2Arr.push(k)
k+=boardSize
}
winningPattern.push(pattern1Arr)
winningPattern.push(pattern2Arr)
tempSize+=boardSize
if(count>=2){
count++
continue
}
const pattern3Arr=[]
let val=count ? 0 : boardSize-1
let valToInc=count ? boardSize+1 : boardSize-1
let m=0
while(m<boardSize){
pattern3Arr.push(val)
val+=valToInc
m++
}
winningPattern.push(pattern3Arr)
count++
}
return winningPattern
}
export default generateWinningPatterns
This function, generateWinningPatterns
, creates winning patterns for a tic-tac-toe board of variable size. Here’s how it works:
Initialize Variables:
winningPattern
holds all winning combinations,count
tracks the columns and diagonal entries, andtempSize
increments to determine row boundaries.Horizontal Pattern:
pattern1Arr
stores indexes for each row. Starts atj = tempSize - boardSize
, incrementsj
to collect indexes across a row.Vertical Pattern:
pattern2Arr
stores column indexes. Starts atk = count
, increments byboardSize
for column cells. Bothpattern1Arr
andpattern2Arr
are saved towinningPattern
.Diagonal Patterns (pattern3Arr): Diagonal patterns are handled by
count
. Only two diagonals exist, sopattern3Arr
is populated for two cases:From top-left to bottom-right with increment by
boardSize + 1
.From top-right to bottom-left with increment by
boardSize - 1
.
Count Management: The
count
variable increments columns in vertical patterns (e.g.,[0,3,6]
,[1,4,7]
) and limits diagonal generation to two patterns.
This approach ensures all horizontal, vertical, and diagonal patterns are generated dynamically based on boardSize
.
import { useSelector } from "react-redux"
import useTicTacToe from "../hooks/useTicTacToe"
import "./gameBoardPage.css"
function GameBoardPage(){
const {boardSize}=useSelector(store=>store.gameOptionReducer)
const {statusMessage,handleClick,board}=useTicTacToe(boardSize)
return(
<div>
<p>{statusMessage()}</p>
<div style={{gridTemplateColumns:`repeat(${boardSize},50px)`}} className="grid-container">
{board.map((content,index)=><button key={index} onClick={()=>handleClick(index)}>{content}</button>)}
</div>
</div>
)
}
export default GameBoardPage
The GameBoardPage
functional component renders a dynamic tic-tac-toe board based on the selected boardSize
. Here’s a breakdown:
State and Actions: It uses
useSelector
to getboardSize
from the Redux store (gameOptionReducer
). It also usesuseTicTacToe
, a custom hook, to manage game state, providingstatusMessage
,handleClick
, andboard
.Status Message:
statusMessage()
displays game updates like the current player's turn, win status or draw.Dynamic Grid: The
gridTemplateColumns
property is set to a repeat pattern based onboardSize
, defining a flexible grid layout for the board, with each cell set to 50x50 pixels.Board Mapping: The
board
array, containing the state of each cell (e.g., 'X' or 'O'), maps to individual buttons. Each button triggershandleClick
with its index, handling cell clicks and updates.
Overall, GameBoardPage
dynamically renders the board and handles user interactions, making it scalable for any board size.
CONCLUSION:
By the end of Day 1, you’ll have a customizable, Pass & Play tic-tac-toe game with the freedom to choose board sizes that best suit your playstyle—from classic 3x3 games to more challenging grids like 5x5. This foundation will set me up perfectly for Day 2, where I’ll enhance the experience with real-time online play using Socket.io, allowing players to challenge friends remotely. Let’s dive into creating an engaging, flexible tic-tac-toe game!
Subscribe to my newsletter
Read articles from Tanmay Bhansali directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by