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 / tic-tac-toe / frontend.js

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

375 lines 13.9 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 LINES = [
5 [0,1,2],[3,4,5],[6,7,8], // rows
6 [0,3,6],[1,4,7],[2,5,8], // cols
7 [0,4,8],[2,4,6] // diagonals
8 ];
9
10 function checkWinner(board) {
11 for (var i = 0; i < LINES.length; i++) {
12 var a = LINES[i][0], b = LINES[i][1], c = LINES[i][2];
13 if (board[a] && board[a] === board[b] && board[a] === board[c]) {
14 return { sym: board[a], line: LINES[i] };
15 }
16 }
17 return null;
18 }
19
20 function isFull(board) {
21 return board.every(function (c) { return c !== ''; });
22 }
23
24 /* Minimax for hard AI */
25 function minimax(board, depth, isMax, aiSym, playerSym) {
26 var w = checkWinner(board);
27 if (w) return w.sym === aiSym ? 10 - depth : depth - 10;
28 if (isFull(board)) return 0;
29
30 var best = isMax ? -Infinity : Infinity;
31 for (var i = 0; i < 9; i++) {
32 if (board[i] !== '') continue;
33 board[i] = isMax ? aiSym : playerSym;
34 var score = minimax(board, depth + 1, !isMax, aiSym, playerSym);
35 board[i] = '';
36 best = isMax ? Math.max(best, score) : Math.min(best, score);
37 }
38 return best;
39 }
40
41 function bestMoveHard(board, aiSym, playerSym) {
42 var best = -Infinity, move = -1;
43 for (var i = 0; i < 9; i++) {
44 if (board[i] !== '') continue;
45 board[i] = aiSym;
46 var score = minimax(board, 0, false, aiSym, playerSym);
47 board[i] = '';
48 if (score > best) { best = score; move = i; }
49 }
50 return move;
51 }
52
53 function bestMoveMedium(board, aiSym, playerSym) {
54 // Win if possible
55 for (var i = 0; i < 9; i++) {
56 if (board[i] !== '') continue;
57 board[i] = aiSym;
58 if (checkWinner(board)) { board[i] = ''; return i; }
59 board[i] = '';
60 }
61 // Block player
62 for (var j = 0; j < 9; j++) {
63 if (board[j] !== '') continue;
64 board[j] = playerSym;
65 if (checkWinner(board)) { board[j] = ''; return j; }
66 board[j] = '';
67 }
68 // Center
69 if (board[4] === '') return 4;
70 // Random
71 return bestMoveRandom(board);
72 }
73
74 function bestMoveRandom(board) {
75 var empties = [];
76 for (var i = 0; i < 9; i++) {
77 if (board[i] === '') empties.push(i);
78 }
79 return empties[Math.floor(Math.random() * empties.length)];
80 }
81
82 function initBlock(root) {
83 var opts = {};
84 try { opts = JSON.parse(root.getAttribute('data-opts') || '{}'); } catch (e) {}
85
86 var playerSym = opts.playerSymbol || 'X';
87 var aiSym = opts.aiSymbol || 'O';
88 var mode = opts.defaultMode || 'ai'; // 'ai' | 'pvp'
89 var aiDiff = opts.aiDifficulty || 'hard';
90 var showScore = opts.showScore !== false;
91 var accentColor = opts.accentColor || '#6366f1';
92 var xColor = opts.xColor || '#ef4444';
93 var oColor = opts.oColor || '#3b82f6';
94 var cellBg = opts.cellBg || '#ffffff';
95 var winLineBg = opts.winLineBg || '#22c55e';
96 var gridLine = opts.gridLine || '#d1d5db';
97 var sectionBg = opts.sectionBg || '#eef2ff';
98 var cardBg = opts.cardBg || '#ffffff';
99 var titleColor = opts.titleColor || '#312e81';
100 var fontSize = opts.fontSize || 28;
101 var subtitleSize = opts.subtitleSize || 15;
102
103 // Unique ID for localStorage
104 var uid = root.getAttribute('data-uid') || root.id || Math.random().toString(36).slice(2);
105
106 // Score persistence
107 var storeKey = 'bkbg_ttt_' + uid;
108 var stored = {};
109 try { stored = JSON.parse(localStorage.getItem(storeKey) || '{}'); } catch (e) {}
110 var score = {
111 player: parseInt(stored.player || 0, 10),
112 ai: parseInt(stored.ai || 0, 10),
113 draws: parseInt(stored.draws || 0, 10)
114 };
115
116 // Game state
117 var board = ['','','','','','','','',''];
118 var currentPlayer = playerSym; // who plays next (pvp uses player1/player2)
119 var player1 = playerSym, player2 = aiSym;
120 var gameOver = false;
121 var aiThinking = false;
122 var currentMode = mode;
123
124 // ----- Render helpers -----
125 function saveScore() {
126 try { localStorage.setItem(storeKey, JSON.stringify(score)); } catch (e) {}
127 }
128
129 function render() {
130 root.innerHTML = '';
131 root.style.background = sectionBg;
132 root.style.borderRadius = '16px';
133 root.style.padding = '28px 20px';
134 root.style.textAlign = 'center';
135
136 var wrap = document.createElement('div');
137 wrap.className = 'bkbg-ttt-wrap';
138 wrap.style.padding = '0';
139
140 // Title
141 var titleEl = root.parentElement && root.parentElement.querySelector('.bkbg-ttt-title-ext');
142 // Title/Subtitle are output by save() outside .bkbg-ttt-app — just render score + board here
143
144 // Score
145 if (showScore) {
146 var scoreRow = document.createElement('div');
147 scoreRow.className = 'bkbg-ttt-score';
148 var scoreItems = [
149 { label: currentMode === 'ai' ? 'You' : 'Player 1', val: score.player, color: xColor },
150 { label: 'Draw', val: score.draws, color: '#6b7280' },
151 { label: currentMode === 'ai' ? 'AI' : 'Player 2', val: score.ai, color: oColor }
152 ];
153 scoreItems.forEach(function (s) {
154 var box = document.createElement('div');
155 box.className = 'bkbg-ttt-score-box';
156 box.style.background = cardBg;
157 box.style.borderTop = '4px solid ' + s.color;
158 var val = document.createElement('div');
159 val.className = 'bkbg-ttt-score-val';
160 val.style.color = s.color;
161 val.textContent = s.val;
162 var lbl = document.createElement('div');
163 lbl.className = 'bkbg-ttt-score-lbl';
164 lbl.style.color = titleColor;
165 lbl.textContent = s.label;
166 box.appendChild(val);
167 box.appendChild(lbl);
168 scoreRow.appendChild(box);
169 });
170 wrap.appendChild(scoreRow);
171 }
172
173 // Mode toggle
174 var modeRow = document.createElement('div');
175 modeRow.className = 'bkbg-ttt-mode-row';
176 ['ai','pvp'].forEach(function (m) {
177 var btn = document.createElement('button');
178 btn.className = 'bkbg-ttt-mode-btn';
179 btn.textContent = m === 'ai' ? 'vs AI' : 'vs Player';
180 btn.style.borderColor = accentColor;
181 btn.style.background = currentMode === m ? accentColor : 'transparent';
182 btn.style.color = currentMode === m ? '#fff' : accentColor;
183 btn.addEventListener('click', function () {
184 if (currentMode !== m) { currentMode = m; resetGame(); }
185 });
186 modeRow.appendChild(btn);
187 });
188 wrap.appendChild(modeRow);
189
190 // Status
191 var status = document.createElement('div');
192 status.className = 'bkbg-ttt-status';
193 status.id = 'bkbg-ttt-status-' + uid;
194 status.style.background = accentColor;
195 status.style.color = '#fff';
196 updateStatusEl(status);
197 wrap.appendChild(status);
198
199 // Board
200 var boardEl = document.createElement('div');
201 boardEl.className = 'bkbg-ttt-board';
202 boardEl.id = 'bkbg-ttt-board-' + uid;
203
204 var result = checkWinner(board);
205 var winLine = result ? result.line : null;
206
207 for (var i = 0; i < 9; i++) {
208 (function (idx) {
209 var cell = document.createElement('div');
210 cell.className = 'bkbg-ttt-cell' + (board[idx] ? ' bkbg-ttt-taken' : '');
211 cell.style.background = cellBg;
212 cell.style.borderColor = gridLine;
213
214 if (winLine && winLine.indexOf(idx) !== -1) {
215 cell.classList.add('bkbg-ttt-win');
216 cell.style.background = winLineBg;
217 }
218
219 if (board[idx]) {
220 var sym = document.createElement('span');
221 sym.textContent = board[idx];
222 sym.style.color = board[idx] === playerSym ? xColor : oColor;
223 cell.appendChild(sym);
224 }
225
226 cell.addEventListener('click', function () { onCellClick(idx); });
227 boardEl.appendChild(cell);
228 })(i);
229 }
230 wrap.appendChild(boardEl);
231
232 // AI thinking dots
233 var thinkEl = document.createElement('div');
234 thinkEl.id = 'bkbg-ttt-think-' + uid;
235 thinkEl.style.height = '24px';
236 thinkEl.style.marginBottom = '8px';
237 if (aiThinking) {
238 var dots = document.createElement('div');
239 dots.className = 'bkbg-ttt-thinking';
240 for (var d = 0; d < 3; d++) {
241 var sp = document.createElement('span');
242 sp.style.background = accentColor;
243 dots.appendChild(sp);
244 }
245 thinkEl.appendChild(dots);
246 }
247 wrap.appendChild(thinkEl);
248
249 // Actions
250 var actions = document.createElement('div');
251 actions.className = 'bkbg-ttt-actions';
252
253 var resetBtn = document.createElement('button');
254 resetBtn.className = 'bkbg-ttt-btn';
255 resetBtn.style.background = accentColor;
256 resetBtn.textContent = 'New Game';
257 resetBtn.addEventListener('click', function () { resetGame(); });
258
259 var clearBtn = document.createElement('button');
260 clearBtn.className = 'bkbg-ttt-btn bkbg-ttt-btn-outline';
261 clearBtn.style.borderColor = '#6b7280';
262 clearBtn.style.color = '#6b7280';
263 clearBtn.textContent = 'Reset Score';
264 clearBtn.addEventListener('click', function () {
265 score = { player: 0, ai: 0, draws: 0 };
266 saveScore();
267 render();
268 });
269
270 actions.appendChild(resetBtn);
271 actions.appendChild(clearBtn);
272 wrap.appendChild(actions);
273
274 root.appendChild(wrap);
275 }
276
277 function updateStatusEl(el) {
278 if (!el) return;
279 var result = checkWinner(board);
280 if (result) {
281 var sym = result.sym;
282 if (sym === playerSym) {
283 el.textContent = currentMode === 'ai' ? '🎉 You Win!' : '🎉 Player 1 Wins!';
284 } else {
285 el.textContent = currentMode === 'ai' ? '🤖 AI Wins!' : '🎉 Player 2 Wins!';
286 }
287 } else if (isFull(board)) {
288 el.textContent = "It's a Draw!";
289 } else {
290 if (currentMode === 'ai') {
291 el.textContent = currentPlayer === playerSym ? 'Your turn (' + playerSym + ')' : 'AI thinking…';
292 } else {
293 el.textContent = (currentPlayer === player1 ? 'Player 1' : 'Player 2') + '' + currentPlayer;
294 }
295 }
296 }
297
298 function onCellClick(idx) {
299 if (gameOver || aiThinking || board[idx] !== '') return;
300 if (currentMode === 'ai' && currentPlayer !== playerSym) return;
301
302 placeSymbol(idx, currentPlayer);
303 }
304
305 function placeSymbol(idx, sym) {
306 board[idx] = sym;
307 var result = checkWinner(board);
308
309 if (result) {
310 gameOver = true;
311 if (result.sym === playerSym) score.player++;
312 else score.ai++;
313 saveScore();
314 render();
315 return;
316 }
317
318 if (isFull(board)) {
319 gameOver = true;
320 score.draws++;
321 saveScore();
322 render();
323 return;
324 }
325
326 // Switch turn
327 currentPlayer = (currentPlayer === player1) ? player2 : player1;
328 render();
329
330 if (currentMode === 'ai' && currentPlayer === aiSym && !gameOver) {
331 scheduleAI();
332 }
333 }
334
335 function scheduleAI() {
336 aiThinking = true;
337 render();
338 var delay = aiDiff === 'easy' ? 200 : (aiDiff === 'medium' ? 350 : 550);
339 setTimeout(function () {
340 aiThinking = false;
341 var move;
342 if (aiDiff === 'hard') move = bestMoveHard(board.slice(), aiSym, playerSym);
343 else if (aiDiff === 'medium') move = bestMoveMedium(board.slice(), aiSym, playerSym);
344 else move = bestMoveRandom(board);
345
346 if (move !== undefined && move !== -1) {
347 placeSymbol(move, aiSym);
348 }
349 }, delay);
350 }
351
352 function resetGame() {
353 board = ['','','','','','','','',''];
354 currentPlayer = player1;
355 gameOver = false;
356 aiThinking = false;
357 render();
358 }
359
360 resetGame();
361 }
362
363 function init() {
364 document.querySelectorAll('.bkbg-ttt-app').forEach(function (root) {
365 initBlock(root);
366 });
367 }
368
369 if (document.readyState === 'loading') {
370 document.addEventListener('DOMContentLoaded', init);
371 } else {
372 init();
373 }
374 })();
375