| 1 | |
| 2 | |
| 3 | |
| 4 | const CAMP = { 主公: 'lord', 忠臣: 'lord', 反贼: 'rebel', 内奸: 'renegade' }; |
| 5 | const KEEP = { 桃: 5, 闪: 4, 无懈可击: 3, 酒: 2, 杀: 1 }; |
| 6 | let turn = ''; |
| 7 | let made = new Map(); |
| 8 | |
| 9 | function 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; |
| 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 | |
| 31 | export 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 | } |