Skip to content

Commit 0526b11

Browse files
committed
Add Tic-Tac-Toe game with AI mode and improved UI
1 parent 77e19dd commit 0526b11

2 files changed

Lines changed: 365 additions & 1 deletion

File tree

web-app/css/styles.css

Lines changed: 183 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1073,4 +1073,186 @@ body {
10731073
.modal-close:hover {
10741074
transform: none;
10751075
}
1076-
}
1076+
}
1077+
1078+
/* =========================
1079+
TIC TAC TOE
1080+
========================= */
1081+
1082+
.tic-tac-toe-board {
1083+
display: grid;
1084+
grid-template-columns: repeat(3, 110px);
1085+
gap: 14px;
1086+
justify-content: center;
1087+
margin: 2rem auto;
1088+
}
1089+
1090+
.cell {
1091+
width: 110px;
1092+
height: 110px;
1093+
border: 2px solid rgba(99, 102, 241, 0.8);
1094+
border-radius: 22px;
1095+
1096+
background:
1097+
linear-gradient(
1098+
145deg,
1099+
rgba(15, 23, 42, 0.95),
1100+
rgba(30, 41, 59, 0.95)
1101+
);
1102+
1103+
color: white;
1104+
font-size: 2.8rem;
1105+
font-weight: 700;
1106+
1107+
cursor: pointer;
1108+
1109+
box-shadow:
1110+
inset 0 2px 10px rgba(255,255,255,0.03),
1111+
0 0 14px rgba(99,102,241,0.18);
1112+
1113+
transition: all 0.25s ease;
1114+
}
1115+
1116+
.cell:hover {
1117+
transform: translateY(-4px) scale(1.04);
1118+
1119+
border-color: #8b5cf6;
1120+
1121+
box-shadow:
1122+
0 0 22px rgba(139,92,246,0.45),
1123+
inset 0 2px 12px rgba(255,255,255,0.06);
1124+
1125+
background:
1126+
linear-gradient(
1127+
145deg,
1128+
rgba(25, 35, 60, 1),
1129+
rgba(37, 50, 80, 1)
1130+
);
1131+
}
1132+
1133+
.cell:active {
1134+
transform: scale(0.96);
1135+
}
1136+
1137+
#ticTacToeStatus {
1138+
text-align: center;
1139+
font-size: 1.35rem;
1140+
font-weight: 600;
1141+
margin-top: 1rem;
1142+
color: var(--text-color);
1143+
}
1144+
1145+
#restartTicTacToe {
1146+
display: block;
1147+
margin: 1.8rem auto 0;
1148+
1149+
background: linear-gradient(
1150+
135deg,
1151+
var(--primary-color),
1152+
var(--secondary-color)
1153+
);
1154+
1155+
color: white;
1156+
border: none;
1157+
padding: 0.9rem 1.6rem;
1158+
border-radius: 14px;
1159+
1160+
font-size: 1rem;
1161+
font-weight: 600;
1162+
1163+
cursor: pointer;
1164+
1165+
box-shadow:
1166+
0 8px 24px rgba(99,102,241,0.3);
1167+
1168+
transition: all 0.25s ease;
1169+
}
1170+
1171+
#restartTicTacToe:hover {
1172+
transform: translateY(-2px);
1173+
1174+
box-shadow:
1175+
0 12px 28px rgba(99,102,241,0.45);
1176+
}
1177+
1178+
.ttt-mode-buttons {
1179+
display: flex;
1180+
justify-content: center;
1181+
gap: 12px;
1182+
margin-top: 1rem;
1183+
margin-bottom: 1rem;
1184+
}
1185+
1186+
.ttt-mode-buttons {
1187+
display: flex;
1188+
justify-content: center;
1189+
gap: 14px;
1190+
margin: 1rem 0 1.5rem;
1191+
}
1192+
1193+
.ttt-mode-buttons button {
1194+
position: relative;
1195+
1196+
padding: 0.8rem 1.4rem;
1197+
1198+
border: 1px solid rgba(255,255,255,0.08);
1199+
border-radius: 14px;
1200+
1201+
background:
1202+
linear-gradient(
1203+
145deg,
1204+
rgba(99,102,241,0.18),
1205+
rgba(139,92,246,0.18)
1206+
);
1207+
1208+
backdrop-filter: blur(10px);
1209+
1210+
color: white;
1211+
1212+
font-size: 0.95rem;
1213+
font-weight: 600;
1214+
letter-spacing: 0.2px;
1215+
1216+
cursor: pointer;
1217+
1218+
transition: all 0.25s ease;
1219+
1220+
box-shadow:
1221+
0 4px 18px rgba(99,102,241,0.18),
1222+
inset 0 1px 0 rgba(255,255,255,0.05);
1223+
}
1224+
1225+
.ttt-mode-buttons button:hover {
1226+
transform: translateY(-2px) scale(1.03);
1227+
1228+
background:
1229+
linear-gradient(
1230+
145deg,
1231+
rgba(99,102,241,0.32),
1232+
rgba(139,92,246,0.32)
1233+
);
1234+
1235+
box-shadow:
1236+
0 8px 24px rgba(99,102,241,0.35),
1237+
0 0 18px rgba(139,92,246,0.22);
1238+
}
1239+
1240+
.ttt-mode-buttons button:active {
1241+
transform: scale(0.96);
1242+
}
1243+
1244+
.ttt-mode-buttons button.active-mode {
1245+
background:
1246+
linear-gradient(
1247+
135deg,
1248+
var(--primary-color),
1249+
var(--secondary-color)
1250+
);
1251+
1252+
border-color: rgba(255,255,255,0.15);
1253+
1254+
box-shadow:
1255+
0 10px 28px rgba(99,102,241,0.45),
1256+
0 0 22px rgba(139,92,246,0.3);
1257+
}
1258+

