Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- (() => {
- const BOARD_SIZE_KEY = "2048BoardSize";
- let SIZE = parseInt(localStorage.getItem(BOARD_SIZE_KEY)) || 4;
- let tileSize = Math.min(60, window.innerWidth / (SIZE + 2));
- const tileGap = 5;
- const hudOpacity = 0.95;
- let aiEnabled = false;
- let numberDisplayMode = "normal";
- let board = [];
- let score = 0;
- let highScore = 0;
- const historyStack = [];
- const UNDO_LIMIT = 20;
- // ---------------- HUD ----------------
- const hud = document.createElement("div");
- hud.style.cssText = `
- position: fixed; top: 50px; left: 50px;
- width: ${SIZE*tileSize + (SIZE-1)*tileGap + 20}px;
- background: rgba(187,173,160,${hudOpacity});
- border-radius: 10px; padding: 10px;
- z-index: 9999; user-select: none; cursor: move;
- display: flex; flex-direction: column; gap: 10px;
- max-width: 90vw;
- `;
- document.body.appendChild(hud);
- // Dragging HUD
- let isDragging=false, offsetX=0, offsetY=0;
- hud.addEventListener("mousedown", e=>{
- isDragging=true; offsetX=e.clientX-hud.getBoundingClientRect().left;
- offsetY=e.clientY-hud.getBoundingClientRect().top;
- });
- document.addEventListener("mousemove", e=>{
- if(!isDragging) return;
- let newX = e.clientX - offsetX;
- let newY = e.clientY - offsetY;
- newX=Math.min(window.innerWidth-hud.offsetWidth, Math.max(0,newX));
- newY=Math.min(window.innerHeight-hud.offsetHeight, Math.max(0,newY));
- hud.style.left=newX+"px"; hud.style.top=newY+"px";
- });
- document.addEventListener("mouseup", ()=>isDragging=false);
- // Score display
- const scoreDiv = document.createElement("div");
- scoreDiv.style.color="#f9f6f2"; scoreDiv.style.fontWeight="bold"; scoreDiv.style.fontSize="16px";
- hud.appendChild(scoreDiv);
- const highScoreDiv = document.createElement("div");
- highScoreDiv.style.color="#f9f6f2"; highScoreDiv.style.fontSize="14px";
- hud.appendChild(highScoreDiv);
- // Board container
- const boardDiv = document.createElement("div");
- boardDiv.style.position="relative"; boardDiv.style.background="#bbada0";
- boardDiv.style.display="grid"; boardDiv.style.gap=`${tileGap}px`;
- hud.appendChild(boardDiv);
- // Arrow controls
- const arrows = document.createElement("div");
- arrows.style.cssText = `
- position: absolute; right: -55px; top: 50%;
- transform: translateY(-50%); display: grid;
- grid-template-rows: repeat(4,1fr); gap: 6px;
- `;
- hud.appendChild(arrows);
- function createArrow(label, key){
- const btn=document.createElement("button");
- btn.textContent=label;
- btn.style.cssText="width:45px;height:40px;font-size:18px;border-radius:6px;border:none;background:#8f7a66;color:#f9f6f2;cursor:pointer";
- btn.addEventListener("mousedown",()=>btn.style.transform="scale(0.9)");
- btn.addEventListener("mouseup",()=>btn.style.transform="scale(1)");
- btn.addEventListener("mouseleave",()=>btn.style.transform="scale(1)");
- btn.addEventListener("click",()=>handleMove(key));
- arrows.appendChild(btn);
- }
- ["↑","←","→","↓"].forEach((label,i)=>{
- const keys=["ArrowUp","ArrowLeft","ArrowRight","ArrowDown"];
- createArrow(label, keys[i]);
- });
- // Buttons container
- const btnContainer=document.createElement("div");
- btnContainer.style.display="flex"; btnContainer.style.gap="5px";
- hud.appendChild(btnContainer);
- function createButton(label, callback){
- const btn=document.createElement("button");
- btn.textContent=label;
- btn.style.cssText="padding:5px;border-radius:5px;border:none;background:#8f7a66;color:#f9f6f2;cursor:pointer";
- btn.addEventListener("click", callback);
- btnContainer.appendChild(btn);
- }
- // Options
- const options=document.createElement("div");
- options.style.cssText="display:flex;flex-direction:column;gap:5px;margin-top:5px";
- hud.appendChild(options);
- // Number style selector
- const numberStyleLabel=document.createElement("div");
- numberStyleLabel.textContent="Number Style"; numberStyleLabel.style.cssText="color:#000;font-size:13px";
- options.appendChild(numberStyleLabel);
- const numberStyleSelect=document.createElement("select");
- numberStyleSelect.style.cssText="padding:5px;border-radius:5px;border:none;background:#eee;cursor:pointer";
- numberStyleSelect.innerHTML=`
- <option value="normal">Normal</option>
- <option value="commas">Commas</option>
- <option value="power">Powers (2ⁿ)</option>
- <option value="letters">Letters (A,B,C)</option>
- <option value="emoji">Emojis</option>
- `;
- numberStyleSelect.value=numberDisplayMode;
- numberStyleSelect.addEventListener("change", e=>{
- numberDisplayMode=e.target.value;
- drawBoard();
- });
- options.appendChild(numberStyleSelect);
- // Board size selector
- const sizeLabel=document.createElement("div");
- sizeLabel.textContent="Board Size"; sizeLabel.style.cssText="color:#f9f6f2;font-size:13px";
- options.appendChild(sizeLabel);
- const sizeSelect=document.createElement("select");
- sizeSelect.style.cssText="padding:5px;border-radius:5px;border:none;cursor:pointer";
- sizeSelect.innerHTML=`
- <option value="3">3 × 3</option>
- <option value="4">4 × 4</option>
- <option value="5">5 × 5</option>
- <option value="6">6 × 6</option>
- `;
- sizeSelect.value=SIZE;
- sizeSelect.addEventListener("change", e=>{
- SIZE=parseInt(e.target.value);
- localStorage.setItem(BOARD_SIZE_KEY,SIZE);
- highScore=parseInt(localStorage.getItem(highScoreKey(SIZE)))||0;
- initBoard(); updateHighScoreTable();
- });
- options.appendChild(sizeSelect);
- // Tile size selector
- const tileSizeLabel = document.createElement("div");
- tileSizeLabel.textContent = "Tile Size (px)"; tileSizeLabel.style.cssText="color:#f9f6f2;font-size:13px";
- options.appendChild(tileSizeLabel);
- const tileSizeSelect = document.createElement("select");
- tileSizeSelect.style.cssText="padding:5px;border-radius:5px;border:none;cursor:pointer;background:#eee";
- [40, 50, 60, 70, 80].forEach(sz=>{
- const opt = document.createElement("option");
- opt.value = sz;
- opt.textContent = sz;
- tileSizeSelect.appendChild(opt);
- });
- tileSizeSelect.value = tileSize;
- tileSizeSelect.addEventListener("change", e=>{
- tileSize = parseInt(e.target.value);
- rebuildBoardUI();
- drawBoard();
- });
- options.appendChild(tileSizeSelect);
- createButton("Restart", initBoard);
- createButton("Undo", undo);
- createButton("AI Toggle", ()=>{ aiEnabled=!aiEnabled; if(aiEnabled) aiMove(); });
- // ---------------- Board Logic ----------------
- let tileMap=[];
- function createTileElement(){
- const tile=document.createElement("div");
- tile.style.position="absolute"; tile.style.borderRadius="5px";
- tile.style.display="flex"; tile.style.alignItems="center"; tile.style.justifyContent="center";
- tile.style.fontWeight="bold"; tile.style.transition="all 0.2s ease";
- boardDiv.appendChild(tile); return tile;
- }
- function getTileColor(val){
- if(val===0) return "#cdc1b4";
- const hue=Math.min(60 + Math.log2(val)*8,360);
- return `hsl(${hue},70%,60%)`;
- }
- function formatTileValue(val){
- if(val===0) return "";
- switch(numberDisplayMode){
- case "commas": return val.toLocaleString();
- case "power": return `2^${Math.log2(val)}`;
- case "letters": return String.fromCharCode(64 + Math.log2(val));
- case "emoji":
- const emojiMap={2:"🟦",4:"🟩",8:"🟨",16:"🟧",32:"🟥",64:"🟪",128:"⬛",256:"⭐",512:"🔥",1024:"💎",2048:"👑"};
- return emojiMap[val]||"✨";
- default: return val;
- }
- }
- function drawBoard(mergePositions=[]){
- for(let r=0;r<SIZE;r++){
- for(let c=0;c<SIZE;c++){
- const val=board[r][c];
- let tile=tileMap[r]?.[c];
- if(val===0){
- if(tile){ boardDiv.removeChild(tile); tileMap[r][c]=null;}
- }else{
- if(!tile){ tile=createTileElement(); if(!tileMap[r]) tileMap[r]=[]; tileMap[r][c]=tile;}
- tile.textContent=formatTileValue(val);
- tile.style.background=getTileColor(val);
- tile.style.color=val>4?"#f9f6f2":"#776e65";
- tile.style.fontSize=Math.max(tileSize/3,10)+"px";
- tile.style.width=tile.style.height=tileSize+"px";
- tile.style.left=`${c*tileSize + c*tileGap}px`;
- tile.style.top=`${r*tileSize + r*tileGap}px`;
- tile.style.transform=mergePositions.some(([mr,mc])=>mr===r && mc===c)?"scale(1.2)":"scale(1)";
- if(mergePositions.some(([mr,mc])=>mr===r && mc===c)){
- setTimeout(()=>{tile.style.transform="scale(1)";},100);
- createParticles(r,c);
- }
- }
- }
- }
- scoreDiv.textContent=`Score: ${score}`;
- highScoreDiv.textContent=`High Score: ${highScore}`;
- }
- function createParticles(r,c){
- const count=10;
- for(let i=0;i<count;i++){
- const p=document.createElement("div");
- p.style.cssText=`position:absolute;width:5px;height:5px;background:yellow;border-radius:50%;left:${c*tileSize + c*tileGap + tileSize/2}px;top:${r*tileSize + r*tileGap + tileSize/2}px;pointer-events:none;opacity:1`;
- boardDiv.appendChild(p);
- const dx=(Math.random()-0.5)*60, dy=(Math.random()-0.5)*60;
- p.animate([{transform:"translate(0,0)",opacity:1},{transform:`translate(${dx}px,${dy}px)`,opacity:0}],{duration:500});
- setTimeout(()=>boardDiv.removeChild(p),500);
- }
- }
- function getEmpty(){
- const empty=[];
- for(let r=0;r<SIZE;r++) for(let c=0;c<SIZE;c++) if(board[r][c]===0) empty.push([r,c]);
- return empty;
- }
- function addRandom(){
- const empty=getEmpty();
- if(empty.length===0) return;
- const [r,c]=empty[Math.floor(Math.random()*empty.length)];
- board[r][c]=Math.random()<0.9?2:4;
- }
- function slide(row){
- const arr=row.filter(v=>v!==0);
- const mergePositions=[];
- for(let i=0;i<arr.length-1;i++){
- if(arr[i]===arr[i+1]){
- arr[i]*=2; score+=arr[i]; arr[i+1]=0;
- mergePositions.push(i);
- }
- }
- const newRow=arr.filter(v=>v!==0).concat(Array(SIZE-arr.filter(v=>v!==0).length).fill(0));
- return [newRow, mergePositions];
- }
- function moveLeft(){const merges=[]; for(let r=0;r<SIZE;r++){const [row,idx]=slide(board[r]); board[r]=row; idx.forEach(c=>merges.push([r,c]));} return merges;}
- function moveRight(){const merges=[]; for(let r=0;r<SIZE;r++){const [row,idx]=slide([...board[r]].reverse()); board[r]=row.reverse(); idx.forEach(c=>merges.push([r,SIZE-1-c]));} return merges;}
- function moveUp(){const merges=[]; for(let c=0;c<SIZE;c++){const col=[]; for(let r=0;r<SIZE;r++) col.push(board[r][c]); const [newCol,idx]=slide(col); for(let r=0;r<SIZE;r++) board[r][c]=newCol[r]; idx.forEach(r=>merges.push([r,c]));} return merges;}
- function moveDown(){const merges=[]; for(let c=0;c<SIZE;c++){const col=[]; for(let r=0;r<SIZE;r++) col.push(board[r][c]); const [newCol,idx]=slide(col.reverse()); const newColRev=newCol.reverse(); for(let r=0;r<SIZE;r++) board[r][c]=newColRev[r]; idx.forEach(r=>merges.push([SIZE-1-r,c]));} return merges;}
- function isOver(){
- if(getEmpty().length>0) return false;
- for(let r=0;r<SIZE;r++) for(let c=0;c<SIZE;c++){
- const val=board[r][c];
- if((c<SIZE-1 && board[r][c+1]===val)||(r<SIZE-1 && board[r+1][c]===val)) return false;
- }
- return true;
- }
- function undo(){
- if(historyStack.length>0){
- const last=historyStack.pop();
- board=last.board.map(r=>[...r]);
- score=last.score;
- drawBoard();
- }
- }
- function rebuildBoardUI(){
- boardDiv.innerHTML="";
- boardDiv.style.width=`${SIZE*tileSize + (SIZE-1)*tileGap}px`;
- boardDiv.style.height=`${SIZE*tileSize + (SIZE-1)*tileGap}px`;
- boardDiv.style.gridTemplateRows=`repeat(${SIZE},1fr)`;
- boardDiv.style.gridTemplateColumns=`repeat(${SIZE},1fr)`;
- hud.style.width=`${SIZE*tileSize + (SIZE-1)*tileGap + 20}px`;
- tileMap=Array.from({length:SIZE},()=>Array(SIZE).fill(null));
- for(let i=0;i<SIZE*SIZE;i++){
- const cell=document.createElement("div");
- cell.style.background="#cdc1b4"; cell.style.borderRadius="5px";
- boardDiv.appendChild(cell);
- }
- }
- function initBoard(){
- board=Array.from({length:SIZE},()=>Array(SIZE).fill(0));
- score=0; historyStack.length=0;
- rebuildBoardUI(); addRandom(); addRandom(); drawBoard();
- }
- function handleMove(key){
- if(aiEnabled) return;
- const copy=board.map(r=>[...r]);
- let merges=[];
- switch(key){
- case "ArrowLeft": merges=moveLeft(); break;
- case "ArrowRight": merges=moveRight(); break;
- case "ArrowUp": merges=moveUp(); break;
- case "ArrowDown": merges=moveDown(); break;
- default: return;
- }
- if(JSON.stringify(copy)!==JSON.stringify(board)){
- historyStack.push({board:copy.map(r=>[...r]), score:score});
- if(historyStack.length>UNDO_LIMIT) historyStack.shift();
- if(score>highScore){
- highScore=score;
- localStorage.setItem(highScoreKey(SIZE),highScore);
- updateHighScoreTable();
- }
- addRandom(); drawBoard(merges);
- if(isOver()) setTimeout(()=>alert(`Game Over! Final Score: ${score}`),100);
- }
- }
- // ---------------- AI ----------------
- function aiMove(){
- if(!aiEnabled) return;
- const moves = ["ArrowUp","ArrowLeft","ArrowRight","ArrowDown"];
- let bestMove = null;
- let bestScore = -Infinity;
- for(let m of moves){
- const newBoard = simulateMove(m, board);
- if(boardsEqual(newBoard, board)) continue;
- const score = evaluateBoard(newBoard);
- if(score > bestScore){ bestScore = score; bestMove = m; }
- }
- if(bestMove){ handleMove(bestMove); setTimeout(aiMove, 120); }
- }
- function cloneBoard(b){ return b.map(r => r.slice()); }
- function boardsEqual(a,b){ return JSON.stringify(a) === JSON.stringify(b); }
- function simulateMove(key, b){
- const savedBoard = board; const savedScore = score;
- board = cloneBoard(b); score = 0;
- switch(key){ case "ArrowLeft": moveLeft(); break; case "ArrowRight": moveRight(); break; case "ArrowUp": moveUp(); break; case "ArrowDown": moveDown(); break; }
- const result = cloneBoard(board);
- board = savedBoard; score = savedScore;
- return result;
- }
- function countEmpty(b){ let n=0; for(let r=0;r<SIZE;r++) for(let c=0;c<SIZE;c++) if(b[r][c]===0)n++; return n; }
- function maxTile(b){ return Math.max(...b.flat()); }
- function isMaxInCorner(b){ const m=maxTile(b); return [b[0][0],b[0][SIZE-1],b[SIZE-1][0],b[SIZE-1][SIZE-1]].includes(m); }
- function smoothness(b){
- let s=0;
- for(let r=0;r<SIZE;r++) for(let c=0;c<SIZE;c++){ if(b[r][c]===0) continue; const v=Math.log2(b[r][c]); if(c+1<SIZE && b[r][c+1]) s-=Math.abs(v-Math.log2(b[r][c+1])); if(r+1<SIZE && b[r+1][c]) s-=Math.abs(v-Math.log2(b[r+1][c])); }
- return s;
- }
- function monotonicity(b){
- let score=0;
- for(let r=0;r<SIZE;r++) for(let c=0;c<SIZE-1;c++){ if(b[r][c] && b[r][c+1]) score+=Math.log2(b[r][c])>=Math.log2(b[r][c+1])?1:-1; }
- for(let c=0;c<SIZE;c++) for(let r=0;r<SIZE-1;r++){ if(b[r][c] && b[r+1][c]) score+=Math.log2(b[r][c])>=Math.log2(b[r+1][c])?1:-1; }
- return score;
- }
- function evaluateBoard(b){ return countEmpty(b)*100 + smoothness(b)*3 + monotonicity(b)*10 + (isMaxInCorner(b)?500:0) + Math.log2(maxTile(b))*20; }
- // ---------------- High Scores ----------------
- const scoreTableContainer=document.createElement("div");
- scoreTableContainer.style.cssText="display:flex;flex-direction:column;gap:3px;margin-top:5px";
- hud.appendChild(scoreTableContainer);
- const tableTitle=document.createElement("div");
- tableTitle.textContent="High Scores by Board Size"; tableTitle.style.cssText="color:#f9f6f2;font-size:13px";
- scoreTableContainer.appendChild(tableTitle);
- const scoreTable=document.createElement("div");
- scoreTableContainer.appendChild(scoreTable);
- function highScoreKey(size){ return `2048HighScore_${size}x${size}`; }
- function updateHighScoreTable(){
- scoreTable.innerHTML="";
- [3,4,5,6].forEach(size=>{
- const hs=parseInt(localStorage.getItem(highScoreKey(size)))||0;
- const row=document.createElement("div");
- row.style.cssText="display:flex;justify-content:space-between;font-size:13px;color:#f9f6f2";
- row.textContent=`${size} × ${size} : ${hs}`;
- scoreTable.appendChild(row);
- });
- }
- window.addEventListener("keydown", e=>handleMove(e.key));
- // Init
- highScore=parseInt(localStorage.getItem(highScoreKey(SIZE)))||0;
- initBoard(); updateHighScoreTable();
- console.log("%c2048 Loaded!", "color:green;font-weight:bold;");
- })();
Advertisement
Comments
-
- incorrect color scheme i can see the difference between this and the original game's
Add Comment
Please, Sign In to add comment