dou-cl  命令行斗地主 / 跑得快 / 510K

牌面只用一个字符表示点数,单机可玩也能联网。默认就是摸鱼模式 —— 全程伪装成正常的开发输出。 下面是真实运行时的画面。

菜单 npx dou-cl

菜单上只有「玩法」和「是否联机」两件事。想连菜单都不出现,直接 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 <名> 现场换皮肤。

默认 · 日志流 npx dou-cl -g doudizhu

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] $ _

换肤 · hexdump npx dou-cl --skin hex

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$ _

换肤 · JSON 应答 npx dou-cl --skin json

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 > _

换肤 · git diff npx dou-cl --skin diff

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)$ _

联网房间 npx dou-cl --serve --port 8080

  房间 192.168.1.10:8080   斗地主 · 3 人
  ────────────────────────────────────────────────────
   1 号位   你
   2 号位   空 · 等待加入
   3 号位   空 · 等待加入
  ────────────────────────────────────────────────────
   等待玩家 1/3    朋友加入: npx dou-cl --join 192.168.1.10:8080

[s] 用电脑补齐先开局   [q] 退出 > _

牌面表示

含义写法
点数 3–93 4 5 6 7 8 9
100(也接受 10 / T)
J / Q / K / AJ Q K A
22
小王 / 大王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 退出。

关掉伪装 npx dou-cl --skin term

不喜欢伪装的话,可以看回原生牌桌(牌面和上面一样,只是排版是牌桌的样子)。

斗地主 npx dou-cl -g doudizhu

 斗地主 · 底分 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 提示 / ? 帮助) > _

跑得快 npx dou-cl -g paodekuai

 跑得快
> [你] 你 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 npx dou-cl -g fivek

 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 提示 / ? 帮助) > _