2048 update js
This commit is contained in:
parent
e2ba499ce6
commit
48d2bdc1f3
188
2048.js
188
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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user