// From DeskPark apps/web/src/components/PlayingCard.tsx (MIT, see viewer/NOTICE). The card type comes along from its // engine (packages/engine/src/core/cards.ts); the back's emblem is left blank. export type Suit = 'S' | 'H' | 'C' | 'D' | 'J'; /** rank 是自然点数:2..10,J=11,Q=12,K=13,A=14;小王=16,大王=17 */ export interface Card { id: number; suit: Suit; rank: number; } import type { CSSProperties, HTMLAttributes } from 'react'; import { memo } from 'react'; import { CourtIcon, JesterIcon, SuitIcon } from './Suit'; const FACE: Record = { 11: 'J', 12: 'Q', 13: 'K', 14: 'A' }; export function rankText(rank: number): string { return FACE[rank] ?? String(rank); } export interface PlayingCardProps extends HTMLAttributes { card?: Card | null; /** 牌宽(CSS 长度),高度自动按 1.4 倍 */ w?: string; faceDown?: boolean; selected?: boolean; dim?: boolean; highlight?: boolean; /** 掼蛋万能牌标记 */ wild?: boolean; } export const PlayingCard = memo(function PlayingCard({ card, w = 'var(--cw)', faceDown, selected, dim, highlight, wild, className = '', style, ...rest }: PlayingCardProps) { const cls = [ 'pcard', faceDown || !card ? 'pcard--back' : '', selected ? 'is-selected' : '', dim ? 'is-dim' : '', highlight ? 'is-highlight' : '', className, ] .filter(Boolean) .join(' '); const s = { '--w': w, ...style } as CSSProperties; if (faceDown || !card) { return (
); } if (card.suit === 'J') { const big = card.rank === 17; return (
{big ? '大王' : '小王'}
); } const face = card.rank >= 11 && card.rank <= 13; return (
{rankText(card.rank)}
{face ? ( ) : ( )} {wild && 配}
); });