PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.11
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.11
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 / sudoku / frontend.js

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

488 lines 21.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 _typoKeys = {
5 family:'font-family', weight:'font-weight', style:'font-style',
6 decoration:'text-decoration', transform:'text-transform',
7 sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m',
8 lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m',
9 letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m',
10 wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m'
11 };
12 function typoCssVarsForEl(el, obj, prefix) {
13 if (!obj || typeof obj !== 'object') return;
14 Object.keys(_typoKeys).forEach(function (k) {
15 var v = obj[k];
16 if (v === undefined || v === '' || v === null) return;
17 if (k === 'sizeDesktop' || k === 'sizeTablet' || k === 'sizeMobile') v = v + (obj.sizeUnit || 'px');
18 else if (k === 'lineHeightDesktop' || k === 'lineHeightTablet' || k === 'lineHeightMobile') v = v + (obj.lineHeightUnit || '');
19 else if (k === 'letterSpacingDesktop' || k === 'letterSpacingTablet' || k === 'letterSpacingMobile') v = v + (obj.letterSpacingUnit || 'px');
20 else if (k === 'wordSpacingDesktop' || k === 'wordSpacingTablet' || k === 'wordSpacingMobile') v = v + (obj.wordSpacingUnit || 'px');
21 el.style.setProperty(prefix + _typoKeys[k], String(v));
22 });
23 }
24
25 /* ================================================================
26 Sudoku Generator & Solver
27 ================================================================ */
28 var CELLS_TO_REMOVE = { easy: 35, medium: 45, hard: 52, expert: 58 };
29
30 function shuffleArr(arr) {
31 for (var i = arr.length - 1; i > 0; i--) {
32 var j = Math.floor(Math.random() * (i + 1));
33 var t = arr[i]; arr[i] = arr[j]; arr[j] = t;
34 }
35 return arr;
36 }
37
38 function makeEmpty() {
39 return Array.from({ length: 9 }, function () { return new Array(9).fill(0); });
40 }
41
42 function isValid(grid, row, col, num) {
43 // Row
44 for (var c = 0; c < 9; c++) { if (grid[row][c] === num) return false; }
45 // Col
46 for (var r = 0; r < 9; r++) { if (grid[r][col] === num) return false; }
47 // Box
48 var br = Math.floor(row / 3) * 3, bc = Math.floor(col / 3) * 3;
49 for (var i = 0; i < 3; i++) for (var j = 0; j < 3; j++) {
50 if (grid[br + i][bc + j] === num) return false;
51 }
52 return true;
53 }
54
55 function fillGrid(grid) {
56 for (var row = 0; row < 9; row++) {
57 for (var col = 0; col < 9; col++) {
58 if (grid[row][col] !== 0) continue;
59 var nums = shuffleArr([1,2,3,4,5,6,7,8,9]);
60 for (var k = 0; k < 9; k++) {
61 if (isValid(grid, row, col, nums[k])) {
62 grid[row][col] = nums[k];
63 if (fillGrid(grid)) return true;
64 grid[row][col] = 0;
65 }
66 }
67 return false;
68 }
69 }
70 return true;
71 }
72
73 function generatePuzzle(difficulty) {
74 var solution = makeEmpty();
75 fillGrid(solution);
76
77 // Deep-copy solution → puzzle
78 var puzzle = solution.map(function (r) { return r.slice(); });
79 var toRemove = CELLS_TO_REMOVE[difficulty] || 45;
80 var positions = shuffleArr(
81 Array.from({ length: 81 }, function (_, i) { return i; })
82 );
83 var removed = 0;
84 for (var i = 0; i < positions.length && removed < toRemove; i++) {
85 var row = Math.floor(positions[i] / 9);
86 var col = positions[i] % 9;
87 puzzle[row][col] = 0;
88 removed++;
89 }
90 return { puzzle: puzzle, solution: solution };
91 }
92
93 /* ================================================================
94 Block initializer
95 ================================================================ */
96 function initBlock(root) {
97 var opts = {};
98 try { opts = JSON.parse(root.getAttribute('data-opts') || '{}'); } catch (e) {}
99
100 var accentColor = opts.accentColor || '#6366f1';
101 var givenColor = opts.givenColor || '#1e1b4b';
102 var enteredColor = opts.enteredColor || '#6366f1';
103 var errorColor = opts.errorColor || '#ef4444';
104 var highlightBg = opts.highlightBg || '#e0e7ff';
105 var selectedBg = opts.selectedBg || '#c7d2fe';
106 var gridBg = opts.gridBg || '#ffffff';
107 var sectionBg = opts.sectionBg || '#f5f3ff';
108 var titleColor = opts.titleColor || '#1e1b4b';
109 var showTimer = opts.showTimer !== false;
110 var showMistakes = opts.showMistakes !== false;
111 var highlightRelated = opts.highlightRelated !== false;
112 var highlightSame = opts.highlightSameNumber !== false;
113 var maxMistakes = parseInt(opts.maxMistakes || 3, 10);
114 var hintsAllowed = parseInt(opts.hintsAllowed || 3, 10);
115 var defaultDiff = opts.defaultDifficulty || 'medium';
116 var fontSize = opts.fontSize || 28;
117 var subtitleSize = opts.subtitleSize || 14;
118
119 root.style.background = sectionBg;
120 root.style.borderRadius = '16px';
121 root.style.padding = '28px 20px';
122 root.style.textAlign = 'center';
123 root.style.fontFamily = 'system-ui,-apple-system,sans-serif';
124 root.style.boxSizing = 'border-box';
125 root.classList.add('bkbg-sdk-wrap');
126
127 typoCssVarsForEl(root, opts.titleTypo, '--bksdk-tt-');
128 typoCssVarsForEl(root, opts.subtitleTypo, '--bksdk-st-');
129
130 // Style title/subtitle from save()
131 var titleEl = root.querySelector('.bkbg-sdk-title');
132 var subEl = root.querySelector('.bkbg-sdk-subtitle');
133 if (titleEl) { titleEl.style.color = titleColor; titleEl.style.margin = '0 0 4px'; }
134 if (subEl) { subEl.style.color = titleColor + 'bb'; subEl.style.margin = '0 0 18px'; }
135
136 // State
137 var currentDiff = defaultDiff;
138 var puzzle = null;
139 var solution = null;
140 var userGrid = null; // 0 = empty, positive = value (including givens)
141 var given = null; // boolean mask for givens
142 var notes = null; // notes[r][c] = Set of numbers
143 var selected = null; // { r, c }
144 var mistakes = 0;
145 var hintsLeft = hintsAllowed;
146 var pencilMode = false;
147 var gameOver = false;
148 var won = false;
149 var timerSecs = 0;
150 var timerInt = null;
151
152 var inner = document.createElement('div');
153 root.appendChild(inner);
154
155 /* ---- helpers ---- */
156 function startTimer() {
157 clearInterval(timerInt);
158 timerSecs = 0;
159 timerInt = setInterval(function () {
160 if (gameOver) return;
161 timerSecs++;
162 var el = document.getElementById('bkbg-sdk-timer-' + _uid);
163 if (el) el.textContent = '' + formatTime(timerSecs);
164 }, 1000);
165 }
166
167 function formatTime(s) {
168 var m = Math.floor(s / 60);
169 return m + ':' + ('0' + (s % 60)).slice(-2);
170 }
171
172 var _uid = Math.random().toString(36).slice(2);
173
174 function newGame() {
175 var result = generatePuzzle(currentDiff);
176 puzzle = result.puzzle;
177 solution = result.solution;
178 userGrid = puzzle.map(function (r) { return r.slice(); });
179 given = puzzle.map(function (r) { return r.map(function (v) { return v !== 0; }); });
180 notes = Array.from({ length: 9 }, function () {
181 return Array.from({ length: 9 }, function () { return {}; });
182 });
183 selected = null;
184 mistakes = 0;
185 hintsLeft = hintsAllowed;
186 pencilMode = false;
187 gameOver = false;
188 won = false;
189 render();
190 startTimer();
191 }
192
193 function render() {
194 inner.innerHTML = '';
195
196 // Difficulty tabs
197 var diffRow = document.createElement('div');
198 diffRow.className = 'bkbg-sdk-diff-row';
199 ['easy','medium','hard','expert'].forEach(function (d) {
200 var btn = document.createElement('button');
201 btn.className = 'bkbg-sdk-diff-btn';
202 btn.textContent = d.charAt(0).toUpperCase() + d.slice(1);
203 btn.style.borderColor = accentColor;
204 if (d === currentDiff) { btn.style.background = accentColor; btn.style.color = '#fff'; }
205 else { btn.style.background = 'transparent'; btn.style.color = accentColor; }
206 btn.addEventListener('click', function () { currentDiff = d; newGame(); });
207 diffRow.appendChild(btn);
208 });
209 inner.appendChild(diffRow);
210
211 if (gameOver) {
212 renderOverlay();
213 return;
214 }
215
216 // Info bar
217 var info = document.createElement('div');
218 info.className = 'bkbg-sdk-info';
219 if (showTimer) {
220 var timerEl = document.createElement('div');
221 timerEl.id = 'bkbg-sdk-timer-' + _uid;
222 timerEl.style.color = titleColor;
223 timerEl.textContent = '' + formatTime(timerSecs);
224 info.appendChild(timerEl);
225 }
226 if (showMistakes) {
227 var mistEl = document.createElement('div');
228 mistEl.style.color = errorColor;
229 mistEl.textContent = '' + mistakes + ' / ' + maxMistakes;
230 info.appendChild(mistEl);
231 }
232 var hintEl = document.createElement('div');
233 hintEl.style.color = accentColor;
234 hintEl.textContent = '💡 ' + hintsLeft;
235 info.appendChild(hintEl);
236 inner.appendChild(info);
237
238 // Grid
239 var boardWrap = document.createElement('div');
240 boardWrap.className = 'bkbg-sdk-board-wrap';
241 var grid = document.createElement('div');
242 grid.className = 'bkbg-sdk-grid';
243 grid.style.borderColor = givenColor;
244
245 for (var r = 0; r < 9; r++) {
246 for (var c = 0; c < 9; c++) {
247 (function (row, col) {
248 var cell = document.createElement('div');
249 cell.className = 'bkbg-sdk-cell';
250
251 // Thick borders for box boundaries
252 if (col === 2 || col === 5) { cell.style.borderRightWidth = '2px'; cell.style.borderRightColor = givenColor; }
253 else cell.style.borderRightColor = '#d1d5db';
254 if (row === 2 || row === 5) { cell.style.borderBottomWidth = '2px'; cell.style.borderBottomColor = givenColor; }
255 else cell.style.borderBottomColor = '#d1d5db';
256 if (col === 8) cell.style.borderRightWidth = '0';
257 if (row === 8) cell.style.borderBottomWidth = '0';
258
259 var val = userGrid[row][col];
260 var isGiven = given[row][col];
261 var isSelected = selected && selected.r === row && selected.c === col;
262 var isRelated = !isSelected && selected && highlightRelated && (
263 selected.r === row || selected.c === col ||
264 (Math.floor(selected.r/3)===Math.floor(row/3) && Math.floor(selected.c/3)===Math.floor(col/3))
265 );
266 var isSame = !isSelected && selected && highlightSame && val && userGrid[selected.r][selected.c] === val;
267 var isError = val && !isGiven && solution && val !== solution[row][col];
268
269 cell.style.background = isSelected ? selectedBg
270 : isSame ? (selectedBg + 'aa')
271 : isRelated ? highlightBg
272 : gridBg;
273
274 if (val) {
275 if (Object.keys(notes[row][col]).length === 0) {
276 // Show value
277 cell.textContent = val;
278 cell.style.color = isError ? errorColor : (isGiven ? givenColor : enteredColor);
279 cell.style.fontWeight = isGiven ? '900' : '700';
280 } else {
281 // Has notes + value — show value
282 cell.textContent = val;
283 cell.style.color = isError ? errorColor : (isGiven ? givenColor : enteredColor);
284 }
285 } else if (Object.keys(notes[row][col]).length > 0) {
286 // Show notes
287 var noteGrid = document.createElement('div');
288 noteGrid.className = 'bkbg-sdk-notes';
289 for (var n = 1; n <= 9; n++) {
290 var nd = document.createElement('div');
291 nd.className = 'bkbg-sdk-note';
292 nd.textContent = notes[row][col][n] ? n : '';
293 nd.style.color = accentColor + 'cc';
294 noteGrid.appendChild(nd);
295 }
296 cell.appendChild(noteGrid);
297 }
298
299 cell.addEventListener('click', function () {
300 if (isGiven) { selected = { r: row, c: col }; render(); return; }
301 selected = { r: row, c: col };
302 render();
303 });
304
305 grid.appendChild(cell);
306 })(r, c);
307 }
308 }
309 boardWrap.appendChild(grid);
310 inner.appendChild(boardWrap);
311
312 // Number pad
313 var numPad = document.createElement('div');
314 numPad.className = 'bkbg-sdk-numpad';
315
316 // Pencil toggle
317 var pencilBtn = document.createElement('button');
318 pencilBtn.className = 'bkbg-sdk-pencil-btn';
319 pencilBtn.title = 'Pencil / Notes mode';
320 pencilBtn.textContent = '✏️';
321 pencilBtn.style.borderColor = pencilMode ? accentColor : '#9ca3af';
322 pencilBtn.style.background = pencilMode ? accentColor + '22' : 'transparent';
323 pencilBtn.style.color = pencilMode ? accentColor : '#9ca3af';
324 pencilBtn.addEventListener('click', function () { pencilMode = !pencilMode; render(); });
325 numPad.appendChild(pencilBtn);
326
327 for (var n = 1; n <= 9; n++) {
328 (function (num) {
329 var btn = document.createElement('button');
330 btn.className = 'bkbg-sdk-num-btn';
331 btn.textContent = num;
332 btn.style.borderColor = accentColor;
333 btn.style.background = 'transparent';
334 btn.style.color = accentColor;
335 btn.addEventListener('click', function () { enterNumber(num); });
336 numPad.appendChild(btn);
337 })(n);
338 }
339
340 var eraseBtn = document.createElement('button');
341 eraseBtn.className = 'bkbg-sdk-num-btn bkbg-sdk-erase-btn';
342 eraseBtn.textContent = '';
343 eraseBtn.addEventListener('click', function () { enterNumber(0); });
344 numPad.appendChild(eraseBtn);
345 inner.appendChild(numPad);
346
347 // Actions
348 var actions = document.createElement('div');
349 actions.className = 'bkbg-sdk-actions';
350
351 var newBtn = document.createElement('button');
352 newBtn.className = 'bkbg-sdk-btn';
353 newBtn.style.background = accentColor;
354 newBtn.textContent = 'New Game';
355 newBtn.addEventListener('click', newGame);
356 actions.appendChild(newBtn);
357
358 var hintActionBtn = document.createElement('button');
359 hintActionBtn.className = 'bkbg-sdk-btn bkbg-sdk-btn-outline';
360 hintActionBtn.style.borderColor = accentColor;
361 hintActionBtn.style.color = accentColor;
362 hintActionBtn.textContent = '💡 Hint (' + hintsLeft + ')';
363 hintActionBtn.disabled = hintsLeft <= 0;
364 hintActionBtn.style.opacity = hintsLeft > 0 ? 1 : 0.4;
365 hintActionBtn.addEventListener('click', useHint);
366 actions.appendChild(hintActionBtn);
367
368 inner.appendChild(actions);
369
370 // Keyboard support
371 root.setAttribute('tabindex', '0');
372 }
373
374 function enterNumber(num) {
375 if (!selected || gameOver) return;
376 var r = selected.r, c = selected.c;
377 if (given[r][c]) return;
378
379 if (pencilMode && num > 0) {
380 if (notes[r][c][num]) delete notes[r][c][num];
381 else notes[r][c][num] = true;
382 userGrid[r][c] = 0;
383 } else {
384 notes[r][c] = {};
385 userGrid[r][c] = num;
386 if (num > 0 && num !== solution[r][c]) {
387 mistakes++;
388 if (mistakes >= maxMistakes) { gameOver = true; won = false; clearInterval(timerInt); render(); return; }
389 }
390 }
391 render();
392 checkWin();
393 }
394
395 function useHint() {
396 if (hintsLeft <= 0 || !selected || gameOver) return;
397 var r = selected.r, c = selected.c;
398 if (given[r][c] || userGrid[r][c] === solution[r][c]) {
399 // Find first empty cell
400 for (var row = 0; row < 9; row++) {
401 for (var col = 0; col < 9; col++) {
402 if (!given[row][col] && userGrid[row][col] !== solution[row][col]) {
403 selected = { r: row, c: col };
404 return useHint();
405 }
406 }
407 }
408 return;
409 }
410 notes[r][c] = {};
411 userGrid[r][c] = solution[r][c];
412 hintsLeft--;
413 render();
414 checkWin();
415 }
416
417 function checkWin() {
418 for (var r = 0; r < 9; r++) {
419 for (var c = 0; c < 9; c++) {
420 if (userGrid[r][c] !== solution[r][c]) return;
421 }
422 }
423 won = true;
424 gameOver = true;
425 clearInterval(timerInt);
426 render();
427 }
428
429 function renderOverlay() {
430 var ov = document.createElement('div');
431 ov.className = 'bkbg-sdk-overlay';
432
433 var emoji = document.createElement('div');
434 emoji.className = 'bkbg-sdk-overlay-emoji';
435 emoji.textContent = won ? '🎉' : '💀';
436
437 var title = document.createElement('div');
438 title.className = 'bkbg-sdk-overlay-title';
439 title.style.color = titleColor;
440 title.textContent = won ? 'Puzzle Solved!' : 'Game Over!';
441
442 var sub = document.createElement('div');
443 sub.className = 'bkbg-sdk-overlay-sub';
444 sub.style.color = titleColor + 'aa';
445 sub.textContent = won
446 ? 'Completed in ' + formatTime(timerSecs) + ' with ' + mistakes + ' mistake(s)'
447 : 'You reached ' + maxMistakes + ' mistakes. Try again!';
448
449 var reBtn = document.createElement('button');
450 reBtn.className = 'bkbg-sdk-btn';
451 reBtn.style.background = accentColor;
452 reBtn.textContent = 'Play Again';
453 reBtn.addEventListener('click', newGame);
454
455 ov.appendChild(emoji);
456 ov.appendChild(title);
457 ov.appendChild(sub);
458 ov.appendChild(reBtn);
459 inner.appendChild(ov);
460 }
461
462 // Keyboard input
463 root.addEventListener('keydown', function (e) {
464 if (!selected || gameOver) return;
465 if (e.key >= '1' && e.key <= '9') { enterNumber(parseInt(e.key, 10)); }
466 else if (e.key === '0' || e.key === 'Backspace' || e.key === 'Delete') { enterNumber(0); }
467 else if (e.key === 'ArrowUp' && selected.r > 0) { selected.r--; render(); }
468 else if (e.key === 'ArrowDown' && selected.r < 8) { selected.r++; render(); }
469 else if (e.key === 'ArrowLeft' && selected.c > 0) { selected.c--; render(); }
470 else if (e.key === 'ArrowRight' && selected.c < 8) { selected.c++; render(); }
471 });
472
473 newGame();
474 }
475
476 function init() {
477 document.querySelectorAll('.bkbg-sdk-app').forEach(function (root) {
478 initBlock(root);
479 });
480 }
481
482 if (document.readyState === 'loading') {
483 document.addEventListener('DOMContentLoaded', init);
484 } else {
485 init();
486 }
487 })();
488