Files
ark-stronghold-protocol/test/match/prep-bench.test.js
T
sganggs db1b44bb71 feedback5 WE: the 整备区 rules of PRTS 卫戍协议/帮助 §手牌区 (GitHub #82, second part) — a full regular hand refuses every purchase and reward pick, also one whose copy would complete a merge at once ("当常规手牌区全满无空位时,玩家将无法执行使手牌溢出的操作" — "例如招募/购入等通常情况下会增加手牌的操作"; BWIKI 盟约 says the same with no exception; the reporter checked it in the official game), and a free regular slot pulls the 临时整备区 in at once ("常规手牌区出现空位时自动移入"): PlayerState._fillHandFromTemp runs in every recompute, the temp row right to left (the order it fills, [ASSUMED]) into the hand's free slots right to left — after a sale, a deployment from the hand, a merge that consumed hand copies, an item equipped / destroyed / used, a summon stack removed with its owner. invariants.js: temp holds pieces only while the hand is full. The client's refusal path follows (gameLogic.shopBlockReason: 整备区已满 on every card — a one-line change in a file the R5 split moves). Bots: a full hand never tries a purchase, an affordable third copy of a held pair sheds the weakest hand single first (never one of this prep), resolveTemp decides temp right to left. Tests: test/match/feedback5-hand.test.js; merge / hardening / playtest6-elite-to-board / economy / prep-bench / uiFixes updated to the rule. Docs: META §1 Hand, DESIGN §6, PLAYING §3, research 00-INDEX / 01 (the old "a merge-completing purchase is allowed" corrected). Golden: 3 match scenarios moved (coop2-NORMAL-3, solo-HARD-9-boosted, coop2-ABYSS-10-boosted — bot seats that reached a full hand: the merge-completing purchase refused or a single shed first, temp pieces moving in by themselves); every battle digest unchanged
2026-10-05 18:17:08 +08:00

144 lines
7.6 KiB
JavaScript

// 观战队友的整备区(游玩记录 #2 item 1「观战队友时看不到队友的未上场的干员」,建议见 GitHub #44
// 「看不见队友的待战栏位」):prep 侦察的 m.field(`Match.prepFieldMeta`)把被侦察玩家的手牌当作
// 单位放进手牌行(row 7,col = 手牌槽位,道具是 kind 'item')——客户端按自己整备区的同一渲染路径
// 画出这一行(app.js battleView / ItemView)。手牌变化(部署 / 购入 / 装备)跟棋盘变化一样触发
// 推送(`_prepScoutSig`);观战席副本与玩家完全一致(test/match/spectator.test.js)。客户端:
// test/ui/prep-bench.test.js。
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { GEO, PHASE } from '../../shared/constants.js';
import { DATA, makeMatch, give, giveItem, legalTileFor, chessOfTier } from './harness.js';
const MELEE = (c) => c.position === 'MELEE' && c.profession === 'TANK';
test('#44 prep scout: held pieces are units on the hand row; deploying moves them onto the field', () => {
const S = 's_spec';
const h = makeMatch({ mode: 'coop', humans: 2, seed: 44, fake: true, spectators: [S] }).start();
h.toPrep(1);
const m = h.m;
assert.equal(m.phase, PHASE.PREP);
const b = h.ps('p_1');
assert.deepEqual(m.handle('p_1', { t: 'g.ready', ready: false }), { ok: true });
for (const p of [...b.board.values(), ...b.hand.filter(Boolean), ...b.temp.filter(Boolean)]) {
if (p.kind === 'chess') b.returnCopies(p);
}
b.board.clear();
b.hand.fill(null);
b.temp.fill(null);
b.recompute();
const id = chessOfTier(1, MELEE).find((x) => m.pool.has(x));
const piece = give(m, b, id); // into the hand
assert.deepEqual(m.handle('p_0', { t: 'g.watch', fieldId: 'n:p_1' }), { ok: true });
assert.deepEqual(m.handle(S, { t: 'g.watch', fieldId: 'n:p_1' }), { ok: true });
for (const pid of ['p_0', S]) {
const first = h.lastTo(pid, 'm.field');
assert.equal(first.prep, true);
const u = first.units.find((x) => x.uid === piece.uid);
assert.ok(u, `${pid}: the held operator is a unit of the scout`);
assert.deepEqual([u.x, u.y], [0, GEO.HAND_ROW], `${pid}: standing on the first hand slot`);
assert.equal(u.kind, 'op');
assert.equal(u.items, undefined, 'no items equipped yet');
}
assert.deepEqual(h.lastTo('p_0', 'm.field').effects, h.ps('p_1').effectsView(), 'the scouted effects column rides the meta');
// deploying it is a hand AND a board change: one new push with the piece on the field and off the hand row
const [r, c] = legalTileFor(m, b, id);
assert.deepEqual(m.handle('p_1', { t: 'g.move', uid: piece.uid, to: { area: 'board', row: r, col: c }, dir: 'LEFT' }), { ok: true });
const last = h.lastTo('p_0', 'm.field');
const u = last.units.find((x) => x.uid === piece.uid);
assert.deepEqual([u.x, u.y], [c, r], 'deployed onto the watched board');
assert.equal(last.units.some((x) => x.uid === piece.uid && x.y === GEO.HAND_ROW), false, 'and gone from the hand row');
m.dispose();
});
test('#44 prep scout: a held item is a kind-item unit on the hand row; equipped items ride their operator', () => {
const h = makeMatch({ mode: 'coop', humans: 2, seed: 46, fake: true }).start();
h.toPrep(1);
const m = h.m;
const b = h.ps('p_1');
assert.deepEqual(m.handle('p_1', { t: 'g.ready', ready: false }), { ok: true });
for (const p of [...b.board.values(), ...b.hand.filter(Boolean), ...b.temp.filter(Boolean)]) {
if (p.kind === 'chess') b.returnCopies(p);
}
b.board.clear();
b.hand.fill(null);
b.temp.fill(null);
b.recompute();
const id = chessOfTier(1, MELEE).find((x) => m.pool.has(x));
const op = give(m, b, id);
const item = giveItem(m, b, 'chess_item_1_01_e_a'); // 维式重锤, into the hand
assert.deepEqual(m.handle('p_0', { t: 'g.watch', fieldId: 'n:p_1' }), { ok: true });
const first = h.lastTo('p_0', 'm.field');
const iu = first.units.find((x) => x.uid === item.uid);
assert.ok(iu, 'the held item is a unit of the scout');
assert.equal(iu.kind, 'item');
assert.equal(iu.y, GEO.HAND_ROW);
// equip the item onto the operator (still in the hand): the operator's unit carries the item id
assert.deepEqual(m.handle('p_1', { t: 'g.equip', itemUid: item.uid, targetUid: op.uid }), { ok: true });
const after = h.lastTo('p_0', 'm.field');
assert.equal(after.units.some((x) => x.uid === item.uid), false, 'the equipped item left the hand row');
const ou = after.units.find((x) => x.uid === op.uid);
assert.deepEqual(ou.items, ['chess_item_1_01_e_a'], 'the operator scouts with its equipment');
m.dispose();
});
test("PR #129 review: moving a hand piece to another slot is a scout change (x follows the slot), and the temp row rides along", () => {
const h = makeMatch({ mode: 'coop', humans: 2, seed: 47, fake: true }).start();
h.toPrep(1);
const m = h.m;
const b = h.ps('p_1');
assert.deepEqual(m.handle('p_1', { t: 'g.ready', ready: false }), { ok: true });
for (const p of [...b.board.values(), ...b.hand.filter(Boolean), ...b.temp.filter(Boolean)]) {
if (p.kind === 'chess') b.returnCopies(p);
}
b.board.clear();
b.hand.fill(null);
b.temp.fill(null);
b.recompute();
const id = chessOfTier(1, MELEE).find((x) => m.pool.has(x));
const a = give(m, b, id, 'hand', 0);
// a full hand (distinct plain equipment: nothing merges), so the temp piece stays in temp — a free hand slot would pull
// it in (PRTS 卫戍协议/帮助 §手牌区 "常规手牌区出现空位时自动移入")
const plain = Object.values(DATA.items).filter((i) => i.itemType === 'EQUIP' && !i.isGolden && i.kind === 'passive').map((i) => i.itemId ?? i.id);
for (let i = 1; i < b.hand.length; i++) b.hand[i] = b.newPiece('item', plain[i]);
const c = give(m, b, id, 'temp', 0); // 临时整备区: PRTS 帮助把它算进手牌区,一起侦察
assert.ok(b.temp.includes(c));
assert.deepEqual(m.handle('p_0', { t: 'g.watch', fieldId: 'n:p_1' }), { ok: true });
const first = h.lastTo('p_0', 'm.field');
const tu = first.units.find((x) => x.uid === c.uid);
assert.deepEqual([tu.x, tu.y], [GEO.TEMP_C0, GEO.TEMP_ROW], 'the temp piece scouts on the temp row (first slot, col 4)');
assert.equal(first.units.find((x) => x.uid === a.uid).x, 0);
// a hand piece moved to another slot keeps uid and id — the slot must be in the signature for the push to fire
assert.deepEqual(m.handle('p_1', { t: 'g.move', uid: a.uid, to: { area: 'hand', idx: 3 } }), { ok: true });
const last = h.lastTo('p_0', 'm.field');
assert.ok(last, 'a new scout push arrived');
assert.equal(last.units.find((x) => x.uid === a.uid).x, 3, 'the piece stands on its new hand slot');
m.dispose();
});
test('#44 prep scout: an empty hand adds no units and never an m.private to a spectator seat', () => {
const S = 's_spec';
const h = makeMatch({ mode: 'coop', humans: 2, seed: 45, fake: true, spectators: [S] }).start();
h.toPrep(1);
const m = h.m;
const b = h.ps('p_1');
assert.deepEqual(m.handle('p_1', { t: 'g.ready', ready: false }), { ok: true });
for (const p of [...b.board.values(), ...b.hand.filter(Boolean), ...b.temp.filter(Boolean)]) {
if (p.kind === 'chess') b.returnCopies(p);
}
b.board.clear();
b.hand.fill(null);
b.temp.fill(null);
b.recompute();
assert.deepEqual(m.handle('p_0', { t: 'g.watch', fieldId: 'n:p_1' }), { ok: true });
assert.deepEqual(m.handle(S, { t: 'g.watch', fieldId: 'n:p_1' }), { ok: true });
const first = h.lastTo('p_0', 'm.field');
assert.equal(first.units.length, 0, 'nothing held, nothing deployed: no units');
assert.equal(first.bench, undefined, 'no separate bench field (the hand rides units)');
assert.equal(h.allTo(S, 'm.private').length, 0, 'still no private state to a scout');
m.dispose();
});