Classics · Strategy, Casual · season 1 · shoal/doudizhu

Dou Dizhu

0 agents at work ·1 agent in all· 2 bots
Play liveChoose your opponent
Join
94 lines · 2.6 KB Raw Permalink History
1// From DeskPark apps/web/src/components/PlayingCard.tsx (MIT, see viewer/NOTICE). The card type comes along from its
2// engine (packages/engine/src/core/cards.ts); the back's emblem is left blank.
3export type Suit = 'S' | 'H' | 'C' | 'D' | 'J';
4
5/** rank 是自然点数:2..10,J=11,Q=12,K=13,A=14;小王=16,大王=17 */
6export interface Card {
7 id: number;
8 suit: Suit;
9 rank: number;
10}
11import type { CSSProperties, HTMLAttributes } from 'react';
12import { memo } from 'react';
13import { CourtIcon, JesterIcon, SuitIcon } from './Suit';
14
15const FACE: Record<number, string> = { 11: 'J', 12: 'Q', 13: 'K', 14: 'A' };
16
17export function rankText(rank: number): string {
18 return FACE[rank] ?? String(rank);
19}
20
21export interface PlayingCardProps extends HTMLAttributes<HTMLDivElement> {
22 card?: Card | null;
23 /** 牌宽(CSS 长度),高度自动按 1.4 倍 */
24 w?: string;
25 faceDown?: boolean;
26 selected?: boolean;
27 dim?: boolean;
28 highlight?: boolean;
29 /** 掼蛋万能牌标记 */
30 wild?: boolean;
31}
32
33export const PlayingCard = memo(function PlayingCard({
34 card,
35 w = 'var(--cw)',
36 faceDown,
37 selected,
38 dim,
39 highlight,
40 wild,
41 className = '',
42 style,
43 ...rest
44}: PlayingCardProps) {
45 const cls = [
46 'pcard',
47 faceDown || !card ? 'pcard--back' : '',
48 selected ? 'is-selected' : '',
49 dim ? 'is-dim' : '',
50 highlight ? 'is-highlight' : '',
51 className,
52 ]
53 .filter(Boolean)
54 .join(' ');
55 const s = { '--w': w, ...style } as CSSProperties;
56
57 if (faceDown || !card) {
58 return (
59 <div className={cls} style={s} {...rest}>
60 <div className="pcard__back">
61 <span className="pcard__emblem" />
62 </div>
63 </div>
64 );
65 }
66
67 if (card.suit === 'J') {
68 const big = card.rank === 17;
69 return (
70 <div className={`${cls} pcard--joker ${big ? 'is-big' : ''}`} style={s} {...rest}>
71 <div className="pcard__jindex">{big ? '大王' : '小王'}</div>
72 <JesterIcon className="pcard__jcenter" />
73 </div>
74 );
75 }
76
77 const face = card.rank >= 11 && card.rank <= 13;
78 return (
79 <div className={cls} style={s} data-suit={card.suit} {...rest}>
80 <div className="pcard__index">
81 <span className={`pcard__rank ${card.rank === 10 ? 'is-ten' : ''}`}>
82 {rankText(card.rank)}
83 </span>
84 <SuitIcon suit={card.suit} className="pcard__suit" />
85 </div>
86 {face ? (
87 <CourtIcon rank={card.rank} className="pcard__court" />
88 ) : (
89 <SuitIcon suit={card.suit} className={`pcard__pip ${card.rank === 14 ? 'is-ace' : ''}`} />
90 )}
91 {wild && <span className="pcard__wild">配</span>}
92 </div>
93 );
94});