2048
This commit is contained in:
parent
02b324ba37
commit
021d61a1b0
153
2048.css
153
2048.css
@ -144,3 +144,156 @@ button:hover {
|
||||
.tile-1024 { background: #00ffaa55; box-shadow: 0 0 10px #00ffaa; }
|
||||
.tile-2048 { background: #ffd70066; box-shadow: 0 0 15px #ffd700; }
|
||||
|
||||
/* ======= ENHANCEMENTS: Animations, Particles, Glows ======= */
|
||||
|
||||
/* ensure board stacking context for absolute animations */
|
||||
#board { position: relative; z-index: 2; }
|
||||
|
||||
.particles {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
|
||||
background:
|
||||
radial-gradient(circle at 20% 30%, rgba(0,234,255,0.18), transparent 45%),
|
||||
radial-gradient(circle at 80% 70%, rgba(255,0,90,0.18), transparent 45%),
|
||||
radial-gradient(circle at 50% 50%, rgba(140,0,255,0.14), transparent 55%),
|
||||
radial-gradient(circle at 10% 85%, rgba(0,255,200,0.12), transparent 55%),
|
||||
radial-gradient(circle at 90% 15%, rgba(255,0,255,0.15), transparent 45%);
|
||||
|
||||
filter: blur(55px) brightness(120%) saturate(130%);
|
||||
animation: particlesFloat 18s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes particlesFloat {
|
||||
0% { transform: translateY(0px) translateX(0px); }
|
||||
50% { transform: translateY(-25px) translateX(10px); }
|
||||
100% { transform: translateY(-40px) translateX(-15px); }
|
||||
}
|
||||
|
||||
|
||||
.cursor-light {
|
||||
position: absolute;
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
background: radial-gradient(circle, rgba(0,255,255,0.25), transparent 70%);
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, -50%);
|
||||
filter: blur(40px);
|
||||
mix-blend-mode: screen;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
|
||||
/* gentle vertical float */
|
||||
@keyframes floatBg { 0% { transform: translateY(0);} 100% { transform: translateY(-14px);} }
|
||||
|
||||
/* Tile move / slide illusion: tiles appear from direction */
|
||||
.tile {
|
||||
transition: transform 0.14s cubic-bezier(.2,.8,.2,1), opacity 0.12s linear, box-shadow 0.12s;
|
||||
will-change: transform, opacity, box-shadow;
|
||||
}
|
||||
|
||||
/* 'new' pop kept but slightly tuned */
|
||||
.tile.new {
|
||||
animation: pop 0.22s cubic-bezier(.2,.9,.2,1);
|
||||
}
|
||||
|
||||
/* merge pop */
|
||||
.tile.merge {
|
||||
animation: mergePop 0.24s ease-out;
|
||||
z-index: 3;
|
||||
}
|
||||
@keyframes mergePop {
|
||||
0% { transform: scale(1); box-shadow: 0 0 6px rgba(255,255,255,0.1); }
|
||||
50% { transform: scale(1.18); box-shadow: 0 0 30px rgba(255,255,255,0.35); }
|
||||
100% { transform: scale(1); box-shadow: 0 0 6px rgba(255,255,255,0.1); }
|
||||
}
|
||||
|
||||
/* board shake when invalid move */
|
||||
#board.shake {
|
||||
animation: shakeBoard 0.36s;
|
||||
}
|
||||
@keyframes shakeBoard {
|
||||
0%,100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-8px); }
|
||||
75% { transform: translateX(8px); }
|
||||
}
|
||||
|
||||
/* ambient glow under board (keeps aesthetics) */
|
||||
#board::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -36px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 340px;
|
||||
height: 84px;
|
||||
background: radial-gradient(ellipse at center, rgba(0,234,255,0.12), transparent 60%);
|
||||
filter: blur(30px);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* special 2048 tile sparkle (adds shimmer) */
|
||||
.tile-2048 {
|
||||
animation: goldShimmer 2.4s infinite;
|
||||
}
|
||||
@keyframes goldShimmer {
|
||||
0% { box-shadow: 0 0 10px #ffd70066; transform: translateZ(0); }
|
||||
50% { box-shadow: 0 0 26px #ffd700aa; transform: translateY(-2px); }
|
||||
100% { box-shadow: 0 0 10px #ffd70066; transform: translateZ(0); }
|
||||
}
|
||||
|
||||
/* small particle bits that appear for merge */
|
||||
.merge-particle {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
opacity: 0.95;
|
||||
will-change: transform, opacity;
|
||||
filter: blur(1px) drop-shadow(0 0 6px rgba(255,255,255,0.08));
|
||||
}
|
||||
|
||||
/* ensure base tile readability on animation */
|
||||
.tile { backface-visibility: hidden; -webkit-backface-visibility: hidden; }
|
||||
|
||||
/* mobile touch hint (optional) */
|
||||
.touch-hint {
|
||||
position: fixed;
|
||||
right: 18px;
|
||||
bottom: 18px;
|
||||
background: rgba(0,0,0,0.45);
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
font-size: 12px;
|
||||
z-index: 4;
|
||||
color: #cfefff;
|
||||
}
|
||||
|
||||
.starfield {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.starfield span {
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: rgba(0,255,255,0.8);
|
||||
border-radius: 50%;
|
||||
filter: blur(2px);
|
||||
animation: starMove linear infinite;
|
||||
}
|
||||
|
||||
/* bintang bergerak random */
|
||||
@keyframes starMove {
|
||||
0% { transform: translateY(0); opacity: 0.8; }
|
||||
100% { transform: translateY(-700px); opacity: 0; }
|
||||
}
|
||||
|
||||
@ -6,8 +6,11 @@
|
||||
<title>2048</title>
|
||||
<link rel="stylesheet" href="2048.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- ADD: ambient particle / neon background layer -->
|
||||
<div class="particles" aria-hidden="true"></div>
|
||||
<div class="starfield"></div>
|
||||
<div class="cursor-light"></div>
|
||||
<h1>2048</h1>
|
||||
<div id="top-menu">
|
||||
<button class="btn" onclick="restartGame()">Restart</button>
|
||||
|
||||
346
2048.js
346
2048.js
@ -1,5 +1,13 @@
|
||||
/* 2048.js — Enhanced with animations, particles, and glows
|
||||
Replace previous 2048.js content with this file.
|
||||
*/
|
||||
|
||||
/* ------------------------
|
||||
State & audio (kept)
|
||||
------------------------ */
|
||||
let board = [];
|
||||
let score = 0;
|
||||
let lastMoveDir = null; // 'left','right','up','down' or null
|
||||
|
||||
// --- Audio setup ---
|
||||
const audio = {
|
||||
@ -7,17 +15,13 @@ const audio = {
|
||||
pop: new Audio("pop.mp3"),
|
||||
merge: new Audio("merge.wav")
|
||||
};
|
||||
|
||||
// lower default volumes
|
||||
audio.bg.volume = 0.25;
|
||||
audio.pop.volume = 0.9;
|
||||
audio.merge.volume = 0.9;
|
||||
audio.bg.loop = true;
|
||||
|
||||
// try to play background music; may be blocked until user interaction
|
||||
function tryPlayBg() {
|
||||
audio.bg.play().catch(() => {
|
||||
// autoplay blocked — will try again on first user keypress or click
|
||||
const unlock = () => {
|
||||
audio.bg.play().catch(()=>{});
|
||||
window.removeEventListener("keydown", unlock);
|
||||
@ -28,20 +32,21 @@ function tryPlayBg() {
|
||||
});
|
||||
}
|
||||
|
||||
// --- DOM ready: setup board and initial tiles ---
|
||||
/* ------------------------
|
||||
DOM ready
|
||||
------------------------ */
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
setupBoard();
|
||||
addNewTile();
|
||||
addNewTile();
|
||||
tryPlayBg();
|
||||
|
||||
// keyboard controls (also used to unlock audio)
|
||||
document.addEventListener("keydown", handleKey);
|
||||
setupAmbientCursor();
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// SETUP BOARD (render tiles ONCE)
|
||||
// ----------------------------
|
||||
/* ------------------------
|
||||
Setup & rendering
|
||||
------------------------ */
|
||||
function setupBoard() {
|
||||
board = [];
|
||||
score = 0;
|
||||
@ -53,7 +58,6 @@ function setupBoard() {
|
||||
return;
|
||||
}
|
||||
|
||||
// empty container and create fixed 4x4 cells
|
||||
container.innerHTML = "";
|
||||
for (let r = 0; r < 4; r++) {
|
||||
board[r] = [];
|
||||
@ -61,40 +65,55 @@ function setupBoard() {
|
||||
board[r][c] = 0;
|
||||
const tile = document.createElement("div");
|
||||
tile.id = `${r}-${c}`;
|
||||
tile.className = "tile"; // base class only
|
||||
// ensure box sizing not influenced by text nodes
|
||||
tile.style.boxSizing = "border-box";
|
||||
tile.className = "tile";
|
||||
container.appendChild(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// UPDATE UI FOR SINGLE TILE
|
||||
// ----------------------------
|
||||
/* update single tile visual with small entrance based on last move */
|
||||
function updateTile(row, col, num) {
|
||||
const tile = document.getElementById(`${row}-${col}`);
|
||||
if (!tile) return;
|
||||
|
||||
// reset classes to base
|
||||
// reset classes except base .tile
|
||||
tile.className = "tile";
|
||||
|
||||
// force reflow to allow re-adding 'new' animation class reliably
|
||||
void tile.offsetWidth;
|
||||
// ensure previous transforms cleared
|
||||
tile.style.transform = "";
|
||||
tile.style.opacity = "";
|
||||
|
||||
if (num > 0) {
|
||||
tile.textContent = num;
|
||||
// add tile class for color (expects classes like tile-2, tile-4, ...)
|
||||
tile.classList.add("tile-" + num);
|
||||
|
||||
// make numbers visually white-neon glow if desired:
|
||||
// ensure text is centered by CSS; no inline style needed
|
||||
// slide-illusion: appear from direction of last move
|
||||
if (lastMoveDir) {
|
||||
let tx = 0, ty = 0;
|
||||
const gap = 22; // small px offset for feel
|
||||
if (lastMoveDir === "left") tx = gap;
|
||||
else if (lastMoveDir === "right") tx = -gap;
|
||||
else if (lastMoveDir === "up") ty = gap;
|
||||
else if (lastMoveDir === "down") ty = -gap;
|
||||
|
||||
// start slightly offset & transparent, then animate to 0
|
||||
tile.style.transform = `translate(${tx}px, ${ty}px)`;
|
||||
tile.style.opacity = "0.0";
|
||||
// force reflow then animate back
|
||||
void tile.offsetWidth;
|
||||
tile.style.transition = "transform 0.14s cubic-bezier(.2,.8,.2,1), opacity 0.12s";
|
||||
tile.style.transform = "";
|
||||
tile.style.opacity = "1";
|
||||
// cleanup transition after done
|
||||
setTimeout(() => { tile.style.transition = ""; }, 160);
|
||||
}
|
||||
|
||||
} else {
|
||||
tile.textContent = "";
|
||||
}
|
||||
}
|
||||
|
||||
// updates entire board DOM from board array
|
||||
/* refresh whole board */
|
||||
function refreshBoard() {
|
||||
for (let r = 0; r < 4; r++) {
|
||||
for (let c = 0; c < 4; c++) {
|
||||
@ -104,17 +123,13 @@ function refreshBoard() {
|
||||
updateScore();
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// SCORE UI
|
||||
// ----------------------------
|
||||
/* score */
|
||||
function updateScore() {
|
||||
const el = document.getElementById("score");
|
||||
if (el) el.textContent = score;
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// ADD NEW TILE (value 2, play pop sound + animation)
|
||||
// ----------------------------
|
||||
/* add new tile with pop animation */
|
||||
function addNewTile() {
|
||||
const empty = [];
|
||||
for (let r = 0; r < 4; r++) {
|
||||
@ -131,65 +146,42 @@ function addNewTile() {
|
||||
const tile = document.getElementById(`${spot.r}-${spot.c}`);
|
||||
if (tile) {
|
||||
tile.classList.add("new");
|
||||
// play pop sound
|
||||
playSound(audio.pop);
|
||||
// remove 'new' class after animation completes to keep DOM tidy
|
||||
tile.addEventListener("animationend", function handler() {
|
||||
tile.classList.remove("new");
|
||||
tile.removeEventListener("animationend", handler);
|
||||
});
|
||||
// update text/color
|
||||
updateTile(spot.r, spot.c, 2);
|
||||
} else {
|
||||
// fallback
|
||||
updateTile(spot.r, spot.c, 2);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// helper to play sound safely
|
||||
/* safe playSound */
|
||||
function playSound(soundObj) {
|
||||
try {
|
||||
soundObj.currentTime = 0;
|
||||
soundObj.play().catch(() => {
|
||||
// suppressed (autoplay or other)
|
||||
});
|
||||
} catch (e) { /* ignore */ }
|
||||
soundObj.play().catch(() => {});
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// INPUT HANDLING
|
||||
// ----------------------------
|
||||
function handleKey(e) {
|
||||
let moved = false;
|
||||
if (e.key === "ArrowLeft") moved = moveLeft();
|
||||
else if (e.key === "ArrowRight") moved = moveRight();
|
||||
else if (e.key === "ArrowUp") moved = moveUp();
|
||||
else if (e.key === "ArrowDown") moved = moveDown();
|
||||
|
||||
if (moved) {
|
||||
// after a successful move: add tile, refresh board, play bg or unlock
|
||||
addNewTile();
|
||||
refreshBoard();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- MOVE HELPERS ----------
|
||||
/* ------------------------
|
||||
Movement helpers (logic preserved)
|
||||
------------------------ */
|
||||
function filterZero(row) {
|
||||
return row.filter(n => n !== 0);
|
||||
}
|
||||
|
||||
function slide(row) {
|
||||
// row is an array of length 4
|
||||
row = filterZero(row);
|
||||
let mergedThisMove = false;
|
||||
|
||||
for (let i = 0; i < row.length - 1; i++) {
|
||||
if (row[i] === row[i + 1]) {
|
||||
row[i] = row[i] * 2;
|
||||
// play merge sound and vibrate
|
||||
playSound(audio.merge);
|
||||
if (navigator.vibrate) navigator.vibrate(30);
|
||||
if (navigator.vibrate) navigator.vibrate(28);
|
||||
|
||||
score += row[i];
|
||||
row[i + 1] = 0;
|
||||
@ -202,7 +194,6 @@ function slide(row) {
|
||||
return { row, merged: mergedThisMove };
|
||||
}
|
||||
|
||||
// Compare arrays helper
|
||||
function arraysEqual(a, b) {
|
||||
return a.length === b.length && a.every((v, i) => v === b[i]);
|
||||
}
|
||||
@ -260,17 +251,44 @@ function moveDown() {
|
||||
return moved;
|
||||
}
|
||||
|
||||
/* after move: refresh and reset lastMoveDir after small delay */
|
||||
function updateAfterMove() {
|
||||
// update all tiles now (this keeps sizes stable)
|
||||
// apply merge glow to merged tiles (scan for high values that were recently created)
|
||||
refreshBoard();
|
||||
// update score DOM
|
||||
updateScore();
|
||||
// small debounce is not required—moves are sequential
|
||||
// schedule dropping lastMoveDir after small delay so new tiles animate in direction
|
||||
setTimeout(() => { lastMoveDir = null; }, 180);
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// RESTART & HOME
|
||||
// ----------------------------
|
||||
/* ------------------------
|
||||
Input handling (adds lastMoveDir + invalid-move shake)
|
||||
------------------------ */
|
||||
function handleKey(e) {
|
||||
let moved = false;
|
||||
if (e.key === "ArrowLeft") { lastMoveDir = "left"; moved = moveLeft(); }
|
||||
else if (e.key === "ArrowRight") { lastMoveDir = "right"; moved = moveRight(); }
|
||||
else if (e.key === "ArrowUp") { lastMoveDir = "up"; moved = moveUp(); }
|
||||
else if (e.key === "ArrowDown") { lastMoveDir = "down"; moved = moveDown(); }
|
||||
|
||||
if (moved) {
|
||||
// add tile + subtle delay so new tile animates from direction
|
||||
setTimeout(() => {
|
||||
addNewTile();
|
||||
refreshBoard();
|
||||
}, 70);
|
||||
} else {
|
||||
// show board shake
|
||||
const b = document.getElementById("board");
|
||||
if (b) {
|
||||
b.classList.add("shake");
|
||||
setTimeout(()=>b.classList.remove("shake"), 360);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------
|
||||
Restart & home
|
||||
------------------------ */
|
||||
function restartGame() {
|
||||
setupBoard();
|
||||
addNewTile();
|
||||
@ -279,14 +297,13 @@ function restartGame() {
|
||||
}
|
||||
|
||||
function goHome() {
|
||||
// stops music to prevent continuing on homepage
|
||||
try { audio.bg.pause(); audio.bg.currentTime = 0; } catch (e) {}
|
||||
window.location.href = "Homepage.html";
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// OPTIONAL: touch swipe for mobile
|
||||
// ----------------------------
|
||||
/* ------------------------
|
||||
Touch swipe
|
||||
------------------------ */
|
||||
let touchStartX = 0;
|
||||
let touchStartY = 0;
|
||||
document.addEventListener("touchstart", function (e) {
|
||||
@ -300,12 +317,191 @@ document.addEventListener("touchend", function (e) {
|
||||
const dx = t.clientX - touchStartX;
|
||||
const dy = t.clientY - touchStartY;
|
||||
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 30) {
|
||||
if (dx > 0) moveRight() && addNewTile() && refreshBoard();
|
||||
else moveLeft() && addNewTile() && refreshBoard();
|
||||
if (dx > 0) { lastMoveDir = "right"; moveRight() && setTimeout(()=>{ addNewTile(); refreshBoard(); }, 70); }
|
||||
else { lastMoveDir = "left"; moveLeft() && setTimeout(()=>{ addNewTile(); refreshBoard(); }, 70); }
|
||||
} else if (Math.abs(dy) > 30) {
|
||||
if (dy > 0) moveDown() && addNewTile() && refreshBoard();
|
||||
else moveUp() && addNewTile() && refreshBoard();
|
||||
if (dy > 0) { lastMoveDir = "down"; moveDown() && setTimeout(()=>{ addNewTile(); refreshBoard(); }, 70); }
|
||||
else { lastMoveDir = "up"; moveUp() && setTimeout(()=>{ addNewTile(); refreshBoard(); }, 70); }
|
||||
}
|
||||
}, { passive: true });
|
||||
|
||||
/* ------------------------
|
||||
Ambient cursor light + merge particles
|
||||
------------------------ */
|
||||
function setupAmbientCursor() {
|
||||
const container = document.querySelector(".particles");
|
||||
if (!container) return;
|
||||
|
||||
// create a subtle cursor-follow blob
|
||||
const cursor = document.createElement("div");
|
||||
cursor.className = "cursor-light";
|
||||
container.appendChild(cursor);
|
||||
|
||||
let lastX = window.innerWidth/2, lastY = window.innerHeight/2;
|
||||
document.addEventListener("mousemove", (e) => {
|
||||
lastX = e.clientX; lastY = e.clientY;
|
||||
cursor.style.left = lastX + "px";
|
||||
cursor.style.top = lastY + "px";
|
||||
});
|
||||
|
||||
// small periodic motion for background
|
||||
setInterval(() => {
|
||||
cursor.style.opacity = (0.4 + Math.random()*0.35).toString();
|
||||
}, 900);
|
||||
}
|
||||
|
||||
/* spawn merge particles at tile center */
|
||||
function spawnMergeParticles(row, col, colorHex="#00eaff") {
|
||||
const container = document.body;
|
||||
const boardRect = document.getElementById("board").getBoundingClientRect();
|
||||
const tileEl = document.getElementById(`${row}-${col}`);
|
||||
if (!tileEl) return;
|
||||
|
||||
const tileRect = tileEl.getBoundingClientRect();
|
||||
const cx = tileRect.left + tileRect.width/2;
|
||||
const cy = tileRect.top + tileRect.height/2;
|
||||
|
||||
const particles = [];
|
||||
const count = 10;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const p = document.createElement("div");
|
||||
p.className = "merge-particle";
|
||||
p.style.background = colorHex;
|
||||
p.style.left = (cx - 6) + "px";
|
||||
p.style.top = (cy - 6) + "px";
|
||||
p.style.opacity = "1";
|
||||
p.style.transform = "translate(0,0) scale(1)";
|
||||
document.body.appendChild(p);
|
||||
particles.push(p);
|
||||
|
||||
// random flight vector
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const dist = 24 + Math.random()*36;
|
||||
const tx = Math.cos(angle) * dist;
|
||||
const ty = Math.sin(angle) * dist;
|
||||
const rot = (Math.random() * 360)|0;
|
||||
p.animate([
|
||||
{ transform: `translate(0,0) rotate(0deg) scale(1)`, opacity: 1 },
|
||||
{ transform: `translate(${tx}px, ${ty}px) rotate(${rot}deg) scale(0.6)`, opacity: 0 }
|
||||
], {
|
||||
duration: 420 + Math.random()*240,
|
||||
easing: "cubic-bezier(.2,.8,.2,1)",
|
||||
fill: "forwards"
|
||||
});
|
||||
|
||||
// cleanup
|
||||
setTimeout(()=>{ try{ p.remove(); }catch(e){} }, 800 + Math.random()*400);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------
|
||||
Optional: call spawn on merges
|
||||
We don't track exact merge positions in slide() local scope here,
|
||||
but we can detect new larger tiles after move vs before and spawn particles.
|
||||
------------------------ */
|
||||
function spawnMergesFromDiff(prev, next) {
|
||||
// prev & next are 4x4 arrays
|
||||
for (let r = 0; r < 4; r++) {
|
||||
for (let c = 0; c < 4; c++) {
|
||||
if (next[r][c] > 0 && prev[r][c] !== next[r][c]) {
|
||||
// if new value appears that wasn't same in prev -> likely merged or moved; if it's > 2 we spawn small effect
|
||||
if (next[r][c] >= 4) {
|
||||
spawnMergeParticles(r, c, chooseColorForValue(next[r][c]));
|
||||
const tileEl = document.getElementById(`${r}-${c}`);
|
||||
if (tileEl) {
|
||||
tileEl.classList.add("merge");
|
||||
setTimeout(()=>tileEl.classList.remove("merge"), 260);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* choose nice color for particle based on value */
|
||||
function chooseColorForValue(n) {
|
||||
if (n >= 2048) return "#ffd700";
|
||||
if (n >= 1024) return "#00ffaa";
|
||||
if (n >= 512) return "#ff00aa";
|
||||
if (n >= 128) return "#5f00ff";
|
||||
if (n >= 32) return "#ffaa00";
|
||||
return "#00eaff";
|
||||
}
|
||||
|
||||
/* We'll wrap move functions to produce prev snapshot, then spawn particles for merges detected */
|
||||
function cloneBoard(b) {
|
||||
const out = [];
|
||||
for (let r = 0; r < 4; r++) out.push([...b[r]]);
|
||||
return out;
|
||||
}
|
||||
|
||||
/* Override move functions to spawn particles after move */
|
||||
function moveLeft() {
|
||||
const prev = cloneBoard(board);
|
||||
let moved = false;
|
||||
for (let r = 0; r < 4; r++) {
|
||||
const { row: newRow } = slide(board[r]);
|
||||
if (!arraysEqual(newRow, board[r])) moved = true;
|
||||
board[r] = newRow;
|
||||
}
|
||||
if (moved) {
|
||||
spawnMergesFromDiff(prev, board);
|
||||
updateAfterMove();
|
||||
}
|
||||
return moved;
|
||||
}
|
||||
function moveRight() {
|
||||
const prev = cloneBoard(board);
|
||||
let moved = false;
|
||||
for (let r = 0; r < 4; r++) {
|
||||
let reversed = [...board[r]].reverse();
|
||||
const { row: slid } = slide(reversed);
|
||||
let newRow = slid.reverse();
|
||||
if (!arraysEqual(newRow, board[r])) moved = true;
|
||||
board[r] = newRow;
|
||||
}
|
||||
if (moved) {
|
||||
spawnMergesFromDiff(prev, board);
|
||||
updateAfterMove();
|
||||
}
|
||||
return moved;
|
||||
}
|
||||
function moveUp() {
|
||||
const prev = cloneBoard(board);
|
||||
let moved = false;
|
||||
for (let c = 0; c < 4; c++) {
|
||||
const col = [board[0][c], board[1][c], board[2][c], board[3][c]];
|
||||
const { row: newCol } = slide(col);
|
||||
for (let r = 0; r < 4; r++) {
|
||||
if (board[r][c] !== newCol[r]) moved = true;
|
||||
board[r][c] = newCol[r];
|
||||
}
|
||||
}
|
||||
if (moved) {
|
||||
spawnMergesFromDiff(prev, board);
|
||||
updateAfterMove();
|
||||
}
|
||||
return moved;
|
||||
}
|
||||
function moveDown() {
|
||||
const prev = cloneBoard(board);
|
||||
let moved = false;
|
||||
for (let c = 0; c < 4; c++) {
|
||||
const col = [board[3][c], board[2][c], board[1][c], board[0][c]];
|
||||
const { row: slid } = 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];
|
||||
}
|
||||
}
|
||||
if (moved) {
|
||||
spawnMergesFromDiff(prev, board);
|
||||
updateAfterMove();
|
||||
}
|
||||
return moved;
|
||||
}
|
||||
|
||||
/* ------------------------
|
||||
End of file
|
||||
------------------------ */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user