2048
This commit is contained in:
parent
c14ba859b8
commit
b08d1b5ce7
169
2048.css
169
2048.css
@ -120,6 +120,175 @@ h1 {
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sound-controls {
|
||||||
|
position: fixed;
|
||||||
|
top: clamp(10px, 2vh, 20px);
|
||||||
|
left: clamp(10px, 2vw, 20px);
|
||||||
|
display: flex;
|
||||||
|
gap: clamp(8px, 1.5vw, 12px);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sound Button Styling */
|
||||||
|
.btn-sound {
|
||||||
|
position: relative;
|
||||||
|
width: clamp(36px, 6vw, 48px);
|
||||||
|
height: clamp(36px, 6vw, 48px);
|
||||||
|
padding: 0;
|
||||||
|
background: rgba(30, 0, 50, 0.85);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
border: 2px solid rgba(0, 217, 255, 0.45);
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow:
|
||||||
|
0 4px 18px rgba(0, 0, 0, 0.35),
|
||||||
|
0 0 20px rgba(0, 217, 255, 0.2),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound svg {
|
||||||
|
width: clamp(18px, 3vw, 24px);
|
||||||
|
height: clamp(18px, 3vw, 24px);
|
||||||
|
color: rgba(0, 234, 255, 0.9);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BG Music Button - Purple */
|
||||||
|
#btn-sound-bg {
|
||||||
|
background: rgba(50, 0, 70, 0.85);
|
||||||
|
border-color: rgba(200, 100, 255, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-bg svg {
|
||||||
|
color: rgba(200, 100, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-bg:hover {
|
||||||
|
background: rgba(200, 100, 255, 0.15);
|
||||||
|
border-color: rgba(200, 100, 255, 0.8);
|
||||||
|
box-shadow:
|
||||||
|
0 6px 25px rgba(0, 0, 0, 0.45),
|
||||||
|
0 0 35px rgba(200, 100, 255, 0.4),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-bg:hover svg {
|
||||||
|
color: rgba(200, 100, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pop SFX Button - Cyan */
|
||||||
|
#btn-sound-pop {
|
||||||
|
background: rgba(0, 40, 50, 0.85);
|
||||||
|
border-color: rgba(0, 234, 255, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-pop svg {
|
||||||
|
color: rgba(0, 234, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-pop:hover {
|
||||||
|
background: rgba(0, 234, 255, 0.15);
|
||||||
|
border-color: rgba(0, 234, 255, 0.8);
|
||||||
|
box-shadow:
|
||||||
|
0 6px 25px rgba(0, 0, 0, 0.45),
|
||||||
|
0 0 35px rgba(0, 234, 255, 0.4),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-pop:hover svg {
|
||||||
|
color: rgba(0, 234, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Merge SFX Button - Orange/Yellow */
|
||||||
|
#btn-sound-merge {
|
||||||
|
background: rgba(60, 30, 0, 0.85);
|
||||||
|
border-color: rgba(255, 170, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-merge svg {
|
||||||
|
color: rgba(255, 170, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-merge:hover {
|
||||||
|
background: rgba(255, 170, 0, 0.15);
|
||||||
|
border-color: rgba(255, 170, 0, 0.8);
|
||||||
|
box-shadow:
|
||||||
|
0 6px 25px rgba(0, 0, 0, 0.45),
|
||||||
|
0 0 35px rgba(255, 170, 0, 0.4),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-sound-merge:hover svg {
|
||||||
|
color: rgba(255, 170, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover & Active States */
|
||||||
|
.btn-sound:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow:
|
||||||
|
0 2px 12px rgba(0, 0, 0, 0.35),
|
||||||
|
0 0 20px rgba(0, 234, 255, 0.3),
|
||||||
|
inset 0 2px 6px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Muted State - Red with X */
|
||||||
|
.btn-sound.muted {
|
||||||
|
background: rgba(60, 0, 10, 0.85) !important;
|
||||||
|
border-color: rgba(255, 50, 50, 0.6) !important;
|
||||||
|
box-shadow:
|
||||||
|
0 4px 18px rgba(0, 0, 0, 0.35),
|
||||||
|
0 0 20px rgba(255, 50, 50, 0.3),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound.muted svg.sound-icon {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound.muted svg.mute-icon {
|
||||||
|
display: block !important;
|
||||||
|
color: rgba(255, 80, 80, 0.9) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound.muted:hover {
|
||||||
|
background: rgba(255, 50, 50, 0.2) !important;
|
||||||
|
border-color: rgba(255, 80, 80, 0.8) !important;
|
||||||
|
box-shadow:
|
||||||
|
0 6px 25px rgba(0, 0, 0, 0.45),
|
||||||
|
0 0 35px rgba(255, 50, 50, 0.5),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.15) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound.muted:hover svg.mute-icon {
|
||||||
|
color: rgba(255, 100, 100, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon Transitions */
|
||||||
|
.btn-sound svg.sound-icon,
|
||||||
|
.btn-sound svg.mute-icon {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sound:hover svg {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sound-controls {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: clamp(6px, 1.2vw, 10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
.icon-btn {
|
.icon-btn {
|
||||||
width: clamp(36px, 6vw, 48px);
|
width: clamp(36px, 6vw, 48px);
|
||||||
height: clamp(36px, 6vw, 48px);
|
height: clamp(36px, 6vw, 48px);
|
||||||
|
|||||||
37
2048.html
37
2048.html
@ -15,6 +15,43 @@
|
|||||||
<div class="starfield" aria-hidden="true"></div>
|
<div class="starfield" aria-hidden="true"></div>
|
||||||
<div class="cursor-light" aria-hidden="true"></div>
|
<div class="cursor-light" aria-hidden="true"></div>
|
||||||
|
|
||||||
|
<!-- SOUND CONTROLS - KIRI ATAS -->
|
||||||
|
<div class="sound-controls">
|
||||||
|
<!-- BG Music Toggle -->
|
||||||
|
<button class="icon-btn btn-sound" id="btn-sound-bg" data-sound="bg" title="Toggle Background Music">
|
||||||
|
<svg class="sound-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||||
|
<path d="M9 18V5l12-2v13M9 18c0 1.657-1.343 3-3 3s-3-1.343-3-3 1.343-3 3-3 3 1.343 3 3zm12-2c0 1.657-1.343 3-3 3s-3-1.343-3-3 1.343-3 3-3 3 1.343 3 3z"/>
|
||||||
|
</svg>
|
||||||
|
<svg class="mute-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="display: none;">
|
||||||
|
<line x1="2" y1="2" x2="22" y2="22"/>
|
||||||
|
<path d="M9 18V5l12-2v13M9 18c0 1.657-1.343 3-3 3s-3-1.343-3-3 1.343-3 3-3 3 1.343 3 3zm12-2c0 1.657-1.343 3-3 3s-3-1.343-3-3 1.343-3 3-3 3 1.343 3 3z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Pop SFX Toggle -->
|
||||||
|
<button class="icon-btn btn-sound" id="btn-sound-pop" data-sound="pop" title="Toggle Pop Sound">
|
||||||
|
<svg class="sound-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="10"/>
|
||||||
|
<path d="M8 12h8M12 8v8"/>
|
||||||
|
</svg>
|
||||||
|
<svg class="mute-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="display: none;">
|
||||||
|
<line x1="2" y1="2" x2="22" y2="22"/>
|
||||||
|
<circle cx="12" cy="12" r="10"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Merge SFX Toggle -->
|
||||||
|
<button class="icon-btn btn-sound" id="btn-sound-merge" data-sound="merge" title="Toggle Merge Sound">
|
||||||
|
<svg class="sound-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||||
|
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
|
||||||
|
</svg>
|
||||||
|
<svg class="mute-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="display: none;">
|
||||||
|
<line x1="2" y1="2" x2="22" y2="22"/>
|
||||||
|
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Top Right Controls -->
|
<!-- Top Right Controls -->
|
||||||
<div class="top-controls">
|
<div class="top-controls">
|
||||||
<button class="icon-btn btn-tutorial" id="btn-tutorial" title="How to Play">
|
<button class="icon-btn btn-tutorial" id="btn-tutorial" title="How to Play">
|
||||||
|
|||||||
119
2048.js
119
2048.js
@ -1,3 +1,5 @@
|
|||||||
|
/* 2048.js — Complete Version with WASD + Interactive Effects + Sound Controls */
|
||||||
|
|
||||||
/* ------------------------
|
/* ------------------------
|
||||||
State & Variables
|
State & Variables
|
||||||
------------------------ */
|
------------------------ */
|
||||||
@ -16,15 +18,29 @@ const audio = {
|
|||||||
pop: new Audio("Pop.mp3"),
|
pop: new Audio("Pop.mp3"),
|
||||||
merge: new Audio("Merge.mp3")
|
merge: new Audio("Merge.mp3")
|
||||||
};
|
};
|
||||||
audio.bg.volume = 0.25;
|
|
||||||
audio.pop.volume = 0.9;
|
// Sound State (baca dari localStorage atau default ON)
|
||||||
audio.merge.volume = 1.0;
|
let soundState = {
|
||||||
|
bg: localStorage.getItem('sound_bg') !== 'false',
|
||||||
|
pop: localStorage.getItem('sound_pop') !== 'false',
|
||||||
|
merge: localStorage.getItem('sound_merge') !== 'false'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update audio volumes based on state
|
||||||
|
function updateAudioVolumes() {
|
||||||
|
audio.bg.volume = soundState.bg ? 0.25 : 0;
|
||||||
|
audio.pop.volume = soundState.pop ? 0.9 : 0;
|
||||||
|
audio.merge.volume = soundState.merge ? 1.0 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
audio.bg.loop = true;
|
audio.bg.loop = true;
|
||||||
|
|
||||||
function tryPlayBg() {
|
function tryPlayBg() {
|
||||||
|
if (!soundState.bg) return; // Jangan play kalau muted
|
||||||
|
|
||||||
audio.bg.play().catch(() => {
|
audio.bg.play().catch(() => {
|
||||||
const unlock = () => {
|
const unlock = () => {
|
||||||
audio.bg.play().catch(()=>{});
|
if (soundState.bg) audio.bg.play().catch(()=>{});
|
||||||
window.removeEventListener("keydown", unlock);
|
window.removeEventListener("keydown", unlock);
|
||||||
window.removeEventListener("click", unlock);
|
window.removeEventListener("click", unlock);
|
||||||
};
|
};
|
||||||
@ -41,6 +57,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
setupBoard();
|
setupBoard();
|
||||||
addNewTile();
|
addNewTile();
|
||||||
addNewTile();
|
addNewTile();
|
||||||
|
updateAudioVolumes(); // Apply saved sound settings
|
||||||
tryPlayBg();
|
tryPlayBg();
|
||||||
document.addEventListener("keydown", handleKey);
|
document.addEventListener("keydown", handleKey);
|
||||||
setupEventListeners();
|
setupEventListeners();
|
||||||
@ -94,12 +111,10 @@ function setupEventListeners() {
|
|||||||
btnHome.addEventListener('click', goHome);
|
btnHome.addEventListener('click', goHome);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close button (X) di game over modal
|
|
||||||
if (gameOverClose) {
|
if (gameOverClose) {
|
||||||
gameOverClose.addEventListener('click', hideGameOver);
|
gameOverClose.addEventListener('click', hideGameOver);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close game over when clicking outside modal
|
|
||||||
const gameOverOverlay = document.getElementById('game-over-overlay');
|
const gameOverOverlay = document.getElementById('game-over-overlay');
|
||||||
if (gameOverOverlay) {
|
if (gameOverOverlay) {
|
||||||
gameOverOverlay.addEventListener('click', function(e) {
|
gameOverOverlay.addEventListener('click', function(e) {
|
||||||
@ -108,6 +123,26 @@ function setupEventListeners() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sound Control Buttons
|
||||||
|
const btnSoundBg = document.getElementById('btn-sound-bg');
|
||||||
|
const btnSoundPop = document.getElementById('btn-sound-pop');
|
||||||
|
const btnSoundMerge = document.getElementById('btn-sound-merge');
|
||||||
|
|
||||||
|
if (btnSoundBg) {
|
||||||
|
btnSoundBg.addEventListener('click', () => toggleSound('bg'));
|
||||||
|
updateSoundButtonState(btnSoundBg, soundState.bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btnSoundPop) {
|
||||||
|
btnSoundPop.addEventListener('click', () => toggleSound('pop'));
|
||||||
|
updateSoundButtonState(btnSoundPop, soundState.pop);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btnSoundMerge) {
|
||||||
|
btnSoundMerge.addEventListener('click', () => toggleSound('merge'));
|
||||||
|
updateSoundButtonState(btnSoundMerge, soundState.merge);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------
|
/* ------------------------
|
||||||
@ -137,7 +172,6 @@ function setupBoard() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update single tile visual */
|
|
||||||
function updateTile(row, col, num) {
|
function updateTile(row, col, num) {
|
||||||
const tile = document.getElementById(`${row}-${col}`);
|
const tile = document.getElementById(`${row}-${col}`);
|
||||||
if (!tile) return;
|
if (!tile) return;
|
||||||
@ -152,7 +186,6 @@ function updateTile(row, col, num) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Refresh whole board */
|
|
||||||
function refreshBoard() {
|
function refreshBoard() {
|
||||||
for (let r = 0; r < 4; r++) {
|
for (let r = 0; r < 4; r++) {
|
||||||
for (let c = 0; c < 4; c++) {
|
for (let c = 0; c < 4; c++) {
|
||||||
@ -191,7 +224,7 @@ function resetScore() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------
|
/* ------------------------
|
||||||
Add New Tile - FIXED: Only play pop sound here
|
Add New Tile
|
||||||
------------------------ */
|
------------------------ */
|
||||||
function addNewTile() {
|
function addNewTile() {
|
||||||
const empty = [];
|
const empty = [];
|
||||||
@ -209,7 +242,6 @@ function addNewTile() {
|
|||||||
const tile = document.getElementById(`${spot.r}-${spot.c}`);
|
const tile = document.getElementById(`${spot.r}-${spot.c}`);
|
||||||
if (tile) {
|
if (tile) {
|
||||||
tile.classList.add("new");
|
tile.classList.add("new");
|
||||||
// ✅ POP SOUND: Hanya main di sini (tile baru muncul)
|
|
||||||
playSound(audio.pop);
|
playSound(audio.pop);
|
||||||
setTimeout(() => tile.classList.remove("new"), 300);
|
setTimeout(() => tile.classList.remove("new"), 300);
|
||||||
}
|
}
|
||||||
@ -217,16 +249,21 @@ function addNewTile() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Safe playSound */
|
/* Safe playSound with mute check */
|
||||||
function playSound(soundObj) {
|
function playSound(soundObj) {
|
||||||
try {
|
try {
|
||||||
|
// Check if sound is enabled
|
||||||
|
if (soundObj === audio.pop && !soundState.pop) return;
|
||||||
|
if (soundObj === audio.merge && !soundState.merge) return;
|
||||||
|
if (soundObj === audio.bg && !soundState.bg) return;
|
||||||
|
|
||||||
soundObj.currentTime = 0;
|
soundObj.currentTime = 0;
|
||||||
soundObj.play().catch(() => {});
|
soundObj.play().catch(() => {});
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------
|
/* ------------------------
|
||||||
Movement Logic - FIXED: Merge sound plays consistently
|
Movement Logic
|
||||||
------------------------ */
|
------------------------ */
|
||||||
function filterZero(row) {
|
function filterZero(row) {
|
||||||
return row.filter(n => n !== 0);
|
return row.filter(n => n !== 0);
|
||||||
@ -242,7 +279,6 @@ function slide(row) {
|
|||||||
if (row[i] === row[i + 1]) {
|
if (row[i] === row[i + 1]) {
|
||||||
row[i] = row[i] * 2;
|
row[i] = row[i] * 2;
|
||||||
|
|
||||||
// ✅ MERGE SOUND & VIBRATION: Selalu main saat merge
|
|
||||||
playSound(audio.merge);
|
playSound(audio.merge);
|
||||||
|
|
||||||
if (navigator.vibrate) {
|
if (navigator.vibrate) {
|
||||||
@ -266,7 +302,7 @@ 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 COMBO DETECTION */
|
/* Move functions */
|
||||||
function moveLeft() {
|
function moveLeft() {
|
||||||
let moved = false;
|
let moved = false;
|
||||||
let mergedCells = [];
|
let mergedCells = [];
|
||||||
@ -383,14 +419,13 @@ function moveDown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------
|
/* ------------------------
|
||||||
Input Handling - WITH WASD SUPPORT
|
Input Handling
|
||||||
------------------------ */
|
------------------------ */
|
||||||
function handleKey(e) {
|
function handleKey(e) {
|
||||||
if (isMoving) return;
|
if (isMoving) return;
|
||||||
|
|
||||||
let moved = false;
|
let moved = false;
|
||||||
|
|
||||||
// Arrow Keys
|
|
||||||
if (e.key === "ArrowLeft") {
|
if (e.key === "ArrowLeft") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
moved = moveLeft();
|
moved = moveLeft();
|
||||||
@ -407,7 +442,6 @@ function handleKey(e) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
moved = moveDown();
|
moved = moveDown();
|
||||||
}
|
}
|
||||||
// WASD Keys
|
|
||||||
else if (e.key === "a" || e.key === "A") {
|
else if (e.key === "a" || e.key === "A") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
moved = moveLeft();
|
moved = moveLeft();
|
||||||
@ -443,7 +477,6 @@ function handleKey(e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if any move is possible */
|
|
||||||
function canMove() {
|
function canMove() {
|
||||||
for (let r = 0; r < 4; r++) {
|
for (let r = 0; r < 4; r++) {
|
||||||
for (let c = 0; c < 4; c++) {
|
for (let c = 0; c < 4; c++) {
|
||||||
@ -573,12 +606,47 @@ function hideGameOver() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* =============================================
|
/* =============================================
|
||||||
COMBO EFFECT HANDLER
|
SOUND CONTROLS
|
||||||
|
============================================= */
|
||||||
|
function toggleSound(soundType) {
|
||||||
|
soundState[soundType] = !soundState[soundType];
|
||||||
|
localStorage.setItem('sound_' + soundType, soundState[soundType]);
|
||||||
|
updateAudioVolumes();
|
||||||
|
|
||||||
|
const button = document.getElementById('btn-sound-' + soundType);
|
||||||
|
if (button) {
|
||||||
|
updateSoundButtonState(button, soundState[soundType]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (soundType === 'bg') {
|
||||||
|
if (soundState.bg) {
|
||||||
|
tryPlayBg();
|
||||||
|
} else {
|
||||||
|
audio.bg.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSoundButtonState(button, isEnabled) {
|
||||||
|
if (!button) return;
|
||||||
|
|
||||||
|
if (isEnabled) {
|
||||||
|
button.classList.remove('muted');
|
||||||
|
button.querySelector('.sound-icon').style.display = 'block';
|
||||||
|
button.querySelector('.mute-icon').style.display = 'none';
|
||||||
|
} else {
|
||||||
|
button.classList.add('muted');
|
||||||
|
button.querySelector('.sound-icon').style.display = 'none';
|
||||||
|
button.querySelector('.mute-icon').style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================
|
||||||
|
COMBO EFFECTS
|
||||||
============================================= */
|
============================================= */
|
||||||
function triggerComboEffect(mergedCells, comboCount) {
|
function triggerComboEffect(mergedCells, comboCount) {
|
||||||
if (mergedCells.length === 0) return;
|
if (mergedCells.length === 0) return;
|
||||||
|
|
||||||
// Trigger individual tile effects
|
|
||||||
mergedCells.forEach(cell => {
|
mergedCells.forEach(cell => {
|
||||||
const tile = document.getElementById(`${cell.r}-${cell.c}`);
|
const tile = document.getElementById(`${cell.r}-${cell.c}`);
|
||||||
if (!tile) return;
|
if (!tile) return;
|
||||||
@ -593,7 +661,6 @@ function triggerComboEffect(mergedCells, comboCount) {
|
|||||||
tile.style.boxShadow = '';
|
tile.style.boxShadow = '';
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
// Individual score popup
|
|
||||||
const rect = tile.getBoundingClientRect();
|
const rect = tile.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;
|
||||||
@ -601,15 +668,11 @@ function triggerComboEffect(mergedCells, comboCount) {
|
|||||||
createScorePopup(centerX, centerY, tileValue);
|
createScorePopup(centerX, centerY, tileValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show COMBO popup based on merge count
|
|
||||||
if (comboCount >= 2) {
|
if (comboCount >= 2) {
|
||||||
showComboPopup(comboCount);
|
showComboPopup(comboCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =============================================
|
|
||||||
COMBO POPUP
|
|
||||||
============================================= */
|
|
||||||
function showComboPopup(comboCount) {
|
function showComboPopup(comboCount) {
|
||||||
const board = document.getElementById('board');
|
const board = document.getElementById('board');
|
||||||
if (!board) return;
|
if (!board) return;
|
||||||
@ -630,7 +693,6 @@ function showComboPopup(comboCount) {
|
|||||||
popup.style.textTransform = 'uppercase';
|
popup.style.textTransform = 'uppercase';
|
||||||
popup.style.letterSpacing = '3px';
|
popup.style.letterSpacing = '3px';
|
||||||
|
|
||||||
// Different text and color based on combo count
|
|
||||||
if (comboCount === 2) {
|
if (comboCount === 2) {
|
||||||
popup.textContent = 'COMBO x2!';
|
popup.textContent = 'COMBO x2!';
|
||||||
popup.style.fontSize = '36px';
|
popup.style.fontSize = '36px';
|
||||||
@ -650,7 +712,6 @@ function showComboPopup(comboCount) {
|
|||||||
|
|
||||||
document.body.appendChild(popup);
|
document.body.appendChild(popup);
|
||||||
|
|
||||||
// Animate combo popup
|
|
||||||
popup.animate([
|
popup.animate([
|
||||||
{
|
{
|
||||||
transform: 'translate(-50%, -50%) scale(0.3) rotate(-10deg)',
|
transform: 'translate(-50%, -50%) scale(0.3) rotate(-10deg)',
|
||||||
@ -676,9 +737,6 @@ function showComboPopup(comboCount) {
|
|||||||
}).onfinish = () => popup.remove();
|
}).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;
|
||||||
@ -772,3 +830,4 @@ function getTileColor(value) {
|
|||||||
};
|
};
|
||||||
return colors[value] || '#00eaff';
|
return colors[value] || '#00eaff';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user