348 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* scense 全屏游戏页交互逻辑(独立 HTML 页内脚本,允许 fetch/setInterval
*
* 依赖:
* - runtime.jsW-10 运行时引擎 RuntimeEngine—— 世界初始化/实体状态/脚本调度/暂停恢复
* - /scense/game/api/*.dspyW-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();
}
})();