diff --git a/web-app/js/projects.js b/web-app/js/projects.js index b341de5..501b820 100644 --- a/web-app/js/projects.js +++ b/web-app/js/projects.js @@ -69,7 +69,8 @@ function initializeProject(projectName) { 'coordinate-polar-transform': initCoordinatePolarTransform, 'derivative-calculator': initDerivativeCalculator, 'morse-code': initMorseCode, - 'tower-of-hanoi': initTowerOfHanoi + 'tower-of-hanoi': initTowerOfHanoi, + '2048-game': init2048Game // Added explicit mapped hook definition binding reference }; if (initializers[projectName]) { diff --git a/web-app/js/projects/2048-game.js b/web-app/js/projects/2048-game.js index 75f59fd..b7dd6c1 100644 --- a/web-app/js/projects/2048-game.js +++ b/web-app/js/projects/2048-game.js @@ -3,408 +3,416 @@ // ============================================ function get2048GameHTML() { - return `
-

🎮 2048 Game

-
-
- Score: - 0 + Score: 0
-
- Best: - 0 + Best: 0
-
-
+
+
- +
+ +
+ + +
+ +
+
+
-
`; } -function init2048Game() { - - const gridContainer = - document.getElementById("grid-container"); - -const scoreDisplay = - document.getElementById("score"); - -const bestDisplay = - document.getElementById("best-score"); - -if (!gridContainer || !scoreDisplay || !bestDisplay) { - console.log("2048 elements not loaded yet"); - return; -} +// ============================================ +// INIT WRAPPER +// ============================================ -let board = []; +function init2048Game() { + const gridContainer = document.getElementById("grid-container"); + const scoreDisplay = document.getElementById("score"); + const bestDisplay = document.getElementById("best-score"); -let score = 0; + if (!gridContainer || !scoreDisplay || !bestDisplay) { + setTimeout(init2048Game, 50); + return; + } -let bestScore = - localStorage.getItem("best2048") || 0; + if (gridContainer.dataset.initialized === "true") return; + gridContainer.dataset.initialized = "true"; -bestDisplay.textContent = bestScore; + let board = []; + let score = 0; + let bestScore = localStorage.getItem("best2048") || 0; + bestDisplay.textContent = bestScore; function createBoard() { - board = [ [0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0] ]; - score = 0; - addNewTile(); addNewTile(); - drawBoard(); } function addNewTile() { - - let emptyCells = []; - - for(let r = 0; r < 4; r++) { - - for(let c = 0; c < 4; c++) { - - if(board[r][c] === 0) { - - emptyCells.push({r,c}); - } + let empty = []; + for (let r = 0; r < 4; r++) { + for (let c = 0; c < 4; c++) { + if (board[r][c] === 0) empty.push({ r, c }); } } + if (!empty.length) return; - if(emptyCells.length === 0) return; - - const randomCell = - emptyCells[ - Math.floor( - Math.random() * emptyCells.length - ) - ]; - - board[randomCell.r][randomCell.c] = - Math.random() < 0.9 ? 2 : 4; + const cell = empty[Math.floor(Math.random() * empty.length)]; + board[cell.r][cell.c] = Math.random() < 0.9 ? 2 : 4; } function drawBoard() { - gridContainer.innerHTML = ""; - board.forEach(row => { - row.forEach(cell => { - - const tile = - document.createElement("div"); - - tile.classList.add("tile"); - - tile.textContent = - cell !== 0 ? cell : ""; - - tile.style.background = getTileColor(cell); - - if(cell <= 4) { - tile.style.color = "#776e65"; - } else { - tile.style.color = "var(--on-accent)"; - } - + const tile = document.createElement("div"); + tile.className = "tile"; + tile.textContent = cell || ""; + tile.style.backgroundColor = getColor(cell); + tile.style.color = cell <= 4 ? "#776e65" : "#fff"; gridContainer.appendChild(tile); }); }); scoreDisplay.textContent = score; - if(score > bestScore) { - + if (score > bestScore) { bestScore = score; - - localStorage.setItem( - "best2048", - bestScore - ); - + localStorage.setItem("best2048", bestScore); bestDisplay.textContent = bestScore; } } - function getTileColor(value) { - - const colors = { - 0: "var(--control-color)", - 2: "#eee4da", - 4: "#ede0c8", - 8: "#f2b179", - 16: "#f59563", - 32: "#f67c5f", - 64: "#f65e3b", - 128: "#edcf72", - 256: "#edcc61", - 512: "#edc850", - 1024: "#edc53f", - 2048: "#ffcc00", - 4096: "#ff5733" - }; - - return colors[value] || "var(--accent-color)"; -} - - function slide(row) { - - row = row.filter(val => val); - - for(let i = 0; i < row.length - 1; i++) { + function getColor(v) { + return { + 0: "#cdc1b4", + 2: "#eee4da", + 4: "#ede0c8", + 8: "#f2b179", + 16: "#f59563", + 32: "#f67c5f", + 64: "#f65e3b", + 128: "#edcf72", + 256: "#edcc61", + 512: "#edc850", + 1024: "#edc53f", + 2048: "#edc22e" + }[v] || "#3c3a32"; + } - if(row[i] === row[i+1]) { + function compress(row) { + let arr = row.filter(v => v); + while (arr.length < 4) arr.push(0); + return arr; + } + function merge(row) { + for (let i = 0; i < 3; i++) { + if (row[i] && row[i] === row[i + 1]) { row[i] *= 2; - if(row[i] === 2048) { - - setTimeout(() => { - - alert("🎉 You reached 2048!"); - - }, 100); -} - score += row[i]; - - row[i+1] = 0; + row[i + 1] = 0; } } - - row = row.filter(val => val); - - while(row.length < 4) { - - row.push(0); - } - return row; } function moveLeft() { - - let changed = false; - - for(let r = 0; r < 4; r++) { - - let original = [...board[r]]; - - board[r] = slide(board[r]); - - if(original.toString() !== board[r].toString()) { - - changed = true; - } - } - - return changed; + let moved = false; + board = board.map(row => { + const old = [...row]; + let newRow = compress(merge(compress(row))); + if (old.toString() !== newRow.toString()) moved = true; + return newRow; + }); + return moved; } function moveRight() { - - let changed = false; - - for(let r = 0; r < 4; r++) { - - let original = [...board[r]]; - - board[r].reverse(); - - board[r] = slide(board[r]); - - board[r].reverse(); - - if(original.toString() !== board[r].toString()) { - - changed = true; - } - } - - return changed; + board = board.map(row => row.reverse()); + let moved = moveLeft(); + board = board.map(row => row.reverse()); + return moved; } function transpose() { - - for(let r = 0; r < 4; r++) { - - for(let c = r; c < 4; c++) { - - let temp = board[r][c]; - - board[r][c] = board[c][r]; - - board[c][r] = temp; - } - } + board = board[0].map((_, i) => board.map(row => row[i])); } function moveUp() { - transpose(); - - let changed = moveLeft(); - + let moved = moveLeft(); transpose(); - - return changed; + return moved; } function moveDown() { - transpose(); - - let changed = moveRight(); - + let moved = moveRight(); transpose(); - - return changed; + return moved; } - window.addEventListener("keydown", (e) => { - + function makeMove(dir) { let moved = false; + if (dir === "left") moved = moveLeft(); + if (dir === "right") moved = moveRight(); + if (dir === "up") moved = moveUp(); + if (dir === "down") moved = moveDown(); - if(e.key === "ArrowLeft") { - moved = moveLeft(); + if (moved) { + addNewTile(); + drawBoard(); } + } - else if(e.key === "ArrowRight") { - moved = moveRight(); - } + // On-Screen Controls Configuration + document.getElementById("ctrl-2048-up").onclick = () => makeMove("up"); + document.getElementById("ctrl-2048-down").onclick = () => makeMove("down"); + document.getElementById("ctrl-2048-left").onclick = () => makeMove("left"); + document.getElementById("ctrl-2048-right").onclick = () => makeMove("right"); - else if(e.key === "ArrowUp") { - moved = moveUp(); - } + // Restart Handling + document.getElementById("restart-btn").onclick = createBoard; - else if(e.key === "ArrowDown") { - moved = moveDown(); + // Keyboard Fallback Setup + const handleKeyDown = (e) => { + if (!document.getElementById("grid-container")) { + window.removeEventListener("keydown", handleKeyDown); + return; } + if (e.key === "ArrowLeft") { e.preventDefault(); makeMove("left"); } + if (e.key === "ArrowRight") { e.preventDefault(); makeMove("right"); } + if (e.key === "ArrowUp") { e.preventDefault(); makeMove("up"); } + if (e.key === "ArrowDown") { e.preventDefault(); makeMove("down"); } + }; + window.addEventListener("keydown", handleKeyDown); - if(moved) { + // Pointer Swiping Mechanics + let touchStartX = 0; + let touchStartY = 0; + const minSwipeDistance = 40; - addNewTile(); + function handleSwipeEnd(endX, endY) { + const diffX = endX - touchStartX; + const diffY = endY - touchStartY; - drawBoard(); + if (Math.max(Math.abs(diffX), Math.abs(diffY)) < minSwipeDistance) return; + + if (Math.abs(diffX) > Math.abs(diffY)) { + if (diffX > 0) makeMove("right"); + else makeMove("left"); + } else { + if (diffY > 0) makeMove("down"); + else makeMove("up"); } - }); + } - document - .getElementById("restart-btn") - .addEventListener("click", () => { + gridContainer.addEventListener("touchstart", (e) => { + touchStartX = e.touches[0].clientX; + touchStartY = e.touches[0].clientY; + }, { passive: true }); + + gridContainer.addEventListener("touchend", (e) => { + if (!e.changedTouches.length) return; + handleSwipeEnd(e.changedTouches[0].clientX, e.changedTouches[0].clientY); + }, { passive: true }); + + let isDragging = false; + gridContainer.addEventListener("mousedown", (e) => { + isDragging = true; + touchStartX = e.clientX; + touchStartY = e.clientY; + }); - createBoard(); - }); + window.addEventListener("mouseup", (e) => { + if (!isDragging) return; + isDragging = false; + handleSwipeEnd(e.clientX, e.clientY); + }); createBoard(); } + +// Global polling initializer fallback hook +const waitFor2048 = setInterval(() => { + if (document.getElementById("grid-container")) { + clearInterval(waitFor2048); + init2048Game(); + } +}, 100); \ No newline at end of file