2048 update js

This commit is contained in:
Jevinca Marvella 2025-11-30 15:02:06 +07:00
parent e2ba499ce6
commit 48d2bdc1f3

188
2048.js
View File

@ -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) {