PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.7
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.7
2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / 2048-game / frontend.js

frontend.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/2048-game/frontend.js

414 lines 15.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () {
2 'use strict';
3
4 var TILE_COLORS = {
5 0: { bg: '#cdc1b4', text: '#776e65' },
6 2: { bg: '#eee4da', text: '#776e65' },
7 4: { bg: '#ede0c8', text: '#776e65' },
8 8: { bg: '#f2b179', text: '#f9f6f2' },
9 16: { bg: '#f59563', text: '#f9f6f2' },
10 32: { bg: '#f67c5f', text: '#f9f6f2' },
11 64: { bg: '#f65e3b', text: '#f9f6f2' },
12 128: { bg: '#edcf72', text: '#f9f6f2' },
13 256: { bg: '#edcc61', text: '#f9f6f2' },
14 512: { bg: '#edc850', text: '#f9f6f2' },
15 1024: { bg: '#edc53f', text: '#f9f6f2' },
16 2048: { bg: '#edc22e', text: '#f9f6f2' }
17 };
18
19 var _typoKeys = {
20 family:'font-family', weight:'font-weight', style:'font-style',
21 transform:'text-transform', decoration:'text-decoration',
22 sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m',
23 lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m',
24 letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m',
25 wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m'
26 };
27 var _typoUnits = { size:'sizeUnit', lineHeight:'lineHeightUnit', letterSpacing:'letterSpacingUnit', wordSpacing:'wordSpacingUnit' };
28 var _typoUnitDefaults = { size:'px', lineHeight:'', letterSpacing:'px', wordSpacing:'px' };
29 function typoCssVarsForEl(el, obj, prefix) {
30 if (!obj || typeof obj !== 'object') return;
31 Object.keys(_typoKeys).forEach(function (k) {
32 var v = obj[k]; if (v === undefined || v === '') return;
33 var prop = _typoKeys[k];
34 var base = k.replace(/Desktop|Tablet|Mobile/, '');
35 var uKey = _typoUnits[base];
36 if (uKey && typeof v === 'number') v = v + (obj[uKey] || _typoUnitDefaults[base] || '');
37 el.style.setProperty('--' + prefix + prop, v);
38 });
39 }
40
41 function getTileColor(val) {
42 return TILE_COLORS[val] || { bg: '#3c3a32', text: '#f9f6f2' };
43 }
44
45 function makeEmptyGrid() {
46 return [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]];
47 }
48
49 function cloneGrid(g) { return g.map(function (r) { return r.slice(); }); }
50
51 function emptyPositions(g) {
52 var pos = [];
53 for (var r = 0; r < 4; r++) for (var c = 0; c < 4; c++) if (!g[r][c]) pos.push([r,c]);
54 return pos;
55 }
56
57 function addRandom(g) {
58 var empty = emptyPositions(g);
59 if (!empty.length) return;
60 var pick = empty[Math.floor(Math.random() * empty.length)];
61 g[pick[0]][pick[1]] = Math.random() < 0.9 ? 2 : 4;
62 return [pick[0], pick[1]];
63 }
64
65 function slideRow(row) {
66 // Remove zeros
67 var nums = row.filter(function (v) { return v !== 0; });
68 var merged = [];
69 var i = 0;
70 while (i < nums.length) {
71 if (i + 1 < nums.length && nums[i] === nums[i+1]) {
72 merged.push(nums[i] * 2);
73 i += 2;
74 } else {
75 merged.push(nums[i]);
76 i++;
77 }
78 }
79 while (merged.length < 4) merged.push(0);
80 return merged;
81 }
82
83 function moveLeft(g) {
84 var next = makeEmptyGrid();
85 var score = 0;
86 for (var r = 0; r < 4; r++) {
87 var orig = g[r];
88 var slid = slideRow(orig.slice());
89 next[r] = slid;
90 slid.forEach(function (v, c) { if (v > orig[c]) score += v; });
91 }
92 return { grid: next, score: score };
93 }
94
95 function rotateRight(g) {
96 var n = makeEmptyGrid();
97 for (var r = 0; r < 4; r++) for (var c = 0; c < 4; c++) n[c][3-r] = g[r][c];
98 return n;
99 }
100
101 function rotateLeft(g) {
102 var n = makeEmptyGrid();
103 for (var r = 0; r < 4; r++) for (var c = 0; c < 4; c++) n[3-c][r] = g[r][c];
104 return n;
105 }
106
107 function moveRight(g) { var res = moveLeft(rotateRight(rotateRight(g))); return { grid: rotateLeft(rotateLeft(res.grid)), score: res.score }; }
108 function moveUp(g) { var res = moveLeft(rotateRight(g)); return { grid: rotateLeft(res.grid), score: res.score }; }
109 function moveDown(g) { var res = moveLeft(rotateLeft(g)); return { grid: rotateRight(res.grid), score: res.score }; }
110
111 function gridsEqual(a, b) {
112 for (var r = 0; r < 4; r++) for (var c = 0; c < 4; c++) if (a[r][c] !== b[r][c]) return false;
113 return true;
114 }
115
116 function hasMoves(g) {
117 if (emptyPositions(g).length) return true;
118 for (var r = 0; r < 4; r++) {
119 for (var c = 0; c < 4; c++) {
120 if (c < 3 && g[r][c] === g[r][c+1]) return true;
121 if (r < 3 && g[r][c] === g[r+1][c]) return true;
122 }
123 }
124 return false;
125 }
126
127 function hasWon(g) {
128 for (var r = 0; r < 4; r++) for (var c = 0; c < 4; c++) if (g[r][c] >= 2048) return true;
129 return false;
130 }
131
132 function initBlock(root) {
133 var optsRaw = root.getAttribute('data-opts');
134 var opts;
135 try { opts = JSON.parse(optsRaw); } catch (e) { opts = {}; }
136
137 var uid = root.getAttribute('id') || ('g48_' + Date.now());
138 var boardSize = opts.boardSize || 380;
139 var gridGap = opts.gridGap || 10;
140 var tileRadius = opts.tileRadius || 8;
141 var gridBg = opts.gridBg || '#bbada0';
142 var tileBg = opts.tileBg || '#cdc1b4';
143 var sectionBg = opts.sectionBg || '';
144 var titleColor = opts.titleColor || '#776e65';
145 var scoreHdrBg = opts.scoreHeaderBg|| '#bbada0';
146 var newGameBg = opts.newGameBg || '#8f7a66';
147 var animate = opts.animateTiles !== false;
148
149 // Apply typography CSS vars
150 typoCssVarsForEl(root, opts.titleTypo, 'bkg48-tt-');
151 typoCssVarsForEl(root, opts.subtitleTypo, 'bkg48-st-');
152
153 if (sectionBg) root.style.background = sectionBg;
154
155 // Apply title styles
156 var titleEl = root.querySelector('.bkbg-g48-title');
157 if (titleEl) { titleEl.style.color = titleColor; }
158 var subEl = root.querySelector('.bkbg-g48-subtitle');
159 if (subEl) { subEl.style.color = titleColor; }
160
161 // Game state
162 var grid = makeEmptyGrid();
163 var score = 0;
164 var best = parseInt(localStorage.getItem('bkbg_2048_best_' + uid)) || 0;
165 var won = false;
166 var over = false;
167 var newTile = null; // [r, c] of recently added tile
168 var mergedTiles = []; // [[r,c],...] that just merged
169
170 addRandom(grid);
171 addRandom(grid);
172
173 // Header
174 var header = document.createElement('div');
175 header.className = 'bkbg-g48-header';
176
177 var titleBlock = document.createElement('div');
178 var titleEl2 = root.querySelector('.bkbg-g48-title');
179 var subEl2 = root.querySelector('.bkbg-g48-subtitle');
180 if (titleEl2) titleBlock.appendChild(titleEl2.cloneNode(true));
181 if (subEl2) titleBlock.appendChild(subEl2.cloneNode(true));
182 header.appendChild(titleBlock);
183
184 // Score section
185 var scoresDiv = document.createElement('div');
186 scoresDiv.className = 'bkbg-g48-scores';
187
188 var scoreBox = null, scorVal = null, bestBox = null, bestVal = null;
189
190 function makeScoreBox(label, val, bg) {
191 var box = document.createElement('div');
192 box.className = 'bkbg-g48-score-box';
193 box.style.background = bg;
194 var lbl = document.createElement('div');
195 lbl.className = 'bkbg-g48-score-label';
196 lbl.style.color = '#eee4da';
197 lbl.textContent = label;
198 var vEl = document.createElement('div');
199 vEl.className = 'bkbg-g48-score-value';
200 vEl.style.color = '#fff';
201 vEl.textContent = val;
202 box.appendChild(lbl);
203 box.appendChild(vEl);
204 return { box: box, valEl: vEl };
205 }
206
207 if (opts.showScore !== false) {
208 var sb = makeScoreBox('Score', '0', scoreHdrBg);
209 scoreBox = sb.box; scorVal = sb.valEl;
210 scoresDiv.appendChild(scoreBox);
211 }
212 if (opts.showBestScore !== false) {
213 var bb = makeScoreBox('Best', best || '0', scoreHdrBg);
214 bestBox = bb.box; bestVal = bb.valEl;
215 scoresDiv.appendChild(bestBox);
216 }
217
218 var newBtn = document.createElement('button');
219 newBtn.className = 'bkbg-g48-new-btn';
220 newBtn.style.background = newGameBg;
221 newBtn.textContent = 'New Game';
222 newBtn.addEventListener('click', newGame);
223 scoresDiv.appendChild(newBtn);
224
225 header.appendChild(scoresDiv);
226 root.appendChild(header);
227
228 // Board
229 var boardWrap = document.createElement('div');
230 boardWrap.className = 'bkbg-g48-board-wrap';
231
232 var boardEl = document.createElement('div');
233 boardEl.className = 'bkbg-g48-grid';
234 boardEl.style.background = gridBg;
235 boardEl.style.gap = gridGap + 'px';
236 boardEl.style.padding = gridGap + 'px';
237 boardEl.style.borderRadius = (tileRadius + gridGap) + 'px';
238 boardEl.style.width = boardSize + 'px';
239 boardEl.style.height = boardSize + 'px';
240 boardEl.style.boxSizing = 'border-box';
241 boardEl.style.touchAction = 'none';
242 boardEl.style.position = 'relative';
243
244 // 16 tile divs
245 var tileDivs = [];
246 for (var i = 0; i < 16; i++) {
247 var td = document.createElement('div');
248 td.className = 'bkbg-g48-tile';
249 td.style.borderRadius = tileRadius + 'px';
250 td.style.fontSize = '36px';
251 boardEl.appendChild(td);
252 tileDivs.push(td);
253 }
254
255 boardWrap.appendChild(boardEl);
256 root.appendChild(boardWrap);
257
258 // Hint
259 var hint = document.createElement('p');
260 hint.className = 'bkbg-g48-hint';
261 hint.style.color = titleColor;
262 hint.textContent = '\u2190 \u2192 \u2191 \u2193 arrow keys or swipe to play';
263 root.appendChild(hint);
264
265 function updateScoreUI() {
266 if (scorVal) scorVal.textContent = score;
267 if (bestVal) bestVal.textContent = best;
268 }
269
270 function renderGrid() {
271 for (var r = 0; r < 4; r++) {
272 for (var c = 0; c < 4; c++) {
273 var val = grid[r][c];
274 var td = tileDivs[r * 4 + c];
275 var tc = val ? getTileColor(val) : { bg: tileBg, text: '#776e65' };
276 td.style.background = tc.bg;
277 td.style.color = tc.text;
278 td.textContent = val || '';
279 var fs = val < 100 ? 30 : val < 1000 ? 22 : val < 10000 ? 17 : 13;
280 td.style.fontSize = fs + 'px';
281
282 // Animations
283 td.classList.remove('bkbg-g48-pop', 'bkbg-g48-new');
284 if (animate) {
285 if (newTile && newTile[0] === r && newTile[1] === c) {
286 td.classList.add('bkbg-g48-new');
287 }
288 var isMerge = mergedTiles.some(function (mt) { return mt[0] === r && mt[1] === c; });
289 if (isMerge) {
290 void td.offsetWidth; // reflow
291 td.classList.add('bkbg-g48-pop');
292 }
293 }
294 }
295 }
296 }
297
298 function showOverlay(msg, sub) {
299 var existing = boardEl.querySelector('.bkbg-g48-overlay');
300 if (existing) existing.remove();
301
302 var ov = document.createElement('div');
303 ov.className = 'bkbg-g48-overlay';
304 ov.style.background = 'rgba(238,228,218,0.73)';
305 ov.style.borderRadius = (tileRadius + gridGap) + 'px';
306 ov.style.color = '#776e65';
307
308 var msgEl = document.createElement('div');
309 msgEl.className = 'bkbg-g48-overlay-msg';
310 msgEl.textContent = msg;
311
312 var subEl = document.createElement('div');
313 subEl.className = 'bkbg-g48-overlay-sub';
314 subEl.textContent = sub;
315
316 var btn = document.createElement('button');
317 btn.className = 'bkbg-g48-new-btn';
318 btn.style.background = newGameBg;
319 btn.textContent = 'Try Again';
320 btn.addEventListener('click', newGame);
321
322 ov.appendChild(msgEl);
323 ov.appendChild(subEl);
324 ov.appendChild(btn);
325 boardEl.appendChild(ov);
326 }
327
328 function removeOverlay() {
329 var ov = boardEl.querySelector('.bkbg-g48-overlay');
330 if (ov) ov.remove();
331 }
332
333 function findMerged(before, after) {
334 var res = [];
335 for (var r = 0; r < 4; r++) {
336 for (var c = 0; c < 4; c++) {
337 if (after[r][c] && after[r][c] === before[r][c] * 2) res.push([r, c]);
338 }
339 }
340 return res;
341 }
342
343 function doMove(moveFn) {
344 if (over) return;
345 var before = cloneGrid(grid);
346 var res = moveFn(grid);
347 if (gridsEqual(before, res.grid)) return; // no change
348 grid = res.grid;
349 score += res.score;
350 if (score > best) { best = score; localStorage.setItem('bkbg_2048_best_' + uid, best); }
351 mergedTiles = findMerged(before, grid);
352 var pos = addRandom(grid);
353 newTile = pos || null;
354 renderGrid();
355 updateScoreUI();
356 if (!won && hasWon(grid)) { won = true; showOverlay('\uD83C\uDF89 You Win!', 'Score: ' + score); }
357 else if (!hasMoves(grid)) { over = true; showOverlay('\uD83D\uDC80 Game Over', 'Score: ' + score); }
358 }
359
360 function newGame() {
361 grid = makeEmptyGrid();
362 score = 0;
363 won = false;
364 over = false;
365 newTile = null;
366 mergedTiles = [];
367 addRandom(grid);
368 addRandom(grid);
369 removeOverlay();
370 renderGrid();
371 updateScoreUI();
372 }
373
374 renderGrid();
375 updateScoreUI();
376
377 // Keyboard
378 document.addEventListener('keydown', function (e) {
379 if (!boardEl.closest('body')) return; // guard if removed
380 switch (e.key) {
381 case 'ArrowLeft': e.preventDefault(); doMove(moveLeft); break;
382 case 'ArrowRight': e.preventDefault(); doMove(moveRight); break;
383 case 'ArrowUp': e.preventDefault(); doMove(moveUp); break;
384 case 'ArrowDown': e.preventDefault(); doMove(moveDown); break;
385 }
386 });
387
388 // Touch / swipe
389 var touchStart = null;
390 boardEl.addEventListener('touchstart', function (e) {
391 var t = e.touches[0];
392 touchStart = { x: t.clientX, y: t.clientY };
393 }, { passive: true });
394
395 boardEl.addEventListener('touchend', function (e) {
396 if (!touchStart) return;
397 var t = e.changedTouches[0];
398 var dx = t.clientX - touchStart.x;
399 var dy = t.clientY - touchStart.y;
400 touchStart = null;
401 if (Math.abs(dx) < 20 && Math.abs(dy) < 20) return;
402 if (Math.abs(dx) > Math.abs(dy)) {
403 if (dx > 0) doMove(moveRight); else doMove(moveLeft);
404 } else {
405 if (dy > 0) doMove(moveDown); else doMove(moveUp);
406 }
407 }, { passive: true });
408 }
409
410 document.querySelectorAll('.bkbg-g48-app').forEach(function (root) {
411 initBlock(root);
412 });
413 })();
414