diff --git a/projects/tic-tac-toe/index.html b/projects/tic-tac-toe/index.html index 9851039..a18a969 100644 --- a/projects/tic-tac-toe/index.html +++ b/projects/tic-tac-toe/index.html @@ -1,35 +1,92 @@ - - - -Tic-Tac-Toe - - - -
-

Tic-Tac-Toe

-
- -
- - - -
- -
+ + + + Tic-Tac-Toe + + + + + +
+

X Wins: 0

O Wins: 0

Draws: 0

-
-
+

Mode: player

+
+
+

Tic-Tac-Toe

+
+
+
+
Player 1
+ + +
Player 2
+
+
+ + settings +
+
+
+
+

Play against:

+
+
+

Player

+

Computer

+
+
+
+
- - + + diff --git a/projects/tic-tac-toe/main.js b/projects/tic-tac-toe/main.js index 8db379e..7f7cca0 100644 --- a/projects/tic-tac-toe/main.js +++ b/projects/tic-tac-toe/main.js @@ -1,116 +1,196 @@ document.addEventListener("DOMContentLoaded", () => { - const boardEl = document.getElementById('board'); - const restartBtn = document.getElementById('restartBtn'); - const modeSelect = document.getElementById('modeSelect'); - const themeSelect = document.getElementById('themeSelect'); - const xScoreEl = document.getElementById('xScore'); - const oScoreEl = document.getElementById('oScore'); - const drawScoreEl = document.getElementById('drawScore'); - - let b = Array(9).fill(null); - let turn = 'X'; - let gameOver = false; - let mode = '2P'; - let scores = { X: 0, O: 0, Draw: 0 }; - - const winningCombos = [ - [0,1,2],[3,4,5],[6,7,8], - [0,3,6],[1,4,7],[2,5,8], - [0,4,8],[2,4,6] - ]; - - function checkWin() { - for (const combo of winningCombos) { - const [a,bIndex,c] = combo; - if (b[a] && b[a] === b[bIndex] && b[a] === b[c]) return b[a]; - } - return null; - } + const boardEl = document.getElementById("board"); + const restartBtn = document.getElementById("restartBtn"); + const xScoreEl = document.getElementById("xScore"); + const oScoreEl = document.getElementById("oScore"); + const Currentmode = document.getElementById("Currentmode"); + const drawScoreEl = document.getElementById("drawScore"); + const leftArrow = document.querySelector(".orange-marker"); // Player 1 arrow + const rightArrow = document.querySelector(".pink-marker"); // Player 2 arrow + let b = Array(9).fill(null); + let turn = "X"; + let gameOver = false; + let mode = "2P"; + let scores = { X: 0, O: 0, Draw: 0 }; - function checkDraw() { - return b.every(cell => cell !== null); - } + const winningCombos = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6], + ]; - function render() { - boardEl.innerHTML = ''; - b.forEach((v,i) => { - const btn = document.createElement('button'); - btn.className = 'cell'; - btn.textContent = v || ''; - btn.addEventListener('click', () => move(i)); - boardEl.appendChild(btn); - }); + function checkWin() { + for (const combo of winningCombos) { + const [a, bIndex, c] = combo; + if (b[a] && b[a] === b[bIndex] && b[a] === b[c]) { + b[a] += "w"; + b[bIndex] += "w"; + b[c] += "w"; + return b[a]; + } } + return null; + } - function move(i) { - if (b[i] || gameOver) return; - - b[i] = turn; - const winner = checkWin(); + function checkDraw() { + return b.every((cell) => cell !== null); + } - if (winner) { - alert(`${winner} wins!`); - scores[winner]++; - gameOver = true; - } else if (checkDraw()) { - alert("It's a draw!"); - scores.Draw++; - gameOver = true; + function render() { + boardEl.innerHTML = ""; + b.forEach((v, i) => { + const btn = document.createElement("button"); + if (v && v.includes("w")) { + v = v[0]; + if (v === "X") { + document.querySelector(".one").classList.add("xWin"); + btn.classList.add("xWin"); } else { - turn = turn === 'X' ? 'O' : 'X'; - if (mode === 'AI' && turn === 'O' && !gameOver) aiMove(); + document.querySelector(".two").classList.add("oWin"); + btn.classList.add("oWin"); } + } + btn.classList.add("cell"); - updateScoreboard(); - render(); + btn.textContent = v || ""; + btn.addEventListener("click", () => move(i)); + boardEl.appendChild(btn); + }); + updateTurnIndicator(); + } + function updateTurnIndicator() { + if (turn === "X") { + leftArrow.style.visibility = "visible"; + rightArrow.style.visibility = "hidden"; + } else { + leftArrow.style.visibility = "hidden"; + rightArrow.style.visibility = "visible"; } + } + function move(i) { + if (b[i] || gameOver) return; - // Smart AI - function aiMove() { - let moveIndex = findBestMove('O'); // Win if possible - if (moveIndex === null) moveIndex = findBestMove('X'); // Block X - if (moveIndex === null && b[4] === null) moveIndex = 4; // Take center - if (moveIndex === null) { - const corners = [0,2,6,8].filter(i => b[i] === null); - if (corners.length) moveIndex = corners[Math.floor(Math.random()*corners.length)]; - } - if (moveIndex === null) { - const empty = b.map((v,i) => v===null? i:null).filter(v=>v!==null); - moveIndex = empty[Math.floor(Math.random()*empty.length)]; - } - move(moveIndex); - } + b[i] = turn; + const winner = checkWin(); - function findBestMove(player) { - for (const combo of winningCombos) { - const [a,bIndex,c] = combo; - const line = [b[a], b[bIndex], b[c]]; - if (line.filter(v => v===player).length === 2 && line.includes(null)) { - return combo[line.indexOf(null)]; - } - } - return null; + if (winner) { + scores[winner[0]]++; + gameOver = true; + setTimeout(() => { + alert(`${winner[0]==='X'?"Player 1":"Player 2"} wins!`); + }, 200); + } else if (checkDraw()) { + scores.Draw++; + gameOver = true; + setTimeout(() => { + alert("It's a draw!"); + + }, 200); + } else { + turn = turn === "X" ? "O" : "X"; + + if (mode === "AI" && turn === "O" && !gameOver) aiMove(); } + render(); + updateScoreboard(); + } - function updateScoreboard() { - xScoreEl.textContent = scores.X; - oScoreEl.textContent = scores.O; - drawScoreEl.textContent = scores.Draw; + // Smart AI + function aiMove() { + let moveIndex = findBestMove("O"); // Win if possible + if (moveIndex === null) moveIndex = findBestMove("X"); // Block X + if (moveIndex === null && b[4] === null) moveIndex = 4; // Take center + if (moveIndex === null) { + const corners = [0, 2, 6, 8].filter((i) => b[i] === null); + if (corners.length) + moveIndex = corners[Math.floor(Math.random() * corners.length)]; + } + if (moveIndex === null) { + const empty = b + .map((v, i) => (v === null ? i : null)) + .filter((v) => v !== null); + moveIndex = empty[Math.floor(Math.random() * empty.length)]; } + move(moveIndex); + } - function restart() { - b = Array(9).fill(null); - turn = 'X'; - gameOver = false; - render(); + function findBestMove(player) { + for (const combo of winningCombos) { + const [a, bIndex, c] = combo; + const line = [b[a], b[bIndex], b[c]]; + if ( + line.filter((v) => v === player).length === 2 && + line.includes(null) + ) { + return combo[line.indexOf(null)]; + } } + return null; + } - // Event listeners - restartBtn.addEventListener('click', restart); - modeSelect.addEventListener('change', e => { mode = e.target.value; restart(); }); - themeSelect.addEventListener('change', e => { document.body.className = e.target.value; }); + function updateScoreboard() { + xScoreEl.textContent = scores.X; + oScoreEl.textContent = scores.O; + drawScoreEl.textContent = scores.Draw; + } - // Initial render + function restart() { + b = Array(9).fill(null); + turn = "X"; + gameOver = false; + document.querySelector(".one").classList.remove("xWin") + document.querySelector(".two").classList.remove("oWin") render(); - updateScoreboard(); + } + + // Event listeners + restartBtn.addEventListener("click", restart); + const choices = document.querySelector(".choices"); + const playerMode = document.querySelector(".select-player"); + const compMode = document.querySelector(".select-computer"); + playerMode.addEventListener("click", (e) => { + mode = playerMode.getAttribute("value"); + Currentmode.innerText="Player" + document.querySelector(".start-modal").style.display = "none"; + restart(); + }); + compMode.addEventListener("click", (e) => { + mode = compMode.getAttribute("value"); + Currentmode.innerText="Computer" + + document.querySelector(".start-modal").style.display = "none"; + restart(); + }); + + const modeSettings = document.querySelector(".exit"); + modeSettings.addEventListener("click", () => { + document.querySelector(".start-modal").style.display = "block"; + }); + + + // Initial render + render(); + updateScoreboard(); }); + +// theme toggle +const themeToggle = document.getElementById("themeToggle"); +const body = document.body; + +const currentTheme = localStorage.getItem("theme") || "light"; +if (currentTheme === "dark") { + body.classList.add("dark-mode"); +} + +themeToggle.addEventListener("click", () => { + body.classList.toggle("dark-mode"); + + const theme = body.classList.contains("dark-mode") ? "dark" : "light"; + localStorage.setItem("theme", theme); +}); + diff --git a/projects/tic-tac-toe/styles.css b/projects/tic-tac-toe/styles.css index f4d29e0..ae0421c 100644 --- a/projects/tic-tac-toe/styles.css +++ b/projects/tic-tac-toe/styles.css @@ -1,59 +1,435 @@ +:root { + --primary: #1f1f2f; + --secondary: #4c495f; + --secondary: #4c495f; + --tertiary: #4c495f; + --hover: #87839f; + --player: #15151c; + --text: #f5f5ff; + --marker: #f59972; + --marker2: #d877aa; + --shadow: 0, 0, 0; +} body { - font-family: Arial, sans-serif; - background: #0f0f12; - color: #eef1f8; - margin: 0; - display: flex; - justify-content: center; - align-items: center; - min-height: 100vh; + background-color: #c7c7ea; + font-family: Arial, sans-serif; + color: #040404; + margin: 0; + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; +} +body.dark-mode { + font-family: Arial, sans-serif; + background: #1f1f2f; + color: #eef1f8; + margin: 0; + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; } main { - text-align: center; + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; } .board { - display: grid; - grid-template-columns: repeat(3, 100px); - grid-template-rows: repeat(3, 100px); - gap: 5px; - margin: 20px auto; + display: grid; + grid-template-columns: repeat(3, 100px); + grid-template-rows: repeat(3, 100px); + gap: 8px; + margin: 20px auto; } .cell { - background: #17171c; - border: 2px solid #262631; - border-radius: 8px; - font-size: 3rem; - cursor: pointer; - display: flex; - justify-content: center; - align-items: center; - user-select: none; + background: #4a495e; + border: 2px solid #262631; + border-radius: 2.5vw; + font-size: 4rem; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + user-select: none; + box-shadow: 0 1px 1px rgba(var(--shadow), 0.15), + 0 2px 2px rgba(var(--shadow), 0.15), 0 4px 4px rgba(var(--shadow), 0.15), + 0 8px 8px rgba(var(--shadow), 0.15); + transition: 0.2s; + color: var(--text); } -.controls, .scoreboard { - display: flex; - justify-content: center; - gap: 10px; - margin: 10px 0; +.controls { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; } - -button, select { - padding: 0.5rem 1rem; - border-radius: 5px; - border: none; - cursor: pointer; +.exit { + margin-top: 15px; + color: var(--hover); + transition: 0.2s; + cursor: pointer; +} +.exit:hover { + transform: scale(1.3) rotate(-360deg); +} +.scoreboard { + display: flex; + justify-content: center; + gap: 10px; + margin: 10px 0; } -body.light { - background: #eef1f8; - color: #0f0f12; +button, +select { + border-radius: 5px; + border: none; + cursor: pointer; } body.light .cell { - background: #ffffff; - color: #0f0f12; - border: 1px solid #ccc; + background: #4a495e; + color: #0f0f12; + + border: 1px solid #ccc; +} + +.HeadingContainer { + display: flex; + justify-content: center; + align-items: center; + height: 75px; + padding: 0 20px; + background-color: var(--secondary); + border-radius: 25px; + box-shadow: 0 1px 1px rgba(var(--shadow), 0.15), + 0 2px 2px rgba(var(--shadow), 0.15), 0 4px 4px rgba(var(--shadow), 0.15), + 0 8px 8px rgba(var(--shadow), 0.15); +} +.HeadingContainer h1 { + font-size: 38px; + font-weight: 800; + margin: 0; + text-align: center; + color: var(--text); + background-image: linear-gradient( + to right, + var(--marker) 30%, + var(--marker2) 100% + ); + background-size: 100%; + background-clip: text; + -webkit-background-clip: text; + -moz-background-clip: text; + -webkit-text-fill-color: transparent; + -moz-text-fill-color: transparent; + z-index: 1; +} +.cell:hover { + transform: scale(1.1); + background: var(--hover); +} +.xWin { + background: #ef927f; +} +.xWin:hover { + background: #ef927f; +} +.oWin { + background: #dd7f9f; +} +.oWin:hover { + background: #dd7f9f; +} +.player.oWin { + background: #dd7f9f; +} +.player.xWin { + background: #ef927f; +} +.players { + display: flex; + justify-content: center; + + height: 35px; + line-height: 35px; + gap: 10px; +} +.player { + width: 100px; + height: 35px; + text-align: center; + background: #4a495e; + border-radius: 5px 25px 5px 25px; + cursor: pointer; +} +.player:hover { + transform: scale(1.1); +} +.material-icons { + font-family: "Material Icons"; + font-weight: normal; + font-style: normal; + font-size: 30px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: "liga"; + -webkit-font-smoothing: antialiased; + cursor: pointer; +} +.material-icons.md-36 { + font-size: 40px; +} +.orange-marker { + color: var(--marker); + margin-left: -10px; + transform: scale(0.5); + transform: translateY(-3px); +} +.pink-marker { + visibility: hidden; + color: var(--marker2); + margin-right: -10px; + transform: scale(0.5); + transform: translateY(-3px); +} +#restartBtn { + font-size: 1.3vw; + text-align: center; + font-weight: 600; + width: 80px; + height: 40px; + line-height: 40px; + border-radius: 30px; + transition: 0.2s; + color: var(--primary); + background-image: linear-gradient( + to right, + var(--marker) 0%, + var(--marker2) 51%, + var(--marker) 100% + ); + background-size: 200% auto; +} +#restartBtn:hover { + transform: scale(1.05); + background-position: right center; + transition: 0.3s; +} +/* Theme toggle */ +.theme-toggle { + background: var(--card); + border: 2px solid var(--border, #262631); + border-radius: 50%; + width: 50px; + height: 50px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + position: fixed; + top: 1rem; + right: 1rem; + z-index: 1000; + color: var(--text); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.theme-toggle:hover { + transform: rotate(15deg) scale(1.1); + border-color: var(--accent); + box-shadow: 0 6px 16px rgba(110, 231, 183, 0.2); +} + +.theme-toggle svg { + position: absolute; + transition: all 0.3s ease; +} + +.theme-toggle .sun-icon { + opacity: 0; + transform: rotate(90deg); +} + +.theme-toggle .moon-icon { + opacity: 1; + transform: rotate(0deg); +} + +body:not(.dark-mode) .theme-toggle .sun-icon { + opacity: 1; + transform: rotate(0deg); +} + +body:not(.dark-mode) .theme-toggle .moon-icon { + opacity: 0; + transform: rotate(-90deg); +} +/* start memu options */ +.start-modal { + display: none; + position: fixed; + width: 100%; + height: 100%; + left: 0; + top: 0; + overflow: auto; + background-color: rgba(var(--shadow), 0.75); +} +.start-screen { + border: 1px solid var(--hover); + margin: 0 auto; + margin-top: 200px; + width: 310px; + height: 130px; + background-color: var(--primary); + border-radius: 20px; + padding-top: 23px; +} +.choices { + display: flex; + justify-content: center; + gap: 35px; + cursor: pointer; +} +.select-player { + box-shadow: 0 1px 1px rgba rgba(0, 0, 0, 0.15) #00000026 (var(--shadow), 0.15), + 0 2px 2px rgba(var(--shadow), 0.15), 0 4px 4px rgba(var(--shadow), 0.15), + 0 8px 8px rgba(var(--shadow), 0.15); +} +h3 { + display: block; + font-size: 1.17em; + font-weight: bold; + unicode-bidi: isolate; + /* height: 15px; */ + font-size: 16px; + text-align: center; + color: var(--text); +} +.choices h3 { + background-color: var(--secondary); + width: 110px; + height: 40px; + line-height: 40px; + border-radius: 14px; + transition: 0.2s; +} + +/* Responsiveness */ + +/* Tablets and small laptops */ +@media (max-width: 900px) { + .board { + grid-template-columns: repeat(3, minmax(80px, 22vw)); + } + .cell { + border-radius: 5vw; + font-size: clamp(1.8rem, 10vw, 3rem); + } +} + +/* Phones */ +@media (max-width: 600px) { + .board { + grid-template-columns: repeat(3, minmax(60px, 28vw)); + gap: 2vw; + } + + .cell { + border-radius: 8vw; + font-size: clamp(1.8rem, 10vw, 3rem); + } + + .HeadingContainer { + width: 90%; + padding: 0.5rem; + } + + .theme-toggle { + top: 0.5rem; + right: 0.5rem; + } + + .players { + gap: 1rem; + } + + .start-screen { + width: 90%; + max-width: 300px; + } +} + +/* 🌞 Light mode theme */ +body:not(.dark-mode) { + --primary: #ffffff; + --secondary: #e8e7f5; + --tertiary: #dcdaf0; + --hover: #c4c1e0; + --player: #f2f1fb; + --text: #1e1d2f; + --marker: #823013; + --marker2: #e6a5ca; + --shadow: 0, 0, 0; + + background-color: #fafaff; + color: var(--text); +} + +body:not(.dark-mode) .cell { + background: var(--secondary); + border: 2px solid #ccc; + color: var(--text); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Light mode heading box */ +body:not(.dark-mode) .HeadingContainer { + background-color: var(--secondary); + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1); +} + +/* Light mode player boxes */ +body:not(.dark-mode) .player { + background-color: var(--player); + color: var(--text); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Light mode restart button */ +body:not(.dark-mode) #restartBtn { + color: #f9f9f9; + background-image: linear-gradient( + to right, + var(--marker) 0%, + var(--marker2) 51%, + var(--marker) 100% + ); + background-size: 200% auto; +} +body:not(.dark-mode) .xWin { + background: #45c14f; +} + +body:not(.dark-mode) .oWin { + background: #5b6bc9; +} +body:not(.dark-mode) .player.oWin { + background: #5b6bc9; +} +body:not(.dark-mode) .player.xWin { + background: #45c14f; }