diff --git a/web/connect-4-game/index.html b/web/connect-4-game/index.html new file mode 100644 index 0000000..2fb8a1d --- /dev/null +++ b/web/connect-4-game/index.html @@ -0,0 +1,26 @@ + + + + + + Connect 4 Game + + + +
+

Connect 4

+
+ + +
+
Current Turn: Player 1
+
+
+ + +
+
+ + + + \ No newline at end of file diff --git a/web/connect-4-game/script.js b/web/connect-4-game/script.js new file mode 100644 index 0000000..1d718e5 --- /dev/null +++ b/web/connect-4-game/script.js @@ -0,0 +1,315 @@ +const ROWS = 6; +const COLS = 7; +let currentPlayer = 1; +let gameBoard = Array(ROWS).fill().map(() => Array(COLS).fill(0)); +let gameMode = 'player'; // 'player' or 'bot' +let gameActive = true; + +function selectMode(mode) { + gameMode = mode; + const buttons = document.querySelectorAll('.mode-button'); + buttons.forEach(btn => btn.classList.remove('selected')); + event.target.classList.add('selected'); + newGame(); +} + +function newGame() { + gameBoard = Array(ROWS).fill().map(() => Array(COLS).fill(0)); + currentPlayer = 1; + gameActive = true; + document.querySelector('.turn-indicator').textContent = 'Current Turn: Player 1'; + createBoard(); +} + +function createBoard() { + const board = document.getElementById('board'); + board.innerHTML = ''; + + for (let col = 0; col < COLS; col++) { + const column = document.createElement('div'); + column.className = 'column'; + column.onclick = () => dropPiece(col); + + for (let row = ROWS - 1; row >= 0; row--) { + const cell = document.createElement('div'); + cell.className = 'cell'; + cell.dataset.row = row; + cell.dataset.col = col; + column.appendChild(cell); + } + board.appendChild(column); + } +} + +function dropPiece(col) { + if (!gameActive) return; + + // Player's move + let row = findLowestEmptyRow(col); + if (row === -1) return; // Column is full + + gameBoard[row][col] = currentPlayer; + updateBoard(); + + if (checkWin(row, col)) { + document.querySelector('.turn-indicator').textContent = `Player ${currentPlayer} wins!`; + gameActive = false; + return; + } + + if (checkDraw()) { + document.querySelector('.turn-indicator').textContent = "It's a draw!"; + gameActive = false; + return; + } + + currentPlayer = currentPlayer === 1 ? 2 : 1; + document.querySelector('.turn-indicator').textContent = `Current Turn: Player ${currentPlayer}`; + + // Bot's move + if (gameMode === 'bot' && currentPlayer === 2 && gameActive) { + setTimeout(() => { + const botCol = getBotMove(); + dropPiece(botCol); + }, 500); + } +} + +function findLowestEmptyRow(col) { + for (let row = 0; row < ROWS; row++) { + if (gameBoard[row][col] === 0) { + return row; + } + } + return -1; +} + +function checkDraw() { + return gameBoard.every(row => row.every(cell => cell !== 0)); +} + +function getBotMove() { + // Create a copy of the board for simulation + function copyBoard() { + return gameBoard.map(row => [...row]); + } + + // Evaluate a position (higher score is better for bot) + function evaluatePosition(board, row, col, player) { + let score = 0; + const directions = [ + [0, 1], // horizontal + [1, 0], // vertical + [1, 1], // diagonal / + [1, -1] // diagonal \ + ]; + + for (let [dx, dy] of directions) { + // Count pieces in both directions + let count = 1; + let spaces = 0; + let blocked = false; + + // Check in positive direction + for (let i = 1; i < 4; i++) { + const newRow = row + dx * i; + const newCol = col + dy * i; + + if (newRow < 0 || newRow >= ROWS || newCol < 0 || newCol >= COLS) { + blocked = true; + break; + } + + if (board[newRow][newCol] === player) { + count++; + } else if (board[newRow][newCol] === 0) { + spaces++; + break; + } else { + blocked = true; + break; + } + } + + // Check in negative direction + for (let i = 1; i < 4; i++) { + const newRow = row - dx * i; + const newCol = col - dy * i; + + if (newRow < 0 || newRow >= ROWS || newCol < 0 || newCol >= COLS) { + blocked = true; + break; + } + + if (board[newRow][newCol] === player) { + count++; + } else if (board[newRow][newCol] === 0) { + spaces++; + break; + } else { + blocked = true; + break; + } + } + + // Score based on the sequence + if (count >= 4) return 100000; // Winning move + if (count === 3 && spaces >= 1 && !blocked) score += 100; // Potential win + if (count === 2 && spaces >= 2 && !blocked) score += 10; // Building sequence + } + + // Bonus for center columns + score += Math.max(0, 3 - Math.abs(3 - col)) * 2; + + return score; + } + + // Try each column and evaluate the resulting position + const moveScores = []; + for (let col = 0; col < COLS; col++) { + const row = findLowestEmptyRow(col); + if (row === -1) { + moveScores.push(-1000000); // Invalid move + continue; + } + + let score = 0; + const boardCopy = copyBoard(); + + // Check immediate win + boardCopy[row][col] = 2; + if (checkWin(row, col)) { + return col; // Winning move, take it immediately + } + + // Check if opponent wins next move + let opponentWinNext = false; + for (let oppCol = 0; oppCol < COLS; oppCol++) { + const oppRow = findLowestEmptyRow(oppCol); + if (oppRow !== -1) { + boardCopy[oppRow][oppCol] = 1; + if (checkWin(oppRow, oppCol)) { + opponentWinNext = true; + if (col === oppCol) { + score += 80; // Block opponent's winning move + } + } + boardCopy[oppRow][oppCol] = 0; + } + } + + // Evaluate the position + score += evaluatePosition(boardCopy, row, col, 2); // Evaluate for bot + score -= evaluatePosition(boardCopy, row, col, 1); // Consider opponent's position + + // Add some randomness to make the bot less predictable + score += Math.random() * 5; + + moveScores.push(score); + } + + // Choose the move with the highest score + let bestScore = -1000000; + let bestMoves = []; + + for (let col = 0; col < COLS; col++) { + if (moveScores[col] > bestScore) { + bestScore = moveScores[col]; + bestMoves = [col]; + } else if (moveScores[col] === bestScore) { + bestMoves.push(col); + } + } + + // Randomly choose from equally good moves + return bestMoves[Math.floor(Math.random() * bestMoves.length)]; +} + +function updateBoard() { + for (let row = 0; row < ROWS; row++) { + for (let col = 0; col < COLS; col++) { + const cell = document.querySelector( + `[data-row="${row}"][data-col="${col}"]` + ); + cell.className = 'cell'; + if (gameBoard[row][col] === 1) cell.classList.add('red'); + if (gameBoard[row][col] === 2) cell.classList.add('yellow'); + } + } +} + +function checkWin(row, col) { + const directions = [ + [[0, 1], [0, -1]], // horizontal + [[1, 0], [-1, 0]], // vertical + [[1, 1], [-1, -1]], // diagonal + [[1, -1], [-1, 1]] // other diagonal + ]; + + for (let dir of directions) { + const winningCells = [[row, col]]; + let count = 1; + + // Check both directions + for (let [deltaRow, deltaCol] of dir) { + let r = row + deltaRow; + let c = col + deltaCol; + + while ( + r >= 0 && r < ROWS && + c >= 0 && c < COLS && + gameBoard[r][c] === currentPlayer + ) { + winningCells.push([r, c]); + count++; + r += deltaRow; + c += deltaCol; + } + } + + if (count >= 4) { + // Highlight winning cells + winningCells.forEach(([r, c]) => { + const cell = document.querySelector( + `[data-row="${r}"][data-col="${c}"]` + ); + cell.classList.add('winner'); + }); + return true; + } + } + return false; +} + +function countDirection(row, col, deltaRow, deltaCol) { + let count = 0; + let r = row + deltaRow; + let c = col + deltaCol; + + while ( + r >= 0 && r < ROWS && + c >= 0 && c < COLS && + gameBoard[r][c] === currentPlayer + ) { + count++; + r += deltaRow; + c += deltaCol; + } + return count; +} + +function resetGame() { + gameBoard = Array(ROWS).fill().map(() => Array(COLS).fill(0)); + currentPlayer = 1; + gameActive = true; + document.querySelector('.turn-indicator').textContent = + 'Current Turn: Player 1'; + // Remove winner class from all cells + document.querySelectorAll('.cell.winner').forEach(cell => { + cell.classList.remove('winner'); + }); + updateBoard(); +} + +// Initialize the game +createBoard(); \ No newline at end of file diff --git a/web/connect-4-game/style.css b/web/connect-4-game/style.css new file mode 100644 index 0000000..c5a7558 --- /dev/null +++ b/web/connect-4-game/style.css @@ -0,0 +1,238 @@ +@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap'); + +:root { + --board-bg: #4a90e2; + --cell-bg: #f0f4f8; + --player1-color: #ff5a5f; + --player2-color: #ffc947; + --text-color: #333; + --bg-color: #f7f9fc; + --white: #ffffff; + --shadow-light: rgba(0, 0, 0, 0.05); + --shadow-dark: rgba(0, 0, 0, 0.15); +} + +body { + margin: 0; + min-height: 100vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: var(--bg-color); + font-family: 'Poppins', sans-serif; + color: var(--text-color); +} + +#game-container { + background: var(--white); + padding: 2rem; + border-radius: 20px; + box-shadow: 0 10px 40px var(--shadow-light); + text-align: center; + max-width: 90%; +} + +h1 { + color: var(--text-color); + text-align: center; + margin-bottom: 1.5rem; + font-weight: 700; + font-size: 2.5rem; +} + +.turn-indicator { + color: var(--text-color); + text-align: center; + margin-bottom: 1.5rem; + font-size: 1.1rem; + font-weight: 600; + padding: 0.75rem 1rem; + background: var(--cell-bg); + border-radius: 12px; + display: inline-block; +} + +.game-board { + display: flex; + gap: 8px; + background: var(--board-bg); + padding: 15px; + border-radius: 15px; + box-shadow: inset 0 4px 10px var(--shadow-dark); +} + +.column { + display: flex; + flex-direction: column; + gap: 8px; + cursor: pointer; + transition: background-color 0.3s ease; + border-radius: 10px; +} + +.column:hover { + background-color: rgba(255, 255, 255, 0.2); +} + +.cell { + width: 60px; + height: 60px; + background: var(--cell-bg); + border-radius: 50%; + transition: background-color 0.4s ease, transform 0.3s ease; + box-shadow: inset 0 3px 6px var(--shadow-dark); +} + +.cell.red, .cell.yellow { + animation: dropPiece 0.4s cubic-bezier(0.6, -0.28, 0.735, 0.045) forwards; + box-shadow: none; +} + +.cell.red { + background-image: radial-gradient(circle at top left, #ff8a80, var(--player1-color)); +} + +.cell.yellow { + background-image: radial-gradient(circle at top left, #ffd166, var(--player2-color)); +} + +.cell.winner { + animation: winPulse 0.8s ease-in-out infinite; +} + +@keyframes winPulse { + 0% { + transform: scale(1); + box-shadow: 0 0 0 0px rgba(255, 255, 255, 0.7); + } + 100% { + transform: scale(1.15); + box-shadow: 0 0 0 15px rgba(255, 255, 255, 0); + } +} + +@keyframes dropPiece { + 0% { + transform: translateY(-400px) scale(0.8); + opacity: 0; + } + 100% { + transform: translateY(0) scale(1); + opacity: 1; + } +} + +#mode-selection { + display: flex; + justify-content: center; + gap: 0.75rem; + margin-bottom: 1.5rem; +} + +.mode-button { + padding: 0.6rem 1.2rem; + font-size: 0.9rem; + font-weight: 600; + background: var(--cell-bg); + color: var(--text-color); + border: 2px solid var(--cell-bg); + border-radius: 10px; + cursor: pointer; + transition: all 0.3s ease; +} + +.mode-button:hover { + background: #e1e8f0; + transform: translateY(-2px); +} + +.mode-button.selected { + background: var(--board-bg); + border-color: var(--board-bg); + color: var(--white); + box-shadow: 0 4px 10px rgba(74, 144, 226, 0.4); +} + +.button-container { + display: flex; + justify-content: center; + gap: 1.5rem; + margin-top: 1.5rem; +} + +.reset-button, .new-game-button { + padding: 0.9rem 2rem; + font-size: 1rem; + font-weight: 600; + color: var(--white); + border: none; + border-radius: 12px; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 5px 15px var(--shadow-dark); + text-transform: uppercase; + letter-spacing: 1px; +} + +.reset-button { + background: var(--player1-color); +} + +.new-game-button { + background: #28a745; +} + +.reset-button:hover, .new-game-button:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px var(--shadow-dark); +} + +.reset-button:hover { + background: #e04a4f; +} + +.new-game-button:hover { + background: #218838; +} + +.reset-button:active, .new-game-button:active { + transform: translateY(0); + box-shadow: 0 2px 5px var(--shadow-dark); +} + +@media (max-width: 768px) { + .cell { + width: 45px; + height: 45px; + } + + #game-container { + padding: 1rem; + } + + h1 { + font-size: 2rem; + } +} + +@media (max-width: 480px) { + .cell { + width: 38px; + height: 38px; + } + + .game-board { + padding: 10px; + gap: 5px; + } + + .column { + gap: 5px; + } + + .button-container { + flex-direction: column; + gap: 0.75rem; + } +} \ No newline at end of file