Files
ark-stronghold-protocol/test/match/feedback5-we2-diy-grants.test.js
T
sganggs 7b83028139 feedback5 WE2: effects, reward pools and 机变 cards that grant a random operator also draw the player's 自选 pieces (#9)
「自选干员放入后模拟中的补给池随机范围也将被相应扩大」: player/diy.js diyStockEntries (every
slotted piece with copies left; the roll's own tier rules, no 调度中心 gate) joins
effectsMeta ctx.rollChess / ctx.rollPool (spDraft rollPool extra + chessOf: bonds through
the player's view) and the 驰援 fallback in choices.js. Granted from the player's own
stock (poolOf). Docs: META, PLAYING. Golden: no scenario moved.
2026-10-06 15:42:59 +08:00

79 lines
4.4 KiB
JavaScript

// test/match/feedback5-we2-diy-grants.test.js — 0.2.0 WE2 #9: an effect / reward / 机变 card that grants a player a random
// operator draws that player's 自选 stock too (「自选干员放入后模拟中的补给池随机范围也将被相应扩大」; PRTS 新手教程 "调度中心随机资源
// 的范围将被扩大"): effectsMeta ctx.rollChess / ctx.rollPool (player/diy.js diyStockEntries), choices.js 驰援 fallback —
// the roll's own tier rules, bonds through the player's view; never another player's stock.
// Run: node --test test/match/feedback5-we2-diy-grants.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeCtx } from '../../server/match/effectsMeta.js';
import { applyCard, tacticCard } from '../../server/match/choices.js';
import { makeMatch, checkInvariants } from './harness.js';
const T5A = 'chess_char_5_diy1_a';
const SIEGE = 'char_112_siege'; // 推进之王 (维多利亚)
const PICK = { charId: SIEGE, skillIndex: 2, uniEquipId: 'uniequip_002_siege' };
/** A co-op match: p_0 slots 推进之王 at T5A, p_1 slots nothing; first prep. */
function prep(seed = 911) {
const seats = [
{ seat: 0, playerId: 'p_0', name: 'P0', isBot: false, connected: true, diy: { [T5A]: PICK } },
{ seat: 1, playerId: 'p_1', name: 'P1', isBot: false, connected: true },
];
const h = makeMatch({ mode: 'coop', difficulty: 'NORMAL', seats, seed }).start();
h.toPrep(1);
return { h, m: h.m, p0: h.ps('p_0'), p1: h.ps('p_1') };
}
/** Take every shared-pool copy of the chess `pred` admits (the draw then sees only what is left); m.restore() gives them back. */
function drain(m, pred) {
const back = (m.drained ??= []);
for (const [id, e] of m.pool.entries) if (pred(id, e) && e.left > 0) back.push([id, m.pool.take(id, e.left)]);
m.restore = () => { for (const [id, n] of back.splice(0)) m.pool.give(id, n); };
}
test('#9 effect rolls (ctx.rollChess, any tier / bond filter) draw the player\'s 自选 stock — its own player only', () => {
const { m, p0, p1 } = prep();
assert.equal(p0.diyStock.left(T5A), 8);
drain(m, (id, e) => e.tier === 5);
const c0 = makeCtx(m, p0, { key: 'test' }, 'onTest');
const c1 = makeCtx(m, p1, { key: 'test' }, 'onTest');
assert.equal(c0.rollChess({ tier: 5 }), T5A, 'the only tier-5 copies left: her stock');
assert.equal(c1.rollChess({ tier: 5 }), null, 'another player never draws it');
// the 调度中心 level is not the shop's gate here: an effect's tier rule alone (p_0 is at level 1)
assert.ok(p0.shop.level < 5);
// a bond filter reads the operator: 推进之王 is 维多利亚
drain(m, (id) => (m.gd.chess(id)?.bonds || []).includes('victoriaShip'));
assert.equal(c0.rollChess({ bond: 'victoriaShip' }), T5A);
assert.equal(c0.rollChess({ bond: 'yanShip', tier: 5 }), null, 'not a 炎 operator');
// granting it takes one copy of her own stock
assert.ok(c0.grantChess(T5A));
assert.equal(p0.diyStock.left(T5A), 7);
m.restore();
checkInvariants(m);
m.dispose();
});
test('#9 reward pools (ctx.rollPool: a shared-pool chess pool) and the 维多利亚驰援 card draw it too', () => {
const { m, p0, p1 } = prep(910);
drain(m, (id, e) => e.tier === 5);
const c0 = makeCtx(m, p0, { key: 'test' }, 'onTest');
const c1 = makeCtx(m, p1, { key: 'test' }, 'onTest');
assert.deepEqual(c0.rollPool('pool_chess_shop_5_reward'), { kind: 'chess', id: T5A, golden: false });
assert.equal(c1.rollPool('pool_chess_shop_5_reward'), null);
// 维多利亚驰援 (allybuff_select_7_7, single_special_choice_gain_bond_chess) with every 维多利亚 copy of the pool taken:
// the card grants her 推进之王 from her own stock
drain(m, (id) => (m.gd.chess(id)?.bonds || []).includes('victoriaShip'));
const card = tacticCard(m.gd.choices.cards.tactic.find((c) => c.effectId === 'allybuff_select_7_7'));
const owned = (ps) => [...ps.board.values(), ...ps.hand, ...ps.temp].filter((x) => x && x.kind === 'chess' && x.id === T5A).length;
const before = p0.diyStock.left(T5A);
applyCard(m, p0, card);
assert.equal(owned(p0), 1, 'she got her 自选 piece');
assert.equal(p0.diyStock.left(T5A), before - 1, 'from her stock');
const had1 = [...p1.hand, ...p1.temp].filter(Boolean).length;
applyCard(m, p1, card);
assert.equal([...p1.hand, ...p1.temp].filter(Boolean).length, had1, 'p_1 has no 自选 piece to draw: nothing');
m.restore();
checkInvariants(m);
m.dispose();
});