web-app/js/projects.js

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
function getProjectHTML(projectName) {
44
const projects = {
5+
'tic-tac-toe': getTicTacToeHTML(),
56
'rock-paper-scissor': getRockPaperScissorHTML(),
67
'dice-rolling': getDiceRollingHTML(),
78
'coin-flip': getCoinFlipHTML(),
@@ -29,6 +30,7 @@ function getProjectHTML(projectName) {
2930

3031
function initializeProject(projectName) {
3132
const initializers = {
33+
'tic-tac-toe': initTicTacToe,
3234
'rock-paper-scissor': initRockPaperScissor,
3335
'dice-rolling': initDiceRolling,
3436
'coin-flip': initCoinFlip,
@@ -284,6 +286,186 @@ function initRockPaperScissor() {
284286
}
285287
}
286288

289+
// ============================================
290+
// Tic Tac Toe
291+
// ============================================
292+
293+
function getTicTacToeHTML() {
294+
return `
295+
<div class="project-content">
296+
<h2>🧩 Tic-Tac-Toe</h2>
297+
<div class="ttt-mode-buttons">
298+
<button id="pvpMode" class="mode-btn active-mode">
299+
👥 2 Players
300+
</button>
301+
302+
<button id="aiMode" class="mode-btn">
303+
you vs Computer
304+
</button>
305+
</div>
306+
<div class="game-container">
307+
<div class="tic-tac-toe-board" id="ticTacToeBoard">
308+
<button class="cell" data-cell="0"></button>
309+
<button class="cell" data-cell="1"></button>
310+
<button class="cell" data-cell="2"></button>
311+
<button class="cell" data-cell="3"></button>
312+
<button class="cell" data-cell="4"></button>
313+
<button class="cell" data-cell="5"></button>
314+
<button class="cell" data-cell="6"></button>
315+
<button class="cell" data-cell="7"></button>
316+
<button class="cell" data-cell="8"></button>
317+
</div>
318+
319+
<p id="ticTacToeStatus">Player X's turn</p>
320+
321+
<button id="restartTicTacToe" class="game-btn">
322+
🔄 Restart Game
323+
</button>
324+
</div>
325+
</div>
326+
`;
327+
}
328+
329+
function initTicTacToe() {
330+
const cells = document.querySelectorAll('.cell');
331+
const statusText = document.getElementById('ticTacToeStatus');
332+
const restartBtn = document.getElementById('restartTicTacToe');
333+
const twoPlayerBtn = document.getElementById('twoPlayerMode');
334+
const computerBtn = document.getElementById('computerMode');
335+
336+
let vsComputer = false;
337+
let currentPlayer = 'X';
338+
let board = ['', '', '', '', '', '', '', '', ''];
339+
let gameActive = true;
340+
341+
342+
343+
const pvpBtn = document.getElementById('pvpMode');
344+
const aiBtn = document.getElementById('aiMode');
345+
346+
pvpBtn.addEventListener('click', () => {
347+
vsComputer = false;
348+
349+
pvpBtn.classList.add('active-mode');
350+
aiBtn.classList.remove('active-mode');
351+
352+
resetGame();
353+
statusText.textContent = "2 Player Mode";
354+
});
355+
356+
aiBtn.addEventListener('click', () => {
357+
vsComputer = true;
358+
359+
aiBtn.classList.add('active-mode');
360+
pvpBtn.classList.remove('active-mode');
361+
362+
resetGame();
363+
statusText.textContent = "Playing vs Computer";
364+
});
365+
366+
const winningCombinations = [
367+
[0,1,2],
368+
[3,4,5],
369+
[6,7,8],
370+
[0,3,6],
371+
[1,4,7],
372+
[2,5,8],
373+
[0,4,8],
374+
[2,4,6]
375+
];
376+
377+
function checkWinner() {
378+
for (let combo of winningCombinations) {
379+
const [a, b, c] = combo;
380+
381+
if (
382+
board[a] &&
383+
board[a] === board[b] &&
384+
board[a] === board[c]
385+
) {
386+
statusText.textContent = `🎉 Player ${board[a]} wins!`;
387+
gameActive = false;
388+
return;
389+
}
390+
}
391+
392+
if (!board.includes('')) {
393+
statusText.textContent = "🤝 It's a draw!";
394+
gameActive = false;
395+
}
396+
}
397+
398+
cells.forEach(cell => {
399+
cell.addEventListener('click', () => {
400+
const index = cell.dataset.cell;
401+
402+
if (board[index] || !gameActive) return;
403+
404+
board[index] = currentPlayer;
405+
cell.textContent = currentPlayer;
406+
407+
checkWinner();
408+
409+
if (vsComputer && gameActive && currentPlayer === 'X') {
410+
currentPlayer = 'O';
411+
statusText.textContent = "Computer's turn";
412+
413+
setTimeout(() => {
414+
computerMove();
415+
}, 500);
416+
417+
return;
418+
}
419+
420+
if (gameActive) {
421+
currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
422+
statusText.textContent = `Player ${currentPlayer}'s turn`;
423+
}
424+
});
425+
});
426+
427+
function resetGame() {
428+
board = ['', '', '', '', '', '', '', '', ''];
429+
gameActive = true;
430+
currentPlayer = 'X';
431+
432+
cells.forEach(cell => {
433+
cell.textContent = '';
434+
});
435+
436+
statusText.textContent = "Player X's turn";
437+
}
438+
439+
function computerMove() {
440+
let emptyCells = [];
441+
442+
board.forEach((cell, index) => {
443+
if (cell === '') {
444+
emptyCells.push(index);
445+
}
446+
});
447+
448+
if (emptyCells.length === 0) return;
449+
450+
const randomIndex =
451+
emptyCells[Math.floor(Math.random() * emptyCells.length)];
452+
453+
board[randomIndex] = 'O';
454+
cells[randomIndex].textContent = 'O';
455+
456+
checkWinner();
457+
458+
if (gameActive) {
459+
currentPlayer = 'X';
460+
statusText.textContent = "Player X's turn";
461+
}
462+
}
463+
464+
restartBtn.addEventListener('click', resetGame);
465+
}
466+
467+
468+
287469
// ============================================
288470
// DICE ROLLING
289471
// ============================================

0 commit comments

Comments
 (0)