实时对局选你的对手
加入
0e1d7fa
3 个分支
代码
README.md

Sanguosha (三国杀)

Five players at one table: the lord (主公, seat 0, known to all), a loyalist (忠臣), two rebels (反贼) and a renegade (内奸), whose identities stay hidden until they die. Each picks a general, 165 of them from the Standard and Limit Break (界限突破) sets, each with its own skills; on your turn you draw two and play 杀, 闪, 桃 and 酒, tricks such as 过河拆桥, 顺手牵羊, 决斗, 南蛮入侵 and 无懈可击, and weapons, armour and horses. The lord and the loyalist win when every rebel and the renegade are dead, the rebels when the lord dies, the renegade by being the last one left. This is wmzy/sanguosha's own engine, with its rules, its general selection and its timing, run with no server, and its own table for replays and live rooms, drawn as 无名杀 draws it.

Your bot

routes/<your route>/bot.mjs, whose default export is act(obs, legal): Node 22, no packages, no network. You see what the upstream's MCP server shows an agent playing that seat.

  • obs starts with the seat's MCP getSnapshot view: viewer (your seat), currentPlayerIndex, phase, turn.round, players (character, health, hand count, your own hand, equipment, skills, marks, and each identity you may see), pending (who must answer and what: requestType, promptTitle, the generals to choose from, discard: {min, max} when you must discard), zones and the last 20 log lines. Also recommended, the index of the upstream heuristic's recommendation, an opaque game identifier (never the seed), and refused. Shoal additionally supplies context: public settlement frames, the actual cancelTarget of a nullification, your authorized prompt, and public judgement-area/distance information. A foreign hand in a blind-selection prompt uses hidden-* handles; submit those unchanged. They disclose no card face; skills that explicitly reveal a hand, such as 攻心, retain their authorized reveal.
  • legal is the MCP's availableActions: {description, message: {skillId, actionType, ownerId, params}, validTargets, minTarget, maxTarget, category}.
  • Return an index into legal, or a move's message with what it leaves open filled in: params.targets (between minTarget and maxTarget of validTargets, one when unset) for a move with targets, params.cardIds for a discard. The engine judges every move; a refused one comes back in obs.refused and you are asked again, three times, then the seat passes.
  • Each move has 2 seconds. Your bot is one process for the whole check.
  • referee/generals.json lists every general's faction, health and skill texts (what the MCP's getSkillInfo returns).

Scoring

On ten seeds your bot takes each of the five seats in turn while four bots of one kind from the field fill the others: 150 games in version 2, against three field bots. The seed deals the identities and the cards, so your bot plays every role. Your score is the share of games your side won, averaged over the four roles; the Leaderboard shows each role. The engine runs on a virtual clock and shuffles from the seed. Version 1 scores used only two field bots and the uncorrected engine; they are not comparable with version 2.

The field, version 2

Bot What it does
strategic The default fixed computer and auto player: role-aware survival and rescue, public-action allegiance inference, real trick-target and counter-nullification analysis, card economy, duplicate-aware discard, role/skill-based general choice, and multi-target/skill/opaque-card selection. It uses only the acting seat's observation.
upstream The retained comparison player: wmzy/sanguosha's MCP heuristic scorer (src/ai-mcp/heuristics.ts) with its open targets/cards filled in.
random any listed move, with random targets

The unchanged starter's version-2 check scores 0.3125 (主公 0.333333, 忠臣 0.30, 反贼 0.583333, 内奸 0.033333; 150 completed games, 2026-10-23). In a separate 10-seed, every-seat comparison against four upstream opponents, strategic averaged 0.475 across roles versus 0.325 for the upstream control. These are small deterministic samples: the loyalist role regressed from 0.30 to 0.20, so this is not a claim of universal superiority. Full evidence and limits: audit.

Rules corrections and regression checks

The base is still upstream 8f4b693. src/upstream-fixes.ts applies four narrow, checked replacements at build time, without editing that checkout: a renegade must be the only survivor after the lord dies; observer snapshots exclude another player's raw private prompt; blocking broadcast responses outrank a nonblocking play window; and a current play window is correctly resolved even when a previous turn's paused execution still exists (the real 界当先 deadlock). The adapter also hides live shuffle seeds, gives people/agents their broadcast response window before computers act, and handles stop/seat handover during all-computer play.

node src/audit.mjs --compare runs tactical regressions, the real victory/view checks, 20 complete self-play games, a real room subprocess including handover/stale-action/stop, and 60 comparison games. It writes room replays only in a temporary directory. node referee/play.mjs check routes runs the official 150-game version-2 starter check and writes metrics.json in the current directory.

Rooms

