/* ViKernel Desktop: Minesweeper (VDR app). Original code and art, drawn with CSS/SVG. */ (function () { 'use strict'; // ---------- i18n ---------- var STR = { en: { mines_title: 'Minesweeper', mines_beginner: 'Beginner', mines_intermediate: 'Intermediate', mines_best: 'Best', mines_none: 'none', mines_new: 'New game', mines_hint: 'AI hint (Muse Spark)', mines_cheat: 'Try to cheat: read mine map', mines_agent: 'Agent and kernel', mines_audit: 'App audit', mines_win: 'Cleared! Time', mines_lose: 'Boom. All mines revealed.', mines_newbest: 'New best time!', mines_thinking: 'Muse Spark is reading the visible board...', mines_empty_agent: 'No agent activity yet. Ask for an AI hint to see the agent propose a tool call and the kernel decide.', mines_empty_audit: 'No verdicts recorded in this session yet.', mines_suggest: 'AI suggestion, may be wrong', mines_rate: 'Too many requests. Please wait a moment and try again.', mines_err: 'The request failed', mines_nohint: 'The model did not name a hidden cell this time.', mines_over: 'Start a new game to ask for a hint.', mines_about: 'About', mines_close: 'Close', mines_retry: 'Retry' }, ko: { mines_title: '지뢰찾기', mines_beginner: '초급', mines_intermediate: '중급', mines_best: '최고', mines_none: '없음', mines_new: '새 게임', mines_hint: 'AI 힌트 (Muse Spark)', mines_cheat: '부정행위 시도: 지뢰 지도 읽기', mines_agent: '에이전트와 커널', mines_audit: '앱 감사 기록', mines_win: '성공! 시간', mines_lose: '펑. 모든 지뢰를 공개합니다.', mines_newbest: '최고 기록!', mines_thinking: 'Muse Spark 가 보이는 판을 읽는 중...', mines_empty_agent: '아직 에이전트 활동이 없습니다. AI 힌트를 요청해 보세요.', mines_empty_audit: '이 세션의 판정 기록이 아직 없습니다.', mines_suggest: 'AI 제안, 틀릴 수 있음', mines_rate: '요청이 너무 많습니다. 잠시 후 다시 시도하세요.', mines_err: '요청 실패', mines_nohint: '이번에는 모델이 숨은 칸을 지목하지 않았습니다.', mines_over: '힌트를 받으려면 새 게임을 시작하세요.', mines_about: '정보', mines_close: '닫기', mines_retry: '다시 시도' } }; try { window.VK_I18N = window.VK_I18N || {}; ['en', 'ko'].forEach(function (l) { window.VK_I18N[l] = Object.assign(window.VK_I18N[l] || {}, STR[l]); }); } catch (e) { /* ignore */ } var MODES = { beginner: { rows: 9, cols: 9, mines: 10, key: 'mines_beginner' }, intermediate: { rows: 16, cols: 16, mines: 40, key: 'mines_intermediate' } }; function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); } function lsGet(k) { try { return localStorage.getItem(k); } catch (e) { return null; } } function lsSet(k, v) { try { localStorage.setItem(k, v); } catch (e) { /* ignore */ } } // ---------- art (original) ---------- var SVG_MINE = ''; var SVG_FLAG = ''; function face(state) { var eyes = '', mouth = ''; if (state === 'tense') mouth = ''; if (state === 'lost') { eyes = ''; mouth = ''; } if (state === 'won') { eyes = ''; mouth = ''; } return ''; } // ---------- styles ---------- var CSS = '' + '.mn{--mn-bg:#f6f8f7;--mn-panel:#fff;--mn-ink:#16211b;--mn-sub:#5b6b62;--mn-line:#dfe6e2;--mn-hid:#cfe3d6;--mn-hid2:#b9d6c3;--mn-open:#fbfdfc;' + '--mn-acc:#16A34A;--mn-flag:#e4572e;--mn-face:#ffd76a;--mn-boom:#fde2dc;--mn-ok:#15803d;--mn-bad:#dc2626;' + '--n1:#2563eb;--n2:#0f8a5f;--n3:#d9480f;--n4:#7c3aed;--n5:#b42318;--n6:#0e7490;--n7:#334155;--n8:#a16207;' + 'display:flex;flex-direction:column;height:100%;background:var(--mn-bg);color:var(--mn-ink);font:13px/1.4 system-ui,-apple-system,"Segoe UI",sans-serif;box-sizing:border-box;position:relative;overflow:hidden}' + '@media (prefers-color-scheme:dark){.mn{--mn-bg:#101613;--mn-panel:#17201b;--mn-ink:#e6efe9;--mn-sub:#93a59a;--mn-line:#26332c;--mn-hid:#2a3d32;--mn-hid2:#335042;--mn-open:#141c18;' + '--mn-face:#e8bf4c;--mn-boom:#4a1f1a;--n1:#7aa7ff;--n2:#4fd19c;--n3:#ff9b5e;--n4:#c4a1ff;--n5:#ff7a73;--n6:#5fd3e6;--n7:#cbd5e1;--n8:#f1c84b}}' + '.mn *{box-sizing:border-box}.mn button{font:inherit;color:inherit}' + '.mn-strip{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:6px 12px;background:color-mix(in srgb,var(--mn-acc) 12%,var(--mn-panel));border-bottom:1px solid var(--mn-line);font-size:11.5px;color:var(--mn-sub)}' + '.mn-strip b{color:var(--mn-acc)}.mn-strip .mn-i{margin-left:auto}' + '.mn-i{width:24px;height:24px;border-radius:50%;border:1px solid var(--mn-line);background:var(--mn-panel);cursor:pointer;font-weight:700;font-style:italic;font-family:Georgia,serif}' + '.mn-main{flex:1;display:flex;gap:12px;padding:12px;min-height:0}' + '.mn-left{flex:1 1 auto;display:flex;flex-direction:column;gap:10px;min-width:0;min-height:0}' + '.mn-side{flex:0 0 230px;display:flex;flex-direction:column;gap:10px;min-height:0;overflow:auto}' + '.mn.narrow .mn-main{flex-direction:column;overflow:auto}.mn.narrow .mn-side{flex:0 0 auto;overflow:visible}.mn.narrow .mn-left{flex:0 0 auto}' + '.mn-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}' + '.mn-seg{display:inline-flex;border:1px solid var(--mn-line);border-radius:9px;overflow:hidden;background:var(--mn-panel)}' + '.mn-seg button{border:0;background:none;padding:5px 10px;cursor:pointer}.mn-seg button[aria-pressed=true]{background:var(--mn-acc);color:#fff}' + '.mn-lcd{font:600 15px ui-monospace,Consolas,monospace;padding:4px 9px;border-radius:8px;background:var(--mn-panel);border:1px solid var(--mn-line);min-width:52px;text-align:center}' + '.mn-face{width:38px;height:38px;border-radius:11px;border:1px solid var(--mn-line);background:var(--mn-panel);cursor:pointer;padding:3px}.mn-face svg{width:100%;height:100%}' + '.mn-face:active{transform:scale(.95)}.mn-best{color:var(--mn-sub);font-size:12px}.mn-sp{flex:1}' + '.mn-wrap{flex:1;min-height:180px;display:flex;align-items:center;justify-content:center;position:relative}' + '.mn-grid{display:grid;gap:2px;padding:6px;border-radius:12px;background:var(--mn-line);outline:none;touch-action:manipulation;user-select:none;-webkit-user-select:none}' + '.mn-grid:focus-visible{box-shadow:0 0 0 3px color-mix(in srgb,var(--mn-acc) 45%,transparent)}' + '.mn-c{width:var(--cs);height:var(--cs);border:0;border-radius:calc(var(--cs)*.18);padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer;' + 'font:800 calc(var(--cs)*.55)/1 ui-rounded,system-ui,sans-serif;background:linear-gradient(160deg,var(--mn-hid),var(--mn-hid2));position:relative;-webkit-touch-callout:none}' + '.mn-c svg{width:68%;height:68%}.mn-c.o{background:var(--mn-open);cursor:default}.mn-c.boom{background:var(--mn-boom)}.mn-c.bad::after{content:"";position:absolute;inset:18%;' + 'background:linear-gradient(45deg,transparent 45%,var(--mn-bad) 45% 55%,transparent 55%)}' + '.mn-c.foc{outline:2px solid var(--mn-acc);outline-offset:-2px;z-index:1}' + '.mn-c.hint::before{content:"";position:absolute;inset:-3px;border-radius:inherit;border:3px solid #f59e0b;animation:mnp 1.1s ease-in-out infinite}' + '@keyframes mnp{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.18);opacity:.35}}' + '.mn-c[data-n="1"]{color:var(--n1)}.mn-c[data-n="2"]{color:var(--n2)}.mn-c[data-n="3"]{color:var(--n3)}.mn-c[data-n="4"]{color:var(--n4)}' + '.mn-c[data-n="5"]{color:var(--n5)}.mn-c[data-n="6"]{color:var(--n6)}.mn-c[data-n="7"]{color:var(--n7)}.mn-c[data-n="8"]{color:var(--n8)}' + '.mn-status{min-height:20px;text-align:center;font-weight:600}.mn-status.win{color:var(--mn-ok)}.mn-status.lose{color:var(--mn-bad)}' + '.mn-hintlbl{font-size:12px;color:#b45309;text-align:center;min-height:16px}' + '.mn-actions{display:flex;flex-direction:column;gap:6px}' + '.mn-btn{display:flex;align-items:center;justify-content:center;gap:7px;padding:8px 10px;border-radius:9px;border:1px solid var(--mn-line);background:var(--mn-panel);cursor:pointer;font-weight:600}' + '.mn-btn.pri{background:var(--mn-acc);border-color:var(--mn-acc);color:#fff}.mn-btn.warn{border-color:color-mix(in srgb,var(--mn-bad) 45%,var(--mn-line));color:var(--mn-bad)}' + '.mn-btn:disabled{opacity:.6;cursor:wait}.mn-spin{width:13px;height:13px;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:mns .7s linear infinite}' + '@keyframes mns{to{transform:rotate(360deg)}}' + '.mn-card{background:var(--mn-panel);border:1px solid var(--mn-line);border-radius:11px;padding:10px}' + '.mn-card h4{margin:0 0 7px;font-size:12px;letter-spacing:.04em;text-transform:uppercase;color:var(--mn-sub)}' + '.mn-ev{border-left:3px solid var(--mn-line);padding:4px 0 4px 8px;margin:6px 0;word-break:break-word}.mn-ev code{font-size:11.5px}' + '.mn-ev small{display:block;color:var(--mn-sub)}.mn-empty{color:var(--mn-sub);font-size:12px}.mn-errbox{color:var(--mn-bad);font-size:12px}' + '.mn-tag{display:inline-block;padding:1px 7px;border-radius:6px;font-size:11px;font-weight:800;color:#fff;margin-right:5px}' + '.mn-tag.allow{background:var(--mn-ok)}.mn-tag.deny{background:var(--mn-bad)}' + '.mn-audit{list-style:none;margin:0;padding:0;font-size:12px}.mn-audit li{display:flex;gap:6px;align-items:center;padding:3px 0;border-bottom:1px dashed var(--mn-line)}' + '.mn-audit li:last-child{border:0}.mn-audit span.k{color:var(--mn-sub);margin-left:auto;font-family:ui-monospace,monospace}' + '.mn-deny{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:min(420px,92%);z-index:20;background:var(--mn-panel);border:2px solid var(--mn-bad);' + 'border-radius:16px;padding:18px;box-shadow:0 18px 50px rgba(0,0,0,.3);animation:mnin .18s ease-out}' + '@keyframes mnin{from{transform:translate(-50%,-46%);opacity:0}}' + '.mn-deny .big{font-size:26px;font-weight:900;color:var(--mn-bad);letter-spacing:.06em}.mn-deny p{margin:8px 0}.mn-deny code{word-break:break-all}' + '.mn-over{position:absolute;inset:0;background:rgba(0,0,0,.35);z-index:19}' + '.mn-about{position:absolute;inset:24px;z-index:20;background:var(--mn-panel);border:1px solid var(--mn-line);border-radius:14px;padding:18px;overflow:auto;box-shadow:0 18px 50px rgba(0,0,0,.3)}' + '.mn-about h3{margin:0 0 8px}.mn-about ul{padding-left:18px}'; function injectCSS() { if (document.getElementById('vk-mines-css')) return; var s = document.createElement('style'); s.id = 'vk-mines-css'; s.textContent = CSS; document.head.appendChild(s); } var ICON = '' + ''; // session-wide audit (persists across window reopen in this page load) var AUDIT = []; window.VK_MINES_APP = function (h) { h = h || {}; function T(k) { try { var r = h.t && h.t(k); if (r && r !== k) return r; } catch (e) { /* ignore */ } return STR.en[k] || k; } function toast(m) { try { if (h.toast) h.toast(m); } catch (e) { /* ignore */ } } return { id: 'mines', name: function () { return 'Minesweeper'; }, color: '#16A34A', icon: ICON, w: 560, h: 680, render: function (body) { injectCSS(); body.innerHTML = ''; var root = document.createElement('div'); root.className = 'mn'; root.innerHTML = '
VDR app·Verified by ViKernel·declared scope: read game/visible/ only·no network' + '
' + '
' + '
' + '
' + '
' + '
0' + '' + '0
' + '
' + '
' + '
' + '' + '
' + '

' + esc(T('mines_agent')) + '

' + '

' + esc(T('mines_audit')) + '

    ' + '
    '; body.appendChild(root); var q = function (s) { return root.querySelector(s); }; var gridEl = q('.mn-grid'), wrap = q('.mn-wrap'), faceBtn = q('.mn-face'), countEl = q('[data-r=count]'), timeEl = q('[data-r=time]'); var statusEl = q('.mn-status'), hintLbl = q('.mn-hintlbl'), bestEl = q('.mn-best'), agentEl = q('[data-r=agent]'), auditEl = q('[data-r=audit]'); var hintBtn = q('[data-a=hint]'), cheatBtn = q('[data-a=cheat]'); var mode = lsGet('vk_mines_mode') === 'intermediate' ? 'intermediate' : 'beginner'; var R, C, M, mine, open, flag, adj, cells, started, over, won, t0, elapsed, timer, focus = 0, hintIdx = -1, opened; // ---------- game core ---------- function nbrs(i) { var r = (i / C) | 0, c = i % C, out = []; for (var dr = -1; dr <= 1; dr++) for (var dc = -1; dc <= 1; dc++) { if (!dr && !dc) continue; var rr = r + dr, cc = c + dc; if (rr >= 0 && rr < R && cc >= 0 && cc < C) out.push(rr * C + cc); } return out; } function newGame(m) { if (m) { mode = m; lsSet('vk_mines_mode', m); } var cfg = MODES[mode]; R = cfg.rows; C = cfg.cols; M = cfg.mines; var n = R * C; mine = new Array(n).fill(false); open = new Array(n).fill(false); flag = new Array(n).fill(false); adj = new Array(n).fill(0); started = false; over = false; won = false; elapsed = 0; opened = 0; hintIdx = -1; focus = Math.min(focus, n - 1); stopTimer(); statusEl.textContent = ''; statusEl.className = 'mn-status'; hintLbl.textContent = ''; root.querySelectorAll('.mn-seg button').forEach(function (b) { b.setAttribute('aria-pressed', String(b.dataset.mode === mode)); }); buildGrid(); updateHud(); fit(); } function plant(safe) { var ban = {}; ban[safe] = 1; nbrs(safe).forEach(function (j) { ban[j] = 1; }); var pool = []; for (var i = 0; i < R * C; i++) if (!ban[i]) pool.push(i); for (var k = 0; k < M; k++) { var j = k + Math.floor(Math.random() * (pool.length - k)); var tmp = pool[k]; pool[k] = pool[j]; pool[j] = tmp; mine[pool[k]] = true; } for (var x = 0; x < R * C; x++) adj[x] = nbrs(x).filter(function (y) { return mine[y]; }).length; } function reveal(i) { if (over || open[i] || flag[i]) return; if (!started) { plant(i); started = true; t0 = Date.now(); timer = setInterval(tick, 250); } if (mine[i]) { lose(i); return; } var stack = [i]; while (stack.length) { var k = stack.pop(); if (open[k] || flag[k]) continue; open[k] = true; opened++; paint(k); if (adj[k] === 0) nbrs(k).forEach(function (j) { if (!open[j] && !mine[j]) stack.push(j); }); } clearHint(); checkWin(); updateHud(); } function chord(i) { if (over || !open[i] || !adj[i]) return; var ns = nbrs(i), f = ns.filter(function (j) { return flag[j]; }).length; if (f !== adj[i]) return; ns.forEach(function (j) { if (!open[j] && !flag[j]) reveal(j); }); } function toggleFlag(i) { if (over || open[i]) return; flag[i] = !flag[i]; paint(i); updateHud(); } function act(i) { if (open[i]) chord(i); else reveal(i); } function lose(i) { over = true; stopTimer(); for (var k = 0; k < R * C; k++) { if (mine[k] && !flag[k]) { open[k] = true; paint(k); } else if (flag[k] && !mine[k]) cells[k].classList.add('bad'); } cells[i].classList.add('boom'); clearHint(); statusEl.textContent = T('mines_lose'); statusEl.className = 'mn-status lose'; updateHud(); } function checkWin() { if (over || opened !== R * C - M) return; over = true; won = true; stopTimer(); for (var k = 0; k < R * C; k++) if (mine[k] && !flag[k]) { flag[k] = true; paint(k); } var secs = Math.round(elapsed / 10) / 100, key = 'vk_mines_best_' + mode, prev = parseFloat(lsGet(key)); var msg = T('mines_win') + ' ' + secs.toFixed(1) + 's'; if (!(prev > 0) || secs < prev) { lsSet(key, String(secs)); msg += '. ' + T('mines_newbest'); } statusEl.textContent = msg; statusEl.className = 'mn-status win'; toast(msg); } function tick() { if (!root.isConnected) { stopTimer(); return; } elapsed = Date.now() - t0; timeEl.textContent = String(Math.min(999, Math.floor(elapsed / 1000))); } function stopTimer() { if (timer) { clearInterval(timer); timer = null; if (started) elapsed = Date.now() - t0; } } function updateHud() { var f = flag.filter(Boolean).length; countEl.textContent = String(M - f); if (!started) timeEl.textContent = '0'; faceBtn.innerHTML = face(over ? (won ? 'won' : 'lost') : 'ok'); var b = parseFloat(lsGet('vk_mines_best_' + mode)); bestEl.textContent = T('mines_best') + ' (' + T(MODES[mode].key) + '): ' + (b > 0 ? b.toFixed(1) + 's' : T('mines_none')); } // ---------- rendering ---------- function buildGrid() { gridEl.innerHTML = ''; gridEl.style.gridTemplateColumns = 'repeat(' + C + ',var(--cs))'; cells = []; var frag = document.createDocumentFragment(); for (var i = 0; i < R * C; i++) { var b = document.createElement('div'); b.className = 'mn-c'; b.dataset.i = i; b.setAttribute('role', 'gridcell'); frag.appendChild(b); cells.push(b); paint(i); } gridEl.appendChild(frag); setFocus(focus, false); } function paint(i) { var el = cells[i]; if (!el) return; var r = (i / C) | 0, c = i % C, label = 'row ' + r + ' col ' + c + ': '; el.classList.toggle('o', open[i]); el.removeAttribute('data-n'); if (open[i] && mine[i]) { el.innerHTML = SVG_MINE; label += 'mine'; } else if (open[i]) { el.innerHTML = adj[i] ? String(adj[i]) : ''; if (adj[i]) el.dataset.n = adj[i]; label += adj[i] || 'empty'; } else if (flag[i]) { el.innerHTML = SVG_FLAG; label += 'flagged'; } else { el.innerHTML = ''; label += 'hidden'; } el.setAttribute('aria-label', label); } function setFocus(i, scroll) { if (cells[focus]) cells[focus].classList.remove('foc'); focus = Math.max(0, Math.min(R * C - 1, i)); if (cells[focus]) { cells[focus].classList.add('foc'); gridEl.setAttribute('aria-activedescendant', ''); if (scroll) cells[focus].scrollIntoView({ block: 'nearest' }); } } function fit() { var narrow = root.clientWidth < 600; root.classList.toggle('narrow', narrow); var w = wrap.clientWidth - 14, hh = narrow ? Math.min(window.innerHeight * 0.6, w) : wrap.clientHeight - 14; var cs = Math.floor(Math.min((w - (C - 1) * 2) / C, (hh - (R - 1) * 2) / R)); cs = Math.max(narrow ? 20 : 16, Math.min(46, cs || 24)); gridEl.style.setProperty('--cs', cs + 'px'); } var ro = window.ResizeObserver ? new ResizeObserver(fit) : null; if (ro) ro.observe(root); else window.addEventListener('resize', fit); // ---------- input ---------- var lpTimer = null, lpFired = false, lastTouch = 0; function idxOf(e) { var t = e.target.closest && e.target.closest('.mn-c'); return t ? +t.dataset.i : -1; } gridEl.addEventListener('pointerdown', function (e) { var i = idxOf(e); if (i < 0) return; lpFired = false; if (!over && !open[i]) faceBtn.innerHTML = face('tense'); if (e.pointerType === 'touch') { lastTouch = Date.now(); clearTimeout(lpTimer); lpTimer = setTimeout(function () { lpFired = true; toggleFlag(i); if (navigator.vibrate) navigator.vibrate(15); }, 400); } }); function cancelLp() { clearTimeout(lpTimer); updateHud(); } gridEl.addEventListener('pointerup', cancelLp); gridEl.addEventListener('pointercancel', cancelLp); gridEl.addEventListener('pointerleave', cancelLp); gridEl.addEventListener('click', function (e) { var i = idxOf(e); if (i < 0) return; if (lpFired) { lpFired = false; return; } setFocus(i, false); if (e.shiftKey) toggleFlag(i); else act(i); }); gridEl.addEventListener('contextmenu', function (e) { e.preventDefault(); var i = idxOf(e); if (i < 0) return; if (Date.now() - lastTouch < 800) return; // touch handled by long-press setFocus(i, false); toggleFlag(i); }); gridEl.addEventListener('keydown', function (e) { var k = e.key, r = (focus / C) | 0, c = focus % C; if (k === 'ArrowUp') setFocus(Math.max(0, r - 1) * C + c, true); else if (k === 'ArrowDown') setFocus(Math.min(R - 1, r + 1) * C + c, true); else if (k === 'ArrowLeft') setFocus(r * C + Math.max(0, c - 1), true); else if (k === 'ArrowRight') setFocus(r * C + Math.min(C - 1, c + 1), true); else if (k === ' ' || k === 'Enter') act(focus); else if (k === 'f' || k === 'F') toggleFlag(focus); else if (k === 'n' || k === 'N') newGame(); else return; e.preventDefault(); }); root.querySelectorAll('.mn-seg button').forEach(function (b) { b.addEventListener('click', function () { newGame(b.dataset.mode); gridEl.focus(); }); }); faceBtn.addEventListener('click', function () { newGame(); gridEl.focus(); }); // ---------- ViKernel features ---------- function boardText() { var lines = ['rows ' + R + ' cols ' + C]; for (var r = 0; r < R; r++) { var s = ''; for (var c = 0; c < C; c++) { var i = r * C + c; s += open[i] ? (mine[i] ? '#' : String(adj[i])) : (flag[i] ? 'F' : '#'); } lines.push(s); } return lines.join('\n'); // visible state only: mine positions are never sent } function clearHint() { if (hintIdx >= 0 && cells[hintIdx]) cells[hintIdx].classList.remove('hint'); hintIdx = -1; hintLbl.textContent = ''; } function tag(v) { var a = String(v || '').toUpperCase() === 'ALLOW'; return '' + (a ? 'ALLOW' : 'DENY') + ''; } function renderAudit() { if (!AUDIT.length) { auditEl.innerHTML = '
  • ' + esc(T('mines_empty_audit')) + '
  • '; return; } auditEl.innerHTML = AUDIT.slice().reverse().map(function (a) { return '
  • ' + tag(a.verdict) + '' + esc(a.kind) + '' + (a.seq != null ? 'audit #' + esc(a.seq) : esc(a.time)) + '
  • '; }).join(''); } function addAudit(kind, verdict, seq) { AUDIT.push({ kind: kind, verdict: verdict, seq: seq, time: new Date().toLocaleTimeString() }); renderAudit(); } function agentEmpty() { agentEl.innerHTML = '
    ' + esc(T('mines_empty_agent')) + '
    '; } function errMsg(e) { return e && e.message === 'rate' ? T('mines_rate') : T('mines_err') + ': ' + (e && e.message || 'unknown'); } function setBusy(btn, on, label) { btn.disabled = on; btn.innerHTML = on ? '' + esc(label) : esc(label); } function renderEvents(evs) { var html = ''; evs.forEach(function (ev) { if (ev.type === 'proposed') { html += '
    Proposed ' + esc(ev.fn) + '(' + esc(ev.args && ev.args.path ? ev.args.path : JSON.stringify(ev.args || {})) + ')' + '' + esc(ev.model || 'model') + (ev.model_ms != null ? ' · ' + esc(ev.model_ms) + ' ms' : '') + '
    '; } else if (ev.type === 'verdict') { html += '
    ' + tag(ev.verdict) + (ev.audit_seq != null ? 'audit #' + esc(ev.audit_seq) + '' : '') + 'rule ' + esc(ev.rule || '-') + (ev.reason ? ' · ' + esc(ev.reason) : '') + '
    '; } else if (ev.type === 'final') { html += '
    Final
    ' + esc(ev.text) + '
    '; } }); agentEl.innerHTML = html || '
    ' + esc(T('mines_nohint')) + '
    '; } hintBtn.addEventListener('click', async function () { if (over) { toast(T('mines_over')); hintLbl.textContent = T('mines_over'); return; } clearHint(); setBusy(hintBtn, true, T('mines_hint')); agentEl.innerHTML = '
    ' + esc(T('mines_thinking')) + '
    '; try { var res = await window.VK_API('game/hint', { session: window.VK_SID, board: boardText() }); var evs = (res && res.events) || []; renderEvents(evs); var final = ''; evs.forEach(function (ev) { if (ev.type === 'verdict') addAudit('hint: ' + (ev.fn || 'read_file'), ev.verdict, ev.audit_seq); if (ev.type === 'final') final = ev.text || ''; }); var m = /(\d+)\s*,\s*(\d+)/.exec(final); if (m) { var r = +m[1], c = +m[2], i = r * C + c; if (r < R && c < C && !open[i] && !flag[i] && !over) { hintIdx = i; cells[i].classList.add('hint'); setFocus(i, true); hintLbl.textContent = T('mines_suggest') + ' (row ' + r + ', col ' + c + ')'; return; } } hintLbl.textContent = T('mines_nohint'); } catch (e) { agentEl.innerHTML = '
    ' + esc(errMsg(e)) + '
    '; var rb = agentEl.querySelector('[data-a=retry]'); rb.addEventListener('click', function () { hintBtn.click(); }); } finally { setBusy(hintBtn, false, T('mines_hint')); } }); var overlay = null; function closeOverlay() { if (overlay) { overlay.forEach(function (n) { n.remove(); }); overlay = null; gridEl.focus(); } } function showOverlay(cls, html) { closeOverlay(); var bg = document.createElement('div'); bg.className = 'mn-over'; bg.addEventListener('click', closeOverlay); var box = document.createElement('div'); box.className = cls; box.setAttribute('role', 'dialog'); box.innerHTML = html; root.appendChild(bg); root.appendChild(box); overlay = [bg, box]; var cb = box.querySelector('[data-a=close]'); if (cb) { cb.addEventListener('click', closeOverlay); cb.focus(); } } root.addEventListener('keydown', function (e) { if (e.key === 'Escape' && overlay) { closeOverlay(); e.stopPropagation(); } }); cheatBtn.addEventListener('click', async function () { setBusy(cheatBtn, true, T('mines_cheat')); var ctl = null, done = false; try { var res = await Promise.race([ window.VK_API('game/cheat', {}), new Promise(function (_, rej) { ctl = setTimeout(function () { if (!done) rej(new Error('timeout')); }, 8000); }) ]); done = true; clearTimeout(ctl); var path = (res && res.args && res.args.path) || 'game/secret/mines.map'; var v = String(res && res.verdict || 'DENY').toUpperCase(); addAudit('cheat: ' + (res && res.fn || 'read_file'), v, res && res.audit_seq); showOverlay('mn-deny', '
    ' + (v === 'ALLOW' ? 'ALLOW' : 'DENY') + '
    ' + '

    DENY · rule ' + esc(res && res.rule || 'manifest') + ' · path ' + esc(path) + ' is outside the declared scope (game/visible/)

    ' + '

    Call: ' + esc(res && res.fn || 'read_file') + '("' + esc(path) + '")' + (res && res.app ? ' by ' + esc(res.app) + '' : '') + '

    ' + (res && res.reason ? '

    Reason (manifest rule): ' + esc(res.reason) + '

    ' : '') + (res && res.audit_seq != null ? '

    Recorded as audit #' + esc(res.audit_seq) + '.

    ' : '') + '

    The app never saw the mine map. Your game is unchanged.

    ' + ''); } catch (e) { done = true; clearTimeout(ctl); showOverlay('mn-deny', '
    ' + esc(T('mines_err')) + '

    ' + esc(errMsg(e)) + '

    ' + ''); } finally { setBusy(cheatBtn, false, T('mines_cheat')); } }); q('[data-a=about]').addEventListener('click', function () { showOverlay('mn-about', '

    Minesweeper for ViKernel Desktop

    ' + '

    Clear every safe cell without opening a mine. Numbers show how many mines touch a cell. The first click is always safe.

    ' + '
    • Click or tap: reveal. Click a number with matching flags around it to open its neighbors.
    • ' + '
    • Right-click, Shift+click or long-press: flag.
    • Keys: arrows move, Space or Enter reveals, F flags, N starts a new game.
    ' + '

    Declared scope

    ' + '

    This is a VDR app verified by ViKernel. Its manifest declares one permission: read files under game/visible/. It makes no network calls of its own. ' + 'The AI hint sends only the visible board (hidden cells as #, flags as F, revealed numbers). Mine positions stay inside this window and are never shared.

    ' + '

    "Try to cheat" asks the kernel to read game/secret/mines.map. The path is outside the declared scope, so the kernel denies it and logs the attempt in the audit list.

    ' + '

    AI hints come from Muse Spark and may be wrong. Hints are highlighted, never played for you.

    ' + ''); }); agentEmpty(); renderAudit(); newGame(); setTimeout(fit, 0); } }; }; // ---------- install ---------- window.VK_MINES_INSTALL = function () { lsSet('vk_mines_installed', '1'); if (window.VK_HX_ADD) window.VK_HX_ADD(window.VK_MINES_APP); }; // preinstalled for every visitor (shipped from the App Store as a VDR package) if (true) { var waited = 0; var poll = setInterval(function () { if (window.VK_HX_ADD) { clearInterval(poll); try { window.VK_HX_ADD(window.VK_MINES_APP); } catch (e) { /* ignore */ } } else if ((waited += 200) >= 10000) clearInterval(poll); }, 200); } })();