diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..76a44fa --- /dev/null +++ b/css/main.css @@ -0,0 +1,64 @@ +* { + box-sizing: border-box; +} + +body { + height: 100vh; + margin: 0; + font-family: 'Audiowide', cursive; + font-family: 'Open Sans', sans-serif; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +header { + font-size: 4vmin; + color: darkgray; + letter-spacing: 1vmin; +} + +h1 { + color: gray; + font-size: 3vmin; +} + +.cell { + transform: scale(0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.cell:hover { + transform: scale(0.9); + transition: transform 150ms ease-in; +} + +#board { + display: grid; + grid-template-columns: repeat(3, 10vmin); + grid-template-rows: repeat(3, 10vmin); + gap: .2vmin; + margin-top: 2vmin; +} + +#board > div { + border: 0.1vmin solid lightgray; +} + +button { + margin-top: 4vmin; + padding: 2vmin; + font-size: 2vmin; + border-radius: 4vmin; + border: 0.1vmin solid gray; + border-color: darkgray; + color: gray; +} + +button:hover { + color: white; + background-color: darkgray; +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..8dbbd3b --- /dev/null +++ b/index.html @@ -0,0 +1,35 @@ + + + + + + + + + + Tic Tac Toe + + + +
Tic-Tac-Toe Game
+ +

Player One's turn

+ +
+
+
+
+ +
+
+
+ +
+
+
+
+ + + + + \ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..e5f3f03 --- /dev/null +++ b/js/main.js @@ -0,0 +1,179 @@ + /*----- constants -----*/ + // const COLORS = { + // '0': 'white', + // '1': 'black', + // '-1': 'red' + // }; + + const VALUE = { + '0': ' ', + '1': 'X', + '-1': 'O' + }; + + /*----- state variables -----*/ + +// Decide State Variables for Game: +// 1. Keep track of who's turn it is (turn: Player One (1)/ Player Two (-1)) +// 2. Track board state (board: 2D array) +// null -> no player +// Player One (1) / Player Two (-1) -> player at that cell +// 3. Variable for winner (winner: yes or no) +// null -> no winner / in play +// Player One (1) / Player Two (-1) -> winner +// 'T' -> tie + +let board; //this will be an array of 3 column arrays +let turn; //this will be Player One (1) & Player Two's turns (-1) +let winner; // null = no winner; 1 or -1 = winner; 'T' = tie game + + /*----- cached elements -----*/ +const messageEl = document.querySelector('h1'); +const resetButton = document.querySelector('button'); +const markerEls = [...document.querySelectorAll('#board > .cell')]; + + + /*----- event listeners -----*/ +document.querySelectorAll('#board > .cell').forEach(cell => { + cell.addEventListener('click', handleDrop); + }); +resetButton.addEventListener('click', init); + + + /*----- functions -----*/ +init(); + +// Initialize all state, then call render() +function init() { + //to visualize the board's mapping to the DOM, rotate the board array 90 degrees counter-clockwise + board = [ + [0, 0, 0], //this is column 0 + [0, 0, 0], //this is column 1 + [0, 0, 0], //this is column 2 + ]; + turn = 1; + winner = null; + render(); +} + +//in response to user interaction, update all impacted state, then call render(); +function handleDrop(event) { + const cellId = event.target.id; + const columnIdx = parseInt(cellId.charAt(1)); + const rowIdx = parseInt(cellId.charAt(3)); + // guards + if (columnIdx === undefined || rowIdx === undefined || winner) return; + // check if the cell is empty + if (board[columnIdx][rowIdx] !== 0) return; + // update board state with current player value + board[columnIdx][rowIdx] = turn; + // switch turns + turn *= -1; + // check for winner + winner = getWinner(columnIdx, rowIdx); + render(); +} + +//check for winner in board state & return null if no winner, 1/-1 if a player has won, or 'T' if there's a tie +function getWinner(columnIdx, rowIdx) { + const hasTie = board.every(row => row.every(cell => cell !== 0)); + if (hasTie) { + return 'T'; // Return 'T' to indicate a tie + } + return checkVerticalWin(columnIdx, rowIdx) || + checkHorizontalWin(columnIdx, rowIdx) || + checkDiagonalWinNESW(columnIdx, rowIdx) || + checkDiagonalWinNWSE(columnIdx, rowIdx); +} + +function checkVerticalWin(columnIdx, rowIdx) { + const adjacentCountUp = countAdjacent(columnIdx, rowIdx, 0, 1); + const adjacentCountDown = countAdjacent(columnIdx, rowIdx, 0, -1); + return (adjacentCountUp + adjacentCountDown) >= 2 ? board[columnIdx][rowIdx] : null; +} + +function checkHorizontalWin(columnIdx, rowIdx) { + const adjacentCountLeft = countAdjacent(columnIdx, rowIdx, -1, 0); + const adjacentCountRight = countAdjacent(columnIdx, rowIdx, 1, 0); + return (adjacentCountLeft + adjacentCountRight) >= 2 ? board[columnIdx][rowIdx] : null; +} + +function checkDiagonalWinNESW(columnIdx, rowIdx) { + const adjacentCountNE = countAdjacent(columnIdx, rowIdx, 1, 1); + const adjacentCountSW = countAdjacent(columnIdx, rowIdx, -1, -1); + return (adjacentCountNE + adjacentCountSW) >= 2 ? board[columnIdx][rowIdx] : null; +} + +function checkDiagonalWinNWSE(columnIdx, rowIdx) { + const adjacentCountNW = countAdjacent(columnIdx, rowIdx, -1, 1); + const adjacentCountSE = countAdjacent(columnIdx, rowIdx, 1, -1); + return (adjacentCountNW + adjacentCountSE) >= 2 ? board[columnIdx][rowIdx] : null; +} + +function countAdjacent(columnIdx, rowIdx, columnOffset, rowOffset) { + //shortcut variable to the player value + const player = board[columnIdx][rowIdx]; + //track count of adjacent cells with the same player value + let count = 0; + //initialize new coordinates + columnIdx += columnOffset; + rowIdx += rowOffset; + while ( + //ensure columnIdx is within bounds of the board array + board[columnIdx] !== undefined && + board[columnIdx][rowIdx] !== undefined && + board[columnIdx][rowIdx] === player + ) { + count++; + columnIdx += columnOffset; + rowIdx += rowOffset; + } + return count; +} + +// Visualize all state in the DOM +function render() { + renderBoard(); + renderMessage(); + //hide/show UI elements (controls) + renderControls(); +} + +function renderBoard() { + board.forEach(function (columnArr, columnIdx) { + //iterate over the cells in the current column (columnArr) + columnArr.forEach(function(cellValue, rowIdx) { + const cellId = `c${columnIdx}r${rowIdx}`; + const cellEl = document.getElementById(cellId); +// cellEl.style.backgroundColor = COLORS[cellValue]; + cellEl.innerText = VALUE[cellValue]; + }); + }); +} + +function renderMessage() { + if (winner === 'T') { + messageEl.innerText = "It's a tie!!!"; + } else if (winner) { + //message for winner + // messageEl.innerHTML = `${COLORS[winner].toUpperCase()} wins!`; + messageEl.innerHTML = `${VALUE[winner].toUpperCase()} wins!`; + } else { + //message when game is in play + // messageEl.innerHTML = `It's your turn, ${COLORS[turn].toUpperCase()}.`; + messageEl.innerHTML = `It's your turn, ${VALUE[turn].toUpperCase()}.`; + } +} + +function renderControls() { + resetButton.style.visibility = winner ? 'visible' : 'hidden'; + board.forEach(function (columnArr, columnIdx) { + // iterate over the cells in the current column (columnArr) + columnArr.forEach(function(cellValue, rowIdx) { + const cellId = `c${columnIdx}r${rowIdx}`; + const cellEl = document.getElementById(cellId); + // cellEl.style.backgroundColor = COLORS[cellValue]; + cellEl.style.backgroundColor = VALUE[cellValue]; + }); + }); +} \ No newline at end of file