牌面只用一个字符表示点数,单机可玩也能联网。默认就是摸鱼模式 —— 全程伪装成正常的开发输出。 下面是真实运行时的画面。
菜单上只有「玩法」和「是否联机」两件事。想连菜单都不出现,直接 npx dou-cl -g doudizhu。
dou-cl v1.0.1
──────────────────────────────────────────────────────────
单机
1 斗地主 3 人 · 17 张 + 3 张底牌
2 跑得快 3 人 · 一副牌去掉大小王
3 510K 4 人 · 两副牌 · 5/10/K 计分
联机(一个 ip:port 就是一个房间)
4 加入房间 5 开一个房间
0 退出
──────────────────────────────────────────────────────────
直接回车 = 1(单机斗地主)
请选择 > _牌桌信息一条都不删 —— 手牌、各家剩几张、谁出的什么、场上最大、轮谁、结果,全都在输出里, 只是换了一层壳。每个壳都是自洽的:hex 是真字节、JSON 能解析、diff 能 apply。 随时敲 reveal 看真身,skin <名> 现场换皮肤。
tail -f 一个服务日志 · hand[] / peers[] / table= 就是牌局
2026-10-09 11:08:57.333 DEBUG [pool-3-thread-2] c.acme.cache.LruCache get key=deck:1E49 hit=true ttl=27 2026-10-09 11:08:57.334 DEBUG [pool-3-thread-2] c.acme.metrics.Meter flush points=128 cost=1ms 2026-10-09 11:08:57.336 INFO [pool-3-thread-2] c.acme.deck.SyncService pool=1E49 seq=1 event=state.sync 2026-10-09 11:08:57.336 INFO [pool-3-thread-2] c.acme.deck.SyncService phase=play turn=self self=pending 2026-10-09 11:08:57.337 INFO [pool-3-thread-2] c.acme.deck.SyncService hand[17]=[w Ah Ks Kd Qh Qc Qd 9s 8c 7s 7h 7c 5c 5d 3s 3h 3c] 2026-10-09 11:08:57.337 INFO [pool-3-thread-2] c.acme.deck.SyncService peers=[mei:17/farmer fei:7/landlord] 2026-10-09 11:08:57.338 INFO [pool-3-thread-2] c.acme.deck.SyncService table=fei->[Jh Jc Jd Kh Kc] type=trio2 cards=5 2026-10-09 11:08:57.338 INFO [pool-3-thread-2] c.acme.deck.SyncService bottom=[4s Jh 6c] 2026-10-09 11:08:57.338 INFO [pool-3-thread-2] c.acme.deck.SyncService base=3 multiplier=x1 2026-10-09 11:08:57.338 INFO [pool-3-thread-2] c.acme.deck.SyncService$Replay mei:bid 1 2026-10-09 11:08:57.338 INFO [pool-3-thread-2] c.acme.deck.SyncService$Replay fei:bid 3 2026-10-09 11:08:57.338 INFO [pool-3-thread-2] c.acme.deck.SyncService$Replay fei:landlord 2026-10-09 11:08:57.338 INFO [pool-3-thread-2] c.acme.deck.SyncService$Replay fei:play 4s4h4c4d6s6c0h0c [four2pair] 2026-10-09 11:08:57.338 INFO [pool-3-thread-2] c.acme.deck.SyncService$Replay self:pass 2026-10-09 11:08:57.338 INFO [pool-3-thread-2] c.acme.deck.SyncService$Replay mei:pass 2026-10-09 11:08:57.338 INFO [pool-3-thread-2] c.acme.deck.SyncService$Replay fei:play JhJcJdKhKc [trio2] 2026-10-09 11:08:57.340 DEBUG [pool-3-thread-2] c.acme.metrics.Meter flush points=67 cost=1ms [tail -f] $ _
xxd 转储 · hex 是真字节,右边 ASCII 列就是要读的
$ xxd -c 24 logs/deck-sync.log 00000000: 32303236 2d31302d 30392031 313a3038 3a35372e 33343120 2026-10-09 11:08:57.341 00000018: 44454255 47206465 636b2e73 796e6320 20202020 20706f6f DEBUG deck.sync poo 00000030: 6c3d3446 33362073 65713d31 0a323032 362d3130 2d303920 l=4F36 seq=1.2026-10-09 00000048: 31313a30 383a3537 2e333432 20444542 55472063 61636865 11:08:57.342 DEBUG cache 00000060: 2e4c7275 43616368 65206765 74206b65 793d6465 636b3a34 .LruCache get key=deck:4 00000078: 46333620 6869743d 74727565 2074746c 3d32370a 32303236 F36 hit=true ttl=27.2026 00000090: 2d31302d 30392031 313a3038 3a35372e 33343320 494e464f -10-09 11:08:57.343 INFO 000000a8: 20206465 636b2e73 796e6320 20202020 20706861 73653d70 deck.sync phase=p 000000c0: 6c617920 7475726e 3d73656c 662a3e0a 32303236 2d31302d lay turn=self*>.2026-10- 000000d8: 30392031 313a3038 3a35372e 33343320 494e464f 20206465 09 11:08:57.343 INFO de 000000f0: 636b2e73 796e6320 20202020 2068616e 645b3135 5d3d5b4b ck.sync hand[15]=[K 00000108: 68204b63 204b6420 4a73204a 68204a63 204a6320 4a642030 h Kc Kd Js Jh Jc Jc Jd 0 00000120: 64203964 20377320 37732037 68203763 2036645d 0a323032 d 9d 7s 7s 7h 7c 6d].202 00000138: 362d3130 2d303920 31313a30 383a3537 2e333434 20494e46 6-10-09 11:08:57.344 INF 00000150: 4f202064 65636b2e 73796e63 20202020 20207065 6572733d O deck.sync peers= 00000168: 5b6d6569 3a313220 6665693a 32332077 616e673a 32335d0a [mei:12 fei:23 wang:23]. 00000180: 32303236 2d31302d 30392031 313a3038 3a35372e 33343420 2026-10-09 11:08:57.344 00000198: 494e464f 20206465 636b2e73 796e6320 20202020 20746162 INFO deck.sync tab 000001b0: 6c653d6d 65692d3e 5b356820 3068204b 685d2066 69667479 le=mei->[5h 0h Kh] fifty 000001c8: 6b0a3230 32362d31 302d3039 2031313a 30383a35 372e3334 k.2026-10-09 11:08:57.34 000001e0: 3520494e 464f2020 6465636b 2e73796e 63202020 20202020 5 INFO deck.sync 000001f8: 2077616e 673a7061 73730a32 3032362d 31302d30 39203131 wang:pass.2026-10-09 11 00000210: 3a30383a 35372e33 34352049 4e464f20 20646563 6b2e7379 :08:57.345 INFO deck.sy 00000228: 6e632020 20202020 20207365 6c663a73 636f7265 202b3130 nc self:score +10 00000240: 0a323032 362d3130 2d303920 31313a30 383a3537 2e333435 .2026-10-09 11:08:57.345 00000258: 20494e46 4f202064 65636b2e 73796e63 20202020 20202020 INFO deck.sync 00000270: 73656c66 3a706c61 79203368 33643364 34733463 34645168 self:play 3h3d3d4s4c4dQh 00000288: 51633263 3264205b 706c616e 65325d0a 32303236 2d31302d Qc2c2d [plane2].2026-10- 000002a0: 30392031 313a3038 3a35372e 33343520 494e464f 20206465 09 11:08:57.345 INFO de 000002b8: 636b2e73 796e6320 20202020 2020206d 65693a70 6c617920 ck.sync mei:play 000002d0: 35683068 4b68205b 66696674 796b5d0a 32303236 2d31302d 5h0hKh [fiftyk].2026-10- 000002e8: 30392031 313a3038 3a35372e 33343520 494e464f 20206465 09 11:08:57.345 INFO de 00000300: 636b2e73 796e6320 20202020 20202066 65693a70 6173730a ck.sync fei:pass. 00000318: 32303236 2d31302d 30392031 313a3038 3a35372e 33343520 2026-10-09 11:08:57.345 00000330: 494e464f 20206465 636b2e73 796e6320 20202020 20202077 INFO deck.sync w 00000348: 616e673a 70617373 0a ang:pass. dev@ws:~/svc$ _
WS 帧 + jq 配色 · 去掉颜色就是合法 JSON
11:08:57.343 DEBUG [ws-client ] <- {"type":"deck.state","seq":1} { "ts": "2026-10-09T11:08:57.343+08:00", "seq": 1, "pool": "0C35", "game": "paodekuai", "phase": "play", "turn": "self", "self": { "id": "self", "pending": true, "hand": ["As", "Kc", "Qd", "0d", "9h", "9c", "5s", "3c"], "hold": 8 }, "peers": [ { "id": "mei", "hold": 8, "out": false, "score": 0 }, { "id": "fei", "hold": 15, "out": false, "score": 0 } ], "table": { "by": "fei", "cards": ["Ah", "Ac"], "type": "pair" }, "recent": [ { "by": "fei", "action": "pass" }, { "by": "self", "action": "play", "cards": ["7h", "8c", "9s", "0h", "Jc", "Qs", "Ks"], "type": "straight" }, { "by": "mei", "action": "pass" }, { "by": "fei", "action": "pass" }, { "by": "self", "action": "play", "cards": ["4s", "4d"], "type": "pair" }, { "by": "mei", "action": "play", "cards": ["Qh", "Qc"], "type": "pair" }, { "by": "fei", "action": "play", "cards": ["Ah", "Ac"], "type": "pair" } ], "result": null } node > _
YAML 统一 diff · 绿色 + 行是当前牌局,diff 可 apply
$ git diff -U50 -- src/deck/session.yaml diff --git a/src/deck/session.yaml b/src/deck/session.yaml index 5568106..025f5f3 100644 --- a/src/deck/session.yaml +++ b/src/deck/session.yaml @@ -1,23 +1,23 @@ session: pool: C5C0 - seq: 1 + seq: 2 game: doudizhu phase: play turn: self self: hand: [2c, 2d, As, Qs, Jd, 0c, 9h, 9c, 9d, 8s, 8d, 7c, 6h, 5h, 4c, 4d, 3d] hold: 17 peers: - { id: mei, hold: 6, role: landlord, out: false, score: 0 } - { id: fei, hold: 15, role: farmer, out: false, score: 0 } table: { by: fei, cards: [0s], type: single } bottom: [4s, 0d, 4h] recent: - fei:pass - self:pass - mei:play 4h [single] - fei:play 5c [single] - self:pass - mei:play 7h [single] - fei:play 0s [single] result: null dev@ws:~/svc (main)$ _
房间 192.168.1.10:8080 斗地主 · 3 人 ──────────────────────────────────────────────────── 1 号位 你 2 号位 空 · 等待加入 3 号位 空 · 等待加入 ──────────────────────────────────────────────────── 等待玩家 1/3 朋友加入: npx dou-cl --join 192.168.1.10:8080 [s] 用电脑补齐先开局 [q] 退出 > _
| 含义 | 写法 |
|---|---|
| 点数 3–9 | 3 4 5 6 7 8 9 |
| 10 | 0(也接受 10 / T) |
| J / Q / K / A | J Q K A |
| 2 | 2 |
| 小王 / 大王 | w / W |
| 花色 | ♠ ♥ ♣ ♦(--ascii 时为 S H C D;输入可用 s h c d) |
| 伪装输出里的牌 | 点数 + 花色两个字符:3s 0h Jd W |
直接敲点数:34567 或 3 4 5 6 7;指定花色 5s 5h。要五十K / 同花顺直接敲 50K / 56789,程序自动挑同花色那组。
p 不要 h 提示 ? 帮助 quit 退出
reveal 看真身 redraw 重画 skin log|hex|json|diff 换皮肤
一个 ip:port 就是一个房间:本机 --serve --port 8080,别人 --join 地址:端口。人满回绝,人不够就等 —— s 让电脑补齐先开局,q 退出。
不喜欢伪装的话,可以看回原生牌桌(牌面和上面一样,只是排版是牌桌的样子)。
斗地主 · 底分 3 > [你] 你 农民 17张 | (电脑) 小美 地主 10张 | (电脑) 阿飞 农民 17张 底牌: 5♣ Q♦ A♣ ────────────────────────────────────────────────────────── 最近: 你 叫 2 分 小美 叫 3 分 小美 抢到地主 小美 出 5♠ 6♠ 7♠ 8♣ 9♥ 0♣ J♠ Q♥ K♦ A♣ 顺子 阿飞 不要 ────────────────────────────────────────────────────────── 场上最大: 小美 的 5♠ 6♠ 7♠ 8♣ 9♥ 0♣ J♠ Q♥ K♦ A♣ 顺子 ────────────────────────────────────────────────────────── 你的手牌 (17张): w A♦ K♠ K♥ K♣ J♥ J♣ J♦ 0♥ 0♦ 9♠ 8♦ 7♦ 6♣ 5♥ 4♠ 4♣ 出牌 (例: 34567 或 5 5 5 6 / p 不要 / h 提示 / ? 帮助) > _
跑得快 > [你] 你 9张 | (电脑) 小美 13张 | (电脑) 阿飞 17张 ────────────────────────────────────────────────────────── 最近: 你 出 3♠ 3♥ 4♥ 4♣ 连对 小美 出 5♥ 5♣ 6♠ 6♣ 连对 阿飞 不要 你 出 K♥ K♣ A♥ A♦ 连对 小美 不要 阿飞 不要 ────────────────────────────────────────────────────────── 场上最大: 无(自由出牌) ────────────────────────────────────────────────────────── 你的手牌 (9张): 2♥ J♠ J♣ 9♠ 9♥ 7♠ 7♥ 6♥ 5♠ 出牌 (例: 34567 或 5 5 5 6 / p 不要 / h 提示 / ? 帮助) > _
510K > [你] 你 17张 | (电脑) 小美 19张 | (电脑) 阿飞 23张 | (电脑) 老王 27张 ────────────────────────────────────────────────────────── 最近: 老王 不要 你 出 w W 王炸 小美 不要 阿飞 不要 老王 不要 你 收分 +10 ────────────────────────────────────────────────────────── 场上最大: 无(自由出牌) ────────────────────────────────────────────────────────── 你的手牌 (17张): w 2♥ A♠ K♠ K♦ J♠ J♠ 0♠ 0♥ 9♠ 9♣ 8♠ 8♠ 8♦ 7♥ 7♦ 5♦ 出牌 (例: 34567 或 5 5 5 6 / p 不要 / h 提示 / ? 帮助) > _