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 / snake-game / frontend.js

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

343 lines 14.0 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 SPEEDS = { slow: 180, medium: 120, fast: 70, insane: 35 };
5
6 var _typoKeys = {
7 family:'font-family', weight:'font-weight', style:'font-style',
8 decoration:'text-decoration', transform:'text-transform',
9 sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m',
10 lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m',
11 letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m',
12 wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m'
13 };
14 function typoCssVarsForEl(el, obj, prefix) {
15 if (!obj || typeof obj !== 'object') return;
16 Object.keys(_typoKeys).forEach(function (k) {
17 var v = obj[k]; if (v === undefined || v === '' || v === null) return;
18 if (k === 'sizeDesktop' || k === 'sizeTablet' || k === 'sizeMobile') v = v + (obj.sizeUnit || 'px');
19 else if (k === 'lineHeightDesktop' || k === 'lineHeightTablet' || k === 'lineHeightMobile') v = v + (obj.lineHeightUnit || '');
20 else if (k === 'letterSpacingDesktop' || k === 'letterSpacingTablet' || k === 'letterSpacingMobile') v = v + (obj.letterSpacingUnit || 'px');
21 else if (k === 'wordSpacingDesktop' || k === 'wordSpacingTablet' || k === 'wordSpacingMobile') v = v + (obj.wordSpacingUnit || 'px');
22 el.style.setProperty(prefix + _typoKeys[k], String(v));
23 });
24 }
25
26 function initBlock(root) {
27 var opts = {};
28 try { opts = JSON.parse(root.getAttribute('data-opts') || '{}'); } catch (e) {}
29
30 var snakeColor = opts.snakeColor || '#22c55e';
31 var snakeHeadColor= opts.snakeHeadColor|| '#16a34a';
32 var foodColor = opts.foodColor || '#ef4444';
33 var gridColor = opts.gridColor || '#f3f4f6';
34 var boardBg = opts.boardBg || '#ffffff';
35 var sectionBg = opts.sectionBg || '#f0fdf4';
36 var accentColor = opts.accentColor || '#22c55e';
37 var titleColor = opts.titleColor || '#14532d';
38 var gridSize = parseInt(opts.gridSize || 20, 10);
39 var canvasSize = parseInt(opts.canvasSize || 400, 10);
40 var wallsKill = opts.wallsKill === true;
41 var showHighScore = opts.showHighScore !== false;
42 var showControls = opts.showControls !== false;
43 var defaultSpeed = opts.defaultSpeed || 'medium';
44 var fontSize = opts.fontSize || 28;
45 var subtitleSize = opts.subtitleSize || 14;
46
47 root.style.background = sectionBg;
48 root.style.borderRadius = '16px';
49 root.style.padding = '28px 20px';
50 root.style.textAlign = 'center';
51
52 var titleEl = root.querySelector('.bkbg-snk-title');
53 var subEl = root.querySelector('.bkbg-snk-subtitle');
54 if (titleEl) { titleEl.style.color = titleColor; titleEl.style.margin = '0 0 4px'; }
55 if (subEl) { subEl.style.color = titleColor + 'bb'; subEl.style.margin = '0 0 14px'; }
56
57 // Apply typography CSS vars to root
58 typoCssVarsForEl(root, opts.titleTypo, '--bksnk-tt-');
59 typoCssVarsForEl(root, opts.subtitleTypo, '--bksnk-st-');
60
61 var uid = Math.random().toString(36).slice(2);
62 var highScoreKey = 'bkbg_snake_hs_' + uid;
63 var highScore = parseInt(localStorage.getItem(highScoreKey) || 0, 10);
64
65 var cell = canvasSize / gridSize;
66 var currentSpeed = defaultSpeed;
67 var snake, dir, nextDir, food, score, running, gameOver, loop;
68
69 var inner = document.createElement('div');
70 root.appendChild(inner);
71
72 function buildUI() {
73 inner.innerHTML = '';
74
75 // Score row
76 var scoreRow = document.createElement('div');
77 scoreRow.className = 'bkbg-snk-score-row';
78 var scoreEl = document.createElement('div');
79 scoreEl.id = 'bkbg-snk-score-' + uid;
80 scoreEl.textContent = 'Score: ' + (score || 0);
81 scoreEl.style.color = titleColor;
82 scoreRow.appendChild(scoreEl);
83 if (showHighScore) {
84 var hsEl = document.createElement('div');
85 hsEl.id = 'bkbg-snk-hs-' + uid;
86 hsEl.textContent = 'Best: ' + highScore;
87 hsEl.style.color = accentColor;
88 scoreRow.appendChild(hsEl);
89 }
90 inner.appendChild(scoreRow);
91
92 // Speed tabs
93 var speedRow = document.createElement('div');
94 speedRow.className = 'bkbg-snk-speed-row';
95 ['slow','medium','fast','insane'].forEach(function (sp) {
96 var btn = document.createElement('button');
97 btn.className = 'bkbg-snk-speed-btn';
98 btn.textContent = sp.charAt(0).toUpperCase() + sp.slice(1);
99 btn.style.borderColor = accentColor;
100 var active = sp === currentSpeed;
101 btn.style.background = active ? accentColor : 'transparent';
102 btn.style.color = active ? '#fff' : accentColor;
103 btn.addEventListener('click', function () {
104 currentSpeed = sp;
105 buildUI();
106 startGame();
107 });
108 speedRow.appendChild(btn);
109 });
110 inner.appendChild(speedRow);
111
112 // Canvas
113 var canvasWrap = document.createElement('div');
114 canvasWrap.className = 'bkbg-snk-canvas-wrap';
115 var canvas = document.createElement('canvas');
116 canvas.className = 'bkbg-snk-canvas';
117 canvas.width = canvasSize;
118 canvas.height = canvasSize;
119 canvas.tabIndex = 0;
120 canvas.style.borderColor = accentColor;
121 canvas.style.maxWidth = '100%';
122 canvasWrap.appendChild(canvas);
123 inner.appendChild(canvasWrap);
124
125 // Touch controls
126 if (showControls) {
127 var ctrls = document.createElement('div');
128 ctrls.className = 'bkbg-snk-controls';
129 var upRow = document.createElement('div');
130 upRow.className = 'bkbg-snk-ctrl-row';
131 var midRow = document.createElement('div');
132 midRow.className = 'bkbg-snk-ctrl-row';
133
134 function makeCtrl(label, action) {
135 var b = document.createElement('button');
136 b.className = 'bkbg-snk-ctrl-btn';
137 b.textContent = label;
138 b.style.borderColor = accentColor;
139 b.style.color = accentColor;
140 b.addEventListener('click', action);
141 return b;
142 }
143 upRow.appendChild(makeCtrl('', function () { tryDir('UP'); }));
144 midRow.appendChild(makeCtrl('', function () { tryDir('LEFT'); }));
145 midRow.appendChild(makeCtrl('', function () { tryDir('DOWN'); }));
146 midRow.appendChild(makeCtrl('', function () { tryDir('RIGHT'); }));
147 ctrls.appendChild(upRow);
148 ctrls.appendChild(midRow);
149 inner.appendChild(ctrls);
150 }
151
152 // Actions
153 var actions = document.createElement('div');
154 actions.className = 'bkbg-snk-actions';
155 var startBtn = document.createElement('button');
156 startBtn.className = 'bkbg-snk-btn';
157 startBtn.style.background = accentColor;
158 startBtn.textContent = 'New Game';
159 startBtn.addEventListener('click', function () { buildUI(); startGame(); });
160 actions.appendChild(startBtn);
161 inner.appendChild(actions);
162
163 // Keyboard
164 var handleKey = function (e) {
165 if (!running) { if (e.key === ' ') { startGame(); } return; }
166 var map = { ArrowUp:'UP', ArrowDown:'DOWN', ArrowLeft:'LEFT', ArrowRight:'RIGHT', w:'UP', s:'DOWN', a:'LEFT', d:'RIGHT' };
167 if (map[e.key]) { e.preventDefault(); tryDir(map[e.key]); }
168 };
169 document.addEventListener('keydown', handleKey);
170
171 // Draw initial idle state
172 draw(canvas, null, null, false);
173
174 return canvas;
175 }
176
177 function tryDir(d) {
178 var opposite = { UP:'DOWN', DOWN:'UP', LEFT:'RIGHT', RIGHT:'LEFT' };
179 if (dir !== opposite[d]) nextDir = d;
180 }
181
182 function startGame() {
183 clearInterval(loop);
184 running = true;
185 gameOver = false;
186 score = 0;
187 dir = 'RIGHT';
188 nextDir = 'RIGHT';
189 var mid = Math.floor(gridSize / 2);
190 snake = [[mid, mid],[mid-1, mid],[mid-2, mid]];
191 placeFood();
192 updateScoreUI();
193
194 var canvas = inner.querySelector('canvas');
195 if (!canvas) return;
196
197 loop = setInterval(function () {
198 if (!running) { clearInterval(loop); return; }
199 tick();
200 draw(canvas, snake, food, true);
201 }, SPEEDS[currentSpeed] || 120);
202 }
203
204 function tick() {
205 dir = nextDir;
206 var head = snake[0].slice();
207 if (dir === 'UP') head[1]--;
208 if (dir === 'DOWN') head[1]++;
209 if (dir === 'LEFT') head[0]--;
210 if (dir === 'RIGHT') head[0]++;
211
212 // Wall collision
213 if (wallsKill) {
214 if (head[0] < 0 || head[0] >= gridSize || head[1] < 0 || head[1] >= gridSize) {
215 endGame(); return;
216 }
217 } else {
218 head[0] = (head[0] + gridSize) % gridSize;
219 head[1] = (head[1] + gridSize) % gridSize;
220 }
221
222 // Self collision
223 for (var i = 0; i < snake.length; i++) {
224 if (snake[i][0] === head[0] && snake[i][1] === head[1]) { endGame(); return; }
225 }
226
227 snake.unshift(head);
228
229 // Food eaten
230 if (head[0] === food[0] && head[1] === food[1]) {
231 score += 10;
232 if (score > highScore) { highScore = score; localStorage.setItem(highScoreKey, highScore); }
233 updateScoreUI();
234 placeFood();
235 } else {
236 snake.pop();
237 }
238 }
239
240 function placeFood() {
241 var empty = [];
242 for (var x = 0; x < gridSize; x++) {
243 for (var y = 0; y < gridSize; y++) {
244 var onSnake = snake.some(function (s) { return s[0] === x && s[1] === y; });
245 if (!onSnake) empty.push([x, y]);
246 }
247 }
248 food = empty[Math.floor(Math.random() * empty.length)] || [gridSize-1, gridSize-1];
249 }
250
251 function endGame() {
252 running = false;
253 gameOver = true;
254 clearInterval(loop);
255 var canvas = inner.querySelector('canvas');
256 if (canvas) drawGameOver(canvas);
257 }
258
259 function updateScoreUI() {
260 var se = document.getElementById('bkbg-snk-score-' + uid);
261 if (se) se.textContent = 'Score: ' + score;
262 var he = document.getElementById('bkbg-snk-hs-' + uid);
263 if (he) he.textContent = 'Best: ' + highScore;
264 }
265
266 function draw(canvas, snk, fd, started) {
267 var ctx = canvas.getContext('2d');
268 ctx.fillStyle = boardBg;
269 ctx.fillRect(0, 0, canvasSize, canvasSize);
270
271 // Grid
272 ctx.strokeStyle = gridColor;
273 ctx.lineWidth = 0.5;
274 for (var i = 0; i <= gridSize; i++) {
275 ctx.beginPath(); ctx.moveTo(i * cell, 0); ctx.lineTo(i * cell, canvasSize); ctx.stroke();
276 ctx.beginPath(); ctx.moveTo(0, i * cell); ctx.lineTo(canvasSize, i * cell); ctx.stroke();
277 }
278
279 if (!started) {
280 // Idle message
281 ctx.fillStyle = 'rgba(0,0,0,0.45)';
282 ctx.fillRect(canvasSize/2-90, canvasSize/2-20, 180, 40);
283 ctx.fillStyle = '#fff';
284 ctx.font = 'bold 14px system-ui';
285 ctx.textAlign = 'center';
286 ctx.textBaseline = 'middle';
287 ctx.fillText('Press SPACE or New Game', canvasSize/2, canvasSize/2);
288 return;
289 }
290
291 // Food
292 ctx.fillStyle = foodColor;
293 ctx.beginPath();
294 ctx.arc(fd[0]*cell + cell/2, fd[1]*cell + cell/2, cell/2 - 1, 0, Math.PI*2);
295 ctx.fill();
296
297 // Snake body
298 ctx.fillStyle = snakeColor;
299 for (var j = 1; j < snk.length; j++) {
300 ctx.beginPath();
301 ctx.roundRect(snk[j][0]*cell+1, snk[j][1]*cell+1, cell-2, cell-2, 3);
302 ctx.fill();
303 }
304
305 // Snake head
306 ctx.fillStyle = snakeHeadColor;
307 ctx.beginPath();
308 ctx.roundRect(snk[0][0]*cell+1, snk[0][1]*cell+1, cell-2, cell-2, 5);
309 ctx.fill();
310 }
311
312 function drawGameOver(canvas) {
313 var ctx = canvas.getContext('2d');
314 ctx.fillStyle = 'rgba(0,0,0,0.55)';
315 ctx.fillRect(0, 0, canvasSize, canvasSize);
316 ctx.fillStyle = '#fff';
317 ctx.font = 'bold 28px system-ui';
318 ctx.textAlign = 'center';
319 ctx.textBaseline = 'middle';
320 ctx.fillText('Game Over', canvasSize/2, canvasSize/2 - 20);
321 ctx.font = '16px system-ui';
322 ctx.fillText('Score: ' + score, canvasSize/2, canvasSize/2 + 14);
323 ctx.font = '14px system-ui';
324 ctx.fillStyle = 'rgba(255,255,255,0.7)';
325 ctx.fillText('Press Space or New Game', canvasSize/2, canvasSize/2 + 42);
326 }
327
328 buildUI();
329 }
330
331 function init() {
332 document.querySelectorAll('.bkbg-snk-app').forEach(function (root) {
333 initBlock(root);
334 });
335 }
336
337 if (document.readyState === 'loading') {
338 document.addEventListener('DOMContentLoaded', init);
339 } else {
340 init();
341 }
342 })();
343