Skip to content

Commit c12afae

Browse files
authored
Tic Tac Toe
1 parent 297c013 commit c12afae

File tree

3 files changed

+301
-0
lines changed

3 files changed

+301
-0
lines changed

Tic Tac Toe/index.html

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Tic tac toe</title>
8+
<link rel="stylesheet" href="style.css" />
9+
<script defer src="./script.js"></script>
10+
</head>
11+
<body>
12+
<div class="container">
13+
<header>
14+
<h1>Tic Tac Toe</h1>
15+
<div class="game-status d-none won">
16+
<h3 class="game-status-title">You Won</h3>
17+
<button class="btn-primary">Restart</button>
18+
</div>
19+
</header>
20+
<div class="board">
21+
<div class="cell"></div>
22+
<div class="cell"></div>
23+
<div class="cell"></div>
24+
<div class="cell"></div>
25+
<div class="cell"></div>
26+
<div class="cell"></div>
27+
<div class="cell"></div>
28+
<div class="cell"></div>
29+
<div class="cell"></div>
30+
</div>
31+
</div>
32+
</body>
33+
</html>

Tic Tac Toe/script.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
const boardEl = document.querySelector(".board");
2+
const cellsEl = [...document.querySelectorAll(".cell")];
3+
const gameStatusEl = document.querySelector(".game-status");
4+
5+
const CLASS_X = "x";
6+
const CLASS_CIRCLE = "circle";
7+
8+
const WINNING_COMBINATIONS = [
9+
[0, 1, 2],
10+
[3, 4, 5],
11+
[6, 7, 8],
12+
[0, 3, 6],
13+
[1, 4, 7],
14+
[2, 5, 8],
15+
[2, 4, 6],
16+
[0, 4, 8],
17+
];
18+
19+
const xPositions = [];
20+
21+
cellsEl.forEach((cell) =>
22+
cell.addEventListener("click", handleClick, { once: true })
23+
);
24+
25+
function handleClick(e) {
26+
const currentXCell = e.target;
27+
28+
updateCell(currentXCell, CLASS_X);
29+
30+
xPositions.push(cellsEl.indexOf(currentXCell));
31+
32+
const xWins = checkWin(CLASS_X);
33+
34+
xWins ? showGameModal(`won`) : addO();
35+
}
36+
37+
function addO() {
38+
const position = getOPositon();
39+
40+
const currentOCell = cellsEl[position];
41+
42+
if (currentOCell) {
43+
updateCell(currentOCell, CLASS_CIRCLE);
44+
currentOCell.removeEventListener("click", handleClick);
45+
const oWins = checkWin(CLASS_CIRCLE);
46+
47+
if (oWins) showGameModal(`lost`);
48+
}
49+
}
50+
51+
function updateCell(cell, currentClass) {
52+
cell.classList.add(currentClass);
53+
}
54+
55+
function getOPositon() {
56+
const xWinningCombinations = WINNING_COMBINATIONS.filter(
57+
(column) =>
58+
xPositions.some((i) => column.includes(i)) &&
59+
!column.some((i) => cellsEl[i].classList.contains(CLASS_CIRCLE))
60+
);
61+
62+
const emptyCells = WINNING_COMBINATIONS.filter((i) =>
63+
i.some((v) => !cellsEl[v].classList.contains(CLASS_CIRCLE))
64+
);
65+
66+
const xPositionArr = xWinningCombinations
67+
.map((column) =>
68+
column.filter((i) => !cellsEl[i].classList.contains(CLASS_X))
69+
)
70+
.sort((a, b) => a.length - b.length);
71+
72+
const position = xPositionArr[0]?.[getRandomNumber(xPositionArr[0]?.length)];
73+
74+
if (!position && position !== 0) showGameModal(`draw`);
75+
76+
return position;
77+
}
78+
79+
function checkWin(currentClass) {
80+
return WINNING_COMBINATIONS.some((column) =>
81+
column.every((i) => cellsEl[i].classList.contains(currentClass))
82+
);
83+
}
84+
85+
function stopGame() {
86+
cellsEl.forEach((cell) => cell.removeEventListener("click", handleClick));
87+
boardEl.classList.add("game-end");
88+
}
89+
90+
function showGameModal(gameStatus) {
91+
const gameStatusTitle = gameStatusEl.querySelector(".game-status-title");
92+
const restartBtn = gameStatusEl.querySelector(".btn-primary");
93+
94+
const gameStatues = {
95+
won: { className: "won", text: "You Won!" },
96+
lost: { className: "lost", text: "Oops! You Lost" },
97+
draw: { className: "draw", text: "It's a draw" },
98+
};
99+
100+
const {
101+
[gameStatus]: { className, text },
102+
} = gameStatues;
103+
104+
gameStatusEl.classList.add(className);
105+
gameStatusEl.style.display = "flex";
106+
gameStatusTitle.textContent = text;
107+
stopGame();
108+
109+
restartBtn.addEventListener("click", () => window.location.reload());
110+
}
111+
112+
// Helper functions
113+
114+
function getRandomNumber(limit) {
115+
return Math.floor(Math.random() * limit);
116+
}

