feat: 新增多项功能并添加新工具
- 私人剪贴板新增限时直通URL生成功能 - doc_cloud_keeper新增标题复制按钮 - 新增贪蛇出动游戏工具 - 新增OSS中转直链工具 - 添加批量抽帧测试脚本
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
/* 贪蛇出动 - 核心逻辑单元测试(无 DOM,Node 直接运行)
|
||||
* 运行: node dev_test_scripts/unit/test_greedy_snake_core.js
|
||||
*/
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
global.window = global; // 让模块的 window.GS 挂到全局
|
||||
const JS_DIR = path.join(__dirname, '..', '..', 'public', 'tools', 'greedy_snake', 'js');
|
||||
for (const f of ['config.js', 'utils.js', 'snake.js', 'ai.js', 'world.js']) {
|
||||
const src = fs.readFileSync(path.join(JS_DIR, f), 'utf-8');
|
||||
new Function(src)();
|
||||
}
|
||||
|
||||
const { U, Snake, AI, World, CFG } = global.GS;
|
||||
let failed = 0;
|
||||
const assert = (cond, msg) => {
|
||||
if (cond) console.log(' [PASS] ' + msg);
|
||||
else { failed++; console.error(' [FAIL] ' + msg); }
|
||||
};
|
||||
|
||||
console.log('== 工具函数 ==');
|
||||
assert(U.fmt(276496) === '276,496', 'fmt 千分位: ' + U.fmt(276496));
|
||||
assert(U.fmt(1194000) === '1,194k', 'fmt k 格式: ' + U.fmt(1194000));
|
||||
assert(Math.abs(U.normAngle(Math.PI * 2)) < 1e-9, 'normAngle 归一化');
|
||||
assert(U.clamp(5, 0, 3) === 3, 'clamp');
|
||||
|
||||
console.log('== 蛇移动与采样 ==');
|
||||
const s = new Snake({ id: 'p', name: 't', isPlayer: true, color: { body: '#f00', dark: '#900' }, x: 2000, y: 2000, angle: 0 });
|
||||
s.targetAngle = 0;
|
||||
for (let i = 0; i < 120; i++) s.update(1 / 60, 0);
|
||||
assert(Math.abs(s.head.x - (2000 + 130 * 2)) < 3, '直线移动距离正确: ' + (s.head.x - 2000).toFixed(1));
|
||||
const segs = s.segments();
|
||||
assert(segs.length === s.segCount, '身体节数=' + segs.length);
|
||||
const d01 = U.dist(segs[0].x, segs[0].y, segs[1].x, segs[1].y);
|
||||
assert(Math.abs(d01 - s.spacing) < 0.5, '节间距=spacing: ' + d01.toFixed(2));
|
||||
|
||||
console.log('== 世界/金币/碰撞 ==');
|
||||
const w = new World();
|
||||
assert(w.coins.length >= CFG.coinCount, '初始金币数: ' + w.coins.length);
|
||||
const before = w.coins.length;
|
||||
w.dropFromSegments(segs);
|
||||
assert(w.coins.length > before, '尸体掉币: +' + (w.coins.length - before));
|
||||
|
||||
const s2 = new Snake({ id: 'p2', name: 't2', isPlayer: true, color: { body: '#f00', dark: '#900' }, x: 2000, y: 3000, angle: 0 });
|
||||
s2.targetAngle = 0;
|
||||
for (let i = 0; i < 120; i++) s2.update(1 / 60, 0); // 先走出一条身体
|
||||
const bot = new Snake({ id: 'b', name: 'bot', color: { body: '#fff', dark: '#ccc' }, x: s2.head.x + 120, y: s2.head.y, score: 20, angle: Math.PI });
|
||||
bot.targetAngle = Math.PI; // 朝玩家身体冲
|
||||
let hit = null;
|
||||
for (let i = 0; i < 60 && !hit; i++) {
|
||||
s2.update(1 / 60, 0); bot.update(1 / 60, 0);
|
||||
hit = w.collide(bot, [s2, bot]);
|
||||
}
|
||||
assert(hit !== null, '撞身体判定: ' + (hit && hit.type));
|
||||
|
||||
console.log('== AI + 综合模拟 600 步 ==');
|
||||
const w2 = new World();
|
||||
const player = new Snake({ id: 'p', name: 'me', isPlayer: true, color: { body: '#f00', dark: '#900' }, x: 2000, y: 2000 });
|
||||
const bots = [];
|
||||
const ais = new Map();
|
||||
for (let i = 0; i < CFG.botCount; i++) {
|
||||
const b = new Snake({ id: 'b' + i, name: 'bot' + i, color: { body: '#fff', dark: '#ccc' }, x: U.rand(500, 3500), y: U.rand(500, 3500), score: U.randInt(10, 200) });
|
||||
bots.push(b); ais.set(b.id, new AI(b));
|
||||
}
|
||||
let deaths = 0, eaten0 = player.score;
|
||||
for (let t = 0; t < 600; t++) {
|
||||
player.targetAngle += 0.02;
|
||||
for (const b of bots) ais.get(b.id).update(1 / 60, w2, [player, ...bots]);
|
||||
for (const x of [player, ...bots]) x.update(1 / 60, t);
|
||||
w2.update(1 / 60, [player, ...bots], t);
|
||||
for (const x of [player, ...bots]) {
|
||||
if (x.dead) continue;
|
||||
const h = w2.collide(x, [player, ...bots]);
|
||||
if (h) {
|
||||
if (x.shield) { x.shield = false; continue; }
|
||||
x.die(); deaths++;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(true, '模拟完成,死亡数=' + deaths + ',玩家分数 ' + eaten0 + ' -> ' + Math.floor(player.score));
|
||||
assert(w2.coins.length > 0, '模拟后金币存在: ' + w2.coins.length);
|
||||
assert(isFinite(player.head.x) && isFinite(player.head.y), '玩家坐标有限');
|
||||
|
||||
console.log(failed === 0 ? '\n全部通过' : `\n${failed} 项失败`);
|
||||
process.exit(failed === 0 ? 0 : 1);
|
||||
Reference in New Issue
Block a user