167 lines
8.8 KiB
JavaScript
167 lines
8.8 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { loadMembers } = require('./new-player-ab-test-support.cjs');
|
|
|
|
const cc = { v2: (x, y) => ({ x, y }), isValid: node => !!node && !node.destroyed };
|
|
const Controller = loadMembers('assets/Script/Map.ts', [
|
|
'checkPass', 'passWall', 'predict_End', 'isWashCell', 'getDirtFrames', 'getWashingBubble', 'unlockSpawnGateBatch',
|
|
], { cc });
|
|
|
|
function exitFixture(overrides = {}) {
|
|
const events = [];
|
|
const block = { isDirty: true, color: 2, type: 0, posX: 1, posY: 2, block_Info: { lock: true }, ...overrides };
|
|
const node = { getComponent: () => block };
|
|
const wall = { posX: 0, posY: 2, color: 2, colorArray: [2, 3], special: 0, open: true,
|
|
toggleOpen: true, wall_Info: { length: 1 }, changeColorWall: () => events.push('door') };
|
|
const wallNode = { getComponent: () => wall, parent: { getChildByName: () => ({ active: false }) } };
|
|
const map = Object.assign(new Controller(), {
|
|
blocks: [node], blockNum: 1, leftDoors: [{ getChildByName: () => wallNode }],
|
|
rightDoors: [], topDoors: [], bottomDoors: [],
|
|
getMinAndMax: () => ({ minX: 1, maxX: 8, minY: 1, maxY: 8 }),
|
|
checkDiraction: () => [false, false, false, false], detectingBlock: () => true,
|
|
checkColor: () => events.push('color'), changeBoom: () => events.push('bomb'),
|
|
special_Treatment: () => events.push('eliminate'), createParticle: () => events.push('particle'),
|
|
});
|
|
return { map, block, node, wall, events };
|
|
}
|
|
|
|
test('dirty blocks reject normal exit before gate, bomb, counter or elimination side effects', () => {
|
|
const f = exitFixture();
|
|
assert.equal(f.map.checkPass(f.node, [{ x: 0, y: 0 }]), -1);
|
|
assert.equal(f.map.blockNum, 1);
|
|
assert.equal(f.block.block_Info.lock, true);
|
|
assert.deepEqual(f.events, []);
|
|
assert.equal(f.block.isDirty, true, 'exit checks must not wash dirt');
|
|
});
|
|
|
|
test('a clean member can exit independently of a dirty adhesive partner', () => {
|
|
const f = exitFixture({ isDirty: false, type: 9 });
|
|
f.block.block_Info.node = { getComponent: () => ({ isDirty: true }) };
|
|
assert.equal(f.map.checkPass(f.node, [{ x: 0, y: 0 }]), 2);
|
|
assert.equal(f.map.blockNum, 0);
|
|
assert.deepEqual(f.events, ['color', 'door', 'bomb', 'eliminate', 'particle']);
|
|
});
|
|
|
|
test('both rainbow variants ignore dirt and color while retaining closed-gate restrictions', () => {
|
|
for (const rainbow of [{ oldRainbow: true, color: 5 }, { color: 100 }]) {
|
|
const f = exitFixture(rainbow);
|
|
assert.equal(f.map.checkPass(f.node, [{ x: 0, y: 0 }]), 2);
|
|
assert.equal(f.map.blockNum, 0);
|
|
const closed = exitFixture(rainbow);
|
|
closed.wall.special = 2;
|
|
closed.wall.open = false;
|
|
assert.equal(closed.map.checkPass(closed.node, [{ x: 0, y: 0 }]), -1);
|
|
assert.equal(closed.map.blockNum, 1);
|
|
assert.deepEqual(closed.events, []);
|
|
}
|
|
});
|
|
|
|
test('potential-move prediction does not call washing or declare a movable dirty block lost', () => {
|
|
const f = exitFixture();
|
|
f.wall.node = { parent: { getChildByName: () => ({ active: false }) } };
|
|
f.wall.judgeButtonWall = () => true;
|
|
f.map.wall_Pass = [f.wall];
|
|
f.map.blockCanMove = () => true;
|
|
f.map.blockCanPass = () => true;
|
|
f.block.tryWashDirt = () => assert.fail('prediction changed dirt');
|
|
assert.equal(f.map.predict_End(false), true);
|
|
assert.equal(f.block.isDirty, true);
|
|
assert.deepEqual(f.events, []);
|
|
});
|
|
|
|
test('wash lookup accepts playable marked cells and rejects wall, gap and out-of-bounds cells', () => {
|
|
const cell = (washing, opacity = 255) => ({ opacity, getComponent: () => ({ washing }) });
|
|
const map = Object.assign(new Controller(), { mapBlocksWall: [[cell(false), cell(true), cell(true, 254)]] });
|
|
assert.equal(map.isWashCell(0, 0), false);
|
|
assert.equal(map.isWashCell(0, 1), true);
|
|
assert.equal(map.isWashCell(0, 2), false);
|
|
assert.equal(map.isWashCell(-1, 1), false);
|
|
assert.equal(map.isWashCell(1, 0), false);
|
|
assert.equal(map.isWashCell(0, 3), false);
|
|
});
|
|
|
|
test('spawned blocks wash only after spawn locking is released, before bomb timers resume', () => {
|
|
const events = [];
|
|
const block = { spawnLocked: true, type: 6,
|
|
setSpawnLocked(value) { this.spawnLocked = value; events.push('unlock'); },
|
|
tryWashDirt() { assert.equal(this.spawnLocked, false); events.push('wash'); } };
|
|
const node = { getComponent: () => block,
|
|
getChildByName: () => ({ getComponent: () => ({ startBoom: () => events.push('bomb') }) }) };
|
|
const map = Object.assign(new Controller(), { gameStart: true, pause: false });
|
|
map.unlockSpawnGateBatch([null, { destroyed: true }, { getComponent: () => null }, node]);
|
|
assert.deepEqual(events, ['unlock', 'wash', 'bomb']);
|
|
});
|
|
|
|
test('initial washing waits for adhesive members and mobile floor setup, including non-clear targets', () => {
|
|
const events = [], frames = [], timers = [];
|
|
const infos = [
|
|
{ id: 1, block: 1, type: 9, color: 2, adhesiveTime: 2, floorMove: true },
|
|
{ id: 2, block: 0, type: 9, color: 3, adhesiveTime: 2 },
|
|
{ id: 3, block: 0, type: 0, color: 11 },
|
|
{ id: 4, block: 0, type: 22, color: 2 },
|
|
].map(info => ({ ...info, position: { x: 0, y: 0 } }));
|
|
const engine = { ...cc, fx: { GameConfig: { BLOCK_INFO: [infos] } }, instantiate() {
|
|
const component = {
|
|
init(info) { this.info = info; events.push('init' + info.id); },
|
|
tryWashDirt() {
|
|
assert.ok(this.info, 'all async members initialized');
|
|
assert.ok(events.includes('floor'), 'mobile floor overlay exists before washing');
|
|
events.push('wash' + this.info.id);
|
|
},
|
|
};
|
|
return { set parent(value) { value.children.push(this); }, setPosition() {}, getComponent: () => component };
|
|
} };
|
|
const Init = loadMembers('assets/Script/Map.ts', ['blockInit'], {
|
|
cc: engine, performance: { now: () => 0 }, setTimeout: fn => timers.push(fn),
|
|
});
|
|
const map = Object.assign(new Init(), {
|
|
node: { children: [] }, blocks: [], blocks2: [], boom17Blocks: [], blockNum: 0,
|
|
Block_Array: [{}, {}], sortBlock: blocks => blocks,
|
|
scheduleOnce: fn => frames.push(fn), addMoveFloor() { events.push('floor'); this.blocks2 = []; },
|
|
});
|
|
map.blockInit(() => events.push('complete'));
|
|
while (frames.length) frames.shift()();
|
|
assert.equal(events.some(event => event.startsWith('wash')), false);
|
|
while (timers.length) timers.shift()();
|
|
assert.deepEqual(events.filter(event => event.startsWith('wash')), ['wash1', 'wash2', 'wash3', 'wash4']);
|
|
assert.equal(events.at(-1), 'complete');
|
|
assert.equal(map.blocks.length, 2, 'gray and monochrome stay outside clear-target counting');
|
|
});
|
|
|
|
test('MapBlock activation is reset on reuse and legacy init defaults to ordinary terrain', () => {
|
|
const Tile = loadMembers('assets/Script/MapBlock.ts', ['init']);
|
|
const icon = { active: false };
|
|
const tile = Object.assign(new Tile(), { node: { getChildByName: name => name === 'xizao' ? icon : null } });
|
|
tile.init(2, 3, true);
|
|
assert.equal(tile.washing, true);
|
|
assert.equal(icon.active, true);
|
|
tile.init(4, 5);
|
|
assert.equal(tile.washing, false);
|
|
assert.equal(icon.active, false);
|
|
assert.deepEqual([tile.posX, tile.posY], [4, 5]);
|
|
});
|
|
|
|
test('terrain prefab binds the supplied washing and four dirt assets without baking dirt into terrain', () => {
|
|
const root = path.resolve(__dirname, '..');
|
|
const json = file => JSON.parse(fs.readFileSync(path.join(root, file), 'utf8'));
|
|
const data = json('assets/prefab/map/MapBlock.prefab');
|
|
const icon = data.find(item => item.__type__ === 'cc.Node' && item._name === 'xizao');
|
|
assert.ok(icon, 'washing child exists');
|
|
assert.equal(icon._active, false, 'ordinary terrain keeps the old appearance');
|
|
assert.deepEqual([icon._contentSize.width, icon._contentSize.height], [120, 120]);
|
|
const sprite = icon._components.map(ref => data[ref.__id__]).find(item => item.__type__ === 'cc.Sprite');
|
|
assert.equal(sprite._spriteFrame.__uuid__, json('assets/UI/prop/niba/xizao.png.meta').subMetas.xizao.uuid);
|
|
const component = data.find(item => Array.isArray(item.dirtFrames));
|
|
const expected = [1, 2, 3, 4].map(index => {
|
|
const meta = json(`assets/UI/prop/niba/niba${index}.png.meta`);
|
|
return Object.values(meta.subMetas)[0].uuid;
|
|
});
|
|
assert.deepEqual(component.dirtFrames.map(ref => ref.__uuid__), expected);
|
|
assert.equal(component.washingBubble.__uuid__, json('assets/UI/prop/niba/paopao.png.meta').subMetas.paopao.uuid);
|
|
const map = Object.assign(new Controller(), { MapBlockPrefab: { data: { getComponent: () => component } } });
|
|
assert.equal(map.getDirtFrames(), component.dirtFrames);
|
|
assert.equal(map.getWashingBubble(), component.washingBubble);
|
|
});
|