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

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

435 lines 16.7 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 // ---- Tetromino definitions (all rotations) ----
5 var PIECES = {
6 I: { rotations: [[[0,1],[1,1],[2,1],[3,1]],[[1,0],[1,1],[1,2],[1,3]],[[0,2],[1,2],[2,2],[3,2]],[[2,0],[2,1],[2,2],[2,3]]], color: '#06b6d4' },
7 O: { rotations: [[[0,0],[0,1],[1,0],[1,1]]], color: '#f59e0b' },
8 T: { rotations: [[[0,1],[1,0],[1,1],[1,2]],[[0,1],[1,1],[1,2],[2,1]],[[1,0],[1,1],[1,2],[2,1]],[[0,1],[1,0],[1,1],[2,1]]], color: '#a855f7' },
9 S: { rotations: [[[0,1],[0,2],[1,0],[1,1]],[[0,1],[1,1],[1,2],[2,2]]], color: '#22c55e' },
10 Z: { rotations: [[[0,0],[0,1],[1,1],[1,2]],[[0,2],[1,1],[1,2],[2,1]]], color: '#ef4444' },
11 L: { rotations: [[[0,2],[1,0],[1,1],[1,2]],[[0,1],[1,1],[2,1],[2,2]],[[1,0],[1,1],[1,2],[2,0]],[[0,0],[0,1],[1,1],[2,1]]], color: '#f97316' },
12 J: { rotations: [[[0,0],[1,0],[1,1],[1,2]],[[0,1],[0,2],[1,1],[2,1]],[[1,0],[1,1],[1,2],[2,2]],[[0,1],[1,1],[2,0],[2,1]]], color: '#3b82f6' }
13 };
14
15 var PIECE_KEYS = Object.keys(PIECES);
16
17 // Points for clearing lines (classic Tetris scoring)
18 var LINE_POINTS = [0, 100, 300, 500, 800];
19
20 // Gravity interval in ms per level (level 1 = 800ms, each level -60ms, min 50ms)
21 function levelInterval(level) { return Math.max(50, 800 - (level - 1) * 60); }
22
23 function makeBoard(W, H) {
24 var b = [];
25 for (var r = 0; r < H; r++) { b.push(new Array(W).fill(null)); }
26 return b;
27 }
28
29 function randomPiece() {
30 var key = PIECE_KEYS[Math.floor(Math.random() * PIECE_KEYS.length)];
31 return { key: key, rot: 0, row: 0, col: 3 };
32 }
33
34 function getCells(piece) {
35 var p = PIECES[piece.key];
36 var offsets = p.rotations[piece.rot % p.rotations.length];
37 return offsets.map(function (o) { return [o[0] + piece.row, o[1] + piece.col]; });
38 }
39
40 function isValid(board, piece, W, H) {
41 var cells = getCells(piece);
42 for (var i = 0; i < cells.length; i++) {
43 var r = cells[i][0], c = cells[i][1];
44 if (c < 0 || c >= W || r >= H) return false;
45 if (r >= 0 && board[r][c]) return false;
46 }
47 return true;
48 }
49
50 function placePiece(board, piece) {
51 var cells = getCells(piece);
52 var color = PIECES[piece.key].color;
53 cells.forEach(function (rc) { if (rc[0] >= 0) board[rc[0]][rc[1]] = color; });
54 }
55
56 function clearLines(board, W, H) {
57 var cleared = [];
58 var newBoard = [];
59 board.forEach(function (row, i) {
60 if (row.every(Boolean)) { cleared.push(i); }
61 else { newBoard.push(row); }
62 });
63 while (newBoard.length < H) newBoard.unshift(new Array(W).fill(null));
64 return { board: newBoard, cleared: cleared.length };
65 }
66
67 function ghostRow(board, piece, W, H) {
68 var ghost = Object.assign({}, piece);
69 while (true) {
70 var next = Object.assign({}, ghost, { row: ghost.row + 1 });
71 if (!isValid(board, next, W, H)) break;
72 ghost = next;
73 }
74 return ghost.row;
75 }
76
77 function initBlock(root) {
78 var optsRaw = root.getAttribute('data-opts');
79 var opts;
80 try { opts = JSON.parse(optsRaw); } catch (e) { opts = {}; }
81
82 var W = opts.boardWidth || 10;
83 var H = opts.boardHeight || 20;
84 var CS = opts.cellSize || 30;
85 var bgColor = opts.bgColor || '#0f0e17';
86 var boardBg = opts.boardBg || '#1a1a2e';
87 var gridColor = opts.gridColor || '#16213e';
88 var ghostColor = opts.ghostColor || 'rgba(255,255,255,0.12)';
89 var titleColor = opts.titleColor || '#fffffe';
90 var accentColor= opts.accentColor || '#ff8906';
91 var showGhost = opts.showGhost !== false;
92 var showNext = opts.showNext !== false;
93 var startLevel = opts.startLevel || 1;
94
95 if (opts.sectionBg) root.style.background = opts.sectionBg;
96
97 // Style existing title/subtitle
98 var titleEl = root.querySelector('.bkbg-ttr-title');
99 if (titleEl) { titleEl.style.color = titleColor; }
100 var subEl = root.querySelector('.bkbg-ttr-subtitle');
101 if (subEl) { subEl.style.color = titleColor; }
102
103 // ---- Build UI ----
104 var layout = document.createElement('div');
105 layout.className = 'bkbg-ttr-layout';
106 root.appendChild(layout);
107
108 // Board canvas
109 var canvas = document.createElement('canvas');
110 canvas.className = 'bkbg-ttr-canvas';
111 canvas.width = W * CS;
112 canvas.height = H * CS;
113 canvas.style.border = '2px solid ' + accentColor + '55';
114 layout.appendChild(canvas);
115 var ctx = canvas.getContext('2d');
116
117 // Side panel
118 var panel = document.createElement('div');
119 panel.className = 'bkbg-ttr-panel';
120 layout.appendChild(panel);
121
122 // Start/Pause button
123 var startBtn = document.createElement('button');
124 startBtn.className = 'bkbg-ttr-start-btn';
125 startBtn.style.background = accentColor;
126 startBtn.style.color = '#fff';
127 startBtn.textContent = 'Start';
128 panel.appendChild(startBtn);
129
130 // Next piece preview
131 var nextCanvas = null;
132 if (showNext) {
133 var nextWrap = document.createElement('div');
134 nextWrap.className = 'bkbg-ttr-stat-box';
135 nextWrap.style.background = bgColor;
136 nextWrap.style.border = '1px solid ' + accentColor + '33';
137 var nextLabel = document.createElement('div');
138 nextLabel.className = 'bkbg-ttr-stat-label';
139 nextLabel.style.color = accentColor;
140 nextLabel.textContent = 'NEXT';
141 nextCanvas = document.createElement('canvas');
142 nextCanvas.className = 'bkbg-ttr-next-canvas';
143 nextCanvas.width = 4 * 24;
144 nextCanvas.height = 4 * 24;
145 nextWrap.appendChild(nextLabel);
146 nextWrap.appendChild(nextCanvas);
147 panel.appendChild(nextWrap);
148 }
149
150 // Stat boxes
151 function makeStatBox(label) {
152 var box = document.createElement('div');
153 box.className = 'bkbg-ttr-stat-box';
154 box.style.background = bgColor;
155 box.style.border = '1px solid ' + accentColor + '33';
156 var lbl = document.createElement('div');
157 lbl.className = 'bkbg-ttr-stat-label';
158 lbl.style.color = accentColor;
159 lbl.textContent = label;
160 var val = document.createElement('div');
161 val.className = 'bkbg-ttr-stat-value';
162 val.style.color = titleColor;
163 val.textContent = '0';
164 box.appendChild(lbl);
165 box.appendChild(val);
166 panel.appendChild(box);
167 return val;
168 }
169
170 var scoreEl = opts.showScore !== false ? makeStatBox('SCORE') : null;
171 var levelEl = opts.showLevel !== false ? makeStatBox('LEVEL') : null;
172 var linesEl = opts.showLines !== false ? makeStatBox('LINES') : null;
173 if (levelEl) levelEl.textContent = startLevel;
174
175 // On-screen controls
176 var ctrlDiv = document.createElement('div');
177 ctrlDiv.className = 'bkbg-ttr-controls';
178
179 function makeCtrl(label) {
180 var btn = document.createElement('button');
181 btn.className = 'bkbg-ttr-btn';
182 btn.style.background = accentColor + '33';
183 btn.style.color = titleColor;
184 btn.textContent = label;
185 return btn;
186 }
187
188 var btnUp = makeCtrl('');
189 var btnLeft = makeCtrl('');
190 var btnDown = makeCtrl('');
191 var btnRight = makeCtrl('');
192 var btnDrop = makeCtrl('');
193 btnDrop.style.width = '98px';
194
195 var row1 = document.createElement('div'); row1.className = 'bkbg-ttr-ctrl-row'; row1.appendChild(btnUp);
196 var row2 = document.createElement('div'); row2.className = 'bkbg-ttr-ctrl-row';
197 [btnLeft, btnDown, btnRight].forEach(function (b) { row2.appendChild(b); });
198 var row3 = document.createElement('div'); row3.className = 'bkbg-ttr-ctrl-row'; row3.appendChild(btnDrop);
199
200 ctrlDiv.appendChild(row1);
201 ctrlDiv.appendChild(row2);
202 ctrlDiv.appendChild(row3);
203
204 var hint = document.createElement('div');
205 hint.className = 'bkbg-ttr-keys-hint';
206 hint.style.color = titleColor;
207 hint.innerHTML = '← → Move &nbsp; ↑ Rotate<br>↓ Soft Drop &nbsp; Space: Hard Drop';
208 ctrlDiv.appendChild(hint);
209 panel.appendChild(ctrlDiv);
210
211 // ---- Game state ----
212 var board, current, next, score, level, lines;
213 var running = false, paused = false, over = false;
214 var dropTimer = 0, lastTime = 0;
215 var rafId = null;
216
217 function initState() {
218 board = makeBoard(W, H);
219 current = randomPiece();
220 next = randomPiece();
221 score = 0;
222 level = startLevel;
223 lines = 0;
224 over = false;
225 if (scoreEl) scoreEl.textContent = 0;
226 if (levelEl) levelEl.textContent = level;
227 if (linesEl) linesEl.textContent = 0;
228 }
229
230 // ---- Drawing ----
231 function drawCell(ctx, c, r, color, cellSize) {
232 var x = c * cellSize, y = r * cellSize, s = cellSize - 1;
233 ctx.fillStyle = color;
234 ctx.beginPath();
235 ctx.roundRect(x + 1, y + 1, s - 1, s - 1, cellSize > 20 ? 3 : 1);
236 ctx.fill();
237 // Highlight
238 ctx.fillStyle = 'rgba(255,255,255,0.22)';
239 ctx.fillRect(x + 2, y + 2, s - 2, 3);
240 ctx.fillStyle = 'rgba(0,0,0,0.18)';
241 ctx.fillRect(x + 2, y + s - 2, s - 2, 2);
242 }
243
244 function drawBoard() {
245 // Background
246 ctx.fillStyle = boardBg;
247 ctx.fillRect(0, 0, W * CS, H * CS);
248
249 // Grid lines
250 ctx.strokeStyle = gridColor;
251 ctx.lineWidth = 0.5;
252 for (var c = 0; c <= W; c++) { ctx.beginPath(); ctx.moveTo(c * CS, 0); ctx.lineTo(c * CS, H * CS); ctx.stroke(); }
253 for (var r = 0; r <= H; r++) { ctx.beginPath(); ctx.moveTo(0, r * CS); ctx.lineTo(W * CS, r * CS); ctx.stroke(); }
254
255 // Placed pieces
256 board.forEach(function (row, r) {
257 row.forEach(function (color, c) {
258 if (color) drawCell(ctx, c, r, color, CS);
259 });
260 });
261
262 if (!running || over) return;
263
264 // Ghost piece
265 if (showGhost && current) {
266 var gRow = ghostRow(board, current, W, H);
267 getCells(Object.assign({}, current, { row: gRow })).forEach(function (rc) {
268 if (rc[0] >= 0) {
269 ctx.fillStyle = ghostColor;
270 ctx.fillRect(rc[1] * CS + 1, rc[0] * CS + 1, CS - 2, CS - 2);
271 }
272 });
273 }
274
275 // Current piece
276 if (current) {
277 getCells(current).forEach(function (rc) {
278 if (rc[0] >= 0) drawCell(ctx, rc[1], rc[0], PIECES[current.key].color, CS);
279 });
280 }
281 }
282
283 function drawNext() {
284 if (!nextCanvas || !next) return;
285 var nctx = nextCanvas.getContext('2d');
286 nctx.fillStyle = bgColor;
287 nctx.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
288 var cells = getCells(Object.assign({}, next, { row: 0, col: 0 }));
289 var minC = Math.min.apply(null, cells.map(function (c) { return c[1]; }));
290 var minR = Math.min.apply(null, cells.map(function (c) { return c[0]; }));
291 cells.forEach(function (rc) {
292 var c = rc[1] - minC, r = rc[0] - minR;
293 drawCell(nctx, c + 0.5, r + 0.5, PIECES[next.key].color, 20);
294 });
295 }
296
297 function drawOverlay(msg) {
298 ctx.fillStyle = 'rgba(0,0,0,0.72)';
299 ctx.fillRect(0, 0, W * CS, H * CS);
300 ctx.fillStyle = titleColor;
301 ctx.font = 'bold ' + CS + 'px system-ui';
302 ctx.textAlign = 'center';
303 ctx.fillText(msg, W * CS / 2, H * CS / 2);
304 ctx.font = '14px system-ui';
305 ctx.fillStyle = accentColor;
306 ctx.fillText('Press Start', W * CS / 2, H * CS / 2 + CS + 10);
307 }
308
309 // ---- Spawn & lock ----
310 function spawn() {
311 current = next;
312 next = randomPiece();
313 drawNext();
314 if (!isValid(board, current, W, H)) {
315 over = true;
316 running = false;
317 drawBoard();
318 drawOverlay('GAME OVER');
319 startBtn.textContent = 'Restart';
320 if (rafId) cancelAnimationFrame(rafId);
321 }
322 }
323
324 function lockPiece() {
325 placePiece(board, current);
326 var result = clearLines(board, W, H);
327 board = result.board;
328 lines += result.cleared;
329 score += LINE_POINTS[result.cleared] * level;
330 level = startLevel + Math.floor(lines / 10);
331 if (scoreEl) scoreEl.textContent = score;
332 if (levelEl) levelEl.textContent = level;
333 if (linesEl) linesEl.textContent = lines;
334 spawn();
335 }
336
337 // ---- Move helpers ----
338 function tryMove(dr, dc) {
339 if (!running || paused || over) return false;
340 var moved = Object.assign({}, current, { row: current.row + dr, col: current.col + dc });
341 if (isValid(board, moved, W, H)) { current = moved; return true; }
342 return false;
343 }
344
345 function rotate() {
346 if (!running || paused || over) return;
347 var p = PIECES[current.key];
348 var nextRot = (current.rot + 1) % p.rotations.length;
349 var rotated = Object.assign({}, current, { rot: nextRot });
350 // Wall kick: try center, -1, +1, -2, +2
351 var kicks = [0, -1, 1, -2, 2];
352 for (var i = 0; i < kicks.length; i++) {
353 var kicked = Object.assign({}, rotated, { col: rotated.col + kicks[i] });
354 if (isValid(board, kicked, W, H)) { current = kicked; return; }
355 }
356 }
357
358 function hardDrop() {
359 if (!running || paused || over) return;
360 while (tryMove(1, 0)) {}
361 lockPiece();
362 dropTimer = 0;
363 }
364
365 // ---- Game loop ----
366 function loop(ts) {
367 var dt = ts - lastTime;
368 lastTime = ts;
369 if (!paused && !over) {
370 dropTimer += dt;
371 if (dropTimer >= levelInterval(level)) {
372 dropTimer = 0;
373 if (!tryMove(1, 0)) lockPiece();
374 }
375 }
376 drawBoard();
377 if (rafId !== null) rafId = requestAnimationFrame(loop);
378 }
379
380 function startGame() {
381 if (rafId) cancelAnimationFrame(rafId);
382 initState();
383 running = true;
384 paused = false;
385 dropTimer = 0;
386 lastTime = performance.now();
387 spawn();
388 drawNext();
389 rafId = requestAnimationFrame(loop);
390 startBtn.textContent = 'Pause';
391 }
392
393 function togglePause() {
394 if (!running || over) return;
395 paused = !paused;
396 startBtn.textContent = paused ? 'Resume' : 'Pause';
397 }
398
399 startBtn.addEventListener('click', function () {
400 if (!running || over) startGame();
401 else togglePause();
402 });
403
404 // Touch-friendly controls
405 btnLeft.addEventListener('click', function () { tryMove(0, -1); });
406 btnRight.addEventListener('click', function () { tryMove(0, 1); });
407 btnDown.addEventListener('click', function () { if (!tryMove(1, 0)) lockPiece(); dropTimer = 0; });
408 btnUp.addEventListener('click', function () { rotate(); });
409 btnDrop.addEventListener('click', function () { hardDrop(); });
410
411 // Keyboard — only when canvas is focused/visible
412 document.addEventListener('keydown', function (e) {
413 if (!canvas.closest('body')) return;
414 if (!running) return;
415 switch (e.key) {
416 case 'ArrowLeft': e.preventDefault(); tryMove(0, -1); break;
417 case 'ArrowRight': e.preventDefault(); tryMove(0, 1); break;
418 case 'ArrowDown': e.preventDefault(); if (!tryMove(1, 0)) lockPiece(); dropTimer = 0; break;
419 case 'ArrowUp': e.preventDefault(); rotate(); break;
420 case ' ': e.preventDefault(); hardDrop(); break;
421 case 'p': case 'P': togglePause(); break;
422 }
423 });
424
425 // Initial draw
426 initState();
427 drawBoard();
428 drawOverlay('TETRIS');
429 }
430
431 document.querySelectorAll('.bkbg-ttr-app').forEach(function (root) {
432 initBlock(root);
433 });
434 })();
435