diff --git a/src/ai/llm/llmPromptTemplates.ts b/src/ai/llm/llmPromptTemplates.ts index 1650c7c..30ce533 100644 --- a/src/ai/llm/llmPromptTemplates.ts +++ b/src/ai/llm/llmPromptTemplates.ts @@ -1,57 +1,39 @@ export const STATIC_LLM_GAME_RULES = `# Shengji / Tractor — Trick-Play Decision Guide -You are an expert Tractor player making ONE play at a genuinely close decision. Easy and forced plays are filtered out before you, and every option shown to you is already legal — don't re-check legality, just make the best JUDGEMENT call. Use the injected CURRENT STATE: treat the **Rule Score** as the engine's prior (higher = preferred lead), obey the **Trick Win Security** verdict, and exploit **confirmed voids** (players who couldn't follow a led suit; others may be void too, just unconfirmed). The **Points still live** readout estimates each off-suit's unseen point cards (some may hide in the kitty) — favour point-rich suits, don't chase drained ones. When a **GUIDANCE FOR THIS SEAT** block is present, it is these rules applied to your exact situation — treat it as your primary instruction. Output JSON only: {"reasoning":"","play":["",...]} — copy each card's bare notation from YOUR HAND; the ×N tag is a count, NOT part of the name, so repeat the notation to play a pair. - -## 1. Setup -- Counter-clockwise teams: South(human)+North(bot2)=Team A vs East(bot1)+West(bot3)=Team B; your teammate is named in CURRENT STATE. Trick order: Leader → 2nd → 3rd → 4th (4th has perfect info). -- Attackers win the round by capturing 80+ points; defenders win by holding them under 80. Points exist only in 5 (5pts), 10 (10pts), K (10pts). - -## 2. Card strength (high → low) -- Trump group is ONE combined suit: Big Joker > Small Joker > trump-rank in trump suit > trump-rank in other suits (all EQUAL; first played wins) > trump-suit regulars (A>K>…>3). -- **Active ranks** = the trump-rank card in every suit; they belong to the TRUMP GROUP (not their printed suit) and beat any Ace. They are ELITE — never spend as fodder or lead away cheaply; if the trump rank is also a point card (5/10/K), protect it doubly. -- Off-suit: the highest unplayed card of a suit (A, or K if A is the trump rank) is "boss" — unbeatable unless trumped. Cross-suit cards can't beat each other; only trump beats off-suit. -- No-Trump round: trump = jokers + the four active ranks only (no trump regulars) → trump is scarce; hoard it, off-suit bosses near-untouchable. - -## 3. Combos & tractors -- Single; Pair (2 identical); Tractor (2+ consecutive pairs in one suit/trump group). -- Trump tractor order: trump-suit A → off-suit rank → trump-suit rank → SJ → BJ. Two off-suit-rank pairs are equal (not consecutive); only off-suit-rank pair + trump-suit-rank pair links. -- Off-suit tractors skip the active rank (6-8 is consecutive when 7 is trump rank). Don't break a pair/tractor to fill a smaller combo when a standalone option exists. - -## 4. Following — fixed rules -- Follow the led suit while you hold it — you can't play off-suit (or shed a 5/10/K elsewhere) when you still have cards of the led suit. Trump led ⇒ trump follows: all your trump (suit + ranks + jokers) is the led "suit"; you can't duck with a side card while holding trump. -- To beat the led card you need a strictly HIGHER same-suit card (or a ruff when void); an equal card loses — earlier play wins ties. Matching a boss already winning — your A onto a led A (opponent's OR teammate's) — wins nothing, so prefer to duck low and save your boss for a trick you can actually take. -- A pair needs a pair, a tractor needs a tractor (two singles can't beat a pair). Exhaust pairs before singles. - -## 5. Following — decision order (stop at first match) -1. **Teammate winning AND safe** (Trick Win Security = SECURED/LIKELY): the trick is theirs — bank the MOST points you can spare, giving your 10s and Ks freely (card rank is moot once the win is locked; a K that isn't your own boss wins nothing later either, so cash its 10 pts now). Hold back only a live boss A/K you can cash on your OWN trick. Cheap trump is fine when void. NEVER out-rank or over-trump your teammate's own winning card. -2. **Teammate winning but UNCERTAIN**: don't feed points (an opponent may still steal them) and don't waste strength — play a low non-point card of the led suit. -3. **Opponent winning, ≥10 pts on table**: fight only with a card that survives the seats still to play — and the more points on the table, the firmer this is. In trump a mid K/10 is NOT beat-back-proof (active ranks/jokers over-trump it), so raising with a beatable point card just feeds the pot you'll lose — secure it with a truly unbeatable trump, or duck low. Ruff if void and worthwhile. Can't win → step 5. -4. **Opponent winning, <10 pts**: duck — play low, conserve. Don't spend a boss/trump on a near-empty trick. -5. **Can't/won't win — disposal**: play lowest non-point cards; dump small DIFFERENT singles rather than break a valuable pair. If forced to add trump you can't win with, use your WEAKEST trump-suit regular (3,4…), never an active rank or joker. NEVER discard 5/10/K into an opponent's trick. - -## 6. Ruffing when void -- Ruff only to secure a worthwhile trick (≥10 pts) or block opponents; otherwise conserve trump (especially No-Trump) — when void and not ruffing, lean toward shedding loose off-suit non-points over trump (even a low trump can ruff later), but a lone low trump is usually worth less than an off-suit pair/tractor or a live boss A (suit not yet void), so letting it go to keep those is often the better trade. -- Size the ruff to who's left: to win a rich trick, ruff high enough to survive a later void player's over-ruff, not a bare minimum that gets topped (when last or points are small, the lowest sufficient trump is enough). If you'll be out-ruffed regardless, don't burn a high trump — use a point/intermediate trump to force a bigger one. -- Don't ruff over a teammate already winning safely; let a low side card do it. - -## 7. Multi-combo -- Lead: non-trump only; legal when every component is unbeatable vs unseen cards, or all three others are void in that suit. -- Follow: match structure + total length; void players may cut with matching trump structure. Trump vs trump: compare highest component type (Tractor>Pair>Single), then highest card. - -## 8. Leading — decision order -Trust the Rule Score ordering; deviate only with a clear reason. Among close options: -1. Lead off-suit boss A/K to seize control and bank points safely (in No-Trump, lead bosses — never spend jokers/ranks). -2. Bleed opponent trump with a LOW trump pair/tractor only when you hold surplus trump to strip them before cashing bosses; avoid high trump pairs and single trumps. -3. To probe voids or shed cards, lead a LOW off-suit single — never a trump combo; keep trump pairs/tractors to ruff and win tricks. Lead into an opponent's void to force their trump. -4. Lead points to a teammate void in that suit so they can ruff and bank them. -Never lead a lone active rank or joker — premium control you keep. - -## 9. Position cues -- 2nd: partial info; teammate (4th) can still cover, so commit early only with a clear boss when points are up. -- 3rd: blocking seat; back a strong teammate or force/block a weak one — but any block must survive the 4th seat, and never over-trump your teammate. -- 4th: perfect info; act precisely to the trick with no waste. - -Conservation through-line: keep top trump (jokers, active ranks, high pairs) and off-suit bosses for moments that matter — winning big-point tricks, blocking, guaranteed leads. Spend the cheapest non-point card when a play won't win or help your teammate. +## 1. Objectives & Points +- **Team Roles**: Attacking vs defending roles are specified in the **## Current State** block (injected in the prompt below). +- **Victory Condition**: Attackers win the round by capturing 80+ points; defenders win by keeping attackers under 80 points. +- **Point Cards**: Points exist only on 5 (5pts), 10 (10pts), and K (10pts) cards. + +## 2. Card Strength (High → Low) +- **Trump Group**: Treated as ONE combined suit: Big Joker > Small Joker > trump-rank in trump suit > trump-rank in other suits (equal; first played wins) > trump regulars (A > K > ... > 3). +- **Active Ranks**: The trump-rank cards in all suits belong to the Trump Group, not their printed suit. They beat any Ace. Protect them and never waste them cheaply. +- **Off-Suit**: The highest unplayed card of a suit (A, or K if A is trump rank) is "boss" — unbeatable unless ruffed. Cross-suit cards cannot beat each other. +- **No-Trump Round**: No trump suit declared. The Trump Group consists ONLY of Jokers and the four active ranks (no other cards are trump). Trump is extremely scarce — hoard it, and off-suit bosses are near-untouchable. + +## 3. Combos & Tractors +- **Combo Types**: Single (1 card); Pair (2 identical cards); Tractor (2+ consecutive pairs in one suit/trump group). +- **Trump Tractor Sequence**: Trump A → off-suit rank → trump rank → SJ → BJ. Two off-suit rank pairs are equal (not consecutive). +- **Off-Suit Tractor Sequence**: Consecutive ranks skip the active rank (e.g. 6-8 is consecutive when 7 is trump rank). Do not break a pair/tractor to fill a smaller combo if a standalone option exists. + +## 4. Leading Strategy +Trust the **Rule Score** ordering as your baseline, cashing bosses first and bleeding opponents' trumps only with low trump pairs if you have excess trump. +- **Boss Cash-out**: Lead off-suit boss A/K to score points safely. Never lead high trump combos or lone active ranks/jokers cheaply. +- **Probe Voids / Discard**: Lead low off-suit singles/rubbish to probe voids, or feed points to a void teammate to ruff. +- **Multi-Combo Leads**: Leading a multi-combo (multiple combos of the same suit played at once, non-trump only) is legal ONLY when every component is unbeatable (boss) or all three other players are void in that suit. + +## 5. Following Strategy & Constraints +- **Absolute Laws**: Follow the led suit/trump group if you hold it. You must match the led combo structure — obey the requirements in **## Suit-Following Analysis** (provided in the prompt below) and NEVER split pairs when matching structure is required (e.g. if a pair/tractor is led and you hold pairs, you must follow with them). If a multi-combo is led, match the combo structure and total length. +- **Seat Guidance**: Obey the situation-specific bullet under **## Seat Guidance** (provided in the prompt below) as your primary instruction. It dynamically tells you when to duck low, bank points on a safe teammate's trick, ruff/sluff, or conserve your elite cards. +- **Ruffing & Sluffing (Void)**: Ruff only to secure a worthwhile trick (≥10 pts) or block opponents. Size your ruff high enough to survive later players' over-ruffs; if you'll be out-ruffed regardless, use a low card to sluff instead of wasting trump. Never ruff over a teammate who is winning safely. +- **Conserve Control**: Keep your high trumps and off-suit bosses for tricks you lead or can absolutely win. Spend your cheapest non-point cards on lost tricks. + +## 6. Position Cues +- **2nd Seat**: Commit early only with a clear boss when points are up. Teammate (4th) can still cover. +- **3rd Seat**: Back a strong teammate or block an opponent, but ensure your play survives the 4th seat. Never over-trump your teammate. +- **4th Seat**: Perfect information. Act precisely to win the trick or dump trash with zero waste. + +Conservation through-line: Keep top trumps and off-suit bosses for moments that matter (winning big tricks, blocking, guaranteed leads). Dump cheap non-point cards when you cannot win. `; export interface UserPromptTemplateArgs { @@ -75,36 +57,89 @@ export interface UserPromptTemplateArgs { taskInstructionStr: string; } -/** - * Builds the dynamic user prompt using injected state variables. - */ -export function buildUserPromptTemplate(args: UserPromptTemplateArgs): string { - return `=== CURRENT STATE === -- You: ${args.playerId} — Team ${args.teamId}, teammate ${args.partnerId} -- Role: ${args.isAttacking ? "ATTACKING (capture 80+ pts to win the round)" : "DEFENDING (hold attackers under 80 pts)"} -- Attackers have captured ${args.attackingPoints} / 80 pts so far +// 1. Current State Block +function buildCurrentStateBlock(args: UserPromptTemplateArgs): string { + return `## Current State +- Player: ${args.playerId} (Team ${args.teamId}, partner: ${args.partnerId}) +- Role: ${args.isAttacking ? "Attacking (capture 80+ pts)" : "Defending (limit to <80 pts)"} +- Attacking team points: ${args.attackingPoints} / 80 - Trump: rank ${args.trumpRank}, suit ${args.trumpSuit} -- Points still live in off-suits (in others' hands or the hidden kitty — reference, not exact): ${args.liveSuitPointsStr} +- Live off-suit points (unseen): ${args.liveSuitPointsStr}`; +} -=== RECENT TRICKS === -${args.historyStr} +// 2. History Block +function buildHistoryBlock(args: UserPromptTemplateArgs): string { + return `## Recent Tricks +${args.historyStr}`; +} -=== CONFIRMED VOIDS === -${args.voidsStr} +// 3. Voids Block +function buildVoidsBlock(args: UserPromptTemplateArgs): string { + return `## Confirmed Voids +${args.voidsStr}`; +} -=== ACTIVE TRICK === -${args.activeTrickStatusStr} -${args.seatGuidanceStr ? `\n=== GUIDANCE FOR THIS SEAT (the rules applied to your exact situation — your primary instruction) ===\n${args.seatGuidanceStr}\n` : ""} -=== YOUR HAND (grouped by suit, strongest → weakest) === -${args.handCardsCount} cards in hand: +// 4. Active Trick Block +function buildActiveTrickBlock(args: UserPromptTemplateArgs): string { + return `## Active Trick +${args.activeTrickStatusStr}`; +} -${args.handChoicesStr} +// 5. Hand Block +function buildHandBlock(args: UserPromptTemplateArgs): string { + return `## Your Hand (grouped by suit, strongest → weakest) +${args.handChoicesStr}`; +} -${args.isLeading ? "=== LEAD OPTIONS (Rule Score = engine's prior) ===" : "=== SUIT-FOLLOWING ANALYSIS ==="} -${args.isLeading ? args.candidateOptionsStr : args.suitAnalysisStr} +// 6. Lead Options Block +function buildLeadOptionsBlock(args: UserPromptTemplateArgs): string { + return `## Lead Options +${args.candidateOptionsStr.trim()}`; +} -=== TASK === +// 7. Suit Following Analysis Block +function buildSuitAnalysisBlock(args: UserPromptTemplateArgs): string { + return `## Suit-Following Analysis +${args.suitAnalysisStr.trim()}`; +} + +// 8. Seat Guidance Block +function buildSeatGuidanceBlock(args: UserPromptTemplateArgs): string { + if (!args.seatGuidanceStr) return ""; + return `## Seat Guidance +${args.seatGuidanceStr}`; +} + +// 9. Task Block +function buildTaskBlock(args: UserPromptTemplateArgs): string { + return `## Task ${args.taskInstructionStr} -Reply with ONLY the JSON object {"reasoning":"","play":[...]}. In "play", use each card's bare notation from YOUR HAND — drop the ×N count tag, and repeat a notation to play a pair (possible only when that card shows ×2). Never invent a card or a pair you don't hold. -`; +Reply with JSON ONLY: {"reasoning":"","play":["",...]}. Copy card notations from YOUR HAND (repeat notation to play a pair). Never play cards you do not hold.`; +} + +/** + * Builds the dynamic user prompt using injected state variables. + */ +export function buildUserPromptTemplate(args: UserPromptTemplateArgs): string { + const blocks = args.isLeading + ? [ + buildCurrentStateBlock(args), + buildHistoryBlock(args), + buildVoidsBlock(args), + buildHandBlock(args), + buildLeadOptionsBlock(args), + buildTaskBlock(args), + ] + : [ + buildCurrentStateBlock(args), + buildHistoryBlock(args), + buildVoidsBlock(args), + buildActiveTrickBlock(args), + buildHandBlock(args), + buildSeatGuidanceBlock(args), + buildSuitAnalysisBlock(args), + buildTaskBlock(args), + ]; + + return blocks.filter(Boolean).join("\n\n") + "\n"; }