Compare commits

..

11 Commits

Author SHA1 Message Date
Evan
bb78d8bf5c feat:更改配置项 2026-09-20 13:59:10 +08:00
Evan
c4641aee19 批量修改 2026-09-17 17:04:15 +08:00
Evan
782f76170f c++bug修复 2026-09-17 11:53:14 +08:00
Evan
d83654837a c++优化 2026-09-15 17:55:13 +08:00
Evan
899adbc9d4 feat: ai功能优化 2026-09-15 17:55:05 +08:00
Evan
4499d7fdef no message 2026-09-14 17:52:02 +08:00
Evan
11d7496b54 feat:更新cocos包 2026-09-11 16:06:20 +08:00
Evan
1012af6039 feat:修改国宝chainese 2026-09-11 16:05:58 +08:00
Evan
37e0413576 feat: 更新大版本 2026-09-11 09:32:07 +08:00
Evan
8dfd42397f feat:部署前配置 2026-09-10 15:15:39 +08:00
Evan
19bb63cd87 整理config 2026-09-07 11:42:49 +08:00
463 changed files with 1347 additions and 2003 deletions

View File

@@ -17,23 +17,6 @@ server {
return 301 /competition.html?competition_id=13;
}
<<<<<<< HEAD
# Brotli 压缩 WebAssembly 文件处理
location ~* \.wasm\.br$ {
default_type "";
add_header Content-Encoding br;
add_header Content-Type application/wasm always;
add_header Vary Accept-Encoding;
expires 30d;
}
location ~* \.asm\.js\.br$ {
default_type "";
add_header Content-Encoding br;
add_header Content-Type application/javascript always;
add_header Vary Accept-Encoding;
expires 30d;
=======
# Unity 预压缩文件:禁止再次 gzip避免 Content-Length 与实际内容不一致
location ~* \.wasm\.br$ {
gzip off;
@@ -62,22 +45,15 @@ server {
add_header Vary Accept-Encoding;
add_header Accept-Ranges bytes;
expires 1h;
>>>>>>> cocos-main
}
# 通用 Brotli 文件处理
location ~* \.br$ {
<<<<<<< HEAD
add_header Content-Encoding br;
add_header Vary Accept-Encoding;
expires 30d;
=======
gzip off;
add_header Content-Encoding br;
add_header Vary Accept-Encoding;
add_header Accept-Ranges bytes;
expires 1h;
>>>>>>> cocos-main
}
# 静态资源缓存配置

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -384,7 +384,7 @@
<footer>
<div class="footer-line" role="presentation"></div>
<p class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">沪ICP备2026042325号</a>
<!-- 公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a> -->
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="assets/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />公网安备31011502407417号</a>
© 灵丌编程001CODE.COM</p>
</footer>
</div>

View File

@@ -28,6 +28,8 @@
"test:lint": "eslint . --ext .js,.jsx",
"test:unit": "jest test[\\\\/]unit[\\\\/]addons",
"test:smoke": "jest --runInBand test[\\\\/]smoke",
"test:cpp": "node scripts/test-cpp-regressions.cjs",
"test:devtool": "node scripts/test-devtool-control.cjs",
"watch": "webpack --colors --watch"
},
"config": {

View File

@@ -0,0 +1,71 @@
// Read-only diagnostic: exercise the current editor interpreter with mocked game APIs.
// Run from scratch-gui: node scripts/diagnose-cpp.cjs
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const babel = require('@babel/core');
const filename = path.resolve(__dirname, '../src/components/stage-cpp/cpp-parser.js');
const transformed = babel.transformSync(fs.readFileSync(filename, 'utf8'), {
filename, configFile: false, babelrc: false,
plugins: ['@babel/plugin-transform-modules-commonjs']
}).code;
const context = {
exports: {}, require, setTimeout,
console: {log() {}, warn() {}, error() {}}, window: {}
};
vm.runInNewContext(transformed, context, {filename});
const {CppCodeExecutor, validateCppSyntax} = context.exports;
const main = body => `int main() {\n${body}\n}`;
const customer = `void test() {
for (int i = 0; i < 2; i++) {
}
}
int main() {
turnLeft(-1);
for (int i = 0; i < 1; i++) {
test();
playerMove(i);
}
}`;
const cases = [
['customer_local_i', customer, {moves: [0]}],
['customer_renamed_inner_j', customer.replace('int i = 0; i < 2; i++', 'int j = 0; j < 2; j++'), {moves: [0]}],
['cout_literal', main('cout << "Hello" << endl;'), {output: ['Hello\n']}],
['cout_variable', main('int i = 7;\ncout << "i=" << i << endl;'), {output: ['i=7\n']}],
['cout_string_assignment_shape', main('cout << "value=42" << endl;'), {output: ['value=42\n']}],
['cout_increment_expression', main('int i = 0;\ncout << i++ << endl;\nplayerMove(i);'), {output: ['0\n'], moves: [1]}],
['cout_increment_literal', main('cout << "C++" << endl;'), {output: ['C++\n']}],
['cout_std_qualified', main('std::cout << "Hello" << std::endl;'), {output: ['Hello\n']}],
['main_return_zero', main('return 0;'), {error: null}],
['legal_space_before_call', main('playerMove (1);'), {valid: true, moves: [1]}],
['nested_loop_shadowing', main('for (int i = 0; i < 2; i++) {\nfor (int i = 0; i < 2; i++) {\n}\nplayerMove(i);\n}'), {moves: [0, 1]}],
['negative_integer_division', main('int a = -3;\na /= 2;\nplayerMove(a);'), {moves: [-1]}],
['function_return_value', 'int answer() {\nreturn 7;\n}\n' + main('int n = answer();\nplayerMove(n);'), {moves: [7]}],
['nested_if_break', main('for (int i = 0; i < 3; i++) {\nif (i == 1) {\nbreak;\n}\nplayerMove(i);\n}'), {moves: [0]}],
['floating_point', main('double x = 1.5;\ncout << x << endl;'), {output: ['1.5\n']}],
['array_index', main('int a[2] = {3, 7};\nplayerMove(a[1]);'), {moves: [7]}],
['unknown_variable', main('playerMove(missing);'), {valid: false}],
['early_return_zero', 'int stop() {\nreturn 0;\nplayerMove(9);\n}\n' + main('stop();'), {moves: []}]
];
(async () => {
const results = [];
for (const [name, code, expected] of cases) {
const actual = {valid: false, moves: [], turns: [], output: [], error: null};
context.window = {isScriptRunOver: false, cppGameAPI: {
playerMove: async n => actual.moves.push(n),
turnLeft: async n => actual.turns.push(n),
cmdPrint: text => actual.output.push(text)
}};
const validation = await validateCppSyntax(code);
actual.valid = validation.isValid;
if (!actual.valid) actual.validationErrors = validation.errors;
const executor = new CppCodeExecutor(code, null);
if (actual.valid) {
try { await executor.run(); } catch (error) {actual.error = error.message;}
}
const matchesExpected = Object.entries(expected).every(([key, value]) =>
JSON.stringify(actual[key]) === JSON.stringify(value));
results.push({name, expected, actual, matchesExpected});
}
process.stdout.write(JSON.stringify(results, null, 2) + '\n');
})().catch(error => {console.error(error); process.exitCode = 1;});

View File

@@ -21,7 +21,7 @@ function getConfigForEnv(env) {
case 'test':
return { url: 'https://test-service.001code.cn', disabledev: false };
case 'prod':
return { url: 'https://service.001code.cn', disabledev: true };
return { url: 'https://service.001code.com', disabledev: true };
case 'start':
default:
return { url: 'http://127.0.0.1:8000', disabledev: false };

View File

@@ -0,0 +1,139 @@
// Regression tests for variable scopes and return control flow in the editor interpreter.
// Run: node scripts/test-cpp-regressions.cjs
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const babel = require('@babel/core');
const parserPath = path.resolve(__dirname, '../src/components/stage-cpp/cpp-parser.js');
const source = fs.readFileSync(parserPath, 'utf8');
const compiled = babel.transformSync(source, {
filename: parserPath, configFile: false, babelrc: false,
plugins: ['@babel/plugin-transform-modules-commonjs'],
presets: process.argv.includes('--transpiled') ? ['@babel/preset-env'] : []
}).code;
const silentConsole = {log() {}, warn() {}, error() {}};
const context = {exports: {}, require, setTimeout, console: silentConsole, window: {}};
vm.runInNewContext(compiled, context, {filename: parserPath});
const {CppCodeExecutor, validateCppSyntax, createCppExecutor} = context.exports;
const main = body => `int main() {\n${body}\n}`;
// Complete program from the first email screenshot; the second repeats this issue.
const customerEmailCode = `void test() {
for (int i = 0; i < 2; i++) {
}
}
int main() {
// 在这里编写你的C++代码来控制角色
turnLeft(-1);
for (int i = 0; i < 1; i++) {
test();
playerMove(i);
}
return 0;
}`;
async function run(code, expected, configure) {
const moves = [];
const turns = [];
context.window = {isScriptRunOver: false, cppGameAPI: {
playerMove: async n => moves.push(n),
turnLeft: async n => turns.push(n),
cmdPrint: () => {}
}};
const executor = new CppCodeExecutor(code, null);
if (configure) configure(executor, context.window);
const validation = await validateCppSyntax(code);
assert.strictEqual(validation.isValid, true, JSON.stringify(validation.errors));
const exitCode = await executor.run();
if (Object.prototype.hasOwnProperty.call(expected, 'exitCode')) assert.strictEqual(exitCode, expected.exitCode);
if (expected.moves) assert.deepStrictEqual(moves, expected.moves);
if (expected.turns) assert.deepStrictEqual(turns, expected.turns);
assert.strictEqual(executor.variables, executor.globalVariables, 'scope must unwind after execution');
assert.strictEqual(executor.callStack.length, 0);
assert.strictEqual(executor.currentExecutionContext, null);
assert.strictEqual(executor.isRunning, false);
return executor;
}
const cases = [
['screenshot: main return 1 is a normal exit', main('playerMove(4);\nreturn 1;'), {moves: [4], exitCode: 1}],
['return 1 skips remaining main statements', main('return 1;\nplayerMove(9);'), {moves: [], exitCode: 1}],
['return 0 also stops main early', main('return 0;\nplayerMove(9);'), {moves: [], exitCode: 0}],
['negative exit code', main('return -1;'), {moves: [], exitCode: -1}],
['parenthesized return expression', main('int status = 2;\nreturn (status + 1);'), {moves: [], exitCode: 3}],
['main fallthrough returns zero', main('playerMove(1);'), {moves: [1], exitCode: 0}],
['return from for loop exits main', main('for (int i = 0; i < 3; i++) {\nplayerMove(i);\nreturn 1;\n}\nplayerMove(9);'), {moves: [0], exitCode: 1}],
['return zero inside loop is not ignored', main('for (int i = 0; i < 3; i++) {\nreturn 0;\n}\nplayerMove(9);'), {moves: [], exitCode: 0}],
['return through while and if exits main', main('while (true) {\nif (true) {\nreturn 2;\n}\nplayerMove(9);\n}\nplayerMove(8);'), {moves: [], exitCode: 2}],
['function return 0 skips only its own remaining statements', 'int f() {\nreturn 0;\nplayerMove(9);\n}\n' + main('f();\nplayerMove(1);\nreturn 1;'), {moves: [1], exitCode: 1}],
['function return inside loop resumes caller', 'int f() {\nfor (int i = 0; i < 3; i++) {\nreturn 1;\n}\nplayerMove(9);\n}\n' + main('f();\nplayerMove(1);'), {moves: [1], exitCode: 0}],
['identifier starting with return is not a return statement', main('int returnValue = 1;\nreturnValue = 2;\nplayerMove(returnValue);\nreturn 0;'), {moves: [2], exitCode: 0}],
['customer email complete program (screenshots 1 and 2)', customerEmailCode, {moves: [0], turns: [-1]}],
['nested loops', main('for (int i = 0; i < 2; i++) {\nfor (int i = 0; i < 2; i++) {\n}\nplayerMove(i);\n}'), {moves: [0, 1]}],
['loop initializer restores shadowed value', main('int i = 9;\nfor (int i = 0; i < 2; i++) {\n}\nplayerMove(i);'), {moves: [9]}],
['loop assignment updates existing value', main('int i = 9;\nfor (i = 0; i < 2; i++) {\n}\nplayerMove(i);'), {moves: [2]}],
['block declaration versus assignment', main('int n = 1;\nif (true) {\nint n = 7;\nplayerMove(n);\n}\nif (true) {\nn = 3;\n}\nplayerMove(n);'), {moves: [7, 3]}],
['for body scope per iteration', main('int x = 9;\nfor (int i = 0; i < 2; i++) {\nplayerMove(x);\nint x = 2;\n}\nplayerMove(x);'), {moves: [9, 9, 9]}],
['while body scope per iteration', main('int x = 9;\nint i = 0;\nwhile (i < 2) {\nplayerMove(x);\nint x = 2;\ni++;\n}\nplayerMove(x);'), {moves: [9, 9, 9]}],
['function locals', 'void f() {\nint n = 7;\nplayerMove(n);\n}\n' + main('int n = 2;\nf();\nplayerMove(n);'), {moves: [7, 2]}],
['all arguments evaluated before binding', 'void f(int a, int b) {\nplayerMove(a);\nplayerMove(b);\n}\n' + main('int a = 2;\nf(7, a);\nplayerMove(a);'), {moves: [7, 2, 2]}],
['recursive locals and parameters', 'void f(int n) {\nint saved = n;\nif (n > 0) {\nf(n - 1);\n}\nplayerMove(saved);\n}\n' + main('f(2);'), {moves: [0, 1, 2]}],
['early void return unwinds scope', 'void f() {\nint n = 8;\nreturn;\n}\n' + main('int n = 2;\nf();\nplayerMove(n);'), {moves: [2]}],
['continue unwinds body scope', main('int x = 9;\nfor (int i = 0; i < 2; i++) {\nint x = 2;\ncontinue;\n}\nplayerMove(x);'), {moves: [9]}],
['break does not increment', main('int i = 0;\nfor (i = 0; i < 2; i++) {\nbreak;\n}\nplayerMove(i);'), {moves: [0]}],
];
(async () => {
for (const [name, code, expected] of cases) {
try { await run(code, expected); } catch (error) {error.message = `${name}: ${error.message}`; throw error;}
console.log(`PASS ${name}`);
}
const strings = new CppCodeExecutor('', null);
await strings.executeSimpleStatement('string msg = "outer";');
await strings.withVariableScope(async () => {
await strings.executeSimpleStatement('string msg = "inner";');
assert.strictEqual(strings.variables.get('msg'), 'inner');
});
assert.strictEqual(strings.variables.get('msg'), 'outer');
console.log('PASS string block shadowing');
// Seed the interpreter's global environment to verify lexical lookup and writes.
const globalExecutor = await run('void f() {\ng += 1;\nplayerMove(g);\n}\n' + main('int g = 9;\nf();\nplayerMove(g);'), {moves: [2, 9]}, executor => executor.variables.set('g', 1));
assert.strictEqual(globalExecutor.variables.get('g'), 2);
console.log('PASS global writes survive function calls; caller locals are not visible');
for (const stop of [false, true]) {
const executor = new CppCodeExecutor('void f(int n) {\nfor (int i = 0; i < 2; i++) {\nplayerMove(i);\n}\n}\n' + main('f(2);'), null);
executor.variables.set('n', 9);
context.window = {isScriptRunOver: false, cppGameAPI: {playerMove: async () => {
if (stop) context.window.isScriptRunOver = true;
else throw new Error('game failure');
}}};
if (stop) await executor.run();
else await assert.rejects(executor.run(), /game failure/);
assert.strictEqual(executor.variables, executor.globalVariables);
assert.strictEqual(executor.variables.get('n'), 9);
assert.strictEqual(executor.variables.has('i'), false);
assert.strictEqual(executor.callStack.length, 0);
assert.strictEqual(executor.currentExecutionContext, null);
console.log(`PASS scope cleanup on ${stop ? 'stop' : 'error'}`);
}
const highlighted = [];
context.window = {isScriptRunOver: false, setHighlightedLine: line => highlighted.push(line)};
const manager = createCppExecutor();
for (const status of [1, 0]) {
assert.strictEqual(await manager.execute(main(`return ${status};`)), status);
assert.strictEqual(manager.currentExecutor, null);
assert.strictEqual(highlighted[highlighted.length - 1], null);
}
console.log('PASS editor executor manager resolves nonzero exit and can run again');
context.window.cppGameAPI = {playerMove: async () => {throw new Error('RETURN_INTERRUPT');}};
await assert.rejects(manager.execute('void f() {\nplayerMove(1);\n}\n' + main('f();')), /RETURN_INTERRUPT/);
assert.strictEqual(manager.currentExecutor, null);
assert.strictEqual(highlighted[highlighted.length - 1], null);
console.log('PASS real game errors are not mistaken for a return signal');
console.log(`${cases.length + 6} regression checks passed`);
})().catch(error => {console.error(error); process.exitCode = 1;});

View File

@@ -0,0 +1,166 @@
// Run: npm run test:devtool
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const babel = require('@babel/core');
const root = path.resolve(__dirname, '..');
const controlPath = 'src/playground/devtool-control.js';
const compile = file => babel.transformFileSync(path.join(root, file), {
configFile: false, babelrc: false, presets: ['@babel/preset-env', '@babel/preset-react']
}).code;
const controlCode = compile(controlPath);
const homeCode = compile('src/playground/home.js');
let checks = 0;
function test(name, action) {
action();
checks++;
console.log(`PASS ${name}`);
}
function page(storage = new Map(), {enabled = true, brokenStorage = false, immediate = false} = {}) {
const starts = [];
const effects = [];
function detector(options) {
starts.push(options);
// Simulate a library probe firing during initialization with F12 already open.
if (immediate) options.ondevtoolopen('already-open');
}
detector.isSuspend = false;
const window = {
location: {href: '/editor.html'}, onbeforeunload: () => {},
cppCodeStopRun: () => effects.push('stop C++'),
loadCppProject: () => effects.push('clear C++'),
pythonCodeStopRun: () => effects.push('stop Python'),
loadPythonProject: () => effects.push('clear Python'),
unityInstance: {Quit: () => effects.push('quit engine')}
};
const sessionStorage = {
getItem(key) {if (brokenStorage) throw new Error('storage denied'); return storage.get(key) || null;},
setItem(key, value) {if (brokenStorage) throw new Error('storage denied'); storage.set(key, value);}
};
const sandbox = {
exports: {}, window, sessionStorage,
localStorage: {getItem: () => '/index.html'},
vm: {
stopAll: () => effects.push('stop Scratch'),
editingTarget: {blocks: {deleteAllBlocks: () => effects.push('clear Scratch')}},
emitWorkspaceUpdate: () => effects.push('update workspace')
},
require(name) {
if (name === 'disable-devtool') return detector;
if (name === './config.js') return {disabledev: enabled};
if (name === './devtool-control.js') return api;
throw new Error(`Unexpected import: ${name}`);
}
};
const context = vm.createContext(sandbox);
vm.runInContext(controlCode, context, {filename: controlPath});
const api = sandbox.exports;
return {api, starts, effects, detector, window, context};
}
// Use the actual options and destructive callback from each editor mode.
function stageOptions(file) {
const source = fs.readFileSync(path.join(root, file), 'utf8');
const ast = babel.parseSync(source, {configFile: false, babelrc: false, parserOpts: {plugins: ['jsx']}});
let options;
function visit(node) {
if (!node || typeof node !== 'object') return;
if (node.type === 'ImportDeclaration') {
assert.notStrictEqual(node.source.value, 'disable-devtool', `${file} bypasses shared control`);
}
if (node.type === 'CallExpression' && node.callee.name === 'initDisableDevtool') options = node.arguments[0];
for (const value of Object.values(node)) {
if (Array.isArray(value)) value.forEach(visit);
else if (value && typeof value === 'object') visit(value);
}
}
visit(ast);
assert(options, `${file} must initialize through shared control`);
// Also verify the entire JSX file remains compatible with project Babel presets.
compile(file);
return source.slice(options.start, options.end);
}
const storage = new Map();
test('home exposes stopDev and persists it for navigation', () => {
const home = page(storage);
vm.runInContext(homeCode, home.context);
home.window.stopDev();
assert.strictEqual(storage.get('stopDev'), '1');
assert.strictEqual(home.detector.isSuspend, true);
});
for (const mode of ['stage-unity', 'stage-python-unity', 'stage-cpp-unity']) {
const optionSource = stageOptions(`src/components/${mode}/stage-unity.jsx`);
test(`${mode}: home -> editor with F12 open does not start detection or clear code`, () => {
const editor = page(storage, {immediate: true});
const options = vm.runInContext(`(${optionSource})`, editor.context);
editor.api.initDisableDevtool(options);
assert.strictEqual(editor.starts.length, 0);
assert.strictEqual(editor.detector.isSuspend, true);
assert.strictEqual(editor.window.location.href, '/editor.html');
assert.deepStrictEqual(editor.effects, []);
// A refresh or another mode initialization in this tab must stay stopped.
editor.api.initDisableDevtool(options);
assert.strictEqual(editor.starts.length, 0);
});
test(`${mode}: stopDev blocks an already queued destructive callback`, () => {
const editor = page();
const options = vm.runInContext(`(${optionSource})`, editor.context);
editor.api.initDisableDevtool(options);
assert.strictEqual(editor.starts.length, 1);
const callback = editor.starts[0];
editor.window.stopDev();
assert.strictEqual(callback.ignore(), true);
callback.ondevtoolopen('queued');
assert.strictEqual(editor.window.location.href, '/editor.html');
assert.deepStrictEqual(editor.effects, []);
});
}
test('normal session retains default detection and redirect', () => {
const normal = page();
normal.api.initDisableDevtool();
assert.strictEqual(normal.starts.length, 1);
assert.strictEqual(normal.starts[0].ignore(), false);
normal.starts[0].ondevtoolopen();
assert.strictEqual(normal.window.location.href, '/index.html');
});
test('custom options, ignore and callback arguments remain intact', () => {
const normal = page();
const seen = [];
const next = () => {};
normal.api.initDisableDevtool({interval: 123, ignore: () => true, ondevtoolopen: (...args) => seen.push(args)});
assert.strictEqual(normal.starts[0].interval, 123);
assert.strictEqual(normal.starts[0].ignore(), true);
normal.starts[0].ondevtoolopen(7, next);
assert.deepStrictEqual(seen, [[7, next]]);
});
test('disabled configuration does not initialize; stopDev remains available', () => {
const development = page(new Map(), {enabled: false});
development.api.initDisableDevtool();
assert.strictEqual(development.starts.length, 0);
assert.strictEqual(typeof development.window.stopDev, 'function');
});
test('blocked storage still permits immediate stop on current page', () => {
const broken = page(new Map(), {brokenStorage: true});
broken.api.initDisableDevtool();
broken.window.stopDev();
assert.strictEqual(broken.detector.isSuspend, true);
assert.strictEqual(broken.starts[0].ignore(), true);
broken.starts[0].ondevtoolopen();
broken.api.initDisableDevtool();
assert.strictEqual(broken.starts.length, 1);
assert.strictEqual(broken.window.location.href, '/editor.html');
});
for (const file of ['src/playground/competition.js', 'src/playground/competition_sum.js']) {
const optionsSource = stageOptions(file);
test(`${file}: same stopDev state is honored`, () => {
const competition = page(storage, {immediate: true});
competition.api.initDisableDevtool(vm.runInContext(`(${optionsSource})`, competition.context));
assert.strictEqual(competition.starts.length, 0);
});
}
console.log(`${checks} devtool regression checks passed`);

View File

@@ -0,0 +1,49 @@
.overlay {
position: absolute;
inset: 0;
z-index: 50;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 14px;
background: rgba(8, 8, 8, 0.92);
pointer-events: auto;
opacity: 1;
transition: opacity 0.28s ease-out, visibility 0.28s ease-out;
}
.overlay.hidden {
opacity: 0;
pointer-events: none;
visibility: hidden;
}
.text {
color: #f2f2f2;
font-size: 15px;
line-height: 1.5;
letter-spacing: 0.5px;
}
.barTrack {
width: min(72%, 280px);
height: 10px;
background: rgba(255, 255, 255, 0.16);
border-radius: 999px;
overflow: hidden;
}
.barFill {
height: 100%;
background: linear-gradient(90deg, #4fc3f7 0%, #29b6f6 100%);
border-radius: 999px;
transition: width 0.25s linear;
}
.percent {
color: #e0e0e0;
font-size: 18px;
font-weight: 600;
font-variant-numeric: tabular-nums;
}

View File

@@ -0,0 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './game-loading-overlay.css';
const GameLoadingOverlay = ({ visible, progress }) => {
const pct = Math.max(0, Math.min(100, Math.round(Number(progress) || 0)));
return (
<div
className={`${styles.overlay}${visible ? '' : ` ${styles.hidden}`}`}
aria-hidden={!visible}
>
<div className={styles.text}>正在加载中</div>
<div className={styles.barTrack}>
<div className={styles.barFill} style={{ width: `${pct}%` }} />
</div>
<div className={styles.percent}>{pct}%</div>
</div>
);
};
GameLoadingOverlay.propTypes = {
visible: PropTypes.bool,
progress: PropTypes.number
};
GameLoadingOverlay.defaultProps = {
visible: true,
progress: 0
};
export default GameLoadingOverlay;

View File

@@ -0,0 +1,47 @@
import { useCallback, useEffect, useRef, useState } from 'react';
const alreadyReady = () => typeof window !== 'undefined' && window.iscreate === 1;
const useGameLoadProgress = () => {
const [progress, setProgress] = useState(() => (alreadyReady() ? 100 : 0));
const [visible, setVisible] = useState(() => !alreadyReady());
const hidingRef = useRef(false);
useEffect(() => {
if (!visible) return undefined;
const timer = setInterval(() => {
if (window.iscreate === 1) {
if (!hidingRef.current) {
hidingRef.current = true;
setProgress(100);
setTimeout(() => setVisible(false), 280);
}
clearInterval(timer);
return;
}
setProgress(prev => (prev >= 90 && prev < 99 ? prev + 1 : prev));
}, 300);
return () => clearInterval(timer);
}, [visible]);
const onUnityProgress = useCallback(unityProgress => {
const mapped = Math.round(Math.min(90, Number(unityProgress) * 90));
if (!Number.isFinite(mapped)) return;
setProgress(prev => (mapped > prev ? mapped : prev));
}, []);
const onUnityInstanceCreated = useCallback(() => {
setProgress(prev => Math.max(prev, 90));
}, []);
return {
progress,
visible,
onUnityProgress,
onUnityInstanceCreated
};
};
export default useGameLoadProgress;

View File

@@ -852,7 +852,7 @@ class MenuBar extends React.Component {
let realLvId_tmp = '';
// 发送消息给 Unity 实例,切换关卡
if (localStorage.getItem('platformMode') === 'normal') {
realLvId_tmp = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv;
realLvId_tmp = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv;
} else {
realLvId_tmp = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber);
}
@@ -902,7 +902,8 @@ class MenuBar extends React.Component {
window.gamelvdataJson.gamelv,
window.gamelvdataJson.gameid,
window.gamelvdataJson.ismatch,
window.gamelvdataJson.gameNumber
window.gamelvdataJson.gameNumber,
window.gamelvdataJson.gamemode
) + (window.gamelvdata ? window.gamelvdata.currentlv : '');
} else {
realLvId_tmp = CONFIG.getRealLvId(

View File

@@ -148,7 +148,7 @@ const CurrentLevelChoose = ({ onCancel }) => {
console.warn('window.loadCppProject 未定义或不是函数');
}
}
const realLvId_tmp = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber);
const realLvId_tmp = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode);
const realLvId = realLvId_tmp + cellIndex
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
CONFIG.checkGameStyle(realLvId);

View File

@@ -11,10 +11,11 @@ import RankBadgeGenerator from '../../playground/rank-badge-generator.js';
// const friendToggle = new FriendToggle();
window.RankBadgeGenerator = RankBadgeGenerator;
window.CONFIG = CONFIG;
import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from '../../playground/devtool-control.js';
import { getUnityPlayerConfig, loadUnityLoaderScript } from '../../lib/unity-build-loader.js';
import { debug } from 'scratch-vm/src/util/log.js';
import GameLoadingOverlay from '../game-loading-overlay/game-loading-overlay.jsx';
import useGameLoadProgress from '../game-loading-overlay/use-game-load-progress.js';
const MAX_LEVELS = 500; // 最大关卡数据数量
const STORAGE_KEY = 'player_levels_data'; // 使用命名空间,防止键名冲突
@@ -48,6 +49,16 @@ const UnityComponent = function (props) {
const [getBlockNum, setBlockNum] = useState(0);
const [testDal, setTestDal] = useState(-1);
const canvasRef = useRef(null);
const {
progress: gameLoadProgress,
visible: gameLoadVisible,
onUnityProgress,
onUnityInstanceCreated
} = useGameLoadProgress();
const onUnityProgressRef = useRef(onUnityProgress);
const onUnityInstanceCreatedRef = useRef(onUnityInstanceCreated);
onUnityProgressRef.current = onUnityProgress;
onUnityInstanceCreatedRef.current = onUnityInstanceCreated;
const [isGameOverVisible, setIsGameOverVisible] = useState(false);
const [isGameOkVisible, setIsGameOkVisible] = useState(false);
const [isLockNextButton, setIsLockNextButton] = useState(true);
@@ -549,7 +560,7 @@ const UnityComponent = function (props) {
useEffect(() => {
if (CONFIG.disabledev) {
disableDevtool({
initDisableDevtool({
ondevtoolopen: () => {
// 临时禁用 onbeforeunload 事件监听,以防止弹出确认离开弹窗
@@ -1274,8 +1285,9 @@ const UnityComponent = function (props) {
window.iscreate = 0;
const boot = () => {
createUnityInstance(canvas, config, (progress) => {
// console.log(`加载进度: ${progress * 100}%`);
onUnityProgressRef.current(progress);
}).then((unityInstance) => {
onUnityInstanceCreatedRef.current();
window.unityInstance = unityInstance;
window.waitForUnityStepFinish = waitForUnityStepFinish;
window.waitForUnityVehicleStepFinish = waitForUnityVehicleStepFinish;
@@ -1339,6 +1351,7 @@ const UnityComponent = function (props) {
}}
>
<canvas id="unity-canvas" ref={canvasRef} width="960" height="600" style={{ width: '100%', height: '100%' }}></canvas>
<GameLoadingOverlay visible={gameLoadVisible} progress={gameLoadProgress} />
@@ -1892,4 +1905,4 @@ UnityComponent.propTypes = {
vm: PropTypes.instanceOf(VM).isRequired
};
export default UnityComponent;
export default UnityComponent;

View File

@@ -510,10 +510,57 @@ export class CppSyntaxValidator {
}
}
// 声明写入当前作用域,赋值更新最近的已有变量;函数不继承调用者的局部变量。
class CppVariableScope extends Map {
constructor(parent = null) {
super();
this.parent = parent;
}
declare(name, value) {
return super.set(name, value);
}
has(name) {
return super.has(name) || Boolean(this.parent && this.parent.has(name));
}
get(name) {
return super.has(name) ? super.get(name) : this.parent && this.parent.get(name);
}
set(name, value) {
if (!super.has(name) && this.parent && this.parent.has(name)) {
this.parent.set(name, value);
return this;
}
return super.set(name, value);
}
*entries() {
const visible = new Map(this.parent ? this.parent.entries() : []);
for (const [name, value] of super.entries()) visible.set(name, value);
yield* visible.entries();
}
[Symbol.iterator]() {
return this.entries();
}
}
// return 是跨越循环/条件块的控制信号,只在所属函数边界消费。
// 使用独立类型,避免吞掉恰好带有相同消息的真实运行错误。
class CppReturnSignal {
constructor(value) {
this.value = value;
}
}
export class CppCodeExecutor {
constructor(code, setHighlightedLine) {
this.code = code;
this.variables = new Map();
this.globalVariables = new CppVariableScope();
this.variables = this.globalVariables;
this.functions = new Map(); // 存储自定义函数
this.currentLine = 0;
this.lines = [];
@@ -527,7 +574,7 @@ export class CppCodeExecutor {
try {
this.isRunning = true;
this.parseCode();
await this.executeMain();
return await this.withVariableScope(() => this.executeMain(), this.globalVariables);
} catch (error) {
console.error('C++执行错误:', error);
throw error;
@@ -544,6 +591,16 @@ export class CppCodeExecutor {
return !this.isRunning || window.isScriptRunOver;
}
async withVariableScope(action, parent = this.variables) {
const previous = this.variables;
this.variables = new CppVariableScope(parent);
try {
return await action();
} finally {
this.variables = previous;
}
}
parseCode() {
debugLog('🔍 开始解析C++代码,支持自定义函数');
@@ -765,12 +822,26 @@ export class CppCodeExecutor {
while (i < lines.length) {
const line = lines[i].trim();
if (!line || line.startsWith('//') || line === '{' || line === '}' || line === 'return 0;' ||
if (!line || line.startsWith('//') || line === '{' || line === '}' ||
line.match(/^\s*}\s*else\s*\{\s*$/) || line.startsWith('else')) {
i++;
continue;
}
// return 不能标记成 continue否则循环会直接跳过它。
if (/^return\b/.test(line)) {
statements.push({
type: 'control',
subtype: 'return',
content: line,
lineNumber: isMainFunction ? this.getActualLineNumber(i) : i + 1,
originalContent: line,
contextPath: isMainFunction ? 'main' : 'function'
});
i++;
continue;
}
// 检查是否是自定义函数调用(包括带括号的实参,如 f((n + 1));
const customCall = this.parseCustomFunctionCall(line);
if (customCall) {
@@ -799,8 +870,8 @@ export class CppCodeExecutor {
statements.push(whileLoop);
i = whileLoop.endIndex;
} else {
// 检查是否是break或continue或return语句
if (line === 'break;' || line === 'continue;' || line.startsWith('return')) {
// 检查是否是break或continue语句
if (line === 'break;' || line === 'continue;') {
statements.push({
type: 'control',
subtype: line === 'break;' ? 'break' : 'continue',
@@ -1278,10 +1349,17 @@ export class CppCodeExecutor {
}
async executeMain() {
for (const statement of this.statements) {
if (this.shouldStop()) break;
await this.executeStatement(statement);
try {
for (const statement of this.statements) {
if (this.shouldStop()) break;
await this.executeStatement(statement);
}
} catch (signal) {
if (!(signal instanceof CppReturnSignal)) throw signal;
return signal.value;
}
// main 正常执行到末尾,相当于 return 0。
return 0;
}
async executeStatement(statement) {
@@ -1300,10 +1378,10 @@ export class CppCodeExecutor {
await this.executeSimpleStatement(statement.content);
break;
case 'for':
await this.executeForLoop(statement);
await this.withVariableScope(() => this.executeForLoop(statement));
break;
case 'if':
await this.executeIfStatement(statement);
await this.withVariableScope(() => this.executeIfStatement(statement));
break;
case 'while':
await this.executeWhileLoop(statement);
@@ -1312,8 +1390,11 @@ export class CppCodeExecutor {
await this.executeFunctionCall(statement);
break;
case 'control':
if (statement.content.startsWith('return')) {
throw new Error('RETURN_INTERRUPT');
if (statement.subtype === 'return') {
const match = statement.content.match(/^return\b([\s\S]*?);(?:\s*\/\/.*)?$/);
if (!match) throw new Error(`无法解析return语句: ${statement.content}`);
const expression = match[1].trim();
throw new CppReturnSignal(expression ? this.evaluateExpression(expression) : undefined);
}
// break和continue在各自的循环中处理这里只记录
debugLog(`🔄 控制语句: ${statement.subtype}`);
@@ -1323,7 +1404,15 @@ export class CppCodeExecutor {
async executeSimpleStatement(content) {
debugLog(`🔍 执行简单语句: ${content}`);
// 声明必须留在当前作用域,不能按普通赋值覆盖外层同名变量。
const declaration = content.match(/^\s*(int|bool|Position|string)\s+(\w+)\s*=\s*(.+);\s*$/);
if (declaration) {
const [, , name, value] = declaration;
this.variables.declare(name, this.evaluateExpression(value));
return;
}
// 检查是否是自定义函数调用(包括带括号的实参,如 f((n + 1));
const customCall = this.parseCustomFunctionCall(content);
if (customCall && this.functions.has(customCall.functionName)) {
@@ -1355,40 +1444,6 @@ export class CppCodeExecutor {
}
}
// 变量声明
if (content.includes('int ') && content.includes('=')) {
const match = content.match(/int\s+(\w+)\s*=\s*(.+?);/);
if (match) {
const [, varName, value] = match;
this.variables.set(varName, this.evaluateExpression(value));
debugLog('声明int变量 ' + varName + ' = ' + this.variables.get(varName));
return;
}
}
// bool类型变量声明
if (content.includes('bool ') && content.includes('=')) {
const match = content.match(/bool\s+(\w+)\s*=\s*(.+?);/);
if (match) {
const [, varName, value] = match;
this.variables.set(varName, this.evaluateExpression(value));
debugLog('声明bool变量 ' + varName + ' = ' + this.variables.get(varName));
return;
}
}
// Position类型变量声明
if (content.includes('Position ') && content.includes('=')) {
const match = content.match(/Position\s+(\w+)\s*=\s*(.+?);/);
if (match) {
const [, varName, value] = match;
const position = this.evaluateExpression(value);
this.variables.set(varName, position);
debugLog(`声明Position变量 ${varName}:`, position);
return;
}
}
// 复合赋值运算符 (+=, -=, *=, /=)
if (content.includes('+=')) {
const match = content.match(/(\w+)\s*\+=\s*(.+?);/);
@@ -1553,41 +1608,46 @@ export class CppCodeExecutor {
let shouldContinue = false;
for (const bodyStatement of forStatement.body) {
if (this.shouldStop()) break;
// 检查是否是break或continue语句
if ((bodyStatement.content && bodyStatement.content.trim() === 'break;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'break')) {
debugLog('🔄 遇到break语句退出for循环');
shouldBreak = true;
break;
}
if ((bodyStatement.content && bodyStatement.content.trim() === 'continue;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'continue')) {
debugLog('🔄 遇到continue语句跳过本次循环剩余部分');
shouldContinue = true;
break;
}
// 使用预先计算好的精确行号,如果没有则使用调整后的行号
let adjustedStatement;
if (bodyStatement.exactLineNumber) {
adjustedStatement = {
...bodyStatement,
lineNumber: bodyStatement.exactLineNumber
};
debugLog(`🎯 使用精确行号: "${bodyStatement.content}" -> 第${bodyStatement.exactLineNumber}`);
} else {
adjustedStatement = this.adjustStatementLineNumber.call(this, bodyStatement);
}
await this.executeStatement(adjustedStatement);
try {
await this.withVariableScope(async () => {
for (const bodyStatement of forStatement.body) {
if (this.shouldStop()) break;
// 检查是否是break或continue语句
if ((bodyStatement.content && bodyStatement.content.trim() === 'break;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'break')) {
debugLog('🔄 遇到break语句退出for循环');
shouldBreak = true;
break;
}
if ((bodyStatement.content && bodyStatement.content.trim() === 'continue;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'continue')) {
debugLog('🔄 遇到continue语句跳过本次循环剩余部分');
shouldContinue = true;
break;
}
// 使用预先计算好的精确行号,如果没有则使用调整后的行号
let adjustedStatement;
if (bodyStatement.exactLineNumber) {
adjustedStatement = {
...bodyStatement,
lineNumber: bodyStatement.exactLineNumber
};
debugLog(`🎯 使用精确行号: "${bodyStatement.content}" -> 第${bodyStatement.exactLineNumber}`);
} else {
adjustedStatement = this.adjustStatementLineNumber.call(this, bodyStatement);
}
await this.executeStatement(adjustedStatement);
}
});
} finally {
this.currentExecutionContext = oldContext;
}
this.currentExecutionContext = oldContext;
if (this.shouldStop()) break;
if (this.shouldStop() || shouldBreak) break;
// 如果遇到continue跳过执行增量直接进入下次循环
if (shouldContinue) {
@@ -1730,43 +1790,48 @@ export class CppCodeExecutor {
let shouldContinue = false;
for (const bodyStatement of whileStatement.body) {
if (this.shouldStop()) break;
// 检查是否是break或continue语句
if ((bodyStatement.content && bodyStatement.content.trim() === 'break;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'break')) {
debugLog('🔄 遇到break语句退出while循环');
shouldBreak = true;
break;
}
if ((bodyStatement.content && bodyStatement.content.trim() === 'continue;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'continue')) {
debugLog('🔄 遇到continue语句跳过本次循环剩余部分');
shouldContinue = true;
break;
}
// 使用预先计算好的精确行号,如果没有则使用调整后的行号
let adjustedStatement;
if (bodyStatement.exactLineNumber) {
adjustedStatement = {
...bodyStatement,
lineNumber: bodyStatement.exactLineNumber
};
debugLog(`🎯 使用精确行号: "${bodyStatement.content}" -> 第${bodyStatement.exactLineNumber}`);
} else {
adjustedStatement = this.adjustStatementLineNumber.call(this, bodyStatement);
}
await this.executeStatement(adjustedStatement);
// 在每个语句执行后重新检查循环条件(特别重要!)
debugLog(`🔍 语句执行后重新检查条件: ${whileStatement.condition} = ${this.evaluateCondition(whileStatement.condition)}`);
try {
await this.withVariableScope(async () => {
for (const bodyStatement of whileStatement.body) {
if (this.shouldStop()) break;
// 检查是否是break或continue语句
if ((bodyStatement.content && bodyStatement.content.trim() === 'break;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'break')) {
debugLog('🔄 遇到break语句退出while循环');
shouldBreak = true;
break;
}
if ((bodyStatement.content && bodyStatement.content.trim() === 'continue;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'continue')) {
debugLog('🔄 遇到continue语句跳过本次循环剩余部分');
shouldContinue = true;
break;
}
// 使用预先计算好的精确行号,如果没有则使用调整后的行号
let adjustedStatement;
if (bodyStatement.exactLineNumber) {
adjustedStatement = {
...bodyStatement,
lineNumber: bodyStatement.exactLineNumber
};
debugLog(`🎯 使用精确行号: "${bodyStatement.content}" -> 第${bodyStatement.exactLineNumber}`);
} else {
adjustedStatement = this.adjustStatementLineNumber.call(this, bodyStatement);
}
await this.executeStatement(adjustedStatement);
// 在每个语句执行后重新检查循环条件(特别重要!)
debugLog(`🔍 语句执行后重新检查条件: ${whileStatement.condition} = ${this.evaluateCondition(whileStatement.condition)}`);
}
});
} finally {
this.currentExecutionContext = oldContext;
}
this.currentExecutionContext = oldContext;
if (shouldContinue) {
continue; // 跳过本次循环的剩余部分,重新开始
}
@@ -1804,6 +1869,11 @@ export class CppCodeExecutor {
throw new Error(`函数调用栈溢出: ${functionName} (可能存在无限递归)`);
}
// 所有实参先在调用者作用域求值,避免前一个形参覆盖后一个实参。
const argumentValues = callArguments.map(arg => this.evaluateExpression(arg));
const callerVariables = this.variables;
this.variables = new CppVariableScope(this.globalVariables);
// 将函数调用信息压入调用栈
this.callStack.push({
functionName: functionName,
@@ -1812,21 +1882,14 @@ export class CppCodeExecutor {
});
try {
// 最小改动:防递归数据污染,只备份会被覆盖的同名参数变量
const savedParams = new Map();
for (let i = 0; i < functionInfo.parameters.length; i++) {
const pName = functionInfo.parameters[i].name;
if (this.variables.has(pName)) savedParams.set(pName, this.variables.get(pName));
}
// 设置函数参数变量
for (let i = 0; i < functionInfo.parameters.length; i++) {
const param = functionInfo.parameters[i];
const argValue = this.evaluateExpression(callArguments[i]);
const argValue = argumentValues[i];
// 类型检查和转换
const convertedValue = this.convertArgumentToType(argValue, param.type);
this.variables.set(param.name, convertedValue);
this.variables.declare(param.name, convertedValue);
console.log(`📋 设置参数: ${param.name} (${param.type}) = ${convertedValue}`);
}
@@ -1856,22 +1919,14 @@ export class CppCodeExecutor {
await this.executeStatement(adjustedStatement);
}
} catch (e) {
if (e.message !== 'RETURN_INTERRUPT') throw e;
}
// 最小改动:仅恢复递归调用前的参数状态,保留全局变量的修改
for (let i = 0; i < functionInfo.parameters.length; i++) {
const pName = functionInfo.parameters[i].name;
if (savedParams.has(pName)) {
this.variables.set(pName, savedParams.get(pName));
} else {
this.variables.delete(pName);
}
if (!(e instanceof CppReturnSignal)) throw e;
return e.value;
}
console.log(`✅ 函数${functionName}执行完成`);
} finally {
this.variables = callerVariables;
// 从调用栈中弹出当前函数
const callInfo = this.callStack.pop();
const executionTime = Date.now() - callInfo.startTime;
@@ -2386,202 +2441,162 @@ export class CppCodeExecutor {
return position;
}
// 字符串表达式
if (expr.includes('"') || expr.includes("'")) {
return expr.replace(/['"]/g, '');
// 整个表达式是字符串字面量时才当字符串;含引号的 API 调用走后面的算术解析
if ((expr.startsWith('"') && expr.endsWith('"')) ||
(expr.startsWith("'") && expr.endsWith("'"))) {
return expr.slice(1, -1);
}
// 使用改进的表达式解析器处理复杂表达式
return this.parseComplexExpression(expr);
}
// 新增:改进的表达式解析器,支持括号嵌套和运算优先级
// 递归下降:一元 +/- ,然后 * / % 优先于 + -
parseComplexExpression(expr) {
try {
// 移除所有空格
expr = expr.replace(/\s+/g, '');
// 处理括号
while (expr.includes('(')) {
const lastOpenParen = expr.lastIndexOf('(');
const closeParen = expr.indexOf(')', lastOpenParen);
if (closeParen === -1) {
debugLog(`⚠️ 括号不匹配: ${expr}`);
return 0;
}
const innerExpr = expr.substring(lastOpenParen + 1, closeParen);
const innerResult = this.parseComplexExpression(innerExpr);
expr = expr.substring(0, lastOpenParen) + innerResult + expr.substring(closeParen + 1);
}
// 处理一元运算符(负号)
expr = this.handleUnaryOperators(expr);
// 按正确的运算优先级解析:
// 加法和减法是最低优先级,因此在此处最先切分。
// 切分后的每一项将交给 parseAdditionSubtraction在其中处理更高级别的乘除法。
return this.parseAdditionSubtraction(expr);
this._exprTokens = this.lexArithmetic(expr);
this._exprPos = 0;
if (this._exprTokens.length === 0) return 0;
const result = this.parseAddExpr();
return typeof result === 'number' && !Number.isNaN(result) ? result : 0;
} catch (error) {
debugLog(`⚠️ 表达式解析错误: ${expr}, 错误: ${error.message}`);
return 0;
}
}
// 处理一元运算符
handleUnaryOperators(expr) {
// 先处理开头的负号
if (expr.startsWith('-')) {
const restExpr = expr.substring(1);
if (/^\d+/.test(restExpr)) {
// 负数
const match = restExpr.match(/^\d+/);
const number = match[0];
const remaining = restExpr.substring(number.length);
return (-parseInt(number)) + remaining;
} else if (/^[a-zA-Z_]\w*/.test(restExpr)) {
// 负变量
const match = restExpr.match(/^[a-zA-Z_]\w*/);
const varName = match[0];
const remaining = restExpr.substring(varName.length);
if (this.variables.has(varName)) {
return (-this.variables.get(varName)) + remaining;
}
}
}
return expr;
}
// 解析乘法、除法和模运算(高优先级)
parseMultiplicationDivision(expr) {
const tokens = this.tokenizeExpression(expr, ['*', '/', '%']);
let result = this.evaluateToken(tokens[0]);
for (let i = 1; i < tokens.length; i += 2) {
const operator = tokens[i];
const operand = this.evaluateToken(tokens[i + 1]);
if (operator === '*') {
result *= operand;
} else if (operator === '/') {
result = Math.floor(result / operand); // C++整数除法
} else if (operator === '%') {
result = result % operand; // 模运算
}
}
return result;
}
// 解析加法和减法(低优先级)
parseAdditionSubtraction(expr) {
const tokens = this.tokenizeExpressionSmart(expr, ['+', '-']);
let result = this.evaluateToken(tokens[0]);
for (let i = 1; i < tokens.length; i += 2) {
const operator = tokens[i];
const operand = this.evaluateToken(tokens[i + 1]);
if (operator === '+') {
result += operand;
} else if (operator === '-') {
result -= operand;
}
}
return result;
}
// 智能分词器,考虑到负号可能是一元运算符
tokenizeExpressionSmart(expr, operators) {
lexArithmetic(expr) {
const tokens = [];
let current = '';
let i = 0;
const isIdChar = (ch) => /[a-zA-Z0-9_.]/.test(ch);
while (i < expr.length) {
const char = expr[i];
if (operators.includes(char)) {
// 检查是否是一元负号
if (char === '-' && (i === 0 || operators.includes(expr[i-1]) || expr[i-1] === '(')) {
// 这是一元负号,继续收集
current += char;
} else {
// 这是二元运算符
if (current.trim()) {
tokens.push(current.trim());
current = '';
}
tokens.push(char);
const ch = expr[i];
if (/\s/.test(ch)) {
i++;
continue;
}
if ('+-*/%()'.includes(ch)) {
tokens.push({ type: ch });
i++;
continue;
}
if (/\d/.test(ch)) {
let num = '';
while (i < expr.length && /\d/.test(expr[i])) {
num += expr[i++];
}
} else {
current += char;
tokens.push({ type: 'num', value: parseInt(num, 10) });
continue;
}
if (/[a-zA-Z_]/.test(ch)) {
let id = '';
while (i < expr.length && isIdChar(expr[i])) {
id += expr[i++];
}
if (expr[i] === '(') {
let depth = 0;
let call = id;
while (i < expr.length) {
const c = expr[i++];
call += c;
if (c === '(') depth++;
else if (c === ')') {
depth--;
if (depth === 0) break;
}
}
tokens.push({ type: 'call', value: call });
} else {
tokens.push({ type: 'id', value: id });
}
continue;
}
i++;
}
if (current.trim()) {
tokens.push(current.trim());
}
return tokens;
}
// 将表达式分解为标记
tokenizeExpression(expr, operators) {
const tokens = [];
let current = '';
for (let i = 0; i < expr.length; i++) {
const char = expr[i];
if (operators.includes(char)) {
if (current.trim()) {
tokens.push(current.trim());
current = '';
}
tokens.push(char);
peekExprToken() {
return this._exprTokens[this._exprPos] || { type: 'eof' };
}
consumeExprToken() {
return this._exprTokens[this._exprPos++] || { type: 'eof' };
}
parseAddExpr() {
let left = this.parseMulExpr();
while (this.peekExprToken().type === '+' || this.peekExprToken().type === '-') {
const op = this.consumeExprToken().type;
const right = this.parseMulExpr();
left = op === '+' ? left + right : left - right;
}
return left;
}
parseMulExpr() {
let left = this.parseUnaryExpr();
while (['*', '/', '%'].includes(this.peekExprToken().type)) {
const op = this.consumeExprToken().type;
const right = this.parseUnaryExpr();
if (op === '*') {
left *= right;
} else if (op === '/') {
left = right === 0 ? 0 : Math.trunc(left / right);
} else {
current += char;
left %= right;
}
}
if (current.trim()) {
tokens.push(current.trim());
}
return tokens;
return left;
}
// 计算单个标记的值
evaluateToken(token) {
token = token.trim();
if (!token) return 0;
// 如果包含乘除法或模运算,交给乘除法解析器处理
if (token.includes('*') || token.includes('/') || token.includes('%')) {
return this.parseMultiplicationDivision(token);
parseUnaryExpr() {
const type = this.peekExprToken().type;
if (type === '-') {
this.consumeExprToken();
return -this.parseUnaryExpr();
}
// 布尔常量
if (type === '+') {
this.consumeExprToken();
return this.parseUnaryExpr();
}
return this.parsePrimaryExpr();
}
parsePrimaryExpr() {
const token = this.peekExprToken();
if (token.type === 'num') {
this.consumeExprToken();
return token.value;
}
if (token.type === 'id') {
this.consumeExprToken();
return this.evaluateAtom(token.value);
}
if (token.type === 'call') {
this.consumeExprToken();
return this.evaluateAtom(token.value);
}
if (token.type === '(') {
this.consumeExprToken();
const value = this.parseAddExpr();
if (this.peekExprToken().type === ')') {
this.consumeExprToken();
}
return value;
}
debugLog(`⚠️ 无法解析标记: ${token.type}`);
return 0;
}
evaluateAtom(token) {
if (token === 'true') return 1;
if (token === 'false') return 0;
// 数字
if (/^-?\d+$/.test(token)) {
return parseInt(token);
}
// 变量查找
if (this.variables.has(token)) {
return this.variables.get(token);
}
// 对象属性访问 (如 pos.x, pos.y, pos.z)
if (token.includes('.')) {
const [objName, propName] = token.split('.');
if (this.variables.has(objName)) {
@@ -2591,8 +2606,7 @@ export class CppCodeExecutor {
}
}
}
// 游戏API调用
if (token.includes('player_data(')) {
const match = token.match(/player_data\s*\(\s*"(.+?)"\s*\)/);
if (match) return window.cppGameAPI.player_data(match[1]);
@@ -2604,7 +2618,7 @@ export class CppCodeExecutor {
if (token.includes('vehicle_position()')) {
return window.cppGameAPI.vehicle_position();
}
debugLog(`⚠️ 无法解析标记: ${token}`);
return 0;
}
@@ -2738,7 +2752,7 @@ export const createCppExecutor = () => {
const executor = new CppCodeExecutor(code, window.setHighlightedLine);
this.currentExecutor = executor;
try {
await executor.run();
return await executor.run();
} finally {
this.currentExecutor = null;
}
@@ -3761,4 +3775,4 @@ export const testCompleteIfElseIfElse = async () => {
}
return result;
};
};

View File

@@ -32,8 +32,8 @@ const StageWrapperCppClang = () => {
const [wasmModule, setWasmModule] = useState(null);
const [syntaxErrorModalVisible, setSyntaxErrorModalVisible] = useState(false);
const [syntaxErrors, setSyntaxErrors] = useState([]);
const [apiDrawerOpen, setApiDrawerOpen] = useState(false);
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
const [apiSelectedCategory, setApiSelectedCategory] = useState('player');
const [isApiPanelReady, setIsApiPanelReady] = useState(() => window.iscreate === 1);
const [overlayActive, setOverlayActive] = useState(false);
const MAX_CODE_LINES = 500;
@@ -41,19 +41,24 @@ const StageWrapperCppClang = () => {
const editorRef = useRef(null);
const lastValidCppCodeRef = useRef(cppCode);
// 计算CodeMirror宽度的函数
const getCodeMirrorWidth = () => {
if (!apiDrawerOpen) return '54vw';
// 根据屏幕宽度调整
if (windowWidth <= 900) {
return '19vw'; // 54vw - 35vw = 19vw
} else if (windowWidth <= 1200) {
return '29vw'; // 54vw - 25vw = 29vw
} else {
return '34vw'; // 54vw - 20vw = 34vw
}
};
// 游戏引擎加载完成前API 面板显示加载态并屏蔽点击5 秒兜底自动解除)
useEffect(() => {
if (isApiPanelReady) return undefined;
const timer = setInterval(() => {
if (window.iscreate === 1) {
setIsApiPanelReady(true);
clearInterval(timer);
}
}, 300);
const fallback = setTimeout(() => {
setIsApiPanelReady(true);
clearInterval(timer);
}, 5000);
return () => {
clearInterval(timer);
clearTimeout(fallback);
};
}, [isApiPanelReady]);
// API参考数据
const apiReference = {
@@ -238,17 +243,6 @@ const StageWrapperCppClang = () => {
useEffect(() => {
initializeEmscripten();
// 添加窗口大小变化监听器
const handleResize = () => {
setWindowWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
// 初始化基于Emscripten的C++执行环境
@@ -660,7 +654,7 @@ const StageWrapperCppClang = () => {
if (window.unityInstance) {
window.unityInstance.SendMessage("UIMain", "SetText", window.cmd_print_text);
}
setCompilationOutput(prev => prev + textStr + "\n");
},
@@ -1118,27 +1112,18 @@ const StageWrapperCppClang = () => {
);
};
// API参考抽屉组件
// API参考面板(与 Python 一样常驻在编辑区左侧)
const ApiDrawer = () => {
const [selectedCategory, setSelectedCategory] = useState('syntax');
const selectedCategory = apiSelectedCategory;
const setSelectedCategory = setApiSelectedCategory;
return (
<div className={`${styles.apiDrawer} ${apiDrawerOpen ? styles.apiDrawerOpen : ''}`}>
{/* 抽屉头部 */}
<div className={styles.apiDrawer}>
<div className={styles.apiDrawerHeader}>
<h3>🔧 API参考</h3>
<button
className={styles.apiDrawerClose}
onClick={() => setApiDrawerOpen(false)}
title="关闭API参考"
>
×
</button>
<h3>🔧 C++ API参考</h3>
</div>
{/* 抽屉内容 */}
<div className={styles.apiDrawerContent}>
{/* 分类导航 */}
<div className={styles.apiDrawerCategories}>
{Object.entries(apiReference).map(([key, category]) => (
<button
@@ -1152,7 +1137,6 @@ const StageWrapperCppClang = () => {
))}
</div>
{/* API列表 */}
<div className={styles.apiDrawerList}>
{apiReference[selectedCategory]?.apis.map((api, index) => (
<div key={index} className={styles.apiDrawerItem}>
@@ -1176,6 +1160,13 @@ const StageWrapperCppClang = () => {
))}
</div>
</div>
<div
className={`${styles.apiDrawerLoading}${isApiPanelReady ? ` ${styles.apiDrawerLoadingHidden}` : ''}`}
aria-hidden={isApiPanelReady}
>
<div className={styles.apiDrawerLoadingSpinner}></div>
<div className={styles.apiDrawerLoadingText}>正在加载中完成后即可插入代码</div>
</div>
</div>
);
};
@@ -1196,6 +1187,12 @@ const StageWrapperCppClang = () => {
// 移除snippet标记转换为普通文本
insertCode = code.replace(/#{([^}]*)}/g, '$1');
}
if (cursor > 0 && view.state.doc.sliceString(cursor - 1, cursor) !== '\n') {
insertCode = '\n' + insertCode;
}
if (!insertCode.endsWith('\n')) {
insertCode += '\n';
}
// 插入代码
view.dispatch({
@@ -1209,10 +1206,6 @@ const StageWrapperCppClang = () => {
}
});
// 关闭API参考窗口
setApiDrawerOpen(false);
// 聚焦编辑器
view.focus();
}
}
@@ -1223,170 +1216,141 @@ const StageWrapperCppClang = () => {
<ErrorModal />
<CompilationModal />
<SyntaxErrorModal />
<div onWheel={(e) => e.stopPropagation()} style={{
height: '100vh',
width: '100%',
position: 'relative',
overflow: 'hidden'
}}>
<div style={{
position: 'relative',
height: '100%',
width: '100%'
}}>
{/* API抽屉标签 */}
<div
className={`${styles.apiDrawerTab} ${apiDrawerOpen ? styles.apiDrawerTabOpen : ''}`}
onClick={() => setApiDrawerOpen(!apiDrawerOpen)}
title={apiDrawerOpen ? "关闭API参考" : "打开API参考"}
>
<div className={styles.apiDrawerTabIcon}>
{apiDrawerOpen ? '▶' : '◀'}
</div>
<div className={styles.apiDrawerTabText}>
{apiDrawerOpen ? '关闭' : 'API'}
</div>
</div>
{/* API抽屉 */}
<ApiDrawer />
{/* CodeMirror编辑器 */}
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
<CodeMirror
ref={editorRef}
<div className={styles.editorWithApi}>
{ApiDrawer()}
<div
className={styles.editorPane}
onWheel={(e) => e.stopPropagation()}
>
<CodeMirror
ref={editorRef}
style={{
width: '100%',
height: '100%',
position: 'relative',
backgroundColor: '#010101',
overflow: 'auto',
fontSize: '20px'
}}
value={cppCode}
theme={abcdef}
extensions={[
cpp(),
createCppCompletions(),
keymap.of(completionKeymap),
keymap.of(snippetKeymap),
classname({
add: (lineNumber) => {
if (lineNumber === highlightedLine) {
return 'highlighted-line';
}
return null;
}
}),
EditorView.theme({
'&': {
height: '100%',
width: '100%'
},
'.cm-editor': {
height: '100%',
width: '100%'
},
'.cm-scroller': {
height: '100%',
overflow: 'auto'
},
'.cm-content': {
minHeight: '100%',
padding: '10px'
},
'.highlighted-line': {
backgroundColor: '#ffeb3b80',
borderLeft: '4px solid #ffeb3b',
borderRight: '2px solid #ffeb3b',
boxShadow: '0 0 10px rgba(255, 235, 59, 0.3)',
animation: 'pulse 1s ease-in-out'
},
'@keyframes pulse': {
'0%': { backgroundColor: '#ffeb3b80' },
'50%': { backgroundColor: '#ffeb3ba0' },
'100%': { backgroundColor: '#ffeb3b80' }
},
fontSize: "20px"
})
]}
basicSetup={{
lineNumbers: true,
highlightActiveLine: true,
highlightSelectionMatches: true,
searchKeymap: true,
autocompletion: true
}}
onChange={(value) => {
const lines = value.split(/\r\n|\r|\n/);
if (lines.length > MAX_CODE_LINES) {
setErrorMessage(`代码不能超过${MAX_CODE_LINES}行!当前已有${lines.length}行,请删除多余代码后再编辑。`);
setErrorModalVisible(true);
if (editorRef.current && editorRef.current.view) {
const view = editorRef.current.view;
view.dispatch({
changes: {
from: 0,
to: view.state.doc.length,
insert: lastValidCppCodeRef.current
}
});
}
return;
}
lastValidCppCodeRef.current = value;
setCppCode(value);
updateBlockCount(value);
}}
/>
{overlayActive && (
<div
style={{
width: getCodeMirrorWidth(),
height: '100%',
position: 'relative',
position: 'absolute',
top: 0,
left: 0,
backgroundColor: '#010101',
fontSize: '20px',
transition: 'width 0.3s ease'
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.25)',
cursor: 'not-allowed',
zIndex: 10,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
userSelect: 'none'
}}
value={cppCode}
theme={abcdef}
extensions={[
cpp(),
createCppCompletions(),
keymap.of(completionKeymap),
keymap.of(snippetKeymap),
classname({
add: (lineNumber) => {
if (lineNumber === highlightedLine) {
return 'highlighted-line';
}
return null;
}
}),
EditorView.theme({
'&': {
height: '100%',
width: '100%'
},
'.cm-editor': {
height: '100%',
width: '100%'
},
'.cm-scroller': {
height: '100%',
overflow: 'auto'
},
'.cm-content': {
minHeight: '100%',
padding: '10px'
},
'.highlighted-line': {
backgroundColor: '#ffeb3b80',
borderLeft: '4px solid #ffeb3b',
borderRight: '2px solid #ffeb3b',
boxShadow: '0 0 10px rgba(255, 235, 59, 0.3)',
animation: 'pulse 1s ease-in-out'
},
'@keyframes pulse': {
'0%': { backgroundColor: '#ffeb3b80' },
'50%': { backgroundColor: '#ffeb3ba0' },
'100%': { backgroundColor: '#ffeb3b80' }
},
fontSize: "20px"
})
]}
basicSetup={{
lineNumbers: true,
highlightActiveLine: true,
highlightSelectionMatches: true,
searchKeymap: true,
autocompletion: true
}}
onChange={(value) => {
const lines = value.split(/\r\n|\r|\n/);
if (lines.length > MAX_CODE_LINES) {
setErrorMessage(`代码不能超过${MAX_CODE_LINES}行!当前已有${lines.length}行,请删除多余代码后再编辑。`);
setErrorModalVisible(true);
// 使用 CodeMirror dispatch 直接回滚编辑器内容
if (editorRef.current && editorRef.current.view) {
const view = editorRef.current.view;
view.dispatch({
changes: {
from: 0,
to: view.state.doc.length,
insert: lastValidCppCodeRef.current
}
});
}
return;
}
lastValidCppCodeRef.current = value;
setCppCode(value);
// 计算代码行数并更新window.blockcount
updateBlockCount(value);
}}
/>
{overlayActive && (
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.25)',
cursor: 'not-allowed',
zIndex: 10,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
userSelect: 'none'
}}
onWheel={(e) => e.preventDefault()}
onTouchMove={(e) => e.preventDefault()}
onClick={(e) => e.preventDefault()}
onMouseDown={(e) => e.preventDefault()}
>
<div style={{
color: '#fff',
fontSize: '28px',
fontWeight: 'bold',
textShadow: '0 2px 8px rgba(0,0,0,0.6)',
letterSpacing: '4px',
animation: 'blink 1.5s ease-in-out infinite'
}}>
执行中...
</div>
<style>{`
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
`}</style>
onWheel={(e) => e.preventDefault()}
onTouchMove={(e) => e.preventDefault()}
onClick={(e) => e.preventDefault()}
onMouseDown={(e) => e.preventDefault()}
>
<div style={{
color: '#fff',
fontSize: '28px',
fontWeight: 'bold',
textShadow: '0 2px 8px rgba(0,0,0,0.6)',
letterSpacing: '4px',
animation: 'blink 1.5s ease-in-out infinite'
}}>
执行中...
</div>
)}
</div>
<style>{`
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
`}</style>
</div>
)}
</div>
</div>
</>
);
};
export default StageWrapperCppClang;
export default StageWrapperCppClang;

View File

@@ -378,6 +378,23 @@
color: #f44336;
}
/* API参考 + 编辑器并排:参考在左,编辑器靠右(与 Python 一致) */
.editorWithApi {
display: flex;
flex-direction: row;
height: 100%;
width: 54vw;
overflow: hidden;
}
.editorPane {
flex: 1;
min-width: 0;
height: 100%;
position: relative;
overflow: auto;
}
/* API抽屉标签样式 */
.apiDrawerTab {
position: fixed;
@@ -486,19 +503,15 @@
}
}
/* API抽屉样式 */
/* API面板样式 */
.apiDrawer {
position: fixed;
top: 0;
right: -20vw;
/* 初始隐藏在右侧 */
position: relative;
flex-shrink: 0;
width: 20vw;
height: 100vh;
height: 100%;
background: linear-gradient(135deg, #1e1e1e 0%, #2d2d2d 100%);
border-left: 2px solid #444;
box-shadow: -5px 0 20px rgba(0, 0, 0, 0.3);
z-index: 1000;
transition: right 0.3s ease;
border-right: 2px solid #444;
box-shadow: 2px 0 12px rgba(0, 0, 0, 0.2);
overflow: hidden;
display: flex;
flex-direction: column;
@@ -518,6 +531,10 @@
cursor: not-allowed;
}
.apiDrawerLoadingHidden {
display: none;
}
.apiDrawerLoadingSpinner {
width: 38px;
height: 38px;
@@ -525,6 +542,7 @@
border-top-color: #ba68c8;
border-radius: 50%;
animation: apiDrawerLoadingSpin 0.9s linear infinite;
will-change: transform;
}
.apiDrawerLoadingText {
@@ -536,6 +554,9 @@
}
@keyframes apiDrawerLoadingSpin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}

View File

@@ -144,7 +144,7 @@ const CurrentLevelChoose = ({ onCancel }) => {
// 清除所有代码
window.loadPythonProject(`` + `\n`.repeat(50));
}
const realLvId_tmp = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber);
const realLvId_tmp = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode);
const realLvId = realLvId_tmp + cellIndex
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
CONFIG.checkGameStyle(realLvId);

View File

@@ -11,10 +11,11 @@ import RankBadgeGenerator from '../../playground/rank-badge-generator.js';
// const friendToggle = new FriendToggle();
window.RankBadgeGenerator = RankBadgeGenerator;
window.CONFIG = CONFIG;
import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from '../../playground/devtool-control.js';
import { getUnityPlayerConfig, loadUnityLoaderScript } from '../../lib/unity-build-loader.js';
import { debug } from 'scratch-vm/src/util/log.js';
import GameLoadingOverlay from '../game-loading-overlay/game-loading-overlay.jsx';
import useGameLoadProgress from '../game-loading-overlay/use-game-load-progress.js';
const MAX_LEVELS = 500; // 最大关卡数据数量
const STORAGE_KEY = 'player_levels_data'; // 使用命名空间,防止键名冲突
@@ -48,6 +49,16 @@ const UnityComponent = function (props) {
const [getBlockNum, setBlockNum] = useState(0);
const [testDal, setTestDal] = useState(-1);
const canvasRef = useRef(null);
const {
progress: gameLoadProgress,
visible: gameLoadVisible,
onUnityProgress,
onUnityInstanceCreated
} = useGameLoadProgress();
const onUnityProgressRef = useRef(onUnityProgress);
const onUnityInstanceCreatedRef = useRef(onUnityInstanceCreated);
onUnityProgressRef.current = onUnityProgress;
onUnityInstanceCreatedRef.current = onUnityInstanceCreated;
const [isGameOverVisible, setIsGameOverVisible] = useState(false);
const [isGameOkVisible, setIsGameOkVisible] = useState(false);
const [isLockNextButton, setIsLockNextButton] = useState(true);
@@ -549,7 +560,7 @@ const UnityComponent = function (props) {
useEffect(() => {
if (CONFIG.disabledev) {
disableDevtool({
initDisableDevtool({
ondevtoolopen: () => {
// 临时禁用 onbeforeunload 事件监听,以防止弹出确认离开弹窗
@@ -1259,8 +1270,9 @@ const UnityComponent = function (props) {
window.iscreate = 0;
const boot = () => {
createUnityInstance(canvas, config, (progress) => {
// console.log(`加载进度: ${progress * 100}%`);
onUnityProgressRef.current(progress);
}).then((unityInstance) => {
onUnityInstanceCreatedRef.current();
window.unityInstance = unityInstance;
window.waitForUnityStepFinish = waitForUnityStepFinish;
window.waitForUnityVehicleStepFinish = waitForUnityVehicleStepFinish;
@@ -1324,6 +1336,7 @@ const UnityComponent = function (props) {
}}
>
<canvas id="unity-canvas" ref={canvasRef} width="960" height="600" style={{ width: '100%', height: '100%' }}></canvas>
<GameLoadingOverlay visible={gameLoadVisible} progress={gameLoadProgress} />
@@ -1878,4 +1891,4 @@ UnityComponent.propTypes = {
vm: PropTypes.instanceOf(VM).isRequired
};
export default UnityComponent;
export default UnityComponent;

View File

@@ -183,6 +183,10 @@
cursor: not-allowed;
}
.apiDrawerLoadingHidden {
display: none;
}
.apiDrawerLoadingSpinner {
width: 38px;
height: 38px;
@@ -190,6 +194,7 @@
border-top-color: #ba68c8;
border-radius: 50%;
animation: apiDrawerLoadingSpin 0.9s linear infinite;
will-change: transform;
}
.apiDrawerLoadingText {
@@ -201,6 +206,9 @@
}
@keyframes apiDrawerLoadingSpin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}

View File

@@ -1287,12 +1287,13 @@ sys.settrace(trace_func)`;
))}
</div>
</div>
{!isApiPanelReady && (
<div className={styles.apiDrawerLoading}>
<div className={styles.apiDrawerLoadingSpinner}></div>
<div className={styles.apiDrawerLoadingText}>游戏引擎加载中完成后即可插入代码</div>
</div>
)}
<div
className={`${styles.apiDrawerLoading}${isApiPanelReady ? ` ${styles.apiDrawerLoadingHidden}` : ''}`}
aria-hidden={isApiPanelReady}
>
<div className={styles.apiDrawerLoadingSpinner}></div>
<div className={styles.apiDrawerLoadingText}>正在加载中完成后即可插入代码</div>
</div>
</div>
);
};
@@ -1305,12 +1306,22 @@ sys.settrace(trace_func)`;
const { state } = view;
const { selection } = state;
const cursor = selection.main.head;
const withSafeNewlines = (text) => {
let next = text;
if (cursor > 0 && view.state.doc.sliceString(cursor - 1, cursor) !== '\n') {
next = '\n' + next;
}
if (!next.endsWith('\n')) {
next += '\n';
}
return next;
};
// 检查代码是否包含snippet占位符
if (code.includes('#{')) {
// 使用snippet功能插入代码
try {
const snippetTemplate = snippet(code);
const snippetTemplate = snippet(withSafeNewlines(code));
const completion = snippetTemplate(view, {
from: cursor,
to: cursor
@@ -1320,7 +1331,7 @@ sys.settrace(trace_func)`;
view.dispatch(completion);
} else {
// 如果snippet创建失败使用简化的插入方式
const cleanCode = code.replace(/#{([^}]*)}/g, '$1');
const cleanCode = withSafeNewlines(code.replace(/#{([^}]*)}/g, '$1'));
view.dispatch({
changes: {
from: cursor,
@@ -1332,7 +1343,7 @@ sys.settrace(trace_func)`;
} catch (error) {
console.warn('Snippet insertion failed, falling back to plain text:', error);
// 回退到普通插入移除snippet标记
const cleanCode = code.replace(/#{([^}]*)}/g, '$1');
const cleanCode = withSafeNewlines(code.replace(/#{([^}]*)}/g, '$1'));
view.dispatch({
changes: {
from: cursor,
@@ -1342,15 +1353,15 @@ sys.settrace(trace_func)`;
});
}
} else {
// 普通代码插入
const insertText = withSafeNewlines(code);
view.dispatch({
changes: {
from: cursor,
to: cursor,
insert: code
insert: insertText
},
selection: {
anchor: cursor + code.length
anchor: cursor + insertText.length
}
});
}
@@ -1365,7 +1376,7 @@ sys.settrace(trace_func)`;
<>
<ErrorModal />
<div className={styles.editorWithApi}>
<ApiDrawer />
{ApiDrawer()}
<div
className={styles.editorPane}
onWheel={(e) => e.stopPropagation()}

View File

@@ -11,10 +11,11 @@ import RankBadgeGenerator from '../../playground/rank-badge-generator.js';
// const friendToggle = new FriendToggle();
window.RankBadgeGenerator = RankBadgeGenerator;
window.CONFIG = CONFIG;
import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from '../../playground/devtool-control.js';
import { getUnityPlayerConfig, loadUnityLoaderScript } from '../../lib/unity-build-loader.js';
import { debug } from 'scratch-vm/src/util/log.js';
import GameLoadingOverlay from '../game-loading-overlay/game-loading-overlay.jsx';
import useGameLoadProgress from '../game-loading-overlay/use-game-load-progress.js';
const MAX_LEVELS = 500; // 最大关卡数据数量
const STORAGE_KEY = 'player_levels_data'; // 使用命名空间,防止键名冲突
@@ -248,6 +249,16 @@ const UnityComponent = function (props) {
const [getBlockNum, setBlockNum] = useState(0);
const [testDal, setTestDal] = useState(-1);
const canvasRef = useRef(null);
const {
progress: gameLoadProgress,
visible: gameLoadVisible,
onUnityProgress,
onUnityInstanceCreated
} = useGameLoadProgress();
const onUnityProgressRef = useRef(onUnityProgress);
const onUnityInstanceCreatedRef = useRef(onUnityInstanceCreated);
onUnityProgressRef.current = onUnityProgress;
onUnityInstanceCreatedRef.current = onUnityInstanceCreated;
const [isGameOverVisible, setIsGameOverVisible] = useState(false);
const [isGameOkVisible, setIsGameOkVisible] = useState(false);
const [isLockNextButton, setIsLockNextButton] = useState(true);
@@ -743,7 +754,7 @@ const UnityComponent = function (props) {
useEffect(() => {
if (CONFIG.disabledev) {
disableDevtool({
initDisableDevtool({
ondevtoolopen: () => {
// 临时禁用 onbeforeunload 事件监听,以防止弹出确认离开弹窗
@@ -1522,8 +1533,9 @@ const UnityComponent = function (props) {
window.iscreate = 0;
const boot = () => {
createUnityInstance(canvas, config, (progress) => {
// console.log(`加载进度: ${progress * 100}%`);
onUnityProgressRef.current(progress);
}).then((unityInstance) => {
onUnityInstanceCreatedRef.current();
window.unityInstance = unityInstance;
window.waitForUnityStepFinish = waitForUnityStepFinish;
window.waitForUnityVehicleStepFinish = waitForUnityVehicleStepFinish;
@@ -1587,6 +1599,7 @@ const UnityComponent = function (props) {
}}
>
<canvas id="unity-canvas" ref={canvasRef} width="960" height="600" style={{ width: '100%', height: '100%' }}></canvas>
<GameLoadingOverlay visible={gameLoadVisible} progress={gameLoadProgress} />
<section
className="u-align-center u-black u-clearfix u-container-align-center u-container-style u-dialog-block u-opacity u-opacity-50 u-valign-middle-md u-valign-middle-xl u-dialog-section-8"
@@ -2148,4 +2161,4 @@ UnityComponent.propTypes = {
vm: PropTypes.instanceOf(VM).isRequired
};
export default UnityComponent;
export default UnityComponent;

View File

@@ -1325,7 +1325,7 @@
<h2 class="cta-title">灵丌编程001CODE城市合伙人招募中</h2>
</div>
<div class="cta-grid">
<a href="mailto:contact@001Code.com" class="contact-card">
<a href="mailto:support@shxshf.com" class="contact-card">
<div class="icon-box">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="4" width="20" height="16" rx="2"></rect>
@@ -1334,11 +1334,11 @@
</div>
<div class="card-info">
<span class="card-label">邮箱地址</span>
<span class="card-value">wangshenxing@shxshf.com</span>
<span class="card-value">support@shxshf.com</span>
</div>
</a>
<button class="contact-card" onclick="navigator.clipboard.writeText('17380126742')">
<button class="contact-card" onclick="navigator.clipboard.writeText('400-659-1659')">
<div class="icon-box">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@@ -1348,7 +1348,7 @@
</div>
<div class="card-info">
<span class="card-label">咨询热线</span>
<span class="card-value">135-2066-2111</span>
<span class="card-value">400-659-1659</span>
</div>
</button>
</div>
@@ -1358,7 +1358,7 @@
<footer class="footer" aria-label="页脚">
<div class="footer-line"></div>
<div class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">沪ICP备2026042325号</a>
<!-- 公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a> -->
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM
<span class="qr" tabindex="0">
<img class="qr-icon" src="../images/qrcode_gery.svg" alt="公众号小图标" />

View File

@@ -613,18 +613,18 @@
<div class="section-card" id="ttsVoiceCard">
<div class="section-title" style="margin-bottom:4px;">选择音色</div>
<div class="section-sub" style="margin-bottom:10px;font-size:12px;">想用自己的声音?先在下方「自定义音色」录一段 5~10 秒</div>
<div class="section-sub" style="margin-bottom:10px;font-size:12px;">选「我的声音」前,请先在下方录制或上传 5~10 秒参考人声</div>
<div class="voice-grid" id="ttsVoiceGrid"></div>
</div>
<div class="section-card" id="ttsMineVoiceCard">
<div class="section-title">自定义音色(录一段你的声音)</div>
<div class="section-sub">录一段 10 秒以内的话AI 就能模仿你的声音说话啦!建议 5~10 秒,清晰无背景音(必填)</div>
<div class="section-sub">仅选择「我的声音」时需要。录一段 5~10 秒清晰人声AI 就能模仿你说话</div>
<div class="record-box">
<div class="record-box__icon"><img src="../images/aigc/icon-voice-mic.png" alt="" /></div>
<div class="record-box__body">
<div class="record-box__title">录一段你的声音</div>
<div class="record-box__sub">请录制 5~10 秒清晰人声,完成后即可选中「我的声音」</div>
<div class="record-box__sub">完成后即可选中「我的声音」;选其他音色无需录音</div>
<div class="record-box__status" id="recordStatus"></div>
</div>
<div class="record-box__actions">
@@ -1331,19 +1331,21 @@
if (!selectedTtsVoiceId || !voiceOpt) {
return fail('请选择音色', document.getElementById('ttsVoiceCard'));
}
// 无论选哪种音色,都必须录制/上传参考音频
if (!voiceBlob) {
return fail('请先录制或上传自定义音色', mineCard);
}
if (!isVoiceDurationValid(voiceDurationSec)) {
return fail(
'自定义音色需 ' + MIN_VOICE_SECONDS + '~' + MAX_VOICE_SECONDS + ' 秒,当前约 ' +
Math.max(0, Math.floor(Number(voiceDurationSec) || 0)) + ' 秒,请重新录制或上传',
mineCard
);
}
if (voiceBlob.size > 10 * 1024 * 1024) {
return fail('参考音频不能超过 10MB', mineCard);
// 只有选择「我的声音」时才必须录制/上传参考音频
if (voiceOpt.mine) {
if (!voiceBlob) {
return fail('请先录制或上传自定义音色', mineCard);
}
if (!isVoiceDurationValid(voiceDurationSec)) {
return fail(
'自定义音色需 ' + MIN_VOICE_SECONDS + '~' + MAX_VOICE_SECONDS + ' 秒,当前约 ' +
Math.max(0, Math.floor(Number(voiceDurationSec) || 0)) + ' 秒,请重新录制或上传',
mineCard
);
}
if (voiceBlob.size > 10 * 1024 * 1024) {
return fail('参考音频不能超过 10MB', mineCard);
}
}
if (!speedEl || !isFinite(speedIdx) || speedIdx < 0 || speedIdx > 7 || !speedLabels[speedIdx]) {
return fail('请设置语速', document.getElementById('ttsSliderCard') || speedEl);
@@ -2009,12 +2011,12 @@
return voiceId;
}
async function generateTtsAudio(text, voiceId) {
if (!voiceId) throw new Error('缺少复刻音色,请先完成 voice-clone');
async function generateTtsAudio(text, voiceId, modelName) {
if (!voiceId) throw new Error('缺少音色');
var payload = {
text: text,
voice: voiceId,
model_name: TTS_MODEL,
model_name: modelName || TTS_MODEL,
instruction: buildTtsInstruction()
};
@@ -2136,14 +2138,21 @@
setVoicePreviewLoading('正在准备合成…');
try {
// 无论选哪种音色:先 voice-clone再 tts/generate
btn.textContent = '复刻中…';
setVoicePreviewLoading('正在上传参考音频并复刻音色…');
var voiceId = await cloneVoiceFromFile(voiceBlob, voiceFileName);
if (!voiceId) throw new Error('音色复刻失败,未返回 voice_id');
var voiceId;
var modelName = TTS_MODEL;
if (voiceOpt.mine) {
btn.textContent = '复刻中…';
setVoicePreviewLoading('正在上传参考音频并复刻音色…');
voiceId = await cloneVoiceFromFile(voiceBlob, voiceFileName);
if (!voiceId) throw new Error('音色复刻失败,未返回 voice_id');
} else {
// 预设音色:与「声音变声」同一套 model / voice
voiceId = CHANGE_VOICE;
modelName = CHANGE_MODEL;
}
btn.textContent = '合成中…';
setVoicePreviewLoading('正在用「' + voiceOpt.name + '」合成语音…');
var gen = await generateTtsAudio(text, voiceId);
var gen = await generateTtsAudio(text, voiceId, modelName);
var meta = '语速 ' + speedLabels[params.speedIdx] +
' · 语调 ' + pitchLabels[params.pitchIdx] +
' · 情绪 ' + emotionLabels[params.emotionIdx];

View File

@@ -1302,6 +1302,12 @@
<span class="nav__pillAIText">灵丌AI</span>
</span>
</div>
<div class="nav-link-group">
<a class="nav-link" href="/competition_list.html">赛事详情</a>
</div>
<div class="nav-link-group">
<a class="nav-link" href="/aboutus.html">关于我们</a>
</div>
</div>
<div class="navbar-right">
<a class="navbar-link" id="open-personal-info" href="javascript:void(0);">个人信息</a>
@@ -1531,7 +1537,7 @@
<footer class="footer" aria-label="页脚">
<div class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">沪ICP备2026042325号</a>
<!-- 公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a> -->
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM
<span class="qr" tabindex="0">
<img class="qr-icon" src="../images/qrcode_gery.svg" alt="公众号小图标" />
@@ -1792,6 +1798,17 @@
return !!localStorage.getItem('access_token');
}
function getExplorerRole() {
var user = window.aiExplorerUserInfo || {};
return String(user.role || '').trim();
}
function isStudentRole() {
var role = getExplorerRole();
var lower = role.toLowerCase();
return lower === 'student' || role === '学生';
}
function authHeaders() {
var headers = { 'Content-Type': 'application/json' };
var token = localStorage.getItem('access_token');
@@ -1894,13 +1911,16 @@
var goBtnHtml = showGoBtn
? '<button type="button" class="classes-modal__go-btn" data-class-id="' + id + '">' + escapeHtml(actionLabel) + '</button>'
: '';
var countHtml = isStudentRole()
? ''
: '<span class="classes-modal__count">' + count + ' 人</span>';
return '<div class="classes-modal__item" data-class-id="' + id + '">' +
'<div class="classes-modal__main">' +
'<div class="classes-modal__name">' + name + '</div>' +
'<div class="classes-modal__meta">年级:' + grade + '</div>' +
'</div>' +
'<div class="classes-modal__aside">' +
'<span class="classes-modal__count">' + count + ' 人</span>' +
countHtml +
goBtnHtml +
'</div>' +
'</div>';

View File

@@ -88,7 +88,7 @@
<footer>
<div class="footer-line"></div>
<p class="footer-text">ICP备案号<a style="color:#478ac9 ;" href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">沪ICP备2026042325号</a>
<!-- 公安备案号:<a style="color:#478ac9 ;" href="https://beian.mps.gov.cn/" target="_blank">川公网安备51011202000980号</a> -->
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM</p>
</footer>
</div>

View File

@@ -1,6 +1,6 @@
// 导入配置信息
import CONFIG from './config.js';
import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from './devtool-control.js';
// 获取 URL 参数的函数
@@ -166,7 +166,7 @@ function showPlatformGuide() {
document.addEventListener('DOMContentLoaded', function () {
if (CONFIG.disabledev) {
disableDevtool({
initDisableDevtool({
ondevtoolopen: () => {
// window.location.href = '/index.html';
// window.location.href = localStorage.getItem('returnUrl');
@@ -3449,4 +3449,4 @@ function getLanguageName(t) {
}
// 导出函数,以便其他模块可以使用
export { getUrlParameter, fetchCompetitionInfo };
export { getUrlParameter, fetchCompetitionInfo };

View File

@@ -917,7 +917,7 @@
<footer class="footer" aria-label="页脚">
<div class="footer-line"></div>
<div class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">沪ICP备2026042325号</a>
<!-- 公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a> -->
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM
<span class="qr" tabindex="0">
<img class="qr-icon" src="../images/qrcode_gery.svg" alt="公众号小图标" />

View File

@@ -160,7 +160,7 @@
<footer>
<div class="footer-line"></div>
<p class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">沪ICP备2026042325号</a>
<!-- 公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a> -->
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM</p>
</footer>
</div>

View File

@@ -1,11 +1,11 @@
import CONFIG from './config.js';
import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from './devtool-control.js';
let gamelvdata = {};
document.addEventListener('DOMContentLoaded', function () {
if (CONFIG.disabledev) {
disableDevtool({
initDisableDevtool({
ondevtoolopen: () => {
},
interval: 500,

View File

@@ -1,12 +1,12 @@
// config.js
// const CONFIG = {
// DataServerBaseUrl: "https://test-service.001code.cn", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test-service.001code.cn", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test-service.001code.cn",
// // DataServerBaseUrl: "https://test-service.001code.cn",
// // DataServerBaseUrl: "https://test-service.001code.cn",
// // DataServerBaseUrl: "https://test-service.001code.cn",
// DataServerBaseUrl: "https://service.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://service.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://service.001code.com",
// // DataServerBaseUrl: "https://service.001code.com",
// // DataServerBaseUrl: "https://service.001code.com",
// // DataServerBaseUrl: "https://service.001code.com",
// version : "2025010301",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
@@ -14,7 +14,7 @@
// // 可以根据需要添加其他配置
// disabledev: false,
// disabledev: true,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn :true, //常规关起锁
@@ -78,12 +78,12 @@
// const CONFIG = {
// // DataServerBaseUrl: "https://test-service.001code.cn", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test-service.001code.cn", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test-service.001code.cn",
// DataServerBaseUrl: "https://test-service.001code.cn",
// // DataServerBaseUrl: "https://test-service.001code.cn",
// // DataServerBaseUrl: "https://test-service.001code.cn",
// // DataServerBaseUrl: "https://service.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://service.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://service.001code.com",
// DataServerBaseUrl: "https://service.001code.com",
// // DataServerBaseUrl: "https://service.001code.com",
// // DataServerBaseUrl: "https://service.001code.com",
// version : "2025011001",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
@@ -91,8 +91,8 @@
// // 可以根据需要添加其他配置
// // disabledev: false,
// disabledev: false,
// // disabledev: true,
// disabledev: true,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn : !(typeof localStorage !== 'undefined' &&
@@ -157,12 +157,12 @@
const CONFIG = {
// DataServerBaseUrl: "https://test-service.001code.cn", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test-service.001code.cn", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test-service.001code.cn",
DataServerBaseUrl: "https://test-service.001code.cn",
// DataServerBaseUrl: "https://test-service.001code.cn",
// DataServerBaseUrl: "https://test-service.001code.cn",
// DataServerBaseUrl: "https://service.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://service.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://service.001code.com",
DataServerBaseUrl: "https://service.001code.com",
// DataServerBaseUrl: "https://service.001code.com",
// DataServerBaseUrl: "https://service.001code.com",
version: "2026071701",
/** OSS 真实地址(生产环境浏览器直连) */
@@ -183,7 +183,7 @@ const CONFIG = {
disabledev: false,
disabledev: true,
usecdn: false,
lvlock: false,
lvlock_normal_learn: !(typeof localStorage !== 'undefined' &&

View File

@@ -1,288 +0,0 @@
// config.js
// const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025010301",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn :true, //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:150,
// SSN_COMPETITION_ID: [13],
// // 计算关卡实际id
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
// const CONFIG = {
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025011001",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// // disabledev: false,
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn : !(typeof localStorage !== 'undefined' &&
// (localStorage.getItem('player_id') === '32' || localStorage.getItem('player_id') === '1506'|| localStorage.getItem('player_id') === '24'|| localStorage.getItem('player_id') === '1550')), //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:120,
// // 计算关卡实际id
// SSN_COMPETITION_ID: [21,22,23],
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com",
DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
version: "20260717",
/** OSS 真实地址(生产环境浏览器直连) */
unityCdnRemote: 'https://oss.eanic.cn/001_code_cocos_res_20260617',
/** 方案 Bnginx / webpack 同源代理前缀(方案 A 直连 OSS 请保持 false */
unityCdnDevProxy: '/cdn-unity',
/** 本地 npm start 也走代理(仅 unityCdnUseSiteProxy 或此项为 true 时生效) */
unityCdnUseDevProxy: false,
/** 生产 nginx 反向代理 OSS 同路径时设为 true方案 B */
unityCdnUseSiteProxy: false,
<<<<<<< HEAD
unitycdndir: 'https://oss.eanic.cn/001_code_cocos_res_20260717',
unitycdnbuilddir: 'https://oss.eanic.cn/001_code_cocos_res_20260717/Build',
=======
unitycdndir: 'https://oss.eanic.cn/001_code_cocos_res_20260617',
unitycdnbuilddir: 'https://oss.eanic.cn/001_code_cocos_res_20260617/Build',
>>>>>>> b08562d603e88576042ff972dedf771c32224b4b
python_pyodide_cdn_dir: 'https://oss.eanic.cn/001_code_python_res_20241213/pyodide/',
// 可以根据需要添加其他配置
disabledev: false,
usecdn: true,
lvlock: false,
lvlock_normal_learn: !(typeof localStorage !== 'undefined' &&
(localStorage.getItem('player_id') === '37' || localStorage.getItem('player_id') === '45' || localStorage.getItem('player_id') === '55')),
lockupdate: false,
mactchlv: false,
learnvideo: true,
gameNumber: 120,
//计算关卡实际id
SSN_COMPETITION_ID: [13],
NOT_ALLOWED_COMPETITION_ID: [72, 62, 64, 65, 69, 61, 63, 67, 70, 74, 73, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84],
BEGINNING_REAL_LVID: 91600,
END_REAL_LVID: 93100,
SIMULATE_COMPETITION_ID: [149], // 用于测试的模拟比赛ID
HIDE_COMPETITION_TIME_ID: [154, 155, 156], // 需要隐藏比赛时间的比赛ID列表
// 计算关卡实际id
getRealLvId(gamelv, gameid, ismatch, gameNumber) {
if (localStorage.getItem('platformMode') === 'competition') {
return parseInt(window.unity_game_number, 10)
} else {
if (!ismatch) {
return ((gamelv - 1) * 30 * gameNumber) + ((gameid - 1) * gameNumber) + this.BEGINNING_REAL_LVID
} else {
//TODO 这里维护一张匹配关卡映射表
if (gamelv === 240896 && gameid === 0) {
return 10000;
}
}
}
},
lastGameStyle: '',
lastGameDefaultSkin: 0,
checkGameStyle(realLvId) {
let gameStyle = 'SILU';
let gameDefaultSkin = 0;
if (realLvId >= this.BEGINNING_REAL_LVID && realLvId <= this.END_REAL_LVID) {
realLvId = realLvId - this.BEGINNING_REAL_LVID;
if (realLvId >= 1 && realLvId <= 300) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else if (realLvId >= 301 && realLvId <= 600) {
gameStyle = 'redArmy';
gameDefaultSkin = 2;
} else if (realLvId >= 601 && realLvId <= 900) {
gameStyle = 'numMan';
gameDefaultSkin = 3;
} else if (realLvId >= 901 && realLvId <= 1200) {
gameStyle = 'chinese';
gameDefaultSkin = 1;
} else if (realLvId >= 1201 && realLvId <= 1500) {
gameStyle = 'snow';
gameDefaultSkin = 4;
} else if (realLvId >= 1501 && realLvId <= 1800) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else {
gameStyle = 'SILU';
gameDefaultSkin = 0;
}
}
if (gameStyle !== this.lastGameStyle) {
window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
this.lastGameStyle = gameStyle;
}
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
const sendSkinMsg = (delay) => {
setTimeout(() => {
if (window.unityInstance) {
try {
window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
console.log("CallChangeSkin sent >>> ", gameDefaultSkin);
} catch (e) {
console.log("Player not ready yet");
}
}
}, delay);
};
sendSkinMsg(2000);
sendSkinMsg(4000);
sendSkinMsg(6000);
this.lastGameDefaultSkin = gameDefaultSkin;
}
}
};
export default CONFIG;

View File

@@ -1,283 +0,0 @@
// config.js
// const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025010301",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn :true, //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:150,
// SSN_COMPETITION_ID: [13],
// // 计算关卡实际id
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
// const CONFIG = {
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025011001",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// // disabledev: false,
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn : !(typeof localStorage !== 'undefined' &&
// (localStorage.getItem('player_id') === '32' || localStorage.getItem('player_id') === '1506'|| localStorage.getItem('player_id') === '24'|| localStorage.getItem('player_id') === '1550')), //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:120,
// // 计算关卡实际id
// SSN_COMPETITION_ID: [21,22,23],
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com",
DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
version: "20260520",
/** OSS 真实地址(生产环境浏览器直连) */
unityCdnRemote: 'https://oss.eanic.cn/001_code_cocos_res_20260616',
/** 方案 Bnginx / webpack 同源代理前缀(方案 A 直连 OSS 请保持 false */
unityCdnDevProxy: '/cdn-unity',
/** 本地 npm start 也走代理(仅 unityCdnUseSiteProxy 或此项为 true 时生效) */
unityCdnUseDevProxy: false,
/** 生产 nginx 反向代理 OSS 同路径时设为 true方案 B */
unityCdnUseSiteProxy: false,
unitycdndir: 'https://oss.eanic.cn/001_code_cocos_res_20260616',
unitycdnbuilddir: 'https://oss.eanic.cn/001_code_cocos_res_20260616/Build',
python_pyodide_cdn_dir: 'https://oss.eanic.cn/001_code_python_res_20241213/pyodide/',
// 可以根据需要添加其他配置
disabledev: false,
usecdn: true,
lvlock: false,
lvlock_normal_learn: !(typeof localStorage !== 'undefined' &&
(localStorage.getItem('player_id') === '37' || localStorage.getItem('player_id') === '45' || localStorage.getItem('player_id') === '55')),
lockupdate: false,
mactchlv: false,
learnvideo: true,
gameNumber: 120,
//计算关卡实际id
SSN_COMPETITION_ID: [13],
NOT_ALLOWED_COMPETITION_ID: [72, 62, 64, 65, 69, 61, 63, 67, 70, 74, 73, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84],
BEGINNING_REAL_LVID: 91600,
END_REAL_LVID: 93100,
SIMULATE_COMPETITION_ID: [149], // 用于测试的模拟比赛ID
HIDE_COMPETITION_TIME_ID: [154, 155, 156], // 需要隐藏比赛时间的比赛ID列表
// 计算关卡实际id
getRealLvId(gamelv, gameid, ismatch, gameNumber) {
if (localStorage.getItem('platformMode') === 'competition') {
return parseInt(window.unity_game_number, 10)
} else {
if (!ismatch) {
return ((gamelv - 1) * 30 * gameNumber) + ((gameid - 1) * gameNumber) + this.BEGINNING_REAL_LVID
} else {
//TODO 这里维护一张匹配关卡映射表
if (gamelv === 240896 && gameid === 0) {
return 10000;
}
}
}
},
lastGameStyle: '',
lastGameDefaultSkin: 0,
checkGameStyle(realLvId) {
let gameStyle = 'SILU';
let gameDefaultSkin = 0;
if (realLvId >= this.BEGINNING_REAL_LVID && realLvId <= this.END_REAL_LVID) {
realLvId = realLvId - this.BEGINNING_REAL_LVID;
if (realLvId >= 1 && realLvId <= 300) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else if (realLvId >= 301 && realLvId <= 600) {
gameStyle = 'redArmy';
gameDefaultSkin = 2;
} else if (realLvId >= 601 && realLvId <= 900) {
gameStyle = 'numMan';
gameDefaultSkin = 3;
} else if (realLvId >= 901 && realLvId <= 1200) {
gameStyle = 'chinese';
gameDefaultSkin = 1;
} else if (realLvId >= 1201 && realLvId <= 1500) {
gameStyle = 'snow';
gameDefaultSkin = 4;
} else if (realLvId >= 1501 && realLvId <= 1800) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else {
gameStyle = 'SILU';
gameDefaultSkin = 0;
}
}
if (gameStyle !== this.lastGameStyle) {
window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
this.lastGameStyle = gameStyle;
}
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
const sendSkinMsg = (delay) => {
setTimeout(() => {
if (window.unityInstance) {
try {
window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
console.log("CallChangeSkin sent >>> ", gameDefaultSkin);
} catch (e) {
console.log("Player not ready yet");
}
}
}, delay);
};
sendSkinMsg(2000);
sendSkinMsg(4000);
sendSkinMsg(6000);
this.lastGameDefaultSkin = gameDefaultSkin;
}
}
};
export default CONFIG;

View File

@@ -1,283 +0,0 @@
// config.js
// const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025010301",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn :true, //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:150,
// SSN_COMPETITION_ID: [13],
// // 计算关卡实际id
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
// const CONFIG = {
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025011001",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// // disabledev: false,
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn : !(typeof localStorage !== 'undefined' &&
// (localStorage.getItem('player_id') === '32' || localStorage.getItem('player_id') === '1506'|| localStorage.getItem('player_id') === '24'|| localStorage.getItem('player_id') === '1550')), //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:120,
// // 计算关卡实际id
// SSN_COMPETITION_ID: [21,22,23],
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com",
DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
version: "20260717",
/** OSS 真实地址(生产环境浏览器直连) */
unityCdnRemote: 'https://oss.eanic.cn/001_code_cocos_res_20260617',
/** 方案 Bnginx / webpack 同源代理前缀(方案 A 直连 OSS 请保持 false */
unityCdnDevProxy: '/cdn-unity',
/** 本地 npm start 也走代理(仅 unityCdnUseSiteProxy 或此项为 true 时生效) */
unityCdnUseDevProxy: false,
/** 生产 nginx 反向代理 OSS 同路径时设为 true方案 B */
unityCdnUseSiteProxy: false,
unitycdndir: 'https://oss.eanic.cn/001_code_cocos_res_20260717',
unitycdnbuilddir: 'https://oss.eanic.cn/001_code_cocos_res_20260717/Build',
python_pyodide_cdn_dir: 'https://oss.eanic.cn/001_code_python_res_20241213/pyodide/',
// 可以根据需要添加其他配置
disabledev: false,
usecdn: true,
lvlock: false,
lvlock_normal_learn: !(typeof localStorage !== 'undefined' &&
(localStorage.getItem('player_id') === '37' || localStorage.getItem('player_id') === '45' || localStorage.getItem('player_id') === '55')),
lockupdate: false,
mactchlv: false,
learnvideo: true,
gameNumber: 120,
//计算关卡实际id
SSN_COMPETITION_ID: [13],
NOT_ALLOWED_COMPETITION_ID: [72, 62, 64, 65, 69, 61, 63, 67, 70, 74, 73, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84],
BEGINNING_REAL_LVID: 91600,
END_REAL_LVID: 93100,
SIMULATE_COMPETITION_ID: [149], // 用于测试的模拟比赛ID
HIDE_COMPETITION_TIME_ID: [154, 155, 156], // 需要隐藏比赛时间的比赛ID列表
// 计算关卡实际id
getRealLvId(gamelv, gameid, ismatch, gameNumber) {
if (localStorage.getItem('platformMode') === 'competition') {
return parseInt(window.unity_game_number, 10)
} else {
if (!ismatch) {
return ((gamelv - 1) * 30 * gameNumber) + ((gameid - 1) * gameNumber) + this.BEGINNING_REAL_LVID
} else {
//TODO 这里维护一张匹配关卡映射表
if (gamelv === 240896 && gameid === 0) {
return 10000;
}
}
}
},
lastGameStyle: '',
lastGameDefaultSkin: 0,
checkGameStyle(realLvId) {
let gameStyle = 'SILU';
let gameDefaultSkin = 0;
if (realLvId >= this.BEGINNING_REAL_LVID && realLvId <= this.END_REAL_LVID) {
realLvId = realLvId - this.BEGINNING_REAL_LVID;
if (realLvId >= 1 && realLvId <= 300) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else if (realLvId >= 301 && realLvId <= 600) {
gameStyle = 'redArmy';
gameDefaultSkin = 2;
} else if (realLvId >= 601 && realLvId <= 900) {
gameStyle = 'numMan';
gameDefaultSkin = 3;
} else if (realLvId >= 901 && realLvId <= 1200) {
gameStyle = 'chinese';
gameDefaultSkin = 1;
} else if (realLvId >= 1201 && realLvId <= 1500) {
gameStyle = 'snow';
gameDefaultSkin = 4;
} else if (realLvId >= 1501 && realLvId <= 1800) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else {
gameStyle = 'SILU';
gameDefaultSkin = 0;
}
}
if (gameStyle !== this.lastGameStyle) {
window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
this.lastGameStyle = gameStyle;
}
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
const sendSkinMsg = (delay) => {
setTimeout(() => {
if (window.unityInstance) {
try {
window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
console.log("CallChangeSkin sent >>> ", gameDefaultSkin);
} catch (e) {
console.log("Player not ready yet");
}
}
}, delay);
};
sendSkinMsg(2000);
sendSkinMsg(4000);
sendSkinMsg(6000);
this.lastGameDefaultSkin = gameDefaultSkin;
}
}
};
export default CONFIG;

View File

@@ -1,283 +0,0 @@
// config.js
// const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025010301",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn :true, //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:150,
// SSN_COMPETITION_ID: [13],
// // 计算关卡实际id
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
// const CONFIG = {
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025011001",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// // disabledev: false,
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn : !(typeof localStorage !== 'undefined' &&
// (localStorage.getItem('player_id') === '32' || localStorage.getItem('player_id') === '1506'|| localStorage.getItem('player_id') === '24'|| localStorage.getItem('player_id') === '1550')), //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:120,
// // 计算关卡实际id
// SSN_COMPETITION_ID: [21,22,23],
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com",
DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
version: "20260520",
/** OSS 真实地址(生产环境浏览器直连) */
unityCdnRemote: 'https://oss.eanic.cn/001_code_cocos_res_20260617',
/** 方案 Bnginx / webpack 同源代理前缀(方案 A 直连 OSS 请保持 false */
unityCdnDevProxy: '/cdn-unity',
/** 本地 npm start 也走代理(仅 unityCdnUseSiteProxy 或此项为 true 时生效) */
unityCdnUseDevProxy: false,
/** 生产 nginx 反向代理 OSS 同路径时设为 true方案 B */
unityCdnUseSiteProxy: false,
unitycdndir: 'https://oss.eanic.cn/001_code_cocos_res_20260617',
unitycdnbuilddir: 'https://oss.eanic.cn/001_code_cocos_res_20260617/Build',
python_pyodide_cdn_dir: 'https://oss.eanic.cn/001_code_python_res_20241213/pyodide/',
// 可以根据需要添加其他配置
disabledev: false,
usecdn: true,
lvlock: false,
lvlock_normal_learn: !(typeof localStorage !== 'undefined' &&
(localStorage.getItem('player_id') === '37' || localStorage.getItem('player_id') === '45' || localStorage.getItem('player_id') === '55')),
lockupdate: false,
mactchlv: false,
learnvideo: true,
gameNumber: 120,
//计算关卡实际id
SSN_COMPETITION_ID: [13],
NOT_ALLOWED_COMPETITION_ID: [72, 62, 64, 65, 69, 61, 63, 67, 70, 74, 73, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84],
BEGINNING_REAL_LVID: 91600,
END_REAL_LVID: 93100,
SIMULATE_COMPETITION_ID: [149], // 用于测试的模拟比赛ID
HIDE_COMPETITION_TIME_ID: [154, 155, 156], // 需要隐藏比赛时间的比赛ID列表
// 计算关卡实际id
getRealLvId(gamelv, gameid, ismatch, gameNumber) {
if (localStorage.getItem('platformMode') === 'competition') {
return parseInt(window.unity_game_number, 10)
} else {
if (!ismatch) {
return ((gamelv - 1) * 30 * gameNumber) + ((gameid - 1) * gameNumber) + this.BEGINNING_REAL_LVID
} else {
//TODO 这里维护一张匹配关卡映射表
if (gamelv === 240896 && gameid === 0) {
return 10000;
}
}
}
},
lastGameStyle: '',
lastGameDefaultSkin: 0,
checkGameStyle(realLvId) {
let gameStyle = 'SILU';
let gameDefaultSkin = 0;
if (realLvId >= this.BEGINNING_REAL_LVID && realLvId <= this.END_REAL_LVID) {
realLvId = realLvId - this.BEGINNING_REAL_LVID;
if (realLvId >= 1 && realLvId <= 300) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else if (realLvId >= 301 && realLvId <= 600) {
gameStyle = 'redArmy';
gameDefaultSkin = 2;
} else if (realLvId >= 601 && realLvId <= 900) {
gameStyle = 'numMan';
gameDefaultSkin = 3;
} else if (realLvId >= 901 && realLvId <= 1200) {
gameStyle = 'chinese';
gameDefaultSkin = 1;
} else if (realLvId >= 1201 && realLvId <= 1500) {
gameStyle = 'snow';
gameDefaultSkin = 4;
} else if (realLvId >= 1501 && realLvId <= 1800) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else {
gameStyle = 'SILU';
gameDefaultSkin = 0;
}
}
if (gameStyle !== this.lastGameStyle) {
window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
this.lastGameStyle = gameStyle;
}
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
const sendSkinMsg = (delay) => {
setTimeout(() => {
if (window.unityInstance) {
try {
window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
console.log("CallChangeSkin sent >>> ", gameDefaultSkin);
} catch (e) {
console.log("Player not ready yet");
}
}
}, delay);
};
sendSkinMsg(2000);
sendSkinMsg(4000);
sendSkinMsg(6000);
this.lastGameDefaultSkin = gameDefaultSkin;
}
}
};
export default CONFIG;

View File

@@ -286,10 +286,20 @@
}
.toc-item__title {
font-size: 13px; font-weight: 600; color: #0b1a4a; line-height: 1.35;
display: flex; align-items: flex-start; gap: 6px;
}
.toc-item__num {
flex-shrink: 0; min-width: 1.15em;
font-variant-numeric: tabular-nums; font-weight: 800; color: #0003FF;
}
.toc-item__name {
flex: 1; min-width: 0;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.toc-item.is-active .toc-item__title { color: #0003FF; }
.toc-item.is-active .toc-item__num { color: #0003FF; }
.toc-item.is-locked .toc-item__title { color: #64748b; font-weight: 500; }
.toc-item.is-locked .toc-item__num { color: #94a3b8; font-weight: 700; }
.toc-item__meta {
margin-top: 2px; font-size: 11px; color: #94a3b8;
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
@@ -960,9 +970,13 @@
function mapCourseware(cw, source, groupTitle) {
// 预览与去上课都尊重接口 is_locked进度条仅在有 class_id 时展示,互不影响
var locked = cw.is_locked === true;
var sortOrder = Number(cw.sort_order);
if (!isFinite(sortOrder)) sortOrder = 0;
return {
id: cw.id,
title: cw.name || '未命名课件',
displayIndex: sortOrder + 1,
sortOrder: sortOrder,
type: String(cw.format || 'html').toLowerCase(),
typeLabel: cw.format_display || cw.format || '课件',
duration: cw.duration_minutes ? (cw.duration_minutes + '分钟') : '',
@@ -980,62 +994,91 @@
return (topics || []).filter(function(t) { return !t.isLocked; }).length;
}
function buildChapters(category, source) {
var coursewares = Array.isArray(category.coursewares) ? category.coursewares.slice() : [];
coursewares.sort(function(a, b) {
function sortCoursewares(coursewares) {
return (coursewares || []).slice().sort(function(a, b) {
return (Number(a.sort_order) || 0) - (Number(b.sort_order) || 0);
});
var groups = Array.isArray(category.groups) ? category.groups.slice() : [];
var chapters = [];
}
if (groups.length) {
chapters = groups.map(function(g, idx) {
var gName = g.group_name || ('分组' + (idx + 1));
var topics = coursewares.filter(function(cw) {
return String(cw.group_name || '') === String(g.group_name || '');
}).map(function(cw) { return mapCourseware(cw, source, gName); });
// 始终按课件实际锁定状态统计,避免 groups.unlocked_count 与课件列表不一致
return {
id: idx + 1,
title: gName,
count: topics.length || Number(g.course_count) || 0,
unlocked: countUnlockedTopics(topics),
topics: topics
};
function collectCoursewares(container) {
if (!container) return [];
if (Array.isArray(container.coursewares) && container.coursewares.length) {
return container.coursewares.slice();
}
var list = [];
if (Array.isArray(container.groups)) {
container.groups.forEach(function(g) {
if (Array.isArray(g.coursewares)) {
g.coursewares.forEach(function(cw) { list.push(cw); });
}
});
}
return list;
}
var groupedNames = {};
groups.forEach(function(g) { groupedNames[String(g.group_name || '')] = true; });
var orphans = coursewares.filter(function(cw) {
var gn = String(cw.group_name || '');
return !Object.prototype.hasOwnProperty.call(groupedNames, gn);
function chapterFromCoursewares(coursewares, opts) {
var title = opts.title || '课件列表';
var topics = sortCoursewares(coursewares).map(function(cw) {
return mapCourseware(cw, opts.source, title);
});
return {
id: opts.id,
indexLabel: opts.indexLabel != null ? opts.indexLabel : opts.id,
title: title,
intro: opts.intro || '',
count: topics.length,
unlocked: countUnlockedTopics(topics),
topics: topics
};
}
function buildChapters(category, source) {
var semesters = Array.isArray(category.semesters) ? category.semesters.slice() : [];
if (semesters.length) {
semesters.sort(function(a, b) {
return (Number(a.sort_order) || 0) - (Number(b.sort_order) || 0);
});
if (orphans.length) {
var orphanTopics = orphans.map(function(cw) {
return mapCourseware(cw, source, '其他课件');
return semesters.map(function(sem, idx) {
var title = String(sem.name || '').trim() || ('第' + (idx + 1) + '学期');
return chapterFromCoursewares(collectCoursewares(sem), {
source: source,
id: sem.id != null ? sem.id : (idx + 1),
indexLabel: Number(sem.sort_order) || (idx + 1),
title: title,
intro: sem.intro || ''
});
chapters.push({
id: chapters.length + 1,
title: '其他课件',
count: orphanTopics.length,
unlocked: countUnlockedTopics(orphanTopics),
topics: orphanTopics
});
}
} else {
var topics = coursewares.map(function(cw) {
return mapCourseware(cw, source, category.name || courseName);
});
chapters = [{
id: 1,
title: category.name || courseName || '课件列表',
count: topics.length,
unlocked: countUnlockedTopics(topics),
topics: topics
}];
}
return chapters;
var coursewares = collectCoursewares(category);
var groups = Array.isArray(category.groups) ? category.groups.slice() : [];
var namedGroups = groups.filter(function(g) {
return String(g.group_name || '').trim();
});
if (namedGroups.length) {
return namedGroups.map(function(g, idx) {
var gName = String(g.group_name || '').trim();
var list = Array.isArray(g.coursewares) && g.coursewares.length
? g.coursewares
: coursewares.filter(function(cw) {
return String(cw.group_name || '') === String(g.group_name || '');
});
return chapterFromCoursewares(list, {
source: source,
id: idx + 1,
indexLabel: idx + 1,
title: gName
});
});
}
return [chapterFromCoursewares(coursewares, {
source: source,
id: 1,
indexLabel: 1,
title: category.name || courseName || '课件列表'
})];
}
function flattenLessons(chapters) {
@@ -1093,7 +1136,7 @@
return;
}
stageTitle.textContent = lesson.title;
stageTitle.textContent = (lesson.displayIndex != null ? (lesson.displayIndex + '. ') : '') + lesson.title;
var metaParts = [];
if (lesson.groupTitle) metaParts.push(lesson.groupTitle);
if (lesson.typeLabel) metaParts.push(lesson.typeLabel);
@@ -1143,6 +1186,12 @@
document.getElementById('stageFrame').removeAttribute('src');
}
function resetLockControl(btn) {
if (!btn) return;
btn.classList.remove('is-loading');
btn.disabled = false;
}
function syncLockedPanel(lesson) {
var desc = document.getElementById('stageLockedDesc');
var unlockBtn = document.getElementById('stageUnlockBtn');
@@ -1153,6 +1202,8 @@
: '请联系老师解锁相关内容。';
}
if (!unlockBtn) return;
// 大按钮是常驻 DOM上次解锁会留下 disabled切到下一课未解锁课件时必须复位
resetLockControl(unlockBtn);
if (teacherCanUnlock) {
unlockBtn.classList.add('is-visible');
unlockBtn.setAttribute('data-id', String(lesson.id));
@@ -1328,15 +1379,16 @@
var keepLessonId = opts.keepLessonId != null ? opts.keepLessonId : currentLessonId;
listEl.innerHTML = chaptersCache.map(function(ch, idx) {
var allUnlocked = ch.topics.length > 0 && ch.topics.every(function(t) { return !t.isLocked; });
var topics = ch.topics || [];
var allUnlocked = topics.length > 0 && topics.every(function(t) { return !t.isLocked; });
var isOpen = opts.preserveOpen
? openGroupIds.indexOf(String(ch.id)) !== -1
: idx === 0;
if (!opts.preserveOpen && keepLessonId) {
var hasKeep = ch.topics.some(function(t) { return String(t.id) === String(keepLessonId); });
var hasKeep = topics.some(function(t) { return String(t.id) === String(keepLessonId); });
if (hasKeep) isOpen = true;
}
var items = ch.topics.map(function(t) {
var items = topics.map(function(t) {
var tagClass = 'toc-item__tag--' + (t.status || 'doing');
var meta = escapeHtml(t.typeLabel || t.type || '课件');
if (t.duration) meta += ' · ' + escapeHtml(t.duration);
@@ -1354,7 +1406,10 @@
return '<div class="' + itemClass + '" data-id="' + t.id + '" role="button" tabindex="0"' + itemTitle + '>' +
'<div class="toc-item__icon"><img src="../images/aigc/icon_book.png" alt="" /></div>' +
'<div class="toc-item__body">' +
'<div class="toc-item__title">' + escapeHtml(t.title) + '</div>' +
'<div class="toc-item__title">' +
'<span class="toc-item__num">' + escapeHtml(String(t.displayIndex != null ? t.displayIndex : '')) + '</span>' +
'<span class="toc-item__name">' + escapeHtml(t.title) + '</span>' +
'</div>' +
'<div class="toc-item__meta">' +
'<span>' + meta + '</span>' +
'<span class="toc-item__tag ' + tagClass + '">' + escapeHtml(statusLabels[t.status] || '') + '</span>' +
@@ -1370,7 +1425,7 @@
: '';
return '<div class="toc-group' + (isOpen ? ' is-open' : '') + '" data-group-id="' + ch.id + '">' +
'<button type="button" class="toc-group__head" data-toggle-group="' + ch.id + '">' +
'<span class="toc-group__idx' + (allUnlocked ? ' is-done' : '') + '">' + (allUnlocked ? '✓' : ch.id) + '</span>' +
'<span class="toc-group__idx' + (allUnlocked ? ' is-done' : '') + '">' + (allUnlocked ? '✓' : escapeHtml(String(ch.indexLabel || ch.id))) + '</span>' +
'<span class="toc-group__meta">' +
'<div class="toc-group__name">' + escapeHtml(ch.title) + '</div>' +
unlockCountHtml +
@@ -1471,10 +1526,8 @@
} catch (error) {
console.error('[course-detail] toggle lock failed', error);
showToast((error && error.message) || '锁定状态切换失败', 'error');
if (btn) {
btn.classList.remove('is-loading');
btn.disabled = false;
}
} finally {
resetLockControl(btn);
}
}
@@ -1500,7 +1553,10 @@
if (!response.ok || (result && result.success === false)) {
throw new Error((result && result.message) || ('HTTP ' + response.status));
}
var data = (result && result.data) || {};
var data = result || {};
if (data.data && (Array.isArray(data.data.categories) || data.data.source)) {
data = data.data;
}
courseSource = data.source || (classId ? 'class' : 'all');
var categories = Array.isArray(data.categories) ? data.categories : [];
var category = categories[0] || null;

View File

@@ -2,8 +2,10 @@ import disableDevtool from 'disable-devtool';
import CONFIG from './config.js';
const STOP_DEV_KEY = 'stopDev';
let stoppedForPage = false;
function isStopped() {
if (stoppedForPage) return true;
try {
return sessionStorage.getItem(STOP_DEV_KEY) === '1';
} catch (e) {
@@ -16,6 +18,8 @@ function isStopped() {
* 可在控制台直接执行stopDev()
*/
export function stopDev() {
// 即使浏览器拒绝写入 sessionStorage当前页面仍应立即停止检测。
stoppedForPage = true;
try {
sessionStorage.setItem(STOP_DEV_KEY, '1');
} catch (e) {
@@ -29,6 +33,8 @@ export function initDisableDevtool(options = {}) {
if (isStopped()) {
disableDevtool.isSuspend = true;
// F12 已打开时,库可能在初始化期间立即触发检测,不能继续初始化。
return;
}
if (!CONFIG.disabledev) {
@@ -36,14 +42,19 @@ export function initDisableDevtool(options = {}) {
}
const userIgnore = options.ignore;
const onDevtoolOpen = options.ondevtoolopen || (() => {
window.location.href = localStorage.getItem('returnUrl');
});
disableDevtool({
ondevtoolopen: () => {
window.location.href = localStorage.getItem('returnUrl');
},
interval: 500,
clearLog: true,
disableMenu: true,
...options,
ondevtoolopen: (...args) => {
// stopDev() 之后,已排队的检测回调也不能清空代码或跳转。
if (isStopped()) return;
return onDevtoolOpen(...args);
},
ignore: () => {
if (isStopped()) {
return true;

View File

@@ -1493,13 +1493,6 @@ top: 2032px; */
</style>
<body>
<script>
// 首页默认清除登录态,避免访客页残留 token
try {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
} catch (e) {}
</script>
<header class="nav">
<div class="nav__inner">
<img class="nav__logo" src="../images/logo.svg" alt="001CODE" />
@@ -1766,7 +1759,7 @@ top: 2032px; */
<footer class="footer" aria-label="页脚">
<div class="footer-line"></div>
<div class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">沪ICP备2026042325号</a>
<!-- 公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a> -->
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM
<span class="qr" tabindex="0">
<img class="qr-icon" src="../images/qrcode_gery.svg" alt="公众号小图标" />

View File

@@ -347,7 +347,7 @@
<footer class="footer" aria-label="页脚">
<div class="footer-line"></div>
<div class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">沪ICP备2026042325号</a>
<!-- 公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a> -->
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM
<span class="qr" tabindex="0">
<img class="qr-icon" src="../images/qrcode_gery.svg" alt="公众号小图标" />

View File

@@ -96,7 +96,7 @@
<footer class="site-footer">
<div class="footer-divider"></div>
<p class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">沪ICP备2026042325号</a>
<!-- 公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a> -->
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM</p></p>
</footer>

View File

@@ -46,7 +46,7 @@
<img class="level-card-cover" src="images/level1_cover.png" alt="" />
<div class="level-card-text">
<!-- <div class="lang-name">图形化</div> -->
<div class="level-card-head">Level A</div><span class="lang-name">(图形化)</span>
<div class="level-card-head">Level A <span style='font-size: 0.7em'>丝路青年</span></div><span class="lang-name">(图形化)</span>
<div class="level-card-sub">编程基础,循环入门</div>
<div class="level-card-desc"></div>
</div>
@@ -94,8 +94,7 @@
<div class="footer-inner">
<div class="footer-text" ">
ICP备案号<a style=" color: #387cbd;" href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">沪ICP备2026042325号</a>
<!-- <span> 公安备案号:<a style=" color: #387cbd;" href="https://beian.mps.gov.cn/"
target="_blank">川公网安备51011202000980号</a></span> -->
<span>公安备案号:<a style="color:#387cbd;" href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a></span>
<span>© 灵丌编程001CODE.COM</span>
</div>
</div>

View File

@@ -106,12 +106,12 @@ document.addEventListener('DOMContentLoaded', function () {
// Update Level Card Text
const levelInfo = {
1: { title: "Level A <span style='font-size: 0.7em'>丝路青年</span>", sub: "编程基础,循环入门" },
2: { title: "Level B <span style='font-size: 0.7em'>红色之旅</span>", sub: "变量入门,循环进阶,变量进阶" },
3: { title: "Level C <span style='font-size: 0.7em'>数智时代</span>", sub: "变量进阶,单条件分支入门,双条件分支入门,复杂路线规划" },
4: { title: "Level D <span style='font-size: 0.7em'>国宝秘境</span>", sub: "条件分支进阶,循环嵌套入门,循环嵌套进阶,变量、循环与条件综合应用" },
5: { title: "Level E <span style='font-size: 0.7em'>雪域征途</span>", sub: "循环嵌套进阶,无参数函数入门,单参数函数入门" },
6: { title: "Level F <span style='font-size: 0.7em'>红色之旅</span>", sub: "综合运用与项目实战" }
1: { title: "Level A <span style='font-size: 0.7em'>丝路青年</span>", sub: "编程基础,循环入门", cover: 1 },
2: { title: "Level B <span style='font-size: 0.7em'>丝路青年</span>", sub: "变量入门,循环进阶,变量进阶", cover: 1 },
3: { title: "Level C <span style='font-size: 0.7em'>国宝秘境</span>", sub: "变量进阶,单条件分支入门,双条件分支入门,复杂路线规划", cover: 4 },
4: { title: "Level D <span style='font-size: 0.7em'>国宝秘境</span>", sub: "条件分支进阶,循环嵌套入门,循环嵌套进阶,变量、循环与条件综合应用", cover: 4 },
5: { title: "Level E <span style='font-size: 0.7em'>数智时代</span>", sub: "循环嵌套进阶,无参数函数入门,单参数函数入门", cover: 3 },
6: { title: "Level F <span style='font-size: 0.7em'>数智时代</span>", sub: "综合运用与项目实战", cover: 3 }
};
const lv = index + 1;
const titleEl = document.querySelector('.level-card-head');
@@ -124,7 +124,8 @@ document.addEventListener('DOMContentLoaded', function () {
// Update Cover Image
const coverEl = document.querySelector('.level-card-cover');
if (coverEl) {
coverEl.src = `images/level${lv}_cover.png`;
const coverId = (levelInfo[lv] && levelInfo[lv].cover) || lv;
coverEl.src = `images/level${coverId}_cover.png`;
}
gamelvid = (index + 1).toString();

View File

@@ -361,7 +361,7 @@
<footer class="footer" aria-label="页脚">
<div class="footer-line"></div>
<div class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">沪ICP备2026042325号</a>
<!-- 公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a> -->
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM
<span class="qr" tabindex="0">
<img class="qr-icon" src="../images/qrcode_gery.svg" alt="公众号小图标" />

View File

@@ -332,7 +332,7 @@
<div class="footer-inner">
<div class="footer-text">
ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">沪ICP备2026042325号</a>
<!-- 公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank">川公网安备51011202000980号</a> --> © 灵丌编程001CODE.COM
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a> © 灵丌编程001CODE.COM
</div>
</div>
</footer>

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -126,7 +126,7 @@ self.onmessage = async (event) => {
const { type, code, unity_data, requestId} = event.data;
if (type === "response") {
self.unitydata = unity_data
console.log("get new response", self.unitydata.externalDatas[0].position)
console.log("get new response", self.unitydata && self.unitydata.externalDatas && self.unitydata.externalDatas[0] && self.unitydata.externalDatas[0].position)
const pending = pendingRequests.get(requestId);
if (pending) {
pending.resolve();
@@ -152,7 +152,7 @@ self.onmessage = async (event) => {
// 发送执行完成的消息
self.postMessage({ type: "executionComplete" });
console.log("Python 执行完成",window.isPythonRun);
console.log("Python 执行完成");
self.postMessage({ type: "result", result });
} catch (error) {
self.postMessage({ type: "error", error: error.message });

View File

@@ -19,7 +19,6 @@
</assembly>
<assembly fullname="Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" preserve="all">
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.AtlasSpriteProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.InstanceProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.LegacyResourcesProvider" preserve="all" />

Some files were not shown because too many files have changed in this diff Show More