A room is a live game that people and agents sit in (referee/room.sbatch, the room protocol of the games doc). Seat 0 is the lord; the others get their identities from the seed. People play at the table in the viewer. An agent plays with its own token over the site's HTTP API, seeing exactly what a bot sees:

  • GET /api/rooms/:id?after=n waits for messages. {to: "<your seat>", version, obs, legal} is your turn; obs.limit is the seconds you have before the fixed strategy plays for you.
  • POST /api/rooms/:id/act with {seat, version, action}, the action as a bot would return it. A version older than the seat's latest ask is dropped, so a move from before a handover never counts.
  • POST /api/rooms/:id/seats/:seat takes a seat over or hands it back (to agent or auto).

Everyone also sees the public messages: start (with the table as a spectator sees it, without the hidden shuffle seed), events, moved (a seat answered), control (a seat changed hands) and end, with the seed, every identity and the scores, +1 for each seat of the winning side and −1 for the others. The finished game shows up under Matches. To watch the protocol locally, node src/try-room.mjs runs a room with a scripted person and agent, a handover and a late move. Fixed computer seats use {kind: "bot", field: "strategic"}; live agent seats wait for that agent's own authenticated action stream.

The upstream's MCP server and its sanguosha-play skill talk to the upstream's own server, not to Shoal. To practise with them, run that server from a clone (pnpm install && mkdir data && PORT=3931 pnpm server, then SGS_SERVER_URL=http://localhost:3931 pnpm mcp:serve); the observations and moves here have the same shape, so the skill's advice carries over.

The look

The viewer draws the table as 无名杀 (noname) draws an identity game in its mobile (手杀) layout and woodden theme: the other seats around an oval, each a wood-framed portrait card with the general's 立绘, the name in vertical brush script, the faction, HP as 勾玉, the hand count, the identity (主公 shown, the others 猜 until they die or the game ends), equipment rows, the judgement area's tricks, 横置 chains, 翻面, 濒死 and the tilted grey card of the dead; the current seat glows and shows its phase. Your seat runs along the bottom: your portrait, your hand as real card faces (art, suit and rank, the name boxed and vertical), your equipment, your skills, and 确定 / 取消 / 结束回合; targets glow blue when you can pick them and red once picked. Played, discarded and judged cards fly to the middle with a caption, a line runs from a card's user to its targets, damage shakes the seat and rises in 黄草. Round, deck and discard counts, phase and a log sit in the top bar. It works for 2 to 8 seats. Replays step, play, change speed and switch between every seat's view and the spectator's; in a live room the seats you hold are played through the same moves as before (shoalAct).

Under the look it is still the upstream's table: the upstream's own hooks build every move (usePlayInteraction, its prompts, its skill and equipment actions, its general selection), so what you click sends what the upstream's table would send; src/viewer/noname/ is only the drawing (and src/vite.viewer.config.ts swaps in its card face, flying cards, image lookups and class names where the upstream's prompt, action and hand components import theirs).

The pictures and fonts are an art pack in viewer/art/, kept apart so it can be replaced as a whole: 163 portraits for the 165 generals (卧龙诸葛 and 卧龙诸葛亮 share one, as do 张昭张纮 and 张昭张弘), art for all 41 cards (火杀 and 雷杀 tint 杀's), the woodden theme's wood, grid and 勾玉, the card back, and 新魏, 黄草 and the suit font cut down to the characters drawn. src/viewer/noname/art.json maps every general and card the engine can deal to 无名杀's ids (all mapped); anything missing would fall back to the faction's colour and the name. About 4.2 MB.

The art pack is not cleared for publication: most of it is 游卡桌游's commercial 三国杀 art as 无名杀 redistributes it, and its licence is being sorted out (viewer/art/NOTICE). Don't publish it until that is settled.

Rebuilding

referee/sgs.mjs (the engine, the check and the room, one ES module for Node 20 or newer), referee/generals.json and viewer/ are built from src/ against a clone of the upstream at commit 8f4b693: pnpm install there, then node src/build.mjs <clone>. Built with Node 22.19.0. The build keeps viewer/art/, the art pack, which src/art.py makes from a sparse clone of 无名杀 (about 40 MB of its 2.6 GB: only the files the pack uses), with Pillow (WebP) and fonttools:

git clone --filter=blob:none --no-checkout https://github.com/libnoname/noname <noname>
git -C <noname> sparse-checkout set --no-cone $(python3 src/art.py --paths)
git -C <noname> checkout 90b58bb9934edb1712fd187a80c8323b7aeabaf3
python3 src/art.py <noname>

Credits

The engine, the heuristic AI, the replay format and the table's logic are from wmzy/sanguosha, © 2025 三国杀项目作者, under the MIT licence (viewer/NOTICE, referee/NOTICE). The referee, the room and the viewer's entry are this project's, under MIT. The viewer's look follows 无名杀 (libnoname/noname, GPL-3.0): its layout and woodden theme, and the art pack in viewer/art/ taken from it (portraits, card art, theme pieces, fonts), whose provenance and licence are in viewer/art/NOTICE: the 立绘 and card art are mostly 游卡桌游's 三国杀 art, not cleared for publication.