2048 update js
This commit is contained in:
parent
e2ba499ce6
commit
48d2bdc1f3
186
2048.js
186
2048.js
@ -1,4 +1,3 @@
|
|||||||
/* 2048.js — Complete Version with WASD + Interactive Merge Effects */
|
|
||||||
|
|
||||||
/* ------------------------
|
/* ------------------------
|
||||||
State & Variables
|
State & Variables
|
||||||
@ -8,6 +7,7 @@ let currentScore = 0;
|
|||||||
let bestScore = parseInt(localStorage.getItem('bestScore2048')) || 0;
|
let bestScore = parseInt(localStorage.getItem('bestScore2048')) || 0;
|
||||||
let lastMoveDir = null;
|
let lastMoveDir = null;
|
||||||
let isMoving = false;
|
let isMoving = false;
|
||||||
|
let mergesInCurrentMove = 0; // NEW: Track combo
|
||||||
|
|
||||||
/* ------------------------
|
/* ------------------------
|
||||||
Audio Setup
|
Audio Setup
|
||||||
@ -44,7 +44,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
addNewTile();
|
addNewTile();
|
||||||
tryPlayBg();
|
tryPlayBg();
|
||||||
document.addEventListener("keydown", handleKey);
|
document.addEventListener("keydown", handleKey);
|
||||||
setupAmbientCursor();
|
|
||||||
setupEventListeners();
|
setupEventListeners();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -227,7 +226,7 @@ function playSound(soundObj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------
|
/* ------------------------
|
||||||
Movement Logic
|
Movement Logic - UPDATED WITH COMBO
|
||||||
------------------------ */
|
------------------------ */
|
||||||
function filterZero(row) {
|
function filterZero(row) {
|
||||||
return row.filter(n => n !== 0);
|
return row.filter(n => n !== 0);
|
||||||
@ -236,7 +235,8 @@ function filterZero(row) {
|
|||||||
function slide(row) {
|
function slide(row) {
|
||||||
row = filterZero(row);
|
row = filterZero(row);
|
||||||
let mergedThisMove = false;
|
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++) {
|
for (let i = 0; i < row.length - 1; i++) {
|
||||||
if (row[i] === row[i + 1]) {
|
if (row[i] === row[i + 1]) {
|
||||||
@ -247,30 +247,33 @@ function slide(row) {
|
|||||||
currentScore += row[i];
|
currentScore += row[i];
|
||||||
row[i + 1] = 0;
|
row[i + 1] = 0;
|
||||||
mergedThisMove = true;
|
mergedThisMove = true;
|
||||||
mergedPositions.push(i); // Simpan posisi merge
|
mergedPositions.push(i);
|
||||||
|
mergeCount++; // NEW: increment count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
row = filterZero(row);
|
row = filterZero(row);
|
||||||
while (row.length < 4) row.push(0);
|
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) {
|
function arraysEqual(a, b) {
|
||||||
return a.length === b.length && a.every((v, i) => v === b[i]);
|
return a.length === b.length && a.every((v, i) => v === b[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Move functions with Interactive Effects */
|
/* Move functions with COMBO DETECTION */
|
||||||
function moveLeft() {
|
function moveLeft() {
|
||||||
let moved = false;
|
let moved = false;
|
||||||
let mergedCells = [];
|
let mergedCells = [];
|
||||||
|
mergesInCurrentMove = 0; // Reset counter
|
||||||
|
|
||||||
for (let r = 0; r < 4; r++) {
|
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;
|
if (!arraysEqual(newRow, board[r])) moved = true;
|
||||||
board[r] = newRow;
|
board[r] = newRow;
|
||||||
|
|
||||||
// Track merged cells untuk animasi
|
mergesInCurrentMove += mergeCount;
|
||||||
|
|
||||||
if (mergedPositions && mergedPositions.length > 0) {
|
if (mergedPositions && mergedPositions.length > 0) {
|
||||||
mergedPositions.forEach(c => {
|
mergedPositions.forEach(c => {
|
||||||
mergedCells.push({ r, c });
|
mergedCells.push({ r, c });
|
||||||
@ -280,10 +283,7 @@ function moveLeft() {
|
|||||||
|
|
||||||
if (moved) {
|
if (moved) {
|
||||||
refreshBoard();
|
refreshBoard();
|
||||||
// Trigger merge animation
|
triggerComboEffect(mergedCells, mergesInCurrentMove);
|
||||||
mergedCells.forEach(cell => {
|
|
||||||
triggerMergeEffect(cell.r, cell.c);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return moved;
|
return moved;
|
||||||
}
|
}
|
||||||
@ -291,18 +291,20 @@ function moveLeft() {
|
|||||||
function moveRight() {
|
function moveRight() {
|
||||||
let moved = false;
|
let moved = false;
|
||||||
let mergedCells = [];
|
let mergedCells = [];
|
||||||
|
mergesInCurrentMove = 0;
|
||||||
|
|
||||||
for (let r = 0; r < 4; r++) {
|
for (let r = 0; r < 4; r++) {
|
||||||
let reversed = [...board[r]].reverse();
|
let reversed = [...board[r]].reverse();
|
||||||
const { row: slid, mergedPositions } = slide(reversed);
|
const { row: slid, mergedPositions, mergeCount } = slide(reversed);
|
||||||
let newRow = slid.reverse();
|
let newRow = slid.reverse();
|
||||||
if (!arraysEqual(newRow, board[r])) moved = true;
|
if (!arraysEqual(newRow, board[r])) moved = true;
|
||||||
board[r] = newRow;
|
board[r] = newRow;
|
||||||
|
|
||||||
// Track merged cells
|
mergesInCurrentMove += mergeCount;
|
||||||
|
|
||||||
if (mergedPositions && mergedPositions.length > 0) {
|
if (mergedPositions && mergedPositions.length > 0) {
|
||||||
mergedPositions.forEach(pos => {
|
mergedPositions.forEach(pos => {
|
||||||
const c = 3 - pos; // Reverse position
|
const c = 3 - pos;
|
||||||
mergedCells.push({ r, c });
|
mergedCells.push({ r, c });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -310,9 +312,7 @@ function moveRight() {
|
|||||||
|
|
||||||
if (moved) {
|
if (moved) {
|
||||||
refreshBoard();
|
refreshBoard();
|
||||||
mergedCells.forEach(cell => {
|
triggerComboEffect(mergedCells, mergesInCurrentMove);
|
||||||
triggerMergeEffect(cell.r, cell.c);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return moved;
|
return moved;
|
||||||
}
|
}
|
||||||
@ -320,16 +320,18 @@ function moveRight() {
|
|||||||
function moveUp() {
|
function moveUp() {
|
||||||
let moved = false;
|
let moved = false;
|
||||||
let mergedCells = [];
|
let mergedCells = [];
|
||||||
|
mergesInCurrentMove = 0;
|
||||||
|
|
||||||
for (let c = 0; c < 4; c++) {
|
for (let c = 0; c < 4; c++) {
|
||||||
const col = [board[0][c], board[1][c], board[2][c], board[3][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++) {
|
for (let r = 0; r < 4; r++) {
|
||||||
if (board[r][c] !== newCol[r]) moved = true;
|
if (board[r][c] !== newCol[r]) moved = true;
|
||||||
board[r][c] = newCol[r];
|
board[r][c] = newCol[r];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track merged cells
|
mergesInCurrentMove += mergeCount;
|
||||||
|
|
||||||
if (mergedPositions && mergedPositions.length > 0) {
|
if (mergedPositions && mergedPositions.length > 0) {
|
||||||
mergedPositions.forEach(r => {
|
mergedPositions.forEach(r => {
|
||||||
mergedCells.push({ r, c });
|
mergedCells.push({ r, c });
|
||||||
@ -339,9 +341,7 @@ function moveUp() {
|
|||||||
|
|
||||||
if (moved) {
|
if (moved) {
|
||||||
refreshBoard();
|
refreshBoard();
|
||||||
mergedCells.forEach(cell => {
|
triggerComboEffect(mergedCells, mergesInCurrentMove);
|
||||||
triggerMergeEffect(cell.r, cell.c);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return moved;
|
return moved;
|
||||||
}
|
}
|
||||||
@ -349,20 +349,22 @@ function moveUp() {
|
|||||||
function moveDown() {
|
function moveDown() {
|
||||||
let moved = false;
|
let moved = false;
|
||||||
let mergedCells = [];
|
let mergedCells = [];
|
||||||
|
mergesInCurrentMove = 0;
|
||||||
|
|
||||||
for (let c = 0; c < 4; c++) {
|
for (let c = 0; c < 4; c++) {
|
||||||
const col = [board[3][c], board[2][c], board[1][c], board[0][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();
|
const newCol = slid.reverse();
|
||||||
for (let r = 0; r < 4; r++) {
|
for (let r = 0; r < 4; r++) {
|
||||||
if (board[r][c] !== newCol[r]) moved = true;
|
if (board[r][c] !== newCol[r]) moved = true;
|
||||||
board[r][c] = newCol[r];
|
board[r][c] = newCol[r];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track merged cells
|
mergesInCurrentMove += mergeCount;
|
||||||
|
|
||||||
if (mergedPositions && mergedPositions.length > 0) {
|
if (mergedPositions && mergedPositions.length > 0) {
|
||||||
mergedPositions.forEach(pos => {
|
mergedPositions.forEach(pos => {
|
||||||
const r = 3 - pos; // Reverse position
|
const r = 3 - pos;
|
||||||
mergedCells.push({ r, c });
|
mergedCells.push({ r, c });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -370,9 +372,7 @@ function moveDown() {
|
|||||||
|
|
||||||
if (moved) {
|
if (moved) {
|
||||||
refreshBoard();
|
refreshBoard();
|
||||||
mergedCells.forEach(cell => {
|
triggerComboEffect(mergedCells, mergesInCurrentMove);
|
||||||
triggerMergeEffect(cell.r, cell.c);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return moved;
|
return moved;
|
||||||
}
|
}
|
||||||
@ -545,7 +545,7 @@ function showGameOver() {
|
|||||||
const bestScoreDisplay = document.getElementById('best-score-display');
|
const bestScoreDisplay = document.getElementById('best-score-display');
|
||||||
|
|
||||||
if (isNewHighScore) {
|
if (isNewHighScore) {
|
||||||
if (newHighScoreBadge) newHighScoreBadge.style.display = 'inline-flex';
|
if (newHighScoreBadge) newHighScoreBadge.style.display = 'inline-block';
|
||||||
if (bestScoreDisplay) bestScoreDisplay.style.display = 'none';
|
if (bestScoreDisplay) bestScoreDisplay.style.display = 'none';
|
||||||
} else {
|
} else {
|
||||||
if (newHighScoreBadge) newHighScoreBadge.style.display = 'none';
|
if (newHighScoreBadge) newHighScoreBadge.style.display = 'none';
|
||||||
@ -567,46 +567,121 @@ function hideGameOver() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------
|
/* =============================================
|
||||||
Visual Effects (Simplified)
|
COMBO EFFECT HANDLER - NEW!
|
||||||
------------------------ */
|
============================================= */
|
||||||
function setupAmbientCursor() {
|
function triggerComboEffect(mergedCells, comboCount) {
|
||||||
// Cursor light effect removed for performance
|
if (mergedCells.length === 0) return;
|
||||||
// Keeping function for compatibility
|
|
||||||
|
// 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 showComboPopup(comboCount) {
|
||||||
|
const board = document.getElementById('board');
|
||||||
|
if (!board) return;
|
||||||
|
|
||||||
function triggerMergeEffect(row, col) {
|
const rect = board.getBoundingClientRect();
|
||||||
const tile = document.getElementById(`${row}-${col}`);
|
const centerX = rect.left + rect.width / 2;
|
||||||
if (!tile) return;
|
const centerY = rect.top + rect.height / 2;
|
||||||
|
|
||||||
// Add merge class for CSS animation
|
const popup = document.createElement('div');
|
||||||
tile.classList.add('merge');
|
popup.className = 'combo-popup';
|
||||||
setTimeout(() => tile.classList.remove('merge'), 300);
|
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';
|
||||||
|
|
||||||
// Create particle burst effect
|
// Different text and color based on combo count
|
||||||
createParticleBurst(tile);
|
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)';
|
||||||
|
}
|
||||||
|
|
||||||
// Add glow pulse
|
document.body.appendChild(popup);
|
||||||
tile.style.boxShadow = '0 0 40px currentColor';
|
|
||||||
setTimeout(() => {
|
// Animate combo popup
|
||||||
tile.style.boxShadow = '';
|
popup.animate([
|
||||||
}, 300);
|
{
|
||||||
|
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) {
|
function createParticleBurst(tileElement) {
|
||||||
const rect = tileElement.getBoundingClientRect();
|
const rect = tileElement.getBoundingClientRect();
|
||||||
const centerX = rect.left + rect.width / 2;
|
const centerX = rect.left + rect.width / 2;
|
||||||
const centerY = rect.top + rect.height / 2;
|
const centerY = rect.top + rect.height / 2;
|
||||||
|
|
||||||
// Get tile color
|
|
||||||
const tileValue = parseInt(tileElement.textContent);
|
const tileValue = parseInt(tileElement.textContent);
|
||||||
const tileColor = getTileColor(tileValue);
|
const tileColor = getTileColor(tileValue);
|
||||||
|
|
||||||
// Create 8-12 particles
|
|
||||||
const particleCount = 8 + Math.floor(Math.random() * 5);
|
const particleCount = 8 + Math.floor(Math.random() * 5);
|
||||||
|
|
||||||
for (let i = 0; i < particleCount; i++) {
|
for (let i = 0; i < particleCount; i++) {
|
||||||
@ -618,13 +693,11 @@ function createParticleBurst(tileElement) {
|
|||||||
|
|
||||||
document.body.appendChild(particle);
|
document.body.appendChild(particle);
|
||||||
|
|
||||||
// Random direction
|
|
||||||
const angle = (Math.PI * 2 * i) / particleCount + (Math.random() - 0.5) * 0.5;
|
const angle = (Math.PI * 2 * i) / particleCount + (Math.random() - 0.5) * 0.5;
|
||||||
const velocity = 60 + Math.random() * 40;
|
const velocity = 60 + Math.random() * 40;
|
||||||
const tx = Math.cos(angle) * velocity;
|
const tx = Math.cos(angle) * velocity;
|
||||||
const ty = Math.sin(angle) * velocity;
|
const ty = Math.sin(angle) * velocity;
|
||||||
|
|
||||||
// Animate particle
|
|
||||||
particle.animate([
|
particle.animate([
|
||||||
{
|
{
|
||||||
transform: 'translate(0, 0) scale(1)',
|
transform: 'translate(0, 0) scale(1)',
|
||||||
@ -639,9 +712,6 @@ function createParticleBurst(tileElement) {
|
|||||||
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'
|
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'
|
||||||
}).onfinish = () => particle.remove();
|
}).onfinish = () => particle.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add score popup
|
|
||||||
createScorePopup(centerX, centerY, tileValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createScorePopup(x, y, score) {
|
function createScorePopup(x, y, score) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user