Tic Tac Toe/style.css

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
:root {
2+
--size: 160px;
3+
}
4+
5+
*,
6+
*::after,
7+
*::before {
8+
box-sizing: border-box;
9+
}
10+
11+
body {
12+
margin: 0;
13+
background: #f4f4f4;
14+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
15+
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
16+
display: flex;
17+
justify-content: center;
18+
}
19+
20+
header {
21+
display: flex;
22+
align-items: center;
23+
justify-content: space-between;
24+
}
25+
26+
header h1 {
27+
color: #222b45;
28+
}
29+
30+
.game-status {
31+
display: flex;
32+
align-items: center;
33+
gap: 1.5rem;
34+
display: none;
35+
}
36+
37+
.game-status > * {
38+
margin: 0;
39+
}
40+
41+
.game-status .game-status-title {
42+
font-size: 1.5rem;
43+
}
44+
45+
.game-status.won .game-status-title {
46+
color: #198754;
47+
}
48+
49+
.game-status.lost .game-status-title {
50+
color: #dc3545;
51+
}
52+
53+
.game-status.draw .game-status-title {
54+
color: #ffc107;
55+
}
56+
57+
.btn-primary {
58+
display: inline-block;
59+
font-weight: 400;
60+
line-height: 1.5;
61+
text-align: center;
62+
cursor: pointer;
63+
padding: 0.375rem 0.75rem;
64+
font-size: 1rem;
65+
border-radius: 0.25rem;
66+
color: #fff;
67+
background-color: #0d6efd;
68+
border: none;
69+
}
70+
71+
.board {
72+
display: grid;
73+
grid-template-columns: repeat(3, minmax(var(--size), 1fr));
74+
grid-template-rows: repeat(3, minmax(var(--size), 1fr));
75+
gap: 0.5rem;
76+
}
77+
78+
.board .cell {
79+
border: 0.0625rem solid #e4e9f2;
80+
background: #fff;
81+
border-radius: 0.25rem;
82+
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
83+
cursor: pointer;
84+
position: relative;
85+
}
86+
87+
.board:not(.game-end) .cell:not(.x):not(.circle):hover {
88+
background-color: #f0f0f0;
89+
}
90+
91+
.cell.x::before,
92+
.board:not(.game-end) .cell:hover:not(.x):not(.circle):before {
93+
content: "";
94+
position: absolute;
95+
display: flex;
96+
background: red;
97+
width: calc(var(--size) * 0.6);
98+
height: calc(var(--size) * 0.6);
99+
top: 50%;
100+
left: 50%;
101+
transform: translate(-50%, -50%);
102+
clip-path: polygon(
103+
20% 0%,
104+
0% 20%,
105+
30% 50%,
106+
0% 80%,
107+
20% 100%,
108+
50% 70%,
109+
80% 100%,
110+
100% 80%,
111+
70% 50%,
112+
100% 20%,
113+
80% 0%,
114+
50% 30%
115+
);
116+
background-color: #4158d0;
117+
background-image: linear-gradient(
118+
43deg,
119+
#4158d0 0%,
120+
#c850c0 46%,
121+
#ffcc70 100%
122+
);
123+
}
124+
125+
.board .cell:hover:not(.x):not(.circle)::before {
126+
opacity: 0.3;
127+
}
128+
129+
.board .cell.circle::after,
130+
.board .cell.circle::before {
131+
content: "";
132+
position: absolute;
133+
width: calc(var(--size) * 0.7);
134+
height: calc(var(--size) * 0.7);
135+
top: 50%;
136+
left: 50%;
137+
transform: translate(-50%, -50%);
138+
background-color: #f7b42c;
139+
background-image: linear-gradient(315deg, #f7b42c 0%, #fc575e 74%);
140+
border-radius: 50%;
141+
}
142+
143+
.board .cell.circle::before {
144+
width: calc(var(--size) * 0.45);
145+
height: calc(var(--size) * 0.45);
146+
background: #fff;
147+
z-index: 2;
148+
}
149+
150+
.cell.active {
151+
background: turquoise;
152+
}

0 commit comments

Comments
 (0)