diff --git a/index.html b/index.html index 48ac21e..43b2ab2 100644 --- a/index.html +++ b/index.html @@ -1,37 +1,62 @@ - - - - - - Tic-Tac-Toe - - -

Tic-Tac-Toe

-

Player 1 Turn

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

Tic-Tac-Toe

+
-
-
-

Result

- -
-
- - + + + + + + + +

Player 1 Turn

+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+
+

Result

+ +
+
+ + + diff --git a/index.js b/index.js index f4b831f..1ba24cd 100644 --- a/index.js +++ b/index.js @@ -1,148 +1,197 @@ -var cube = document.getElementsByClassName("box"); -var count = 0; +let mode = "pvp"; // "pvp" = player vs player, "pvc" = player vs computer +let cube = document.getElementsByClassName("box"); +let count = 0; let turn = 1; let green = "#5ef141"; +let humanMoves = []; +let computerMoves = []; +let magicSquare = [8, 1, 6, 3, 5, 7, 4, 9, 2]; // Magic square mapping + +// Disable mode selection after the first click +let gameStarted = false; function func(ths) { + if (!gameStarted) { + // Disable mode selection radios + document.querySelectorAll('input[name="mode"]').forEach(r => r.disabled = true); + gameStarted = true; + } + + if (ths.textContent) return; // Already clicked + + if (mode === "pvp") { + twoPlayerMove(ths); + } else if (mode === "pvc") { + playerMove(ths); + } +} + +function twoPlayerMove(ths) { let playerTurn = document.getElementsByClassName("player_turn"); - if (turn === 1) playerTurn[0].innerHTML = "Player 2 Turn"; - else if (turn === 2) playerTurn[0].innerHTML = "Player 1 Turn"; - ths.classList.add("click-disable"); ths.classList.add("click-disable"); count++; - if (count % 2 == 0) { - ths.innerHTML = "X"; - ths.accessKey = 1; - turn = 1; - } else { + + if (count % 2 !== 0) { + // Player 1 (O) ths.innerHTML = "O"; ths.accessKey = 0; - turn = 2; + playerTurn[0].innerHTML = "Player 2 Turn"; + } else { + // Player 2 (X) + ths.innerHTML = "X"; + ths.accessKey = 1; + playerTurn[0].innerHTML = "Player 1 Turn"; + } + + checkResult(); +} + +function playerMove(ths) { + ths.innerHTML = "O"; + ths.accessKey = 0; + ths.classList.add("click-disable"); + let idx = Array.from(cube).indexOf(ths); + humanMoves.push(magicSquare[idx]); + count++; + + if (checkWin(humanMoves)) { + endGame("Human (O) wins!!", humanMoves); + return; + } + + if (count === 9) { + endGame("It's a Draw!!"); + return; + } + + setTimeout(computerMove, 200); +} + +function computerMove() { + let moveIdx = findBestMove(); + cube[moveIdx].innerHTML = "X"; + cube[moveIdx].accessKey = 1; + cube[moveIdx].classList.add("click-disable"); + computerMoves.push(magicSquare[moveIdx]); + count++; + + if (checkWin(computerMoves)) { + endGame("Computer (X) wins!!", computerMoves); + return; + } + + if (count === 9) { + endGame("It's a Draw!!"); + return; } - const a = parseInt(cube[0].accessKey); - const b = parseInt(cube[1].accessKey); - const c = parseInt(cube[2].accessKey); - const d = parseInt(cube[3].accessKey); - const e = parseInt(cube[4].accessKey); - const f = parseInt(cube[5].accessKey); - const g = parseInt(cube[6].accessKey); - const h = parseInt(cube[7].accessKey); - const i = parseInt(cube[8].accessKey); - if ( - a + b + c == 3 || - d + e + f == 3 || - g + h + i == 3 || - a + d + g == 3 || - b + e + h == 3 || - c + f + i == 3 || - a + e + i == 3 || - c + e + g == 3 - ) { - if (a + b + c == 3) { - cube[0].style.backgroundColor = green; - cube[1].style.backgroundColor = green; - cube[2].style.backgroundColor = green; - }else if(d + e + f == 3){ - cube[3].style.backgroundColor = green; - cube[4].style.backgroundColor = green; - cube[5].style.backgroundColor = green; - }else if(g + h + i == 3){ - cube[6].style.backgroundColor = green; - cube[7].style.backgroundColor = green; - cube[8].style.backgroundColor = green; - }else if(a + d + g == 3){ - cube[0].style.backgroundColor = green; - cube[3].style.backgroundColor = green; - cube[6].style.backgroundColor = green; - }else if(b + e + h == 3){ - cube[1].style.backgroundColor = green; - cube[4].style.backgroundColor = green; - cube[7].style.backgroundColor = green; - }else if(c + f + i == 3){ - cube[2].style.backgroundColor = green; - cube[5].style.backgroundColor = green; - cube[8].style.backgroundColor = green; - }else if(a + e + i == 3){ - cube[0].style.backgroundColor = green; - cube[4].style.backgroundColor = green; - cube[8].style.backgroundColor = green; - }else if(c + e + g == 3){ - cube[2].style.backgroundColor = green; - cube[4].style.backgroundColor = green; - cube[6].style.backgroundColor = green; +} + +function findBestMove() { + for (let i = 0; i < 9; i++) { + if (!cube[i].textContent && checkWin(computerMoves.concat(magicSquare[i]))) return i; + } + for (let i = 0; i < 9; i++) { + if (!cube[i].textContent && checkWin(humanMoves.concat(magicSquare[i]))) return i; + } + if (!cube[4].textContent) return 4; + const corners = [0, 2, 6, 8]; + for (let c of corners) if (!cube[c].textContent) return c; + for (let i = 0; i < 9; i++) if (!cube[i].textContent) return i; +} + +// Common win checker +function checkWin(moves) { + if (moves.length < 3) return false; + for (let i = 0; i < moves.length; i++) + for (let j = i + 1; j < moves.length; j++) + for (let k = j + 1; k < moves.length; k++) + if (moves[i] + moves[j] + moves[k] === 15) return true; + return false; +} + +// Highlight winning combination +function highlightWin(moves) { + const winCombos = [ + [0,1,2],[3,4,5],[6,7,8], + [0,3,6],[1,4,7],[2,5,8], + [0,4,8],[2,4,6] + ]; + for (let combo of winCombos) { + let sum = combo.reduce((acc, idx) => { + if (cube[idx].textContent === "O") return acc + 0; + if (cube[idx].textContent === "X") return acc + 1; + return acc; + }, 0); + if (sum === 3 || sum === 0) { + combo.forEach(idx => cube[idx].style.backgroundColor = green); + break; } + } +} + +// Common result checker for PvP +function checkResult() { + const keys = Array.from(cube).map(c => parseInt(c.accessKey)); + const combos = [ + [0,1,2],[3,4,5],[6,7,8], + [0,3,6],[1,4,7],[2,5,8], + [0,4,8],[2,4,6] + ]; - //alert("X wins!! "); - document.getElementById("result").innerText = "Player 2 (X) wins!!"; - on(); - } else if ( - a + b + c == 0 || - d + e + f == 0 || - g + h + i == 0 || - a + d + g == 0 || - b + e + h == 0 || - c + f + i == 0 || - a + e + i == 0 || - c + e + g == 0 - ) { - //alert("O wins!! "); - document.getElementById("result").innerText = "Player 1 (O) wins!!"; - on(); - - if (a + b + c == 0) { - cube[0].style.backgroundColor = green; - cube[1].style.backgroundColor = green; - cube[2].style.backgroundColor = green; - }else if(d + e + f == 0){ - cube[3].style.backgroundColor = green; - cube[4].style.backgroundColor = green; - cube[5].style.backgroundColor = green; - }else if(g + h + i == 0){ - cube[6].style.backgroundColor = green; - cube[7].style.backgroundColor = green; - cube[8].style.backgroundColor = green; - }else if(a + d + g == 0){ - cube[0].style.backgroundColor = green; - cube[3].style.backgroundColor = green; - cube[6].style.backgroundColor = green; - }else if(b + e + h == 0){ - cube[1].style.backgroundColor = green; - cube[4].style.backgroundColor = green; - cube[7].style.backgroundColor = green; - }else if(c + f + i == 0){ - cube[2].style.backgroundColor = green; - cube[5].style.backgroundColor = green; - cube[8].style.backgroundColor = green; - }else if(a + e + i == 0){ - cube[0].style.backgroundColor = green; - cube[4].style.backgroundColor = green; - cube[8].style.backgroundColor = green; - }else if(c + e + g == 0){ - cube[2].style.backgroundColor = green; - cube[4].style.backgroundColor = green; - cube[6].style.backgroundColor = green; + for (let combo of combos) { + const sum = combo.reduce((a, idx) => a + keys[idx], 0); + if (sum === 3) { + combo.forEach(idx => cube[idx].style.backgroundColor = green); + endGame("Player 2 (X) wins!!"); + return; + } else if (sum === 0) { + combo.forEach(idx => cube[idx].style.backgroundColor = green); + endGame("Player 1 (O) wins!!"); + return; } - } else if (a + b + c + d + e + f + g + h + i) { - //alert("Its a Draw !!!"); - document.getElementById("result").innerText = "It's a Draw!!"; - on(); + } + + if (keys.every(k => k === 0 || k === 1) && count === 9) { + endGame("It's a Draw!!"); } } -function on(){ +// Show overlay and stop further moves +function endGame(message, winningMoves=[]) { + document.getElementById("result").innerText = message; + if (winningMoves.length) highlightWin(winningMoves); document.getElementById("overlay").style.display = "block"; + + // Disable all remaining cubes + Array.from(cube).forEach(c => c.classList.add("click-disable")); } -// Resets inmediatly the game if the user has finished. If not it will ask -// for a confirmation alert -function restart(isGameFinished) { - if (isGameFinished) { return reloadWindow() } +// Restart the game properly +function restart(fromOverlay) { + // Reset variables + count = 0; + turn = 1; + humanMoves = []; + computerMoves = []; + gameStarted = false; + + // Clear the board + Array.from(cube).forEach(c => { + c.textContent = ""; + c.accessKey = ""; + c.classList.remove("click-disable"); + c.style.backgroundColor = ""; + }); - if (confirm('Are you sure yo want to restart the game?')) { - window.location.reload(); + // Reset player turn display + document.getElementsByClassName("player_turn")[0].innerText = "Player 1 Turn"; + + // Hide overlay if restart is from popup + if (fromOverlay) { + document.getElementById("overlay").style.display = "none"; } -} -function reloadWindow() { - window.location.reload(); + // Re-enable mode selection + document.querySelectorAll('input[name="mode"]').forEach(r => r.disabled = false); } + diff --git a/style.css b/style.css index 5485eff..ad49fca 100644 --- a/style.css +++ b/style.css @@ -9,9 +9,18 @@ body { height: 100vh; place-content: center; display: grid; + overflow: hidden; /* prevent scrolling */ background: url("https://images.squarespace-cdn.com/content/v1/5a2613f6be42d61192e3e478/1582999868688-PEVK3HU4FN1D9V5UDM4K/Screen+Shot+2020-02-29+at+1.07.46+PM.png?format=1000w"); } +#board { + width: 300px; + height: 300px; + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-gap: 5px; +} + h3 { text-align: center; /* margin-bottom: 20px; */ @@ -88,6 +97,37 @@ button { } } +/* Hide the native radio buttons */ +input[type="radio"] { + display: none; +} + +/* Base style for the labels (buttons) */ +label { + background-color: #006eff; /* blue */ + color: white; + padding: 10px 20px; + border-radius: 6px; + cursor: pointer; + margin-right: 8px; + transition: background-color 0.3s, transform 0.1s; + user-select: none; +} + +/* Hover effect */ +label:hover { + background-color: #0057cc; +} + +/* Active (clicked) effect */ +label:active { + transform: scale(0.97); +} + +/* Selected (checked) radio button */ +input[type="radio"]:checked + label { + background-color: #00b33c; /* green when selected */ +} /* Overlay for result display */ #overlay { position: fixed;