/// 2048 – ImageJ Macro (mouse-controlled) // Created by Claude, May 2026 // ---------- constants ---------- var CELL = 100; // px per tile var GAP = 12; // px gap between tiles var COLS = 4; var ROWS = 4; var MARGIN = 20; // left/top margin before grid // Canvas size var CW = MARGIN*2 + COLS*CELL + (COLS-1)*GAP; var CH = MARGIN*2 + ROWS*CELL + (ROWS-1)*GAP; // Colour palette (RGB packed) // tile value -> background colour var TILE_BG = newArray(18); var TILE_FG = newArray(18); // board & score stored as global arrays var board = newArray(16); // row-major [row*4+col] var score = 0; var bestScore = 0; var gameOver = false; var gameWon = false; // ------------------------------------------------------- initColours(); newImage("2048", "RGB", CW, CH, 1); imgID = getImageID(); setTool("hand"); newGame(); render(); // ---- main event loop ---- prevX = -1; prevY = -1; while (true) { // Poll for mouse click inside our window if (!isOpen(imgID)) exit(); selectImage(imgID); // getCursorLoc fills x,y,z,flags // flags bit 16 = left button pressed getCursorLoc(mx, my, mz, flags); pressed = (flags & 16) > 0; if (pressed && prevX == -1) { // button just went down – record position prevX = mx; prevY = my; } if (!pressed && prevX != -1) { // button just went up – process click/drag dx = mx - prevX; dy = my - prevY; handleInput(prevX, prevY, dx, dy); prevX = -1; prevY = -1; } wait(30); } // ------------------------------------------------------- function handleInput(sx, sy, dx, dy) { // Check button bar first (y < MARGIN) btnY = MARGIN; if (sy >= btnY && sy <= btnY) { // Four buttons laid out horizontally centred bx0 = (CW - 3*GAP) / 2; for (b = 0; b < 4; b++) { bx = bx0 + b*GAP; if (sx >= bx && sx <= bx) { if (b == 0) doMove("up"); else if (b == 1) doMove("down"); else if (b == 2) doMove("left"); else doMove("right"); return; } } // Maybe NEW GAME button (far right area) if (sx >= CW - 100 && sx <= CW - MARGIN) { newGame(); render(); return; } } // Swipe on grid area if (abs(dx) < 10 && abs(dy) < 10) return; // tiny click, ignore if (abs(dx) > abs(dy)) { if (dx > 0) doMove("right"); else doMove("left"); } else { if (dy > 0) doMove("down"); else doMove("up"); } } // ------------------------------------------------------- function doMove(dir) { if (gameOver) return; moved = false; if (dir == "left") moved = slideLeft(); if (dir == "right") moved = slideRight(); if (dir == "up") moved = slideUp(); if (dir == "down") moved = slideDown(); if (moved) { addRandomTile(); if (checkLose()) gameOver = true; } render(); } // ------------------------------------------------------- // Slide helpers – all implemented via slideLeft on rotated board // ------------------------------------------------------- function slideLeft() { changed = false; for (r = 0; r < 4; r++) { // extract row row = newArray(4); for (c = 0; c < 4; c++) row[c] = board[r*4+c]; newRow = mergeRow(row); for (c = 0; c < 4; c++) { if (board[r*4+c] != newRow[c]) changed = true; board[r*4+c] = newRow[c]; } } return changed; } function slideRight() { reverseRows(); changed = slideLeft(); reverseRows(); return changed; } function slideUp() { transposeBoard(); changed = slideLeft(); transposeBoard(); return changed; } function slideDown() { transposeBoard(); changed = slideRight(); transposeBoard(); return changed; } function reverseRows() { for (r = 0; r < 4; r++) { tmp = board[r*4+0]; board[r*4+0] = board[r*4+3]; board[r*4+3] = tmp; tmp = board[r*4+1]; board[r*4+1] = board[r*4+2]; board[r*4+2] = tmp; } } function transposeBoard() { for (r = 0; r < 4; r++) { for (c = r+1; c < 4; c++) { tmp = board[r*4+c]; board[r*4+c] = board[c*4+r]; board[c*4+r] = tmp; } } } // Merge a single row leftward; updates score function mergeRow(row) { // compact non-zero to left out = newArray(4); pos = 0; for (i = 0; i < 4; i++) { if (row[i] != 0) { out[pos] = row[i]; pos++; } } // merge adjacent equals for (i = 0; i < 3; i++) { if (out[i] != 0 && out[i] == out[i+1]) { out[i] = out[i] * 2; score += out[i]; if (score > bestScore) bestScore = score; if (out[i] == 2048) gameWon = true; out[i+1] = 0; i++; // skip merged cell } } // compact again out2 = newArray(4); pos = 0; for (i = 0; i < 4; i++) { if (out[i] != 0) { out2[pos] = out[i]; pos++; } } return out2; } // ------------------------------------------------------- function addRandomTile() { empty = newArray(16); n = 0; for (i = 0; i < 16; i++) { if (board[i] == 0) { empty[n] = i; n++; } } if (n == 0) return; idx = empty[floor(random()*n)]; if (random() < 0.9) board[idx] = 2; else board[idx] = 2; } function checkLose() { // Any empty cell? for (i = 0; i < 16; i++) if (board[i] == 0) return false; // Any horizontal merge? for (r = 0; r < 4; r++) for (c = 0; c < 3; c++) if (board[r*4+c] == board[r*4+c+1]) return false; // Any vertical merge? for (r = 0; r < 3; r++) for (c = 0; c < 4; c++) if (board[r*4+c] == board[(r+1)*4+c]) return false; return true; } function newGame() { for (i = 0; i < 16; i++) board[i] = 0; score = 0; gameOver = false; gameWon = false; addRandomTile(); addRandomTile(); } // ------------------------------------------------------- // RENDERING // ------------------------------------------------------- function render() { selectImage(imgID); // Background setColor(250, 248, 239); fillRect(0, 0, CW, CH); // Grid background gx = MARGIN; gy = MARGIN; setColor(187, 173, 160); fillRect(gx - GAP, gy - GAP, COLS*CELL + (COLS+1)*GAP, ROWS*CELL + (ROWS+1)*GAP); // Tiles for (r = 0; r < 4; r++) { for (c = 0; c < 4; c++) { tx = gx + c*(CELL+GAP); ty = gy + r*(CELL+GAP); val = board[r*4+c]; drawTile(tx, ty, val); } } // Overlay messages if (gameWon) { drawOverlay("You reached 2048!", "Keep going or New Game"); } if (gameOver) { drawOverlay("Game Over!", "Click NEW GAME to retry"); } setOption("Changes", false); updateDisplay(); } function drawScoreBox(label, val, bx, by) { setColor(187, 173, 160); fillRect(bx, by, 95, 55); //6 setColor(238, 228, 218); setFont("SansSerif", 11, "bold"); drawString(label, bx + 8, by + 16); setColor(249, 246, 242); setFont("SansSerif", 18, "bold"); sv = "" + val; drawString(sv, bx + 8, by + 42); } function drawTile(tx, ty, val) { colorIdx = tileColorIndex(val); bg = TILE_BG[colorIdx]; fg = TILE_FG[colorIdx]; r_bg = (bg >> 16) & 0xFF; g_bg = (bg >> 8) & 0xFF; b_bg = bg & 0xFF; setColor(r_bg, g_bg, b_bg); fillRect(tx, ty, CELL, CELL); //8 if (val > 0) { r_fg = (fg >> 16) & 0xFF; g_fg = (fg >> 8) & 0xFF; b_fg = fg & 0xFF; setColor(r_fg, g_fg, b_fg); s = "" + val; fontSize = 36; if (val >= 1000) fontSize = 24; if (val >= 10000) fontSize = 18; setFont("SansSerif", fontSize, "bold"); sw = getStringWidth(s); // Approximate vertical centre drawString(s, tx + (CELL - sw)/2, ty + CELL/2 + fontSize*0.38); } } function drawOverlay(line1, line2) { // Semi-transparent dark overlay setColor(119, 110, 101); // Draw multiple translucent rectangles to simulate transparency for (i = 0; i < 5; i++) { setColorAlpha(119, 110, 101, 180); } // Solid overlay box ox = MARGIN + 10; oy = MARGIN + 50; ow = CW - ox - MARGIN - 10; oh = 140; setColor(119, 110, 101); fillRect(ox, oy, ow, oh); //12 setColor(249, 246, 242); setFont("SansSerif", 28, "bold"); sw = getStringWidth(line1); drawString(line1, ox + (ow-sw)/2, oy + 55); setFont("SansSerif", 14, "plain"); sw = getStringWidth(line2); drawString(line2, ox + (ow-sw)/2, oy + 90); } // ------------------------------------------------------- // Colour helpers // ------------------------------------------------------- function tileColorIndex(val) { // map power of 2 → index 0..17 if (val == 0) return 0; if (val == 2) return 1; if (val == 4) return 2; if (val == 8) return 3; if (val == 16) return 4; if (val == 32) return 5; if (val == 64) return 6; if (val == 128) return 7; if (val == 256) return 8; if (val == 512) return 9; if (val == 1024) return 10; if (val == 2048) return 11; return 12; // 4096+ } function rgb(r, g, b) { return (r << 16) | (g << 8) | b; } function initColours() { // Background colours for each tile value TILE_BG[0] = rgb(205, 193, 180); // empty TILE_BG[1] = rgb(238, 228, 218); // 2 TILE_BG[2] = rgb(237, 224, 200); // 4 TILE_BG[3] = rgb(242, 177, 121); // 8 TILE_BG[4] = rgb(245, 149, 99); // 16 TILE_BG[5] = rgb(246, 124, 95); // 32 TILE_BG[6] = rgb(246, 94, 59); // 64 TILE_BG[7] = rgb(237, 207, 114); // 128 TILE_BG[8] = rgb(237, 204, 97); // 256 TILE_BG[9] = rgb(237, 200, 80); // 512 TILE_BG[10] = rgb(237, 197, 63); // 1024 TILE_BG[11] = rgb(237, 194, 46); // 2048 ← gold! TILE_BG[12] = rgb( 60, 58, 50); // 4096+ // Foreground (text) colours TILE_FG[0] = rgb(205, 193, 180); TILE_FG[1] = rgb(119, 110, 101); // dark text for light tiles TILE_FG[2] = rgb(119, 110, 101); TILE_FG[3] = rgb(249, 246, 242); // white text for coloured tiles TILE_FG[4] = rgb(249, 246, 242); TILE_FG[5] = rgb(249, 246, 242); TILE_FG[6] = rgb(249, 246, 242); TILE_FG[7] = rgb(249, 246, 242); TILE_FG[8] = rgb(249, 246, 242); TILE_FG[9] = rgb(249, 246, 242); TILE_FG[10] = rgb(249, 246, 242); TILE_FG[11] = rgb(249, 246, 242); TILE_FG[12] = rgb(249, 246, 242); }