/* scense 全屏游戏页交互逻辑(独立 HTML 页内脚本,允许 fetch/setInterval) * * 依赖: * - runtime.js(W-10 运行时引擎 RuntimeEngine)—— 世界初始化/实体状态/脚本调度/暂停恢复 * - /scense/game/api/*.dspy(W-08 会话与结果) * * 功能点:W-08b 开始玩(进入运行态) / W-08c 暂停 / W-08d 继续 / * W-08e 结束结算 / W-08f 重新开始 / W-08g 分数结果展示 / * W-08h 分享链接 / W-08i 玩中交互(点选实体触发脚本) / W-08j 退出保存进度 */ (function () { 'use strict'; var CFG = {gameId: '', sessionId: '', shareToken: '', worldId: ''}; var session = null; // 当前会话 var engine = null; // W-10 RuntimeEngine 实例 var score = 0; var level = 1; var startTs = 0; var clockTimer = null; var statusBar = null; function $id(id) { return document.getElementById(id); } /* API 基础路径:由页面 URL 推导(/scense/game/game_play.html -> /scense) */ function apiBase() { return window.location.pathname.replace(/\/game\/.*$/, ''); } function setStatus(t) { if (statusBar) { statusBar.textContent = t; statusBar.className = 'hud-status ' + (t === '运行中' ? 'ok' : ''); } } function api(name, params) { return fetch(apiBase() + '/game/api/' + name + '.dspy', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(params || {}) }).then(function (r) { return r.json(); }); } function elapsedMs() { return startTs ? (Date.now() - startTs) : 0; } function refreshHud() { $id('hud_score').textContent = score; $id('hud_level').textContent = level; } function startClock() { if (clockTimer) clearInterval(clockTimer); clockTimer = setInterval(function () { var s = Math.floor(elapsedMs() / 1000); var m = Math.floor(s / 60); s = s % 60; $id('hud_time').textContent = (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s); }, 500); } /* ---------- W-08b 开始玩:初始化会话 + 进入运行态 ---------- */ function startGame() { setStatus('启动中…'); api('game_start', {game_id: CFG.gameId, session_id: CFG.sessionId || '', world_id: CFG.worldId || ''}) .then(function (res) { if (!res || !res.success) { setStatus('启动失败: ' + (res && res.error || '未知错误')); return; } session = res.data.session; CFG.sessionId = session.session_id; CFG.shareToken = session.share_token; CFG.worldId = session.world_id; score = session.score || 0; level = session.level || 1; startTs = Date.now() - (session.elapsed_ms || 0); refreshHud(); startClock(); initEngine(); // 进入 W-10 运行态 showPanel('hud'); setStatus('运行中'); enableControls(true); }); } /* ---------- W-10 运行时引擎初始化(runtime.js) ---------- */ function initEngine() { if (typeof RuntimeEngine === 'undefined') { setStatus('运行时引擎未加载(runtime.js),降级独立模式'); initDegradedEngine(); return; } engine = new RuntimeEngine({ worldId: CFG.worldId, canvas: $id('game_canvas'), onEntityClick: onEntityClick, // W-08i 玩中交互:点选实体 onEntityState: onEntityState, onScriptLog: onScriptLog, onError: onEngineError }); engine.initWorld(CFG.worldId).then(function (info) { setStatus('运行中' + (info && info.degraded ? '(独立模式)' : '')); engine.start(); }); } /* 独立模式本地引擎(后端不可用时降级,W-10i) */ function initDegradedEngine() { var canvas = $id('game_canvas'); var ctx = canvas && canvas.getContext('2d'); var entities = [ {id: 'hero', x: 60, y: 240, w: 40, h: 40, color: '#2563EB', state: 'idle', label: '英雄'}, {id: 'chest', x: 400, y: 200, w: 36, h: 36, color: '#D97706', state: 'idle', label: '宝箱'}, {id: 'slime', x: 260, y: 320, w: 34, h: 34, color: '#16A34A', state: 'idle', label: '史莱姆'} ]; engine = { paused: false, entities: entities, start: function () { this.draw(); }, stop: function () {}, pause: function () { this.paused = true; }, resume: function () { this.paused = false; this.draw(); }, restart: function () { this.paused = false; this.draw(); }, draw: function () { if (!ctx) return; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#F9FAFB'; ctx.fillRect(0, 0, canvas.width, canvas.height); entities.forEach(function (e) { ctx.fillStyle = e.color; ctx.fillRect(e.x, e.y, e.w, e.h); ctx.fillStyle = '#111827'; ctx.font = '12px sans-serif'; ctx.fillText(e.label + (e.state === 'active' ? ' ★' : ''), e.x - 4, e.y - 6); }); }, findEntity: function (x, y) { for (var i = entities.length - 1; i >= 0; i--) { var e = entities[i]; if (x >= e.x && x <= e.x + e.w && y >= e.y && y <= e.y + e.h) return e; } return null; } }; if (canvas) canvas.onclick = function (ev) { if (engine.paused) return; var r = canvas.getBoundingClientRect(); var ex = ev.clientX - r.left, ey = ev.clientY - r.top; var ent = engine.findEntity(ex, ey); if (ent) onEntityClick(ent); }; engine.draw(); } /* ---------- W-08i 玩中交互:点选实体触发脚本逻辑 ---------- */ function onEntityClick(entity) { if (!entity) return; if (engine && engine.paused) { setStatus('已暂停,请先继续'); return; } // 得分规则:点击宝箱/史莱姆得 10 分并触发实体脚本(state 变化) score += 10; refreshHud(); logLine('点击实体 ' + entity.label + '(' + entity.id + ')→ 触发脚本 action,得分 +10'); if (engine && engine.dispatchEvent) { engine.dispatchEvent({type: 'click', entity_id: entity.id, payload: {score: 10}}); } else if (engine && engine.entities) { var hit = null; for (var i = 0; i < engine.entities.length; i++) { if (engine.entities[i].id === entity.id) { hit = engine.entities[i]; break; } } if (hit) { hit.state = 'active'; engine.draw && engine.draw(); } setTimeout(function () { if (hit) { hit.state = 'idle'; engine.draw && engine.draw(); } }, 800); } } function onEntityState(e) { logLine('实体状态: ' + (e && (e.entity_id || e.id)) + ' -> ' + (e && e.state)); } function onScriptLog(msg) { logLine('脚本: ' + (msg && msg.message || msg || '')); } function onEngineError(err) { logLine('运行时错误: ' + (err && err.message || err || '') + '(已降级继续)'); } function logLine(text) { var box = $id('game_log'); if (!box) return; var line = document.createElement('div'); line.textContent = '[' + new Date().toLocaleTimeString() + '] ' + text; box.appendChild(line); box.scrollTop = box.scrollHeight; while (box.children.length > 60) box.removeChild(box.firstChild); } /* ---------- W-08c 暂停 ---------- */ function pauseGame() { if (!session) return; api('game_pause', {session_id: CFG.sessionId}).then(function (res) { if (!res || !res.success) { setStatus('暂停失败: ' + (res && res.error)); return; } if (engine && engine.pause) engine.pause(); if (clockTimer) clearInterval(clockTimer); setStatus('已暂停'); $id('btn_pause').style.display = 'none'; $id('btn_resume').style.display = 'inline-block'; }); } /* ---------- W-08d 继续 ---------- */ function resumeGame() { if (!session) return; api('game_resume', {session_id: CFG.sessionId}).then(function (res) { if (!res || !res.success) { setStatus('继续失败: ' + (res && res.error)); return; } if (engine && engine.resume) engine.resume(); startClock(); setStatus('运行中'); $id('btn_pause').style.display = 'inline-block'; $id('btn_resume').style.display = 'none'; }); } /* ---------- W-08e/g 结束结算 ---------- */ function endGame() { if (!session) return; var dur = elapsedMs(); api('game_end', { session_id: CFG.sessionId, score: score, duration_ms: dur, level: level, result_json: JSON.stringify({actions: 'clicks', entities: engine ? (engine.entities || []).length : 0}) }).then(function (res) { if (!res || !res.success) { setStatus('结算失败: ' + (res && res.error)); return; } if (engine && engine.stop) engine.stop(); if (clockTimer) clearInterval(clockTimer); var r = res.data.result; $id('result_score').textContent = r.score; $id('result_level').textContent = r.level; $id('result_duration').textContent = Math.round(r.duration_ms / 1000) + ' 秒'; $id('result_time').textContent = r.created_at; showPanel('result'); setStatus('已结束'); enableControls(false); }); } /* ---------- W-08f 重新开始 ---------- */ function restartGame() { if (!session) return; api('game_restart', {session_id: CFG.sessionId}).then(function (res) { if (!res || !res.success) { setStatus('重开失败: ' + (res && res.error)); return; } score = 0; level = 1; startTs = Date.now(); refreshHud(); if (engine && engine.restart) engine.restart(); else if (engine && engine.start) engine.start(); startClock(); showPanel('hud'); enableControls(true); $id('btn_pause').style.display = 'inline-block'; $id('btn_resume').style.display = 'none'; setStatus('运行中'); }); } /* ---------- W-08h 分享链接 ---------- */ function shareGame() { if (!session) return; api('game_share', {action: 'generate', session_id: CFG.sessionId}).then(function (res) { if (!res || !res.success) { setStatus('分享失败: ' + (res && res.error)); return; } var url = location.origin + apiBase() + '/game/game_play.html?share_token=' + res.data.share_token; $id('share_url').value = url; $id('share_box').style.display = 'block'; logLine('已生成分享链接'); }); } /* ---------- W-08j 退出保存进度 ---------- */ function saveProgress() { if (!session) return; api('game_save', { session_id: CFG.sessionId, progress_json: JSON.stringify({entities: (engine && engine.entities || []).length}), score: score, level: level, elapsed_ms: elapsedMs() }).then(function (res) { if (res && res.success) { logLine('进度已保存'); } }); } function exitGame() { saveProgress(); setTimeout(function () { window.close(); }, 400); } /* ---------- 分享链接进入(W-08h 解析 token) ---------- */ function enterByShareToken(token) { api('game_share', {action: 'parse', token: token}).then(function (res) { if (!res || !res.success) { setStatus('分享链接无效: ' + (res && res.error)); return; } CFG.gameId = res.data.game_id; CFG.sessionId = res.data.session_id; CFG.shareToken = token; CFG.worldId = res.data.world_id || ''; $id('page_title').textContent = '分享进入 · ' + res.data.game_name; setStatus('已通过分享链接进入'); startGame(); // 复用会话进入运行态 }); } /* ---------- UI 辅助 ---------- */ function showPanel(name) { ['hud', 'result'].forEach(function (p) { $id('panel_' + p).style.display = (p === name) ? 'block' : 'none'; }); } function enableControls(on) { ['btn_pause', 'btn_resume', 'btn_end', 'btn_restart', 'btn_share', 'btn_save', 'btn_exit'] .forEach(function (id) { var b = $id(id); if (b) b.disabled = !on; }); } function bindEvents() { $id('btn_pause').onclick = pauseGame; $id('btn_resume').onclick = resumeGame; $id('btn_end').onclick = endGame; $id('btn_restart').onclick = restartGame; $id('btn_share').onclick = shareGame; $id('btn_save').onclick = saveProgress; $id('btn_exit').onclick = exitGame; $id('btn_copy').onclick = function () { var u = $id('share_url'); u.select(); try { document.execCommand('copy'); setStatus('已复制分享链接'); } catch (e) { setStatus('复制失败,请手动复制'); } }; $id('btn_result_again').onclick = restartGame; $id('btn_result_back').onclick = exitGame; window.addEventListener('beforeunload', function () { saveProgress(); }); } /* ---------- 启动 ---------- */ function boot() { var qs = new URLSearchParams(location.search); CFG.gameId = qs.get('game_id') || ''; CFG.sessionId = qs.get('session_id') || ''; statusBar = $id('hud_status'); bindEvents(); showPanel('hud'); enableControls(false); var token = qs.get('share_token'); if (token) { enterByShareToken(token); return; } if (CFG.gameId) { startGame(); return; } setStatus('缺少 game_id / share_token'); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', boot); } else { boot(); } })();