0.2.6-dev: browser tests read the round's real rules — free deploy tiles come from the client's own placement legality (terrain 机变 cards, devices, the boss half) instead of the stage's plain tiles, the facing tour waits for a later round when its purchase merged into an elite, and real.e2e skips its one strategy turn only while 跳过 is enabled (PR #546); golden: unchanged
This commit is contained in:
1 parent
a1f0d1bb6e
commit
2b685011e9
3 files changed
+42
-11
No files matched your search
+30
-7
@@ -342,25 +342,48 @@ export class Client {
|
||||
await m.up();
|
||||
}
|
||||
|
||||
/** An empty legal tile for a piece (mirrors the deploy rules: melee on melee tiles, ranged anywhere). */
|
||||
/**
|
||||
* An empty legal tile for a piece: the client's own legality (gameLogic/placement.js boardTargets over the screen's
|
||||
* placement context — the stage's deploy tiles under the player's terrain 机变 cards and devices, the boss field's
|
||||
* half in a boss round), so a tile a round's 机变 card took away is never chosen; the data's plain deploy tiles
|
||||
* (melee on melee tiles, ranged anywhere) when that module cannot be read.
|
||||
*/
|
||||
freeTileFor(uid, { prefer = 'gate' } = {}) {
|
||||
return this.page.evaluate((uid, prefer) => {
|
||||
return this.page.evaluate(async (uid, prefer) => {
|
||||
const { store, data } = globalThis.__SP__;
|
||||
const s = store.get();
|
||||
const priv = s.match.private;
|
||||
const st = data.lookup('stages', s.match.public.stageId);
|
||||
const pub = s.match.public;
|
||||
const st = data.lookup('stages', pub.stageId);
|
||||
const piece = [...priv.hand, ...priv.temp, ...priv.board].find((p) => p && p.uid === uid);
|
||||
if (!piece || !st?.deployTiles?.normal) return null;
|
||||
const order = (list) => {
|
||||
const a = list.slice();
|
||||
if (prefer === 'gate') a.sort((x, y) => (y[1] - x[1]) || (x[0] - y[0]));
|
||||
else a.sort((x, y) => (x[1] - y[1]) || (y[0] - x[0]));
|
||||
return a;
|
||||
};
|
||||
const used = new Set(priv.board.map((p) => `${p.row},${p.col}`));
|
||||
try {
|
||||
const P = await import('/js/ui/gameLogic/placement.js');
|
||||
const C = await import('/js/ui/gameLogic/camera.js');
|
||||
const ctx = P.placementContext({
|
||||
priv, stage: st, editable: true, field: C.deployFieldOf(pub, s.me?.playerId ?? s.me),
|
||||
getChess: (id) => data.lookup('chess', id), getToken: (id) => data.lookup('tokens', id),
|
||||
getItem: (id) => data.lookup('items', id), getEffect: (id) => data.lookup('effects', id), backups: data.get('backups'),
|
||||
});
|
||||
const legal = P.boardTargets(ctx, uid).legal.filter(([r, c]) => !used.has(`${r},${c}`));
|
||||
if (legal.length) { const [r, c] = order(legal)[0]; return { row: r, col: c }; }
|
||||
return null;
|
||||
} catch { /* the plain deploy tiles below */ }
|
||||
const rec = piece.kind === 'token' ? data.lookup('tokens', piece.id) : data.lookup('chess', piece.id);
|
||||
const melee = rec?.position === 'MELEE';
|
||||
const dt = st.deployTiles.normal;
|
||||
const tiles = melee ? dt.melee : [...dt.rangedOnly, ...dt.melee];
|
||||
const used = new Set(priv.board.map((p) => `${p.row},${p.col}`));
|
||||
const free = tiles.filter(([r, c]) => !used.has(`${r},${c}`));
|
||||
if (!free.length) return null;
|
||||
if (prefer === 'gate') free.sort((a, b) => (b[1] - a[1]) || (a[0] - b[0]));
|
||||
else free.sort((a, b) => (a[1] - b[1]) || (b[0] - a[0]));
|
||||
return { row: free[0][0], col: free[0][1] };
|
||||
const [r, c] = order(free)[0];
|
||||
return { row: r, col: c };
|
||||
}, uid, prefer);
|
||||
}
|
||||
|
||||
|
||||
@@ -798,7 +798,9 @@ async function facingTour(c, did) {
|
||||
if (!did.item && !(await c.handPieces('item')).length) did.itemBought = await buyOne(c, '.shopbar__item .scard:not(.scard--sold):not(.is-disabled)').catch(() => false);
|
||||
const units = () => c.handPieces().then((h) => h.filter((p) => p.kind === 'chess'));
|
||||
let hand = await units();
|
||||
assert.ok(hand.length >= 1, 'bought operators');
|
||||
// a bought operator can merge into an elite at once (two copies already fielded) and leave the bench empty: the
|
||||
// placement steps then wait for a later round (`did` carries them over)
|
||||
if (!hand.length) { c.note('facing: no operator on the bench this round (the bought ones merged)'); return; }
|
||||
// place + choose a direction (UP: striped rotated range + 拖回中心区域取消 while held) — the unit with the widest range
|
||||
// grid shows it best (a defender's grid is its own tile only)
|
||||
if (!did.place) {
|
||||
|
||||
@@ -500,10 +500,16 @@ describe('real server + real browsers', { skip: !ENABLED && 'set SP_REAL_E2E=1 (
|
||||
if (s.phase !== 'BAND_DRAFT' || picked.has(c.label) || s.draft?.turn !== s.me) continue;
|
||||
if (!shotDraft) { await sleep(600); await c.shot('draft'); shotDraft = true; }
|
||||
if (c === guest && !guestSkipped) {
|
||||
await c.click('.draft-detail__btns .btn', '跳过');
|
||||
guestSkipped = true;
|
||||
c.note('skipped once');
|
||||
continue;
|
||||
// one skip — only while someone is left to pass to: the last player still to pick sees 跳过 disabled (PR #546)
|
||||
const canSkip = await c.page.evaluate(() => [...document.querySelectorAll('.draft-detail__btns .btn')]
|
||||
.some((b) => b.textContent.includes('跳过') && !b.disabled));
|
||||
if (canSkip) {
|
||||
await c.click('.draft-detail__btns .btn', '跳过');
|
||||
c.note('skipped once');
|
||||
continue;
|
||||
}
|
||||
c.note('跳过 disabled (the last player still to pick)');
|
||||
}
|
||||
await c.click('.dband:not(.is-taken)', null, { nth: c === host ? 3 : 7 });
|
||||
await c.click('.draft-detail__btns .btn--primary', '确认选择');
|
||||
|
||||
Reference in new issue
Block a user