From 2b685011e9b3ef44330fdf021be40c095dffeb24 Mon Sep 17 00:00:00 2001 From: sganggs Date: Tue, 13 Oct 2026 14:20:02 +0800 Subject: [PATCH] =?UTF-8?q?0.2.6-dev:=20browser=20tests=20read=20the=20rou?= =?UTF-8?q?nd's=20real=20rules=20=E2=80=94=20free=20deploy=20tiles=20come?= =?UTF-8?q?=20from=20the=20client's=20own=20placement=20legality=20(terrai?= =?UTF-8?q?n=20=E6=9C=BA=E5=8F=98=20cards,=20devices,=20the=20boss=20half)?= =?UTF-8?q?=20instead=20of=20the=20stage's=20plain=20tiles,=20the=20facing?= =?UTF-8?q?=20tour=20waits=20for=20a=20later=20round=20when=20its=20purcha?= =?UTF-8?q?se=20merged=20into=20an=20elite,=20and=20real.e2e=20skips=20its?= =?UTF-8?q?=20one=20strategy=20turn=20only=20while=20=E8=B7=B3=E8=BF=87=20?= =?UTF-8?q?is=20enabled=20(PR=20#546);=20golden:=20unchanged?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/client.mjs | 37 ++++++++++++++++++++++++++++++------- test/e2e/coop.e2e.mjs | 4 +++- test/ui/real.e2e.test.js | 12 +++++++++--- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/test/e2e/client.mjs b/test/e2e/client.mjs index 982fde27..dc9126a4 100644 --- a/test/e2e/client.mjs +++ b/test/e2e/client.mjs @@ -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); } diff --git a/test/e2e/coop.e2e.mjs b/test/e2e/coop.e2e.mjs index 29f157f6..52b471a8 100644 --- a/test/e2e/coop.e2e.mjs +++ b/test/e2e/coop.e2e.mjs @@ -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) { diff --git a/test/ui/real.e2e.test.js b/test/ui/real.e2e.test.js index 09b3aef2..54e5367d 100644 --- a/test/ui/real.e2e.test.js +++ b/test/ui/real.e2e.test.js @@ -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', '确认选择');