From 48d2bdc1f3bb410764ac80c378fa7eb377281e1d Mon Sep 17 00:00:00 2001 From: Jevinca Marvella Date: Sun, 30 Nov 2025 15:02:06 +0700 Subject: [PATCH] 2048 update js --- 2048.js | 188 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 129 insertions(+), 59 deletions(-) diff --git a/2048.js b/2048.js index 71aa00e..a83b061 100644 --- a/2048.js +++ b/2048.js @@ -1,4 +1,3 @@ -/* 2048.js — Complete Version with WASD + Interactive Merge Effects */ /* ------------------------ State & Variables @@ -8,6 +7,7 @@ let currentScore = 0; let bestScore = parseInt(localStorage.getItem('bestScore2048')) || 0; let lastMoveDir = null; let isMoving = false; +let mergesInCurrentMove = 0; // NEW: Track combo /* ------------------------ Audio Setup @@ -44,7 +44,6 @@ document.addEventListener("DOMContentLoaded", () => { addNewTile(); tryPlayBg(); document.addEventListener("keydown", handleKey); - setupAmbientCursor(); setupEventListeners(); }); @@ -227,7 +226,7 @@ function playSound(soundObj) { } /* ------------------------ - Movement Logic + Movement Logic - UPDATED WITH COMBO ------------------------ */ function filterZero(row) { return row.filter(n => n !== 0); @@ -236,7 +235,8 @@ function filterZero(row) { function slide(row) { row = filterZero(row); let mergedThisMove = false; - let mergedPositions = []; // Track posisi yang merge + let mergedPositions = []; + let mergeCount = 0; // NEW: count merges for (let i = 0; i < row.length - 1; i++) { if (row[i] === row[i + 1]) { @@ -247,30 +247,33 @@ function slide(row) { currentScore += row[i]; row[i + 1] = 0; mergedThisMove = true; - mergedPositions.push(i); // Simpan posisi merge + mergedPositions.push(i); + mergeCount++; // NEW: increment count } } row = filterZero(row); while (row.length < 4) row.push(0); - return { row, merged: mergedThisMove, mergedPositions }; + return { row, merged: mergedThisMove, mergedPositions, mergeCount }; // NEW: return count } function arraysEqual(a, b) { return a.length === b.length && a.every((v, i) => v === b[i]); } -/* Move functions with Interactive Effects */ +/* Move functions with COMBO DETECTION */ function moveLeft() { let moved = false; let mergedCells = []; + mergesInCurrentMove = 0; // Reset counter for (let r = 0; r < 4; r++) { - const { row: newRow, mergedPositions } = slide(board[r]); + const { row: newRow, mergedPositions, mergeCount } = slide(board[r]); if (!arraysEqual(newRow, board[r])) moved = true; board[r] = newRow; - // Track merged cells untuk animasi + mergesInCurrentMove += mergeCount; + if (mergedPositions && mergedPositions.length > 0) { mergedPositions.forEach(c => { mergedCells.push({ r, c }); @@ -280,10 +283,7 @@ function moveLeft() { if (moved) { refreshBoard(); - // Trigger merge animation - mergedCells.forEach(cell => { - triggerMergeEffect(cell.r, cell.c); - }); + triggerComboEffect(mergedCells, mergesInCurrentMove); } return moved; } @@ -291,18 +291,20 @@ function moveLeft() { function moveRight() { let moved = false; let mergedCells = []; + mergesInCurrentMove = 0; for (let r = 0; r < 4; r++) { let reversed = [...board[r]].reverse(); - const { row: slid, mergedPositions } = slide(reversed); + const { row: slid, mergedPositions, mergeCount } = slide(reversed); let newRow = slid.reverse(); if (!arraysEqual(newRow, board[r])) moved = true; board[r] = newRow; - // Track merged cells + mergesInCurrentMove += mergeCount; + if (mergedPositions && mergedPositions.length > 0) { mergedPositions.forEach(pos => { - const c = 3 - pos; // Reverse position + const c = 3 - pos; mergedCells.push({ r, c }); }); } @@ -310,9 +312,7 @@ function moveRight() { if (moved) { refreshBoard(); - mergedCells.forEach(cell => { - triggerMergeEffect(cell.r, cell.c); - }); + triggerComboEffect(mergedCells, mergesInCurrentMove); } return moved; } @@ -320,16 +320,18 @@ function moveRight() { function moveUp() { let moved = false; let mergedCells = []; + mergesInCurrentMove = 0; for (let c = 0; c < 4; c++) { const col = [board[0][c], board[1][c], board[2][c], board[3][c]]; - const { row: newCol, mergedPositions } = slide(col); + const { row: newCol, mergedPositions, mergeCount } = slide(col); for (let r = 0; r < 4; r++) { if (board[r][c] !== newCol[r]) moved = true; board[r][c] = newCol[r]; } - // Track merged cells + mergesInCurrentMove += mergeCount; + if (mergedPositions && mergedPositions.length > 0) { mergedPositions.forEach(r => { mergedCells.push({ r, c }); @@ -339,9 +341,7 @@ function moveUp() { if (moved) { refreshBoard(); - mergedCells.forEach(cell => { - triggerMergeEffect(cell.r, cell.c); - }); + triggerComboEffect(mergedCells, mergesInCurrentMove); } return moved; } @@ -349,20 +349,22 @@ function moveUp() { function moveDown() { let moved = false; let mergedCells = []; + mergesInCurrentMove = 0; for (let c = 0; c < 4; c++) { const col = [board[3][c], board[2][c], board[1][c], board[0][c]]; - const { row: slid, mergedPositions } = slide(col); + const { row: slid, mergedPositions, mergeCount } = slide(col); const newCol = slid.reverse(); for (let r = 0; r < 4; r++) { if (board[r][c] !== newCol[r]) moved = true; board[r][c] = newCol[r]; } - // Track merged cells + mergesInCurrentMove += mergeCount; + if (mergedPositions && mergedPositions.length > 0) { mergedPositions.forEach(pos => { - const r = 3 - pos; // Reverse position + const r = 3 - pos; mergedCells.push({ r, c }); }); } @@ -370,9 +372,7 @@ function moveDown() { if (moved) { refreshBoard(); - mergedCells.forEach(cell => { - triggerMergeEffect(cell.r, cell.c); - }); + triggerComboEffect(mergedCells, mergesInCurrentMove); } return moved; } @@ -545,7 +545,7 @@ function showGameOver() { const bestScoreDisplay = document.getElementById('best-score-display'); if (isNewHighScore) { - if (newHighScoreBadge) newHighScoreBadge.style.display = 'inline-flex'; + if (newHighScoreBadge) newHighScoreBadge.style.display = 'inline-block'; if (bestScoreDisplay) bestScoreDisplay.style.display = 'none'; } else { if (newHighScoreBadge) newHighScoreBadge.style.display = 'none'; @@ -567,46 +567,121 @@ function hideGameOver() { } } -/* ------------------------ - Visual Effects (Simplified) - ------------------------ */ -function setupAmbientCursor() { - // Cursor light effect removed for performance - // Keeping function for compatibility +/* ============================================= + COMBO EFFECT HANDLER - NEW! + ============================================= */ +function triggerComboEffect(mergedCells, comboCount) { + if (mergedCells.length === 0) return; + + // Trigger individual tile effects + mergedCells.forEach(cell => { + const tile = document.getElementById(`${cell.r}-${cell.c}`); + if (!tile) return; + + tile.classList.add('merge'); + setTimeout(() => tile.classList.remove('merge'), 300); + + createParticleBurst(tile); + + tile.style.boxShadow = '0 0 40px currentColor'; + setTimeout(() => { + tile.style.boxShadow = ''; + }, 300); + + // Individual score popup + const rect = tile.getBoundingClientRect(); + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + const tileValue = parseInt(tile.textContent); + createScorePopup(centerX, centerY, tileValue); + }); + + // Show COMBO popup based on merge count + if (comboCount >= 2) { + showComboPopup(comboCount); + } } /* ============================================= - INTERACTIVE MERGE EFFECTS + COMBO POPUP - NEW! ============================================= */ - -function triggerMergeEffect(row, col) { - const tile = document.getElementById(`${row}-${col}`); - if (!tile) return; +function showComboPopup(comboCount) { + const board = document.getElementById('board'); + if (!board) return; - // Add merge class for CSS animation - tile.classList.add('merge'); - setTimeout(() => tile.classList.remove('merge'), 300); + const rect = board.getBoundingClientRect(); + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; - // Create particle burst effect - createParticleBurst(tile); + const popup = document.createElement('div'); + popup.className = 'combo-popup'; + popup.style.left = centerX + 'px'; + popup.style.top = centerY + 'px'; + popup.style.position = 'fixed'; + popup.style.fontWeight = '900'; + popup.style.pointerEvents = 'none'; + popup.style.zIndex = '9999'; + popup.style.transform = 'translate(-50%, -50%)'; + popup.style.textTransform = 'uppercase'; + popup.style.letterSpacing = '3px'; - // Add glow pulse - tile.style.boxShadow = '0 0 40px currentColor'; - setTimeout(() => { - tile.style.boxShadow = ''; - }, 300); + // Different text and color based on combo count + if (comboCount === 2) { + popup.textContent = 'COMBO x2!'; + popup.style.fontSize = '36px'; + popup.style.color = '#00ff99'; + popup.style.textShadow = '0 0 30px rgba(0, 255, 153, 1), 0 0 50px rgba(0, 255, 153, 0.5)'; + } else if (comboCount === 3) { + popup.textContent = 'AMAZING x3!'; + popup.style.fontSize = '42px'; + popup.style.color = '#ff00ff'; + popup.style.textShadow = '0 0 35px rgba(255, 0, 255, 1), 0 0 60px rgba(255, 0, 255, 0.6)'; + } else if (comboCount >= 4) { + popup.textContent = 'PERFECT x' + comboCount + '!'; + popup.style.fontSize = '48px'; + popup.style.color = '#ffd700'; + popup.style.textShadow = '0 0 40px rgba(255, 215, 0, 1), 0 0 70px rgba(255, 215, 0, 0.7)'; + } + + document.body.appendChild(popup); + + // Animate combo popup + popup.animate([ + { + transform: 'translate(-50%, -50%) scale(0.3) rotate(-10deg)', + opacity: 0 + }, + { + transform: 'translate(-50%, -50%) scale(1.3) rotate(5deg)', + opacity: 1, + offset: 0.3 + }, + { + transform: 'translate(-50%, -50%) scale(1.1) rotate(-2deg)', + opacity: 1, + offset: 0.6 + }, + { + transform: 'translate(-50%, -50%) scale(0.8) rotate(0deg)', + opacity: 0 + } + ], { + duration: 1200, + easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)' + }).onfinish = () => popup.remove(); } +/* ============================================= + PARTICLE & SCORE EFFECTS + ============================================= */ function createParticleBurst(tileElement) { const rect = tileElement.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; - // Get tile color const tileValue = parseInt(tileElement.textContent); const tileColor = getTileColor(tileValue); - // Create 8-12 particles const particleCount = 8 + Math.floor(Math.random() * 5); for (let i = 0; i < particleCount; i++) { @@ -618,13 +693,11 @@ function createParticleBurst(tileElement) { document.body.appendChild(particle); - // Random direction const angle = (Math.PI * 2 * i) / particleCount + (Math.random() - 0.5) * 0.5; const velocity = 60 + Math.random() * 40; const tx = Math.cos(angle) * velocity; const ty = Math.sin(angle) * velocity; - // Animate particle particle.animate([ { transform: 'translate(0, 0) scale(1)', @@ -639,9 +712,6 @@ function createParticleBurst(tileElement) { easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)' }).onfinish = () => particle.remove(); } - - // Add score popup - createScorePopup(centerX, centerY, tileValue); } function createScorePopup(x, y, score) {