diff --git a/CSS/2048-index.css b/CSS/2048-index.css new file mode 100644 index 00000000..caaca1f0 --- /dev/null +++ b/CSS/2048-index.css @@ -0,0 +1,325 @@ + + *, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; + } + + body { + font-family: 'Arial', sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + color: #333; + overflow-x: hidden; + } + + .game-container { + display: flex; + gap: 2rem; + max-width: 1200px; + width: 100%; + padding: 1rem; + align-items: flex-start; + } + + .game-section { + background: rgba(255, 255, 255, 0.95); + border-radius: 20px; + padding: 2rem; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + } + + .main-game { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 1.5rem; + } + + .instructions { + width: 700px; + min-height: 150px; + } + + .game-header { + text-align: center; + margin-bottom: 1rem; + } + + .game-title { + font-size: 3.5rem; + font-weight: bold; + color: #ed6a5a; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); + margin-bottom: 0.5rem; + } + + .game-subtitle { + font-size: 1rem; + color: #666; + margin-bottom: 1rem; + } + + .score-container { + display: flex; + gap: 1rem; + justify-content: center; + margin-bottom: 1rem; + } + + .score-box { + background: #ed6a5a; + color: white; + padding: 0.8rem 1.5rem; + border-radius: 12px; + text-align: center; + min-width: 100px; + box-shadow: 0 4px 8px rgba(237, 106, 90, 0.3); + } + + .score-label { + font-size: 0.8rem; + opacity: 0.9; + margin-bottom: 0.2rem; + } + + .score-value { + font-size: 1.5rem; + font-weight: bold; + } + + .controls { + display: flex; + gap: 1rem; + margin-bottom: 1rem; + } + + .btn { + padding: 0.8rem 1.5rem; + border: none; + border-radius: 12px; + font-size: 1rem; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + } + + .btn-primary { + background: #4ecdc4; + color: white; + } + + .btn-primary:hover { + background: #45b7aa; + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(78, 205, 196, 0.4); + } + + .btn-secondary { + background: #f7f7f7; + color: #333; + border: 2px solid #ddd; + } + + .btn-secondary:hover { + background: #e9e9e9; + transform: translateY(-2px); + } + + #board { + display: grid; + grid-template-columns: repeat(4, 80px); + grid-template-rows: repeat(4, 80px); + gap: 8px; + background: #bbada0; + border-radius: 16px; + padding: 12px; + position: relative; + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15); + } + + .tile { + display: flex; + justify-content: center; + align-items: center; + border-radius: 8px; + font-size: 2rem; + font-weight: bold; + background: rgba(238, 228, 218, 0.35); + transition: all 0.15s ease-in-out; + user-select: none; + } + + .tile.new-tile { + animation: appear 0.2s ease-in-out; + } + + @keyframes appear { + 0% { opacity: 0; transform: scale(0); } + 100% { opacity: 1; transform: scale(1); } + } + + .tile.merged { + animation: merge 0.15s ease-in-out; + } + + @keyframes merge { + 0% { transform: scale(1); } + 50% { transform: scale(1.1); } + 100% { transform: scale(1); } + } + + /* Tile colors */ + .x2 { background: #eee4da; color: #727371; } + .x4 { background: #ece0ca; color: #727371; } + .x8 { background: #f4b17a; color: white; } + .x16 { background: #f59575; color: white; } + .x32 { background: #f57c5f; color: white; } + .x64 { background: #f65d3b; color: white; } + .x128 { background: #edce71; color: white; font-size: 1.8rem; } + .x256 { background: #edcc63; color: white; font-size: 1.8rem; } + .x512 { background: #edc651; color: white; font-size: 1.8rem; } + .x1024 { background: #eec744; color: white; font-size: 1.5rem; } + .x2048 { background: #ecc230; color: white; font-size: 1.5rem; box-shadow: 0 0 20px rgba(236, 194, 48, 0.5); } + .x4096 { background: #fe3d3d; color: white; font-size: 1.5rem; } + .x8192 { background: #ff2020; color: white; font-size: 1.5rem; } + + /* Instructions panel */ + .instructions h2 { + color: #ed6a5a; + font-size: 1.8rem; + margin-bottom: 1rem; + text-align: center; + } + + .instructions h3 { + color: #4ecdc4; + font-size: 1.2rem; + margin: 1.5rem 0 0.5rem 0; + } + + .instructions p, .instructions li { + line-height: 1.6; + margin-bottom: 0.8rem; + color: #555; + } + + .instructions ul { + margin-left: 1.2rem; + } + + .key-hint { + display: inline-flex; + align-items: center; + background: #f0f0f0; + border: 2px solid #ddd; + border-radius: 6px; + padding: 0.2rem 0.5rem; + font-family: monospace; + font-weight: bold; + margin: 0 0.2rem; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + .goal-highlight { + background: linear-gradient(90deg, #ecc230, #edce71); + color: white; + padding: 0.8rem; + border-radius: 12px; + text-align: center; + font-weight: bold; + margin: 1rem 0; + box-shadow: 0 4px 8px rgba(236, 194, 48, 0.3); + } + + .game-over { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.8); + display: none; + justify-content: center; + align-items: center; + z-index: 1000; + } + + .game-over-content { + background: white; + padding: 2rem; + border-radius: 20px; + text-align: center; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); + } + + .game-over h2 { + color: #ed6a5a; + font-size: 2rem; + margin-bottom: 1rem; + } + + .game-over p { + font-size: 1.2rem; + margin-bottom: 1.5rem; + color: #666; + } + + /* Mobile responsive */ + @media (max-width: 768px) { + .game-container { + flex-direction: column; + align-items: center; + } + + .instructions { + width: 100%; + max-width: 400px; + order: 2; + } + + .main-game { + order: 1; + } + + .game-title { + font-size: 2.5rem; + } + + #board { + grid-template-columns: repeat(4, 70px); + grid-template-rows: repeat(4, 70px); + } + + .tile { + font-size: 1.5rem; + } + + .x128, .x256, .x512 { + font-size: 1.3rem; + } + + .x1024, .x2048, .x4096, .x8192 { + font-size: 1.1rem; + } + } + + @media (max-width: 480px) { + #board { + grid-template-columns: repeat(4, 60px); + grid-template-rows: repeat(4, 60px); + gap: 6px; + } + + .tile { + font-size: 1.3rem; + } + } + \ No newline at end of file diff --git a/HTML Pages/2048-index.html b/HTML Pages/2048-index.html new file mode 100644 index 00000000..4f5f1ab8 --- /dev/null +++ b/HTML Pages/2048-index.html @@ -0,0 +1,76 @@ + + + + + + 2048 Game + + + + +
+
+
+

2048

+

Join the tiles, get to 2048!

+
+ +
+
+
SCORE
+
0
+
+
+
BEST
+
0
+
+
+ +
+ + +
+ +
+
+ +
+

🎮 How to Play

+ +
+ Goal: Reach the 2048 tile! +
+ +

🎯 Game Rules

+ + +

⌨️ Controls

+

Desktop:

+ + +

Mobile: Swipe in any direction

+ +

🏆 Scoring

+

Every time you merge tiles, you get points equal to the value of the new tile created. Try to beat your best score!

+
+
+ +
+
+

Game Over!

+

Final Score: 0

+ +
+
+ + \ No newline at end of file diff --git a/Logic/2048-script.js b/Logic/2048-script.js new file mode 100644 index 00000000..0923fe53 --- /dev/null +++ b/Logic/2048-script.js @@ -0,0 +1,320 @@ +var board; + var score = 0; + var best = localStorage.getItem('best2048') || 0; + var rows = 4; + var columns = 4; + var gameStarted = false; + + // Touch handling variables + let pageWidth = window.innerWidth || document.body.clientWidth; + let threshold = Math.max(1, Math.floor(0.01 * pageWidth)); + let touchstartX = 0; + let touchstartY = 0; + let touchendX = 0; + let touchendY = 0; + + const limit = Math.tan(45 * 1.5 / 180 * Math.PI); + + window.onload = function() { + document.getElementById('best').innerText = best; + setGame(); + } + + function setGame() { + board = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0] + ]; + + // Clear the board + document.getElementById("board").innerHTML = ''; + + for (let row = 0; row < rows; row++) { + for (let column = 0; column < columns; column++) { + let tile = document.createElement("div"); + tile.id = row.toString() + "-" + column.toString(); + let num = board[row][column]; + updateTile(tile, num); + document.getElementById("board").append(tile); + } + } + + generateRandomTile(); + generateRandomTile(); + gameStarted = true; + } + + function newGame() { + score = 0; + document.getElementById("score").innerText = score; + document.getElementById("gameOver").style.display = 'none'; + setGame(); + } + + function toggleInstructions() { + const instructions = document.getElementById('instructions'); + if (window.innerWidth <= 768) { + instructions.style.display = instructions.style.display === 'none' ? 'block' : 'none'; + } + } + + function hasEmptyTile() { + for (let row = 0; row < rows; row++) { + for (let column = 0; column < columns; column++) { + if (board[row][column] == 0) { + return true; + } + } + } + return false; + } + + function canMove() { + if (hasEmptyTile()) return true; + + // Check for possible merges + for (let row = 0; row < rows; row++) { + for (let column = 0; column < columns; column++) { + let current = board[row][column]; + if ((row < rows - 1 && current == board[row + 1][column]) || + (column < columns - 1 && current == board[row][column + 1])) { + return true; + } + } + } + return false; + } + + function generateRandomTile() { + if (!hasEmptyTile()) { + if (!canMove()) { + gameOver(); + } + return; + } + + let found = false; + while (!found) { + let row = Math.floor(Math.random() * rows); + let column = Math.floor(Math.random() * columns); + + if (board[row][column] == 0) { + board[row][column] = Math.random() < 0.9 ? 2 : 4; + let tile = document.getElementById(row.toString() + "-" + column.toString()); + updateTile(tile, board[row][column]); + tile.classList.add('new-tile'); + setTimeout(() => tile.classList.remove('new-tile'), 200); + found = true; + } + } + } + + function gameOver() { + document.getElementById('finalScore').innerText = score; + document.getElementById('gameOver').style.display = 'flex'; + + if (score > best) { + best = score; + localStorage.setItem('best2048', best); + document.getElementById('best').innerText = best; + } + } + + function updateTile(tile, num) { + tile.innerText = ""; + tile.classList.value = ""; + tile.classList.add("tile"); + + if (num > 0) { + tile.innerText = num; + if (num <= 8192) { + tile.classList.add("x" + num.toString()); + } else { + tile.classList.add("x8192"); + } + } + } + + function makeMove(direction) { + if (!gameStarted) return; + + let moved = false; + let oldBoard = board.map(row => row.slice()); + + switch(direction) { + case 'left': moved = slideLeft(); break; + case 'right': moved = slideRight(); break; + case 'up': moved = slideUp(); break; + case 'down': moved = slideDown(); break; + } + + if (moved) { + generateRandomTile(); + document.getElementById("score").innerText = score; + + // Check for 2048 tile + for (let row = 0; row < rows; row++) { + for (let column = 0; column < columns; column++) { + if (board[row][column] == 2048) { + setTimeout(() => { + alert('Congratulations! You reached 2048! 🎉\nYou can continue playing to reach higher scores.'); + }, 300); + } + } + } + } + } + + // Keyboard controls + document.addEventListener("keyup", (event) => { + switch(event.code) { + case "ArrowLeft": makeMove('left'); break; + case "ArrowRight": makeMove('right'); break; + case "ArrowUp": makeMove('up'); break; + case "ArrowDown": makeMove('down'); break; + } + }); + + // Touch controls + document.addEventListener('touchstart', function(event) { + touchstartX = event.changedTouches[0].screenX; + touchstartY = event.changedTouches[0].screenY; + }, false); + + document.addEventListener('touchend', function(event) { + touchendX = event.changedTouches[0].screenX; + touchendY = event.changedTouches[0].screenY; + handleGesture(); + }, false); + + function handleGesture() { + let x = touchendX - touchstartX; + let y = touchendY - touchstartY; + let xy = Math.abs(x / y); + let yx = Math.abs(y / x); + + if (Math.abs(x) > threshold || Math.abs(y) > threshold) { + if (yx <= limit) { + if (x < 0) { + makeMove('left'); + } else { + makeMove('right'); + } + } + if (xy <= limit) { + if (y < 0) { + makeMove('up'); + } else { + makeMove('down'); + } + } + } + } + + function filterZeros(row) { + return row.filter(num => num != 0); + } + + function slide(row) { + row = filterZeros(row); + let merged = new Array(row.length).fill(false); + + for (let i = 0; i < row.length - 1; i++) { + if (row[i] == row[i + 1] && !merged[i] && !merged[i + 1]) { + row[i] *= 2; + row[i + 1] = 0; + score += row[i]; + merged[i] = true; + } + } + + row = filterZeros(row); + + while (row.length < columns) { + row.push(0); + } + + return row; + } + + function slideLeft() { + let moved = false; + for (let i = 0; i < rows; i++) { + let originalRow = board[i].slice(); + let row = slide(board[i]); + board[i] = row; + + if (JSON.stringify(originalRow) !== JSON.stringify(row)) { + moved = true; + } + + for (let j = 0; j < columns; j++) { + let tile = document.getElementById(i.toString() + "-" + j.toString()); + updateTile(tile, board[i][j]); + } + } + return moved; + } + + function slideRight() { + let moved = false; + for (let i = 0; i < rows; i++) { + let originalRow = board[i].slice(); + let row = board[i].slice().reverse(); + row = slide(row); + row.reverse(); + board[i] = row; + + if (JSON.stringify(originalRow) !== JSON.stringify(row)) { + moved = true; + } + + for (let j = 0; j < columns; j++) { + let tile = document.getElementById(i.toString() + "-" + j.toString()); + updateTile(tile, board[i][j]); + } + } + return moved; + } + + function slideUp() { + let moved = false; + for (let j = 0; j < columns; j++) { + let originalColumn = [board[0][j], board[1][j], board[2][j], board[3][j]]; + let row = slide(originalColumn); + + if (JSON.stringify(originalColumn) !== JSON.stringify(row)) { + moved = true; + } + + for (let i = 0; i < rows; i++) { + board[i][j] = row[i]; + let tile = document.getElementById(i.toString() + "-" + j.toString()); + updateTile(tile, board[i][j]); + } + } + return moved; + } + + function slideDown() { + let moved = false; + for (let j = 0; j < columns; j++) { + let originalColumn = [board[0][j], board[1][j], board[2][j], board[3][j]]; + let row = originalColumn.slice().reverse(); + row = slide(row); + row.reverse(); + + if (JSON.stringify(originalColumn) !== JSON.stringify(row)) { + moved = true; + } + + for (let i = 0; i < rows; i++) { + board[i][j] = row[i]; + let tile = document.getElementById(i.toString() + "-" + j.toString()); + updateTile(tile, board[i][j]); + } + } + return moved; + } \ No newline at end of file diff --git a/scripts/app.js b/scripts/app.js index 99d61b3e..7970d589 100644 --- a/scripts/app.js +++ b/scripts/app.js @@ -1134,9 +1134,21 @@ class WebDev100Days { features: [ "Dynamic grid scaling, pattern generation & replay, dark/light mode, score & level tracking, keyboard accessibility, animated feedback, and replay option." ] +}, +{ + originalDay: 166, + name:"2048 Game", + description:"2048 is a sliding puzzle game where you combine numbered tiles to reach the 2048 tile.", + demoLink:"./HTML Pages/2048-index.html", + category: "games", + technologies: ["HTML", "CSS", "JavaScript"], + features: [ + "Sliding puzzle with high score , winner badge and score tracker." + ] } + ]; this.projects = projectsData.map((project, index) => ({