Welcome to our tutorial on creating an advanced Tic-Tac-Toe game using plain JavaScript, HTML, and CSS. Tic-tac-toe, also known as Noughts and Crosses, is a timeless game known for its simplicity and depth. This web-based version will involve interactive elements, allowing players to choose squares on a grid and aim to form a straight line of their symbol (X or O) while preventing their opponent from doing the same.
Project Specifications
User Interaction: Players will interact with the game board by clicking squares to place their symbols.
Game Logic: Implement logic to determine win conditions, draw conditions, and turn switching.
UI/UX: Create a user interface that is responsive and intuitive, with visual cues for the current turn, win, or draw.
Restart Feature: Include a button to restart the game once a round is completed.
Setup Create Project
Workspace: Name your folder JavaScript-Tic-Tac-Toe.
Project Files: Within this folder, create three files: index.html, style.css, and script.js.
Building the Game
1. HTML Structure (index.html)
Outline the basic HTML structure. Include a div with class "board" that contains 9 div elements, each representing a cell of the Tic Tac Toe grid. Add a "winning-message" div to display the game outcome and a restart button.
Open the index.html and add the following code to it:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="style.css">
<script src="script.js" defer></script>
<title>Document</title>
</head>
<body>
<div class="board" id="board">
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
</div>
<div class="winning-message" id="winningMessage">
<div data-winning-message-text></div>
<button id="restartButton">Restart</button>
</div>
</body>
</html>
2. Styling (style.css)
- Style the game board and cells to visually represent a Tic Tac Toe grid.
- Apply styles to differentiate between X and O marks, hover effects, and the winning message overlay.
- Use CSS variables for easy theme changes or adjustments.
*, *::after, *::before {
box-sizing: border-box;
}
:root {
--cell-size: 100px;
--mark-size: calc(var(--cell-size) * .9);
}
body {
margin: 0;
}
.board {
width: 100vw;
height: 100vh;
display: grid;
justify-content: center;
align-content: center;
justify-items: center;
align-items: center;
grid-template-columns: repeat(3, auto)
}
.cell {
width: var(--cell-size);
height: var(--cell-size);
border: 1px solid black;
display: flex;
justify-content: center;
align-items: center;
position: relative;
cursor: pointer;
}
.cell:first-child,
.cell:nth-child(2),
.cell:nth-child(3) {
border-top: none;
}
.cell:nth-child(3n + 1) {
border-left: none;
}
.cell:nth-child(3n + 3) {
border-right: none;
}
.cell:last-child,
.cell:nth-child(8),
.cell:nth-child(7) {
border-bottom: none;
}
.cell.x,
.cell.circle {
cursor: not-allowed;
}
.cell.x::before,
.cell.x::after,
.cell.circle::before {
background-color: black;
}
.board.x .cell:not(.x):not(.circle):hover::before,
.board.x .cell:not(.x):not(.circle):hover::after,
.board.circle .cell:not(.x):not(.circle):hover::before {
background-color: lightgrey;
}
.cell.x::before,
.cell.x::after,
.board.x .cell:not(.x):not(.circle):hover::before,
.board.x .cell:not(.x):not(.circle):hover::after {
content: '';
position: absolute;
width: calc(var(--mark-size) * .15);
height: var(--mark-size);
}
.cell.x::before,
.board.x .cell:not(.x):not(.circle):hover::before {
transform: rotate(45deg);
}
.cell.x::after,
.board.x .cell:not(.x):not(.circle):hover::after {
transform: rotate(-45deg);
}
.cell.circle::before,
.cell.circle::after,
.board.circle .cell:not(.x):not(.circle):hover::before,
.board.circle .cell:not(.x):not(.circle):hover::after {
content: '';
position: absolute;
border-radius: 50%;
}
.cell.circle::before,
.board.circle .cell:not(.x):not(.circle):hover::before {
width: var(--mark-size);
height: var(--mark-size);
}
.cell.circle::after,
.board.circle .cell:not(.x):not(.circle):hover::after {
width: calc(var(--mark-size) * .7);
height: calc(var(--mark-size) * .7);
background-color: white;
}
.winning-message {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, .9);
justify-content: center;
align-items: center;
color: white;
font-size: 5rem;
flex-direction: column;
}
.winning-message button {
font-size: 3rem;
background-color: white;
border: 1px solid black;
padding: .25em .5em;
cursor: pointer;
}
.winning-message button:hover {
background-color: black;
color: white;
border-color: white;
}
.winning-message.show {
display: flex;
}
3. JavaScript Logic (script.js)
const X_CLASS = 'x'
const CIRCLE_CLASS = 'circle'
const WINNING_COMBINATIONS = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
]
const cellElements = document.querySelectorAll('[data-cell]')
const board = document.getElementById('board')
const winningMessageElement = document.getElementById('winningMessage')
const restartButton = document.getElementById('restartButton')
const winningMessageTextElement = document.querySelector('[data-winning-message-text]')
let circleTurn
startGame()
restartButton.addEventListener('click', startGame)
function startGame() {
circleTurn = false
cellElements.forEach(cell => {
cell.classList.remove(X_CLASS)
cell.classList.remove(CIRCLE_CLASS)
cell.removeEventListener('click', handleClick)
cell.addEventListener('click', handleClick, { once: true })
})
setBoardHoverClass()
winningMessageElement.classList.remove('show')
}
function handleClick(e) {
const cell = e.target
const currentClass = circleTurn ? CIRCLE_CLASS : X_CLASS
placeMark(cell, currentClass)
if (checkWin(currentClass)) {
endGame(false)
} else if (isDraw()) {
endGame(true)
} else {
swapTurns()
setBoardHoverClass()
}
}
function endGame(draw) {
if (draw) {
winningMessageTextElement.innerText = 'Draw!'
} else {
winningMessageTextElement.innerText = `${circleTurn ? "O's" : "X's"} Wins!`
}
winningMessageElement.classList.add('show')
}
function isDraw() {
return [...cellElements].every(cell => {
return cell.classList.contains(X_CLASS) || cell.classList.contains(CIRCLE_CLASS)
})
}
function placeMark(cell, currentClass) {
cell.classList.add(currentClass)
}
function swapTurns() {
circleTurn = !circleTurn
}
function setBoardHoverClass() {
board.classList.remove(X_CLASS)
board.classList.remove(CIRCLE_CLASS)
if (circleTurn) {
board.classList.add(CIRCLE_CLASS)
} else {
board.classList.add(X_CLASS)
}
}
function checkWin(currentClass) {
return WINNING_COMBINATIONS.some(combination => {
return combination.every(index => {
return cellElements[index].classList.contains(currentClass)
})
})
}
Comments
Post a Comment
Leave Comment