Coment section
This commit is contained in:
parent
7499811a24
commit
4243584706
@ -1,33 +1,58 @@
|
|||||||
/* ------------------------
|
/* ==========================================
|
||||||
6. INPUT HANDLER
|
2048 CONTROLS
|
||||||
------------------------
|
==========================================
|
||||||
*/
|
fungsi utama:
|
||||||
|
1. handleKey() - mendeteksi keyboard (Arrow/WASD)
|
||||||
|
2. Touch Events - meneteksi swipe mobile
|
||||||
|
3. inputLocked - prevent konflik saat panel volume buka
|
||||||
|
========================================== */
|
||||||
|
|
||||||
|
// flag untuk blokir input game saat setting panel aktif
|
||||||
let inputLocked = false;
|
let inputLocked = false;
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
KEYBOARD INPUT HANDLER
|
||||||
|
========================================== */
|
||||||
function handleKey(e) {
|
function handleKey(e) {
|
||||||
if (isMoving || inputLocked) return; // ⛔ BLOK kalau sedang buka sound panel
|
// Blokir input kalau:
|
||||||
|
// 1. Tile sedang bergerak (isMoving = true)
|
||||||
|
// 2. Panel volume sedang dibuka (inputLocked = true)
|
||||||
|
if (isMoving || inputLocked) return;
|
||||||
|
|
||||||
|
|
||||||
let moved = false;
|
let moved = false;
|
||||||
const k = e.key;
|
const k = e.key;
|
||||||
|
|
||||||
if (k === "ArrowLeft" || k === "a" || k === "A") { e.preventDefault(); moved = moveLeft(); }
|
// Deteksi 4 arah: Arrow keys atau WASD
|
||||||
else if (k === "ArrowRight" || k === "d" || k === "D") { e.preventDefault(); moved = moveRight(); }
|
if (k === "ArrowLeft" || k === "a" || k === "A") {
|
||||||
else if (k === "ArrowUp" || k === "w" || k === "W") { e.preventDefault(); moved = moveUp(); }
|
e.preventDefault(); // Cegah scroll halaman
|
||||||
else if (k === "ArrowDown" || k === "s" || k === "S") { e.preventDefault(); moved = moveDown(); }
|
moved = moveLeft(); }
|
||||||
|
else if (k === "ArrowRight" || k === "d" || k === "D") {
|
||||||
|
e.preventDefault();
|
||||||
|
moved = moveRight(); }
|
||||||
|
else if (k === "ArrowUp" || k === "w" || k === "W") {
|
||||||
|
e.preventDefault();
|
||||||
|
moved = moveUp(); }
|
||||||
|
else if (k === "ArrowDown" || k === "s" || k === "S") {
|
||||||
|
e.preventDefault();
|
||||||
|
moved = moveDown(); }
|
||||||
|
|
||||||
if (moved) {
|
// Kalau tile berhasil bergerak
|
||||||
isMoving = true;
|
if (moved) {
|
||||||
|
isMoving = true; // Lock input biar nggak spam
|
||||||
|
|
||||||
|
// Delay 100ms untuk animasi selesai
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const added = addNewTile();
|
const added = addNewTile(); // Spawn tile baru
|
||||||
|
|
||||||
|
// Cek game over: board penuh ATAU nggak bisa move
|
||||||
if (!added || !canMove()) {
|
if (!added || !canMove()) {
|
||||||
setTimeout(() => showGameOver(), 300);
|
setTimeout(() => showGameOver(), 300);
|
||||||
}
|
}
|
||||||
isMoving = false;
|
isMoving = false; // Unlock input
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Shake effect on invalid move
|
//Invalid move - Kasih feedback shake
|
||||||
const b = document.getElementById("board");
|
const b = document.getElementById("board");
|
||||||
if (b) {
|
if (b) {
|
||||||
b.classList.add("shake");
|
b.classList.add("shake");
|
||||||
@ -36,37 +61,49 @@ function handleKey(e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Touch Swipe */
|
/* ==========================================
|
||||||
|
TOUCH/SWIPE INPUT HANDLER (Mobile)
|
||||||
|
========================================== */
|
||||||
|
|
||||||
|
// Simpan posisi saat awal sentuhan
|
||||||
let touchStartX = 0;
|
let touchStartX = 0;
|
||||||
let touchStartY = 0;
|
let touchStartY = 0;
|
||||||
|
|
||||||
|
// Event 1: Catat posisi awal saat jari menyentuh layar
|
||||||
document.addEventListener("touchstart", function (e) {
|
document.addEventListener("touchstart", function (e) {
|
||||||
if (inputLocked) return; // ⛔ jangan catat swipe
|
if (inputLocked) return; // Jangan catat swipe kalau panel volume aktif
|
||||||
|
|
||||||
const t = e.touches[0];
|
const t = e.touches[0];
|
||||||
touchStartX = t.clientX;
|
touchStartX = t.clientX; // Posisi X awal
|
||||||
touchStartY = t.clientY;
|
touchStartY = t.clientY; // Posisi Y awal
|
||||||
}, { passive: true });
|
}, { passive: true }); // Passive = performa lebih smooth
|
||||||
|
|
||||||
|
|
||||||
|
// Event 2: Deteksi arah swipe saat jari diangkat
|
||||||
document.addEventListener("touchend", function (e) {
|
document.addEventListener("touchend", function (e) {
|
||||||
if (isMoving || inputLocked) return; // ⛔ swipe diblok saat sound panel aktif
|
if (isMoving || inputLocked) return; // cek isMoving DAN inputLocked
|
||||||
|
|
||||||
const t = e.changedTouches[0];
|
const t = e.changedTouches[0];
|
||||||
const dx = t.clientX - touchStartX;
|
|
||||||
const dy = t.clientY - touchStartY;
|
// Hitung selisih posisi (delta)
|
||||||
|
const dx = t.clientX - touchStartX; //Horizontal movement
|
||||||
|
const dy = t.clientY - touchStartY; //Vertical movement
|
||||||
|
|
||||||
let moved = false;
|
let moved = false;
|
||||||
|
|
||||||
|
// Tentukan arah swipe berdasarkan delta terbesar
|
||||||
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 30) {
|
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 30) {
|
||||||
if (dx > 0) moved = moveRight();
|
// Swipe HORIZONTAL (kiri/kanan)
|
||||||
else moved = moveLeft();
|
// Minimal 30px biar nggak terlalu sensitif
|
||||||
|
if (dx > 0) moved = moveRight(); // swipe kanan
|
||||||
|
else moved = moveLeft(); // swipe kiri
|
||||||
|
|
||||||
} else if (Math.abs(dy) > 30) {
|
} else if (Math.abs(dy) > 30) {
|
||||||
if (dy > 0) moved = moveDown();
|
// Swipe VERTICAL (atas/bawah)
|
||||||
else moved = moveUp();
|
if (dy > 0) moved = moveDown(); // swipe bawah
|
||||||
|
else moved = moveUp(); // swipe atas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logic sama seperti keyboard handler
|
||||||
if (moved) {
|
if (moved) {
|
||||||
isMoving = true;
|
isMoving = true;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
134
2048_Logic.js
134
2048_Logic.js
@ -1,79 +1,138 @@
|
|||||||
/* ------------------------
|
/* ==========================================
|
||||||
5. GAME LOGIC
|
2048 LOGIC
|
||||||
------------------------ */
|
==========================================
|
||||||
|
fungsi inti:
|
||||||
|
1. addNewTile() - spawn tile baru (angka 2)
|
||||||
|
2. slide() - algoritma merge tile
|
||||||
|
3. move functions - 4 arah pergerakan
|
||||||
|
4. canMove() - Cek game over
|
||||||
|
========================================== */
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
SPAWN TILE BARU
|
||||||
|
========================================== */
|
||||||
|
|
||||||
function addNewTile() {
|
function addNewTile() {
|
||||||
|
// Step 1: Cari semua cell kosong (value = 0)
|
||||||
const empty = [];
|
const empty = [];
|
||||||
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++) {
|
||||||
if (board[r][c] === 0) empty.push({ r, c });
|
if (board[r][c] === 0) empty.push({ r, c });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kalau board penuh, return false (game over)
|
||||||
if (empty.length === 0) return false;
|
if (empty.length === 0) return false;
|
||||||
|
|
||||||
|
// Step 2: Pilih 1 posisi random dari cell kosong
|
||||||
const spot = empty[Math.floor(Math.random() * empty.length)];
|
const spot = empty[Math.floor(Math.random() * empty.length)];
|
||||||
board[spot.r][spot.c] = 2;
|
board[spot.r][spot.c] = 2; // Tile baru selalu angka 2
|
||||||
|
|
||||||
|
// Step 3: Animasi "new" + sound effect
|
||||||
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"); // Trigger animasi pop
|
||||||
playSound(audio.pop);
|
playSound(audio.pop); // Sound effect
|
||||||
setTimeout(() => tile.classList.remove("new"), 300);
|
setTimeout(() => tile.classList.remove("new"), 300);
|
||||||
}
|
}
|
||||||
updateTile(spot.r, spot.c, 2);
|
|
||||||
|
updateTile(spot.r, spot.c, 2); // Update visual tile
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
HELPER: FILTER ANGKA NOL
|
||||||
|
========================================== */
|
||||||
function filterZero(row) {
|
function filterZero(row) {
|
||||||
|
// Buang semua 0 dari array
|
||||||
|
// Contoh: [2, 0, 2, 0] → [2, 2]
|
||||||
return row.filter(n => n !== 0);
|
return row.filter(n => n !== 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
ALGORITMA SLIDE & MERGE (CORE!)
|
||||||
|
==========================================
|
||||||
|
Ini yang bikin 2+2=4, 4+4=8, dst
|
||||||
|
========================================== */
|
||||||
function slide(row) {
|
function slide(row) {
|
||||||
|
// Step 1: Filter zero - kumpulkan tile
|
||||||
|
// [2, 0, 2, 4] → [2, 2, 4]
|
||||||
row = filterZero(row);
|
row = filterZero(row);
|
||||||
let mergedThisMove = false;
|
|
||||||
let mergedPositions = [];
|
|
||||||
let mergeCount = 0;
|
|
||||||
|
|
||||||
|
let mergedThisMove = false;
|
||||||
|
let mergedPositions = []; // Posisi tile yang di-merge (untuk animasi)
|
||||||
|
let mergeCount = 0; // Hitung berapa kali merge (untuk combo)
|
||||||
|
|
||||||
|
// Step 2: Loop cek tile sebelahan
|
||||||
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]) {
|
||||||
row[i] = row[i] * 2;
|
// MERGE! Tile sama ketemu
|
||||||
|
row[i] = row[i] * 2; // Tile pertama jadi 2x lipat
|
||||||
|
|
||||||
playSound(audio.merge);
|
playSound(audio.merge); // Sound effect merge
|
||||||
|
|
||||||
|
// Haptic feedback (getaran) di mobile
|
||||||
if (navigator.vibrate) {
|
if (navigator.vibrate) {
|
||||||
navigator.vibrate([80, 20, 80]);
|
navigator.vibrate([80, 20, 80]);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentScore += row[i];
|
currentScore += row[i]; // Tambah score
|
||||||
row[i + 1] = 0;
|
row[i + 1] = 0; // Tile kedua hilang (jadi 0)
|
||||||
|
|
||||||
mergedThisMove = true;
|
mergedThisMove = true;
|
||||||
mergedPositions.push(i);
|
mergedPositions.push(i); // Simpan posisi untuk animasi
|
||||||
mergeCount++;
|
mergeCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 3: Filter zero lagi & padding
|
||||||
|
// [4, 0, 4] → [4, 4] → [4, 4, 0, 0]
|
||||||
row = filterZero(row);
|
row = filterZero(row);
|
||||||
while (row.length < 4) row.push(0);
|
while (row.length < 4) row.push(0); // Padding 0 di kanan
|
||||||
return { row, merged: mergedThisMove, mergedPositions, mergeCount };
|
|
||||||
|
return {
|
||||||
|
row,
|
||||||
|
merged: mergedThisMove,
|
||||||
|
mergedPositions,
|
||||||
|
mergeCount };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
HELPER: CEK ARRAY SAMA ATAU TIDAK
|
||||||
|
========================================== */
|
||||||
function arraysEqual(a, b) {
|
function arraysEqual(a, b) {
|
||||||
|
// Bandingin setiap elemen
|
||||||
|
// Dipakai untuk cek apakah board berubah setelah move
|
||||||
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 */
|
/* ==========================================
|
||||||
|
MOVE FUNCTIONS - 4 ARAH PERGERAKAN
|
||||||
|
==========================================
|
||||||
|
Semua punya struktur yang sama:
|
||||||
|
1. Loop setiap baris/kolom
|
||||||
|
2. Panggil slide() untuk merge
|
||||||
|
3. Cek perubahan dengan arraysEqual()
|
||||||
|
4. Refresh board kalau ada perubahan
|
||||||
|
========================================== */
|
||||||
|
|
||||||
|
/* MOVE LEFT - Geser ke kiri */
|
||||||
function moveLeft() {
|
function moveLeft() {
|
||||||
let moved = false;
|
let moved = false;
|
||||||
let mergedCells = [];
|
let mergedCells = []; // Track cell yang di-merge
|
||||||
mergesInCurrentMove = 0;
|
mergesInCurrentMove = 0; // Reset combo counter
|
||||||
|
|
||||||
|
// Loop setiap baris (horizontal)
|
||||||
for (let r = 0; r < 4; r++) {
|
for (let r = 0; r < 4; r++) {
|
||||||
const { row: newRow, mergedPositions, mergeCount } = slide(board[r]);
|
const { row: newRow, mergedPositions, mergeCount } = slide(board[r]);
|
||||||
|
|
||||||
|
// Cek apakah row berubah
|
||||||
if (!arraysEqual(newRow, board[r])) moved = true;
|
if (!arraysEqual(newRow, board[r])) moved = true;
|
||||||
board[r] = newRow;
|
board[r] = newRow;
|
||||||
|
|
||||||
mergesInCurrentMove += mergeCount;
|
mergesInCurrentMove += mergeCount;
|
||||||
|
|
||||||
|
// Simpan posisi cell yang merge untuk combo effect
|
||||||
if (mergedPositions && mergedPositions.length > 0) {
|
if (mergedPositions && mergedPositions.length > 0) {
|
||||||
mergedPositions.forEach(c => {
|
mergedPositions.forEach(c => {
|
||||||
mergedCells.push({ r, c });
|
mergedCells.push({ r, c });
|
||||||
@ -81,6 +140,7 @@ function moveLeft() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kalau ada pergerakan, update visual + effect
|
||||||
if (moved) {
|
if (moved) {
|
||||||
refreshBoard();
|
refreshBoard();
|
||||||
triggerComboEffect(mergedCells, mergesInCurrentMove);
|
triggerComboEffect(mergedCells, mergesInCurrentMove);
|
||||||
@ -88,23 +148,28 @@ function moveLeft() {
|
|||||||
return moved;
|
return moved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* MOVE RIGHT - Geser ke kanan */
|
||||||
function moveRight() {
|
function moveRight() {
|
||||||
let moved = false;
|
let moved = false;
|
||||||
let mergedCells = [];
|
let mergedCells = [];
|
||||||
mergesInCurrentMove = 0;
|
mergesInCurrentMove = 0;
|
||||||
|
|
||||||
|
// TRICK: Reverse → slide → reverse lagi
|
||||||
|
// Supaya bisa pakai logic slide yang sama
|
||||||
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, mergeCount } = slide(reversed);
|
const { row: slid, mergedPositions, mergeCount } = slide(reversed);
|
||||||
let newRow = slid.reverse();
|
let newRow = slid.reverse(); // Balik lagi ke posisi asli
|
||||||
|
|
||||||
if (!arraysEqual(newRow, board[r])) moved = true;
|
if (!arraysEqual(newRow, board[r])) moved = true;
|
||||||
board[r] = newRow;
|
board[r] = newRow;
|
||||||
|
|
||||||
mergesInCurrentMove += mergeCount;
|
mergesInCurrentMove += mergeCount;
|
||||||
|
|
||||||
|
// Convert posisi merge ke koordinat asli (dari kanan)
|
||||||
if (mergedPositions && mergedPositions.length > 0) {
|
if (mergedPositions && mergedPositions.length > 0) {
|
||||||
mergedPositions.forEach(pos => {
|
mergedPositions.forEach(pos => {
|
||||||
const c = 3 - pos;
|
const c = 3 - pos; // Mirror position
|
||||||
mergedCells.push({ r, c });
|
mergedCells.push({ r, c });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -117,14 +182,19 @@ function moveRight() {
|
|||||||
return moved;
|
return moved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* MOVE UP - Geser ke atas */
|
||||||
function moveUp() {
|
function moveUp() {
|
||||||
let moved = false;
|
let moved = false;
|
||||||
let mergedCells = [];
|
let mergedCells = [];
|
||||||
mergesInCurrentMove = 0;
|
mergesInCurrentMove = 0;
|
||||||
|
|
||||||
|
// Loop setiap kolom (vertical)
|
||||||
for (let c = 0; c < 4; c++) {
|
for (let c = 0; c < 4; c++) {
|
||||||
|
// Ambil kolom vertikal sebagai array
|
||||||
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, mergeCount } = slide(col);
|
const { row: newCol, mergedPositions, mergeCount } = slide(col);
|
||||||
|
|
||||||
|
// Update board per row
|
||||||
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];
|
||||||
@ -146,15 +216,18 @@ function moveUp() {
|
|||||||
return moved;
|
return moved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* MOVE DOWN - Geser ke bawah */
|
||||||
function moveDown() {
|
function moveDown() {
|
||||||
let moved = false;
|
let moved = false;
|
||||||
let mergedCells = [];
|
let mergedCells = [];
|
||||||
mergesInCurrentMove = 0;
|
mergesInCurrentMove = 0;
|
||||||
|
|
||||||
for (let c = 0; c < 4; c++) {
|
for (let c = 0; c < 4; c++) {
|
||||||
|
//TRICK: Ambil kolom dari bawah ke atas (reversed)
|
||||||
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, mergeCount } = slide(col);
|
const { row: slid, mergedPositions, mergeCount } = slide(col);
|
||||||
const newCol = slid.reverse();
|
const newCol = slid.reverse(); // Balik ke urutan normal
|
||||||
|
|
||||||
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];
|
||||||
@ -162,9 +235,10 @@ function moveDown() {
|
|||||||
|
|
||||||
mergesInCurrentMove += mergeCount;
|
mergesInCurrentMove += mergeCount;
|
||||||
|
|
||||||
|
// Convert posisi merge ke koordinat asli (dari bawah)
|
||||||
if (mergedPositions && mergedPositions.length > 0) {
|
if (mergedPositions && mergedPositions.length > 0) {
|
||||||
mergedPositions.forEach(pos => {
|
mergedPositions.forEach(pos => {
|
||||||
const r = 3 - pos;
|
const r = 3 - pos; // Mirror position
|
||||||
mergedCells.push({ r, c });
|
mergedCells.push({ r, c });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -177,19 +251,27 @@ function moveDown() {
|
|||||||
return moved;
|
return moved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
CEK GAME OVER
|
||||||
|
========================================== */
|
||||||
function canMove() {
|
function canMove() {
|
||||||
|
// Kondisi 1: Ada cell kosong?
|
||||||
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++) {
|
||||||
if (board[r][c] === 0) return true;
|
if (board[r][c] === 0) return true; // Masih ada ruang
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kondisi 2: Ada tile sebelahan yang bisa di-merge?
|
||||||
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++) {
|
||||||
const current = board[r][c];
|
const current = board[r][c];
|
||||||
|
//cek kanan
|
||||||
if (c < 3 && board[r][c + 1] === current) return true;
|
if (c < 3 && board[r][c + 1] === current) return true;
|
||||||
|
//cek bawah
|
||||||
if (r < 3 && board[r + 1][c] === current) return true;
|
if (r < 3 && board[r + 1][c] === current) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Kalau kedua kondisi false → STUCK = Game Over
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user