dsh-poker 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/cordis.patch.yml +3 -0
- package/lib/client.js +2419 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +3051 -0
- package/lib/index.js.map +1 -0
- package/lib/invariant-C3ZiFskw.js +13 -0
- package/lib/invariant-C3ZiFskw.js.map +1 -0
- package/lib/invariant.js +3 -0
- package/lib/join/card-back-landlord.png +0 -0
- package/lib/join/card-back.png +0 -0
- package/lib/join/face-a.png +0 -0
- package/lib/join/face-j.png +0 -0
- package/lib/join/face-k.png +0 -0
- package/lib/join/face-q.png +0 -0
- package/lib/join/index.html +13 -0
- package/lib/join/index.js +8910 -0
- package/lib/join/index.js.map +1 -0
- package/lib/join/join.css +14 -0
- package/lib/join/joker-black.png +0 -0
- package/lib/join/joker-red.png +0 -0
- package/lib/join/role-farmer-b.png +0 -0
- package/lib/join/role-farmer-c.png +0 -0
- package/lib/join/role-farmer.png +0 -0
- package/lib/join/role-landlord-b.png +0 -0
- package/lib/join/role-landlord.png +0 -0
- package/lib/join/role-spectator.png +0 -0
- package/lib/types/client/CardBack.d.ts +7 -0
- package/lib/types/client/CardBack.d.ts.map +1 -0
- package/lib/types/client/CardFace.d.ts +8 -0
- package/lib/types/client/CardFace.d.ts.map +1 -0
- package/lib/types/client/HostApp.d.ts +4 -0
- package/lib/types/client/HostApp.d.ts.map +1 -0
- package/lib/types/client/InviteDialog.d.ts +15 -0
- package/lib/types/client/InviteDialog.d.ts.map +1 -0
- package/lib/types/client/JoinForm.d.ts +13 -0
- package/lib/types/client/JoinForm.d.ts.map +1 -0
- package/lib/types/client/LobbyView.d.ts +34 -0
- package/lib/types/client/LobbyView.d.ts.map +1 -0
- package/lib/types/client/SeatAvatar.d.ts +18 -0
- package/lib/types/client/SeatAvatar.d.ts.map +1 -0
- package/lib/types/client/SeatRow.d.ts +8 -0
- package/lib/types/client/SeatRow.d.ts.map +1 -0
- package/lib/types/client/SettingsCard.d.ts +6 -0
- package/lib/types/client/SettingsCard.d.ts.map +1 -0
- package/lib/types/client/SettlementView.d.ts +6 -0
- package/lib/types/client/SettlementView.d.ts.map +1 -0
- package/lib/types/client/TableView.d.ts +16 -0
- package/lib/types/client/TableView.d.ts.map +1 -0
- package/lib/types/client/card-stack.d.ts +3 -0
- package/lib/types/client/card-stack.d.ts.map +1 -0
- package/lib/types/client/host-api.d.ts +50 -0
- package/lib/types/client/host-api.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +5 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/slots.d.ts +3 -0
- package/lib/types/client/slots.d.ts.map +1 -0
- package/lib/types/client/store.d.ts +15 -0
- package/lib/types/client/store.d.ts.map +1 -0
- package/lib/types/config.d.ts +76 -0
- package/lib/types/config.d.ts.map +1 -0
- package/lib/types/crypto.d.ts +9 -0
- package/lib/types/crypto.d.ts.map +1 -0
- package/lib/types/engine/cards.d.ts +22 -0
- package/lib/types/engine/cards.d.ts.map +1 -0
- package/lib/types/engine/deal.d.ts +11 -0
- package/lib/types/engine/deal.d.ts.map +1 -0
- package/lib/types/engine/hands.d.ts +16 -0
- package/lib/types/engine/hands.d.ts.map +1 -0
- package/lib/types/engine/index.d.ts +7 -0
- package/lib/types/engine/index.d.ts.map +1 -0
- package/lib/types/engine/play.d.ts +73 -0
- package/lib/types/engine/play.d.ts.map +1 -0
- package/lib/types/engine/random.d.ts +2 -0
- package/lib/types/engine/random.d.ts.map +1 -0
- package/lib/types/engine/score.d.ts +11 -0
- package/lib/types/engine/score.d.ts.map +1 -0
- package/lib/types/identity/player.d.ts +6 -0
- package/lib/types/identity/player.d.ts.map +1 -0
- package/lib/types/index.d.ts +8 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/invariant.d.ts +9 -0
- package/lib/types/invariant.d.ts.map +1 -0
- package/lib/types/join/JoinApp.d.ts +2 -0
- package/lib/types/join/JoinApp.d.ts.map +1 -0
- package/lib/types/join/main.d.ts +2 -0
- package/lib/types/join/main.d.ts.map +1 -0
- package/lib/types/net/auth.d.ts +16 -0
- package/lib/types/net/auth.d.ts.map +1 -0
- package/lib/types/net/join-page.d.ts +6 -0
- package/lib/types/net/join-page.d.ts.map +1 -0
- package/lib/types/net/json.d.ts +3 -0
- package/lib/types/net/json.d.ts.map +1 -0
- package/lib/types/net/protocol.d.ts +2 -0
- package/lib/types/net/protocol.d.ts.map +1 -0
- package/lib/types/net/routes.d.ts +4 -0
- package/lib/types/net/routes.d.ts.map +1 -0
- package/lib/types/net/views.d.ts +3 -0
- package/lib/types/net/views.d.ts.map +1 -0
- package/lib/types/net/ws.d.ts +8 -0
- package/lib/types/net/ws.d.ts.map +1 -0
- package/lib/types/persist/domain.d.ts +104 -0
- package/lib/types/persist/domain.d.ts.map +1 -0
- package/lib/types/persist/schemas.d.ts +79 -0
- package/lib/types/persist/schemas.d.ts.map +1 -0
- package/lib/types/room/RoomManager.d.ts +142 -0
- package/lib/types/room/RoomManager.d.ts.map +1 -0
- package/lib/types/settle/audit.d.ts +15 -0
- package/lib/types/settle/audit.d.ts.map +1 -0
- package/lib/types/settle/escrow.d.ts +4 -0
- package/lib/types/settle/escrow.d.ts.map +1 -0
- package/lib/types/settle/grant.d.ts +7 -0
- package/lib/types/settle/grant.d.ts.map +1 -0
- package/lib/types/settle/ledger.d.ts +39 -0
- package/lib/types/settle/ledger.d.ts.map +1 -0
- package/lib/types/settle/math.d.ts +12 -0
- package/lib/types/settle/math.d.ts.map +1 -0
- package/lib/types/types.d.ts +253 -0
- package/lib/types/types.d.ts.map +1 -0
- package/package.json +116 -0
- package/prepare.mjs +16 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":["ws: WebSocket | null","poll: ReturnType<typeof setInterval> | undefined","css","ASSET","css","css","name","css","css","RANK_VALUE: Record<string, number>","cardBase","rankOf","isWildCard","wilds: CardId[]","naturals: CardId[]","isWildCard","found: ClassifiedHand[]","rankOf","best: ClassifiedHand | null","hand: ClassifiedHand","runs: Rank[][]","leftover: CardId[]","ASSET","css","SUIT_GLYPH: Record<string, string>","FACE_ART: Record<string, string>","red","css","css","css","out: Seat[]","created: CreateRoomResponse","css","parsed: URL","next: SettingsDraft","writes: Array<readonly [keyof SettingsDraft, string | number | boolean]>","css","host: HTMLElement | null","css","node: HTMLElement"],"sources":["../src/settle/math.ts","../src/client/host-api.ts","../src/client/InviteDialog.tsx","../src/client/SeatAvatar.tsx","../src/client/JoinForm.tsx","../src/client/LobbyView.tsx","../src/types.ts","../src/client/SettlementView.tsx","../src/engine/cards.ts","../src/engine/hands.ts","../src/client/store.ts","../src/client/card-stack.ts","../src/client/CardBack.tsx","../src/client/CardFace.tsx","../src/client/SeatRow.tsx","../src/client/TableView.tsx","../src/client/HostApp.tsx","../src/client/SettingsCard.tsx","../src/client/slots.tsx","../src/client/index.tsx"],"sourcesContent":["export const STAKE_LADDER_M = [1, 2, 5, 10, 20, 50, 100] as const\nexport const DEFAULT_STAKE_M = 1\nexport const DEFAULT_MAX_MULTIPLIER = 8\nexport const DEFAULT_WELCOME_ATOMS = 200_000_000n\nexport const ATOMS_PER_M = 1_000_000n\nexport const ATOMS_PER_B = 1_000_000_000n\n\nexport function stakeAtomsFromM(stakeM: number): bigint {\n return BigInt(stakeM) * ATOMS_PER_M\n}\n\nexport function perFarmerCapAtoms(stakeAtoms: bigint, maxMultiplier: number): bigint {\n return stakeAtoms * 3n * BigInt(maxMultiplier) * 2n\n}\n\nexport function seatCapAtoms(stakeAtoms: bigint, maxMultiplier: number, seatCount: 3 | 4 = 3): bigint {\n const farmers = BigInt(seatCount - 1)\n return farmers * perFarmerCapAtoms(stakeAtoms, maxMultiplier)\n}\n\n/** Display token atoms as AI-token units: M (million) or B (billion). Never dump raw zeros. */\nexport function formatM(atoms: bigint): string {\n const sign = atoms < 0n ? '-' : ''\n const abs = atoms < 0n ? -atoms : atoms\n if (abs >= ATOMS_PER_B) return `${sign}${formatScaled(abs, ATOMS_PER_B)}B`\n return `${sign}${formatScaled(abs, ATOMS_PER_M)}M`\n}\n\nfunction formatScaled(abs: bigint, unit: bigint): string {\n const whole = abs / unit\n const rem = abs % unit\n if (rem === 0n) return whole.toString()\n const decimals = unit.toString().length - 1\n const frac = rem.toString().padStart(decimals, '0').replace(/0+$/, '')\n return `${whole.toString()}.${frac}`\n}\n","import type { ClientCommand, PlayerView, PublicSettlement, ServerEvent } from '../types.ts'\n\nconst PREFIX = '/doudizhu'\n\nexport interface CreateRoomResponse {\n roomId: string\n roomCode: string\n sitUrl: string\n watchUrl: string\n seatCapAtoms: string\n shareable: boolean\n hostPlayerId: string\n view: PlayerView\n wsTicket: string\n}\n\nexport interface JoinResponse {\n playerId: string\n roomId: string\n seat: 0 | 1 | 2 | 3 | null\n wsTicket: string\n view: PlayerView\n}\n\nasync function request<T>(path: string, init?: RequestInit): Promise<T> {\n const response = await fetch(`${PREFIX}${path}`, {\n credentials: 'same-origin',\n ...init,\n headers: {\n 'content-type': 'application/json',\n 'x-requested-with': 'doudizhu',\n ...(init?.headers ?? {}),\n },\n })\n const text = await response.text()\n const body = text ? JSON.parse(text) as T & { error?: string; message?: string } : {} as T\n if (!response.ok) {\n throw new Error((body as { message?: string }).message ?? (body as { error?: string }).error ?? response.statusText)\n }\n return body\n}\n\nexport function createRoom(input: {\n stakeM: number\n maxMultiplier: number\n seatCount?: 3 | 4\n laiZi?: boolean\n hostDisplayName?: string\n title?: string\n}) {\n return request<CreateRoomResponse>('/api/rooms', { method: 'POST', body: JSON.stringify(input) })\n}\n\nexport function joinRoom(input: { roomCode: string; invite: string; displayName: string; role: 'sit' | 'watch' }) {\n return request<JoinResponse>('/api/join', { method: 'POST', body: JSON.stringify(input) })\n}\n\nexport function sendCommand(roomId: string, command: ClientCommand) {\n return request<{ seq: number }>(`/api/rooms/${roomId}/command`, { method: 'POST', body: JSON.stringify(command) })\n}\n\nexport function fetchSince(roomId: string, seq: number) {\n return request<{ events: ServerEvent[]; untilSeq: number }>(`/api/rooms/${roomId}/since?seq=${seq}`)\n}\n\nexport function fetchLedger() {\n return request<{ availableAtoms: string; escrowAtoms: string }>(`/api/me/ledger`)\n}\n\nexport function connectChannel(\n ticket: string,\n onEvent: (event: ServerEvent) => void,\n fallback: { roomId: string; seq: () => number },\n): () => void {\n const proto = location.protocol === 'https:' ? 'wss' : 'ws'\n let closed = false\n let ws: WebSocket | null = null\n let poll: ReturnType<typeof setInterval> | undefined\n try {\n ws = new WebSocket(`${proto}://${location.host}${PREFIX}/ws?ticket=${encodeURIComponent(ticket)}`, 'doudizhu.v1')\n ws.onmessage = (event) => {\n try { onEvent(JSON.parse(String(event.data)) as ServerEvent) } catch { /* ignore */ }\n }\n ws.onerror = () => { startPoll() }\n ws.onclose = () => { if (!closed) startPoll() }\n } catch {\n startPoll()\n }\n function startPoll(): void {\n if (poll || closed) return\n poll = setInterval(() => {\n void fetchSince(fallback.roomId, fallback.seq()).then((body) => {\n for (const event of body.events) onEvent(event)\n }).catch(() => { /* keep polling */ })\n }, 800)\n }\n return () => {\n closed = true\n ws?.close()\n if (poll) clearInterval(poll)\n }\n}\n\nexport type { PlayerView, PublicSettlement, ServerEvent }\n","import { useEffect, useState } from 'react'\nimport css from './styles.module.css'\n\nexport function InviteDialog({\n roomCode, sitUrl, shareable,\n}: {\n roomCode: string\n sitUrl: string\n watchUrl?: string\n shareable: boolean\n}) {\n return (\n <div className={css.inviteBox}>\n <div className={css.codeRow}>\n <span>房间号</span>\n <code className={css.codeValue} onClick={() => { void navigator.clipboard.writeText(roomCode) }}>{roomCode}</code>\n <button type=\"button\" className={css.ghost} onClick={() => { void navigator.clipboard.writeText(roomCode) }}>复制房号</button>\n </div>\n {shareable\n ? (\n <>\n <p className={css.hint}>同一条链接。先到的人入座,坐满或开打后来的人观战。</p>\n <button type=\"button\" className={css.ghost} onClick={() => { void navigator.clipboard.writeText(sitUrl) }}>复制房间链接</button>\n <img className={css.qr} alt=\"room qr\" src={`https://api.qrserver.com/v1/create-qr-code/?size=132x132&data=${encodeURIComponent(sitUrl)}`} />\n </>\n )\n : (\n <p className={`${css.hint} ${css.warn}`}>\n 要给别的机器用,先在插件设置里填 publicBaseUrl(named tunnel 或反代到本机端口)。\n 生产环境的 dsh web 不能绑 0.0.0.0,所以局域网 IP 用不了。本机可以打开 {sitUrl || 'loopback 加入页'} 自己测。\n </p>\n )}\n </div>\n )\n}\n\nexport function RoomCodeBar({\n title, roomCode, sitUrl, shareable, canRename, onRename,\n}: {\n title: string\n roomCode: string\n sitUrl?: string\n shareable?: boolean\n canRename?: boolean\n onRename?: (title: string) => void\n}) {\n const [draft, setDraft] = useState(title)\n const [editing, setEditing] = useState(false)\n useEffect(() => { setDraft(title) }, [title])\n return (\n <div className={css.codeBar}>\n {canRename && editing\n ? (\n <form\n className={css.codeEdit}\n onSubmit={(event) => {\n event.preventDefault()\n onRename?.(draft)\n setEditing(false)\n }}\n >\n <input className={css.input} value={draft} maxLength={24} onChange={(event) => { setDraft(event.target.value) }} />\n <button type=\"submit\" className={css.ghost}>保存</button>\n </form>\n )\n : (\n <button type=\"button\" className={css.titleBtn} disabled={!canRename} onClick={() => { if (canRename) setEditing(true) }}>\n {title || '好友局'}\n </button>\n )}\n <span className={css.muted}>房号</span>\n <code className={css.codeValue} onClick={() => { void navigator.clipboard.writeText(roomCode) }}>{roomCode}</code>\n <button type=\"button\" className={css.ghost} onClick={() => { void navigator.clipboard.writeText(roomCode) }}>复制房号</button>\n {shareable && sitUrl\n ? <button type=\"button\" className={css.ghost} onClick={() => { void navigator.clipboard.writeText(sitUrl) }}>复制链接</button>\n : sitUrl\n ? <span className={css.muted}>未配置 publicBaseUrl,仅本机可加入</span>\n : null}\n </div>\n )\n}\n","import type { SeatState } from '../types.ts'\nimport css from './styles.module.css'\n\nconst ASSET = '/doudizhu/assets'\n\nexport const ROLE_ICONS = {\n landlord: `${ASSET}/role-landlord.png`,\n landlordB: `${ASSET}/role-landlord-b.png`,\n farmer: `${ASSET}/role-farmer.png`,\n farmerB: `${ASSET}/role-farmer-b.png`,\n farmerC: `${ASSET}/role-farmer-c.png`,\n spectator: `${ASSET}/role-spectator.png`,\n} as const\n\nconst FARMER_ICONS = [ROLE_ICONS.farmer, ROLE_ICONS.farmerB, ROLE_ICONS.farmerC] as const\n\nexport function roleIconSrc(role: SeatState['role'], seat = 0): string {\n if (role === 'landlord') return ROLE_ICONS.landlord\n return FARMER_ICONS[Math.abs(seat) % FARMER_ICONS.length] ?? ROLE_ICONS.farmer\n}\n\nexport function SeatAvatar({\n avatarUrl,\n role = 'empty',\n seat = 0,\n occupied = false,\n spectator = false,\n}: {\n avatarUrl?: string | null\n role?: SeatState['role']\n seat?: number\n occupied?: boolean\n spectator?: boolean\n}) {\n if (spectator) {\n return (\n <img\n className={css.avatar}\n src={ROLE_ICONS.spectator}\n alt=\"观战\"\n onError={(event) => { event.currentTarget.remove() }}\n />\n )\n }\n const sitting = occupied || role === 'landlord' || role === 'farmer'\n const src = sitting ? roleIconSrc(role === 'empty' ? 'farmer' : role, seat) : avatarUrl\n if (!src) return null\n const landlord = role === 'landlord'\n return (\n <img\n className={css.avatar}\n src={src}\n alt={landlord ? '地主' : sitting ? '农民' : ''}\n onError={(event) => { event.currentTarget.remove() }}\n />\n )\n}\n","import { ROLE_ICONS } from './SeatAvatar.tsx'\nimport css from './styles.module.css'\n\nexport function JoinForm({\n code, invite, name, onCode, onInvite, onName, onJoin, joining = false, showInvite = true, error,\n}: {\n code: string\n invite: string\n name: string\n onCode: (value: string) => void\n onInvite: (value: string) => void\n onName: (value: string) => void\n onJoin: () => void\n joining?: boolean\n showInvite?: boolean\n error: string | null\n}) {\n return (\n <div className={css.joinForm}>\n <div className={css.roleHero}>\n <figure className={css.roleHeroItem}>\n <img className={css.roleHeroImg} src={ROLE_ICONS.landlord} alt=\"地主\" />\n <figcaption>地主</figcaption>\n </figure>\n <figure className={css.roleHeroItem}>\n <img className={css.roleHeroImg} src={ROLE_ICONS.farmer} alt=\"农民\" />\n <figcaption>农民</figcaption>\n </figure>\n <figure className={css.roleHeroItem}>\n <img className={css.roleHeroImg} src={ROLE_ICONS.farmerB} alt=\"农民\" />\n <figcaption>农民</figcaption>\n </figure>\n <figure className={css.roleHeroItem}>\n <img className={css.roleHeroImg} src={ROLE_ICONS.farmerC} alt=\"农民\" />\n <figcaption>农民</figcaption>\n </figure>\n </div>\n <p className={css.hint}>填房号就能进。先到的人入座,坐满或已经开打后来的人观战。出牌每手 120 秒。</p>\n <div className={css.joinFields}>\n {showInvite\n ? (\n <label className={css.field}>\n 邀请\n <input className={css.input} value={invite} autoComplete=\"off\" spellCheck={false} onChange={(event) => { onInvite(event.target.value) }} />\n </label>\n )\n : null}\n <div className={css.joinInline}>\n <div className={css.joinField}>\n <span className={css.settingsLabel}>房号</span>\n <input\n className={`${css.input} ${css.codeInput}`}\n value={code}\n inputMode=\"numeric\"\n autoComplete=\"off\"\n spellCheck={false}\n placeholder=\"6 位房号\"\n onChange={(event) => { onCode(event.target.value) }}\n />\n </div>\n <div className={css.joinField}>\n <span className={css.settingsLabel}>昵称</span>\n <input className={css.input} value={name} maxLength={16} onChange={(event) => { onName(event.target.value) }} />\n </div>\n </div>\n <button type=\"button\" className={css.primary} disabled={joining || !code.trim()} onClick={onJoin}>\n {joining ? '加入中…' : '加入'}\n </button>\n {error ? <p className={css.error}>{error}</p> : null}\n </div>\n </div>\n )\n}\n","import { DEFAULT_MAX_MULTIPLIER, DEFAULT_STAKE_M, formatM, seatCapAtoms, stakeAtomsFromM, STAKE_LADDER_M } from '../settle/math.ts'\nimport type { SeatCount } from '../types.ts'\nimport { InviteDialog } from './InviteDialog.tsx'\nimport { JoinForm } from './JoinForm.tsx'\nimport css from './styles.module.css'\n\nconst MAX_MULTIPLIER_OPTIONS = [8, 16, 32, 64] as const\n\nexport function LobbyView({\n tab, onTab, title, stakeM, maxMultiplier, seatCount, laiZi, onTitle, onStake, onCap, onSeats, onLaiZi, onCreate, creating, error,\n roomCode, sitUrl, watchUrl, shareable, welcomeAtoms, join,\n}: {\n tab: 'create' | 'join'\n onTab: (tab: 'create' | 'join') => void\n title: string\n stakeM: number\n maxMultiplier: number\n seatCount: SeatCount\n laiZi: boolean\n onTitle: (value: string) => void\n onStake: (value: number) => void\n onCap: (value: number) => void\n onSeats: (value: SeatCount) => void\n onLaiZi: (value: boolean) => void\n onCreate: () => void\n creating: boolean\n error: string | null\n roomCode: string\n sitUrl: string\n watchUrl: string\n shareable: boolean\n welcomeAtoms: bigint\n join: {\n code: string\n invite: string\n name: string\n joining: boolean\n onCode: (value: string) => void\n onInvite: (value: string) => void\n onName: (value: string) => void\n onJoin: () => void\n }\n}) {\n const cap = seatCapAtoms(stakeAtomsFromM(stakeM), maxMultiplier, seatCount)\n const blocked = welcomeAtoms < cap\n return (\n <div className={css.lobby}>\n <div className={css.card}>\n <div className={css.tabs} role=\"tablist\" aria-label=\"开房或加入\">\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={tab === 'create'}\n className={`${css.tab} ${tab === 'create' ? css.tabActive : ''}`}\n onClick={() => { onTab('create') }}\n >\n 创建\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={tab === 'join'}\n className={`${css.tab} ${tab === 'join' ? css.tabActive : ''}`}\n onClick={() => { onTab('join') }}\n >\n 加入\n </button>\n </div>\n {tab === 'join'\n ? (\n <>\n <h2>加入房间</h2>\n <p className={css.hint}>别人开的房填房号就能进。积分记在开房那台机器上,不是 DeepSeek 平台余额。</p>\n <JoinForm\n code={join.code}\n invite={join.invite}\n name={join.name}\n onCode={join.onCode}\n onInvite={join.onInvite}\n onName={join.onName}\n onJoin={join.onJoin}\n joining={join.joining}\n showInvite={false}\n error={error}\n />\n </>\n )\n : (\n <>\n <h2>创建房间</h2>\n <p className={css.hint}>好友局信房主这台机器上的账本。积分不是 DeepSeek 平台余额。</p>\n <div className={css.createFields}>\n <label className={`${css.field} ${css.fieldWide}`}>\n 房间名\n <input\n className={css.input}\n value={title}\n maxLength={24}\n placeholder=\"好友局\"\n onChange={(event) => { onTitle(event.target.value) }}\n />\n </label>\n <label className={css.field}>\n 人数\n <select className={css.select} value={seatCount} onChange={(event) => { onSeats(Number(event.target.value) === 4 ? 4 : 3) }}>\n <option value={3}>3 人 · 一副牌</option>\n <option value={4}>4 人 · 两副牌</option>\n </select>\n </label>\n <label className={css.field}>\n 玩法\n <select className={css.select} value={laiZi ? 'laizi' : 'classic'} onChange={(event) => { onLaiZi(event.target.value === 'laizi') }}>\n <option value=\"classic\">经典</option>\n <option value=\"laizi\">癞子</option>\n </select>\n </label>\n <label className={css.field}>\n 底注\n <select className={css.select} value={stakeM} onChange={(event) => { onStake(Number(event.target.value)) }}>\n {STAKE_LADDER_M.map((value) => <option key={value} value={value}>{value}M</option>)}\n </select>\n </label>\n <label className={css.field}>\n 倍数封顶\n <select className={css.select} value={maxMultiplier} onChange={(event) => { onCap(Number(event.target.value)) }}>\n {MAX_MULTIPLIER_OPTIONS.map((value) => <option key={value} value={value}>{value}</option>)}\n </select>\n </label>\n </div>\n <p className={css.hint}>\n {seatCount} 人{laiZi ? '癞子' : '经典'}。人齐自动开打,后来的人观战。出牌 120 秒。入座冻结 {formatM(cap)},欢迎 {formatM(welcomeAtoms)}。\n </p>\n {blocked ? <p className={css.error}>欢迎积分不够入座冻结,把底注或封顶调低,或者去设置里加欢迎积分。</p> : null}\n <button type=\"button\" className={css.primary} disabled={blocked || creating} onClick={onCreate}>\n {creating ? '创建中…' : '创建房间'}\n </button>\n {error ? <p className={css.error}>{error}</p> : null}\n {roomCode\n ? <InviteDialog roomCode={roomCode} sitUrl={sitUrl} watchUrl={watchUrl} shareable={shareable} />\n : <p className={css.hint}>默认底注 {DEFAULT_STAKE_M}M / 封顶 {DEFAULT_MAX_MULTIPLIER}。</p>}\n </>\n )}\n </div>\n </div>\n )\n}\n","export type RoomId = string & { readonly __brand: 'RoomId' }\nexport type PlayerId = string & { readonly __brand: 'PlayerId' }\nexport type HandId = string & { readonly __brand: 'HandId' }\nexport type GrantId = string & { readonly __brand: 'GrantId' }\nexport type SettlementId = string & { readonly __brand: 'SettlementId' }\nexport type EntryId = string & { readonly __brand: 'EntryId' }\nexport type CardId = string & { readonly __brand: 'CardId' }\nexport type SeatCount = 3 | 4\nexport type Seat = 0 | 1 | 2 | 3\nexport type GameMode = 'classic' | 'laizi'\nexport type TokenAtomString = string & { readonly __atom: unique symbol }\n\nexport type RoomPhase =\n | 'waiting' | 'dealing' | 'bidding' | 'doubling'\n | 'playing' | 'settling' | 'void' | 'closed'\n\nexport type HandType =\n | 'solo' | 'pair' | 'trio' | 'trioSolo' | 'trioPair'\n | 'seq' | 'seqPair' | 'plane' | 'planeSolo' | 'planePair'\n | 'fourDualSolo' | 'fourDualPair' | 'bomb' | 'rocket' | 'pass'\n\nexport type BidScore = number\nexport type BidAction = 'pass' | 'call' | 'rob'\nexport type DoubleAction = 'pass' | 'double' | 'reDouble'\nexport type AuctionKind = 'call' | 'rob'\n\nexport type RejectCode =\n | 'illegal' | 'not-your-turn' | 'phase' | 'auth' | 'duplicate-nonce'\n | 'insufficient' | 'room-full' | 'expired'\n\nexport interface Player {\n readonly playerId: PlayerId\n readonly displayName: string\n readonly avatarUrl: string | null\n readonly createdAt: string\n}\n\nexport interface SeatState {\n readonly seat: Seat\n readonly playerId: PlayerId | null\n readonly displayName: string | null\n readonly avatarUrl: string | null\n readonly ready: boolean\n readonly connected: boolean\n readonly role: 'empty' | 'farmer' | 'landlord'\n readonly grantId: GrantId | null\n readonly cardsLeft: number\n}\n\nexport interface Room {\n readonly roomId: RoomId\n readonly roomCode: string\n readonly title: string\n readonly hostPlayerId: PlayerId\n readonly phase: RoomPhase\n readonly stakeAtoms: bigint\n readonly maxMultiplier: number\n readonly seatCount: SeatCount\n readonly laiZi: boolean\n readonly seats: readonly SeatState[]\n readonly spectatorIds: readonly PlayerId[]\n readonly currentHandId: HandId | null\n readonly createdAt: string\n readonly inviteExpiresAt: string\n readonly shareable: boolean\n}\n\n/** Wire copy of Room: atoms are decimal strings so JSON/WS stay serializable. */\nexport interface RoomView extends Omit<Room, 'stakeAtoms'> {\n readonly stakeAtoms: TokenAtomString\n}\n\nexport interface Play {\n readonly seat: Seat\n readonly type: HandType\n readonly cards: readonly CardId[]\n readonly seq: number\n readonly ts: string\n}\n\nexport interface Hand {\n readonly handId: HandId\n readonly roomId: RoomId\n readonly dealerSeat: Seat\n readonly bottom: readonly CardId[]\n readonly hands: readonly CardId[][]\n readonly bid: BidScore\n readonly landlordSeat: Seat | null\n readonly farmerDoubledBySeat: Partial<Record<Seat, number>>\n readonly landlordReDouble: number\n readonly mingPaiBySeat: Partial<Record<Seat, boolean>>\n readonly mingPaiMult: number\n readonly bombCount: number\n readonly rocketCount: number\n readonly spring: 'none' | 'spring' | 'anti'\n readonly table: readonly Play[]\n readonly turnSeat: Seat\n readonly leadSeat: Seat\n readonly laiZiRanks: readonly string[]\n}\n\nexport interface LegalCombo {\n readonly type: Exclude<HandType, 'pass'>\n readonly cards: readonly CardId[]\n}\n\nexport interface PlayerView {\n readonly room: RoomView\n readonly you: {\n readonly playerId: PlayerId\n readonly seat: Seat | null\n readonly spectator: boolean\n readonly cards: CardId[]\n }\n readonly publicHands: readonly number[]\n readonly lastPlays: readonly Play[]\n readonly bottom: readonly CardId[] | null\n readonly laiZiRanks: readonly string[]\n readonly bid: BidScore\n readonly auction: { readonly kind: AuctionKind; readonly multiplier: number } | null\n readonly revealedBySeat: Partial<Record<Seat, readonly CardId[]>>\n readonly mingPaiBySeat: Partial<Record<Seat, boolean>>\n readonly turnSeat: Seat | null\n readonly leadSeat: Seat | null\n readonly deadlineAt: string | null\n readonly yourAvailableAtoms: TokenAtomString\n readonly yourEscrowAtoms: TokenAtomString\n readonly legal: { readonly canPass: boolean; readonly combos: readonly LegalCombo[] }\n readonly remainingRanks: Readonly<Record<string, number>> | null\n readonly chat: readonly ChatLine[]\n}\n\nexport interface ChatLine {\n readonly playerId: PlayerId\n readonly displayName: string\n readonly text: string\n readonly ts: string\n}\n\nexport interface Settlement {\n readonly settlementId: SettlementId\n readonly handId: HandId\n readonly winner: 'landlord' | 'farmers'\n readonly unitAtoms: TokenAtomString\n readonly deltas: readonly { seat: Seat; playerId: PlayerId; atoms: TokenAtomString }[]\n readonly rakeAtoms: TokenAtomString\n readonly formula: string\n}\n\nexport interface PublicSettlement {\n readonly settlementId: SettlementId\n readonly handId: HandId\n readonly winner: 'landlord' | 'farmers'\n readonly unitAtoms: TokenAtomString\n readonly deltas: Settlement['deltas']\n readonly rakeAtoms: TokenAtomString\n readonly formula: string\n readonly trustNote: string\n}\n\nexport interface TokenGrant {\n readonly grantId: GrantId\n readonly playerId: PlayerId\n readonly roomId: RoomId\n readonly maxExposureAtoms: TokenAtomString\n readonly issuedAt: string\n readonly expiresAt: string\n readonly source: 'host-welcome'\n readonly issuedBy: 'room-host'\n}\n\nexport type ClientCommand =\n | { type: 'sit'; seat: Seat; displayName: string }\n | { type: 'stand' }\n | { type: 'ready'; ready: boolean }\n | { type: 'bid'; action: BidAction; score?: BidScore }\n | { type: 'double'; action: DoubleAction }\n | { type: 'mingPai' }\n | { type: 'rename'; title: string }\n | { type: 'play'; cards: CardId[]; nonce: string }\n | { type: 'pass'; nonce: string }\n | { type: 'chat'; text: string }\n | { type: 'rematch' }\n | { type: 'hostKick'; playerId: PlayerId }\n | { type: 'hostClose' }\n | { type: 'ping' }\n\nexport type ServerEvent =\n | { type: 'snapshot'; seq: number; view: PlayerView }\n | { type: 'reject'; nonce?: string; code: RejectCode; reason: string }\n | { type: 'settled'; seq: number; settlement: PublicSettlement }\n | { type: 'pong'; ts: number }\n\nexport class CommandError extends Error {\n readonly code: RejectCode\n constructor(code: RejectCode, reason: string) {\n super(reason)\n this.code = code\n this.name = 'CommandError'\n }\n}\n\nexport function asTokenAtomString(value: bigint | number | string): TokenAtomString {\n return String(value) as TokenAtomString\n}\n\nexport function parseAtoms(value: string): bigint {\n if (!/^-?\\d+$/.test(value)) throw new Error(`invalid atom string: ${value}`)\n return BigInt(value)\n}\n\nexport function asCardId(value: string): CardId {\n return value as CardId\n}\n\nexport function asPlayerId(value: string): PlayerId {\n return value as PlayerId\n}\n\nexport function asRoomId(value: string): RoomId {\n return value as RoomId\n}\n\nexport function asHandId(value: string): HandId {\n return value as HandId\n}\n\nexport function asGrantId(value: string): GrantId {\n return value as GrantId\n}\n\nexport function asSettlementId(value: string): SettlementId {\n return value as SettlementId\n}\n\nexport function asEntryId(value: string): EntryId {\n return value as EntryId\n}\n\nexport function nextSeat(seat: Seat, count: number = 3): Seat {\n return ((seat + 1) % count) as Seat\n}\n\nexport function prevSeat(seat: Seat, count: number = 3): Seat {\n return ((seat + count - 1) % count) as Seat\n}\n\nexport function isSeat(value: unknown, count: number = 4): value is Seat {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0 && value < count\n}\n\nexport function allSeats(count: SeatCount): Seat[] {\n return Array.from({ length: count }, (_, index) => index as Seat)\n}\n\nexport function decksFor(count: SeatCount): 1 | 2 {\n return count === 3 ? 1 : 2\n}\n\nexport function holeCount(count: SeatCount): number {\n return count === 3 ? 3 : 8\n}\n\nexport function dealtHandSize(count: SeatCount): number {\n return count === 3 ? 17 : 25\n}\n\nexport function landlordHandSize(count: SeatCount): number {\n return dealtHandSize(count) + holeCount(count)\n}\n","import { formatM } from '../settle/math.ts'\nimport type { PublicSettlement } from '../types.ts'\nimport { parseAtoms } from '../types.ts'\nimport { ROLE_ICONS } from './SeatAvatar.tsx'\nimport css from './styles.module.css'\n\nexport function SettlementView({\n settlement, onRematch,\n}: {\n settlement: PublicSettlement\n onRematch: () => void\n}) {\n return (\n <div className={css.settlement}>\n <div className={css.card}>\n <div className={css.roleHero}>\n {settlement.winner === 'landlord'\n ? <img className={css.roleHeroImg} src={ROLE_ICONS.landlord} alt=\"地主\" />\n : (\n <>\n <img className={css.roleHeroImg} src={ROLE_ICONS.farmer} alt=\"农民\" />\n <img className={css.roleHeroImg} src={ROLE_ICONS.farmerB} alt=\"农民\" />\n </>\n )}\n </div>\n <h2>{settlement.winner === 'landlord' ? '地主胜' : '农民胜'}</h2>\n <p className={css.hint}>{settlement.formula}</p>\n <ul>\n {settlement.deltas.map((delta) => (\n <li key={delta.seat}>座 {delta.seat}: {formatM(parseAtoms(delta.atoms))}</li>\n ))}\n </ul>\n <p className={`${css.hint} ${css.warn}`}>{settlement.trustNote}</p>\n <p className={css.hint}>积分 ≠ 平台余额,也不是 API key。</p>\n <button type=\"button\" className={css.primary} onClick={onRematch}>再来一局</button>\n </div>\n </div>\n )\n}\n","import { asCardId, type CardId } from '../types.ts'\n\nexport const SUITS = ['S', 'H', 'C', 'D'] as const\nexport const RANK_ORDER = [\n '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A', '2', 'BJ', 'RJ',\n] as const\n\nexport type Suit = typeof SUITS[number]\nexport type Rank = typeof RANK_ORDER[number]\n\nconst RANK_VALUE: Record<string, number> = Object.fromEntries(\n RANK_ORDER.map((rank, index) => [rank, index]),\n)\n\nexport function cardBase(card: CardId | string): string {\n return String(card).split('~')[0] ?? String(card)\n}\n\nexport function rankOf(card: CardId): Rank {\n const raw = cardBase(card)\n if (raw === 'BJ' || raw === 'RJ') return raw\n if (raw.startsWith('S') || raw.startsWith('H') || raw.startsWith('C') || raw.startsWith('D')) {\n return raw.slice(1) as Rank\n }\n throw new Error(`invalid card: ${raw}`)\n}\n\nexport function suitOf(card: CardId): Suit | null {\n const raw = cardBase(card)\n if (raw === 'BJ' || raw === 'RJ') return null\n return raw[0] as Suit\n}\n\nexport function rankValue(rank: string): number {\n const value = RANK_VALUE[rank]\n if (value === undefined) throw new Error(`invalid rank: ${rank}`)\n return value\n}\n\nexport function cardValue(card: CardId): number {\n return rankValue(rankOf(card))\n}\n\nexport function compareCards(a: CardId, b: CardId): number {\n const byRank = cardValue(a) - cardValue(b)\n if (byRank !== 0) return byRank\n return a.localeCompare(b)\n}\n\nexport function sortCards(cards: readonly CardId[]): CardId[] {\n return [...cards].sort(compareCards)\n}\n\nexport function fullDeck(copy = 0): CardId[] {\n const suffix = copy === 0 ? '' : `~${copy}`\n const cards: CardId[] = []\n for (const suit of SUITS) {\n for (const rank of RANK_ORDER) {\n if (rank === 'BJ' || rank === 'RJ') continue\n cards.push(asCardId(`${suit}${rank}${suffix}`))\n }\n }\n cards.push(asCardId(`BJ${suffix}`), asCardId(`RJ${suffix}`))\n return cards\n}\n\nexport function stackedDeck(decks: 1 | 2): CardId[] {\n const cards: CardId[] = []\n for (let copy = 0; copy < decks; copy += 1) cards.push(...fullDeck(copy))\n return cards\n}\n\nexport const WILDABLE_RANKS = RANK_ORDER.filter((rank) => rank !== 'BJ' && rank !== 'RJ')\n\nexport function isJokerRank(rank: Rank): boolean {\n return rank === 'BJ' || rank === 'RJ'\n}\n\nexport function isWildCard(card: CardId, wildRanks: readonly string[]): boolean {\n if (wildRanks.length === 0) return false\n const rank = rankOf(card)\n return !isJokerRank(rank) && wildRanks.includes(rank)\n}\n\nexport function shuffle<T>(items: readonly T[], random: () => number): T[] {\n const next = [...items]\n for (let i = next.length - 1; i > 0; i -= 1) {\n const j = Math.floor(random() * (i + 1))\n const a = next[i]\n const b = next[j]\n if (a === undefined || b === undefined) continue\n next[i] = b\n next[j] = a\n }\n return next\n}\n\nexport function parseCardId(raw: string): CardId | null {\n if (/^(BJ|RJ)(?:~\\d+)?$/.test(raw)) return asCardId(raw)\n const match = /^(S|H|C|D)(10|[3-9JQKA2])(?:~\\d+)?$/.exec(raw)\n return match ? asCardId(raw) : null\n}\n\nexport function countRanks(cards: readonly CardId[]): Map<Rank, CardId[]> {\n const groups = new Map<Rank, CardId[]>()\n for (const card of cards) {\n const rank = rankOf(card)\n const bucket = groups.get(rank)\n if (bucket) bucket.push(card)\n else groups.set(rank, [card])\n }\n return groups\n}\n\nexport function isNaturalSeqRank(rank: Rank): boolean {\n return rank !== '2' && rank !== 'BJ' && rank !== 'RJ'\n}\n","import type { CardId, HandType, LegalCombo } from '../types.ts'\nimport { asCardId } from '../types.ts'\nimport {\n cardValue, countRanks, isJokerRank, isNaturalSeqRank, isWildCard, rankOf, rankValue, sortCards,\n WILDABLE_RANKS, type Rank,\n} from './cards.ts'\n\nexport interface ClassifiedHand {\n readonly type: Exclude<HandType, 'pass'>\n readonly cards: readonly CardId[]\n readonly key: number\n readonly length: number\n readonly wingLength: number\n readonly wild?: boolean\n}\n\nexport function classify(cards: readonly CardId[], laiZiRanks: readonly string[] = []): ClassifiedHand | null {\n if (laiZiRanks.length === 0) return classifyNatural(cards)\n const wilds: CardId[] = []\n const naturals: CardId[] = []\n for (const card of cards) {\n if (isWildCard(card, laiZiRanks)) wilds.push(card)\n else naturals.push(card)\n }\n if (wilds.length === 0) return classifyNatural(cards)\n return classifyWithWilds(cards, naturals, wilds.length)\n}\n\nfunction classifyNatural(cards: readonly CardId[]): ClassifiedHand | null {\n const sorted = sortCards(cards)\n if (sorted.length === 0) return null\n const ranks = countRanks(sorted)\n const entries = [...ranks.entries()].sort((a, b) => rankValue(a[0]) - rankValue(b[0]))\n\n if (jokerCount(ranks) === sorted.length && sorted.length >= 2) {\n return { type: 'rocket', cards: sorted, key: rankValue('RJ'), length: sorted.length, wingLength: 0 }\n }\n if (sorted.length === 1) {\n return { type: 'solo', cards: sorted, key: cardValue(sorted[0]!), length: 1, wingLength: 0 }\n }\n if (sorted.length === 2 && entries.length === 1) {\n return { type: 'pair', cards: sorted, key: rankValue(entries[0]![0]), length: 2, wingLength: 0 }\n }\n if (sorted.length === 3 && entries.length === 1) {\n return { type: 'trio', cards: sorted, key: rankValue(entries[0]![0]), length: 3, wingLength: 0 }\n }\n if (sorted.length >= 4 && entries.length === 1) {\n return { type: 'bomb', cards: sorted, key: rankValue(entries[0]![0]), length: sorted.length, wingLength: 0 }\n }\n\n const trioSolo = classifyTrioSolo(sorted, entries)\n if (trioSolo) return trioSolo\n const trioPair = classifyTrioPair(sorted, entries)\n if (trioPair) return trioPair\n const seq = classifySeq(sorted, entries)\n if (seq) return seq\n const seqPair = classifySeqPair(sorted, entries)\n if (seqPair) return seqPair\n const plane = classifyBarePlane(sorted, entries)\n if (plane) return plane\n const planePair = classifyPlanePair(sorted, entries)\n if (planePair) return planePair\n const planeSolo = classifyPlaneSolo(sorted, entries)\n if (planeSolo) return planeSolo\n const fourDualPair = classifyFourDualPair(sorted, entries)\n if (fourDualPair) return fourDualPair\n const fourDualSolo = classifyFourDualSolo(sorted, entries)\n if (fourDualSolo) return fourDualSolo\n return null\n}\n\nexport function beats(prev: ClassifiedHand, next: ClassifiedHand, decks: 1 | 2 = 1): boolean {\n const nextBomb = next.type === 'bomb' || next.type === 'rocket'\n const prevBomb = prev.type === 'bomb' || prev.type === 'rocket'\n if (next.type === 'rocket' && decks === 1) return prev.type !== 'rocket'\n if (prev.type === 'rocket' && decks === 1) return false\n if (nextBomb && prevBomb) {\n if (next.length !== prev.length) return next.length > prev.length\n if (!!next.wild !== !!prev.wild) return !next.wild && !!prev.wild\n if (next.type !== prev.type) return next.type === 'rocket'\n return next.key > prev.key\n }\n if (nextBomb) return true\n if (prevBomb) return false\n if (next.type !== prev.type || next.length !== prev.length || next.wingLength !== prev.wingLength) {\n return false\n }\n return next.key > prev.key\n}\n\nexport function enumerateLegal(\n hand: readonly CardId[],\n lead: ClassifiedHand | null,\n laiZiRanks: readonly string[] = [],\n decks: 1 | 2 = 1,\n): LegalCombo[] {\n const sorted = sortCards(hand)\n const combos: LegalCombo[] = []\n const seen = new Set<string>()\n const push = (type: Exclude<HandType, 'pass'>, cards: readonly CardId[]): void => {\n const key = `${type}:${sortCards(cards).join(',')}`\n if (seen.has(key)) return\n seen.add(key)\n combos.push({ type, cards: sortCards(cards) })\n }\n\n if (lead === null) {\n for (const subset of generateCandidates(sorted)) {\n const classified = classify(subset, laiZiRanks)\n if (classified) push(classified.type, classified.cards)\n }\n return capCombos(combos)\n }\n\n if (!(lead.type === 'rocket' && decks === 1)) {\n for (const bomb of findBombs(sorted, laiZiRanks)) {\n const classified = classify(bomb, laiZiRanks)\n if (classified && beats(lead, classified, decks)) push(classified.type, classified.cards)\n }\n const rocket = sorted.filter((card) => isJokerRank(rankOf(card)))\n if (rocket.length >= 2) push('rocket', rocket)\n }\n\n for (const subset of generateFollowCandidates(sorted, lead)) {\n const classified = classify(subset, laiZiRanks)\n if (classified && beats(lead, classified, decks)) push(classified.type, classified.cards)\n }\n return capCombos(combos)\n}\n\nexport function smallestSolo(hand: readonly CardId[]): CardId | null {\n const sorted = sortCards(hand)\n return sorted[0] ?? null\n}\n\nfunction classifyTrioSolo(\n cards: readonly CardId[],\n entries: readonly [Rank, CardId[]][],\n): ClassifiedHand | null {\n if (cards.length !== 4) return null\n const trio = entries.find(([, group]) => group.length === 3)\n const kicker = entries.find(([, group]) => group.length === 1)\n if (!trio || !kicker) return null\n if (kicker[0] === 'BJ' && entries.some(([rank]) => rank === 'RJ')) return null\n if (kicker[0] === 'RJ' && entries.some(([rank]) => rank === 'BJ')) return null\n return { type: 'trioSolo', cards, key: rankValue(trio[0]), length: 4, wingLength: 1 }\n}\n\nfunction classifyTrioPair(\n cards: readonly CardId[],\n entries: readonly [Rank, CardId[]][],\n): ClassifiedHand | null {\n if (cards.length !== 5) return null\n const trio = entries.find(([, group]) => group.length === 3)\n const pair = entries.find(([, group]) => group.length === 2)\n if (!trio || !pair || pair[0] === trio[0]) return null\n return { type: 'trioPair', cards, key: rankValue(trio[0]), length: 5, wingLength: 2 }\n}\n\nfunction classifySeq(\n cards: readonly CardId[],\n entries: readonly [Rank, CardId[]][],\n): ClassifiedHand | null {\n if (cards.length < 5) return null\n if (!entries.every(([, group]) => group.length === 1)) return null\n if (!isConsecutive(entries.map(([rank]) => rank), 1)) return null\n return {\n type: 'seq',\n cards,\n key: rankValue(entries[entries.length - 1]![0]),\n length: cards.length,\n wingLength: 0,\n }\n}\n\nfunction classifySeqPair(\n cards: readonly CardId[],\n entries: readonly [Rank, CardId[]][],\n): ClassifiedHand | null {\n if (cards.length < 6 || cards.length % 2 !== 0) return null\n if (!entries.every(([, group]) => group.length === 2)) return null\n if (!isConsecutive(entries.map(([rank]) => rank), 1)) return null\n if (entries.length < 3) return null\n return {\n type: 'seqPair',\n cards,\n key: rankValue(entries[entries.length - 1]![0]),\n length: cards.length,\n wingLength: 0,\n }\n}\n\nfunction classifyBarePlane(\n cards: readonly CardId[],\n entries: readonly [Rank, CardId[]][],\n): ClassifiedHand | null {\n const trios = entries.filter(([, group]) => group.length === 3).map(([rank]) => rank)\n if (trios.length < 2) return null\n if (entries.some(([, group]) => group.length !== 3)) return null\n if (!isConsecutive(trios, 1)) return null\n return {\n type: 'plane',\n cards,\n key: rankValue(trios[trios.length - 1]!),\n length: cards.length,\n wingLength: 0,\n }\n}\n\nexport function classifyPlaneSolo(\n cards: readonly CardId[],\n entries: readonly [Rank, CardId[]][] = [...countRanks(cards).entries()].sort(\n (a, b) => rankValue(a[0]) - rankValue(b[0]),\n ),\n): ClassifiedHand | null {\n const available = entries\n .filter(([rank, group]) => group.length >= 3 && isNaturalSeqRank(rank) && rank !== '2')\n .map(([rank]) => rank)\n .sort((a, b) => rankValue(a) - rankValue(b))\n const maxK = Math.min(available.length, Math.floor(cards.length / 4))\n for (let k = maxK; k >= 2; k -= 1) {\n if (cards.length !== k * 4) continue\n const found: ClassifiedHand[] = []\n for (const body of consecutiveRuns(available, k)) {\n const leftover = leftoverAfterBody(cards, body, 3)\n if (leftover.length !== k) continue\n if (containsConsecutiveTrios(leftover)) continue\n found.push({\n type: 'planeSolo',\n cards,\n key: rankValue(body[body.length - 1]!),\n length: cards.length,\n wingLength: k,\n })\n }\n if (found.length > 0) {\n found.sort((a, b) => b.key - a.key)\n return found[0] ?? null\n }\n }\n return null\n}\n\nfunction classifyPlanePair(\n cards: readonly CardId[],\n entries: readonly [Rank, CardId[]][],\n): ClassifiedHand | null {\n const trios = entries.filter(([, group]) => group.length >= 3 && isNaturalSeqRank(group[0] ? rankOf(group[0]) : '3') && rankOf(group[0]!) !== '2')\n const trioRanks = entries\n .filter(([rank, group]) => group.length >= 3 && isNaturalSeqRank(rank) && rank !== '2')\n .map(([rank]) => rank)\n .sort((a, b) => rankValue(a) - rankValue(b))\n for (let k = Math.min(trioRanks.length, Math.floor(cards.length / 5)); k >= 2; k -= 1) {\n if (cards.length !== k * 5) continue\n for (const body of consecutiveRuns(trioRanks, k)) {\n const leftoverPairs = pairWings(cards, body)\n if (leftoverPairs === k) {\n return {\n type: 'planePair',\n cards,\n key: rankValue(body[body.length - 1]!),\n length: cards.length,\n wingLength: k,\n }\n }\n }\n }\n void trios\n return null\n}\n\nfunction classifyFourDualSolo(\n cards: readonly CardId[],\n entries: readonly [Rank, CardId[]][],\n): ClassifiedHand | null {\n if (cards.length !== 6) return null\n const quad = entries.find(([, group]) => group.length === 4)\n if (!quad) return null\n const leftovers = cards.filter((card) => rankOf(card) !== quad[0])\n if (leftovers.length !== 2) return null\n if (hasRocket(countRanks(leftovers))) return null\n return { type: 'fourDualSolo', cards, key: rankValue(quad[0]), length: 6, wingLength: 2 }\n}\n\nfunction classifyFourDualPair(\n cards: readonly CardId[],\n entries: readonly [Rank, CardId[]][],\n): ClassifiedHand | null {\n if (cards.length !== 8) return null\n const quad = entries.find(([, group]) => group.length === 4)\n if (!quad) return null\n const leftover = leftoverAfterBody(cards, [quad[0]], 4)\n const leftoverGroups = [...countRanks(leftover).entries()]\n if (leftoverGroups.some(([rank, group]) => rank === quad[0] || group.length !== 2)) return null\n if (leftoverGroups.length !== 2) return null\n return { type: 'fourDualPair', cards, key: rankValue(quad[0]), length: 8, wingLength: 4 }\n}\n\nfunction hasRocket(ranks: Map<Rank, CardId[]>): boolean {\n return (ranks.get('BJ')?.length ?? 0) >= 1 && (ranks.get('RJ')?.length ?? 0) >= 1\n}\n\nfunction jokerCount(ranks: Map<Rank, CardId[]>): number {\n return (ranks.get('BJ')?.length ?? 0) + (ranks.get('RJ')?.length ?? 0)\n}\n\nfunction classifyWithWilds(original: readonly CardId[], naturals: readonly CardId[], wildCount: number): ClassifiedHand | null {\n if (wildCount <= 0) return classifyNatural(original)\n const ranksToTry = wildAssignmentRanks(naturals, wildCount)\n let best: ClassifiedHand | null = null\n const choice = new Array<number>(wildCount).fill(0)\n const walk = (): void => {\n const virtuals = choice.map((index) => asCardId(`S${ranksToTry[index]}`))\n const result = classifyNatural([...naturals, ...virtuals])\n if (!result) return\n const hand: ClassifiedHand = { ...result, cards: sortCards(original), wild: true }\n if (!best || wildPreference(hand, best) > 0) best = hand\n }\n const rec = (depth: number): void => {\n if (depth === wildCount) {\n walk()\n return\n }\n for (let i = 0; i < ranksToTry.length; i += 1) {\n choice[depth] = i\n rec(depth + 1)\n }\n }\n rec(0)\n return best\n}\n\nfunction wildAssignmentRanks(naturals: readonly CardId[], wildCount: number): Rank[] {\n if (naturals.length === 0) return wildCount >= 4 ? ['2'] : [...WILDABLE_RANKS]\n const present = new Set(naturals.map((card) => rankOf(card)))\n if (wildCount >= 5) {\n return WILDABLE_RANKS.filter((rank) => present.has(rank) || neighborsOf(present).has(rank))\n }\n return [...WILDABLE_RANKS]\n}\n\nfunction neighborsOf(present: Set<Rank>): Set<Rank> {\n const out = new Set<Rank>()\n for (const rank of present) {\n const value = rankValue(rank)\n for (const candidate of WILDABLE_RANKS) {\n if (Math.abs(rankValue(candidate) - value) === 1) out.add(candidate)\n }\n }\n return out\n}\n\nfunction wildPreference(next: ClassifiedHand, prev: ClassifiedHand): number {\n const bombScore = (hand: ClassifiedHand): number => {\n if (hand.type === 'rocket') return 200 + hand.length\n if (hand.type === 'bomb') return 100 + hand.length\n return 0\n }\n const byBomb = bombScore(next) - bombScore(prev)\n if (byBomb !== 0) return byBomb\n if (next.length !== prev.length) return next.length - prev.length\n return next.key - prev.key\n}\n\nfunction isConsecutive(ranks: readonly Rank[], step: number): boolean {\n if (ranks.some((rank) => !isNaturalSeqRank(rank))) return false\n for (let i = 1; i < ranks.length; i += 1) {\n if (rankValue(ranks[i]!) - rankValue(ranks[i - 1]!) !== step) return false\n }\n return ranks.length > 0\n}\n\nfunction consecutiveRuns(ranks: readonly Rank[], length: number): Rank[][] {\n const runs: Rank[][] = []\n for (let i = 0; i + length <= ranks.length; i += 1) {\n const slice = ranks.slice(i, i + length)\n if (isConsecutive(slice, 1)) runs.push(slice)\n }\n return runs\n}\n\nfunction containsConsecutiveTrios(cards: readonly CardId[]): boolean {\n const groups = [...countRanks(cards).entries()]\n .filter(([, group]) => group.length >= 3)\n .map(([rank]) => rank)\n .filter((rank) => isNaturalSeqRank(rank) && rank !== '2')\n .sort((a, b) => rankValue(a) - rankValue(b))\n return consecutiveRuns(groups, 2).length > 0\n}\n\nfunction pairWings(cards: readonly CardId[], body: readonly Rank[]): number | null {\n const leftover = leftoverAfterBody(cards, body, 3)\n const groups = countRanks(leftover)\n if ([...groups.values()].some((group) => group.length !== 2 && group.length !== 4)) return null\n let pairs = 0\n for (const [rank, group] of groups) {\n if (body.includes(rank)) return null\n if (group.length === 2) pairs += 1\n else if (group.length === 4) pairs += 2\n else return null\n }\n return pairs\n}\n\nfunction leftoverAfterBody(cards: readonly CardId[], body: readonly Rank[], take: number): CardId[] {\n const used = new Map<Rank, number>()\n for (const rank of body) used.set(rank, take)\n const leftover: CardId[] = []\n for (const card of cards) {\n const rank = rankOf(card)\n const remaining = used.get(rank) ?? 0\n if (remaining > 0) used.set(rank, remaining - 1)\n else leftover.push(card)\n }\n return leftover\n}\n\nfunction findBombs(hand: readonly CardId[], laiZiRanks: readonly string[] = []): CardId[][] {\n const bombs: CardId[][] = []\n const ranks = countRanks(hand)\n const wilds = laiZiRanks.length === 0\n ? []\n : hand.filter((card) => isWildCard(card, laiZiRanks))\n for (const [rank, group] of ranks) {\n if (isJokerRank(rank)) continue\n const natural = laiZiRanks.includes(rank)\n ? group.filter((card) => !isWildCard(card, laiZiRanks))\n : group\n const total = natural.length + wilds.length\n if (natural.length + (laiZiRanks.includes(rank) ? 0 : 0) >= 4 || total >= 4) {\n if (natural.length >= 4) bombs.push(natural.slice(0, Math.max(4, natural.length)))\n if (wilds.length > 0 && total >= 4) bombs.push([...natural, ...wilds].slice(0, total))\n }\n }\n const jokers = hand.filter((card) => isJokerRank(rankOf(card)))\n if (jokers.length >= 2) bombs.push(jokers)\n return bombs\n}\n\nfunction generateCandidates(hand: readonly CardId[]): CardId[][] {\n const out: CardId[][] = []\n const ranks = countRanks(hand)\n for (const group of ranks.values()) {\n for (let n = 1; n <= group.length; n += 1) out.push(group.slice(0, n))\n }\n const rocket = hand.filter((card) => isJokerRank(rankOf(card)))\n if (rocket.length === 2) out.push(rocket)\n appendSeqs(out, ranks, 1, 5)\n appendSeqs(out, ranks, 2, 3)\n appendPlanes(out, hand, ranks)\n appendKickers(out, hand, ranks)\n return out\n}\n\nfunction generateFollowCandidates(hand: readonly CardId[], lead: ClassifiedHand): CardId[][] {\n return generateCandidates(hand).filter((subset) => subset.length === lead.length)\n}\n\nfunction appendSeqs(\n out: CardId[][],\n ranks: Map<Rank, CardId[]>,\n copies: number,\n minLen: number,\n): void {\n const usable = [...ranks.entries()]\n .filter(([rank, group]) => isNaturalSeqRank(rank) && group.length >= copies)\n .sort((a, b) => rankValue(a[0]) - rankValue(b[0]))\n for (let len = minLen; len <= usable.length; len += 1) {\n for (let i = 0; i + len <= usable.length; i += 1) {\n const slice = usable.slice(i, i + len)\n if (!isConsecutive(slice.map(([rank]) => rank), 1)) continue\n out.push(slice.flatMap(([, group]) => group.slice(0, copies)))\n }\n }\n}\n\nfunction appendPlanes(out: CardId[][], hand: readonly CardId[], ranks: Map<Rank, CardId[]>): void {\n const trioRanks = [...ranks.entries()]\n .filter(([rank, group]) => group.length >= 3 && isNaturalSeqRank(rank) && rank !== '2')\n .map(([rank]) => rank)\n .sort((a, b) => rankValue(a) - rankValue(b))\n for (let k = 2; k <= trioRanks.length; k += 1) {\n for (const body of consecutiveRuns(trioRanks, k)) {\n const bodyCards = leftoverInverse(hand, body, 3)\n out.push(bodyCards)\n const rest = leftoverAfterBody(hand, body, 3)\n for (const wings of combinations(rest, k)) out.push([...bodyCards, ...wings])\n const pairs = pairCombinations(rest, k)\n for (const wings of pairs) out.push([...bodyCards, ...wings])\n }\n }\n}\n\nfunction leftoverInverse(hand: readonly CardId[], body: readonly Rank[], take: number): CardId[] {\n const used = new Map<Rank, number>()\n for (const rank of body) used.set(rank, take)\n const taken: CardId[] = []\n for (const card of hand) {\n const rank = rankOf(card)\n const remaining = used.get(rank) ?? 0\n if (remaining > 0) {\n used.set(rank, remaining - 1)\n taken.push(card)\n }\n }\n return taken\n}\n\nfunction appendKickers(out: CardId[][], hand: readonly CardId[], ranks: Map<Rank, CardId[]>): void {\n for (const [rank, group] of ranks) {\n if (group.length >= 3) {\n const trio = group.slice(0, 3)\n for (const card of hand) {\n if (rankOf(card) === rank) continue\n out.push([...trio, card])\n }\n for (const [other, otherGroup] of ranks) {\n if (other === rank || otherGroup.length < 2) continue\n out.push([...trio, ...otherGroup.slice(0, 2)])\n }\n }\n if (group.length === 4) {\n const rest = hand.filter((card) => rankOf(card) !== rank)\n for (const wings of combinations(rest, 2)) out.push([...group, ...wings])\n const pairRanks = [...ranks.entries()].filter(([r, g]) => r !== rank && g.length >= 2)\n for (let i = 0; i < pairRanks.length; i += 1) {\n for (let j = i + 1; j < pairRanks.length; j += 1) {\n out.push([\n ...group,\n ...pairRanks[i]![1].slice(0, 2),\n ...pairRanks[j]![1].slice(0, 2),\n ])\n }\n }\n }\n }\n}\n\nfunction combinations(items: readonly CardId[], k: number): CardId[][] {\n const out: CardId[][] = []\n const walk = (start: number, acc: CardId[]): void => {\n if (acc.length === k) {\n out.push([...acc])\n return\n }\n for (let i = start; i < items.length; i += 1) {\n acc.push(items[i]!)\n walk(i + 1, acc)\n acc.pop()\n }\n }\n if (k <= items.length) walk(0, [])\n return out\n}\n\nfunction pairCombinations(items: readonly CardId[], k: number): CardId[][] {\n const groups = [...countRanks(items).entries()].filter(([, group]) => group.length >= 2)\n const out: CardId[][] = []\n const walk = (start: number, acc: CardId[], taken: number): void => {\n if (taken === k) {\n out.push([...acc])\n return\n }\n for (let i = start; i < groups.length; i += 1) {\n const group = groups[i]![1]\n acc.push(...group.slice(0, 2))\n walk(i + 1, acc, taken + 1)\n acc.splice(acc.length - 2, 2)\n if (group.length >= 4 && taken + 2 <= k) {\n acc.push(...group.slice(0, 4))\n walk(i + 1, acc, taken + 2)\n acc.splice(acc.length - 4, 4)\n }\n }\n }\n walk(0, [], 0)\n return out\n}\n\nfunction capCombos(combos: LegalCombo[]): LegalCombo[] {\n if (combos.length <= 200) return combos\n return combos.slice(0, 200)\n}\n","import { decksFor } from '../types.ts'\nimport { beats, classify } from '../engine/hands.ts'\nimport type { CardId, PlayerView, PublicSettlement } from '../types.ts'\n\nexport interface ClientState {\n view: PlayerView | null\n settlement: PublicSettlement | null\n selected: CardId[]\n error: string | null\n shareable: boolean\n sitUrl: string\n watchUrl: string\n roomCode: string\n}\n\nexport function emptyState(): ClientState {\n return {\n view: null,\n settlement: null,\n selected: [],\n error: null,\n shareable: false,\n sitUrl: '',\n watchUrl: '',\n roomCode: '',\n }\n}\n\nexport function toggleCard(selected: CardId[], card: CardId): CardId[] {\n return selected.includes(card) ? selected.filter((item) => item !== card) : [...selected, card]\n}\n\nexport function selectionLegal(view: PlayerView | null, selected: CardId[]): boolean {\n if (!view || selected.length === 0) return false\n const key = [...selected].sort().join(',')\n if (view.legal.combos.some((combo) => [...combo.cards].sort().join(',') === key)) return true\n const ranks = view.laiZiRanks ?? []\n const classified = classify(selected, ranks)\n if (!classified) return false\n const last = [...view.lastPlays].reverse().find((play) => play.type !== 'pass')\n if (!last || view.leadSeat === view.you.seat) return true\n const lead = classify(last.cards, ranks)\n if (!lead) return true\n const seats = view.room.seatCount ?? (view.room.seats.length >= 4 ? 4 : 3)\n return beats(lead, classified, decksFor(seats))\n}\n","export const COMPACT_STACK_MAX = 5\n\nexport function visibleBackCount(remaining: number, maxVisible = COMPACT_STACK_MAX): number {\n if (remaining <= 0) return 0\n return Math.min(remaining, maxVisible)\n}\n","import { visibleBackCount } from './card-stack.ts'\nimport css from './styles.module.css'\n\nconst ASSET = '/doudizhu/assets'\n\nexport { COMPACT_STACK_MAX, visibleBackCount } from './card-stack.ts'\n\nexport function CardBack({\n count = 1,\n compact = false,\n landlord = false,\n}: {\n count?: number\n compact?: boolean\n landlord?: boolean\n}) {\n const remaining = Math.max(0, count)\n const n = compact ? visibleBackCount(remaining) : Math.min(remaining, 20)\n const src = landlord ? `${ASSET}/card-back-landlord.png` : `${ASSET}/card-back.png`\n return (\n <div className={`${css.remain} ${compact ? css.remainCompact : ''}`}>\n {compact && remaining > 0\n ? <span className={css.cardsLeft} aria-label={`剩余 ${remaining} 张`}>{remaining}</span>\n : null}\n <div className={`${css.backs} ${compact ? css.backsCompact : ''}`} aria-hidden=\"true\">\n {Array.from({ length: n }, (_, index) => (\n <div\n className={`${css.cardBack} ${landlord ? css.cardBackLandlord : ''}`}\n key={index}\n style={{ zIndex: index }}\n >\n <img className={css.cardBackArt} src={src} alt=\"\" />\n </div>\n ))}\n </div>\n </div>\n )\n}\n","import type { CardId } from '../types.ts'\nimport css from './styles.module.css'\n\nconst SUIT_GLYPH: Record<string, string> = { S: '♠', H: '♥', C: '♣', D: '♦' }\nconst ASSET = '/doudizhu/assets'\nexport const FACE_ART_RANKS = ['J', 'Q', 'K', 'A'] as const\nconst FACE_ART: Record<string, string> = Object.fromEntries(\n FACE_ART_RANKS.map((rank) => [rank, `${ASSET}/face-${rank.toLowerCase()}.png`]),\n)\n\nfunction cardBase(card: CardId | string): string {\n return String(card).split('~')[0] ?? String(card)\n}\n\nfunction rankOf(card: CardId): string {\n const raw = cardBase(card)\n if (raw === 'BJ' || raw === 'RJ') return raw\n return raw.slice(1)\n}\n\nfunction isWildCard(card: CardId, wildRanks: readonly string[]): boolean {\n const rank = rankOf(card)\n return wildRanks.length > 0 && rank !== 'BJ' && rank !== 'RJ' && wildRanks.includes(rank)\n}\n\nexport function CardFace({\n card, selected = false, laiZiRanks = [],\n}: {\n card: CardId\n selected?: boolean\n laiZiRanks?: readonly string[]\n}) {\n const raw = cardBase(card)\n const wild = isWildCard(card, laiZiRanks)\n if (raw === 'BJ' || raw === 'RJ' || rankOf(card) === 'BJ' || rankOf(card) === 'RJ') {\n const red = rankOf(card) === 'RJ'\n return (\n <div className={`${css.cardFace} ${css.jokerFace} ${red ? css.red : ''} ${selected ? css.selected : ''}`}>\n <img\n className={css.jokerArt}\n src={red ? `${ASSET}/joker-red.png` : `${ASSET}/joker-black.png`}\n alt=\"\"\n />\n </div>\n )\n }\n const suit = raw[0] ?? 'S'\n const rank = raw.slice(1)\n const red = suit === 'H' || suit === 'D'\n const art = FACE_ART[rank]\n const glyph = SUIT_GLYPH[suit] ?? suit\n return (\n <div className={`${css.cardFace} ${red ? css.red : ''} ${selected ? css.selected : ''} ${wild ? css.wild : ''} ${art ? '' : css.plainFace}`}>\n <span className={css.rank}>{rank}{wild ? '*' : ''}</span>\n {art\n ? <img className={css.faceArt} src={art} alt=\"\" />\n : <span className={css.suitCenter}>{glyph}</span>}\n <span className={css.suit}>{glyph}</span>\n </div>\n )\n}\n","import type { CardId, SeatState } from '../types.ts'\nimport { CardBack } from './CardBack.tsx'\nimport { CardFace } from './CardFace.tsx'\nimport { SeatAvatar } from './SeatAvatar.tsx'\nimport css from './styles.module.css'\n\nexport function SeatRow({\n seat, align = 'left', revealed, laiZiRanks = [],\n}: {\n seat: SeatState\n align?: 'left' | 'right' | 'center'\n revealed?: readonly CardId[]\n laiZiRanks?: readonly string[]\n}) {\n const alignClass = align === 'right' ? css.seatRight : align === 'center' ? css.seatCenter : ''\n return (\n <div className={`${css.seat} ${alignClass}`}>\n <div className={css.seatHead}>\n <SeatAvatar\n avatarUrl={seat.avatarUrl}\n role={seat.role}\n seat={seat.seat}\n occupied={Boolean(seat.playerId)}\n />\n <div className={css.seatMeta}>\n <div className={css.seatName}>\n {seat.displayName ?? '空座'}\n {seat.role === 'landlord' ? <span className={css.badge}>地主</span> : null}\n {seat.role === 'farmer' ? <span className={css.badge}>农民</span> : null}\n {revealed && revealed.length > 0 ? <span className={css.badge}>明牌</span> : null}\n </div>\n {seat.ready && seat.role === 'empty' ? <div className={css.ready}>准备</div> : null}\n <div className={css.muted}>{seat.connected ? '在线' : seat.playerId ? '离线' : ''}</div>\n </div>\n </div>\n {revealed && revealed.length > 0\n ? (\n <div className={css.miniHand}>\n {revealed.map((card) => <CardFace key={card} card={card} laiZiRanks={laiZiRanks} />)}\n </div>\n )\n : seat.playerId && seat.cardsLeft > 0\n ? <CardBack count={seat.cardsLeft} compact landlord={seat.role === 'landlord'} />\n : null}\n </div>\n )\n}\n","import { useEffect, useState } from 'react'\nimport { holeCount, type BidAction, type DoubleAction } from '../types.ts'\nimport { formatM } from '../settle/math.ts'\nimport { parseAtoms, type CardId, type PlayerView, type Seat, type SeatState } from '../types.ts'\nimport { CardBack } from './CardBack.tsx'\nimport { CardFace } from './CardFace.tsx'\nimport { SeatAvatar } from './SeatAvatar.tsx'\nimport { SeatRow } from './SeatRow.tsx'\nimport { selectionLegal } from './store.ts'\nimport css from './styles.module.css'\n\nexport function TableView({\n view, selected, onToggle, onBid, onDouble, onMingPai, onPlay, onPass, onChat,\n}: {\n view: PlayerView\n selected: CardId[]\n onToggle: (card: CardId) => void\n onBid: (action: BidAction) => void\n onDouble: (action: DoubleAction) => void\n onMingPai?: () => void\n onPlay: () => void\n onPass: () => void\n onReady?: (ready: boolean) => void\n onChat: (text: string) => void\n}) {\n const seats = view.room.seats\n const count = view.room.seatCount ?? (seats.length >= 4 ? 4 : 3)\n const self = view.you.seat ?? 0\n const opponents = opponentSeats(self, count).map((seat) => seatAt(seats, seat))\n const mine = seatAt(seats, self)\n const legal = selectionLegal(view, selected)\n const phase = view.room.phase\n const laiZi = view.laiZiRanks ?? []\n const seconds = useDeadline(phase === 'playing' ? view.deadlineAt : null)\n const seated = seats.filter((seat) => seat.playerId).length\n return (\n <div className={css.table}>\n <div className={css.topbar}>\n <div>\n {view.room.title || '好友局'} · {count}人{view.room.laiZi ? '癞子' : '经典'} · 底注 {formatM(parseAtoms(view.room.stakeAtoms))} · {view.bid ? `${view.bid}倍` : '未叫'} · 阶段 {phaseLabel(phase)}\n </div>\n <div className={css.muted}>余额 {formatM(parseAtoms(view.yourAvailableAtoms))} · 冻结 {formatM(parseAtoms(view.yourEscrowAtoms))}</div>\n </div>\n {seconds !== null\n ? (\n <div className={`${css.timer} ${seconds <= 10 ? css.timerUrgent : ''}`}>\n 出牌剩余 <strong>{seconds}</strong> / 120 秒\n </div>\n )\n : phase === 'waiting'\n ? <div className={css.hint}>{seated}/{count} 人入座,人齐开打。后来的人观战。</div>\n : null}\n {laiZi.length > 0\n ? <div className={css.muted}>癞子:{laiZi.join('、')}</div>\n : null}\n <div className={`${css.opponents} ${opponents.length > 2 ? css.opponentsThree : ''}`}>\n {opponents.map((seat, index) => (\n <SeatRow\n key={seat.seat}\n seat={seat}\n align={index === opponents.length - 1 ? 'right' : index === 0 ? 'left' : 'center'}\n {...(view.revealedBySeat?.[seat.seat] ? { revealed: view.revealedBySeat[seat.seat] } : {})}\n laiZiRanks={laiZi}\n />\n ))}\n </div>\n <div className={css.centerPlay}>\n {view.lastPlays.slice(-1).map((play, index) => (\n play.type === 'pass'\n ? <span key={index} className={css.muted}>过</span>\n : play.cards.map((card) => <CardFace key={card} card={card} laiZiRanks={laiZi} />)\n ))}\n </div>\n <div className={css.bottom}>\n {view.bottom\n ? view.bottom.map((card) => <CardFace key={card} card={card} laiZiRanks={laiZi} />)\n : phase !== 'waiting'\n ? <CardBack count={holeCount(count)} />\n : null}\n </div>\n {view.you.spectator\n ? (\n <>\n <div className={css.selfRow}>\n <div className={css.selfSeat}>\n <SeatAvatar spectator />\n <div className={css.seatName}>\n 观战\n <span className={css.badge}>观战</span>\n </div>\n </div>\n </div>\n <div className={css.hint}>观战中。只能看见已打出的牌与公开区。</div>\n </>\n )\n : (\n <div className={css.selfRow}>\n <div className={css.selfSeat}>\n <SeatAvatar\n avatarUrl={mine?.avatarUrl ?? null}\n role={mine?.role ?? 'empty'}\n seat={self}\n occupied={Boolean(mine?.playerId)}\n />\n <div className={css.seatName}>\n {mine?.displayName}\n {mine?.role === 'landlord' ? <span className={css.badge}>地主</span> : null}\n {mine?.role === 'farmer' ? <span className={css.badge}>农民</span> : null}\n {view.mingPaiBySeat?.[self] ? <span className={css.badge}>明牌</span> : null}\n </div>\n </div>\n {phase === 'waiting' && !view.bottom\n ? <CardBack count={holeCount(count)} compact />\n : (\n <div className={css.hand}>\n <div className={css.handInner}>\n {view.you.cards.map((card) => (\n <button type=\"button\" key={card} onClick={() => { onToggle(card) }}>\n <CardFace card={card} selected={selected.includes(card)} laiZiRanks={laiZi} />\n </button>\n ))}\n </div>\n </div>\n )}\n </div>\n )}\n <div className={css.actions}>\n {phase === 'waiting' && !view.you.spectator\n ? <p className={css.hint}>等满 {count} 人自动开打。</p>\n : null}\n {phase === 'bidding' && isTurn(view)\n ? (\n <>\n {view.auction?.kind === 'rob'\n ? (\n <>\n <button type=\"button\" className={css.ghost} onClick={() => { onBid('pass') }}>不抢</button>\n <button type=\"button\" className={css.primary} onClick={() => { onBid('rob') }}>抢地主</button>\n </>\n )\n : (\n <>\n <button type=\"button\" className={css.ghost} onClick={() => { onBid('pass') }}>不叫</button>\n <button type=\"button\" className={css.primary} onClick={() => { onBid('call') }}>叫地主</button>\n </>\n )}\n </>\n )\n : null}\n {canMingPai(view) && onMingPai\n ? <button type=\"button\" className={css.ghost} onClick={onMingPai}>明牌</button>\n : null}\n {phase === 'doubling' && !view.you.spectator\n ? (\n <>\n <button type=\"button\" className={css.ghost} onClick={() => { onDouble('pass') }}>不加倍</button>\n <button type=\"button\" className={css.primary} onClick={() => { onDouble('double') }}>加倍 ×2</button>\n <button type=\"button\" className={css.primary} onClick={() => { onDouble('reDouble') }}>超级加倍 ×4</button>\n </>\n )\n : null}\n {phase === 'playing' && isTurn(view) && !view.you.spectator\n ? (\n <>\n <button type=\"button\" className={css.primary} disabled={!legal} onClick={onPlay}>出牌</button>\n <button type=\"button\" className={css.ghost} disabled={!view.legal.canPass} onClick={onPass}>过</button>\n </>\n )\n : null}\n </div>\n <div className={css.chat}>\n {view.chat.map((line) => <div key={line.ts}>{line.displayName}: {line.text}</div>)}\n {!view.you.spectator\n ? (\n <form onSubmit={(event) => {\n event.preventDefault()\n const data = new FormData(event.currentTarget)\n const text = String(data.get('chat') ?? '')\n if (text) onChat(text)\n event.currentTarget.reset()\n }}\n >\n <input className={css.input} name=\"chat\" maxLength={80} placeholder=\"聊天\" />\n </form>\n )\n : null}\n </div>\n </div>\n )\n}\n\nfunction opponentSeats(self: Seat, count: number): Seat[] {\n const out: Seat[] = []\n for (let step = 1; step < count; step += 1) {\n out.push(((self + step) % count) as Seat)\n }\n return out\n}\n\nfunction seatAt(seats: readonly SeatState[], index: number): SeatState {\n return seats[index] ?? {\n seat: index as Seat,\n playerId: null,\n displayName: null,\n avatarUrl: null,\n ready: false,\n connected: false,\n role: 'empty',\n grantId: null,\n cardsLeft: 0,\n }\n}\n\nfunction isTurn(view: PlayerView): boolean {\n return view.you.seat !== null && view.turnSeat === view.you.seat\n}\n\nfunction useDeadline(deadlineAt: string | null): number | null {\n const [now, setNow] = useState(() => Date.now())\n useEffect(() => {\n if (!deadlineAt) return undefined\n const tick = setInterval(() => { setNow(Date.now()) }, 250)\n return () => { clearInterval(tick) }\n }, [deadlineAt])\n if (!deadlineAt) return null\n return Math.max(0, Math.ceil((Date.parse(deadlineAt) - now) / 1000))\n}\n\nfunction canMingPai(view: PlayerView): boolean {\n if (view.you.spectator || view.you.seat === null) return false\n if (view.mingPaiBySeat?.[view.you.seat]) return false\n return view.room.phase === 'bidding' || view.room.phase === 'doubling' || view.room.phase === 'playing'\n}\n\nfunction phaseLabel(phase: PlayerView['room']['phase']): string {\n if (phase === 'waiting') return '等人'\n if (phase === 'bidding') return '叫抢'\n if (phase === 'doubling') return '加倍'\n if (phase === 'playing') return '出牌'\n if (phase === 'settling') return '结算'\n return phase\n}\n\nexport type { Seat }\n","import { useEffect, useMemo, useState } from 'react'\nimport { DEFAULT_MAX_MULTIPLIER, DEFAULT_STAKE_M, DEFAULT_WELCOME_ATOMS } from '../settle/math.ts'\nimport type { CardId, ClientCommand, PlayerView, SeatCount } from '../types.ts'\nimport { connectChannel, createRoom, joinRoom, sendCommand, type CreateRoomResponse } from './host-api.ts'\nimport { RoomCodeBar } from './InviteDialog.tsx'\nimport { LobbyView } from './LobbyView.tsx'\nimport { SettlementView } from './SettlementView.tsx'\nimport { emptyState, toggleCard } from './store.ts'\nimport { TableView } from './TableView.tsx'\nimport css from './styles.module.css'\n\nexport function HostApp({ onClose }: { onClose: () => void }) {\n const [stakeM, setStakeM] = useState(DEFAULT_STAKE_M)\n const [maxMultiplier, setMaxMultiplier] = useState(DEFAULT_MAX_MULTIPLIER)\n const [seatCount, setSeatCount] = useState<SeatCount>(3)\n const [laiZi, setLaiZi] = useState(false)\n const [title, setTitle] = useState('好友局')\n const [tab, setTab] = useState<'create' | 'join'>('create')\n const [joinCode, setJoinCode] = useState('')\n const [joinInvite, setJoinInvite] = useState('')\n const [joinName, setJoinName] = useState('好友')\n const [state, setState] = useState(emptyState)\n const [creating, setCreating] = useState(false)\n const [joining, setJoining] = useState(false)\n const [roomId, setRoomId] = useState<string | null>(null)\n const seq = useMemo(() => ({ value: 0 }), [])\n\n useEffect(() => {\n const onHash = (): void => {\n const match = /#\\/doudizhu\\/room\\/([^/]+)/.exec(location.hash)\n if (match) setRoomId(match[1] ?? null)\n }\n onHash()\n window.addEventListener('hashchange', onHash)\n return () => { window.removeEventListener('hashchange', onHash) }\n }, [])\n\n useEffect(() => {\n if (!roomId || !state.view) return\n return undefined\n }, [roomId, state.view])\n\n const applyView = (view: PlayerView): void => {\n setState((prev) => ({ ...prev, view, selected: prev.selected.filter((card) => view.you.cards.includes(card)) }))\n }\n\n const startRoom = async (): Promise<void> => {\n setCreating(true)\n setState((prev) => ({ ...prev, error: null }))\n try {\n const created: CreateRoomResponse = await createRoom({\n stakeM, maxMultiplier, seatCount, laiZi, hostDisplayName: '房主', title,\n })\n setRoomId(created.roomId)\n setState((prev) => ({\n ...prev,\n view: created.view,\n shareable: created.shareable,\n sitUrl: created.sitUrl,\n watchUrl: created.watchUrl,\n roomCode: created.roomCode,\n }))\n location.hash = `#/doudizhu/room/${created.roomId}`\n listenRoom(created.roomId, created.wsTicket)\n } catch (error) {\n setState((prev) => ({ ...prev, error: error instanceof Error ? error.message : 'create failed' }))\n } finally {\n setCreating(false)\n }\n }\n\n const enterRoom = async (): Promise<void> => {\n setJoining(true)\n setState((prev) => ({ ...prev, error: null }))\n try {\n const result = await joinRoom({\n roomCode: joinCode.trim(),\n invite: joinInvite.trim(),\n displayName: joinName,\n role: 'sit',\n })\n setRoomId(result.roomId)\n applyView(result.view)\n setState((prev) => ({ ...prev, roomCode: result.view.room.roomCode }))\n location.hash = `#/doudizhu/room/${result.roomId}`\n listenRoom(result.roomId, result.wsTicket)\n } catch (error) {\n setState((prev) => ({ ...prev, error: error instanceof Error ? error.message : 'join failed' }))\n } finally {\n setJoining(false)\n }\n }\n\n const listenRoom = (nextRoomId: string, ticket: string): void => {\n connectChannel(ticket, (event) => {\n if (event.type === 'snapshot') {\n seq.value = event.seq\n applyView(event.view)\n }\n if (event.type === 'settled') {\n seq.value = event.seq\n setState((prev) => ({ ...prev, settlement: event.settlement }))\n }\n if (event.type === 'reject') {\n setState((prev) => ({ ...prev, error: event.reason }))\n }\n }, { roomId: nextRoomId, seq: () => seq.value })\n }\n\n const command = (cmd: ClientCommand): void => {\n if (!roomId) return\n void sendCommand(roomId, cmd).catch((error: unknown) => {\n setState((prev) => ({ ...prev, error: error instanceof Error ? error.message : 'command failed' }))\n })\n }\n\n return (\n <div className={css.root}>\n <div className={css.topbar}>\n <div className={css.brand}>斗地主</div>\n <button type=\"button\" className={css.close} onClick={onClose}>关闭</button>\n </div>\n {state.error ? <div className={css.banner}>{state.error}</div> : null}\n {!state.view\n ? (\n <LobbyView\n tab={tab}\n onTab={setTab}\n title={title}\n stakeM={stakeM}\n maxMultiplier={maxMultiplier}\n seatCount={seatCount}\n laiZi={laiZi}\n onTitle={setTitle}\n onStake={setStakeM}\n onCap={setMaxMultiplier}\n onSeats={setSeatCount}\n onLaiZi={setLaiZi}\n onCreate={() => { void startRoom() }}\n creating={creating}\n error={state.error}\n roomCode={state.roomCode}\n sitUrl={state.sitUrl}\n watchUrl={state.watchUrl}\n shareable={state.shareable}\n welcomeAtoms={DEFAULT_WELCOME_ATOMS}\n join={{\n code: joinCode,\n invite: joinInvite,\n name: joinName,\n joining,\n onCode: setJoinCode,\n onInvite: setJoinInvite,\n onName: setJoinName,\n onJoin: () => { void enterRoom() },\n }}\n />\n )\n : state.settlement && state.view.room.phase === 'waiting'\n ? (\n <SettlementView\n settlement={state.settlement}\n onRematch={() => {\n setState((prev) => ({ ...prev, settlement: null }))\n command({ type: 'rematch' })\n }}\n />\n )\n : (\n <>\n {state.roomCode\n ? (\n <div className={css.banner}>\n <RoomCodeBar\n title={state.view.room.title}\n roomCode={state.roomCode}\n sitUrl={state.sitUrl}\n shareable={state.shareable}\n canRename\n onRename={(next) => { command({ type: 'rename', title: next }) }}\n />\n </div>\n )\n : null}\n <TableView\n view={state.view}\n selected={state.selected}\n onToggle={(card: CardId) => { setState((prev) => ({ ...prev, selected: toggleCard(prev.selected, card) })) }}\n onBid={(action) => { command({ type: 'bid', action }) }}\n onDouble={(action) => { command({ type: 'double', action }) }}\n onMingPai={() => { command({ type: 'mingPai' }) }}\n onPlay={() => { command({ type: 'play', cards: state.selected, nonce: crypto.randomUUID() }) }}\n onPass={() => { command({ type: 'pass', nonce: crypto.randomUUID() }) }}\n onReady={(ready) => { command({ type: 'ready', ready }) }}\n onChat={(text) => { command({ type: 'chat', text }) }}\n />\n </>\n )}\n </div>\n )\n}\n","import { useEffect, useState } from 'react'\nimport type { SettingsScope } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { PluginConfig } from '../config.ts'\nimport css from './styles.module.css'\n\ninterface SettingsDraft {\n publicBaseUrl: string\n welcomeAtoms: string\n defaultSeatCount: 3 | 4\n defaultMaxMultiplier: number\n defaultLaiZi: boolean\n spectatorCardCounter: boolean\n}\n\nfunction draftFrom(value: PluginConfig): SettingsDraft {\n return {\n publicBaseUrl: value.publicBaseUrl ?? '',\n welcomeAtoms: value.welcomeAtoms ?? '200000000',\n defaultSeatCount: value.defaultSeatCount === 4 ? 4 : 3,\n defaultMaxMultiplier: value.defaultMaxMultiplier ?? 8,\n defaultLaiZi: value.defaultLaiZi === true,\n spectatorCardCounter: value.spectatorCardCounter === true,\n }\n}\n\nfunction draftsEqual(left: SettingsDraft, right: SettingsDraft): boolean {\n return left.publicBaseUrl === right.publicBaseUrl\n && left.welcomeAtoms === right.welcomeAtoms\n && left.defaultSeatCount === right.defaultSeatCount\n && left.defaultMaxMultiplier === right.defaultMaxMultiplier\n && left.defaultLaiZi === right.defaultLaiZi\n && left.spectatorCardCounter === right.spectatorCardCounter\n}\n\nfunction validateDraft(draft: SettingsDraft): string | null {\n const publicBaseUrl = draft.publicBaseUrl.trim().replace(/\\/+$/, '')\n if (publicBaseUrl !== '') {\n let parsed: URL\n try {\n parsed = new URL(publicBaseUrl)\n } catch {\n return 'publicBaseUrl 必须是绝对 http(s) origin,不要路径或尾斜杠'\n }\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n return 'publicBaseUrl 必须是 http 或 https'\n }\n if (parsed.origin !== publicBaseUrl) {\n return 'publicBaseUrl 必须是 origin,不要路径、查询或尾斜杠'\n }\n }\n if (!/^\\d+$/.test(draft.welcomeAtoms)) return '欢迎积分必须是非负整数'\n if (![8, 16, 32, 64].includes(draft.defaultMaxMultiplier)) return '倍数封顶只能是 8、16、32 或 64'\n return null\n}\n\nexport function SettingsCard({ scope }: { scope: SettingsScope<PluginConfig> }) {\n const [snap, setSnap] = useState(() => scope.getSnapshot())\n const [open, setOpen] = useState(false)\n const [draft, setDraft] = useState<SettingsDraft | null>(() => snap.value ? draftFrom(snap.value) : null)\n const [saving, setSaving] = useState(false)\n const [saved, setSaved] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n useEffect(() => scope.subscribe(() => {\n const next = scope.getSnapshot()\n setSnap((prevSnap) => {\n if (next.value) {\n const incoming = draftFrom(next.value)\n const previousStored = prevSnap.value ? draftFrom(prevSnap.value) : incoming\n setDraft((prev) => {\n if (!prev || draftsEqual(prev, previousStored)) return incoming\n return prev\n })\n }\n return next\n })\n }), [scope])\n\n useEffect(() => {\n if (draft || !snap.value) return\n setDraft(draftFrom(snap.value))\n }, [draft, snap.value])\n\n const value = snap.value\n if (!value || !draft) return null\n const stored = draftFrom(value)\n const dirty = !draftsEqual(draft, stored)\n const patch = <K extends keyof SettingsDraft>(field: K, next: SettingsDraft[K]): void => {\n setSaved(false)\n setError(null)\n setDraft((prev) => prev ? { ...prev, [field]: next } : prev)\n }\n\n const save = async (): Promise<void> => {\n const invalid = validateDraft(draft)\n if (invalid) {\n setError(invalid)\n return\n }\n const next: SettingsDraft = {\n ...draft,\n publicBaseUrl: draft.publicBaseUrl.trim().replace(/\\/+$/, ''),\n }\n setDraft(next)\n setSaving(true)\n setError(null)\n try {\n const writes: Array<readonly [keyof SettingsDraft, string | number | boolean]> = [\n ['publicBaseUrl', next.publicBaseUrl],\n ['welcomeAtoms', next.welcomeAtoms],\n ['defaultSeatCount', next.defaultSeatCount],\n ['defaultMaxMultiplier', next.defaultMaxMultiplier],\n ['defaultLaiZi', next.defaultLaiZi],\n ['spectatorCardCounter', next.spectatorCardCounter],\n ]\n for (const [field, fieldValue] of writes) {\n if (stored[field] === fieldValue) continue\n await scope.set(String(field), fieldValue)\n }\n setSaved(true)\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : '保存失败')\n } finally {\n setSaving(false)\n }\n }\n\n return (\n <div className={`${css.settingsCard} ${open ? css.settingsOpen : ''}`}>\n <button\n type=\"button\"\n className={css.settingsHeader}\n aria-expanded={open}\n onClick={() => { setOpen(!open) }}\n >\n <span className={css.settingsHeadText}>\n <span className={css.settingsName}>dsh-poker</span>\n <span className={css.settingsDesc}>本机房间默认:人数、癞子、邀请地址和欢迎积分。同一房间先到入座,后来观战。</span>\n </span>\n <svg\n className={`${css.settingsChevron} ${open ? css.settingsChevronOpen : ''}`}\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n aria-hidden=\"true\"\n >\n <path d=\"M3.2 5.1 7 8.9l3.8-3.8\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </button>\n {open\n ? (\n <div className={css.settingsBody}>\n <p className={css.settingsHint}>开房在侧栏 New Session 下面的「斗地主」,这里只改下次开房的默认值。点保存后生效,已经开着的房间不动。</p>\n <label className={css.settingsField}>\n <span className={css.settingsLabel}>对外地址 publicBaseUrl</span>\n <input\n className={css.settingsInput}\n value={draft.publicBaseUrl}\n placeholder=\"https://your-named-tunnel.example\"\n onChange={(event) => { patch('publicBaseUrl', event.target.value) }}\n />\n <p className={css.settingsFieldHint}>填能打到本机的 https origin,好友才能用邀请链接进桌。空着只能本机标签页互打。</p>\n </label>\n <label className={css.settingsField}>\n <span className={css.settingsLabel}>欢迎积分</span>\n <input\n className={css.settingsInput}\n inputMode=\"numeric\"\n value={draft.welcomeAtoms}\n onChange={(event) => { patch('welcomeAtoms', event.target.value) }}\n />\n <p className={css.settingsFieldHint}>进房时账本里的起始积分,不是 DeepSeek 平台余额。默认 200M。</p>\n </label>\n <label className={css.settingsField}>\n <span className={css.settingsLabel}>默认人数</span>\n <select\n className={css.settingsInput}\n value={draft.defaultSeatCount}\n onChange={(event) => { patch('defaultSeatCount', Number(event.target.value) === 4 ? 4 : 3) }}\n >\n <option value={3}>3 人</option>\n <option value={4}>4 人</option>\n </select>\n <p className={css.settingsFieldHint}>3 人一副牌 3 张底牌;4 人两副牌 8 张底牌。</p>\n </label>\n <label className={css.settingsField}>\n <span className={css.settingsLabel}>默认倍数封顶</span>\n <input\n className={css.settingsInput}\n inputMode=\"numeric\"\n value={String(draft.defaultMaxMultiplier)}\n onChange={(event) => { patch('defaultMaxMultiplier', Number(event.target.value)) }}\n />\n </label>\n <div className={css.settingsField}>\n <div className={css.settingsSwitchRow}>\n <span className={css.settingsLabel}>默认开癞子</span>\n <button\n type=\"button\"\n className={css.settingsSwitch}\n role=\"switch\"\n aria-checked={draft.defaultLaiZi}\n onClick={() => { patch('defaultLaiZi', !draft.defaultLaiZi) }}\n >\n <span className={css.settingsSwitchTrack} data-on={draft.defaultLaiZi ? '' : undefined}>\n <span className={css.settingsSwitchThumb} />\n </span>\n </button>\n </div>\n <p className={css.settingsFieldHint}>下次开房默认癞子。翻两张万能牌,大小王不当癞子。</p>\n </div>\n <div className={css.settingsField}>\n <div className={css.settingsSwitchRow}>\n <span className={css.settingsLabel}>旁观记牌器</span>\n <button\n type=\"button\"\n className={css.settingsSwitch}\n role=\"switch\"\n aria-checked={draft.spectatorCardCounter}\n onClick={() => { patch('spectatorCardCounter', !draft.spectatorCardCounter) }}\n >\n <span className={css.settingsSwitchTrack} data-on={draft.spectatorCardCounter ? '' : undefined}>\n <span className={css.settingsSwitchThumb} />\n </span>\n </button>\n </div>\n <p className={css.settingsFieldHint}>观战可见剩余牌张。默认关。</p>\n </div>\n <div className={css.settingsActions}>\n <button\n type=\"button\"\n className={css.primary}\n disabled={saving || !dirty || snap.writable === false}\n onClick={() => { void save() }}\n >\n {saving ? '保存中…' : 'Save'}\n </button>\n <button\n type=\"button\"\n className={css.ghost}\n disabled={saving || !dirty}\n onClick={() => {\n setDraft(stored)\n setSaved(false)\n setError(null)\n }}\n >\n Discard\n </button>\n {error\n ? <p className={css.settingsSaveError}>{error}</p>\n : saved && !dirty\n ? <p className={css.settingsSaveOk}>已保存,下次开房生效</p>\n : dirty\n ? <p className={css.settingsFieldHint}>有未保存的更改</p>\n : null}\n </div>\n {snap.status === 'unavailable'\n ? <p className={css.settingsHint}>没挂 storageDomain 时房间只在内存里,重启就没了。</p>\n : null}\n </div>\n )\n : null}\n </div>\n )\n}\n","import { useEffect, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { PluginConfig } from '../config.ts'\nimport { HostApp } from './HostApp.tsx'\nimport { SettingsCard } from './SettingsCard.tsx'\nimport css from './styles.module.css'\n\nexport function registerClientSlots(ctx: ClientContext): void {\n ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({\n name: 'sidebar.footer.action',\n id: 'doudizhu',\n }, DouDizhuSidebarButton))\n\n ctx.slots.inject('shell.overlay', () => ctx.slots.register({\n name: 'shell.overlay',\n id: 'doudizhu',\n }, DouDizhuOverlay))\n\n ctx.slots.inject('settings.plugin.item', () => {\n const scope = ctx.settingsScope.bind<PluginConfig>({ namespace: 'doudizhu' })\n return ctx.slots.register({\n name: 'settings.plugin.item',\n key: 'doudizhu',\n }, function DouDizhuSettingsCard() {\n return <SettingsCard scope={scope} />\n })\n })\n}\n\nfunction DouDizhuSidebarButton({ wide }: { wide?: boolean }) {\n const [mount, setMount] = useState<HTMLElement | null>(null)\n\n useEffect(() => {\n let host: HTMLElement | null = null\n const place = (): void => {\n const after = findNewSessionAnchor()\n if (!after) return\n if (!host) {\n host = document.createElement('div')\n host.dataset.doudizhuEntry = ''\n }\n if (host.previousElementSibling !== after || !host.isConnected) {\n after.insertAdjacentElement('afterend', host)\n }\n setMount((prev) => (prev === host ? prev : host))\n }\n place()\n const observer = new MutationObserver(place)\n observer.observe(document.body, { childList: true, subtree: true })\n return () => {\n observer.disconnect()\n host?.remove()\n }\n }, [])\n\n useEffect(() => {\n if (!mount) return\n const slot = css.sidebarSlot ?? ''\n const rail = css.sidebarSlotRail ?? ''\n mount.className = wide ? slot : `${slot} ${rail}`\n }, [mount, wide])\n\n if (!mount) return null\n return createPortal(\n <button\n type=\"button\"\n className={`${css.sidebarBtn} ${wide ? '' : css.sidebarBtnRail}`}\n onClick={() => { location.hash = '#/doudizhu' }}\n >\n <span>{wide ? '斗地主' : '斗'}</span>\n {wide ? <span className={css.muted}>dsh-poker</span> : null}\n </button>,\n mount,\n )\n}\n\nfunction findNewSessionAnchor(): HTMLElement | null {\n const labeled = document.querySelectorAll<HTMLButtonElement>(\n 'button[aria-label=\"New session\"], button[aria-label=\"新建会话\"]',\n )\n const button = labeled[labeled.length - 1]\n ?? [...document.querySelectorAll('button')].find((el) => {\n const text = el.textContent?.replace(/\\s+/g, ' ').trim()\n return text === 'New Session' || text === '新会话'\n })\n ?? null\n if (!button) return null\n let node: HTMLElement = button\n let parent = node.parentElement\n while (parent) {\n const style = getComputedStyle(parent)\n if (style.display === 'flex' && style.flexDirection === 'column') return node\n node = parent\n parent = node.parentElement\n }\n return button\n}\n\nfunction DouDizhuOverlay() {\n const [open, setOpen] = useState(() => location.hash.startsWith('#/doudizhu'))\n useHashOpen(setOpen)\n if (!open) return null\n return <HostApp onClose={() => { location.hash = '#/' }} />\n}\n\nfunction useHashOpen(setOpen: (open: boolean) => void): void {\n useEffect(() => {\n const sync = (): void => { setOpen(location.hash.startsWith('#/doudizhu')) }\n window.addEventListener('hashchange', sync)\n return () => { window.removeEventListener('hashchange', sync) }\n }, [setOpen])\n}\n","import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only: settingsScope + settings.plugin.item. Value-importing ui-settings\n// fails the client bundle-purity gate.\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport { registerClientSlots } from './slots.tsx'\n\nexport const name = 'doudizhu-client'\n// settingsScope.bind() reads connection + remote from the caller fiber.\nexport const inject = ['slots', 'connection', 'remote', 'settingsScope']\n\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => {\n registerClientSlots(ctx)\n return () => { /* slot injects dispose with fiber */ }\n })\n}\n"],"mappings":"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"}
|