refactor: game 模块拆分至独立仓库 scense_game

This commit is contained in:
pipeline-agent 2026-08-29 21:22:13 +08:00
parent f337333432
commit 87ca62ea9f
17 changed files with 0 additions and 742 deletions

View File

@ -1 +0,0 @@
{"tblname":"game_result","alias":"game_result_list","params":{"sortby":"created_at desc","logined_userorgid":"user_id","browserfields":{"fields":[{"name":"id","width":90,"frozen":true},{"name":"game_id","title":"游戏","width":120},{"name":"score","title":"得分","width":80},{"name":"duration_ms","title":"时长ms","width":90},{"name":"level","title":"关卡","width":70},{"name":"created_at","title":"结算时间","width":160}],"exclouded":["session_id","result_json"]},"editexclouded":["id","session_id","user_id","game_id","result_json","created_at"],"editable":{"new_data_url":null,"update_data_url":null,"delete_data_url":null}}

View File

@ -1 +0,0 @@
{"tblname":"game_session","alias":"game_session_list","params":{"sortby":"updated_at desc","logined_userorgid":"user_id","new_data_url":"{{entire_url('/scense/game/api/game_start.dspy')}}","update_data_url":"{{entire_url('/scense/game/api/game_save.dspy')}}","delete_data_url":"{{entire_url('/scense/game/api/game_end.dspy')}}","browserfields":{"fields":[{"name":"id","width":90,"frozen":true},{"name":"game_name","title":"游戏名称","width":140},{"name":"status","title":"状态","width":90,"uitype":"code","data":[{"value":"created","text":"已创建"},{"value":"playing","text":"进行中"},{"value":"paused","text":"已暂停"},{"value":"ended","text":"已结束"}]},{"name":"score","title":"得分","width":80},{"name":"level","title":"关卡","width":70},{"name":"elapsed_ms","title":"时长ms","width":90},{"name":"updated_at","title":"更新时间","width":160}],"exclouded":["progress_json","share_token","world_id","created_at"]},"editexclouded":["id","user_id","game_id","share_token","created_at","updated_at","ended_at","progress_json"],"editable":{"new_data_url":"{{entire_url('/scense/game/api/game_start.dspy')}}","update_data_url":"{{entire_url('/scense/game/api/game_save.dspy')}}","delete_data_url":"{{entire_url('/scense/game/api/game_end.dspy')}}"}}

View File

@ -1 +0,0 @@
{"summary":[{"table":"game_result","desc":"游戏结果表","pkey":"id","primary":["id"]}],"fields":[{"name":"id","type":"varchar","length":32,"desc":"结果ID","primary":true},{"name":"session_id","type":"varchar","length":32,"desc":"会话ID"},{"name":"user_id","type":"varchar","length":32,"desc":"用户ID"},{"name":"game_id","type":"varchar","length":64,"desc":"游戏标识"},{"name":"score","type":"int","length":11,"desc":"结算得分"},{"name":"duration_ms","type":"bigint","length":20,"desc":"游戏时长毫秒"},{"name":"level","type":"int","length":11,"desc":"结束关卡"},{"name":"result_json","type":"text","desc":"结果详情JSON"},{"name":"created_at","type":"datetime","desc":"结算时间"}],"indexes":[{"name":"idx_session","fields":["session_id"]},{"name":"idx_user_game","fields":["user_id","game_id"]}],"codes":[]}

View File

@ -1 +0,0 @@
{"summary":[{"table":"game_session","desc":"游戏会话表","pkey":"id","primary":["id"]}],"fields":[{"name":"id","type":"varchar","length":32,"desc":"会话ID","primary":true},{"name":"user_id","type":"varchar","length":32,"desc":"用户ID"},{"name":"game_id","type":"varchar","length":64,"desc":"游戏标识"},{"name":"game_name","type":"varchar","length":128,"desc":"游戏名称"},{"name":"status","type":"varchar","length":16,"desc":"会话状态 created/playing/paused/ended"},{"name":"score","type":"int","length":11,"desc":"当前得分"},{"name":"level","type":"int","length":11,"desc":"当前关卡"},{"name":"elapsed_ms","type":"bigint","length":20,"desc":"已玩时长毫秒"},{"name":"progress_json","type":"text","desc":"进度快照JSON"},{"name":"share_token","type":"varchar","length":64,"desc":"分享令牌"},{"name":"world_id","type":"varchar","length":64,"desc":"世界ID"},{"name":"created_at","type":"datetime","desc":"创建时间"},{"name":"updated_at","type":"datetime","desc":"更新时间"},{"name":"ended_at","type":"datetime","desc":"结束时间"}],"indexes":[{"name":"idx_user_status","fields":["user_id","status"]},{"name":"idx_user_game","fields":["user_id","game_id"]},{"name":"idx_share_token","fields":["share_token"]}],"codes":[{"field":"status","table":"appcodes","valuefield":"k","textfield":"v","cond":{"parentid":"game_session_status"}}]}

View File

@ -1,42 +0,0 @@
-- 元景 W-08 游戏展示模块 DDLscense 应用库)
-- 表game_session游戏会话 + game_result游戏结果
CREATE TABLE IF NOT EXISTS `game_session` (
`id` VARCHAR(32) NOT NULL COMMENT '会话ID',
`game_code` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '分享码',
`world_id` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '世界ID',
`user_id` VARCHAR(32) NOT NULL DEFAULT '0' COMMENT '用户ID',
`org_id` VARCHAR(32) NOT NULL DEFAULT '0' COMMENT '组织ID',
`status` VARCHAR(16) NOT NULL DEFAULT 'created' COMMENT '状态: created/playing/paused/ended',
`score` INT NOT NULL DEFAULT 0 COMMENT '分数',
`level` INT NOT NULL DEFAULT 1 COMMENT '关卡',
`elapsed_ms` INT NOT NULL DEFAULT 0 COMMENT '已玩毫秒',
`progress_json` TEXT COMMENT '进度快照',
`share_token` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '分享令牌',
`started_at` DATETIME DEFAULT NULL COMMENT '开始时间',
`paused_at` DATETIME DEFAULT NULL COMMENT '暂停时间',
`ended_at` DATETIME DEFAULT NULL COMMENT '结束时间',
`created_at` DATETIME NOT NULL COMMENT '创建时间',
`updated_at` DATETIME NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_game_session_user` (`user_id`,`world_id`),
KEY `idx_game_session_code` (`game_code`),
KEY `idx_game_session_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='游戏会话';
CREATE TABLE IF NOT EXISTS `game_result` (
`id` VARCHAR(32) NOT NULL COMMENT '结果ID',
`session_id` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '会话ID',
`game_code` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '分享码',
`world_id` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '世界ID',
`user_id` VARCHAR(32) NOT NULL DEFAULT '0' COMMENT '用户ID',
`org_id` VARCHAR(32) NOT NULL DEFAULT '0' COMMENT '组织ID',
`score` INT NOT NULL DEFAULT 0 COMMENT '最终分数',
`duration_ms` INT NOT NULL DEFAULT 0 COMMENT '游戏时长毫秒',
`level` INT NOT NULL DEFAULT 1 COMMENT '到达关卡',
`result_json` TEXT COMMENT '结算明细',
`created_at` DATETIME NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_game_result_session` (`session_id`),
KEY `idx_game_result_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='游戏结果';

View File

@ -1,24 +0,0 @@
#!/usr/bin/env python3
"""W-08e/g 结束结算:写 game_result 并返回结算结果(分数/时长/关卡展示)。"""
result = {'success': False, 'data': {}}
try:
user_id = await get_user()
session_id = params_kw.get('session_id', '')
if not session_id:
result['code'] = 400
result['error'] = '缺少 session_id'
else:
score = params_kw.get('score', None)
duration_ms = params_kw.get('duration_ms', None)
level = params_kw.get('level', None)
result_json = params_kw.get('result_json', None)
data = await game_end_session(user_id, session_id, score=score, duration_ms=duration_ms,
level=level, result_json=result_json)
result['success'] = True
result['data'] = data
except ValueError as e:
result['code'] = 400
result['error'] = str(e)
except Exception as e:
result['error'] = str(e)
return json.dumps(result, ensure_ascii=False, default=str)

View File

@ -1,13 +0,0 @@
#!/usr/bin/env python3
"""W-08a 游戏列表入口:游戏目录 + 历史会话 + 历史结果。"""
result = {'success': False, 'data': {}}
try:
user_id = await get_user()
games = await game_list_games(user_id)
sessions = await game_list_sessions(user_id)
results = await game_list_results(user_id)
result['success'] = True
result['data'] = {'games': games, 'sessions': sessions, 'results': results}
except Exception as e:
result['error'] = str(e)
return json.dumps(result, ensure_ascii=False, default=str)

View File

@ -1,19 +0,0 @@
#!/usr/bin/env python3
"""W-08c 暂停playing -> paused。"""
result = {'success': False, 'data': {}}
try:
user_id = await get_user()
session_id = params_kw.get('session_id', '')
if not session_id:
result['code'] = 400
result['error'] = '缺少 session_id'
else:
data = await game_pause_session(user_id, session_id)
result['success'] = True
result['data'] = data
except ValueError as e:
result['code'] = 400
result['error'] = str(e)
except Exception as e:
result['error'] = str(e)
return json.dumps(result, ensure_ascii=False, default=str)

View File

@ -1,19 +0,0 @@
#!/usr/bin/env python3
"""W-08f 重新开始:任意状态 -> playing重置分数/关卡/时长/进度。"""
result = {'success': False, 'data': {}}
try:
user_id = await get_user()
session_id = params_kw.get('session_id', '')
if not session_id:
result['code'] = 400
result['error'] = '缺少 session_id'
else:
data = await game_restart_session(user_id, session_id)
result['success'] = True
result['data'] = data
except ValueError as e:
result['code'] = 400
result['error'] = str(e)
except Exception as e:
result['error'] = str(e)
return json.dumps(result, ensure_ascii=False, default=str)

View File

@ -1,19 +0,0 @@
#!/usr/bin/env python3
"""W-08d 继续paused -> playing。"""
result = {'success': False, 'data': {}}
try:
user_id = await get_user()
session_id = params_kw.get('session_id', '')
if not session_id:
result['code'] = 400
result['error'] = '缺少 session_id'
else:
data = await game_resume_session(user_id, session_id)
result['success'] = True
result['data'] = data
except ValueError as e:
result['code'] = 400
result['error'] = str(e)
except Exception as e:
result['error'] = str(e)
return json.dumps(result, ensure_ascii=False, default=str)

View File

@ -1,24 +0,0 @@
#!/usr/bin/env python3
"""W-08j 退出保存进度:保存进度快照(不改变状态)。"""
result = {'success': False, 'data': {}}
try:
user_id = await get_user()
session_id = params_kw.get('session_id', '')
if not session_id:
result['code'] = 400
result['error'] = '缺少 session_id'
else:
progress_json = params_kw.get('progress_json', '')
score = params_kw.get('score', None)
level = params_kw.get('level', None)
elapsed_ms = params_kw.get('elapsed_ms', None)
data = await game_save_progress(user_id, session_id, progress_json=progress_json,
score=score, level=level, elapsed_ms=elapsed_ms)
result['success'] = True
result['data'] = data
except ValueError as e:
result['code'] = 400
result['error'] = str(e)
except Exception as e:
result['error'] = str(e)
return json.dumps(result, ensure_ascii=False, default=str)

View File

@ -1,24 +0,0 @@
#!/usr/bin/env python3
"""W-08h 分享链接:生成/解析分享令牌。action=generate|parse"""
result = {'success': False, 'data': {}}
try:
user_id = await get_user()
action = params_kw.get('action', 'generate')
if action == 'parse':
token = params_kw.get('token', '')
data = await game_parse_share(token)
else:
session_id = params_kw.get('session_id', '')
if not session_id:
result['code'] = 400
result['error'] = '缺少 session_id'
else:
data = await game_generate_share(user_id, session_id)
result['success'] = True
result['data'] = data
except ValueError as e:
result['code'] = 400
result['error'] = str(e)
except Exception as e:
result['error'] = str(e)
return json.dumps(result, ensure_ascii=False, default=str)

View File

@ -1,21 +0,0 @@
#!/usr/bin/env python3
"""W-08b 点击开始玩:创建/恢复会话并进入运行态(返回 session 供前端初始化运行时)。"""
result = {'success': False, 'data': {}}
try:
user_id = await get_user()
game_id = params_kw.get('game_id', '')
session_id = params_kw.get('session_id', '')
world_id = params_kw.get('world_id', '')
if not game_id:
result['code'] = 400
result['error'] = '缺少 game_id'
else:
data = await game_start_session(user_id, game_id, session_id, world_id)
result['success'] = True
result['data'] = data
except ValueError as e:
result['code'] = 400
result['error'] = str(e)
except Exception as e:
result['error'] = str(e)
return json.dumps(result, ensure_ascii=False, default=str)

View File

@ -1,347 +0,0 @@
/* 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();
}
})();

View File

@ -1,75 +0,0 @@
/* scense 游戏列表入口页渲染bricks 页面内 Html 脚本区调用) */
window.scenseGameList = (function () {
function api(url, params) {
return fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(params || {})
}).then(function (r) { return r.json(); });
}
function base() {
return window.location.pathname.replace(/\/game\/.*$/, '');
}
function statusText(s) {
var map = {created: '已创建', playing: '进行中', paused: '已暂停', ended: '已结束'};
return map[s] || s;
}
function renderSessions(sessions) {
var box = document.getElementById('scense_session_list');
if (!box) return;
if (!sessions || !sessions.length) { box.innerHTML = '<div style="color:#9CA3AF;">暂无历史会话</div>'; return; }
var html = '';
sessions.forEach(function (s) {
html += '<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 12px;' +
'background:#FFF;border-radius:6px;margin-bottom:8px;border:1px solid #E5E7EB;">' +
'<div><b>' + s.game_name + '</b> <span style="color:#6B7280;font-size:12px;">' +
(s.score || 0) + ' 分 / Lv.' + (s.level || 1) + '</span></div>' +
'<div><span style="color:' + (s.status === 'playing' ? '#16A34A' : '#6B7280') + ';margin-right:10px;">' +
statusText(s.status) + '</span>' +
'<button onclick="scenseGameList.startGame(\'' + s.game_id + '\',\'' + s.session_id + '\')" ' +
'style="padding:4px 12px;background:#2563EB;color:#FFF;border:none;border-radius:4px;cursor:pointer;">继续</button></div></div>';
});
box.innerHTML = html;
}
function renderResults(results) {
var box = document.getElementById('scense_result_list');
if (!box) return;
if (!results || !results.length) { box.innerHTML = '<div style="color:#9CA3AF;">暂无结算记录</div>'; return; }
var html = '';
results.forEach(function (r) {
html += '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#FFF;' +
'border-radius:6px;margin-bottom:6px;border:1px solid #E5E7EB;">' +
'<span><b>' + r.game_id + '</b> <span style="color:#6B7280;font-size:12px;">' + r.created_at + '</span></span>' +
'<span style="color:#2563EB;"><b>' + r.score + '</b> 分 · Lv.' + r.level + ' · ' +
Math.round(r.duration_ms / 1000) + 's</span></div>';
});
box.innerHTML = html;
}
function load() {
api(base() + '/game/api/game_list.dspy', {}).then(function (res) {
if (res && res.success && res.data) {
renderSessions(res.data.sessions);
renderResults(res.data.results);
}
});
}
function startGame(gameId, sessionId) {
var url = base() + '/game/game_play.html?game_id=' + encodeURIComponent(gameId || '');
if (sessionId) url += '&session_id=' + encodeURIComponent(sessionId);
window.open(url, '_blank');
}
return {api: api, load: load, startGame: startGame, renderSessions: renderSessions, renderResults: renderResults};
})();
(function () {
if (window.bricks && window.bricks.app) {
setTimeout(function () { window.scenseGameList && window.scenseGameList.load(); }, 300);
}
})();

View File

@ -1,87 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>元景 · 游戏展示</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { width: 100%; height: 100%; overflow: hidden; font-family: "PingFang SC", "Microsoft YaHei", sans-serif; background: #0F172A; color: #E2E8F0; }
#game_wrap { position: fixed; inset: 0; display: flex; flex-direction: column; }
#hud_bar { height: 48px; background: #1E293B; display: flex; align-items: center; padding: 0 14px; gap: 12px; border-bottom: 1px solid #334155; flex-shrink: 0; }
#hud_bar .title { font-weight: bold; font-size: 15px; }
.hud-item { font-size: 13px; color: #94A3B8; }
.hud-item b { color: #FBBF24; font-size: 15px; }
#hud_status { margin-left: auto; font-size: 13px; padding: 4px 10px; border-radius: 12px; background: #334155; color: #CBD5E1; }
#hud_status.ok { background: #14532D; color: #4ADE80; }
#hud_btns { display: flex; gap: 8px; }
#hud_btns button { padding: 5px 14px; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; background: #2563EB; color: #FFF; }
#hud_btns button:disabled { opacity: .45; cursor: not-allowed; }
#hud_btns button.warn { background: #DC2626; }
#hud_btns button.ghost { background: #475569; }
#stage { flex: 1; position: relative; display: flex; }
#game_canvas { flex: 1; background: #F8FAFC; cursor: pointer; }
#side_panel { width: 300px; background: #1E293B; border-left: 1px solid #334155; display: flex; flex-direction: column; padding: 10px; gap: 8px; }
#game_log { flex: 1; overflow-y: auto; font-size: 12px; color: #94A3B8; line-height: 1.7; background: #0F172A; border-radius: 6px; padding: 8px; }
#share_box { display: none; }
#share_box input { width: 100%; padding: 6px; font-size: 12px; border-radius: 4px; border: 1px solid #475569; background: #0F172A; color: #E2E8F0; }
#share_box button { margin-top: 4px; padding: 4px 10px; font-size: 12px; border: none; border-radius: 4px; background: #059669; color: #FFF; cursor: pointer; }
#panel_result { position: absolute; inset: 0; display: none; background: rgba(15,23,42,.88); z-index: 10; }
#panel_hud { display: none; }
.result-card { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 400px; background: #1E293B; border: 1px solid #334155; border-radius: 12px; padding: 28px; text-align: center; }
.result-card h2 { margin-bottom: 18px; color: #FBBF24; }
.result-card .row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px dashed #334155; font-size: 14px; }
.result-card .row b { color: #F8FAFC; }
.result-card .btns { margin-top: 20px; display: flex; gap: 10px; justify-content: center; }
.result-card .btns button { padding: 8px 22px; border: none; border-radius: 6px; cursor: pointer; background: #2563EB; color: #FFF; font-size: 14px; }
.result-card .btns button.ghost { background: #475569; }
</style>
</head>
<body>
<div id="game_wrap">
<div id="hud_bar">
<span class="title" id="page_title">🎮 世界探索 Demo</span>
<span class="hud-item">分数 <b id="hud_score">0</b></span>
<span class="hud-item">关卡 Lv.<b id="hud_level">1</b></span>
<span class="hud-item">时长 <b id="hud_time">00:00</b></span>
<span id="hud_status">加载中…</span>
<div id="hud_btns">
<button id="btn_pause" class="ghost" disabled>⏸ 暂停</button>
<button id="btn_resume" class="ghost" style="display:none" disabled>▶ 继续</button>
<button id="btn_share" class="ghost" disabled>🔗 分享</button>
<button id="btn_save" class="ghost" disabled>💾 保存</button>
<button id="btn_end" class="warn" disabled>🏁 结束</button>
<button id="btn_restart" class="ghost" disabled>🔄 重开</button>
<button id="btn_exit" class="ghost" disabled>✖ 退出</button>
</div>
</div>
<div id="stage">
<canvas id="game_canvas" width="900" height="560"></canvas>
<div id="side_panel">
<div id="share_box">
<input id="share_url" readonly>
<button id="btn_copy">复制链接</button>
</div>
<div id="game_log"></div>
</div>
</div>
</div>
<div id="panel_hud"></div>
<div id="panel_result">
<div class="result-card">
<h2>🏁 游戏结算</h2>
<div class="row"><span>最终得分</span><b id="result_score">0</b></div>
<div class="row"><span>到达关卡</span><b id="result_level">1</b></div>
<div class="row"><span>游戏时长</span><b id="result_duration">0 秒</b></div>
<div class="row"><span>结算时间</span><b id="result_time">-</b></div>
<div class="btns">
<button id="btn_result_again">🔄 再来一局</button>
<button id="btn_result_back" class="ghost">✖ 退出</button>
</div>
</div>
</div>
<!-- W-10 运行时引擎runtime 模块any 权限自动服务) -->
<script src="/runtime/runtime.js" defer></script>
<script src="game.js" defer></script>
</body>
</html>

View File

@ -1,24 +0,0 @@
{"widgettype": "VBox", "options": {"width": "100%", "height": "100%", "padding": "16px", "backgroundColor": "#F5F7FA"}, "subwidgets": [
{"widgettype": "Text", "options": {"label": "元景 · 游戏展示", "fontSize": "24px", "fontWeight": "bold", "color": "#1F2937"}},
{"widgettype": "Html", "options": {"html": "<div style=\"color:#6B7280;margin:4px 0 12px 0;\">点击「开始玩」进入全屏游戏,支持暂停/继续/结束结算/重新开始/分享/保存进度</div>"}},
{"widgettype": "ResponsableBox", "options": {"gap": "14px", "minWidth": "260px"}, "subwidgets": [
{"widgettype": "VBox", "options": {"backgroundColor": "#FFFFFF", "padding": "18px", "borderRadius": "8px", "cursor": "pointer", "boxShadow": "0 1px 3px rgba(0,0,0,0.1)"},
"binds": [{"wid": "self", "event": "click", "actiontype": "script", "script": "window.scenseGameList && scenseGameList.startGame('world_demo')"}],
"subwidgets": [
{"widgettype": "Text", "options": {"label": "🎮 世界探索 Demo", "fontSize": "18px", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"label": "点击实体触发脚本逻辑的世界演示游戏(依赖 W-10 运行时)", "color": "#6B7280", "fontSize": "13px", "marginTop": "6px"}},
{"widgettype": "Text", "options": {"label": "▶ 开始玩", "color": "#2563EB", "fontSize": "14px", "fontWeight": "bold", "marginTop": "10px"}}
]},
{"widgettype": "VBox", "options": {"backgroundColor": "#FFFFFF", "padding": "18px", "borderRadius": "8px", "cursor": "pointer", "boxShadow": "0 1px 3px rgba(0,0,0,0.1)"},
"binds": [{"wid": "self", "event": "click", "actiontype": "script", "script": "window.scenseGameList && scenseGameList.startGame('click_quest')"}],
"subwidgets": [
{"widgettype": "Text", "options": {"label": "⚡ 点击冒险", "fontSize": "18px", "fontWeight": "bold"}},
{"widgettype": "Text", "options": {"label": "点选移动实体触发脚本逻辑的冒险小游戏", "color": "#6B7280", "fontSize": "13px", "marginTop": "6px"}},
{"widgettype": "Text", "options": {"label": "▶ 开始玩", "color": "#2563EB", "fontSize": "14px", "fontWeight": "bold", "marginTop": "10px"}}
]}
]},
{"widgettype": "Text", "options": {"label": "历史会话", "fontSize": "17px", "fontWeight": "bold", "marginTop": "22px"}},
{"widgettype": "Html", "id": "scense_session_box", "options": {"html": "<div id=\"scense_session_list\">加载中…</div>"}},
{"widgettype": "Text", "options": {"label": "历史结算", "fontSize": "17px", "fontWeight": "bold", "marginTop": "18px"}},
{"widgettype": "Html", "id": "scense_result_box", "options": {"html": "<div id=\"scense_result_list\">加载中…</div>"}}
]}