Play liveChoose your opponent
Join
429632f
46 lines · 2.5 KB Raw Permalink History
1// The starter bot: plays the move the upstream AI recommends (obs.recommended, the MCP's recommendedAction), fills in
2// what that move leaves open (targets, cards to discard), and passes when it cannot. Change anything; keep the shape:
3// act(obs, legal) returns an index into legal, or a legal move's message with its params filled in.
4const CAMP = { 主公: 'lord', 忠臣: 'lord', 反贼: 'rebel', 内奸: 'renegade' };
5const KEEP = { 桃: 5, 闪: 4, 无懈可击: 3, 酒: 2, 杀: 1 };
6let turn = '';
7let made = new Map(); // moves made this turn: an ask nobody answers (激将) must not be repeated forever
8
9function fill(obs, move) {
10 const m = structuredClone(move.message);
11 const me = obs.players[obs.viewer];
12 if (move.validTargets.length && !Array.isArray(m.params.targets)) {
13 const ally = (i) => me.identity !== '内奸' && CAMP[obs.players[i].identity] === CAMP[me.identity];
14 const lord = obs.players.findIndex((p) => p.identity === '主公');
15 const foes = move.validTargets.filter((i) => !ally(i)).sort((a, b) => (me.identity === '反贼' ? (b === lord) - (a === lord) : 0));
16 if (!foes.length && move.category === 'play') return null; // only friends in reach: hold the card
17 const targets = foes.length ? foes : move.validTargets;
18 m.params.targets = targets.slice(0, Math.max(move.minTarget ?? 1, 1));
19 m.params.target = m.params.targets[0];
20 }
21 if (Array.isArray(m.params.cardIds) && !m.params.cardIds.length) {
22 const need = obs.pending?.discard?.min ?? 1;
23 const hand = [...(me.hand ?? [])].sort((a, b) => (KEEP[a.name] ?? 0) - (KEEP[b.name] ?? 0));
24 if (hand.length < need) return null;
25 m.params.cardIds = hand.slice(0, need).map((c) => c.id);
26 }
27 if (Array.isArray(m.params.allocation) && !m.params.allocation.length) return null;
28 return m;
29}
30
31export default function act(obs, legal) {
32 const now = `${obs.game}:${obs.turn.round}:${obs.currentPlayerIndex}`;
33 if (now !== turn) (turn = now), (made = new Map());
34 const refused = new Set((obs.refused ?? []).map((m) => JSON.stringify(m)));
35 const order = [obs.recommended, ...legal.keys()].filter((i) => i !== null && i !== undefined);
36 for (const i of order) {
37 const m = fill(obs, legal[i]);
38 const key = JSON.stringify(m);
39 if (m && !refused.has(key) && (made.get(key) ?? 0) < 2) {
40 made.set(key, (made.get(key) ?? 0) + 1);
41 return m;
42 }
43 }
44 const pass = legal.findIndex((l) => l.category === 'skip' || l.message.actionType === 'end');
45 return pass >= 0 ? pass : 0;
46}