HelloSun commited on
Commit
7b94ee0
·
verified ·
1 Parent(s): db0ac45

d1 x d1 blackjack battle recap

Browse files
Files changed (6) hide show
  1. README.md +16 -4
  2. battle.json +3415 -0
  3. index.html +672 -17
  4. source/blackjack.py +535 -0
  5. source/build_site.py +753 -0
  6. source/run_demo.sh +42 -0
README.md CHANGED
@@ -1,10 +1,22 @@
1
  ---
2
- title: D1demo
3
- emoji: ⚡
4
  colorFrom: green
5
- colorTo: gray
6
  sdk: static
7
  pinned: false
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: d1demo
3
+ emoji: 🃏
4
  colorFrom: green
5
+ colorTo: yellow
6
  sdk: static
7
  pinned: false
8
+ license: mit
9
  ---
10
 
11
+ # d1 × d1 21 點對戰實錄
12
+
13
+ 兩個 **LiquidAI d1-3B** 決策模型,各自跑在一個 llama.cpp server 上,在同一張 21 點牌桌上對打。每一次下注、補牌、停牌、加倍,都是模型輸出的機率分佈;這頁把每一步整理成可回放的視覺化紀錄。
14
+
15
+ - 模型:`LiquidAI/d1-3B-GGUF` (Q8_0),llama.cpp b11786
16
+ - API:`POST /v1/systemone`(d1 是 decision model,不是文字生成模型)
17
+ - 莊家:規則操作,S17,6 副牌,BJ 3:2
18
+ - 內容:對戰時間軸、決策機率、風險評分、信心度、延遲、資金曲線、動作統計
19
+ - 原始碼:`source/blackjack.py`(對戰 + 記錄)、`source/build_site.py`(靜態網頁編譯)
20
+ - 原始資料:`battle.json`
21
+
22
+ 頁面是靜態的,所有資料都在 `battle.json`;`build_site.py` 可重新編譯整站。
battle.json ADDED
@@ -0,0 +1,3415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "meta": {
3
+ "title": "d1 × d1 21 點對戰實錄",
4
+ "subtitle": "兩個 LiquidAI d1-3B 決策模型,透過 llama.cpp 在同一個牌桌上對打",
5
+ "created_at": "2026-10-21T11:09:42+02:00",
6
+ "seed": 20261021,
7
+ "model": "LiquidAI/d1-3B-GGUF",
8
+ "quant": "Q8_0",
9
+ "llama_build": "b11786 (0.7.0-dev)",
10
+ "rules": {
11
+ "decks": 6,
12
+ "dealer": "莊家軟 17 停牌 (S17)",
13
+ "blackjack_pays": "3:2",
14
+ "double": "僅限硬 9–11 或軟 13–18 時加倍,加倍後只補一張",
15
+ "split": "本局不開放分牌",
16
+ "insurance": "不開放保險",
17
+ "reshuffle": "剩餘牌數低於 25% 時重洗",
18
+ "start_bankroll": 1000
19
+ },
20
+ "start_bankroll": 1000,
21
+ "agents": [
22
+ {
23
+ "id": "a",
24
+ "name": "阿保",
25
+ "style": "保守算牌派",
26
+ "description": "以保住本金為第一優先,只有在牌面明顯有利時才加注,遇到風險寧可停牌。",
27
+ "url": "http://127.0.0.1:8081",
28
+ "final_bankroll": 940
29
+ },
30
+ {
31
+ "id": "b",
32
+ "name": "阿衝",
33
+ "style": "激進壓迫派",
34
+ "description": "相信進攻是最好的防守,喜歡加倍與大注,願意為了期望值承擔波動。",
35
+ "url": "http://127.0.0.1:8082",
36
+ "final_bankroll": 900
37
+ }
38
+ ],
39
+ "rounds_played": 12,
40
+ "duration_sec": 161.39,
41
+ "inference": {
42
+ "a": {
43
+ "calls": 27,
44
+ "avg_latency_ms": 2589
45
+ },
46
+ "b": {
47
+ "calls": 27,
48
+ "avg_latency_ms": 3387
49
+ }
50
+ }
51
+ },
52
+ "rounds": [
53
+ {
54
+ "n": 1,
55
+ "shoe_before": {
56
+ "decks": 6,
57
+ "remaining_cards": 306,
58
+ "penetration_pct": 2,
59
+ "cards_left_by_rank": {
60
+ "A": 23,
61
+ "2": 24,
62
+ "3": 23,
63
+ "4": 24,
64
+ "5": 24,
65
+ "6": 23,
66
+ "7": 23,
67
+ "8": 24,
68
+ "9": 24,
69
+ "10": 24,
70
+ "J": 24,
71
+ "Q": 23,
72
+ "K": 23
73
+ },
74
+ "hi_lo_running_count": -1,
75
+ "true_count": -0.17
76
+ },
77
+ "dealer_up": "A♣",
78
+ "dealer_hole": "K♣",
79
+ "dealer_revealed": true,
80
+ "dealer": {
81
+ "cards": [
82
+ "A♣",
83
+ "K♣"
84
+ ],
85
+ "total": 21,
86
+ "soft": true,
87
+ "blackjack": true,
88
+ "bust": false
89
+ },
90
+ "events": [
91
+ "莊家明牌為 A 或 10 點,進行暗牌確認",
92
+ "莊家直接拿到 Blackjack,本局立即結束"
93
+ ],
94
+ "players": [
95
+ {
96
+ "cards": [
97
+ "7♥",
98
+ "3♣"
99
+ ],
100
+ "bet": 10,
101
+ "doubled": false,
102
+ "blackjack": false,
103
+ "bust": false,
104
+ "total": 10,
105
+ "soft": false,
106
+ "result": "lose",
107
+ "net": -10,
108
+ "bet_decision": {
109
+ "decision": {
110
+ "type": "choice",
111
+ "choice": "min",
112
+ "probabilities": {
113
+ "min": 0.8015038103456409,
114
+ "standard": 0.13503222188459674,
115
+ "max": 0.06346396776976232
116
+ },
117
+ "confidence": 0.7022557155184613
118
+ },
119
+ "bet": 10,
120
+ "confidence": {
121
+ "type": "score",
122
+ "score": 1.2743893137525868,
123
+ "legend": {
124
+ "0": "沒信心",
125
+ "1": "普通",
126
+ "2": "有信心",
127
+ "3": "非常有信心"
128
+ },
129
+ "probabilities": {
130
+ "0": 0.177727990768028,
131
+ "1": 0.48287248232676894,
132
+ "2": 0.22668174928979082,
133
+ "3": 0.11271777761541213
134
+ },
135
+ "confidence": 0.3701547047113569
136
+ },
137
+ "latency_ms": 2619
138
+ },
139
+ "bankroll_after": 990,
140
+ "id": "a",
141
+ "name": "阿保",
142
+ "style": "保守算牌派"
143
+ },
144
+ {
145
+ "cards": [
146
+ "Q♦",
147
+ "6♠"
148
+ ],
149
+ "bet": 50,
150
+ "doubled": false,
151
+ "blackjack": false,
152
+ "bust": false,
153
+ "total": 16,
154
+ "soft": false,
155
+ "result": "lose",
156
+ "net": -50,
157
+ "bet_decision": {
158
+ "decision": {
159
+ "type": "choice",
160
+ "choice": "max",
161
+ "probabilities": {
162
+ "min": 0.22510969265696443,
163
+ "standard": 0.3522666589432454,
164
+ "max": 0.42262364839979016
165
+ },
166
+ "confidence": 0.13393547259968525
167
+ },
168
+ "bet": 50,
169
+ "confidence": {
170
+ "type": "score",
171
+ "score": 1.4739367761682,
172
+ "legend": {
173
+ "0": "沒信心",
174
+ "1": "普通",
175
+ "2": "有信心",
176
+ "3": "非常有信心"
177
+ },
178
+ "probabilities": {
179
+ "0": 0.16212488082911708,
180
+ "1": 0.364637000115999,
181
+ "2": 0.3104145811124513,
182
+ "3": 0.16282353794243282
183
+ },
184
+ "confidence": 0.201813462173566
185
+ },
186
+ "latency_ms": 2899
187
+ },
188
+ "bankroll_after": 950,
189
+ "id": "b",
190
+ "name": "阿衝",
191
+ "style": "激進壓迫派"
192
+ }
193
+ ],
194
+ "shoe_after": {
195
+ "decks": 6,
196
+ "remaining_cards": 306,
197
+ "penetration_pct": 2,
198
+ "cards_left_by_rank": {
199
+ "A": 23,
200
+ "2": 24,
201
+ "3": 23,
202
+ "4": 24,
203
+ "5": 24,
204
+ "6": 23,
205
+ "7": 23,
206
+ "8": 24,
207
+ "9": 24,
208
+ "10": 24,
209
+ "J": 24,
210
+ "Q": 23,
211
+ "K": 23
212
+ },
213
+ "hi_lo_running_count": -1,
214
+ "true_count": -0.17
215
+ }
216
+ },
217
+ {
218
+ "n": 2,
219
+ "shoe_before": {
220
+ "decks": 6,
221
+ "remaining_cards": 300,
222
+ "penetration_pct": 4,
223
+ "cards_left_by_rank": {
224
+ "A": 22,
225
+ "2": 24,
226
+ "3": 22,
227
+ "4": 24,
228
+ "5": 23,
229
+ "6": 23,
230
+ "7": 23,
231
+ "8": 23,
232
+ "9": 24,
233
+ "10": 23,
234
+ "J": 24,
235
+ "Q": 23,
236
+ "K": 22
237
+ },
238
+ "hi_lo_running_count": -2,
239
+ "true_count": -0.35
240
+ },
241
+ "dealer_up": "8♣",
242
+ "dealer_hole": "A♠",
243
+ "dealer_revealed": true,
244
+ "dealer": {
245
+ "cards": [
246
+ "8♣",
247
+ "A♠"
248
+ ],
249
+ "total": 19,
250
+ "soft": true,
251
+ "blackjack": false,
252
+ "bust": false
253
+ },
254
+ "events": [
255
+ "莊家最終 19 點"
256
+ ],
257
+ "players": [
258
+ {
259
+ "cards": [
260
+ "5♠",
261
+ "3♥",
262
+ "9♠"
263
+ ],
264
+ "bet": 10,
265
+ "doubled": false,
266
+ "total": 17,
267
+ "soft": false,
268
+ "bust": false,
269
+ "blackjack": false,
270
+ "steps": [
271
+ {
272
+ "type": "action",
273
+ "cards": [
274
+ "5♠",
275
+ "3♥"
276
+ ],
277
+ "total": 8,
278
+ "soft": false,
279
+ "dealer_up": "8♣",
280
+ "allowed": [
281
+ "hit",
282
+ "stand"
283
+ ],
284
+ "decision": {
285
+ "type": "choice",
286
+ "choice": "hit",
287
+ "probabilities": {
288
+ "hit": 0.7903426468089709,
289
+ "stand": 0.2096573531910292
290
+ },
291
+ "confidence": 0.5806852936179419
292
+ },
293
+ "risk": {
294
+ "type": "score",
295
+ "score": 2.1705379533840112,
296
+ "legend": {
297
+ "0": "非常有利",
298
+ "1": "稍微有利",
299
+ "2": "有點不利",
300
+ "3": "非常不利"
301
+ },
302
+ "probabilities": {
303
+ "0": 0.04566941566120645,
304
+ "1": 0.12003841453217244,
305
+ "2": 0.4523769705680244,
306
+ "3": 0.38191519923859674
307
+ },
308
+ "confidence": 0.406707554906818
309
+ },
310
+ "latency_ms": 3068,
311
+ "drawn": "9♠",
312
+ "total_after": 17
313
+ },
314
+ {
315
+ "type": "action",
316
+ "cards": [
317
+ "5♠",
318
+ "3♥",
319
+ "9♠"
320
+ ],
321
+ "total": 17,
322
+ "soft": false,
323
+ "dealer_up": "8♣",
324
+ "allowed": [
325
+ "hit",
326
+ "stand"
327
+ ],
328
+ "decision": {
329
+ "type": "choice",
330
+ "choice": "stand",
331
+ "probabilities": {
332
+ "hit": 0.34748348200597706,
333
+ "stand": 0.6525165179940229
334
+ },
335
+ "confidence": 0.30503303598804576
336
+ },
337
+ "risk": {
338
+ "type": "score",
339
+ "score": 1.3057660239341171,
340
+ "legend": {
341
+ "0": "非常有利",
342
+ "1": "稍微有利",
343
+ "2": "有點不利",
344
+ "3": "非常不利"
345
+ },
346
+ "probabilities": {
347
+ "0": 0.1680177506175406,
348
+ "1": 0.4059181822464935,
349
+ "2": 0.3783443597202743,
350
+ "3": 0.04771970741569162
351
+ },
352
+ "confidence": 0.3581984748308018
353
+ },
354
+ "latency_ms": 2844,
355
+ "drawn": null,
356
+ "total_after": 17
357
+ }
358
+ ],
359
+ "player_id": "a",
360
+ "result": "lose",
361
+ "net": -10,
362
+ "bet_decision": {
363
+ "decision": {
364
+ "type": "choice",
365
+ "choice": "min",
366
+ "probabilities": {
367
+ "min": 0.7725987262844797,
368
+ "standard": 0.1413424593652034,
369
+ "max": 0.08605881435031675
370
+ },
371
+ "confidence": 0.6588980894267195
372
+ },
373
+ "bet": 10,
374
+ "confidence": {
375
+ "type": "score",
376
+ "score": 1.3006956637914562,
377
+ "legend": {
378
+ "0": "沒信心",
379
+ "1": "普通",
380
+ "2": "有信心",
381
+ "3": "非常有信心"
382
+ },
383
+ "probabilities": {
384
+ "0": 0.16014576713262269,
385
+ "1": 0.4790571948548963,
386
+ "2": 0.2607526451008829,
387
+ "3": 0.100044392911598
388
+ },
389
+ "confidence": 0.37901280194329834
390
+ },
391
+ "latency_ms": 2449
392
+ },
393
+ "bankroll_after": 980,
394
+ "id": "a",
395
+ "name": "阿保",
396
+ "style": "保守算牌派"
397
+ },
398
+ {
399
+ "cards": [
400
+ "K♦",
401
+ "10♣"
402
+ ],
403
+ "bet": 50,
404
+ "doubled": false,
405
+ "total": 20,
406
+ "soft": false,
407
+ "bust": false,
408
+ "blackjack": false,
409
+ "steps": [
410
+ {
411
+ "type": "action",
412
+ "cards": [
413
+ "K♦",
414
+ "10♣"
415
+ ],
416
+ "total": 20,
417
+ "soft": false,
418
+ "dealer_up": "8♣",
419
+ "allowed": [
420
+ "hit",
421
+ "stand"
422
+ ],
423
+ "decision": {
424
+ "type": "choice",
425
+ "choice": "stand",
426
+ "probabilities": {
427
+ "hit": 0.29523031516652504,
428
+ "stand": 0.704769684833475
429
+ },
430
+ "confidence": 0.40953936966695004
431
+ },
432
+ "risk": {
433
+ "type": "score",
434
+ "score": 1.1019380316438792,
435
+ "legend": {
436
+ "0": "非常有利",
437
+ "1": "稍微有利",
438
+ "2": "有點不利",
439
+ "3": "非常不利"
440
+ },
441
+ "probabilities": {
442
+ "0": 0.270273972567917,
443
+ "1": 0.3948283551800062,
444
+ "2": 0.2975833402923578,
445
+ "3": 0.03731433195971913
446
+ },
447
+ "confidence": 0.3575140232202869
448
+ },
449
+ "latency_ms": 4289,
450
+ "drawn": null,
451
+ "total_after": 20
452
+ }
453
+ ],
454
+ "player_id": "b",
455
+ "result": "win",
456
+ "net": 50,
457
+ "bet_decision": {
458
+ "decision": {
459
+ "type": "choice",
460
+ "choice": "max",
461
+ "probabilities": {
462
+ "min": 0.19967317203877727,
463
+ "standard": 0.3811921558998119,
464
+ "max": 0.4191346720614108
465
+ },
466
+ "confidence": 0.1287020080921162
467
+ },
468
+ "bet": 50,
469
+ "confidence": {
470
+ "type": "score",
471
+ "score": 1.5668315252239973,
472
+ "legend": {
473
+ "0": "沒信心",
474
+ "1": "普通",
475
+ "2": "有信心",
476
+ "3": "非常有信心"
477
+ },
478
+ "probabilities": {
479
+ "0": 0.14124839285420554,
480
+ "1": 0.3269367548074964,
481
+ "2": 0.3555497865983932,
482
+ "3": 0.17626506573990477
483
+ },
484
+ "confidence": 0.21430139374418777
485
+ },
486
+ "latency_ms": 2759
487
+ },
488
+ "bankroll_after": 1000,
489
+ "id": "b",
490
+ "name": "阿衝",
491
+ "style": "激進壓迫派"
492
+ }
493
+ ],
494
+ "shoe_after": {
495
+ "decks": 6,
496
+ "remaining_cards": 299,
497
+ "penetration_pct": 4,
498
+ "cards_left_by_rank": {
499
+ "A": 22,
500
+ "2": 24,
501
+ "3": 22,
502
+ "4": 24,
503
+ "5": 23,
504
+ "6": 23,
505
+ "7": 23,
506
+ "8": 23,
507
+ "9": 23,
508
+ "10": 23,
509
+ "J": 24,
510
+ "Q": 23,
511
+ "K": 22
512
+ },
513
+ "hi_lo_running_count": -2,
514
+ "true_count": -0.35
515
+ }
516
+ },
517
+ {
518
+ "n": 3,
519
+ "shoe_before": {
520
+ "decks": 6,
521
+ "remaining_cards": 293,
522
+ "penetration_pct": 6,
523
+ "cards_left_by_rank": {
524
+ "A": 22,
525
+ "2": 22,
526
+ "3": 21,
527
+ "4": 24,
528
+ "5": 23,
529
+ "6": 22,
530
+ "7": 23,
531
+ "8": 23,
532
+ "9": 23,
533
+ "10": 21,
534
+ "J": 24,
535
+ "Q": 23,
536
+ "K": 22
537
+ },
538
+ "hi_lo_running_count": 0,
539
+ "true_count": 0.0
540
+ },
541
+ "dealer_up": "3♦",
542
+ "dealer_hole": "2♠",
543
+ "dealer_revealed": true,
544
+ "dealer": {
545
+ "cards": [
546
+ "3♦",
547
+ "2♠",
548
+ "2♠",
549
+ "3♦",
550
+ "A♣"
551
+ ],
552
+ "total": 21,
553
+ "soft": true,
554
+ "blackjack": false,
555
+ "bust": false
556
+ },
557
+ "events": [
558
+ "莊家補牌 2♠ → 7 點",
559
+ "莊家補牌 3♦ → 10 點",
560
+ "莊家補牌 A♣ → 21 點",
561
+ "莊家最終 21 點"
562
+ ],
563
+ "players": [
564
+ {
565
+ "cards": [
566
+ "10♠",
567
+ "2♦",
568
+ "8♣"
569
+ ],
570
+ "bet": 10,
571
+ "doubled": false,
572
+ "total": 20,
573
+ "soft": false,
574
+ "bust": false,
575
+ "blackjack": false,
576
+ "steps": [
577
+ {
578
+ "type": "action",
579
+ "cards": [
580
+ "10♠",
581
+ "2♦"
582
+ ],
583
+ "total": 12,
584
+ "soft": false,
585
+ "dealer_up": "3♦",
586
+ "allowed": [
587
+ "hit",
588
+ "stand"
589
+ ],
590
+ "decision": {
591
+ "type": "choice",
592
+ "choice": "hit",
593
+ "probabilities": {
594
+ "hit": 0.564536710896258,
595
+ "stand": 0.43546328910374205
596
+ },
597
+ "confidence": 0.12907342179251602
598
+ },
599
+ "risk": {
600
+ "type": "score",
601
+ "score": 1.5462336728175854,
602
+ "legend": {
603
+ "0": "非常有利",
604
+ "1": "稍微有利",
605
+ "2": "有點不利",
606
+ "3": "非常不利"
607
+ },
608
+ "probabilities": {
609
+ "0": 0.11070758630941745,
610
+ "1": 0.3353281508308657,
611
+ "2": 0.45098726659243077,
612
+ "3": 0.10297699626728603
613
+ },
614
+ "confidence": 0.34027968028301336
615
+ },
616
+ "latency_ms": 2619,
617
+ "drawn": "8♣",
618
+ "total_after": 20
619
+ },
620
+ {
621
+ "type": "action",
622
+ "cards": [
623
+ "10♠",
624
+ "2♦",
625
+ "8♣"
626
+ ],
627
+ "total": 20,
628
+ "soft": false,
629
+ "dealer_up": "3♦",
630
+ "allowed": [
631
+ "hit",
632
+ "stand"
633
+ ],
634
+ "decision": {
635
+ "type": "choice",
636
+ "choice": "stand",
637
+ "probabilities": {
638
+ "hit": 0.23511610687855905,
639
+ "stand": 0.764883893121441
640
+ },
641
+ "confidence": 0.529767786242882
642
+ },
643
+ "risk": {
644
+ "type": "score",
645
+ "score": 1.1724233566485125,
646
+ "legend": {
647
+ "0": "非常有利",
648
+ "1": "稍微有利",
649
+ "2": "有點不利",
650
+ "3": "非常不利"
651
+ },
652
+ "probabilities": {
653
+ "0": 0.2259679727325013,
654
+ "1": 0.41366566628608514,
655
+ "2": 0.32234139258181355,
656
+ "3": 0.038024968399600075
657
+ },
658
+ "confidence": 0.375640697886485
659
+ },
660
+ "latency_ms": 3056,
661
+ "drawn": null,
662
+ "total_after": 20
663
+ }
664
+ ],
665
+ "player_id": "a",
666
+ "result": "lose",
667
+ "net": -10,
668
+ "bet_decision": {
669
+ "decision": {
670
+ "type": "choice",
671
+ "choice": "min",
672
+ "probabilities": {
673
+ "min": 0.7433957153910495,
674
+ "standard": 0.16651591247876724,
675
+ "max": 0.09008837213018317
676
+ },
677
+ "confidence": 0.6150935730865743
678
+ },
679
+ "bet": 10,
680
+ "confidence": {
681
+ "type": "score",
682
+ "score": 1.2496433080778582,
683
+ "legend": {
684
+ "0": "沒信心",
685
+ "1": "普通",
686
+ "2": "有信心",
687
+ "3": "非常有信心"
688
+ },
689
+ "probabilities": {
690
+ "0": 0.17187017754479506,
691
+ "1": 0.48519722500579504,
692
+ "2": 0.26435170927616675,
693
+ "3": 0.07858088817324316
694
+ },
695
+ "confidence": 0.40661633683255194
696
+ },
697
+ "latency_ms": 2226
698
+ },
699
+ "bankroll_after": 970,
700
+ "id": "a",
701
+ "name": "阿保",
702
+ "style": "保守算牌派"
703
+ },
704
+ {
705
+ "cards": [
706
+ "6♦",
707
+ "10♠",
708
+ "4♠"
709
+ ],
710
+ "bet": 50,
711
+ "doubled": false,
712
+ "total": 20,
713
+ "soft": false,
714
+ "bust": false,
715
+ "blackjack": false,
716
+ "steps": [
717
+ {
718
+ "type": "action",
719
+ "cards": [
720
+ "6♦",
721
+ "10♠"
722
+ ],
723
+ "total": 16,
724
+ "soft": false,
725
+ "dealer_up": "3♦",
726
+ "allowed": [
727
+ "hit",
728
+ "stand"
729
+ ],
730
+ "decision": {
731
+ "type": "choice",
732
+ "choice": "hit",
733
+ "probabilities": {
734
+ "hit": 0.6418322181399073,
735
+ "stand": 0.3581677818600927
736
+ },
737
+ "confidence": 0.28366443627981464
738
+ },
739
+ "risk": {
740
+ "type": "score",
741
+ "score": 1.4828880574132604,
742
+ "legend": {
743
+ "0": "非常有利",
744
+ "1": "稍微有利",
745
+ "2": "有點不利",
746
+ "3": "非常不利"
747
+ },
748
+ "probabilities": {
749
+ "0": 0.12154439867797127,
750
+ "1": 0.3522754018654166,
751
+ "2": 0.44792794282199283,
752
+ "3": 0.07825225663461932
753
+ },
754
+ "confidence": 0.3263835441440215
755
+ },
756
+ "latency_ms": 3581,
757
+ "drawn": "4♠",
758
+ "total_after": 20
759
+ },
760
+ {
761
+ "type": "action",
762
+ "cards": [
763
+ "6♦",
764
+ "10♠",
765
+ "4♠"
766
+ ],
767
+ "total": 20,
768
+ "soft": false,
769
+ "dealer_up": "3♦",
770
+ "allowed": [
771
+ "hit",
772
+ "stand"
773
+ ],
774
+ "decision": {
775
+ "type": "choice",
776
+ "choice": "stand",
777
+ "probabilities": {
778
+ "hit": 0.3440449520657691,
779
+ "stand": 0.6559550479342309
780
+ },
781
+ "confidence": 0.31191009586846175
782
+ },
783
+ "risk": {
784
+ "type": "score",
785
+ "score": 1.2733321008675031,
786
+ "legend": {
787
+ "0": "非常有利",
788
+ "1": "稍微有利",
789
+ "2": "有點不利",
790
+ "3": "非常不利"
791
+ },
792
+ "probabilities": {
793
+ "0": 0.19239615322374934,
794
+ "1": 0.3916707506879337,
795
+ "2": 0.36613793808538125,
796
+ "3": 0.049795158002935644
797
+ },
798
+ "confidence": 0.3418755926849981
799
+ },
800
+ "latency_ms": 3581,
801
+ "drawn": null,
802
+ "total_after": 20
803
+ }
804
+ ],
805
+ "player_id": "b",
806
+ "result": "lose",
807
+ "net": -50,
808
+ "bet_decision": {
809
+ "decision": {
810
+ "type": "choice",
811
+ "choice": "max",
812
+ "probabilities": {
813
+ "min": 0.17446344549537493,
814
+ "standard": 0.3824838673364471,
815
+ "max": 0.44305268716817786
816
+ },
817
+ "confidence": 0.1645790307522668
818
+ },
819
+ "bet": 50,
820
+ "confidence": {
821
+ "type": "score",
822
+ "score": 1.6022477730280071,
823
+ "legend": {
824
+ "0": "沒信心",
825
+ "1": "普通",
826
+ "2": "有信心",
827
+ "3": "非常有信心"
828
+ },
829
+ "probabilities": {
830
+ "0": 0.12490168872094283,
831
+ "1": 0.32284211542202057,
832
+ "2": 0.3773629299651229,
833
+ "3": 0.17489326589191365
834
+ },
835
+ "confidence": 0.2524612412441801
836
+ },
837
+ "latency_ms": 2818
838
+ },
839
+ "bankroll_after": 950,
840
+ "id": "b",
841
+ "name": "阿衝",
842
+ "style": "激進壓迫派"
843
+ }
844
+ ],
845
+ "shoe_after": {
846
+ "decks": 6,
847
+ "remaining_cards": 288,
848
+ "penetration_pct": 8,
849
+ "cards_left_by_rank": {
850
+ "A": 21,
851
+ "2": 21,
852
+ "3": 20,
853
+ "4": 23,
854
+ "5": 23,
855
+ "6": 22,
856
+ "7": 23,
857
+ "8": 22,
858
+ "9": 23,
859
+ "10": 21,
860
+ "J": 24,
861
+ "Q": 23,
862
+ "K": 22
863
+ },
864
+ "hi_lo_running_count": 2,
865
+ "true_count": 0.36
866
+ }
867
+ },
868
+ {
869
+ "n": 4,
870
+ "shoe_before": {
871
+ "decks": 6,
872
+ "remaining_cards": 282,
873
+ "penetration_pct": 10,
874
+ "cards_left_by_rank": {
875
+ "A": 20,
876
+ "2": 21,
877
+ "3": 19,
878
+ "4": 23,
879
+ "5": 23,
880
+ "6": 22,
881
+ "7": 22,
882
+ "8": 21,
883
+ "9": 22,
884
+ "10": 21,
885
+ "J": 24,
886
+ "Q": 23,
887
+ "K": 21
888
+ },
889
+ "hi_lo_running_count": 1,
890
+ "true_count": 0.18
891
+ },
892
+ "dealer_up": "7♠",
893
+ "dealer_hole": "K♥",
894
+ "dealer_revealed": true,
895
+ "dealer": {
896
+ "cards": [
897
+ "7♠",
898
+ "K♥"
899
+ ],
900
+ "total": 17,
901
+ "soft": false,
902
+ "blackjack": false,
903
+ "bust": false
904
+ },
905
+ "events": [
906
+ "莊家最終 17 點"
907
+ ],
908
+ "players": [
909
+ {
910
+ "cards": [
911
+ "8♥",
912
+ "A♦"
913
+ ],
914
+ "bet": 10,
915
+ "doubled": false,
916
+ "total": 19,
917
+ "soft": true,
918
+ "bust": false,
919
+ "blackjack": false,
920
+ "steps": [
921
+ {
922
+ "type": "action",
923
+ "cards": [
924
+ "8♥",
925
+ "A♦"
926
+ ],
927
+ "total": 19,
928
+ "soft": true,
929
+ "dealer_up": "7♠",
930
+ "allowed": [
931
+ "hit",
932
+ "stand"
933
+ ],
934
+ "decision": {
935
+ "type": "choice",
936
+ "choice": "stand",
937
+ "probabilities": {
938
+ "hit": 0.2797133858793715,
939
+ "stand": 0.7202866141206284
940
+ },
941
+ "confidence": 0.4405732282412569
942
+ },
943
+ "risk": {
944
+ "type": "score",
945
+ "score": 1.0755577590046503,
946
+ "legend": {
947
+ "0": "非常有利",
948
+ "1": "稍微有利",
949
+ "2": "有點不利",
950
+ "3": "非常不利"
951
+ },
952
+ "probabilities": {
953
+ "0": 0.2653823049012448,
954
+ "1": 0.42404250943999217,
955
+ "2": 0.2802103074116306,
956
+ "3": 0.030364878247132344
957
+ },
958
+ "confidence": 0.3936776311928599
959
+ },
960
+ "latency_ms": 3126,
961
+ "drawn": null,
962
+ "total_after": 19
963
+ }
964
+ ],
965
+ "player_id": "a",
966
+ "result": "win",
967
+ "net": 10,
968
+ "bet_decision": {
969
+ "decision": {
970
+ "type": "choice",
971
+ "choice": "min",
972
+ "probabilities": {
973
+ "min": 0.7249067710505043,
974
+ "standard": 0.17984278558883143,
975
+ "max": 0.09525044336066422
976
+ },
977
+ "confidence": 0.5873601565757564
978
+ },
979
+ "bet": 10,
980
+ "confidence": {
981
+ "type": "score",
982
+ "score": 1.1590048973716087,
983
+ "legend": {
984
+ "0": "沒信心",
985
+ "1": "普通",
986
+ "2": "有信心",
987
+ "3": "非常有信心"
988
+ },
989
+ "probabilities": {
990
+ "0": 0.21064627872594902,
991
+ "1": 0.4865225308156261,
992
+ "2": 0.23601120481929241,
993
+ "3": 0.06681998563913258
994
+ },
995
+ "confidence": 0.4197025451764934
996
+ },
997
+ "latency_ms": 2265
998
+ },
999
+ "bankroll_after": 980,
1000
+ "id": "a",
1001
+ "name": "阿保",
1002
+ "style": "保守算牌派"
1003
+ },
1004
+ {
1005
+ "cards": [
1006
+ "9♠",
1007
+ "3♦",
1008
+ "K♥"
1009
+ ],
1010
+ "bet": 50,
1011
+ "doubled": false,
1012
+ "total": 22,
1013
+ "soft": false,
1014
+ "bust": true,
1015
+ "blackjack": false,
1016
+ "steps": [
1017
+ {
1018
+ "type": "action",
1019
+ "cards": [
1020
+ "9♠",
1021
+ "3♦"
1022
+ ],
1023
+ "total": 12,
1024
+ "soft": false,
1025
+ "dealer_up": "7♠",
1026
+ "allowed": [
1027
+ "hit",
1028
+ "stand"
1029
+ ],
1030
+ "decision": {
1031
+ "type": "choice",
1032
+ "choice": "hit",
1033
+ "probabilities": {
1034
+ "hit": 0.7049549850357781,
1035
+ "stand": 0.295045014964222
1036
+ },
1037
+ "confidence": 0.4099099700715563
1038
+ },
1039
+ "risk": {
1040
+ "type": "score",
1041
+ "score": 1.5146466061989001,
1042
+ "legend": {
1043
+ "0": "非常有利",
1044
+ "1": "稍微有利",
1045
+ "2": "有點不利",
1046
+ "3": "非常不利"
1047
+ },
1048
+ "probabilities": {
1049
+ "0": 0.12572631444400376,
1050
+ "1": 0.33533827834479635,
1051
+ "2": 0.43749789377949644,
1052
+ "3": 0.10143751343170365
1053
+ },
1054
+ "confidence": 0.3117715793354925
1055
+ },
1056
+ "latency_ms": 3834,
1057
+ "drawn": "K♥",
1058
+ "total_after": 22
1059
+ }
1060
+ ],
1061
+ "player_id": "b",
1062
+ "result": "lose",
1063
+ "net": -50,
1064
+ "bet_decision": {
1065
+ "decision": {
1066
+ "type": "choice",
1067
+ "choice": "max",
1068
+ "probabilities": {
1069
+ "min": 0.19672050531851887,
1070
+ "standard": 0.3845980993465475,
1071
+ "max": 0.41868139533493354
1072
+ },
1073
+ "confidence": 0.1280220930024003
1074
+ },
1075
+ "bet": 50,
1076
+ "confidence": {
1077
+ "type": "score",
1078
+ "score": 1.5623486708262693,
1079
+ "legend": {
1080
+ "0": "沒信心",
1081
+ "1": "普通",
1082
+ "2": "有信心",
1083
+ "3": "非常有信心"
1084
+ },
1085
+ "probabilities": {
1086
+ "0": 0.13876933530621044,
1087
+ "1": 0.3289561938301259,
1088
+ "2": 0.36343093559484774,
1089
+ "3": 0.1688435352688159
1090
+ },
1091
+ "confidence": 0.22466160028863735
1092
+ },
1093
+ "latency_ms": 3123
1094
+ },
1095
+ "bankroll_after": 900,
1096
+ "id": "b",
1097
+ "name": "阿衝",
1098
+ "style": "激進壓迫派"
1099
+ }
1100
+ ],
1101
+ "shoe_after": {
1102
+ "decks": 6,
1103
+ "remaining_cards": 281,
1104
+ "penetration_pct": 10,
1105
+ "cards_left_by_rank": {
1106
+ "A": 20,
1107
+ "2": 21,
1108
+ "3": 19,
1109
+ "4": 23,
1110
+ "5": 23,
1111
+ "6": 22,
1112
+ "7": 22,
1113
+ "8": 21,
1114
+ "9": 22,
1115
+ "10": 21,
1116
+ "J": 24,
1117
+ "Q": 23,
1118
+ "K": 20
1119
+ },
1120
+ "hi_lo_running_count": 0,
1121
+ "true_count": 0.0
1122
+ }
1123
+ },
1124
+ {
1125
+ "n": 5,
1126
+ "shoe_before": {
1127
+ "decks": 6,
1128
+ "remaining_cards": 275,
1129
+ "penetration_pct": 12,
1130
+ "cards_left_by_rank": {
1131
+ "A": 19,
1132
+ "2": 20,
1133
+ "3": 19,
1134
+ "4": 23,
1135
+ "5": 23,
1136
+ "6": 21,
1137
+ "7": 21,
1138
+ "8": 21,
1139
+ "9": 22,
1140
+ "10": 20,
1141
+ "J": 23,
1142
+ "Q": 23,
1143
+ "K": 20
1144
+ },
1145
+ "hi_lo_running_count": -1,
1146
+ "true_count": -0.19
1147
+ },
1148
+ "dealer_up": "A♣",
1149
+ "dealer_hole": "J♠",
1150
+ "dealer_revealed": true,
1151
+ "dealer": {
1152
+ "cards": [
1153
+ "A♣",
1154
+ "J♠"
1155
+ ],
1156
+ "total": 21,
1157
+ "soft": true,
1158
+ "blackjack": true,
1159
+ "bust": false
1160
+ },
1161
+ "events": [
1162
+ "莊家明牌為 A 或 10 點,進行暗牌確認",
1163
+ "莊家直接拿到 Blackjack,本局立即結束"
1164
+ ],
1165
+ "players": [
1166
+ {
1167
+ "cards": [
1168
+ "10♦",
1169
+ "2♦"
1170
+ ],
1171
+ "bet": 10,
1172
+ "doubled": false,
1173
+ "blackjack": false,
1174
+ "bust": false,
1175
+ "total": 12,
1176
+ "soft": false,
1177
+ "result": "lose",
1178
+ "net": -10,
1179
+ "bet_decision": {
1180
+ "decision": {
1181
+ "type": "choice",
1182
+ "choice": "min",
1183
+ "probabilities": {
1184
+ "min": 0.7562291100284627,
1185
+ "standard": 0.16685391773152444,
1186
+ "max": 0.07691697224001287
1187
+ },
1188
+ "confidence": 0.634343665042694
1189
+ },
1190
+ "bet": 10,
1191
+ "confidence": {
1192
+ "type": "score",
1193
+ "score": 1.2320991965875188,
1194
+ "legend": {
1195
+ "0": "沒信心",
1196
+ "1": "普通",
1197
+ "2": "有信心",
1198
+ "3": "非常有信心"
1199
+ },
1200
+ "probabilities": {
1201
+ "0": 0.19676299225478636,
1202
+ "1": 0.46805537175271755,
1203
+ "2": 0.24150108314268687,
1204
+ "3": 0.09368055284980918
1205
+ },
1206
+ "confidence": 0.37437481890290836
1207
+ },
1208
+ "latency_ms": 2277
1209
+ },
1210
+ "bankroll_after": 970,
1211
+ "id": "a",
1212
+ "name": "阿保",
1213
+ "style": "保守算牌派"
1214
+ },
1215
+ {
1216
+ "cards": [
1217
+ "7♠",
1218
+ "6♦"
1219
+ ],
1220
+ "bet": 50,
1221
+ "doubled": false,
1222
+ "blackjack": false,
1223
+ "bust": false,
1224
+ "total": 13,
1225
+ "soft": false,
1226
+ "result": "lose",
1227
+ "net": -50,
1228
+ "bet_decision": {
1229
+ "decision": {
1230
+ "type": "choice",
1231
+ "choice": "max",
1232
+ "probabilities": {
1233
+ "min": 0.20601333171277045,
1234
+ "standard": 0.34842502176841295,
1235
+ "max": 0.4455616465188165
1236
+ },
1237
+ "confidence": 0.16834246977822478
1238
+ },
1239
+ "bet": 50,
1240
+ "confidence": {
1241
+ "type": "score",
1242
+ "score": 1.564656149705211,
1243
+ "legend": {
1244
+ "0": "沒信心",
1245
+ "1": "普通",
1246
+ "2": "有信心",
1247
+ "3": "非常有信心"
1248
+ },
1249
+ "probabilities": {
1250
+ "0": 0.14718873328060164,
1251
+ "1": 0.32229188630260436,
1252
+ "2": 0.34919387784777556,
1253
+ "3": 0.1813255025690185
1254
+ },
1255
+ "confidence": 0.2020051445671739
1256
+ },
1257
+ "latency_ms": 3202
1258
+ },
1259
+ "bankroll_after": 850,
1260
+ "id": "b",
1261
+ "name": "阿衝",
1262
+ "style": "激進壓迫派"
1263
+ }
1264
+ ],
1265
+ "shoe_after": {
1266
+ "decks": 6,
1267
+ "remaining_cards": 275,
1268
+ "penetration_pct": 12,
1269
+ "cards_left_by_rank": {
1270
+ "A": 19,
1271
+ "2": 20,
1272
+ "3": 19,
1273
+ "4": 23,
1274
+ "5": 23,
1275
+ "6": 21,
1276
+ "7": 21,
1277
+ "8": 21,
1278
+ "9": 22,
1279
+ "10": 20,
1280
+ "J": 23,
1281
+ "Q": 23,
1282
+ "K": 20
1283
+ },
1284
+ "hi_lo_running_count": -1,
1285
+ "true_count": -0.19
1286
+ }
1287
+ },
1288
+ {
1289
+ "n": 6,
1290
+ "shoe_before": {
1291
+ "decks": 6,
1292
+ "remaining_cards": 269,
1293
+ "penetration_pct": 14,
1294
+ "cards_left_by_rank": {
1295
+ "A": 17,
1296
+ "2": 20,
1297
+ "3": 18,
1298
+ "4": 23,
1299
+ "5": 22,
1300
+ "6": 21,
1301
+ "7": 21,
1302
+ "8": 21,
1303
+ "9": 21,
1304
+ "10": 20,
1305
+ "J": 23,
1306
+ "Q": 23,
1307
+ "K": 19
1308
+ },
1309
+ "hi_lo_running_count": -2,
1310
+ "true_count": -0.39
1311
+ },
1312
+ "dealer_up": "9♦",
1313
+ "dealer_hole": "5♣",
1314
+ "dealer_revealed": true,
1315
+ "dealer": {
1316
+ "cards": [
1317
+ "9♦",
1318
+ "5♣",
1319
+ "8♣"
1320
+ ],
1321
+ "total": 22,
1322
+ "soft": false,
1323
+ "blackjack": false,
1324
+ "bust": true
1325
+ },
1326
+ "events": [
1327
+ "莊家補牌 8♣ → 22 點",
1328
+ "莊家最終 22 點,爆牌"
1329
+ ],
1330
+ "players": [
1331
+ {
1332
+ "cards": [
1333
+ "3♠",
1334
+ "A♦",
1335
+ "K♣",
1336
+ "7♠"
1337
+ ],
1338
+ "bet": 10,
1339
+ "doubled": false,
1340
+ "total": 21,
1341
+ "soft": false,
1342
+ "bust": false,
1343
+ "blackjack": false,
1344
+ "steps": [
1345
+ {
1346
+ "type": "action",
1347
+ "cards": [
1348
+ "3♠",
1349
+ "A♦"
1350
+ ],
1351
+ "total": 14,
1352
+ "soft": true,
1353
+ "dealer_up": "9♦",
1354
+ "allowed": [
1355
+ "hit",
1356
+ "stand",
1357
+ "double"
1358
+ ],
1359
+ "decision": {
1360
+ "type": "choice",
1361
+ "choice": "hit",
1362
+ "probabilities": {
1363
+ "hit": 0.408721663227952,
1364
+ "stand": 0.29303182266998506,
1365
+ "double": 0.29824651410206277
1366
+ },
1367
+ "confidence": 0.11308249484192802
1368
+ },
1369
+ "risk": {
1370
+ "type": "score",
1371
+ "score": 1.5610409422586708,
1372
+ "legend": {
1373
+ "0": "非常有利",
1374
+ "1": "稍微有利",
1375
+ "2": "有點不利",
1376
+ "3": "非常不利"
1377
+ },
1378
+ "probabilities": {
1379
+ "0": 0.10553867166975685,
1380
+ "1": 0.32969436958342446,
1381
+ "2": 0.46295430356520967,
1382
+ "3": 0.10181265518160906
1383
+ },
1384
+ "confidence": 0.3574156318954528
1385
+ },
1386
+ "latency_ms": 3156,
1387
+ "drawn": "K♣",
1388
+ "total_after": 14
1389
+ },
1390
+ {
1391
+ "type": "action",
1392
+ "cards": [
1393
+ "3♠",
1394
+ "A♦",
1395
+ "K♣"
1396
+ ],
1397
+ "total": 14,
1398
+ "soft": false,
1399
+ "dealer_up": "9♦",
1400
+ "allowed": [
1401
+ "hit",
1402
+ "stand"
1403
+ ],
1404
+ "decision": {
1405
+ "type": "choice",
1406
+ "choice": "hit",
1407
+ "probabilities": {
1408
+ "hit": 0.5122837140095126,
1409
+ "stand": 0.48771628599048733
1410
+ },
1411
+ "confidence": 0.024567428019025117
1412
+ },
1413
+ "risk": {
1414
+ "type": "score",
1415
+ "score": 1.5881935559142273,
1416
+ "legend": {
1417
+ "0": "非常有利",
1418
+ "1": "稍微有利",
1419
+ "2": "有點不利",
1420
+ "3": "非常不利"
1421
+ },
1422
+ "probabilities": {
1423
+ "0": 0.1232644149177596,
1424
+ "1": 0.3080900808526087,
1425
+ "2": 0.4258330376272768,
1426
+ "3": 0.14281246660235497
1427
+ },
1428
+ "confidence": 0.30256862270951723
1429
+ },
1430
+ "latency_ms": 2963,
1431
+ "drawn": "7♠",
1432
+ "total_after": 21
1433
+ }
1434
+ ],
1435
+ "player_id": "a",
1436
+ "result": "win",
1437
+ "net": 10,
1438
+ "bet_decision": {
1439
+ "decision": {
1440
+ "type": "choice",
1441
+ "choice": "min",
1442
+ "probabilities": {
1443
+ "min": 0.7443943535681774,
1444
+ "standard": 0.1724434598311167,
1445
+ "max": 0.08316218660070591
1446
+ },
1447
+ "confidence": 0.6165915303522661
1448
+ },
1449
+ "bet": 10,
1450
+ "confidence": {
1451
+ "type": "score",
1452
+ "score": 1.2134591868984879,
1453
+ "legend": {
1454
+ "0": "沒信心",
1455
+ "1": "普通",
1456
+ "2": "有信心",
1457
+ "3": "非常有信心"
1458
+ },
1459
+ "probabilities": {
1460
+ "0": 0.1889940408450792,
1461
+ "1": 0.48665170909181565,
1462
+ "2": 0.24625527238264336,
1463
+ "3": 0.07809897768046187
1464
+ },
1465
+ "confidence": 0.40855273141135373
1466
+ },
1467
+ "latency_ms": 2442
1468
+ },
1469
+ "bankroll_after": 980,
1470
+ "id": "a",
1471
+ "name": "阿保",
1472
+ "style": "保守算牌派"
1473
+ },
1474
+ {
1475
+ "cards": [
1476
+ "A♠",
1477
+ "K♣"
1478
+ ],
1479
+ "bet": 50,
1480
+ "doubled": false,
1481
+ "total": 21,
1482
+ "soft": true,
1483
+ "bust": false,
1484
+ "blackjack": true,
1485
+ "steps": [],
1486
+ "player_id": "b",
1487
+ "result": "blackjack",
1488
+ "net": 75,
1489
+ "bet_decision": {
1490
+ "decision": {
1491
+ "type": "choice",
1492
+ "choice": "max",
1493
+ "probabilities": {
1494
+ "min": 0.1771453995341398,
1495
+ "standard": 0.3923319399455236,
1496
+ "max": 0.43052266052033666
1497
+ },
1498
+ "confidence": 0.145783990780505
1499
+ },
1500
+ "bet": 50,
1501
+ "confidence": {
1502
+ "type": "score",
1503
+ "score": 1.5896896540311678,
1504
+ "legend": {
1505
+ "0": "沒信心",
1506
+ "1": "普通",
1507
+ "2": "有信心",
1508
+ "3": "非常有信心"
1509
+ },
1510
+ "probabilities": {
1511
+ "0": 0.13640040426554614,
1512
+ "1": 0.3189928849419576,
1513
+ "2": 0.36312336328827843,
1514
+ "3": 0.18148334750421774
1515
+ },
1516
+ "confidence": 0.22672295902273243
1517
+ },
1518
+ "latency_ms": 3056
1519
+ },
1520
+ "bankroll_after": 925,
1521
+ "id": "b",
1522
+ "name": "阿衝",
1523
+ "style": "激進壓迫派"
1524
+ }
1525
+ ],
1526
+ "shoe_after": {
1527
+ "decks": 6,
1528
+ "remaining_cards": 266,
1529
+ "penetration_pct": 15,
1530
+ "cards_left_by_rank": {
1531
+ "A": 17,
1532
+ "2": 20,
1533
+ "3": 18,
1534
+ "4": 23,
1535
+ "5": 22,
1536
+ "6": 21,
1537
+ "7": 20,
1538
+ "8": 20,
1539
+ "9": 21,
1540
+ "10": 20,
1541
+ "J": 23,
1542
+ "Q": 23,
1543
+ "K": 18
1544
+ },
1545
+ "hi_lo_running_count": -3,
1546
+ "true_count": -0.59
1547
+ }
1548
+ },
1549
+ {
1550
+ "n": 7,
1551
+ "shoe_before": {
1552
+ "decks": 6,
1553
+ "remaining_cards": 260,
1554
+ "penetration_pct": 17,
1555
+ "cards_left_by_rank": {
1556
+ "A": 16,
1557
+ "2": 19,
1558
+ "3": 17,
1559
+ "4": 23,
1560
+ "5": 22,
1561
+ "6": 21,
1562
+ "7": 20,
1563
+ "8": 19,
1564
+ "9": 21,
1565
+ "10": 18,
1566
+ "J": 23,
1567
+ "Q": 23,
1568
+ "K": 18
1569
+ },
1570
+ "hi_lo_running_count": -4,
1571
+ "true_count": -0.8
1572
+ },
1573
+ "dealer_up": "8♠",
1574
+ "dealer_hole": "3♦",
1575
+ "dealer_revealed": true,
1576
+ "dealer": {
1577
+ "cards": [
1578
+ "8♠",
1579
+ "3♦",
1580
+ "7♦"
1581
+ ],
1582
+ "total": 18,
1583
+ "soft": false,
1584
+ "blackjack": false,
1585
+ "bust": false
1586
+ },
1587
+ "events": [
1588
+ "莊家補牌 7♦ → 18 點",
1589
+ "莊家最終 18 點"
1590
+ ],
1591
+ "players": [
1592
+ {
1593
+ "cards": [
1594
+ "10♣",
1595
+ "2♣",
1596
+ "K♠"
1597
+ ],
1598
+ "bet": 10,
1599
+ "doubled": false,
1600
+ "total": 22,
1601
+ "soft": false,
1602
+ "bust": true,
1603
+ "blackjack": false,
1604
+ "steps": [
1605
+ {
1606
+ "type": "action",
1607
+ "cards": [
1608
+ "10♣",
1609
+ "2♣"
1610
+ ],
1611
+ "total": 12,
1612
+ "soft": false,
1613
+ "dealer_up": "8♠",
1614
+ "allowed": [
1615
+ "hit",
1616
+ "stand"
1617
+ ],
1618
+ "decision": {
1619
+ "type": "choice",
1620
+ "choice": "hit",
1621
+ "probabilities": {
1622
+ "hit": 0.5500152300334371,
1623
+ "stand": 0.44998476996656295
1624
+ },
1625
+ "confidence": 0.10003046006687422
1626
+ },
1627
+ "risk": {
1628
+ "type": "score",
1629
+ "score": 1.5209343777308968,
1630
+ "legend": {
1631
+ "0": "非常有利",
1632
+ "1": "稍微有利",
1633
+ "2": "有點不利",
1634
+ "3": "非常不利"
1635
+ },
1636
+ "probabilities": {
1637
+ "0": 0.1166634305190376,
1638
+ "1": 0.3360687076482218,
1639
+ "2": 0.4569379154155469,
1640
+ "3": 0.0903299464171938
1641
+ },
1642
+ "confidence": 0.34027448489650913
1643
+ },
1644
+ "latency_ms": 3071,
1645
+ "drawn": "K♠",
1646
+ "total_after": 22
1647
+ }
1648
+ ],
1649
+ "player_id": "a",
1650
+ "result": "lose",
1651
+ "net": -10,
1652
+ "bet_decision": {
1653
+ "decision": {
1654
+ "type": "choice",
1655
+ "choice": "min",
1656
+ "probabilities": {
1657
+ "min": 0.69219418752702,
1658
+ "standard": 0.21671663493691912,
1659
+ "max": 0.09108917753606088
1660
+ },
1661
+ "confidence": 0.5382912812905299
1662
+ },
1663
+ "bet": 10,
1664
+ "confidence": {
1665
+ "type": "score",
1666
+ "score": 1.2634052808341587,
1667
+ "legend": {
1668
+ "0": "沒信心",
1669
+ "1": "普通",
1670
+ "2": "有信心",
1671
+ "3": "非常有信心"
1672
+ },
1673
+ "probabilities": {
1674
+ "0": 0.17743488347704425,
1675
+ "1": 0.46766459898284457,
1676
+ "2": 0.2689608707690193,
1677
+ "3": 0.08593964677109182
1678
+ },
1679
+ "confidence": 0.38172495221175273
1680
+ },
1681
+ "latency_ms": 2427
1682
+ },
1683
+ "bankroll_after": 970,
1684
+ "id": "a",
1685
+ "name": "阿保",
1686
+ "style": "保守算牌派"
1687
+ },
1688
+ {
1689
+ "cards": [
1690
+ "10♠",
1691
+ "A♠"
1692
+ ],
1693
+ "bet": 50,
1694
+ "doubled": false,
1695
+ "total": 21,
1696
+ "soft": true,
1697
+ "bust": false,
1698
+ "blackjack": true,
1699
+ "steps": [],
1700
+ "player_id": "b",
1701
+ "result": "blackjack",
1702
+ "net": 75,
1703
+ "bet_decision": {
1704
+ "decision": {
1705
+ "type": "choice",
1706
+ "choice": "max",
1707
+ "probabilities": {
1708
+ "min": 0.17415716770498335,
1709
+ "standard": 0.3849795123905866,
1710
+ "max": 0.44086331990443
1711
+ },
1712
+ "confidence": 0.161294979856645
1713
+ },
1714
+ "bet": 50,
1715
+ "confidence": {
1716
+ "type": "score",
1717
+ "score": 1.6670431235423304,
1718
+ "legend": {
1719
+ "0": "沒信心",
1720
+ "1": "普通",
1721
+ "2": "有信心",
1722
+ "3": "非常有信心"
1723
+ },
1724
+ "probabilities": {
1725
+ "0": 0.12673888933460348,
1726
+ "1": 0.2859844451294625,
1727
+ "2": 0.38077131819493437,
1728
+ "3": 0.20650534734099973
1729
+ },
1730
+ "confidence": 0.25403242886033084
1731
+ },
1732
+ "latency_ms": 2975
1733
+ },
1734
+ "bankroll_after": 1000,
1735
+ "id": "b",
1736
+ "name": "阿衝",
1737
+ "style": "激進壓迫派"
1738
+ }
1739
+ ],
1740
+ "shoe_after": {
1741
+ "decks": 6,
1742
+ "remaining_cards": 258,
1743
+ "penetration_pct": 17,
1744
+ "cards_left_by_rank": {
1745
+ "A": 16,
1746
+ "2": 19,
1747
+ "3": 17,
1748
+ "4": 23,
1749
+ "5": 22,
1750
+ "6": 21,
1751
+ "7": 19,
1752
+ "8": 19,
1753
+ "9": 21,
1754
+ "10": 18,
1755
+ "J": 23,
1756
+ "Q": 23,
1757
+ "K": 17
1758
+ },
1759
+ "hi_lo_running_count": -5,
1760
+ "true_count": -1.01
1761
+ }
1762
+ },
1763
+ {
1764
+ "n": 8,
1765
+ "shoe_before": {
1766
+ "decks": 6,
1767
+ "remaining_cards": 252,
1768
+ "penetration_pct": 19,
1769
+ "cards_left_by_rank": {
1770
+ "A": 16,
1771
+ "2": 19,
1772
+ "3": 16,
1773
+ "4": 23,
1774
+ "5": 21,
1775
+ "6": 21,
1776
+ "7": 19,
1777
+ "8": 19,
1778
+ "9": 19,
1779
+ "10": 17,
1780
+ "J": 23,
1781
+ "Q": 22,
1782
+ "K": 17
1783
+ },
1784
+ "hi_lo_running_count": -5,
1785
+ "true_count": -1.03
1786
+ },
1787
+ "dealer_up": "10♦",
1788
+ "dealer_hole": "9♦",
1789
+ "dealer_revealed": true,
1790
+ "dealer": {
1791
+ "cards": [
1792
+ "10♦",
1793
+ "9♦"
1794
+ ],
1795
+ "total": 19,
1796
+ "soft": false,
1797
+ "blackjack": false,
1798
+ "bust": false
1799
+ },
1800
+ "events": [
1801
+ "莊家明牌為 A 或 10 點,進行暗牌確認",
1802
+ "確認結果:莊家沒有 Blackjack",
1803
+ "莊家最終 19 點"
1804
+ ],
1805
+ "players": [
1806
+ {
1807
+ "cards": [
1808
+ "3♥",
1809
+ "Q♦"
1810
+ ],
1811
+ "bet": 10,
1812
+ "doubled": false,
1813
+ "total": 13,
1814
+ "soft": false,
1815
+ "bust": false,
1816
+ "blackjack": false,
1817
+ "steps": [
1818
+ {
1819
+ "type": "action",
1820
+ "cards": [
1821
+ "3♥",
1822
+ "Q♦"
1823
+ ],
1824
+ "total": 13,
1825
+ "soft": false,
1826
+ "dealer_up": "10♦",
1827
+ "allowed": [
1828
+ "hit",
1829
+ "stand"
1830
+ ],
1831
+ "decision": {
1832
+ "type": "choice",
1833
+ "choice": "stand",
1834
+ "probabilities": {
1835
+ "hit": 0.39634239573570534,
1836
+ "stand": 0.6036576042642946
1837
+ },
1838
+ "confidence": 0.2073152085285892
1839
+ },
1840
+ "risk": {
1841
+ "type": "score",
1842
+ "score": 1.3805666512347543,
1843
+ "legend": {
1844
+ "0": "非常有利",
1845
+ "1": "稍微有利",
1846
+ "2": "有點不利",
1847
+ "3": "非常不利"
1848
+ },
1849
+ "probabilities": {
1850
+ "0": 0.16969624145522536,
1851
+ "1": 0.3621346556920123,
1852
+ "2": 0.3860753130155444,
1853
+ "3": 0.08209378983721773
1854
+ },
1855
+ "confidence": 0.2163790715603192
1856
+ },
1857
+ "latency_ms": 2496,
1858
+ "drawn": null,
1859
+ "total_after": 13
1860
+ }
1861
+ ],
1862
+ "player_id": "a",
1863
+ "result": "lose",
1864
+ "net": -10,
1865
+ "bet_decision": {
1866
+ "decision": {
1867
+ "type": "choice",
1868
+ "choice": "min",
1869
+ "probabilities": {
1870
+ "min": 0.7656214237065283,
1871
+ "standard": 0.14200820159033867,
1872
+ "max": 0.09237037470313315
1873
+ },
1874
+ "confidence": 0.6484321355597924
1875
+ },
1876
+ "bet": 10,
1877
+ "confidence": {
1878
+ "type": "score",
1879
+ "score": 1.2299018689518944,
1880
+ "legend": {
1881
+ "0": "沒信心",
1882
+ "1": "普通",
1883
+ "2": "有信心",
1884
+ "3": "非常有信心"
1885
+ },
1886
+ "probabilities": {
1887
+ "0": 0.19870975918080533,
1888
+ "1": 0.4610254792488059,
1889
+ "2": 0.2519178950080773,
1890
+ "3": 0.08834686656231128
1891
+ },
1892
+ "confidence": 0.3726786126864948
1893
+ },
1894
+ "latency_ms": 2564
1895
+ },
1896
+ "bankroll_after": 960,
1897
+ "id": "a",
1898
+ "name": "阿保",
1899
+ "style": "保守算牌派"
1900
+ },
1901
+ {
1902
+ "cards": [
1903
+ "5♦",
1904
+ "9♣",
1905
+ "3♠"
1906
+ ],
1907
+ "bet": 50,
1908
+ "doubled": false,
1909
+ "total": 17,
1910
+ "soft": false,
1911
+ "bust": false,
1912
+ "blackjack": false,
1913
+ "steps": [
1914
+ {
1915
+ "type": "action",
1916
+ "cards": [
1917
+ "5♦",
1918
+ "9♣"
1919
+ ],
1920
+ "total": 14,
1921
+ "soft": false,
1922
+ "dealer_up": "10♦",
1923
+ "allowed": [
1924
+ "hit",
1925
+ "stand"
1926
+ ],
1927
+ "decision": {
1928
+ "type": "choice",
1929
+ "choice": "hit",
1930
+ "probabilities": {
1931
+ "hit": 0.6363603957200638,
1932
+ "stand": 0.3636396042799362
1933
+ },
1934
+ "confidence": 0.2727207914401275
1935
+ },
1936
+ "risk": {
1937
+ "type": "score",
1938
+ "score": 1.621485575327213,
1939
+ "legend": {
1940
+ "0": "非常有利",
1941
+ "1": "稍微有利",
1942
+ "2": "有點不利",
1943
+ "3": "非常不利"
1944
+ },
1945
+ "probabilities": {
1946
+ "0": 0.12499958056879856,
1947
+ "1": 0.28623851581568904,
1948
+ "2": 0.4310386513350132,
1949
+ "3": 0.15772325228049922
1950
+ },
1951
+ "confidence": 0.3060390707662146
1952
+ },
1953
+ "latency_ms": 4541,
1954
+ "drawn": "3♠",
1955
+ "total_after": 17
1956
+ },
1957
+ {
1958
+ "type": "action",
1959
+ "cards": [
1960
+ "5♦",
1961
+ "9♣",
1962
+ "3♠"
1963
+ ],
1964
+ "total": 17,
1965
+ "soft": false,
1966
+ "dealer_up": "10♦",
1967
+ "allowed": [
1968
+ "hit",
1969
+ "stand"
1970
+ ],
1971
+ "decision": {
1972
+ "type": "choice",
1973
+ "choice": "stand",
1974
+ "probabilities": {
1975
+ "hit": 0.41681588405691944,
1976
+ "stand": 0.5831841159430805
1977
+ },
1978
+ "confidence": 0.166368231886161
1979
+ },
1980
+ "risk": {
1981
+ "type": "score",
1982
+ "score": 1.3499666013886922,
1983
+ "legend": {
1984
+ "0": "非常有利",
1985
+ "1": "稍微有利",
1986
+ "2": "有點不利",
1987
+ "3": "非常不利"
1988
+ },
1989
+ "probabilities": {
1990
+ "0": 0.19919145911352995,
1991
+ "1": 0.34421202407439494,
1992
+ "2": 0.364034973121928,
1993
+ "3": 0.09256154369014709
1994
+ },
1995
+ "confidence": 0.16484351400839803
1996
+ },
1997
+ "latency_ms": 3635,
1998
+ "drawn": null,
1999
+ "total_after": 17
2000
+ }
2001
+ ],
2002
+ "player_id": "b",
2003
+ "result": "lose",
2004
+ "net": -50,
2005
+ "bet_decision": {
2006
+ "decision": {
2007
+ "type": "choice",
2008
+ "choice": "max",
2009
+ "probabilities": {
2010
+ "min": 0.17807237353368274,
2011
+ "standard": 0.3629673835384618,
2012
+ "max": 0.4589602429278555
2013
+ },
2014
+ "confidence": 0.18844036439178322
2015
+ },
2016
+ "bet": 50,
2017
+ "confidence": {
2018
+ "type": "score",
2019
+ "score": 1.6467384307127664,
2020
+ "legend": {
2021
+ "0": "沒信心",
2022
+ "1": "普通",
2023
+ "2": "有信心",
2024
+ "3": "非常有信心"
2025
+ },
2026
+ "probabilities": {
2027
+ "0": 0.12712966024657835,
2028
+ "1": 0.29849798780601905,
2029
+ "2": 0.37487661293546065,
2030
+ "3": 0.199495739011942
2031
+ },
2032
+ "confidence": 0.24774695268888225
2033
+ },
2034
+ "latency_ms": 3080
2035
+ },
2036
+ "bankroll_after": 950,
2037
+ "id": "b",
2038
+ "name": "阿衝",
2039
+ "style": "激進壓迫派"
2040
+ }
2041
+ ],
2042
+ "shoe_after": {
2043
+ "decks": 6,
2044
+ "remaining_cards": 251,
2045
+ "penetration_pct": 20,
2046
+ "cards_left_by_rank": {
2047
+ "A": 16,
2048
+ "2": 19,
2049
+ "3": 15,
2050
+ "4": 23,
2051
+ "5": 21,
2052
+ "6": 21,
2053
+ "7": 19,
2054
+ "8": 19,
2055
+ "9": 19,
2056
+ "10": 17,
2057
+ "J": 23,
2058
+ "Q": 22,
2059
+ "K": 17
2060
+ },
2061
+ "hi_lo_running_count": -4,
2062
+ "true_count": -0.83
2063
+ }
2064
+ },
2065
+ {
2066
+ "n": 9,
2067
+ "shoe_before": {
2068
+ "decks": 6,
2069
+ "remaining_cards": 245,
2070
+ "penetration_pct": 21,
2071
+ "cards_left_by_rank": {
2072
+ "A": 14,
2073
+ "2": 19,
2074
+ "3": 15,
2075
+ "4": 23,
2076
+ "5": 21,
2077
+ "6": 20,
2078
+ "7": 18,
2079
+ "8": 19,
2080
+ "9": 18,
2081
+ "10": 17,
2082
+ "J": 23,
2083
+ "Q": 22,
2084
+ "K": 16
2085
+ },
2086
+ "hi_lo_running_count": -6,
2087
+ "true_count": -1.27
2088
+ },
2089
+ "dealer_up": "K♥",
2090
+ "dealer_hole": "7♣",
2091
+ "dealer_revealed": true,
2092
+ "dealer": {
2093
+ "cards": [
2094
+ "K♥",
2095
+ "7♣"
2096
+ ],
2097
+ "total": 17,
2098
+ "soft": false,
2099
+ "blackjack": false,
2100
+ "bust": false
2101
+ },
2102
+ "events": [
2103
+ "莊家明牌為 A 或 10 點,進行暗牌確認",
2104
+ "確認結果:莊家沒有 Blackjack",
2105
+ "莊家最終 17 點"
2106
+ ],
2107
+ "players": [
2108
+ {
2109
+ "cards": [
2110
+ "6♦",
2111
+ "9♦"
2112
+ ],
2113
+ "bet": 10,
2114
+ "doubled": false,
2115
+ "total": 15,
2116
+ "soft": false,
2117
+ "bust": false,
2118
+ "blackjack": false,
2119
+ "steps": [
2120
+ {
2121
+ "type": "action",
2122
+ "cards": [
2123
+ "6♦",
2124
+ "9♦"
2125
+ ],
2126
+ "total": 15,
2127
+ "soft": false,
2128
+ "dealer_up": "K♥",
2129
+ "allowed": [
2130
+ "hit",
2131
+ "stand"
2132
+ ],
2133
+ "decision": {
2134
+ "type": "choice",
2135
+ "choice": "stand",
2136
+ "probabilities": {
2137
+ "hit": 0.4065556882060927,
2138
+ "stand": 0.5934443117939072
2139
+ },
2140
+ "confidence": 0.18688862358781444
2141
+ },
2142
+ "risk": {
2143
+ "type": "score",
2144
+ "score": 1.4625511690470288,
2145
+ "legend": {
2146
+ "0": "非常有利",
2147
+ "1": "稍微有利",
2148
+ "2": "有點不利",
2149
+ "3": "非常不利"
2150
+ },
2151
+ "probabilities": {
2152
+ "0": 0.13799581285873977,
2153
+ "1": 0.34352512010586594,
2154
+ "2": 0.43641115216501986,
2155
+ "3": 0.08206791487037438
2156
+ },
2157
+ "confidence": 0.29841533930628017
2158
+ },
2159
+ "latency_ms": 2666,
2160
+ "drawn": null,
2161
+ "total_after": 15
2162
+ }
2163
+ ],
2164
+ "player_id": "a",
2165
+ "result": "lose",
2166
+ "net": -10,
2167
+ "bet_decision": {
2168
+ "decision": {
2169
+ "type": "choice",
2170
+ "choice": "min",
2171
+ "probabilities": {
2172
+ "min": 0.7083570651368878,
2173
+ "standard": 0.19545918656860584,
2174
+ "max": 0.09618374829450631
2175
+ },
2176
+ "confidence": 0.5625355977053317
2177
+ },
2178
+ "bet": 10,
2179
+ "confidence": {
2180
+ "type": "score",
2181
+ "score": 1.213641546925921,
2182
+ "legend": {
2183
+ "0": "沒信心",
2184
+ "1": "普通",
2185
+ "2": "有信心",
2186
+ "3": "非常有信心"
2187
+ },
2188
+ "probabilities": {
2189
+ "0": 0.20136564532087406,
2190
+ "1": 0.4638059699745116,
2191
+ "2": 0.25464957716243375,
2192
+ "3": 0.08017880754218061
2193
+ },
2194
+ "confidence": 0.38362716243233097
2195
+ },
2196
+ "latency_ms": 1984
2197
+ },
2198
+ "bankroll_after": 950,
2199
+ "id": "a",
2200
+ "name": "阿保",
2201
+ "style": "保守算牌派"
2202
+ },
2203
+ {
2204
+ "cards": [
2205
+ "A♥",
2206
+ "A♠",
2207
+ "Q♠",
2208
+ "8♣"
2209
+ ],
2210
+ "bet": 50,
2211
+ "doubled": false,
2212
+ "total": 20,
2213
+ "soft": false,
2214
+ "bust": false,
2215
+ "blackjack": false,
2216
+ "steps": [
2217
+ {
2218
+ "type": "action",
2219
+ "cards": [
2220
+ "A♥",
2221
+ "A♠"
2222
+ ],
2223
+ "total": 12,
2224
+ "soft": true,
2225
+ "dealer_up": "K♥",
2226
+ "allowed": [
2227
+ "hit",
2228
+ "stand"
2229
+ ],
2230
+ "decision": {
2231
+ "type": "choice",
2232
+ "choice": "hit",
2233
+ "probabilities": {
2234
+ "hit": 0.5939977924213096,
2235
+ "stand": 0.4060022075786904
2236
+ },
2237
+ "confidence": 0.18799558484261913
2238
+ },
2239
+ "risk": {
2240
+ "type": "score",
2241
+ "score": 1.4685976982166182,
2242
+ "legend": {
2243
+ "0": "非常有利",
2244
+ "1": "稍微有利",
2245
+ "2": "有點不利",
2246
+ "3": "非常不利"
2247
+ },
2248
+ "probabilities": {
2249
+ "0": 0.18819208543459834,
2250
+ "1": 0.30035061456540596,
2251
+ "2": 0.3661248163487746,
2252
+ "3": 0.14533248365122103
2253
+ },
2254
+ "confidence": 0.17793273091417627
2255
+ },
2256
+ "latency_ms": 3686,
2257
+ "drawn": "Q♠",
2258
+ "total_after": 12
2259
+ },
2260
+ {
2261
+ "type": "action",
2262
+ "cards": [
2263
+ "A♥",
2264
+ "A♠",
2265
+ "Q♠"
2266
+ ],
2267
+ "total": 12,
2268
+ "soft": false,
2269
+ "dealer_up": "K♥",
2270
+ "allowed": [
2271
+ "hit",
2272
+ "stand"
2273
+ ],
2274
+ "decision": {
2275
+ "type": "choice",
2276
+ "choice": "hit",
2277
+ "probabilities": {
2278
+ "hit": 0.6052363769804255,
2279
+ "stand": 0.39476362301957446
2280
+ },
2281
+ "confidence": 0.21047275396085108
2282
+ },
2283
+ "risk": {
2284
+ "type": "score",
2285
+ "score": 1.5574917347618844,
2286
+ "legend": {
2287
+ "0": "非常有利",
2288
+ "1": "稍微有利",
2289
+ "2": "有點不利",
2290
+ "3": "非常不利"
2291
+ },
2292
+ "probabilities": {
2293
+ "0": 0.16846858723710814,
2294
+ "1": 0.28599754847447173,
2295
+ "2": 0.3651074065778472,
2296
+ "3": 0.18042645771057275
2297
+ },
2298
+ "confidence": 0.19663881934073923
2299
+ },
2300
+ "latency_ms": 3449,
2301
+ "drawn": "8♣",
2302
+ "total_after": 20
2303
+ },
2304
+ {
2305
+ "type": "action",
2306
+ "cards": [
2307
+ "A♥",
2308
+ "A♠",
2309
+ "Q♠",
2310
+ "8♣"
2311
+ ],
2312
+ "total": 20,
2313
+ "soft": false,
2314
+ "dealer_up": "K♥",
2315
+ "allowed": [
2316
+ "hit",
2317
+ "stand"
2318
+ ],
2319
+ "decision": {
2320
+ "type": "choice",
2321
+ "choice": "stand",
2322
+ "probabilities": {
2323
+ "hit": 0.3199784228049078,
2324
+ "stand": 0.6800215771950923
2325
+ },
2326
+ "confidence": 0.3600431543901845
2327
+ },
2328
+ "risk": {
2329
+ "type": "score",
2330
+ "score": 1.2731733674465937,
2331
+ "legend": {
2332
+ "0": "非常有利",
2333
+ "1": "稍微有利",
2334
+ "2": "有點不利",
2335
+ "3": "非常不利"
2336
+ },
2337
+ "probabilities": {
2338
+ "0": 0.2453494945322414,
2339
+ "1": 0.3366067819479323,
2340
+ "2": 0.31756458506081714,
2341
+ "3": 0.10047913845900906
2342
+ },
2343
+ "confidence": 0.23612764348892334
2344
+ },
2345
+ "latency_ms": 3726,
2346
+ "drawn": null,
2347
+ "total_after": 20
2348
+ }
2349
+ ],
2350
+ "player_id": "b",
2351
+ "result": "win",
2352
+ "net": 50,
2353
+ "bet_decision": {
2354
+ "decision": {
2355
+ "type": "choice",
2356
+ "choice": "max",
2357
+ "probabilities": {
2358
+ "min": 0.18317788211857033,
2359
+ "standard": 0.37100120794841007,
2360
+ "max": 0.4458209099330196
2361
+ },
2362
+ "confidence": 0.1687313648995294
2363
+ },
2364
+ "bet": 50,
2365
+ "confidence": {
2366
+ "type": "score",
2367
+ "score": 1.7016208412378346,
2368
+ "legend": {
2369
+ "0": "沒信心",
2370
+ "1": "普通",
2371
+ "2": "有信心",
2372
+ "3": "非常有信心"
2373
+ },
2374
+ "probabilities": {
2375
+ "0": 0.1245882351848078,
2376
+ "1": 0.2690667058185058,
2377
+ "2": 0.3864810415707307,
2378
+ "3": 0.21986401742595574
2379
+ },
2380
+ "confidence": 0.2618928063859228
2381
+ },
2382
+ "latency_ms": 2915
2383
+ },
2384
+ "bankroll_after": 1000,
2385
+ "id": "b",
2386
+ "name": "阿衝",
2387
+ "style": "激進壓迫派"
2388
+ }
2389
+ ],
2390
+ "shoe_after": {
2391
+ "decks": 6,
2392
+ "remaining_cards": 243,
2393
+ "penetration_pct": 22,
2394
+ "cards_left_by_rank": {
2395
+ "A": 14,
2396
+ "2": 19,
2397
+ "3": 15,
2398
+ "4": 23,
2399
+ "5": 21,
2400
+ "6": 20,
2401
+ "7": 18,
2402
+ "8": 18,
2403
+ "9": 18,
2404
+ "10": 17,
2405
+ "J": 23,
2406
+ "Q": 21,
2407
+ "K": 16
2408
+ },
2409
+ "hi_lo_running_count": -7,
2410
+ "true_count": -1.5
2411
+ }
2412
+ },
2413
+ {
2414
+ "n": 10,
2415
+ "shoe_before": {
2416
+ "decks": 6,
2417
+ "remaining_cards": 237,
2418
+ "penetration_pct": 24,
2419
+ "cards_left_by_rank": {
2420
+ "A": 14,
2421
+ "2": 18,
2422
+ "3": 15,
2423
+ "4": 22,
2424
+ "5": 19,
2425
+ "6": 20,
2426
+ "7": 18,
2427
+ "8": 18,
2428
+ "9": 17,
2429
+ "10": 17,
2430
+ "J": 23,
2431
+ "Q": 21,
2432
+ "K": 15
2433
+ },
2434
+ "hi_lo_running_count": -4,
2435
+ "true_count": -0.88
2436
+ },
2437
+ "dealer_up": "4♦",
2438
+ "dealer_hole": "5♣",
2439
+ "dealer_revealed": true,
2440
+ "dealer": {
2441
+ "cards": [
2442
+ "4♦",
2443
+ "5♣",
2444
+ "A♦"
2445
+ ],
2446
+ "total": 20,
2447
+ "soft": true,
2448
+ "blackjack": false,
2449
+ "bust": false
2450
+ },
2451
+ "events": [
2452
+ "莊家補牌 A♦ → 20 點",
2453
+ "莊家最終 20 點"
2454
+ ],
2455
+ "players": [
2456
+ {
2457
+ "cards": [
2458
+ "5♥",
2459
+ "2♦",
2460
+ "3♦",
2461
+ "5♥",
2462
+ "Q♠"
2463
+ ],
2464
+ "bet": 10,
2465
+ "doubled": false,
2466
+ "total": 25,
2467
+ "soft": false,
2468
+ "bust": true,
2469
+ "blackjack": false,
2470
+ "steps": [
2471
+ {
2472
+ "type": "action",
2473
+ "cards": [
2474
+ "5♥",
2475
+ "2♦"
2476
+ ],
2477
+ "total": 7,
2478
+ "soft": false,
2479
+ "dealer_up": "4♦",
2480
+ "allowed": [
2481
+ "hit",
2482
+ "stand"
2483
+ ],
2484
+ "decision": {
2485
+ "type": "choice",
2486
+ "choice": "hit",
2487
+ "probabilities": {
2488
+ "hit": 0.7694504382534303,
2489
+ "stand": 0.23054956174656965
2490
+ },
2491
+ "confidence": 0.5389008765068606
2492
+ },
2493
+ "risk": {
2494
+ "type": "score",
2495
+ "score": 2.1228442812944257,
2496
+ "legend": {
2497
+ "0": "非常有利",
2498
+ "1": "稍微有利",
2499
+ "2": "有點不利",
2500
+ "3": "非常不利"
2501
+ },
2502
+ "probabilities": {
2503
+ "0": 0.054250014780109976,
2504
+ "1": 0.1406058459410434,
2505
+ "2": 0.4331939824831577,
2506
+ "3": 0.37195015679568905
2507
+ },
2508
+ "confidence": 0.3789439677030476
2509
+ },
2510
+ "latency_ms": 2707,
2511
+ "drawn": "3♦",
2512
+ "total_after": 10
2513
+ },
2514
+ {
2515
+ "type": "action",
2516
+ "cards": [
2517
+ "5♥",
2518
+ "2♦",
2519
+ "3♦"
2520
+ ],
2521
+ "total": 10,
2522
+ "soft": false,
2523
+ "dealer_up": "4♦",
2524
+ "allowed": [
2525
+ "hit",
2526
+ "stand"
2527
+ ],
2528
+ "decision": {
2529
+ "type": "choice",
2530
+ "choice": "hit",
2531
+ "probabilities": {
2532
+ "hit": 0.7577920957558095,
2533
+ "stand": 0.24220790424419048
2534
+ },
2535
+ "confidence": 0.515584191511619
2536
+ },
2537
+ "risk": {
2538
+ "type": "score",
2539
+ "score": 1.6885405801021331,
2540
+ "legend": {
2541
+ "0": "非常有利",
2542
+ "1": "稍微有利",
2543
+ "2": "有點不利",
2544
+ "3": "非常不利"
2545
+ },
2546
+ "probabilities": {
2547
+ "0": 0.08428075713214693,
2548
+ "1": 0.28471381134884216,
2549
+ "2": 0.489189525803742,
2550
+ "3": 0.14181590571526903
2551
+ },
2552
+ "confidence": 0.404908768671595
2553
+ },
2554
+ "latency_ms": 2613,
2555
+ "drawn": "5♥",
2556
+ "total_after": 15
2557
+ },
2558
+ {
2559
+ "type": "action",
2560
+ "cards": [
2561
+ "5♥",
2562
+ "2♦",
2563
+ "3♦",
2564
+ "5♥"
2565
+ ],
2566
+ "total": 15,
2567
+ "soft": false,
2568
+ "dealer_up": "4♦",
2569
+ "allowed": [
2570
+ "hit",
2571
+ "stand"
2572
+ ],
2573
+ "decision": {
2574
+ "type": "choice",
2575
+ "choice": "hit",
2576
+ "probabilities": {
2577
+ "hit": 0.5788127737999339,
2578
+ "stand": 0.421187226200066
2579
+ },
2580
+ "confidence": 0.1576255475998678
2581
+ },
2582
+ "risk": {
2583
+ "type": "score",
2584
+ "score": 1.4818083791924999,
2585
+ "legend": {
2586
+ "0": "非常有利",
2587
+ "1": "稍微有利",
2588
+ "2": "有點不利",
2589
+ "3": "非常不利"
2590
+ },
2591
+ "probabilities": {
2592
+ "0": 0.11315441258530298,
2593
+ "1": 0.36485505760468034,
2594
+ "2": 0.4490182678422307,
2595
+ "3": 0.07297226196778603
2596
+ },
2597
+ "confidence": 0.33586385525692763
2598
+ },
2599
+ "latency_ms": 2554,
2600
+ "drawn": "Q♠",
2601
+ "total_after": 25
2602
+ }
2603
+ ],
2604
+ "player_id": "a",
2605
+ "result": "lose",
2606
+ "net": -10,
2607
+ "bet_decision": {
2608
+ "decision": {
2609
+ "type": "choice",
2610
+ "choice": "min",
2611
+ "probabilities": {
2612
+ "min": 0.7286488493616847,
2613
+ "standard": 0.175529357430751,
2614
+ "max": 0.09582179320756426
2615
+ },
2616
+ "confidence": 0.5929732740425271
2617
+ },
2618
+ "bet": 10,
2619
+ "confidence": {
2620
+ "type": "score",
2621
+ "score": 1.169905749673971,
2622
+ "legend": {
2623
+ "0": "沒信心",
2624
+ "1": "普通",
2625
+ "2": "有信心",
2626
+ "3": "非常有信心"
2627
+ },
2628
+ "probabilities": {
2629
+ "0": 0.216524204542941,
2630
+ "1": 0.46785689500435057,
2631
+ "2": 0.24480784668850483,
2632
+ "3": 0.07081105376420364
2633
+ },
2634
+ "confidence": 0.39704584124014686
2635
+ },
2636
+ "latency_ms": 2177
2637
+ },
2638
+ "bankroll_after": 940,
2639
+ "id": "a",
2640
+ "name": "阿保",
2641
+ "style": "保守算牌派"
2642
+ },
2643
+ {
2644
+ "cards": [
2645
+ "K♠",
2646
+ "9♥"
2647
+ ],
2648
+ "bet": 50,
2649
+ "doubled": false,
2650
+ "total": 19,
2651
+ "soft": false,
2652
+ "bust": false,
2653
+ "blackjack": false,
2654
+ "steps": [
2655
+ {
2656
+ "type": "action",
2657
+ "cards": [
2658
+ "K♠",
2659
+ "9♥"
2660
+ ],
2661
+ "total": 19,
2662
+ "soft": false,
2663
+ "dealer_up": "4♦",
2664
+ "allowed": [
2665
+ "hit",
2666
+ "stand"
2667
+ ],
2668
+ "decision": {
2669
+ "type": "choice",
2670
+ "choice": "stand",
2671
+ "probabilities": {
2672
+ "hit": 0.29597815331328925,
2673
+ "stand": 0.7040218466867108
2674
+ },
2675
+ "confidence": 0.4080436933734215
2676
+ },
2677
+ "risk": {
2678
+ "type": "score",
2679
+ "score": 1.0629240761621292,
2680
+ "legend": {
2681
+ "0": "非常有利",
2682
+ "1": "稍微有利",
2683
+ "2": "有點不利",
2684
+ "3": "非常不利"
2685
+ },
2686
+ "probabilities": {
2687
+ "0": 0.2850042266335565,
2688
+ "1": 0.40113920626045185,
2689
+ "2": 0.2797848314162972,
2690
+ "3": 0.03407173568969435
2691
+ },
2692
+ "confidence": 0.3670674705707576
2693
+ },
2694
+ "latency_ms": 3464,
2695
+ "drawn": null,
2696
+ "total_after": 19
2697
+ }
2698
+ ],
2699
+ "player_id": "b",
2700
+ "result": "lose",
2701
+ "net": -50,
2702
+ "bet_decision": {
2703
+ "decision": {
2704
+ "type": "choice",
2705
+ "choice": "max",
2706
+ "probabilities": {
2707
+ "min": 0.17685549991326993,
2708
+ "standard": 0.3770309433442875,
2709
+ "max": 0.44611355674244274
2710
+ },
2711
+ "confidence": 0.1691703351136641
2712
+ },
2713
+ "bet": 50,
2714
+ "confidence": {
2715
+ "type": "score",
2716
+ "score": 1.6562748371119183,
2717
+ "legend": {
2718
+ "0": "沒信心",
2719
+ "1": "普通",
2720
+ "2": "有信心",
2721
+ "3": "非常有信心"
2722
+ },
2723
+ "probabilities": {
2724
+ "0": 0.1332817973157961,
2725
+ "1": 0.2844193759896284,
2726
+ "2": 0.3750410189614367,
2727
+ "3": 0.20725780773313882
2728
+ },
2729
+ "confidence": 0.24175922164564057
2730
+ },
2731
+ "latency_ms": 2647
2732
+ },
2733
+ "bankroll_after": 950,
2734
+ "id": "b",
2735
+ "name": "阿衝",
2736
+ "style": "激進壓迫派"
2737
+ }
2738
+ ],
2739
+ "shoe_after": {
2740
+ "decks": 6,
2741
+ "remaining_cards": 233,
2742
+ "penetration_pct": 25,
2743
+ "cards_left_by_rank": {
2744
+ "A": 13,
2745
+ "2": 18,
2746
+ "3": 14,
2747
+ "4": 22,
2748
+ "5": 18,
2749
+ "6": 20,
2750
+ "7": 18,
2751
+ "8": 18,
2752
+ "9": 17,
2753
+ "10": 17,
2754
+ "J": 23,
2755
+ "Q": 20,
2756
+ "K": 15
2757
+ },
2758
+ "hi_lo_running_count": -4,
2759
+ "true_count": -0.89
2760
+ }
2761
+ },
2762
+ {
2763
+ "n": 11,
2764
+ "shoe_before": {
2765
+ "decks": 6,
2766
+ "remaining_cards": 227,
2767
+ "penetration_pct": 27,
2768
+ "cards_left_by_rank": {
2769
+ "A": 13,
2770
+ "2": 17,
2771
+ "3": 13,
2772
+ "4": 22,
2773
+ "5": 18,
2774
+ "6": 19,
2775
+ "7": 18,
2776
+ "8": 18,
2777
+ "9": 17,
2778
+ "10": 17,
2779
+ "J": 22,
2780
+ "Q": 20,
2781
+ "K": 13
2782
+ },
2783
+ "hi_lo_running_count": -4,
2784
+ "true_count": -0.92
2785
+ },
2786
+ "dealer_up": "J♥",
2787
+ "dealer_hole": "6♦",
2788
+ "dealer_revealed": true,
2789
+ "dealer": {
2790
+ "cards": [
2791
+ "J♥",
2792
+ "6♦",
2793
+ "2♠"
2794
+ ],
2795
+ "total": 18,
2796
+ "soft": false,
2797
+ "blackjack": false,
2798
+ "bust": false
2799
+ },
2800
+ "events": [
2801
+ "莊家明牌為 A 或 10 點,進行暗牌確認",
2802
+ "確認結果:莊家沒有 Blackjack",
2803
+ "莊家補牌 2♠ → 18 點",
2804
+ "莊家最終 18 點"
2805
+ ],
2806
+ "players": [
2807
+ {
2808
+ "cards": [
2809
+ "K♦",
2810
+ "K♠"
2811
+ ],
2812
+ "bet": 10,
2813
+ "doubled": false,
2814
+ "total": 20,
2815
+ "soft": false,
2816
+ "bust": false,
2817
+ "blackjack": false,
2818
+ "steps": [
2819
+ {
2820
+ "type": "action",
2821
+ "cards": [
2822
+ "K♦",
2823
+ "K♠"
2824
+ ],
2825
+ "total": 20,
2826
+ "soft": false,
2827
+ "dealer_up": "J♥",
2828
+ "allowed": [
2829
+ "hit",
2830
+ "stand"
2831
+ ],
2832
+ "decision": {
2833
+ "type": "choice",
2834
+ "choice": "stand",
2835
+ "probabilities": {
2836
+ "hit": 0.20113812988182753,
2837
+ "stand": 0.7988618701181724
2838
+ },
2839
+ "confidence": 0.5977237402363449
2840
+ },
2841
+ "risk": {
2842
+ "type": "score",
2843
+ "score": 0.9617831874646433,
2844
+ "legend": {
2845
+ "0": "非常有利",
2846
+ "1": "稍微有利",
2847
+ "2": "有點不利",
2848
+ "3": "非常不利"
2849
+ },
2850
+ "probabilities": {
2851
+ "0": 0.37266878093983297,
2852
+ "1": 0.3419774875432344,
2853
+ "2": 0.2362554946293891,
2854
+ "3": 0.04909823688754356
2855
+ },
2856
+ "confidence": 0.03821681253535669
2857
+ },
2858
+ "latency_ms": 2807,
2859
+ "drawn": null,
2860
+ "total_after": 20
2861
+ }
2862
+ ],
2863
+ "player_id": "a",
2864
+ "result": "win",
2865
+ "net": 10,
2866
+ "bet_decision": {
2867
+ "decision": {
2868
+ "type": "choice",
2869
+ "choice": "min",
2870
+ "probabilities": {
2871
+ "min": 0.705457858991765,
2872
+ "standard": 0.19844058997151406,
2873
+ "max": 0.09610155103672088
2874
+ },
2875
+ "confidence": 0.5581867884876476
2876
+ },
2877
+ "bet": 10,
2878
+ "confidence": {
2879
+ "type": "score",
2880
+ "score": 1.1786459226702077,
2881
+ "legend": {
2882
+ "0": "沒信心",
2883
+ "1": "普通",
2884
+ "2": "有信心",
2885
+ "3": "非常有信心"
2886
+ },
2887
+ "probabilities": {
2888
+ "0": 0.2098777886585959,
2889
+ "1": 0.4725124514370032,
2890
+ "2": 0.24669580847999806,
2891
+ "3": 0.07091395142440278
2892
+ },
2893
+ "confidence": 0.40159850001260045
2894
+ },
2895
+ "latency_ms": 2281
2896
+ },
2897
+ "bankroll_after": 950,
2898
+ "id": "a",
2899
+ "name": "阿保",
2900
+ "style": "保守算牌派"
2901
+ },
2902
+ {
2903
+ "cards": [
2904
+ "2♦",
2905
+ "3♥",
2906
+ "2♥",
2907
+ "A♠"
2908
+ ],
2909
+ "bet": 50,
2910
+ "doubled": false,
2911
+ "total": 18,
2912
+ "soft": true,
2913
+ "bust": false,
2914
+ "blackjack": false,
2915
+ "steps": [
2916
+ {
2917
+ "type": "action",
2918
+ "cards": [
2919
+ "2♦",
2920
+ "3♥"
2921
+ ],
2922
+ "total": 5,
2923
+ "soft": false,
2924
+ "dealer_up": "J♥",
2925
+ "allowed": [
2926
+ "hit",
2927
+ "stand"
2928
+ ],
2929
+ "decision": {
2930
+ "type": "choice",
2931
+ "choice": "hit",
2932
+ "probabilities": {
2933
+ "hit": 0.8339648493225332,
2934
+ "stand": 0.1660351506774667
2935
+ },
2936
+ "confidence": 0.6679296986450665
2937
+ },
2938
+ "risk": {
2939
+ "type": "score",
2940
+ "score": 1.9632358033511101,
2941
+ "legend": {
2942
+ "0": "非常有利",
2943
+ "1": "稍微有利",
2944
+ "2": "有點不利",
2945
+ "3": "非常不利"
2946
+ },
2947
+ "probabilities": {
2948
+ "0": 0.08778014756208807,
2949
+ "1": 0.17939112699816237,
2950
+ "2": 0.414641499966301,
2951
+ "3": 0.3181872254734486
2952
+ },
2953
+ "confidence": 0.3268613524042129
2954
+ },
2955
+ "latency_ms": 3984,
2956
+ "drawn": "2♥",
2957
+ "total_after": 7
2958
+ },
2959
+ {
2960
+ "type": "action",
2961
+ "cards": [
2962
+ "2♦",
2963
+ "3♥",
2964
+ "2♥"
2965
+ ],
2966
+ "total": 7,
2967
+ "soft": false,
2968
+ "dealer_up": "J♥",
2969
+ "allowed": [
2970
+ "hit",
2971
+ "stand"
2972
+ ],
2973
+ "decision": {
2974
+ "type": "choice",
2975
+ "choice": "hit",
2976
+ "probabilities": {
2977
+ "hit": 0.8254946752313272,
2978
+ "stand": 0.17450532476867275
2979
+ },
2980
+ "confidence": 0.6509893504626545
2981
+ },
2982
+ "risk": {
2983
+ "type": "score",
2984
+ "score": 1.9199601944797384,
2985
+ "legend": {
2986
+ "0": "非常有利",
2987
+ "1": "稍微有利",
2988
+ "2": "有點不利",
2989
+ "3": "非常不利"
2990
+ },
2991
+ "probabilities": {
2992
+ "0": 0.08961070661335929,
2993
+ "1": 0.1944316247075459,
2994
+ "2": 0.4223444362650919,
2995
+ "3": 0.2936132324140029
2996
+ },
2997
+ "confidence": 0.33273372965173253
2998
+ },
2999
+ "latency_ms": 3647,
3000
+ "drawn": "A♠",
3001
+ "total_after": 18
3002
+ },
3003
+ {
3004
+ "type": "action",
3005
+ "cards": [
3006
+ "2♦",
3007
+ "3♥",
3008
+ "2♥",
3009
+ "A♠"
3010
+ ],
3011
+ "total": 18,
3012
+ "soft": true,
3013
+ "dealer_up": "J♥",
3014
+ "allowed": [
3015
+ "hit",
3016
+ "stand"
3017
+ ],
3018
+ "decision": {
3019
+ "type": "choice",
3020
+ "choice": "stand",
3021
+ "probabilities": {
3022
+ "hit": 0.4516507728170757,
3023
+ "stand": 0.5483492271829243
3024
+ },
3025
+ "confidence": 0.09669845436584867
3026
+ },
3027
+ "risk": {
3028
+ "type": "score",
3029
+ "score": 1.3633877251471496,
3030
+ "legend": {
3031
+ "0": "非常有利",
3032
+ "1": "稍微有利",
3033
+ "2": "有點不利",
3034
+ "3": "非常不利"
3035
+ },
3036
+ "probabilities": {
3037
+ "0": 0.1898920049001741,
3038
+ "1": 0.35446905502554144,
3039
+ "2": 0.3579981501012455,
3040
+ "3": 0.09764078997303899
3041
+ },
3042
+ "confidence": 0.16810614520107148
3043
+ },
3044
+ "latency_ms": 3469,
3045
+ "drawn": null,
3046
+ "total_after": 18
3047
+ }
3048
+ ],
3049
+ "player_id": "b",
3050
+ "result": "push",
3051
+ "net": 0,
3052
+ "bet_decision": {
3053
+ "decision": {
3054
+ "type": "choice",
3055
+ "choice": "max",
3056
+ "probabilities": {
3057
+ "min": 0.17659368546509877,
3058
+ "standard": 0.38463171221217046,
3059
+ "max": 0.43877460232273074
3060
+ },
3061
+ "confidence": 0.15816190348409612
3062
+ },
3063
+ "bet": 50,
3064
+ "confidence": {
3065
+ "type": "score",
3066
+ "score": 1.6844293372148722,
3067
+ "legend": {
3068
+ "0": "沒信心",
3069
+ "1": "普通",
3070
+ "2": "有信心",
3071
+ "3": "非常有信心"
3072
+ },
3073
+ "probabilities": {
3074
+ "0": 0.12644015955206472,
3075
+ "1": 0.27508981392517495,
3076
+ "2": 0.3860705562785838,
3077
+ "3": 0.21239947024417657
3078
+ },
3079
+ "confidence": 0.259630396726519
3080
+ },
3081
+ "latency_ms": 2718
3082
+ },
3083
+ "bankroll_after": 950,
3084
+ "id": "b",
3085
+ "name": "阿衝",
3086
+ "style": "激進壓迫派"
3087
+ }
3088
+ ],
3089
+ "shoe_after": {
3090
+ "decks": 6,
3091
+ "remaining_cards": 224,
3092
+ "penetration_pct": 28,
3093
+ "cards_left_by_rank": {
3094
+ "A": 12,
3095
+ "2": 15,
3096
+ "3": 13,
3097
+ "4": 22,
3098
+ "5": 18,
3099
+ "6": 19,
3100
+ "7": 18,
3101
+ "8": 18,
3102
+ "9": 17,
3103
+ "10": 17,
3104
+ "J": 22,
3105
+ "Q": 20,
3106
+ "K": 13
3107
+ },
3108
+ "hi_lo_running_count": -3,
3109
+ "true_count": -0.7
3110
+ }
3111
+ },
3112
+ {
3113
+ "n": 12,
3114
+ "shoe_before": {
3115
+ "decks": 6,
3116
+ "remaining_cards": 218,
3117
+ "penetration_pct": 30,
3118
+ "cards_left_by_rank": {
3119
+ "A": 12,
3120
+ "2": 15,
3121
+ "3": 13,
3122
+ "4": 22,
3123
+ "5": 18,
3124
+ "6": 18,
3125
+ "7": 16,
3126
+ "8": 17,
3127
+ "9": 17,
3128
+ "10": 17,
3129
+ "J": 22,
3130
+ "Q": 19,
3131
+ "K": 12
3132
+ },
3133
+ "hi_lo_running_count": -4,
3134
+ "true_count": -0.95
3135
+ },
3136
+ "dealer_up": "K♦",
3137
+ "dealer_hole": "8♦",
3138
+ "dealer_revealed": true,
3139
+ "dealer": {
3140
+ "cards": [
3141
+ "K♦",
3142
+ "8♦"
3143
+ ],
3144
+ "total": 18,
3145
+ "soft": false,
3146
+ "blackjack": false,
3147
+ "bust": false
3148
+ },
3149
+ "events": [
3150
+ "莊家明牌為 A 或 10 點,進行暗牌確認",
3151
+ "確認結果:莊家沒有 Blackjack",
3152
+ "莊家最終 18 點"
3153
+ ],
3154
+ "players": [
3155
+ {
3156
+ "cards": [
3157
+ "Q♦",
3158
+ "6♣"
3159
+ ],
3160
+ "bet": 10,
3161
+ "doubled": false,
3162
+ "total": 16,
3163
+ "soft": false,
3164
+ "bust": false,
3165
+ "blackjack": false,
3166
+ "steps": [
3167
+ {
3168
+ "type": "action",
3169
+ "cards": [
3170
+ "Q♦",
3171
+ "6♣"
3172
+ ],
3173
+ "total": 16,
3174
+ "soft": false,
3175
+ "dealer_up": "K♦",
3176
+ "allowed": [
3177
+ "hit",
3178
+ "stand"
3179
+ ],
3180
+ "decision": {
3181
+ "type": "choice",
3182
+ "choice": "stand",
3183
+ "probabilities": {
3184
+ "hit": 0.39016492109703543,
3185
+ "stand": 0.6098350789029646
3186
+ },
3187
+ "confidence": 0.21967015780592924
3188
+ },
3189
+ "risk": {
3190
+ "type": "score",
3191
+ "score": 1.7370799154639605,
3192
+ "legend": {
3193
+ "0": "非常有利",
3194
+ "1": "稍微有利",
3195
+ "2": "有點不利",
3196
+ "3": "非常不利"
3197
+ },
3198
+ "probabilities": {
3199
+ "0": 0.09344393533096118,
3200
+ "1": 0.2685085201031415,
3201
+ "2": 0.4455712383368731,
3202
+ "3": 0.19247630622902423
3203
+ },
3204
+ "confidence": 0.3521273030059119
3205
+ },
3206
+ "latency_ms": 2460,
3207
+ "drawn": null,
3208
+ "total_after": 16
3209
+ }
3210
+ ],
3211
+ "player_id": "a",
3212
+ "result": "lose",
3213
+ "net": -10,
3214
+ "bet_decision": {
3215
+ "decision": {
3216
+ "type": "choice",
3217
+ "choice": "min",
3218
+ "probabilities": {
3219
+ "min": 0.7338713056072282,
3220
+ "standard": 0.171097765938789,
3221
+ "max": 0.09503092845398282
3222
+ },
3223
+ "confidence": 0.6008069584108423
3224
+ },
3225
+ "bet": 10,
3226
+ "confidence": {
3227
+ "type": "score",
3228
+ "score": 1.2594716391475735,
3229
+ "legend": {
3230
+ "0": "沒信心",
3231
+ "1": "普通",
3232
+ "2": "有信心",
3233
+ "3": "非常有信心"
3234
+ },
3235
+ "probabilities": {
3236
+ "0": 0.16968632812925138,
3237
+ "1": 0.4794955805426685,
3238
+ "2": 0.2724782153793357,
3239
+ "3": 0.07833987594874454
3240
+ },
3241
+ "confidence": 0.4011557045939238
3242
+ },
3243
+ "latency_ms": 1995
3244
+ },
3245
+ "bankroll_after": 940,
3246
+ "id": "a",
3247
+ "name": "阿保",
3248
+ "style": "保守算牌派"
3249
+ },
3250
+ {
3251
+ "cards": [
3252
+ "7♥",
3253
+ "7♦",
3254
+ "3♥"
3255
+ ],
3256
+ "bet": 50,
3257
+ "doubled": false,
3258
+ "total": 17,
3259
+ "soft": false,
3260
+ "bust": false,
3261
+ "blackjack": false,
3262
+ "steps": [
3263
+ {
3264
+ "type": "action",
3265
+ "cards": [
3266
+ "7♥",
3267
+ "7♦"
3268
+ ],
3269
+ "total": 14,
3270
+ "soft": false,
3271
+ "dealer_up": "K♦",
3272
+ "allowed": [
3273
+ "hit",
3274
+ "stand"
3275
+ ],
3276
+ "decision": {
3277
+ "type": "choice",
3278
+ "choice": "hit",
3279
+ "probabilities": {
3280
+ "hit": 0.608349823613437,
3281
+ "stand": 0.3916501763865629
3282
+ },
3283
+ "confidence": 0.21669964722687407
3284
+ },
3285
+ "risk": {
3286
+ "type": "score",
3287
+ "score": 1.4682163416498657,
3288
+ "legend": {
3289
+ "0": "非常有利",
3290
+ "1": "稍微有利",
3291
+ "2": "有點不利",
3292
+ "3": "非常不利"
3293
+ },
3294
+ "probabilities": {
3295
+ "0": 0.17040055353150113,
3296
+ "1": 0.3169814206595017,
3297
+ "2": 0.38661915643662725,
3298
+ "3": 0.12599886937236981
3299
+ },
3300
+ "confidence": 0.21621860290512618
3301
+ },
3302
+ "latency_ms": 3602,
3303
+ "drawn": "3♥",
3304
+ "total_after": 17
3305
+ },
3306
+ {
3307
+ "type": "action",
3308
+ "cards": [
3309
+ "7♥",
3310
+ "7♦",
3311
+ "3♥"
3312
+ ],
3313
+ "total": 17,
3314
+ "soft": false,
3315
+ "dealer_up": "K♦",
3316
+ "allowed": [
3317
+ "hit",
3318
+ "stand"
3319
+ ],
3320
+ "decision": {
3321
+ "type": "choice",
3322
+ "choice": "stand",
3323
+ "probabilities": {
3324
+ "hit": 0.4373052794431983,
3325
+ "stand": 0.5626947205568017
3326
+ },
3327
+ "confidence": 0.1253894411136034
3328
+ },
3329
+ "risk": {
3330
+ "type": "score",
3331
+ "score": 1.27082220318929,
3332
+ "legend": {
3333
+ "0": "非常有利",
3334
+ "1": "稍微有利",
3335
+ "2": "有點不利",
3336
+ "3": "非常不利"
3337
+ },
3338
+ "probabilities": {
3339
+ "0": 0.21799232108026073,
3340
+ "1": 0.36294600367568364,
3341
+ "2": 0.34930882621856046,
3342
+ "3": 0.06975284902549515
3343
+ },
3344
+ "confidence": 0.29319315465018847
3345
+ },
3346
+ "latency_ms": 3726,
3347
+ "drawn": null,
3348
+ "total_after": 17
3349
+ }
3350
+ ],
3351
+ "player_id": "b",
3352
+ "result": "lose",
3353
+ "net": -50,
3354
+ "bet_decision": {
3355
+ "decision": {
3356
+ "type": "choice",
3357
+ "choice": "max",
3358
+ "probabilities": {
3359
+ "min": 0.18420429014082615,
3360
+ "standard": 0.39700684030663047,
3361
+ "max": 0.4187888695525434
3362
+ },
3363
+ "confidence": 0.1281833043288151
3364
+ },
3365
+ "bet": 50,
3366
+ "confidence": {
3367
+ "type": "score",
3368
+ "score": 1.6458985137494473,
3369
+ "legend": {
3370
+ "0": "沒信心",
3371
+ "1": "普通",
3372
+ "2": "有信心",
3373
+ "3": "非常有信心"
3374
+ },
3375
+ "probabilities": {
3376
+ "0": 0.1260372937960504,
3377
+ "1": 0.2951391749084489,
3378
+ "2": 0.3857112550455043,
3379
+ "3": 0.19311227624999663
3380
+ },
3381
+ "confidence": 0.25967396124945374
3382
+ },
3383
+ "latency_ms": 3039
3384
+ },
3385
+ "bankroll_after": 900,
3386
+ "id": "b",
3387
+ "name": "阿衝",
3388
+ "style": "激進壓迫派"
3389
+ }
3390
+ ],
3391
+ "shoe_after": {
3392
+ "decks": 6,
3393
+ "remaining_cards": 217,
3394
+ "penetration_pct": 30,
3395
+ "cards_left_by_rank": {
3396
+ "A": 12,
3397
+ "2": 15,
3398
+ "3": 12,
3399
+ "4": 22,
3400
+ "5": 18,
3401
+ "6": 18,
3402
+ "7": 16,
3403
+ "8": 17,
3404
+ "9": 17,
3405
+ "10": 17,
3406
+ "J": 22,
3407
+ "Q": 19,
3408
+ "K": 12
3409
+ },
3410
+ "hi_lo_running_count": -3,
3411
+ "true_count": -0.72
3412
+ }
3413
+ }
3414
+ ]
3415
+ }
index.html CHANGED
@@ -1,19 +1,674 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
  <!doctype html>
2
+ <html lang="zh-Hant">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>d1 × d1 21 點對戰實錄</title>
7
+ <meta name="description" content="兩個 LiquidAI d1-3B 決策模型,透過 llama.cpp 在同一個牌桌上對打">
8
+ <style>
9
+ :root{
10
+ --bg:#07120e; --bg2:#0b1c16; --panel:#0e241c; --panel2:#123026;
11
+ --line:rgba(255,255,255,.09); --text:#e8f3ed; --muted:#8fae9f;
12
+ --gold:#f6c453; --green:#39d98a; --red:#ff6b6b; --blue:#5cc8ff; --amber:#ffb020;
13
+ --hit:#ff6b6b; --stand:#5cc8ff; --double:#f6c453;
14
+ --shadow:0 18px 50px rgba(0,0,0,.45);
15
+ }
16
+ *{box-sizing:border-box}
17
+ html,body{margin:0;padding:0}
18
+ body{
19
+ background:
20
+ radial-gradient(1100px 700px at 12% -10%, rgba(57,217,138,.13), transparent 60%),
21
+ radial-gradient(900px 600px at 92% 0%, rgba(246,196,83,.10), transparent 55%),
22
+ var(--bg);
23
+ color:var(--text);
24
+ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans TC","PingFang TC","Microsoft JhengHei",sans-serif;
25
+ line-height:1.6; min-height:100vh;
26
+ }
27
+ a{color:var(--gold)}
28
+ button{font:inherit;color:inherit;cursor:pointer}
29
+ .topbar{
30
+ position:sticky;top:0;z-index:50;backdrop-filter:blur(14px);
31
+ background:rgba(7,18,14,.8);border-bottom:1px solid var(--line);
32
+ }
33
+ .topbar-inner{max-width:1180px;margin:0 auto;padding:12px 20px;display:flex;gap:18px;align-items:center;flex-wrap:wrap}
34
+ .brand{display:flex;gap:10px;align-items:center;font-weight:800;letter-spacing:.02em}
35
+ .brand .logo{
36
+ width:30px;height:30px;border-radius:9px;display:grid;place-items:center;
37
+ background:linear-gradient(145deg,#f6c453,#b8860b);color:#1b1200;font-weight:900;
38
+ }
39
+ nav{display:flex;gap:4px;margin-left:auto;flex-wrap:wrap}
40
+ nav button{
41
+ border:1px solid transparent;background:transparent;padding:8px 14px;border-radius:999px;
42
+ color:var(--muted);font-weight:600;
43
+ }
44
+ nav button:hover{color:var(--text);background:rgba(255,255,255,.05)}
45
+ nav button.active{color:#1b1200;background:var(--gold);border-color:var(--gold)}
46
+ main{max-width:1180px;margin:0 auto;padding:28px 20px 60px}
47
+ .view{display:none}
48
+ .view.active{display:block;animation:fade .25s ease}
49
+ @keyframes fade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
50
+ .hero{padding:18px 0 6px}
51
+ .hero h1{font-size:clamp(30px,4vw,50px);line-height:1.15;margin:0 0 10px;letter-spacing:-.01em}
52
+ .hero h1 em{font-style:normal;color:var(--gold)}
53
+ .hero p{color:var(--muted);margin:0;max-width:760px;font-size:16px}
54
+ .chips{display:flex;gap:8px;flex-wrap:wrap;margin-top:18px}
55
+ .chip{
56
+ font-size:12.5px;color:#cfe4d9;background:rgba(255,255,255,.045);
57
+ border:1px solid var(--line);border-radius:999px;padding:5px 11px;
58
+ }
59
+ .chip b{color:var(--gold)}
60
+ .scoreboard{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin:22px 0 24px}
61
+ .score{
62
+ position:relative;overflow:hidden;border:1px solid var(--line);border-radius:18px;
63
+ padding:18px 18px 16px;background:linear-gradient(160deg,var(--panel2),var(--panel));
64
+ box-shadow:var(--shadow);
65
+ }
66
+ .score:before{content:"";position:absolute;inset:0 auto 0 0;width:4px;background:var(--accent,var(--gold))}
67
+ .score .id{font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:var(--muted)}
68
+ .score .name{font-size:22px;font-weight:800;margin:2px 0 0}
69
+ .score .style{font-size:13px;color:var(--accent,var(--gold))}
70
+ .score .bank{font-size:34px;font-weight:900;letter-spacing:-.02em;margin-top:10px;font-variant-numeric:tabular-nums}
71
+ .score .sub{font-size:13px;color:var(--muted)}
72
+ .big-delta{font-weight:800}
73
+ .pos{color:var(--green)}.neg{color:var(--red)}.neutral{color:var(--muted)}
74
+ .panel{
75
+ border:1px solid var(--line);border-radius:20px;background:linear-gradient(180deg,rgba(255,255,255,.025),rgba(0,0,0,.12));
76
+ box-shadow:var(--shadow);
77
+ }
78
+ .table-panel{padding:16px}
79
+ .round-toolbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:14px}
80
+ .icon-btn{
81
+ border:1px solid var(--line);background:rgba(255,255,255,.05);border-radius:10px;
82
+ padding:8px 13px;font-weight:700;color:var(--text);
83
+ }
84
+ .icon-btn:hover{background:rgba(255,255,255,.1)}
85
+ .icon-btn:disabled{opacity:.35;cursor:not-allowed}
86
+ .round-pills{display:flex;gap:6px;overflow-x:auto;padding:2px;max-width:100%;scrollbar-width:thin}
87
+ .pill{
88
+ flex:0 0 auto;min-width:38px;height:34px;border-radius:9px;border:1px solid var(--line);
89
+ background:rgba(255,255,255,.04);font-weight:800;font-size:13px;color:var(--muted);
90
+ display:grid;place-items:center;padding:0 8px;
91
+ }
92
+ .pill:hover{border-color:rgba(246,196,83,.5);color:var(--text)}
93
+ .pill.active{background:var(--gold);border-color:var(--gold);color:#1b1200}
94
+ .pill .dot{width:6px;height:6px;border-radius:50%;display:block;margin:0 auto 2px}
95
+ .pill.win .dot{background:var(--green)}
96
+ .pill.lose .dot{background:var(--red)}
97
+ .pill.mixed .dot{background:var(--amber)}
98
+ .pill.push .dot{background:#93a89d}
99
+ .round-label{margin-left:auto;color:var(--muted);font-size:13px;font-variant-numeric:tabular-nums}
100
+ .round-head{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin-bottom:10px}
101
+ .round-head h2{margin:0;font-size:20px}
102
+ .round-head .shoe{color:var(--muted);font-size:12.5px}
103
+ .dealer{
104
+ border:1px solid rgba(246,196,83,.25);border-radius:16px;padding:14px 16px;margin-bottom:14px;
105
+ background:radial-gradient(700px 200px at 20% 0%,rgba(246,196,83,.1),transparent 70%),rgba(0,0,0,.18);
106
+ display:flex;gap:16px;align-items:center;flex-wrap:wrap;
107
+ }
108
+ .dealer .label{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--gold);font-weight:800}
109
+ .dealer .total-badge{font-size:26px;font-weight:900;font-variant-numeric:tabular-nums}
110
+ .dealer .note{color:var(--muted);font-size:13px;flex:1;min-width:180px}
111
+ .players{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
112
+ .player{
113
+ border:1px solid var(--line);border-radius:16px;padding:15px;background:rgba(255,255,255,.025);
114
+ }
115
+ .player.win{border-color:rgba(57,217,138,.45);box-shadow:inset 0 0 0 1px rgba(57,217,138,.12)}
116
+ .player.lose{border-color:rgba(255,107,107,.35)}
117
+ .player.blackjack{border-color:rgba(246,196,83,.6)}
118
+ .p-head{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;flex-wrap:wrap}
119
+ .p-head h3{margin:0;font-size:18px}
120
+ .p-head .style{font-size:12.5px;color:var(--accent,var(--gold))}
121
+ .p-head .bet{
122
+ font-size:12.5px;color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:4px 10px;
123
+ background:rgba(0,0,0,.2);white-space:nowrap;
124
+ }
125
+ .p-head .bet b{color:var(--gold);font-size:15px}
126
+ .hand{display:flex;gap:14px;align-items:center;margin:12px 0 8px;flex-wrap:wrap}
127
+ .cards{display:flex;gap:6px;flex-wrap:wrap}
128
+ .card{
129
+ width:52px;height:72px;border-radius:8px;background:#f7f7f2;color:#141414;
130
+ display:flex;flex-direction:column;align-items:center;justify-content:center;
131
+ font-weight:900;box-shadow:0 6px 14px rgba(0,0,0,.35);border:1px solid rgba(0,0,0,.15);
132
+ line-height:1;user-select:none;
133
+ }
134
+ .card .r{font-size:21px}
135
+ .card .s{font-size:17px;margin-top:2px}
136
+ .card.red{color:#c0182b}
137
+ .card.down{background:repeating-linear-gradient(45deg,#123c6b,#123c6b 5px,#0b2a4d 5px,#0b2a4d 10px);color:transparent;border-color:#0a2340}
138
+ .total{
139
+ font-size:30px;font-weight:900;font-variant-numeric:tabular-nums;display:flex;align-items:baseline;gap:6px;
140
+ }
141
+ .total small{font-size:11px;font-weight:700;color:var(--muted);border:1px solid var(--line);border-radius:5px;padding:1px 5px}
142
+ .result{
143
+ display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:800;
144
+ padding:3px 10px;border-radius:999px;border:1px solid var(--line);
145
+ }
146
+ .result.win{color:var(--green);border-color:rgba(57,217,138,.4);background:rgba(57,217,138,.1)}
147
+ .result.lose{color:var(--red);border-color:rgba(255,107,107,.4);background:rgba(255,107,107,.08)}
148
+ .result.push{color:#b9c9c0;background:rgba(255,255,255,.05)}
149
+ .result.blackjack{color:var(--gold);border-color:rgba(246,196,83,.5);background:rgba(246,196,83,.1)}
150
+ .bet-line{display:flex;gap:8px;align-items:center;flex-wrap:wrap;font-size:12.5px;color:var(--muted);margin:6px 0 2px}
151
+ .bet-line .tag{border:1px solid var(--line);border-radius:6px;padding:1px 7px;color:#cfe4d9}
152
+ details{margin-top:8px}
153
+ summary{cursor:pointer;color:var(--muted);font-size:12.5px;font-weight:700}
154
+ summary:hover{color:var(--text)}
155
+ details .body{padding:8px 0 0;color:var(--muted);font-size:13px}
156
+ .steps{list-style:none;margin:12px 0 0;padding:0;display:flex;flex-direction:column;gap:8px}
157
+ .step{
158
+ border:1px solid var(--line);border-radius:11px;padding:9px 11px;background:rgba(0,0,0,.2);
159
+ }
160
+ .step.enter{animation:slide .3s ease}
161
+ @keyframes slide{from{opacity:0;transform:translateX(-6px)}to{opacity:1;transform:none}}
162
+ .step-head{display:flex;gap:8px;align-items:center;flex-wrap:wrap;font-size:12.5px}
163
+ .step-no{color:var(--muted);font-weight:800;font-variant-numeric:tabular-nums}
164
+ .mini{font-weight:800;font-variant-numeric:tabular-nums}
165
+ .action{
166
+ font-weight:900;font-size:12px;letter-spacing:.04em;padding:2px 9px;border-radius:6px;color:#0b0f0d;
167
+ }
168
+ .action.hit{background:var(--hit)}.action.stand{background:var(--stand)}.action.double{background:var(--double)}
169
+ .action.unknown{background:#7a8a81}
170
+ .step .meta{margin-left:auto;color:var(--muted);font-size:11.5px;display:flex;gap:8px;flex-wrap:wrap}
171
+ .probs{display:grid;grid-template-columns:64px 1fr 46px;gap:6px;align-items:center;margin-top:7px;font-size:11.5px;color:var(--muted)}
172
+ .bar{height:7px;border-radius:4px;background:rgba(255,255,255,.07);overflow:hidden}
173
+ .bar>i{display:block;height:100%;border-radius:4px;background:var(--c,var(--gold));width:0;transition:width .5s ease}
174
+ .probs .pct{text-align:right;font-variant-numeric:tabular-nums;color:#d6e7de}
175
+ .step-foot{display:flex;gap:10px;flex-wrap:wrap;color:var(--muted);font-size:11.5px;margin-top:7px}
176
+ .step-foot b{color:#d6e7de}
177
+ .net{margin-top:11px;padding-top:10px;border-top:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;font-size:13px}
178
+ .net .amt{font-size:19px;font-weight:900;font-variant-numeric:tabular-nums}
179
+ .events{margin-top:14px;border-top:1px dashed var(--line);padding-top:10px;color:var(--muted);font-size:12.5px}
180
+ .events ul{margin:6px 0 0;padding-left:18px}
181
+ .events li{margin:2px 0}
182
+ .arena-note{margin-top:14px;color:var(--muted);font-size:12px}
183
+ .section-title{font-size:22px;margin:26px 0 12px}
184
+ .stats-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
185
+ .stat-card{border:1px solid var(--line);border-radius:16px;padding:16px;background:rgba(255,255,255,.025)}
186
+ .stat-card h3{margin:0 0 4px;font-size:17px}
187
+ .stat-card .style{font-size:12.5px;color:var(--accent,var(--gold))}
188
+ .kv{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin-top:12px}
189
+ .kv .k{font-size:11.5px;color:var(--muted)}
190
+ .kv .v{font-size:19px;font-weight:800;font-variant-numeric:tabular-nums}
191
+ .dist{margin-top:12px}
192
+ .dist .row{display:grid;grid-template-columns:66px 1fr 44px;gap:7px;align-items:center;font-size:12px;margin:5px 0;color:var(--muted)}
193
+ .mini-title{font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin-top:14px}
194
+ .chart-wrap{margin-top:14px;border:1px solid var(--line);border-radius:16px;padding:14px;background:rgba(0,0,0,.18)}
195
+ .chart-legend{display:flex;gap:14px;font-size:12px;color:var(--muted);flex-wrap:wrap;margin-bottom:6px}
196
+ .chart-legend i{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:5px}
197
+ .h2h{margin-top:14px;border:1px solid var(--line);border-radius:16px;padding:16px;background:rgba(255,255,255,.025)}
198
+ .h2h .big{font-size:30px;font-weight:900;color:var(--gold)}
199
+ .code-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}
200
+ .code-tabs button{border:1px solid var(--line);background:rgba(255,255,255,.04);border-radius:8px;padding:6px 12px;font-size:12.5px;color:var(--muted)}
201
+ .code-tabs button.active{background:var(--gold);color:#1b1200;border-color:var(--gold);font-weight:800}
202
+ .code-bar{display:flex;gap:10px;align-items:center;margin-bottom:8px;color:var(--muted);font-size:12.5px;flex-wrap:wrap}
203
+ pre.code{
204
+ margin:0;border:1px solid var(--line);border-radius:14px;background:#04100c;padding:16px;
205
+ overflow:auto;max-height:70vh;font-size:12.5px;line-height:1.55;
206
+ }
207
+ pre.code code{font-family:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,monospace}
208
+ .data-box{border:1px solid var(--line);border-radius:16px;padding:16px;background:rgba(255,255,255,.025);margin-bottom:14px}
209
+ .data-box h3{margin:0 0 8px;font-size:17px}
210
+ .data-box p{color:var(--muted);margin:6px 0;font-size:14px}
211
+ .btn{
212
+ display:inline-flex;align-items:center;gap:7px;border:1px solid rgba(246,196,83,.45);
213
+ background:rgba(246,196,83,.1);color:var(--gold);padding:7px 13px;border-radius:9px;
214
+ text-decoration:none;font-weight:700;font-size:13px;
215
+ }
216
+ .btn:hover{background:rgba(246,196,83,.18)}
217
+ .btn.ghost{border-color:var(--line);background:rgba(255,255,255,.04);color:var(--text)}
218
+ footer{border-top:1px solid var(--line);margin-top:30px;padding:22px 20px 40px;color:var(--muted);font-size:12.5px;text-align:center}
219
+ .loading{padding:40px;text-align:center;color:var(--muted)}
220
+ .err{border:1px solid rgba(255,107,107,.4);background:rgba(255,107,107,.08);padding:16px;border-radius:12px;color:#ffd4d4}
221
+ @media (max-width:760px){
222
+ .scoreboard,.stats-grid,.players{grid-template-columns:1fr}
223
+ .card{width:44px;height:62px}
224
+ .card .r{font-size:18px}
225
+ .total{font-size:25px}
226
+ .round-label{margin-left:0}
227
+ }
228
+ </style>
229
+ </head>
230
+ <body>
231
+ <header class="topbar">
232
+ <div class="topbar-inner">
233
+ <div class="brand"><span class="logo">21</span><span>d1 × d1 對戰實驗室</span></div>
234
+ <nav>
235
+ <button data-view="battle" class="active">對戰</button>
236
+ <button data-view="stats">統計</button>
237
+ <button data-view="code">整理程式</button>
238
+ <button data-view="data">原始資料</button>
239
+ </nav>
240
+ </div>
241
+ </header>
242
+
243
+ <main>
244
+ <div id="app"><div class="loading">載入對戰資料中…</div></div>
245
+ </main>
246
+
247
+ <footer>
248
+ <div>由 llama.cpp 上的兩個 <b>LiquidAI d1-3B</b> 決策模型自動產生 · 頁面由 build_site.py 靜態編譯</div>
249
+ <div style="margin-top:6px">Hosted on Hugging Face Static Space · <a href="https://huggingface.co/spaces/HelloSun/d1demo">HelloSun/d1demo</a></div>
250
+ </footer>
251
+
252
+ <script>
253
+ window.BATTLE_DATA = {"meta": {"title": "d1 × d1 21 點對戰實錄", "subtitle": "兩個 LiquidAI d1-3B 決策模型,透過 llama.cpp 在同一個牌桌上對打", "created_at": "2026-10-21T11:09:42+02:00", "seed": 20261021, "model": "LiquidAI/d1-3B-GGUF", "quant": "Q8_0", "llama_build": "b11786 (0.7.0-dev)", "rules": {"decks": 6, "dealer": "莊家軟 17 停牌 (S17)", "blackjack_pays": "3:2", "double": "僅限硬 9–11 或軟 13–18 時加倍,加倍後只補一張", "split": "本局不開放分牌", "insurance": "不開放保險", "reshuffle": "剩餘牌數低於 25% 時重洗", "start_bankroll": 1000}, "start_bankroll": 1000, "agents": [{"id": "a", "name": "阿保", "style": "保守算牌派", "description": "以保住本金為第一優先,只有在牌面明顯有利時才加注,遇到風險寧可停牌。", "url": "http://127.0.0.1:8081", "final_bankroll": 940}, {"id": "b", "name": "阿衝", "style": "激進壓迫派", "description": "相信進攻是最好的防守,喜歡加倍與大注,願意為了期望值承擔波動。", "url": "http://127.0.0.1:8082", "final_bankroll": 900}], "rounds_played": 12, "duration_sec": 161.39, "inference": {"a": {"calls": 27, "avg_latency_ms": 2589}, "b": {"calls": 27, "avg_latency_ms": 3387}}}, "rounds": [{"n": 1, "shoe_before": {"decks": 6, "remaining_cards": 306, "penetration_pct": 2, "cards_left_by_rank": {"A": 23, "2": 24, "3": 23, "4": 24, "5": 24, "6": 23, "7": 23, "8": 24, "9": 24, "10": 24, "J": 24, "Q": 23, "K": 23}, "hi_lo_running_count": -1, "true_count": -0.17}, "dealer_up": "A♣", "dealer_hole": "K♣", "dealer_revealed": true, "dealer": {"cards": ["A♣", "K♣"], "total": 21, "soft": true, "blackjack": true, "bust": false}, "events": ["莊家明牌為 A 或 10 點,進行暗牌確認", "莊家直接拿到 Blackjack,本局立即結束"], "players": [{"cards": ["7♥", "3♣"], "bet": 10, "doubled": false, "blackjack": false, "bust": false, "total": 10, "soft": false, "result": "lose", "net": -10, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.8015038103456409, "standard": 0.13503222188459674, "max": 0.06346396776976232}, "confidence": 0.7022557155184613}, "bet": 10, "confidence": {"type": "score", "score": 1.2743893137525868, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.177727990768028, "1": 0.48287248232676894, "2": 0.22668174928979082, "3": 0.11271777761541213}, "confidence": 0.3701547047113569}, "latency_ms": 2619}, "bankroll_after": 990, "id": "a", "name": "阿保", "style": "保守算牌派"}, {"cards": ["Q♦", "6♠"], "bet": 50, "doubled": false, "blackjack": false, "bust": false, "total": 16, "soft": false, "result": "lose", "net": -50, "bet_decision": {"decision": {"type": "choice", "choice": "max", "probabilities": {"min": 0.22510969265696443, "standard": 0.3522666589432454, "max": 0.42262364839979016}, "confidence": 0.13393547259968525}, "bet": 50, "confidence": {"type": "score", "score": 1.4739367761682, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.16212488082911708, "1": 0.364637000115999, "2": 0.3104145811124513, "3": 0.16282353794243282}, "confidence": 0.201813462173566}, "latency_ms": 2899}, "bankroll_after": 950, "id": "b", "name": "阿衝", "style": "激進壓迫派"}], "shoe_after": {"decks": 6, "remaining_cards": 306, "penetration_pct": 2, "cards_left_by_rank": {"A": 23, "2": 24, "3": 23, "4": 24, "5": 24, "6": 23, "7": 23, "8": 24, "9": 24, "10": 24, "J": 24, "Q": 23, "K": 23}, "hi_lo_running_count": -1, "true_count": -0.17}}, {"n": 2, "shoe_before": {"decks": 6, "remaining_cards": 300, "penetration_pct": 4, "cards_left_by_rank": {"A": 22, "2": 24, "3": 22, "4": 24, "5": 23, "6": 23, "7": 23, "8": 23, "9": 24, "10": 23, "J": 24, "Q": 23, "K": 22}, "hi_lo_running_count": -2, "true_count": -0.35}, "dealer_up": "8♣", "dealer_hole": "A♠", "dealer_revealed": true, "dealer": {"cards": ["8♣", "A♠"], "total": 19, "soft": true, "blackjack": false, "bust": false}, "events": ["莊家最終 19 點"], "players": [{"cards": ["5♠", "3♥", "9♠"], "bet": 10, "doubled": false, "total": 17, "soft": false, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["5♠", "3♥"], "total": 8, "soft": false, "dealer_up": "8♣", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.7903426468089709, "stand": 0.2096573531910292}, "confidence": 0.5806852936179419}, "risk": {"type": "score", "score": 2.1705379533840112, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.04566941566120645, "1": 0.12003841453217244, "2": 0.4523769705680244, "3": 0.38191519923859674}, "confidence": 0.406707554906818}, "latency_ms": 3068, "drawn": "9♠", "total_after": 17}, {"type": "action", "cards": ["5♠", "3♥", "9♠"], "total": 17, "soft": false, "dealer_up": "8♣", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "stand", "probabilities": {"hit": 0.34748348200597706, "stand": 0.6525165179940229}, "confidence": 0.30503303598804576}, "risk": {"type": "score", "score": 1.3057660239341171, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.1680177506175406, "1": 0.4059181822464935, "2": 0.3783443597202743, "3": 0.04771970741569162}, "confidence": 0.3581984748308018}, "latency_ms": 2844, "drawn": null, "total_after": 17}], "player_id": "a", "result": "lose", "net": -10, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.7725987262844797, "standard": 0.1413424593652034, "max": 0.08605881435031675}, "confidence": 0.6588980894267195}, "bet": 10, "confidence": {"type": "score", "score": 1.3006956637914562, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.16014576713262269, "1": 0.4790571948548963, "2": 0.2607526451008829, "3": 0.100044392911598}, "confidence": 0.37901280194329834}, "latency_ms": 2449}, "bankroll_after": 980, "id": "a", "name": "阿保", "style": "保守算牌派"}, {"cards": ["K♦", "10♣"], "bet": 50, "doubled": false, "total": 20, "soft": false, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["K♦", "10♣"], "total": 20, "soft": false, "dealer_up": "8♣", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "stand", "probabilities": {"hit": 0.29523031516652504, "stand": 0.704769684833475}, "confidence": 0.40953936966695004}, "risk": {"type": "score", "score": 1.1019380316438792, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.270273972567917, "1": 0.3948283551800062, "2": 0.2975833402923578, "3": 0.03731433195971913}, "confidence": 0.3575140232202869}, "latency_ms": 4289, "drawn": null, "total_after": 20}], "player_id": "b", "result": "win", "net": 50, "bet_decision": {"decision": {"type": "choice", "choice": "max", "probabilities": {"min": 0.19967317203877727, "standard": 0.3811921558998119, "max": 0.4191346720614108}, "confidence": 0.1287020080921162}, "bet": 50, "confidence": {"type": "score", "score": 1.5668315252239973, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.14124839285420554, "1": 0.3269367548074964, "2": 0.3555497865983932, "3": 0.17626506573990477}, "confidence": 0.21430139374418777}, "latency_ms": 2759}, "bankroll_after": 1000, "id": "b", "name": "阿衝", "style": "激進壓迫派"}], "shoe_after": {"decks": 6, "remaining_cards": 299, "penetration_pct": 4, "cards_left_by_rank": {"A": 22, "2": 24, "3": 22, "4": 24, "5": 23, "6": 23, "7": 23, "8": 23, "9": 23, "10": 23, "J": 24, "Q": 23, "K": 22}, "hi_lo_running_count": -2, "true_count": -0.35}}, {"n": 3, "shoe_before": {"decks": 6, "remaining_cards": 293, "penetration_pct": 6, "cards_left_by_rank": {"A": 22, "2": 22, "3": 21, "4": 24, "5": 23, "6": 22, "7": 23, "8": 23, "9": 23, "10": 21, "J": 24, "Q": 23, "K": 22}, "hi_lo_running_count": 0, "true_count": 0.0}, "dealer_up": "3♦", "dealer_hole": "2♠", "dealer_revealed": true, "dealer": {"cards": ["3♦", "2♠", "2♠", "3♦", "A♣"], "total": 21, "soft": true, "blackjack": false, "bust": false}, "events": ["莊家補牌 2♠ → 7 點", "莊家補牌 3♦ → 10 點", "莊家補牌 A♣ → 21 點", "莊家最終 21 點"], "players": [{"cards": ["10♠", "2♦", "8♣"], "bet": 10, "doubled": false, "total": 20, "soft": false, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["10♠", "2♦"], "total": 12, "soft": false, "dealer_up": "3♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.564536710896258, "stand": 0.43546328910374205}, "confidence": 0.12907342179251602}, "risk": {"type": "score", "score": 1.5462336728175854, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.11070758630941745, "1": 0.3353281508308657, "2": 0.45098726659243077, "3": 0.10297699626728603}, "confidence": 0.34027968028301336}, "latency_ms": 2619, "drawn": "8♣", "total_after": 20}, {"type": "action", "cards": ["10♠", "2♦", "8♣"], "total": 20, "soft": false, "dealer_up": "3♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "stand", "probabilities": {"hit": 0.23511610687855905, "stand": 0.764883893121441}, "confidence": 0.529767786242882}, "risk": {"type": "score", "score": 1.1724233566485125, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.2259679727325013, "1": 0.41366566628608514, "2": 0.32234139258181355, "3": 0.038024968399600075}, "confidence": 0.375640697886485}, "latency_ms": 3056, "drawn": null, "total_after": 20}], "player_id": "a", "result": "lose", "net": -10, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.7433957153910495, "standard": 0.16651591247876724, "max": 0.09008837213018317}, "confidence": 0.6150935730865743}, "bet": 10, "confidence": {"type": "score", "score": 1.2496433080778582, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.17187017754479506, "1": 0.48519722500579504, "2": 0.26435170927616675, "3": 0.07858088817324316}, "confidence": 0.40661633683255194}, "latency_ms": 2226}, "bankroll_after": 970, "id": "a", "name": "阿保", "style": "保守算牌派"}, {"cards": ["6♦", "10♠", "4♠"], "bet": 50, "doubled": false, "total": 20, "soft": false, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["6♦", "10♠"], "total": 16, "soft": false, "dealer_up": "3♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.6418322181399073, "stand": 0.3581677818600927}, "confidence": 0.28366443627981464}, "risk": {"type": "score", "score": 1.4828880574132604, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.12154439867797127, "1": 0.3522754018654166, "2": 0.44792794282199283, "3": 0.07825225663461932}, "confidence": 0.3263835441440215}, "latency_ms": 3581, "drawn": "4♠", "total_after": 20}, {"type": "action", "cards": ["6♦", "10♠", "4♠"], "total": 20, "soft": false, "dealer_up": "3♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "stand", "probabilities": {"hit": 0.3440449520657691, "stand": 0.6559550479342309}, "confidence": 0.31191009586846175}, "risk": {"type": "score", "score": 1.2733321008675031, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.19239615322374934, "1": 0.3916707506879337, "2": 0.36613793808538125, "3": 0.049795158002935644}, "confidence": 0.3418755926849981}, "latency_ms": 3581, "drawn": null, "total_after": 20}], "player_id": "b", "result": "lose", "net": -50, "bet_decision": {"decision": {"type": "choice", "choice": "max", "probabilities": {"min": 0.17446344549537493, "standard": 0.3824838673364471, "max": 0.44305268716817786}, "confidence": 0.1645790307522668}, "bet": 50, "confidence": {"type": "score", "score": 1.6022477730280071, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.12490168872094283, "1": 0.32284211542202057, "2": 0.3773629299651229, "3": 0.17489326589191365}, "confidence": 0.2524612412441801}, "latency_ms": 2818}, "bankroll_after": 950, "id": "b", "name": "阿衝", "style": "激進壓迫派"}], "shoe_after": {"decks": 6, "remaining_cards": 288, "penetration_pct": 8, "cards_left_by_rank": {"A": 21, "2": 21, "3": 20, "4": 23, "5": 23, "6": 22, "7": 23, "8": 22, "9": 23, "10": 21, "J": 24, "Q": 23, "K": 22}, "hi_lo_running_count": 2, "true_count": 0.36}}, {"n": 4, "shoe_before": {"decks": 6, "remaining_cards": 282, "penetration_pct": 10, "cards_left_by_rank": {"A": 20, "2": 21, "3": 19, "4": 23, "5": 23, "6": 22, "7": 22, "8": 21, "9": 22, "10": 21, "J": 24, "Q": 23, "K": 21}, "hi_lo_running_count": 1, "true_count": 0.18}, "dealer_up": "7♠", "dealer_hole": "K♥", "dealer_revealed": true, "dealer": {"cards": ["7♠", "K♥"], "total": 17, "soft": false, "blackjack": false, "bust": false}, "events": ["莊家最終 17 點"], "players": [{"cards": ["8♥", "A♦"], "bet": 10, "doubled": false, "total": 19, "soft": true, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["8♥", "A♦"], "total": 19, "soft": true, "dealer_up": "7♠", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "stand", "probabilities": {"hit": 0.2797133858793715, "stand": 0.7202866141206284}, "confidence": 0.4405732282412569}, "risk": {"type": "score", "score": 1.0755577590046503, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.2653823049012448, "1": 0.42404250943999217, "2": 0.2802103074116306, "3": 0.030364878247132344}, "confidence": 0.3936776311928599}, "latency_ms": 3126, "drawn": null, "total_after": 19}], "player_id": "a", "result": "win", "net": 10, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.7249067710505043, "standard": 0.17984278558883143, "max": 0.09525044336066422}, "confidence": 0.5873601565757564}, "bet": 10, "confidence": {"type": "score", "score": 1.1590048973716087, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.21064627872594902, "1": 0.4865225308156261, "2": 0.23601120481929241, "3": 0.06681998563913258}, "confidence": 0.4197025451764934}, "latency_ms": 2265}, "bankroll_after": 980, "id": "a", "name": "阿保", "style": "保守算牌派"}, {"cards": ["9♠", "3♦", "K♥"], "bet": 50, "doubled": false, "total": 22, "soft": false, "bust": true, "blackjack": false, "steps": [{"type": "action", "cards": ["9♠", "3♦"], "total": 12, "soft": false, "dealer_up": "7♠", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.7049549850357781, "stand": 0.295045014964222}, "confidence": 0.4099099700715563}, "risk": {"type": "score", "score": 1.5146466061989001, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.12572631444400376, "1": 0.33533827834479635, "2": 0.43749789377949644, "3": 0.10143751343170365}, "confidence": 0.3117715793354925}, "latency_ms": 3834, "drawn": "K♥", "total_after": 22}], "player_id": "b", "result": "lose", "net": -50, "bet_decision": {"decision": {"type": "choice", "choice": "max", "probabilities": {"min": 0.19672050531851887, "standard": 0.3845980993465475, "max": 0.41868139533493354}, "confidence": 0.1280220930024003}, "bet": 50, "confidence": {"type": "score", "score": 1.5623486708262693, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.13876933530621044, "1": 0.3289561938301259, "2": 0.36343093559484774, "3": 0.1688435352688159}, "confidence": 0.22466160028863735}, "latency_ms": 3123}, "bankroll_after": 900, "id": "b", "name": "阿衝", "style": "激進壓迫派"}], "shoe_after": {"decks": 6, "remaining_cards": 281, "penetration_pct": 10, "cards_left_by_rank": {"A": 20, "2": 21, "3": 19, "4": 23, "5": 23, "6": 22, "7": 22, "8": 21, "9": 22, "10": 21, "J": 24, "Q": 23, "K": 20}, "hi_lo_running_count": 0, "true_count": 0.0}}, {"n": 5, "shoe_before": {"decks": 6, "remaining_cards": 275, "penetration_pct": 12, "cards_left_by_rank": {"A": 19, "2": 20, "3": 19, "4": 23, "5": 23, "6": 21, "7": 21, "8": 21, "9": 22, "10": 20, "J": 23, "Q": 23, "K": 20}, "hi_lo_running_count": -1, "true_count": -0.19}, "dealer_up": "A♣", "dealer_hole": "J♠", "dealer_revealed": true, "dealer": {"cards": ["A♣", "J♠"], "total": 21, "soft": true, "blackjack": true, "bust": false}, "events": ["莊家明牌為 A 或 10 點,進行暗牌確認", "莊家直接拿到 Blackjack,本局立即結束"], "players": [{"cards": ["10♦", "2♦"], "bet": 10, "doubled": false, "blackjack": false, "bust": false, "total": 12, "soft": false, "result": "lose", "net": -10, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.7562291100284627, "standard": 0.16685391773152444, "max": 0.07691697224001287}, "confidence": 0.634343665042694}, "bet": 10, "confidence": {"type": "score", "score": 1.2320991965875188, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.19676299225478636, "1": 0.46805537175271755, "2": 0.24150108314268687, "3": 0.09368055284980918}, "confidence": 0.37437481890290836}, "latency_ms": 2277}, "bankroll_after": 970, "id": "a", "name": "阿保", "style": "保守算牌派"}, {"cards": ["7♠", "6♦"], "bet": 50, "doubled": false, "blackjack": false, "bust": false, "total": 13, "soft": false, "result": "lose", "net": -50, "bet_decision": {"decision": {"type": "choice", "choice": "max", "probabilities": {"min": 0.20601333171277045, "standard": 0.34842502176841295, "max": 0.4455616465188165}, "confidence": 0.16834246977822478}, "bet": 50, "confidence": {"type": "score", "score": 1.564656149705211, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.14718873328060164, "1": 0.32229188630260436, "2": 0.34919387784777556, "3": 0.1813255025690185}, "confidence": 0.2020051445671739}, "latency_ms": 3202}, "bankroll_after": 850, "id": "b", "name": "阿衝", "style": "激進壓迫派"}], "shoe_after": {"decks": 6, "remaining_cards": 275, "penetration_pct": 12, "cards_left_by_rank": {"A": 19, "2": 20, "3": 19, "4": 23, "5": 23, "6": 21, "7": 21, "8": 21, "9": 22, "10": 20, "J": 23, "Q": 23, "K": 20}, "hi_lo_running_count": -1, "true_count": -0.19}}, {"n": 6, "shoe_before": {"decks": 6, "remaining_cards": 269, "penetration_pct": 14, "cards_left_by_rank": {"A": 17, "2": 20, "3": 18, "4": 23, "5": 22, "6": 21, "7": 21, "8": 21, "9": 21, "10": 20, "J": 23, "Q": 23, "K": 19}, "hi_lo_running_count": -2, "true_count": -0.39}, "dealer_up": "9♦", "dealer_hole": "5♣", "dealer_revealed": true, "dealer": {"cards": ["9♦", "5♣", "8♣"], "total": 22, "soft": false, "blackjack": false, "bust": true}, "events": ["莊家補牌 8♣ → 22 點", "莊家最終 22 點,爆牌"], "players": [{"cards": ["3♠", "A♦", "K♣", "7♠"], "bet": 10, "doubled": false, "total": 21, "soft": false, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["3♠", "A♦"], "total": 14, "soft": true, "dealer_up": "9♦", "allowed": ["hit", "stand", "double"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.408721663227952, "stand": 0.29303182266998506, "double": 0.29824651410206277}, "confidence": 0.11308249484192802}, "risk": {"type": "score", "score": 1.5610409422586708, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.10553867166975685, "1": 0.32969436958342446, "2": 0.46295430356520967, "3": 0.10181265518160906}, "confidence": 0.3574156318954528}, "latency_ms": 3156, "drawn": "K♣", "total_after": 14}, {"type": "action", "cards": ["3♠", "A♦", "K♣"], "total": 14, "soft": false, "dealer_up": "9♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.5122837140095126, "stand": 0.48771628599048733}, "confidence": 0.024567428019025117}, "risk": {"type": "score", "score": 1.5881935559142273, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.1232644149177596, "1": 0.3080900808526087, "2": 0.4258330376272768, "3": 0.14281246660235497}, "confidence": 0.30256862270951723}, "latency_ms": 2963, "drawn": "7♠", "total_after": 21}], "player_id": "a", "result": "win", "net": 10, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.7443943535681774, "standard": 0.1724434598311167, "max": 0.08316218660070591}, "confidence": 0.6165915303522661}, "bet": 10, "confidence": {"type": "score", "score": 1.2134591868984879, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.1889940408450792, "1": 0.48665170909181565, "2": 0.24625527238264336, "3": 0.07809897768046187}, "confidence": 0.40855273141135373}, "latency_ms": 2442}, "bankroll_after": 980, "id": "a", "name": "阿保", "style": "保守算牌派"}, {"cards": ["A♠", "K♣"], "bet": 50, "doubled": false, "total": 21, "soft": true, "bust": false, "blackjack": true, "steps": [], "player_id": "b", "result": "blackjack", "net": 75, "bet_decision": {"decision": {"type": "choice", "choice": "max", "probabilities": {"min": 0.1771453995341398, "standard": 0.3923319399455236, "max": 0.43052266052033666}, "confidence": 0.145783990780505}, "bet": 50, "confidence": {"type": "score", "score": 1.5896896540311678, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.13640040426554614, "1": 0.3189928849419576, "2": 0.36312336328827843, "3": 0.18148334750421774}, "confidence": 0.22672295902273243}, "latency_ms": 3056}, "bankroll_after": 925, "id": "b", "name": "阿衝", "style": "激進壓迫派"}], "shoe_after": {"decks": 6, "remaining_cards": 266, "penetration_pct": 15, "cards_left_by_rank": {"A": 17, "2": 20, "3": 18, "4": 23, "5": 22, "6": 21, "7": 20, "8": 20, "9": 21, "10": 20, "J": 23, "Q": 23, "K": 18}, "hi_lo_running_count": -3, "true_count": -0.59}}, {"n": 7, "shoe_before": {"decks": 6, "remaining_cards": 260, "penetration_pct": 17, "cards_left_by_rank": {"A": 16, "2": 19, "3": 17, "4": 23, "5": 22, "6": 21, "7": 20, "8": 19, "9": 21, "10": 18, "J": 23, "Q": 23, "K": 18}, "hi_lo_running_count": -4, "true_count": -0.8}, "dealer_up": "8♠", "dealer_hole": "3♦", "dealer_revealed": true, "dealer": {"cards": ["8♠", "3♦", "7♦"], "total": 18, "soft": false, "blackjack": false, "bust": false}, "events": ["莊家補牌 7♦ → 18 點", "莊家最終 18 點"], "players": [{"cards": ["10♣", "2♣", "K♠"], "bet": 10, "doubled": false, "total": 22, "soft": false, "bust": true, "blackjack": false, "steps": [{"type": "action", "cards": ["10♣", "2♣"], "total": 12, "soft": false, "dealer_up": "8♠", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.5500152300334371, "stand": 0.44998476996656295}, "confidence": 0.10003046006687422}, "risk": {"type": "score", "score": 1.5209343777308968, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.1166634305190376, "1": 0.3360687076482218, "2": 0.4569379154155469, "3": 0.0903299464171938}, "confidence": 0.34027448489650913}, "latency_ms": 3071, "drawn": "K♠", "total_after": 22}], "player_id": "a", "result": "lose", "net": -10, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.69219418752702, "standard": 0.21671663493691912, "max": 0.09108917753606088}, "confidence": 0.5382912812905299}, "bet": 10, "confidence": {"type": "score", "score": 1.2634052808341587, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.17743488347704425, "1": 0.46766459898284457, "2": 0.2689608707690193, "3": 0.08593964677109182}, "confidence": 0.38172495221175273}, "latency_ms": 2427}, "bankroll_after": 970, "id": "a", "name": "阿保", "style": "保守算牌派"}, {"cards": ["10♠", "A♠"], "bet": 50, "doubled": false, "total": 21, "soft": true, "bust": false, "blackjack": true, "steps": [], "player_id": "b", "result": "blackjack", "net": 75, "bet_decision": {"decision": {"type": "choice", "choice": "max", "probabilities": {"min": 0.17415716770498335, "standard": 0.3849795123905866, "max": 0.44086331990443}, "confidence": 0.161294979856645}, "bet": 50, "confidence": {"type": "score", "score": 1.6670431235423304, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.12673888933460348, "1": 0.2859844451294625, "2": 0.38077131819493437, "3": 0.20650534734099973}, "confidence": 0.25403242886033084}, "latency_ms": 2975}, "bankroll_after": 1000, "id": "b", "name": "阿衝", "style": "激進壓迫派"}], "shoe_after": {"decks": 6, "remaining_cards": 258, "penetration_pct": 17, "cards_left_by_rank": {"A": 16, "2": 19, "3": 17, "4": 23, "5": 22, "6": 21, "7": 19, "8": 19, "9": 21, "10": 18, "J": 23, "Q": 23, "K": 17}, "hi_lo_running_count": -5, "true_count": -1.01}}, {"n": 8, "shoe_before": {"decks": 6, "remaining_cards": 252, "penetration_pct": 19, "cards_left_by_rank": {"A": 16, "2": 19, "3": 16, "4": 23, "5": 21, "6": 21, "7": 19, "8": 19, "9": 19, "10": 17, "J": 23, "Q": 22, "K": 17}, "hi_lo_running_count": -5, "true_count": -1.03}, "dealer_up": "10♦", "dealer_hole": "9♦", "dealer_revealed": true, "dealer": {"cards": ["10♦", "9♦"], "total": 19, "soft": false, "blackjack": false, "bust": false}, "events": ["莊家明牌為 A 或 10 點,進行暗牌確認", "確認結果:莊家沒有 Blackjack", "莊家最終 19 點"], "players": [{"cards": ["3♥", "Q♦"], "bet": 10, "doubled": false, "total": 13, "soft": false, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["3♥", "Q♦"], "total": 13, "soft": false, "dealer_up": "10♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "stand", "probabilities": {"hit": 0.39634239573570534, "stand": 0.6036576042642946}, "confidence": 0.2073152085285892}, "risk": {"type": "score", "score": 1.3805666512347543, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.16969624145522536, "1": 0.3621346556920123, "2": 0.3860753130155444, "3": 0.08209378983721773}, "confidence": 0.2163790715603192}, "latency_ms": 2496, "drawn": null, "total_after": 13}], "player_id": "a", "result": "lose", "net": -10, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.7656214237065283, "standard": 0.14200820159033867, "max": 0.09237037470313315}, "confidence": 0.6484321355597924}, "bet": 10, "confidence": {"type": "score", "score": 1.2299018689518944, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.19870975918080533, "1": 0.4610254792488059, "2": 0.2519178950080773, "3": 0.08834686656231128}, "confidence": 0.3726786126864948}, "latency_ms": 2564}, "bankroll_after": 960, "id": "a", "name": "阿保", "style": "保守算牌派"}, {"cards": ["5♦", "9♣", "3♠"], "bet": 50, "doubled": false, "total": 17, "soft": false, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["5♦", "9♣"], "total": 14, "soft": false, "dealer_up": "10♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.6363603957200638, "stand": 0.3636396042799362}, "confidence": 0.2727207914401275}, "risk": {"type": "score", "score": 1.621485575327213, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.12499958056879856, "1": 0.28623851581568904, "2": 0.4310386513350132, "3": 0.15772325228049922}, "confidence": 0.3060390707662146}, "latency_ms": 4541, "drawn": "3♠", "total_after": 17}, {"type": "action", "cards": ["5♦", "9♣", "3♠"], "total": 17, "soft": false, "dealer_up": "10♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "stand", "probabilities": {"hit": 0.41681588405691944, "stand": 0.5831841159430805}, "confidence": 0.166368231886161}, "risk": {"type": "score", "score": 1.3499666013886922, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.19919145911352995, "1": 0.34421202407439494, "2": 0.364034973121928, "3": 0.09256154369014709}, "confidence": 0.16484351400839803}, "latency_ms": 3635, "drawn": null, "total_after": 17}], "player_id": "b", "result": "lose", "net": -50, "bet_decision": {"decision": {"type": "choice", "choice": "max", "probabilities": {"min": 0.17807237353368274, "standard": 0.3629673835384618, "max": 0.4589602429278555}, "confidence": 0.18844036439178322}, "bet": 50, "confidence": {"type": "score", "score": 1.6467384307127664, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.12712966024657835, "1": 0.29849798780601905, "2": 0.37487661293546065, "3": 0.199495739011942}, "confidence": 0.24774695268888225}, "latency_ms": 3080}, "bankroll_after": 950, "id": "b", "name": "阿衝", "style": "激進壓迫派"}], "shoe_after": {"decks": 6, "remaining_cards": 251, "penetration_pct": 20, "cards_left_by_rank": {"A": 16, "2": 19, "3": 15, "4": 23, "5": 21, "6": 21, "7": 19, "8": 19, "9": 19, "10": 17, "J": 23, "Q": 22, "K": 17}, "hi_lo_running_count": -4, "true_count": -0.83}}, {"n": 9, "shoe_before": {"decks": 6, "remaining_cards": 245, "penetration_pct": 21, "cards_left_by_rank": {"A": 14, "2": 19, "3": 15, "4": 23, "5": 21, "6": 20, "7": 18, "8": 19, "9": 18, "10": 17, "J": 23, "Q": 22, "K": 16}, "hi_lo_running_count": -6, "true_count": -1.27}, "dealer_up": "K♥", "dealer_hole": "7♣", "dealer_revealed": true, "dealer": {"cards": ["K♥", "7♣"], "total": 17, "soft": false, "blackjack": false, "bust": false}, "events": ["莊家明牌為 A 或 10 點,進行暗牌確認", "確認結果:莊家沒有 Blackjack", "莊家最終 17 點"], "players": [{"cards": ["6♦", "9♦"], "bet": 10, "doubled": false, "total": 15, "soft": false, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["6♦", "9♦"], "total": 15, "soft": false, "dealer_up": "K♥", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "stand", "probabilities": {"hit": 0.4065556882060927, "stand": 0.5934443117939072}, "confidence": 0.18688862358781444}, "risk": {"type": "score", "score": 1.4625511690470288, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.13799581285873977, "1": 0.34352512010586594, "2": 0.43641115216501986, "3": 0.08206791487037438}, "confidence": 0.29841533930628017}, "latency_ms": 2666, "drawn": null, "total_after": 15}], "player_id": "a", "result": "lose", "net": -10, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.7083570651368878, "standard": 0.19545918656860584, "max": 0.09618374829450631}, "confidence": 0.5625355977053317}, "bet": 10, "confidence": {"type": "score", "score": 1.213641546925921, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.20136564532087406, "1": 0.4638059699745116, "2": 0.25464957716243375, "3": 0.08017880754218061}, "confidence": 0.38362716243233097}, "latency_ms": 1984}, "bankroll_after": 950, "id": "a", "name": "阿保", "style": "保守算牌派"}, {"cards": ["A♥", "A♠", "Q♠", "8♣"], "bet": 50, "doubled": false, "total": 20, "soft": false, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["A♥", "A♠"], "total": 12, "soft": true, "dealer_up": "K♥", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.5939977924213096, "stand": 0.4060022075786904}, "confidence": 0.18799558484261913}, "risk": {"type": "score", "score": 1.4685976982166182, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.18819208543459834, "1": 0.30035061456540596, "2": 0.3661248163487746, "3": 0.14533248365122103}, "confidence": 0.17793273091417627}, "latency_ms": 3686, "drawn": "Q♠", "total_after": 12}, {"type": "action", "cards": ["A♥", "A♠", "Q♠"], "total": 12, "soft": false, "dealer_up": "K♥", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.6052363769804255, "stand": 0.39476362301957446}, "confidence": 0.21047275396085108}, "risk": {"type": "score", "score": 1.5574917347618844, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.16846858723710814, "1": 0.28599754847447173, "2": 0.3651074065778472, "3": 0.18042645771057275}, "confidence": 0.19663881934073923}, "latency_ms": 3449, "drawn": "8♣", "total_after": 20}, {"type": "action", "cards": ["A♥", "A♠", "Q♠", "8♣"], "total": 20, "soft": false, "dealer_up": "K♥", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "stand", "probabilities": {"hit": 0.3199784228049078, "stand": 0.6800215771950923}, "confidence": 0.3600431543901845}, "risk": {"type": "score", "score": 1.2731733674465937, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.2453494945322414, "1": 0.3366067819479323, "2": 0.31756458506081714, "3": 0.10047913845900906}, "confidence": 0.23612764348892334}, "latency_ms": 3726, "drawn": null, "total_after": 20}], "player_id": "b", "result": "win", "net": 50, "bet_decision": {"decision": {"type": "choice", "choice": "max", "probabilities": {"min": 0.18317788211857033, "standard": 0.37100120794841007, "max": 0.4458209099330196}, "confidence": 0.1687313648995294}, "bet": 50, "confidence": {"type": "score", "score": 1.7016208412378346, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.1245882351848078, "1": 0.2690667058185058, "2": 0.3864810415707307, "3": 0.21986401742595574}, "confidence": 0.2618928063859228}, "latency_ms": 2915}, "bankroll_after": 1000, "id": "b", "name": "阿衝", "style": "激進壓迫派"}], "shoe_after": {"decks": 6, "remaining_cards": 243, "penetration_pct": 22, "cards_left_by_rank": {"A": 14, "2": 19, "3": 15, "4": 23, "5": 21, "6": 20, "7": 18, "8": 18, "9": 18, "10": 17, "J": 23, "Q": 21, "K": 16}, "hi_lo_running_count": -7, "true_count": -1.5}}, {"n": 10, "shoe_before": {"decks": 6, "remaining_cards": 237, "penetration_pct": 24, "cards_left_by_rank": {"A": 14, "2": 18, "3": 15, "4": 22, "5": 19, "6": 20, "7": 18, "8": 18, "9": 17, "10": 17, "J": 23, "Q": 21, "K": 15}, "hi_lo_running_count": -4, "true_count": -0.88}, "dealer_up": "4♦", "dealer_hole": "5♣", "dealer_revealed": true, "dealer": {"cards": ["4♦", "5♣", "A♦"], "total": 20, "soft": true, "blackjack": false, "bust": false}, "events": ["莊家補牌 A♦ → 20 點", "莊家最終 20 點"], "players": [{"cards": ["5♥", "2♦", "3♦", "5♥", "Q♠"], "bet": 10, "doubled": false, "total": 25, "soft": false, "bust": true, "blackjack": false, "steps": [{"type": "action", "cards": ["5♥", "2♦"], "total": 7, "soft": false, "dealer_up": "4♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.7694504382534303, "stand": 0.23054956174656965}, "confidence": 0.5389008765068606}, "risk": {"type": "score", "score": 2.1228442812944257, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.054250014780109976, "1": 0.1406058459410434, "2": 0.4331939824831577, "3": 0.37195015679568905}, "confidence": 0.3789439677030476}, "latency_ms": 2707, "drawn": "3♦", "total_after": 10}, {"type": "action", "cards": ["5♥", "2♦", "3♦"], "total": 10, "soft": false, "dealer_up": "4♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.7577920957558095, "stand": 0.24220790424419048}, "confidence": 0.515584191511619}, "risk": {"type": "score", "score": 1.6885405801021331, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.08428075713214693, "1": 0.28471381134884216, "2": 0.489189525803742, "3": 0.14181590571526903}, "confidence": 0.404908768671595}, "latency_ms": 2613, "drawn": "5♥", "total_after": 15}, {"type": "action", "cards": ["5♥", "2♦", "3♦", "5♥"], "total": 15, "soft": false, "dealer_up": "4♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.5788127737999339, "stand": 0.421187226200066}, "confidence": 0.1576255475998678}, "risk": {"type": "score", "score": 1.4818083791924999, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.11315441258530298, "1": 0.36485505760468034, "2": 0.4490182678422307, "3": 0.07297226196778603}, "confidence": 0.33586385525692763}, "latency_ms": 2554, "drawn": "Q♠", "total_after": 25}], "player_id": "a", "result": "lose", "net": -10, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.7286488493616847, "standard": 0.175529357430751, "max": 0.09582179320756426}, "confidence": 0.5929732740425271}, "bet": 10, "confidence": {"type": "score", "score": 1.169905749673971, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.216524204542941, "1": 0.46785689500435057, "2": 0.24480784668850483, "3": 0.07081105376420364}, "confidence": 0.39704584124014686}, "latency_ms": 2177}, "bankroll_after": 940, "id": "a", "name": "阿保", "style": "保守算牌派"}, {"cards": ["K♠", "9♥"], "bet": 50, "doubled": false, "total": 19, "soft": false, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["K♠", "9♥"], "total": 19, "soft": false, "dealer_up": "4♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "stand", "probabilities": {"hit": 0.29597815331328925, "stand": 0.7040218466867108}, "confidence": 0.4080436933734215}, "risk": {"type": "score", "score": 1.0629240761621292, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.2850042266335565, "1": 0.40113920626045185, "2": 0.2797848314162972, "3": 0.03407173568969435}, "confidence": 0.3670674705707576}, "latency_ms": 3464, "drawn": null, "total_after": 19}], "player_id": "b", "result": "lose", "net": -50, "bet_decision": {"decision": {"type": "choice", "choice": "max", "probabilities": {"min": 0.17685549991326993, "standard": 0.3770309433442875, "max": 0.44611355674244274}, "confidence": 0.1691703351136641}, "bet": 50, "confidence": {"type": "score", "score": 1.6562748371119183, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.1332817973157961, "1": 0.2844193759896284, "2": 0.3750410189614367, "3": 0.20725780773313882}, "confidence": 0.24175922164564057}, "latency_ms": 2647}, "bankroll_after": 950, "id": "b", "name": "阿衝", "style": "激進壓迫派"}], "shoe_after": {"decks": 6, "remaining_cards": 233, "penetration_pct": 25, "cards_left_by_rank": {"A": 13, "2": 18, "3": 14, "4": 22, "5": 18, "6": 20, "7": 18, "8": 18, "9": 17, "10": 17, "J": 23, "Q": 20, "K": 15}, "hi_lo_running_count": -4, "true_count": -0.89}}, {"n": 11, "shoe_before": {"decks": 6, "remaining_cards": 227, "penetration_pct": 27, "cards_left_by_rank": {"A": 13, "2": 17, "3": 13, "4": 22, "5": 18, "6": 19, "7": 18, "8": 18, "9": 17, "10": 17, "J": 22, "Q": 20, "K": 13}, "hi_lo_running_count": -4, "true_count": -0.92}, "dealer_up": "J♥", "dealer_hole": "6♦", "dealer_revealed": true, "dealer": {"cards": ["J♥", "6♦", "2♠"], "total": 18, "soft": false, "blackjack": false, "bust": false}, "events": ["莊家明牌為 A 或 10 點,進行暗牌確認", "確認結果:莊家沒有 Blackjack", "莊家補牌 2♠ → 18 點", "莊家最終 18 點"], "players": [{"cards": ["K♦", "K♠"], "bet": 10, "doubled": false, "total": 20, "soft": false, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["K♦", "K♠"], "total": 20, "soft": false, "dealer_up": "J♥", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "stand", "probabilities": {"hit": 0.20113812988182753, "stand": 0.7988618701181724}, "confidence": 0.5977237402363449}, "risk": {"type": "score", "score": 0.9617831874646433, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.37266878093983297, "1": 0.3419774875432344, "2": 0.2362554946293891, "3": 0.04909823688754356}, "confidence": 0.03821681253535669}, "latency_ms": 2807, "drawn": null, "total_after": 20}], "player_id": "a", "result": "win", "net": 10, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.705457858991765, "standard": 0.19844058997151406, "max": 0.09610155103672088}, "confidence": 0.5581867884876476}, "bet": 10, "confidence": {"type": "score", "score": 1.1786459226702077, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.2098777886585959, "1": 0.4725124514370032, "2": 0.24669580847999806, "3": 0.07091395142440278}, "confidence": 0.40159850001260045}, "latency_ms": 2281}, "bankroll_after": 950, "id": "a", "name": "阿保", "style": "保守算牌派"}, {"cards": ["2♦", "3♥", "2♥", "A♠"], "bet": 50, "doubled": false, "total": 18, "soft": true, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["2♦", "3♥"], "total": 5, "soft": false, "dealer_up": "J♥", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.8339648493225332, "stand": 0.1660351506774667}, "confidence": 0.6679296986450665}, "risk": {"type": "score", "score": 1.9632358033511101, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.08778014756208807, "1": 0.17939112699816237, "2": 0.414641499966301, "3": 0.3181872254734486}, "confidence": 0.3268613524042129}, "latency_ms": 3984, "drawn": "2♥", "total_after": 7}, {"type": "action", "cards": ["2♦", "3♥", "2♥"], "total": 7, "soft": false, "dealer_up": "J♥", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.8254946752313272, "stand": 0.17450532476867275}, "confidence": 0.6509893504626545}, "risk": {"type": "score", "score": 1.9199601944797384, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.08961070661335929, "1": 0.1944316247075459, "2": 0.4223444362650919, "3": 0.2936132324140029}, "confidence": 0.33273372965173253}, "latency_ms": 3647, "drawn": "A♠", "total_after": 18}, {"type": "action", "cards": ["2♦", "3♥", "2♥", "A♠"], "total": 18, "soft": true, "dealer_up": "J♥", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "stand", "probabilities": {"hit": 0.4516507728170757, "stand": 0.5483492271829243}, "confidence": 0.09669845436584867}, "risk": {"type": "score", "score": 1.3633877251471496, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.1898920049001741, "1": 0.35446905502554144, "2": 0.3579981501012455, "3": 0.09764078997303899}, "confidence": 0.16810614520107148}, "latency_ms": 3469, "drawn": null, "total_after": 18}], "player_id": "b", "result": "push", "net": 0, "bet_decision": {"decision": {"type": "choice", "choice": "max", "probabilities": {"min": 0.17659368546509877, "standard": 0.38463171221217046, "max": 0.43877460232273074}, "confidence": 0.15816190348409612}, "bet": 50, "confidence": {"type": "score", "score": 1.6844293372148722, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.12644015955206472, "1": 0.27508981392517495, "2": 0.3860705562785838, "3": 0.21239947024417657}, "confidence": 0.259630396726519}, "latency_ms": 2718}, "bankroll_after": 950, "id": "b", "name": "阿衝", "style": "激進壓迫派"}], "shoe_after": {"decks": 6, "remaining_cards": 224, "penetration_pct": 28, "cards_left_by_rank": {"A": 12, "2": 15, "3": 13, "4": 22, "5": 18, "6": 19, "7": 18, "8": 18, "9": 17, "10": 17, "J": 22, "Q": 20, "K": 13}, "hi_lo_running_count": -3, "true_count": -0.7}}, {"n": 12, "shoe_before": {"decks": 6, "remaining_cards": 218, "penetration_pct": 30, "cards_left_by_rank": {"A": 12, "2": 15, "3": 13, "4": 22, "5": 18, "6": 18, "7": 16, "8": 17, "9": 17, "10": 17, "J": 22, "Q": 19, "K": 12}, "hi_lo_running_count": -4, "true_count": -0.95}, "dealer_up": "K♦", "dealer_hole": "8♦", "dealer_revealed": true, "dealer": {"cards": ["K♦", "8♦"], "total": 18, "soft": false, "blackjack": false, "bust": false}, "events": ["莊家明牌為 A 或 10 點,進行暗牌確認", "確認結果:莊家沒有 Blackjack", "莊家最終 18 點"], "players": [{"cards": ["Q♦", "6♣"], "bet": 10, "doubled": false, "total": 16, "soft": false, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["Q♦", "6♣"], "total": 16, "soft": false, "dealer_up": "K♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "stand", "probabilities": {"hit": 0.39016492109703543, "stand": 0.6098350789029646}, "confidence": 0.21967015780592924}, "risk": {"type": "score", "score": 1.7370799154639605, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.09344393533096118, "1": 0.2685085201031415, "2": 0.4455712383368731, "3": 0.19247630622902423}, "confidence": 0.3521273030059119}, "latency_ms": 2460, "drawn": null, "total_after": 16}], "player_id": "a", "result": "lose", "net": -10, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.7338713056072282, "standard": 0.171097765938789, "max": 0.09503092845398282}, "confidence": 0.6008069584108423}, "bet": 10, "confidence": {"type": "score", "score": 1.2594716391475735, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.16968632812925138, "1": 0.4794955805426685, "2": 0.2724782153793357, "3": 0.07833987594874454}, "confidence": 0.4011557045939238}, "latency_ms": 1995}, "bankroll_after": 940, "id": "a", "name": "阿保", "style": "保守算牌派"}, {"cards": ["7♥", "7♦", "3♥"], "bet": 50, "doubled": false, "total": 17, "soft": false, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["7♥", "7♦"], "total": 14, "soft": false, "dealer_up": "K♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "hit", "probabilities": {"hit": 0.608349823613437, "stand": 0.3916501763865629}, "confidence": 0.21669964722687407}, "risk": {"type": "score", "score": 1.4682163416498657, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.17040055353150113, "1": 0.3169814206595017, "2": 0.38661915643662725, "3": 0.12599886937236981}, "confidence": 0.21621860290512618}, "latency_ms": 3602, "drawn": "3♥", "total_after": 17}, {"type": "action", "cards": ["7♥", "7♦", "3♥"], "total": 17, "soft": false, "dealer_up": "K♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "stand", "probabilities": {"hit": 0.4373052794431983, "stand": 0.5626947205568017}, "confidence": 0.1253894411136034}, "risk": {"type": "score", "score": 1.27082220318929, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.21799232108026073, "1": 0.36294600367568364, "2": 0.34930882621856046, "3": 0.06975284902549515}, "confidence": 0.29319315465018847}, "latency_ms": 3726, "drawn": null, "total_after": 17}], "player_id": "b", "result": "lose", "net": -50, "bet_decision": {"decision": {"type": "choice", "choice": "max", "probabilities": {"min": 0.18420429014082615, "standard": 0.39700684030663047, "max": 0.4187888695525434}, "confidence": 0.1281833043288151}, "bet": 50, "confidence": {"type": "score", "score": 1.6458985137494473, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.1260372937960504, "1": 0.2951391749084489, "2": 0.3857112550455043, "3": 0.19311227624999663}, "confidence": 0.25967396124945374}, "latency_ms": 3039}, "bankroll_after": 900, "id": "b", "name": "阿衝", "style": "激進壓迫派"}], "shoe_after": {"decks": 6, "remaining_cards": 217, "penetration_pct": 30, "cards_left_by_rank": {"A": 12, "2": 15, "3": 12, "4": 22, "5": 18, "6": 18, "7": 16, "8": 17, "9": 17, "10": 17, "J": 22, "Q": 19, "K": 12}, "hi_lo_running_count": -3, "true_count": -0.72}}]};
254
+ const SUIT_RED = new Set(["♥","♦"]);
255
+ const LABEL = {hit:"補牌", stand:"停牌", double:"加倍", min:"最小注", standard:"標準注", max:"重注"};
256
+ const VALUE = {A:11,"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9,"10":10,J:10,Q:10,K:10};
257
+ const RESULT_LABEL = {win:"勝", lose:"負", push:"和局", blackjack:"Blackjack"};
258
+ const $ = (s,el=document)=>el.querySelector(s);
259
+ let DATA=null, roundIdx=0, autoTimer=null, dealerOpen=true;
260
+
261
+ function money(n){ return (n>0?"+":"") + n.toLocaleString("en-US"); }
262
+ function cls(n){ return n>0?"pos":(n<0?"neg":"neutral"); }
263
+ function pct(x){ return (Math.max(0,Math.min(1,x||0))*100).toFixed(0)+"%"; }
264
+ function cardHTML(card, hidden){
265
+ if(hidden) return '<div class="card down">?</div>';
266
+ const r=card.slice(0,-1), s=card.slice(-1);
267
+ return '<div class="card '+(SUIT_RED.has(s)?"red":"")+'"><span class="r">'+r+'</span><span class="s">'+s+'</span></div>';
268
+ }
269
+ function cardsHTML(cards, opts={}){
270
+ return '<div class="cards">'+cards.map((c,i)=>cardHTML(c, opts.hideIndex===i)).join("")+'</div>';
271
+ }
272
+ function agentColor(id){ return id==="a" ? "var(--green)" : "var(--blue)"; }
273
+ function agentMeta(id){ return DATA.meta.agents.find(a=>a.id===id) || {name:id,style:""}; }
274
+
275
+ function totalsFor(round){
276
+ const out={};
277
+ for(const p of round.players){
278
+ out[p.id]=out[p.id]||{bet:0,net:0};
279
+ out[p.id].bet+=p.bet; out[p.id].net+=p.net;
280
+ }
281
+ return out;
282
+ }
283
+
284
+ function init(){
285
+ DATA = window.BATTLE_DATA;
286
+ if(!DATA){ $("#app").innerHTML='<div class="err">找不到 battle.json,請用 HTTP 伺服器開啟此頁。</div>'; return; }
287
+ document.title = DATA.meta.title + " · d1demo";
288
+ $("#app").innerHTML = `
289
+ <section class="view active" id="view-battle">
290
+ <div class="hero">
291
+ <h1>${DATA.meta.title.split(" ")[0]} <em>×</em> d1:21 點決策對戰</h1>
292
+ <p>${DATA.meta.subtitle}。每一注、每一次補牌/停牌/加倍,都是模型即時輸出的機率分佈,不是寫死的策略表。</p>
293
+ <div class="chips" id="metaChips"></div>
294
+ </div>
295
+ <div class="scoreboard" id="scoreboard"></div>
296
+ <div class="panel table-panel">
297
+ <div class="round-toolbar">
298
+ <button class="icon-btn" id="prevRound">← 上一局</button>
299
+ <div class="round-pills" id="roundPills"></div>
300
+ <button class="icon-btn" id="nextRound">下一局 →</button>
301
+ <button class="icon-btn" id="playBtn">▶ 自動播放</button>
302
+ <span class="round-label" id="roundLabel"></span>
303
+ </div>
304
+ <div id="roundView"></div>
305
+ <div class="arena-note">莊家由固定規則操作(S17),兩個玩家各自連到一個獨立的 llama.cpp server;模型只知道牌面、剩餘牌組成、真數與資金,不知道對手的手牌。</div>
306
+ </div>
307
+ </section>
308
+
309
+ <section class="view" id="view-stats"></section>
310
+ <section class="view" id="view-code">
311
+ <h2 class="section-title">整理程式</h2>
312
+ <div class="data-box">
313
+ <p>這一頁不是手寫的靜態稿:<b>blackjack.py</b> 負責發牌、呼叫兩個 d1 server 做決策並記錄每一步;<b>build_site.py</b> 把 battle.json 編譯成你現在看到的網站。原始碼同步放在這個 Space 的 <code>source/</code> 目錄。</p>
314
+ </div>
315
+ <div class="code-tabs" id="codeTabs"></div>
316
+ <div class="code-bar">
317
+ <span id="codeName">blackjack.py</span>
318
+ <a class="btn ghost" id="codeDownload" href="source/blackjack.py" download>下載此檔</a>
319
+ <button class="icon-btn" id="copyCode">複製</button>
320
+ </div>
321
+ <pre class="code"><code id="codeBody">載入中…</code></pre>
322
+ </section>
323
+
324
+ <section class="view" id="view-data">
325
+ <h2 class="section-title">原始資料</h2>
326
+ <div class="data-box">
327
+ <h3>battle.json</h3>
328
+ <p>完整對戰紀錄:每一局的牌、注碼、決策機率、信心度、風險評分、延遲、莊家補牌與資金變化。</p>
329
+ <p><a class="btn" href="battle.json" download>下載 battle.json</a>
330
+ <a class="btn ghost" href="battle.json" target="_blank" rel="noopener">在瀏覽器開啟</a></p>
331
+ </div>
332
+ <div class="data-box">
333
+ <h3>重現方式</h3>
334
+ <p>需要 llama.cpp b11786 以上版本、LiquidAI/d1-3B-GGUF(Q8_0)與 Python 3。</p>
335
+ <pre class="code"><code id="repro"></code></pre>
336
+ </div>
337
+ <div class="data-box">
338
+ <h3>本次執行摘要</h3>
339
+ <pre class="code"><code id="metaJson"></code></pre>
340
+ </div>
341
+ </section>
342
+ <footer style="border:0;padding-bottom:0"></footer>
343
+ `;
344
+ renderChips();
345
+ renderScoreboard();
346
+ renderPills();
347
+ renderRound();
348
+ renderStats();
349
+ renderCodeTabs();
350
+ bind();
351
+ }
352
+
353
+ function renderChips(){
354
+ const m=DATA.meta;
355
+ const chips=[
356
+ ["模型",m.model],["量化",m.quant],["llama.cpp",m.llama_build],
357
+ ["局數",m.rounds_played],["隨機種子",m.seed],["耗時",m.duration_sec+"s"],
358
+ ["莊家","S17"],["BJ 賠率","3:2"]
359
+ ];
360
+ $("#metaChips").innerHTML=chips.map(([k,v])=>`<span class="chip">${k} <b>${v}</b></span>`).join("");
361
+ }
362
+
363
+ function renderScoreboard(){
364
+ const agents=DATA.meta.agents;
365
+ $("#scoreboard").innerHTML=agents.map(a=>{
366
+ const last=DATA.rounds[DATA.rounds.length-1];
367
+ const p=last.players.find(x=>x.id===a.id);
368
+ const profit=a.final_bankroll-DATA.meta.start_bankroll;
369
+ const color=agentColor(a.id);
370
+ return `<div class="score" style="--accent:${color}">
371
+ <div class="id">PLAYER ${a.id.toUpperCase()}</div>
372
+ <div class="name">${a.name}</div>
373
+ <div class="style">${a.style}</div>
374
+ <div class="bank" style="color:${color}">${a.final_bankroll.toLocaleString("en-US")}</div>
375
+ <div class="sub">起始 ${DATA.meta.start_bankroll} · 損益 <span class="big-delta ${cls(profit)}">${money(profit)}</span></div>
376
+ <div class="sub">最終一局:${p?(RESULT_LABEL[p.result]||p.result):""} ${p?money(p.net):""}</div>
377
+ </div>`;
378
+ }).join("");
379
+ }
380
+
381
+ function roundTone(r){
382
+ const res=r.players.map(p=>p.result);
383
+ const wins=res.filter(x=>x==="win"||x==="blackjack").length;
384
+ const loses=res.filter(x=>x==="lose").length;
385
+ if(wins===res.length) return "win";
386
+ if(loses===res.length) return "lose";
387
+ if(res.every(x=>x==="push")) return "push";
388
+ return "mixed";
389
+ }
390
+
391
+ function renderPills(){
392
+ $("#roundPills").innerHTML=DATA.rounds.map((r,i)=>{
393
+ return `<button class="pill ${roundTone(r)} ${i===roundIdx?"active":""}" data-round="${i}" title="第 ${r.n} 局">
394
+ <span class="dot"></span>${r.n}</button>`;
395
+ }).join("");
396
+ $("#roundPills").querySelectorAll(".pill").forEach(b=>b.onclick=()=>{stopAuto();roundIdx=+b.dataset.round;dealerOpen=true;renderRound();renderPills();});
397
+ }
398
+
399
+ function stepHTML(step, i){
400
+ const d=step.decision||{};
401
+ const probs=d.probabilities||{};
402
+ const keys=Object.keys(probs).sort((a,b)=>probs[b]-probs[a]);
403
+ const risk=step.risk;
404
+ const riskText=risk ? `風險評分 <b>${risk.score.toFixed(2)}</b>/3 · 信心 ${pct(risk.confidence)}` : "";
405
+ const drawn=step.drawn ? `補到 <b>${step.drawn}</b> → <b>${step.total_after}</b> 點` : (d.choice==="stand"?"維持 <b>"+step.total+"</b> 點":`→ <b>${step.total_after}</b> 點`);
406
+ return `<li class="step">
407
+ <div class="step-head">
408
+ <span class="step-no">#${i+1}</span>
409
+ <span class="mini">${step.cards.join(" ")}</span>
410
+ <span class="mini" style="color:var(--muted)">(${step.total}${step.soft?" 軟":""})</span>
411
+ <span class="action ${LABEL[d.choice]?d.choice:"unknown"}">${LABEL[d.choice]||d.choice||"?"}</span>
412
+ <span class="meta">信心 ${pct(d.confidence)} · ${((step.latency_ms||0)/1000).toFixed(2)}s</span>
413
+ </div>
414
+ <div class="probs">
415
+ ${keys.map(k=>`<span>${LABEL[k]||k}</span><span class="bar"><i style="--c:var(--${k==="hit"?"hit":k==="stand"?"stand":"double"});width:${(probs[k]*100).toFixed(1)}%"></i></span><span class="pct">${pct(probs[k])}</span>`).join("")}
416
+ </div>
417
+ <div class="step-foot"><span>${drawn}</span>${riskText?`<span>${riskText}</span>`:""}${d.fallback?`<span style="color:var(--red)">伺服器無回應,改用規則備援</span>`:""}</div>
418
+ </li>`;
419
+ }
420
+
421
+ function playerHTML(p){
422
+ const steps=(p.steps||[]).map(stepHTML).join("");
423
+ const bd=p.bet_decision||{};
424
+ const betDecision=bd.decision||{};
425
+ const betProbs=betDecision.probabilities||{};
426
+ const betLabel=LABEL[betDecision.choice]||betDecision.choice||"";
427
+ return `<article class="player ${p.result} ${p.blackjack?"blackjack":""}" style="--accent:${agentColor(p.id)}">
428
+ <div class="p-head">
429
+ <div><h3>${p.name}</h3><div class="style">${p.style||""}</div></div>
430
+ <div class="bet">注碼 <b>${p.bet}</b>${p.doubled?" · 已加倍":""}</div>
431
+ </div>
432
+ <div class="hand">
433
+ ${cardsHTML(p.cards)}
434
+ <div class="total">${p.total}<small>${p.bust?"BUST":p.soft?"軟牌":p.blackjack?"BJ":"硬牌"}</small></div>
435
+ <span class="result ${p.result}">${RESULT_LABEL[p.result]||p.result}</span>
436
+ </div>
437
+ <div class="bet-line">
438
+ <span class="tag">注碼決策 ${betLabel}</span>
439
+ ${p.rebuy?`<span class="tag" style="color:var(--amber)">補充籌碼 +${p.rebuy}</span>`:""}
440
+ <span>信心 ${pct(betDecision.confidence)}</span>
441
+ ${Object.keys(betProbs).length?`<span>${Object.keys(betProbs).sort((a,b)=>betProbs[b]-betProbs[a]).map(k=>`${LABEL[k]||k} ${pct(betProbs[k])}`).join(" · ")}</span>`:""}
442
+ </div>
443
+ <details><summary>看模型怎麼決定注碼</summary>
444
+ <div class="body">狀態包含資金、剩余牌、Hi-Lo 真數與規則;模型在 min / standard / max 三種注碼中挑一個,並另外給出信心評分。</div>
445
+ </details>
446
+ ${steps?`<ol class="steps">${steps}</ol>`:`<div class="step-foot" style="margin-top:12px">莊家直接 Blackjack,玩家沒有動作機會。</div>`}
447
+ <div class="net"><span>本局結果</span><span class="amt ${cls(p.net)}">${money(p.net)} <small style="font-size:12px;color:var(--muted)">→ ${p.bankroll_after}</small></span></div>
448
+ </article>`;
449
+ }
450
+
451
+ function renderRound(){
452
+ const r=DATA.rounds[roundIdx];
453
+ if(!r){ return; }
454
+ const d=r.dealer||{};
455
+ const hideHole=!dealerOpen && r.dealer_revealed;
456
+ const dealerCards=(d.cards||[]).map((c,i)=>cardHTML(c, hideHole && i===1)).join("");
457
+ const totals=totalsFor(r);
458
+ const pillTone=roundTone(r);
459
+ $("#roundLabel").textContent=`第 ${r.n} / ${DATA.rounds.length} 局 · 剩餘 ${r.shoe_before.remaining_cards} 張 · 真數 ${r.shoe_before.true_count}`;
460
+ $("#roundView").innerHTML=`
461
+ <div class="round-head">
462
+ <h2>第 ${r.n} 局</h2>
463
+ <span class="shoe">洗牌前剩餘 ${r.shoe_before.remaining_cards} 張(穿透率 ${r.shoe_before.penetration_pct}%) · Hi-Lo 真數 ${r.shoe_before.true_count}</span>
464
+ </div>
465
+ <div class="dealer">
466
+ <div>
467
+ <div class="label">DEALER · 莊家</div>
468
+ <div class="total-badge">${hideHole?"?":d.total}${d.bust?' <span style="font-size:12px;color:var(--red)">BUST</span>':""}${d.blackjack?' <span style="font-size:12px;color:var(--gold)">BJ</span>':""}</div>
469
+ </div>
470
+ <div class="cards">${dealerCards}</div>
471
+ <div class="note">${r.events.join(" · ")||"莊家依規則補牌"}</div>
472
+ ${r.dealer_revealed?`<button class="icon-btn" id="revealBtn">${dealerOpen?"蓋回暗牌":"揭開暗牌"}</button>`:""}
473
+ </div>
474
+ <div class="players">${r.players.map(playerHTML).join("")}</div>
475
+ <div class="events"><b>牌桌事件</b><ul>${r.events.map(e=>`<li>${e}</li>`).join("")}</ul></div>
476
+ `;
477
+ const rb=$("#revealBtn");
478
+ if(rb) rb.onclick=()=>{dealerOpen=!dealerOpen;renderRound();};
479
+ $("#prevRound").disabled=roundIdx===0;
480
+ $("#nextRound").disabled=roundIdx===DATA.rounds.length-1;
481
+ document.querySelectorAll(".pill").forEach((b,i)=>b.classList.toggle("active",i===roundIdx));
482
+ }
483
+
484
+ function bind(){
485
+ $("#prevRound").onclick=()=>{stopAuto(); if(roundIdx>0){roundIdx--;dealerOpen=true;renderRound();}};
486
+ $("#nextRound").onclick=()=>{stopAuto(); if(roundIdx<DATA.rounds.length-1){roundIdx++;dealerOpen=true;renderRound();}};
487
+ $("#playBtn").onclick=()=>{
488
+ if(autoTimer){stopAuto();return;}
489
+ roundIdx=0;dealerOpen=true;renderRound();
490
+ $("#playBtn").textContent="⏸ 暫停";
491
+ autoTimer=setInterval(()=>{
492
+ if(roundIdx>=DATA.rounds.length-1){stopAuto();return;}
493
+ roundIdx++;dealerOpen=true;renderRound();
494
+ },4000);
495
+ };
496
+ document.querySelectorAll("nav button").forEach(b=>{
497
+ b.onclick=()=>{
498
+ document.querySelectorAll("nav button").forEach(x=>x.classList.toggle("active",x===b));
499
+ document.querySelectorAll(".view").forEach(v=>v.classList.toggle("active",v.id==="view-"+b.dataset.view));
500
+ window.scrollTo({top:0,behavior:"smooth"});
501
+ };
502
+ });
503
+ }
504
+ function stopAuto(){ if(autoTimer){clearInterval(autoTimer);autoTimer=null;} const b=$("#playBtn"); if(b)b.textContent="▶ 自動播放"; }
505
+
506
+ function statsFor(id){
507
+ const s={rounds:0,win:0,lose:0,push:0,bj:0,bust:0,double:0,net:0,wagered:0,steps:0,conf:0,lat:0,actions:{hit:0,stand:0,double:0},firstActions:[]};
508
+ for(const r of DATA.rounds){
509
+ const p=r.players.find(x=>x.id===id); if(!p) continue;
510
+ s.rounds++;
511
+ if(p.result==="win")s.win++;
512
+ else if(p.result==="lose")s.lose++;
513
+ else if(p.result==="push")s.push++;
514
+ else if(p.result==="blackjack")s.win++;
515
+ if(p.blackjack)s.bj++;
516
+ if(p.bust)s.bust++;
517
+ if(p.doubled)s.double++;
518
+ s.net+=p.net; s.wagered+=p.bet;
519
+ (p.steps||[]).forEach(st=>{
520
+ s.steps++; s.conf+=st.decision.confidence||0; s.lat+=st.latency_ms||0;
521
+ s.actions[st.decision.choice]=(s.actions[st.decision.choice]||0)+1;
522
+ });
523
+ const p1=(p.steps||[])[0];
524
+ if(p1) s.firstActions.push({round:r.n,action:p1.decision.choice});
525
+ }
526
+ s.avgConf=s.steps?s.conf/s.steps:0;
527
+ s.avgLat=s.steps?s.lat/s.steps:0;
528
+ return s;
529
+ }
530
+
531
+ function renderStats(){
532
+ const a=statsFor("a"), b=statsFor("b");
533
+ const agree=a.firstActions.filter(x=>{
534
+ const y=b.firstActions.find(z=>z.round===x.round);
535
+ return y && y.action===x.action;
536
+ }).length;
537
+ const compared=a.firstActions.filter(x=>b.firstActions.some(z=>z.round===x.round)).length;
538
+ const dealerBust=DATA.rounds.filter(r=>r.dealer&&r.dealer.bust).length;
539
+ const dealerBj=DATA.rounds.filter(r=>r.dealer&&r.dealer.blackjack).length;
540
+ const dealerAvg=DATA.rounds.reduce((sum,r)=>sum+((r.dealer||{}).total||0),0)/Math.max(DATA.rounds.length,1);
541
+ const totalNet=a.net+b.net;
542
+ const houseEdge=totalNet;
543
+
544
+ function statCard(id,s){
545
+ const m=agentMeta(id);
546
+ const totalActions=Math.max(s.steps,1);
547
+ return `<div class="stat-card" style="--accent:${agentColor(id)}">
548
+ <h3>${m.name}</h3><div class="style">${m.style}</div>
549
+ <div class="kv">
550
+ <div><div class="k">損益</div><div class="v ${cls(s.net)}">${money(s.net)}</div></div>
551
+ <div><div class="k">勝 / 負 / 和</div><div class="v">${s.win} / ${s.lose} / ${s.push}</div></div>
552
+ <div><div class="k">Blackjack</div><div class="v">${s.bj}</div></div>
553
+ <div><div class="k">爆牌</div><div class="v">${s.bust}</div></div>
554
+ <div><div class="k">加倍次數</div><div class="v">${s.double}</div></div>
555
+ <div><div class="k">總下注</div><div class="v">${s.wagered}</div></div>
556
+ <div><div class="k">平均信心</div><div class="v">${pct(s.avgConf)}</div></div>
557
+ <div><div class="k">平均推理</div><div class="v">${(s.avgLat/1000).toFixed(2)}s</div></div>
558
+ </div>
559
+ <div class="dist">
560
+ <div class="mini-title">動作分佈(${s.steps} 次)</div>
561
+ ${["hit","stand","double"].map(k=>`<div class="row"><span>${LABEL[k]}</span><span class="bar"><i style="--c:var(--${k});width:${(100*s.actions[k]/totalActions).toFixed(1)}%"></i></span><span class="pct">${s.actions[k]}</span></div>`).join("")}
562
+ </div>
563
+ </div>`;
564
+ }
565
+
566
+ const chart=bankrollChart();
567
+ $("#view-stats").innerHTML=`
568
+ <h2 class="section-title">決策統計</h2>
569
+ <div class="stats-grid">${statCard("a",a)}${statCard("b",b)}</div>
570
+ <div class="chart-wrap">
571
+ <div class="chart-legend">
572
+ <span><i style="background:var(--green)"></i>${agentMeta("a").name}</span>
573
+ <span><i style="background:var(--blue)"></i>${agentMeta("b").name}</span>
574
+ <span>起始資金 ${DATA.meta.start_bankroll}</span>
575
+ </div>
576
+ ${chart}
577
+ </div>
578
+ <div class="h2h">
579
+ <h3 style="margin:0 0 6px">兩個 d1 有多像?</h3>
580
+ <p style="color:var(--muted);margin:4px 0 10px">比較每一局雙方第一次動作(同一副牌、同一個莊家明牌、相同的剩餘牌資訊)。</p>
581
+ <div class="big">${compared?Math.round(100*agree/compared):0}%</div>
582
+ <div style="color:var(--muted);font-size:13px">首個動作一致率 · 共比較 ${compared} 局</div>
583
+ <div class="kv" style="grid-template-columns:repeat(3,1fr);margin-top:16px">
584
+ <div><div class="k">莊家爆牌率</div><div class="v">${Math.round(100*dealerBust/DATA.rounds.length)}%</div></div>
585
+ <div><div class="k">莊家 Blackjack</div><div class="v">${dealerBj} 局</div></div>
586
+ <div><div class="k">莊家平均點數</div><div class="v">${dealerAvg.toFixed(1)}</div></div>
587
+ </div>
588
+ </div>
589
+ <div class="data-box" style="margin-top:14px">
590
+ <h3>兩個模型合計對抗莊家</h3>
591
+ <p style="font-size:15px">總損益 <b class="${cls(totalNet)}" style="font-size:24px">${money(totalNet)}</b> · 每位玩家起始 ${DATA.meta.start_bankroll},合計起始 ${DATA.meta.start_bankroll*2}</p>
592
+ </div>
593
+ `;
594
+ }
595
+
596
+ function bankrollChart(){
597
+ const W=760,H=230,pad={l:52,r:14,t:14,b:26};
598
+ const agents=DATA.meta.agents;
599
+ const series=agents.map(a=>{
600
+ const vals=[DATA.meta.start_bankroll];
601
+ for(const r of DATA.rounds){
602
+ const p=r.players.find(x=>x.id===a.id);
603
+ vals.push(p?p.bankroll_after:DATA.meta.start_bankroll);
604
+ }
605
+ return {a,vals,color:a.id==="a"?"#39d98a":"#5cc8ff"};
606
+ });
607
+ const all=series.flatMap(s=>s.vals);
608
+ let min=Math.min(...all),max=Math.max(...all);
609
+ const span=Math.max(max-min,100);
610
+ min=Math.floor((min-span*0.1)/50)*50; max=Math.ceil((max+span*0.1)/50)*50;
611
+ const X=i=>pad.l+(W-pad.l-pad.r)*(i/(DATA.rounds.length||1));
612
+ const Y=v=>H-pad.b-(H-pad.t-pad.b)*((v-min)/(max-min||1));
613
+ const step=Math.max(50,Math.ceil((max-min)/4/50)*50);
614
+ min=Math.floor(min/step)*step; max=Math.ceil(max/step)*step;
615
+ const ticks=Math.round((max-min)/step);
616
+ let grid="";
617
+ for(let i=0;i<=ticks;i++){
618
+ const v=min+(max-min)*i/ticks, y=Y(v);
619
+ grid+=`<line x1="${pad.l}" y1="${y}" x2="${W-pad.r}" y2="${y}" stroke="rgba(255,255,255,.07)"/>`;
620
+ grid+=`<text x="${pad.l-8}" y="${y+4}" fill="#8fae9f" font-size="10" text-anchor="end">${Math.round(v)}</text>`;
621
+ }
622
+ const paths=series.map(s=>{
623
+ const d=s.vals.map((v,i)=>`${i?"L":"M"}${X(i).toFixed(1)},${Y(v).toFixed(1)}`).join(" ");
624
+ const dots=s.vals.map((v,i)=>`<circle cx="${X(i)}" cy="${Y(v)}" r="2.6" fill="${s.color}"><title>第 ${i} 局後:${v}</title></circle>`).join("");
625
+ return `<path d="${d}" fill="none" stroke="${s.color}" stroke-width="2.4" stroke-linejoin="round"/>${dots}`;
626
+ }).join("");
627
+ return `<svg viewBox="0 0 ${W} ${H}" width="100%" role="img" aria-label="資金曲線">${grid}${paths}</svg>`;
628
+ }
629
+
630
+ const CODE_FILES=[
631
+ {file:"blackjack.py",label:"blackjack.py · 對戰與記錄"},
632
+ {file:"build_site.py",label:"build_site.py · 網頁編譯"},
633
+ {file:"run_demo.sh",label:"run_demo.sh · 一鍵執行"},
634
+ ];
635
+ function renderCodeTabs(){
636
+ $("#codeTabs").innerHTML=CODE_FILES.map((f,i)=>`<button data-file="${f.file}" class="${i===0?"active":""}">${f.label}</button>`).join("");
637
+ $("#codeTabs").querySelectorAll("button").forEach(b=>b.onclick=()=>{
638
+ $("#codeTabs").querySelectorAll("button").forEach(x=>x.classList.toggle("active",x===b));
639
+ loadCode(b.dataset.file);
640
+ });
641
+ loadCode("blackjack.py");
642
+ $("#copyCode").onclick=async()=>{
643
+ try{await navigator.clipboard.writeText($("#codeBody").textContent);$("#copyCode").textContent="已複製";setTimeout(()=>$("#copyCode").textContent="複製",1200);}catch(e){}
644
+ };
645
+ $("#repro").textContent=`# 1) 下載模型與 llama.cpp(CPU 版)
646
+ llama-server -m d1-3B-Q8_0.gguf --port 8081 -c 2048 -np 1 --no-webui &
647
+ llama-server -m d1-3B-Q8_0.gguf --port 8082 -c 2048 -np 1 --no-webui &
648
+
649
+ # 2) 讓兩個 d1 打 12 局,並把每一步寫成 JSON
650
+ python3 blackjack.py --rounds 12 --seed ${DATA.meta.seed} --out battle.json
651
+
652
+ # 3) 把 JSON 編譯成這個靜態網站
653
+ python3 build_site.py --battle battle.json --out site
654
+
655
+ # 4) 上傳 static Space
656
+ python3 - <<'PY'
657
+ from huggingface_hub import HfApi
658
+ HfApi().upload_folder(folder_path="site", repo_id="HelloSun/d1demo", repo_type="space")
659
+ PY`;
660
+ $("#metaJson").textContent=JSON.stringify(DATA.meta,null,2);
661
+ }
662
+ function loadCode(file){
663
+ $("#codeName").textContent=file;
664
+ $("#codeDownload").href="source/"+file;
665
+ $("#codeBody").textContent="載入中…";
666
+ fetch("source/"+file).then(r=>{if(!r.ok)throw new Error(r.status);return r.text();})
667
+ .then(t=>{$("#codeBody").textContent=t;})
668
+ .catch(()=>{$("#codeBody").textContent="(無法載入 "+file+",請確認 source/ 目錄存在)";});
669
+ }
670
+
671
+ init();
672
+ </script>
673
+ </body>
674
  </html>
source/blackjack.py ADDED
@@ -0,0 +1,535 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """d1 vs d1 blackjack battle.
3
+
4
+ Two llama.cpp servers, each hosting a LiquidAI d1-3B decision model, play
5
+ blackjack against a rule-based dealer. Every decision (bet size + hit / stand /
6
+ double) is a /v1/systemone query. The complete battle is written as JSON and
7
+ rendered into a static web page by build_site.py.
8
+
9
+ Usage:
10
+ python3 blackjack.py --rounds 12 --out battle.json
11
+ """
12
+ import argparse
13
+ import json
14
+ import random
15
+ import time
16
+ import urllib.error
17
+ import urllib.request
18
+ from datetime import datetime, timezone
19
+ from pathlib import Path
20
+
21
+ SUITS = ["♠", "♥", "♦", "♣"]
22
+ RANKS = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
23
+ RANK_VALUE = {r: (11 if r == "A" else 10 if r in ("10", "J", "Q", "K") else int(r)) for r in RANKS}
24
+
25
+ DECKS = 6
26
+ BASE_BET = 10
27
+ START_BANKROLL = 1000
28
+
29
+ AGENTS = [
30
+ {
31
+ "id": "a",
32
+ "name": "阿保",
33
+ "style": "保守算牌派",
34
+ "description": "以保住本金為第一優先,只有在牌面明顯有利時才加注,遇到風險寧可停牌。",
35
+ "url": "http://127.0.0.1:8081",
36
+ },
37
+ {
38
+ "id": "b",
39
+ "name": "阿衝",
40
+ "style": "激進壓迫派",
41
+ "description": "相信進攻是最好的防守,喜歡加倍與大注,願意為了期望值承擔波動。",
42
+ "url": "http://127.0.0.1:8082",
43
+ },
44
+ ]
45
+
46
+ RULES = {
47
+ "decks": DECKS,
48
+ "dealer": "莊家軟 17 停牌 (S17)",
49
+ "blackjack_pays": "3:2",
50
+ "double": "僅限硬 9–11 或軟 13–18 時加倍,加倍後只補一張",
51
+ "split": "本局不開放分牌",
52
+ "insurance": "不開放保險",
53
+ "reshuffle": "剩餘牌數低於 25% 時重洗",
54
+ "start_bankroll": START_BANKROLL,
55
+ }
56
+
57
+
58
+ def make_shoe(rng):
59
+ shoe = [(r, s) for _ in range(DECKS) for s in SUITS for r in RANKS]
60
+ rng.shuffle(shoe)
61
+ return shoe
62
+
63
+
64
+ def card_str(card):
65
+ return f"{card[0]}{card[1]}"
66
+
67
+
68
+ def hand_value(cards):
69
+ total = sum(RANK_VALUE[r] for r, _ in cards)
70
+ soft_aces = sum(1 for r, _ in cards if r == "A")
71
+ while total > 21 and soft_aces:
72
+ total -= 10
73
+ soft_aces -= 1
74
+ return total, soft_aces > 0
75
+
76
+
77
+ def is_blackjack(cards):
78
+ return len(cards) == 2 and hand_value(cards)[0] == 21
79
+
80
+
81
+ def hi_lo(card):
82
+ r = card[0]
83
+ if r in ("2", "3", "4", "5", "6"):
84
+ return 1
85
+ if r in ("10", "J", "Q", "K", "A"):
86
+ return -1
87
+ return 0
88
+
89
+
90
+ def shoe_state(shoe, running_count):
91
+ remaining = len(shoe)
92
+ decks_left = max(remaining / 52.0, 0.15)
93
+ counts = {r: 0 for r in RANKS}
94
+ for r, _ in shoe:
95
+ counts[r] += 1
96
+ return {
97
+ "decks": DECKS,
98
+ "remaining_cards": remaining,
99
+ "penetration_pct": round(100 * (1 - remaining / (DECKS * 52))),
100
+ "cards_left_by_rank": counts,
101
+ "hi_lo_running_count": running_count,
102
+ "true_count": round(running_count / decks_left, 2),
103
+ }
104
+
105
+
106
+ class D1Client:
107
+ def __init__(self, agent):
108
+ self.agent = agent
109
+ self.url = agent["url"].rstrip("/")
110
+ self.calls = 0
111
+ self.total_ms = 0
112
+
113
+ def ask(self, state, questions, retries=2):
114
+ payload = json.dumps({"state": state, "questions": questions}).encode()
115
+ last_err = None
116
+ for _ in range(retries + 1):
117
+ started = time.time()
118
+ req = urllib.request.Request(
119
+ f"{self.url}/v1/systemone",
120
+ data=payload,
121
+ headers={"Content-Type": "application/json"},
122
+ )
123
+ try:
124
+ with urllib.request.urlopen(req, timeout=120) as resp:
125
+ body = json.loads(resp.read())
126
+ elapsed = int((time.time() - started) * 1000)
127
+ self.calls += 1
128
+ self.total_ms += elapsed
129
+ return body["answers"], elapsed, None
130
+ except Exception as exc: # noqa: BLE001
131
+ last_err = f"{type(exc).__name__}: {exc}"
132
+ time.sleep(0.4)
133
+ return None, 0, last_err
134
+
135
+
136
+ def pick(answers, key, valid):
137
+ ans = (answers or {}).get(key) or {}
138
+ choice = ans.get("choice")
139
+ if choice not in valid:
140
+ return None
141
+ return ans
142
+
143
+
144
+ def draw(shoe, count):
145
+ cards = [shoe.pop() for _ in range(min(count, len(shoe)))]
146
+ return cards
147
+
148
+
149
+ def fallback_action(total, soft, allowed):
150
+ if "double" in allowed and total in (10, 11):
151
+ return "double"
152
+ if soft and total >= 19:
153
+ return "stand"
154
+ if total >= 17:
155
+ return "stand"
156
+ return "hit"
157
+
158
+
159
+ def play_hand(client, shoe, player, hand, dealer_up, running_count, rng):
160
+ steps = []
161
+ while True:
162
+ total, soft = hand_value(hand["cards"])
163
+ if total >= 21:
164
+ break
165
+ allowed = ["hit", "stand"]
166
+ double_spot = (not soft and 9 <= total <= 11) or (soft and 13 <= total <= 18)
167
+ if len(hand["cards"]) == 2 and player["bankroll"] >= hand["bet"] and double_spot:
168
+ allowed.append("double")
169
+
170
+ state = {
171
+ "game": "blackjack",
172
+ "you": {
173
+ "name": client.agent["name"],
174
+ "style": client.agent["style"],
175
+ "personality": client.agent["description"],
176
+ "bankroll": player["bankroll"],
177
+ },
178
+ "hand": {
179
+ "cards": [card_str(c) for c in hand["cards"]],
180
+ "total": total,
181
+ "soft": soft,
182
+ "is_pair": len(hand["cards"]) == 2 and hand["cards"][0][0] == hand["cards"][1][0],
183
+ },
184
+ "dealer_upcard": card_str(dealer_up),
185
+ "bet": hand["bet"],
186
+ "allowed_actions": allowed,
187
+ "shoe": shoe_state(shoe, running_count),
188
+ "rules": RULES,
189
+ }
190
+ criteria = {
191
+ "hit": "再補一張牌:點數偏低、需要往 17 點以上推進時使用",
192
+ "stand": "停牌:點數已接近 21,或再補牌爆牌風險過高時使用",
193
+ }
194
+ if "double" in allowed:
195
+ criteria["double"] = "加倍下注:再多押一倍且只補一張,通常用在 9、10、11 點這種有把握的局面"
196
+ cards_text = " ".join(card_str(c) for c in hand["cards"])
197
+ questions = {
198
+ "action": {
199
+ "type": "choice",
200
+ "instructions": (
201
+ f"你是 {client.agent['name']},風格「{client.agent['style']}」:"
202
+ f"{client.agent['description']} "
203
+ "你在玩 21 點,目標是讓手牌點數盡量接近 21 但不超過,並贏過莊家。"
204
+ f"你的手牌是 {cards_text},合計 {total} 點"
205
+ f"({'軟牌,A 可算 11 或 1' if soft else '硬牌'})。"
206
+ f"莊家明牌是 {card_str(dealer_up)}。現在只能選一個動作,請選長期期望值最高的。"
207
+ ),
208
+ "criteria": criteria,
209
+ },
210
+ "risk": {
211
+ "type": "score",
212
+ "instructions": "以你的風格判斷,現在這個局面對你有多不利?",
213
+ "criteria": ["非常有利", "稍微有利", "有點不利", "非常不利"],
214
+ },
215
+ }
216
+ answers, latency, err = client.ask(state, questions)
217
+ decision = pick(answers, "action", allowed)
218
+ if decision is None:
219
+ if not allowed:
220
+ break
221
+ action = fallback_action(total, soft, allowed)
222
+ decision = {
223
+ "type": "choice",
224
+ "choice": action,
225
+ "probabilities": {a: (1.0 if a == action else 0.0) for a in allowed},
226
+ "confidence": 0.0,
227
+ "fallback": True,
228
+ "error": err,
229
+ }
230
+ action = decision["choice"]
231
+ step = {
232
+ "type": "action",
233
+ "cards": [card_str(c) for c in hand["cards"]],
234
+ "total": total,
235
+ "soft": soft,
236
+ "dealer_up": card_str(dealer_up),
237
+ "allowed": allowed,
238
+ "decision": decision,
239
+ "risk": (answers or {}).get("risk"),
240
+ "latency_ms": latency,
241
+ "drawn": None,
242
+ "total_after": total,
243
+ }
244
+ steps.append(step)
245
+
246
+ if action == "stand":
247
+ break
248
+ if action == "double":
249
+ player["bankroll"] -= hand["bet"]
250
+ hand["bet"] *= 2
251
+ hand["doubled"] = True
252
+ new_card = draw(shoe, 1)[0]
253
+ running_count += hi_lo(new_card)
254
+ step["drawn"] = card_str(new_card)
255
+ hand["cards"].append(new_card)
256
+ hand["total"], hand["soft"] = hand_value(hand["cards"])
257
+ step["total_after"] = hand["total"]
258
+ break
259
+ new_card = draw(shoe, 1)[0]
260
+ running_count += hi_lo(new_card)
261
+ step["drawn"] = card_str(new_card)
262
+ hand["cards"].append(new_card)
263
+ hand["total"], hand["soft"] = hand_value(hand["cards"])
264
+ step["total_after"] = hand["total"]
265
+ if hand["total"] > 21:
266
+ break
267
+
268
+ hand["total"], hand["soft"] = hand_value(hand["cards"])
269
+ hand["bust"] = hand["total"] > 21
270
+ hand["blackjack"] = is_blackjack(hand["cards"]) and not hand["doubled"]
271
+ return running_count, steps
272
+
273
+
274
+ def settle(hand, dealer_total, dealer_blackjack, dealer_bust):
275
+ if hand["bust"]:
276
+ return "lose", -hand["bet"]
277
+ if hand["blackjack"] and not dealer_blackjack:
278
+ return "blackjack", int(hand["bet"] * 1.5)
279
+ if dealer_blackjack and not hand["blackjack"]:
280
+ return "lose", -hand["bet"]
281
+ if dealer_bust:
282
+ return "win", hand["bet"]
283
+ if hand["total"] > dealer_total:
284
+ return "win", hand["bet"]
285
+ if hand["total"] < dealer_total:
286
+ return "lose", -hand["bet"]
287
+ return "push", 0
288
+
289
+
290
+ def ask_bet(client, player, dealer_up_hint, shoe, running_count):
291
+ state = {
292
+ "game": "blackjack",
293
+ "you": {
294
+ "name": client.agent["name"],
295
+ "style": client.agent["style"],
296
+ "personality": client.agent["description"],
297
+ "bankroll": player["bankroll"],
298
+ },
299
+ "phase": "betting",
300
+ "base_bet": BASE_BET,
301
+ "shoe": shoe_state(shoe, running_count),
302
+ "rules": RULES,
303
+ }
304
+ questions = {
305
+ "bet": {
306
+ "type": "choice",
307
+ "instructions": (
308
+ f"你是 {client.agent['name']},風格是「{client.agent['style']}」:"
309
+ f"{client.agent['description']} 新的一局要下注了,"
310
+ "請依資金、剩餘牌組成與真數決定注碼。"
311
+ ),
312
+ "criteria": {
313
+ "min": f"最小注 {BASE_BET}",
314
+ "standard": f"標準注 {BASE_BET * 2}",
315
+ "max": f"重注 {BASE_BET * 5}",
316
+ },
317
+ },
318
+ "confidence": {
319
+ "type": "score",
320
+ "instructions": "你對這一注的信心有多高?",
321
+ "criteria": ["沒信心", "普通", "有信心", "非常有信心"],
322
+ },
323
+ }
324
+ answers, latency, err = client.ask(state, questions)
325
+ decision = pick(answers, "bet", ["min", "standard", "max"])
326
+ if decision is None:
327
+ decision = {
328
+ "choice": "min",
329
+ "probabilities": {"min": 1.0, "standard": 0.0, "max": 0.0},
330
+ "confidence": 0.0,
331
+ "fallback": True,
332
+ "error": err,
333
+ }
334
+ amounts = {"min": BASE_BET, "standard": BASE_BET * 2, "max": BASE_BET * 5}
335
+ bet = min(amounts[decision["choice"]], player["bankroll"])
336
+ if bet <= 0:
337
+ bet = min(BASE_BET, player["bankroll"])
338
+ return {
339
+ "decision": decision,
340
+ "bet": max(bet, 0),
341
+ "confidence": (answers or {}).get("confidence"),
342
+ "latency_ms": latency,
343
+ }
344
+
345
+
346
+ def run(rounds, seed, out_path):
347
+ rng = random.Random(seed)
348
+ shoe = make_shoe(rng)
349
+ running_count = 0
350
+ players = [
351
+ {"id": a["id"], "name": a["name"], "style": a["style"], "bankroll": START_BANKROLL}
352
+ for a in AGENTS
353
+ ]
354
+ clients = {a["id"]: D1Client(a) for a in AGENTS}
355
+ started = time.time()
356
+ history = []
357
+
358
+ for n in range(1, rounds + 1):
359
+ if len(shoe) < DECKS * 52 * 0.25:
360
+ shoe = make_shoe(rng)
361
+ running_count = 0
362
+
363
+ rebuys = {}
364
+ for a in AGENTS:
365
+ player = next(p for p in players if p["id"] == a["id"])
366
+ if player["bankroll"] < BASE_BET:
367
+ rebuys[a["id"]] = 500
368
+ player["bankroll"] += 500
369
+
370
+ bets = {}
371
+ for a in AGENTS:
372
+ player = next(p for p in players if p["id"] == a["id"])
373
+ bets[a["id"]] = ask_bet(clients[a["id"]], player, None, shoe, running_count)
374
+
375
+ hands = {}
376
+ for a in AGENTS:
377
+ player = next(p for p in players if p["id"] == a["id"])
378
+ hands[a["id"]] = {
379
+ "cards": draw(shoe, 2),
380
+ "bet": bets[a["id"]]["bet"],
381
+ "doubled": False,
382
+ }
383
+ running_count += sum(hi_lo(c) for c in hands[a["id"]]["cards"])
384
+ player["bankroll"] -= hands[a["id"]]["bet"]
385
+
386
+ dealer_cards = draw(shoe, 2)
387
+ running_count += sum(hi_lo(c) for c in dealer_cards)
388
+ dealer_up, dealer_hole = dealer_cards[0], dealer_cards[1]
389
+ dealer_blackjack = is_blackjack(dealer_cards)
390
+ peek = RANK_VALUE[dealer_up[0]] in (10, 11)
391
+ events = []
392
+ for aid, amount in rebuys.items():
393
+ events.append(f"{next(p for p in players if p['id'] == aid)['name']} 籌碼不足,補充 {amount}")
394
+
395
+ round_rec = {
396
+ "n": n,
397
+ "shoe_before": shoe_state(shoe, running_count),
398
+ "dealer_up": card_str(dealer_up),
399
+ "dealer_hole": card_str(dealer_hole),
400
+ "dealer_revealed": False,
401
+ "dealer": None,
402
+ "events": events,
403
+ "players": [],
404
+ }
405
+ if peek:
406
+ events.append("莊家明牌為 A 或 10 點,進行暗牌確認")
407
+
408
+ if dealer_blackjack:
409
+ events.append("莊家直接拿到 Blackjack,本局立即結束")
410
+ round_rec["dealer_revealed"] = True
411
+ round_rec["dealer"] = {
412
+ "cards": [card_str(c) for c in dealer_cards],
413
+ "total": 21,
414
+ "soft": hand_value(dealer_cards)[1],
415
+ "blackjack": True,
416
+ "bust": False,
417
+ }
418
+ else:
419
+ for a in AGENTS:
420
+ hand = hands[a["id"]]
421
+ player = next(p for p in players if p["id"] == a["id"])
422
+ running_count, steps = play_hand(
423
+ clients[a["id"]], shoe, player, hand, dealer_up, running_count, rng
424
+ )
425
+ hand["steps"] = steps
426
+ hand["player_id"] = a["id"]
427
+
428
+ if not dealer_blackjack:
429
+ dealer_total, dealer_soft = hand_value(dealer_cards)
430
+ if any(not h["bust"] for h in hands.values()):
431
+ if peek:
432
+ events.append("確認結果:莊家沒有 Blackjack")
433
+ while dealer_total < 17:
434
+ card = draw(shoe, 1)[0]
435
+ running_count += hi_lo(card)
436
+ dealer_cards.append(card)
437
+ dealer_total, dealer_soft = hand_value(dealer_cards)
438
+ events.append(f"莊家補牌 {card_str(card)} → {dealer_total} 點")
439
+ events.append(f"莊家最終 {dealer_total} 點" + (",爆牌" if dealer_total > 21 else ""))
440
+ round_rec["dealer_revealed"] = True
441
+ round_rec["dealer"] = {
442
+ "cards": [card_str(c) for c in dealer_cards],
443
+ "total": dealer_total,
444
+ "soft": dealer_soft,
445
+ "blackjack": False,
446
+ "bust": dealer_total > 21,
447
+ }
448
+
449
+ dealer_total = round_rec["dealer"]["total"]
450
+ dealer_bust = round_rec["dealer"]["bust"]
451
+
452
+ for a in AGENTS:
453
+ player = next(p for p in players if p["id"] == a["id"])
454
+ hand = hands[a["id"]]
455
+ hand["blackjack"] = is_blackjack(hand["cards"]) and not hand["doubled"]
456
+ hand["bust"] = hand_value(hand["cards"])[0] > 21
457
+ result, net = settle(hand, dealer_total, dealer_blackjack, dealer_bust)
458
+ hand.update(
459
+ {
460
+ "cards": [card_str(c) for c in hand["cards"]],
461
+ "total": hand_value(hand["cards"])[0],
462
+ "soft": hand_value(hand["cards"])[1],
463
+ "blackjack": is_blackjack(hand["cards"]) and not hand["doubled"],
464
+ "bust": hand_value(hand["cards"])[0] > 21,
465
+ "result": result,
466
+ "net": net,
467
+ "bet_decision": bets[a["id"]],
468
+ }
469
+ )
470
+ player["bankroll"] += hand["bet"] + net
471
+ hand["bankroll_after"] = player["bankroll"]
472
+ if a["id"] in rebuys:
473
+ hand["rebuy"] = rebuys[a["id"]]
474
+ hand["id"] = a["id"]
475
+ hand["name"] = a["name"]
476
+ hand["style"] = a["style"]
477
+ round_rec["players"].append(hand)
478
+
479
+ round_rec["shoe_after"] = shoe_state(shoe, running_count)
480
+ history.append(round_rec)
481
+ print(
482
+ f"round {n:02d} | dealer {dealer_total} | "
483
+ + " | ".join(
484
+ f"{h['name']} {h['total']} {h['result']} net {h['net']:+d} bank {h['bankroll_after']}"
485
+ for h in round_rec["players"]
486
+ ),
487
+ flush=True,
488
+ )
489
+
490
+ meta = {
491
+ "title": "d1 × d1 21 點對戰實錄",
492
+ "subtitle": "兩個 LiquidAI d1-3B 決策模型,透過 llama.cpp 在同一個牌桌上對打",
493
+ "created_at": datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds"),
494
+ "seed": seed,
495
+ "model": "LiquidAI/d1-3B-GGUF",
496
+ "quant": "Q8_0",
497
+ "llama_build": "b11786 (0.7.0-dev)",
498
+ "rules": RULES,
499
+ "start_bankroll": START_BANKROLL,
500
+ "agents": [
501
+ {
502
+ **a,
503
+ "final_bankroll": next(p for p in players if p["id"] == a["id"])["bankroll"],
504
+ }
505
+ for a in AGENTS
506
+ ],
507
+ "rounds_played": len(history),
508
+ "duration_sec": round(time.time() - started, 2),
509
+ "inference": {
510
+ a["id"]: {
511
+ "calls": clients[a["id"]].calls,
512
+ "avg_latency_ms": round(clients[a["id"]].total_ms / max(clients[a["id"]].calls, 1)),
513
+ }
514
+ for a in AGENTS
515
+ },
516
+ }
517
+ data = {"meta": meta, "rounds": history}
518
+ out = Path(out_path)
519
+ out.parent.mkdir(parents=True, exist_ok=True)
520
+ out.write_text(json.dumps(data, ensure_ascii=False, indent=1), encoding="utf-8")
521
+ print(f"wrote {out} ({out.stat().st_size / 1024:.1f} KiB)")
522
+ return data
523
+
524
+
525
+ def main():
526
+ parser = argparse.ArgumentParser()
527
+ parser.add_argument("--rounds", type=int, default=12)
528
+ parser.add_argument("--seed", type=int, default=20261021)
529
+ parser.add_argument("--out", default="battle.json")
530
+ args = parser.parse_args()
531
+ run(args.rounds, args.seed, args.out)
532
+
533
+
534
+ if __name__ == "__main__":
535
+ main()
source/build_site.py ADDED
@@ -0,0 +1,753 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Turn a battle.json produced by blackjack.py into a static website.
3
+
4
+ Usage:
5
+ python3 build_site.py --battle battle.json --out site
6
+ """
7
+ import argparse
8
+ import json
9
+ import shutil
10
+ from datetime import datetime, timezone
11
+ from pathlib import Path
12
+
13
+ ROOT = Path(__file__).resolve().parent
14
+
15
+ HTML = r"""<!doctype html>
16
+ <html lang="zh-Hant">
17
+ <head>
18
+ <meta charset="utf-8">
19
+ <meta name="viewport" content="width=device-width, initial-scale=1">
20
+ <title>__TITLE__</title>
21
+ <meta name="description" content="__SUBTITLE__">
22
+ <style>
23
+ :root{
24
+ --bg:#07120e; --bg2:#0b1c16; --panel:#0e241c; --panel2:#123026;
25
+ --line:rgba(255,255,255,.09); --text:#e8f3ed; --muted:#8fae9f;
26
+ --gold:#f6c453; --green:#39d98a; --red:#ff6b6b; --blue:#5cc8ff; --amber:#ffb020;
27
+ --hit:#ff6b6b; --stand:#5cc8ff; --double:#f6c453;
28
+ --shadow:0 18px 50px rgba(0,0,0,.45);
29
+ }
30
+ *{box-sizing:border-box}
31
+ html,body{margin:0;padding:0}
32
+ body{
33
+ background:
34
+ radial-gradient(1100px 700px at 12% -10%, rgba(57,217,138,.13), transparent 60%),
35
+ radial-gradient(900px 600px at 92% 0%, rgba(246,196,83,.10), transparent 55%),
36
+ var(--bg);
37
+ color:var(--text);
38
+ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans TC","PingFang TC","Microsoft JhengHei",sans-serif;
39
+ line-height:1.6; min-height:100vh;
40
+ }
41
+ a{color:var(--gold)}
42
+ button{font:inherit;color:inherit;cursor:pointer}
43
+ .topbar{
44
+ position:sticky;top:0;z-index:50;backdrop-filter:blur(14px);
45
+ background:rgba(7,18,14,.8);border-bottom:1px solid var(--line);
46
+ }
47
+ .topbar-inner{max-width:1180px;margin:0 auto;padding:12px 20px;display:flex;gap:18px;align-items:center;flex-wrap:wrap}
48
+ .brand{display:flex;gap:10px;align-items:center;font-weight:800;letter-spacing:.02em}
49
+ .brand .logo{
50
+ width:30px;height:30px;border-radius:9px;display:grid;place-items:center;
51
+ background:linear-gradient(145deg,#f6c453,#b8860b);color:#1b1200;font-weight:900;
52
+ }
53
+ nav{display:flex;gap:4px;margin-left:auto;flex-wrap:wrap}
54
+ nav button{
55
+ border:1px solid transparent;background:transparent;padding:8px 14px;border-radius:999px;
56
+ color:var(--muted);font-weight:600;
57
+ }
58
+ nav button:hover{color:var(--text);background:rgba(255,255,255,.05)}
59
+ nav button.active{color:#1b1200;background:var(--gold);border-color:var(--gold)}
60
+ main{max-width:1180px;margin:0 auto;padding:28px 20px 60px}
61
+ .view{display:none}
62
+ .view.active{display:block;animation:fade .25s ease}
63
+ @keyframes fade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
64
+ .hero{padding:18px 0 6px}
65
+ .hero h1{font-size:clamp(30px,4vw,50px);line-height:1.15;margin:0 0 10px;letter-spacing:-.01em}
66
+ .hero h1 em{font-style:normal;color:var(--gold)}
67
+ .hero p{color:var(--muted);margin:0;max-width:760px;font-size:16px}
68
+ .chips{display:flex;gap:8px;flex-wrap:wrap;margin-top:18px}
69
+ .chip{
70
+ font-size:12.5px;color:#cfe4d9;background:rgba(255,255,255,.045);
71
+ border:1px solid var(--line);border-radius:999px;padding:5px 11px;
72
+ }
73
+ .chip b{color:var(--gold)}
74
+ .scoreboard{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin:22px 0 24px}
75
+ .score{
76
+ position:relative;overflow:hidden;border:1px solid var(--line);border-radius:18px;
77
+ padding:18px 18px 16px;background:linear-gradient(160deg,var(--panel2),var(--panel));
78
+ box-shadow:var(--shadow);
79
+ }
80
+ .score:before{content:"";position:absolute;inset:0 auto 0 0;width:4px;background:var(--accent,var(--gold))}
81
+ .score .id{font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:var(--muted)}
82
+ .score .name{font-size:22px;font-weight:800;margin:2px 0 0}
83
+ .score .style{font-size:13px;color:var(--accent,var(--gold))}
84
+ .score .bank{font-size:34px;font-weight:900;letter-spacing:-.02em;margin-top:10px;font-variant-numeric:tabular-nums}
85
+ .score .sub{font-size:13px;color:var(--muted)}
86
+ .big-delta{font-weight:800}
87
+ .pos{color:var(--green)}.neg{color:var(--red)}.neutral{color:var(--muted)}
88
+ .panel{
89
+ border:1px solid var(--line);border-radius:20px;background:linear-gradient(180deg,rgba(255,255,255,.025),rgba(0,0,0,.12));
90
+ box-shadow:var(--shadow);
91
+ }
92
+ .table-panel{padding:16px}
93
+ .round-toolbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:14px}
94
+ .icon-btn{
95
+ border:1px solid var(--line);background:rgba(255,255,255,.05);border-radius:10px;
96
+ padding:8px 13px;font-weight:700;color:var(--text);
97
+ }
98
+ .icon-btn:hover{background:rgba(255,255,255,.1)}
99
+ .icon-btn:disabled{opacity:.35;cursor:not-allowed}
100
+ .round-pills{display:flex;gap:6px;overflow-x:auto;padding:2px;max-width:100%;scrollbar-width:thin}
101
+ .pill{
102
+ flex:0 0 auto;min-width:38px;height:34px;border-radius:9px;border:1px solid var(--line);
103
+ background:rgba(255,255,255,.04);font-weight:800;font-size:13px;color:var(--muted);
104
+ display:grid;place-items:center;padding:0 8px;
105
+ }
106
+ .pill:hover{border-color:rgba(246,196,83,.5);color:var(--text)}
107
+ .pill.active{background:var(--gold);border-color:var(--gold);color:#1b1200}
108
+ .pill .dot{width:6px;height:6px;border-radius:50%;display:block;margin:0 auto 2px}
109
+ .pill.win .dot{background:var(--green)}
110
+ .pill.lose .dot{background:var(--red)}
111
+ .pill.mixed .dot{background:var(--amber)}
112
+ .pill.push .dot{background:#93a89d}
113
+ .round-label{margin-left:auto;color:var(--muted);font-size:13px;font-variant-numeric:tabular-nums}
114
+ .round-head{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin-bottom:10px}
115
+ .round-head h2{margin:0;font-size:20px}
116
+ .round-head .shoe{color:var(--muted);font-size:12.5px}
117
+ .dealer{
118
+ border:1px solid rgba(246,196,83,.25);border-radius:16px;padding:14px 16px;margin-bottom:14px;
119
+ background:radial-gradient(700px 200px at 20% 0%,rgba(246,196,83,.1),transparent 70%),rgba(0,0,0,.18);
120
+ display:flex;gap:16px;align-items:center;flex-wrap:wrap;
121
+ }
122
+ .dealer .label{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--gold);font-weight:800}
123
+ .dealer .total-badge{font-size:26px;font-weight:900;font-variant-numeric:tabular-nums}
124
+ .dealer .note{color:var(--muted);font-size:13px;flex:1;min-width:180px}
125
+ .players{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
126
+ .player{
127
+ border:1px solid var(--line);border-radius:16px;padding:15px;background:rgba(255,255,255,.025);
128
+ }
129
+ .player.win{border-color:rgba(57,217,138,.45);box-shadow:inset 0 0 0 1px rgba(57,217,138,.12)}
130
+ .player.lose{border-color:rgba(255,107,107,.35)}
131
+ .player.blackjack{border-color:rgba(246,196,83,.6)}
132
+ .p-head{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;flex-wrap:wrap}
133
+ .p-head h3{margin:0;font-size:18px}
134
+ .p-head .style{font-size:12.5px;color:var(--accent,var(--gold))}
135
+ .p-head .bet{
136
+ font-size:12.5px;color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:4px 10px;
137
+ background:rgba(0,0,0,.2);white-space:nowrap;
138
+ }
139
+ .p-head .bet b{color:var(--gold);font-size:15px}
140
+ .hand{display:flex;gap:14px;align-items:center;margin:12px 0 8px;flex-wrap:wrap}
141
+ .cards{display:flex;gap:6px;flex-wrap:wrap}
142
+ .card{
143
+ width:52px;height:72px;border-radius:8px;background:#f7f7f2;color:#141414;
144
+ display:flex;flex-direction:column;align-items:center;justify-content:center;
145
+ font-weight:900;box-shadow:0 6px 14px rgba(0,0,0,.35);border:1px solid rgba(0,0,0,.15);
146
+ line-height:1;user-select:none;
147
+ }
148
+ .card .r{font-size:21px}
149
+ .card .s{font-size:17px;margin-top:2px}
150
+ .card.red{color:#c0182b}
151
+ .card.down{background:repeating-linear-gradient(45deg,#123c6b,#123c6b 5px,#0b2a4d 5px,#0b2a4d 10px);color:transparent;border-color:#0a2340}
152
+ .total{
153
+ font-size:30px;font-weight:900;font-variant-numeric:tabular-nums;display:flex;align-items:baseline;gap:6px;
154
+ }
155
+ .total small{font-size:11px;font-weight:700;color:var(--muted);border:1px solid var(--line);border-radius:5px;padding:1px 5px}
156
+ .result{
157
+ display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:800;
158
+ padding:3px 10px;border-radius:999px;border:1px solid var(--line);
159
+ }
160
+ .result.win{color:var(--green);border-color:rgba(57,217,138,.4);background:rgba(57,217,138,.1)}
161
+ .result.lose{color:var(--red);border-color:rgba(255,107,107,.4);background:rgba(255,107,107,.08)}
162
+ .result.push{color:#b9c9c0;background:rgba(255,255,255,.05)}
163
+ .result.blackjack{color:var(--gold);border-color:rgba(246,196,83,.5);background:rgba(246,196,83,.1)}
164
+ .bet-line{display:flex;gap:8px;align-items:center;flex-wrap:wrap;font-size:12.5px;color:var(--muted);margin:6px 0 2px}
165
+ .bet-line .tag{border:1px solid var(--line);border-radius:6px;padding:1px 7px;color:#cfe4d9}
166
+ details{margin-top:8px}
167
+ summary{cursor:pointer;color:var(--muted);font-size:12.5px;font-weight:700}
168
+ summary:hover{color:var(--text)}
169
+ details .body{padding:8px 0 0;color:var(--muted);font-size:13px}
170
+ .steps{list-style:none;margin:12px 0 0;padding:0;display:flex;flex-direction:column;gap:8px}
171
+ .step{
172
+ border:1px solid var(--line);border-radius:11px;padding:9px 11px;background:rgba(0,0,0,.2);
173
+ }
174
+ .step.enter{animation:slide .3s ease}
175
+ @keyframes slide{from{opacity:0;transform:translateX(-6px)}to{opacity:1;transform:none}}
176
+ .step-head{display:flex;gap:8px;align-items:center;flex-wrap:wrap;font-size:12.5px}
177
+ .step-no{color:var(--muted);font-weight:800;font-variant-numeric:tabular-nums}
178
+ .mini{font-weight:800;font-variant-numeric:tabular-nums}
179
+ .action{
180
+ font-weight:900;font-size:12px;letter-spacing:.04em;padding:2px 9px;border-radius:6px;color:#0b0f0d;
181
+ }
182
+ .action.hit{background:var(--hit)}.action.stand{background:var(--stand)}.action.double{background:var(--double)}
183
+ .action.unknown{background:#7a8a81}
184
+ .step .meta{margin-left:auto;color:var(--muted);font-size:11.5px;display:flex;gap:8px;flex-wrap:wrap}
185
+ .probs{display:grid;grid-template-columns:64px 1fr 46px;gap:6px;align-items:center;margin-top:7px;font-size:11.5px;color:var(--muted)}
186
+ .bar{height:7px;border-radius:4px;background:rgba(255,255,255,.07);overflow:hidden}
187
+ .bar>i{display:block;height:100%;border-radius:4px;background:var(--c,var(--gold));width:0;transition:width .5s ease}
188
+ .probs .pct{text-align:right;font-variant-numeric:tabular-nums;color:#d6e7de}
189
+ .step-foot{display:flex;gap:10px;flex-wrap:wrap;color:var(--muted);font-size:11.5px;margin-top:7px}
190
+ .step-foot b{color:#d6e7de}
191
+ .net{margin-top:11px;padding-top:10px;border-top:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;font-size:13px}
192
+ .net .amt{font-size:19px;font-weight:900;font-variant-numeric:tabular-nums}
193
+ .events{margin-top:14px;border-top:1px dashed var(--line);padding-top:10px;color:var(--muted);font-size:12.5px}
194
+ .events ul{margin:6px 0 0;padding-left:18px}
195
+ .events li{margin:2px 0}
196
+ .arena-note{margin-top:14px;color:var(--muted);font-size:12px}
197
+ .section-title{font-size:22px;margin:26px 0 12px}
198
+ .stats-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
199
+ .stat-card{border:1px solid var(--line);border-radius:16px;padding:16px;background:rgba(255,255,255,.025)}
200
+ .stat-card h3{margin:0 0 4px;font-size:17px}
201
+ .stat-card .style{font-size:12.5px;color:var(--accent,var(--gold))}
202
+ .kv{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin-top:12px}
203
+ .kv .k{font-size:11.5px;color:var(--muted)}
204
+ .kv .v{font-size:19px;font-weight:800;font-variant-numeric:tabular-nums}
205
+ .dist{margin-top:12px}
206
+ .dist .row{display:grid;grid-template-columns:66px 1fr 44px;gap:7px;align-items:center;font-size:12px;margin:5px 0;color:var(--muted)}
207
+ .mini-title{font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin-top:14px}
208
+ .chart-wrap{margin-top:14px;border:1px solid var(--line);border-radius:16px;padding:14px;background:rgba(0,0,0,.18)}
209
+ .chart-legend{display:flex;gap:14px;font-size:12px;color:var(--muted);flex-wrap:wrap;margin-bottom:6px}
210
+ .chart-legend i{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:5px}
211
+ .h2h{margin-top:14px;border:1px solid var(--line);border-radius:16px;padding:16px;background:rgba(255,255,255,.025)}
212
+ .h2h .big{font-size:30px;font-weight:900;color:var(--gold)}
213
+ .code-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}
214
+ .code-tabs button{border:1px solid var(--line);background:rgba(255,255,255,.04);border-radius:8px;padding:6px 12px;font-size:12.5px;color:var(--muted)}
215
+ .code-tabs button.active{background:var(--gold);color:#1b1200;border-color:var(--gold);font-weight:800}
216
+ .code-bar{display:flex;gap:10px;align-items:center;margin-bottom:8px;color:var(--muted);font-size:12.5px;flex-wrap:wrap}
217
+ pre.code{
218
+ margin:0;border:1px solid var(--line);border-radius:14px;background:#04100c;padding:16px;
219
+ overflow:auto;max-height:70vh;font-size:12.5px;line-height:1.55;
220
+ }
221
+ pre.code code{font-family:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,monospace}
222
+ .data-box{border:1px solid var(--line);border-radius:16px;padding:16px;background:rgba(255,255,255,.025);margin-bottom:14px}
223
+ .data-box h3{margin:0 0 8px;font-size:17px}
224
+ .data-box p{color:var(--muted);margin:6px 0;font-size:14px}
225
+ .btn{
226
+ display:inline-flex;align-items:center;gap:7px;border:1px solid rgba(246,196,83,.45);
227
+ background:rgba(246,196,83,.1);color:var(--gold);padding:7px 13px;border-radius:9px;
228
+ text-decoration:none;font-weight:700;font-size:13px;
229
+ }
230
+ .btn:hover{background:rgba(246,196,83,.18)}
231
+ .btn.ghost{border-color:var(--line);background:rgba(255,255,255,.04);color:var(--text)}
232
+ footer{border-top:1px solid var(--line);margin-top:30px;padding:22px 20px 40px;color:var(--muted);font-size:12.5px;text-align:center}
233
+ .loading{padding:40px;text-align:center;color:var(--muted)}
234
+ .err{border:1px solid rgba(255,107,107,.4);background:rgba(255,107,107,.08);padding:16px;border-radius:12px;color:#ffd4d4}
235
+ @media (max-width:760px){
236
+ .scoreboard,.stats-grid,.players{grid-template-columns:1fr}
237
+ .card{width:44px;height:62px}
238
+ .card .r{font-size:18px}
239
+ .total{font-size:25px}
240
+ .round-label{margin-left:0}
241
+ }
242
+ </style>
243
+ </head>
244
+ <body>
245
+ <header class="topbar">
246
+ <div class="topbar-inner">
247
+ <div class="brand"><span class="logo">21</span><span>d1 × d1 對戰實驗室</span></div>
248
+ <nav>
249
+ <button data-view="battle" class="active">對戰</button>
250
+ <button data-view="stats">統計</button>
251
+ <button data-view="code">整理程式</button>
252
+ <button data-view="data">原始資料</button>
253
+ </nav>
254
+ </div>
255
+ </header>
256
+
257
+ <main>
258
+ <div id="app"><div class="loading">載入對戰資料中…</div></div>
259
+ </main>
260
+
261
+ <footer>
262
+ <div>由 llama.cpp 上的兩個 <b>LiquidAI d1-3B</b> 決策模型自動產生 · 頁面由 build_site.py 靜態編譯</div>
263
+ <div style="margin-top:6px">Hosted on Hugging Face Static Space · <a href="https://huggingface.co/spaces/HelloSun/d1demo">HelloSun/d1demo</a></div>
264
+ </footer>
265
+
266
+ <script>
267
+ const SUIT_RED = new Set(["♥","♦"]);
268
+ const LABEL = {hit:"補牌", stand:"停牌", double:"加倍", min:"最小注", standard:"標準注", max:"重注"};
269
+ const VALUE = {A:11,"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9,"10":10,J:10,Q:10,K:10};
270
+ const RESULT_LABEL = {win:"勝", lose:"負", push:"和局", blackjack:"Blackjack"};
271
+ const $ = (s,el=document)=>el.querySelector(s);
272
+ let DATA=null, roundIdx=0, autoTimer=null, dealerOpen=true;
273
+
274
+ function money(n){ return (n>0?"+":"") + n.toLocaleString("en-US"); }
275
+ function cls(n){ return n>0?"pos":(n<0?"neg":"neutral"); }
276
+ function pct(x){ return (Math.max(0,Math.min(1,x||0))*100).toFixed(0)+"%"; }
277
+ function cardHTML(card, hidden){
278
+ if(hidden) return '<div class="card down">?</div>';
279
+ const r=card.slice(0,-1), s=card.slice(-1);
280
+ return '<div class="card '+(SUIT_RED.has(s)?"red":"")+'"><span class="r">'+r+'</span><span class="s">'+s+'</span></div>';
281
+ }
282
+ function cardsHTML(cards, opts={}){
283
+ return '<div class="cards">'+cards.map((c,i)=>cardHTML(c, opts.hideIndex===i)).join("")+'</div>';
284
+ }
285
+ function agentColor(id){ return id==="a" ? "var(--green)" : "var(--blue)"; }
286
+ function agentMeta(id){ return DATA.meta.agents.find(a=>a.id===id) || {name:id,style:""}; }
287
+
288
+ function totalsFor(round){
289
+ const out={};
290
+ for(const p of round.players){
291
+ out[p.id]=out[p.id]||{bet:0,net:0};
292
+ out[p.id].bet+=p.bet; out[p.id].net+=p.net;
293
+ }
294
+ return out;
295
+ }
296
+
297
+ function init(){
298
+ DATA = window.BATTLE_DATA;
299
+ if(!DATA){ $("#app").innerHTML='<div class="err">找不到 battle.json,請用 HTTP 伺服器開啟此頁。</div>'; return; }
300
+ document.title = DATA.meta.title + " · d1demo";
301
+ $("#app").innerHTML = `
302
+ <section class="view active" id="view-battle">
303
+ <div class="hero">
304
+ <h1>${DATA.meta.title.split(" ")[0]} <em>×</em> d1:21 點決策對戰</h1>
305
+ <p>${DATA.meta.subtitle}。每一注、每一次補牌/停牌/加倍,都是模型即時輸出的機率分佈,不是寫死的策略表。</p>
306
+ <div class="chips" id="metaChips"></div>
307
+ </div>
308
+ <div class="scoreboard" id="scoreboard"></div>
309
+ <div class="panel table-panel">
310
+ <div class="round-toolbar">
311
+ <button class="icon-btn" id="prevRound">← 上一局</button>
312
+ <div class="round-pills" id="roundPills"></div>
313
+ <button class="icon-btn" id="nextRound">下一局 →</button>
314
+ <button class="icon-btn" id="playBtn">▶ 自動播放</button>
315
+ <span class="round-label" id="roundLabel"></span>
316
+ </div>
317
+ <div id="roundView"></div>
318
+ <div class="arena-note">莊家由固定規則操作(S17),兩個玩家各自連到一個獨立的 llama.cpp server;模型只知道牌面、剩餘牌組成、真數與資金,不知道對手的手牌。</div>
319
+ </div>
320
+ </section>
321
+
322
+ <section class="view" id="view-stats"></section>
323
+ <section class="view" id="view-code">
324
+ <h2 class="section-title">整理程式</h2>
325
+ <div class="data-box">
326
+ <p>這一頁不是手寫的靜態稿:<b>blackjack.py</b> 負責發牌、呼叫兩個 d1 server 做決策並記錄每一步;<b>build_site.py</b> 把 battle.json 編譯成你現在看到的網站。原始碼同步放在這個 Space 的 <code>source/</code> 目錄。</p>
327
+ </div>
328
+ <div class="code-tabs" id="codeTabs"></div>
329
+ <div class="code-bar">
330
+ <span id="codeName">blackjack.py</span>
331
+ <a class="btn ghost" id="codeDownload" href="source/blackjack.py" download>下載此檔</a>
332
+ <button class="icon-btn" id="copyCode">複製</button>
333
+ </div>
334
+ <pre class="code"><code id="codeBody">載入中…</code></pre>
335
+ </section>
336
+
337
+ <section class="view" id="view-data">
338
+ <h2 class="section-title">原始資料</h2>
339
+ <div class="data-box">
340
+ <h3>battle.json</h3>
341
+ <p>完整對戰紀錄:每一局的牌、注碼、決策機率、信心度、風險評分、延遲、莊家補牌與資金變化。</p>
342
+ <p><a class="btn" href="battle.json" download>下載 battle.json</a>
343
+ <a class="btn ghost" href="battle.json" target="_blank" rel="noopener">在瀏覽器開啟</a></p>
344
+ </div>
345
+ <div class="data-box">
346
+ <h3>重現方式</h3>
347
+ <p>需要 llama.cpp b11786 以上版本、LiquidAI/d1-3B-GGUF(Q8_0)與 Python 3。</p>
348
+ <pre class="code"><code id="repro"></code></pre>
349
+ </div>
350
+ <div class="data-box">
351
+ <h3>本次執行摘要</h3>
352
+ <pre class="code"><code id="metaJson"></code></pre>
353
+ </div>
354
+ </section>
355
+ <footer style="border:0;padding-bottom:0"></footer>
356
+ `;
357
+ renderChips();
358
+ renderScoreboard();
359
+ renderPills();
360
+ renderRound();
361
+ renderStats();
362
+ renderCodeTabs();
363
+ bind();
364
+ }
365
+
366
+ function renderChips(){
367
+ const m=DATA.meta;
368
+ const chips=[
369
+ ["模型",m.model],["量化",m.quant],["llama.cpp",m.llama_build],
370
+ ["局數",m.rounds_played],["隨機種子",m.seed],["耗時",m.duration_sec+"s"],
371
+ ["莊家","S17"],["BJ 賠率","3:2"]
372
+ ];
373
+ $("#metaChips").innerHTML=chips.map(([k,v])=>`<span class="chip">${k} <b>${v}</b></span>`).join("");
374
+ }
375
+
376
+ function renderScoreboard(){
377
+ const agents=DATA.meta.agents;
378
+ $("#scoreboard").innerHTML=agents.map(a=>{
379
+ const last=DATA.rounds[DATA.rounds.length-1];
380
+ const p=last.players.find(x=>x.id===a.id);
381
+ const profit=a.final_bankroll-DATA.meta.start_bankroll;
382
+ const color=agentColor(a.id);
383
+ return `<div class="score" style="--accent:${color}">
384
+ <div class="id">PLAYER ${a.id.toUpperCase()}</div>
385
+ <div class="name">${a.name}</div>
386
+ <div class="style">${a.style}</div>
387
+ <div class="bank" style="color:${color}">${a.final_bankroll.toLocaleString("en-US")}</div>
388
+ <div class="sub">起始 ${DATA.meta.start_bankroll} · 損益 <span class="big-delta ${cls(profit)}">${money(profit)}</span></div>
389
+ <div class="sub">最終一局:${p?(RESULT_LABEL[p.result]||p.result):""} ${p?money(p.net):""}</div>
390
+ </div>`;
391
+ }).join("");
392
+ }
393
+
394
+ function roundTone(r){
395
+ const res=r.players.map(p=>p.result);
396
+ const wins=res.filter(x=>x==="win"||x==="blackjack").length;
397
+ const loses=res.filter(x=>x==="lose").length;
398
+ if(wins===res.length) return "win";
399
+ if(loses===res.length) return "lose";
400
+ if(res.every(x=>x==="push")) return "push";
401
+ return "mixed";
402
+ }
403
+
404
+ function renderPills(){
405
+ $("#roundPills").innerHTML=DATA.rounds.map((r,i)=>{
406
+ return `<button class="pill ${roundTone(r)} ${i===roundIdx?"active":""}" data-round="${i}" title="第 ${r.n} 局">
407
+ <span class="dot"></span>${r.n}</button>`;
408
+ }).join("");
409
+ $("#roundPills").querySelectorAll(".pill").forEach(b=>b.onclick=()=>{stopAuto();roundIdx=+b.dataset.round;dealerOpen=true;renderRound();renderPills();});
410
+ }
411
+
412
+ function stepHTML(step, i){
413
+ const d=step.decision||{};
414
+ const probs=d.probabilities||{};
415
+ const keys=Object.keys(probs).sort((a,b)=>probs[b]-probs[a]);
416
+ const risk=step.risk;
417
+ const riskText=risk ? `風險評分 <b>${risk.score.toFixed(2)}</b>/3 · 信心 ${pct(risk.confidence)}` : "";
418
+ const drawn=step.drawn ? `補到 <b>${step.drawn}</b> → <b>${step.total_after}</b> 點` : (d.choice==="stand"?"維持 <b>"+step.total+"</b> 點":`→ <b>${step.total_after}</b> 點`);
419
+ return `<li class="step">
420
+ <div class="step-head">
421
+ <span class="step-no">#${i+1}</span>
422
+ <span class="mini">${step.cards.join(" ")}</span>
423
+ <span class="mini" style="color:var(--muted)">(${step.total}${step.soft?" 軟":""})</span>
424
+ <span class="action ${LABEL[d.choice]?d.choice:"unknown"}">${LABEL[d.choice]||d.choice||"?"}</span>
425
+ <span class="meta">信心 ${pct(d.confidence)} · ${((step.latency_ms||0)/1000).toFixed(2)}s</span>
426
+ </div>
427
+ <div class="probs">
428
+ ${keys.map(k=>`<span>${LABEL[k]||k}</span><span class="bar"><i style="--c:var(--${k==="hit"?"hit":k==="stand"?"stand":"double"});width:${(probs[k]*100).toFixed(1)}%"></i></span><span class="pct">${pct(probs[k])}</span>`).join("")}
429
+ </div>
430
+ <div class="step-foot"><span>${drawn}</span>${riskText?`<span>${riskText}</span>`:""}${d.fallback?`<span style="color:var(--red)">伺服器無回應,改用規則備援</span>`:""}</div>
431
+ </li>`;
432
+ }
433
+
434
+ function playerHTML(p){
435
+ const steps=(p.steps||[]).map(stepHTML).join("");
436
+ const bd=p.bet_decision||{};
437
+ const betDecision=bd.decision||{};
438
+ const betProbs=betDecision.probabilities||{};
439
+ const betLabel=LABEL[betDecision.choice]||betDecision.choice||"";
440
+ return `<article class="player ${p.result} ${p.blackjack?"blackjack":""}" style="--accent:${agentColor(p.id)}">
441
+ <div class="p-head">
442
+ <div><h3>${p.name}</h3><div class="style">${p.style||""}</div></div>
443
+ <div class="bet">注碼 <b>${p.bet}</b>${p.doubled?" · 已加倍":""}</div>
444
+ </div>
445
+ <div class="hand">
446
+ ${cardsHTML(p.cards)}
447
+ <div class="total">${p.total}<small>${p.bust?"BUST":p.soft?"軟牌":p.blackjack?"BJ":"硬牌"}</small></div>
448
+ <span class="result ${p.result}">${RESULT_LABEL[p.result]||p.result}</span>
449
+ </div>
450
+ <div class="bet-line">
451
+ <span class="tag">注碼決策 ${betLabel}</span>
452
+ ${p.rebuy?`<span class="tag" style="color:var(--amber)">補充籌碼 +${p.rebuy}</span>`:""}
453
+ <span>信心 ${pct(betDecision.confidence)}</span>
454
+ ${Object.keys(betProbs).length?`<span>${Object.keys(betProbs).sort((a,b)=>betProbs[b]-betProbs[a]).map(k=>`${LABEL[k]||k} ${pct(betProbs[k])}`).join(" · ")}</span>`:""}
455
+ </div>
456
+ <details><summary>看模型怎麼決定注碼</summary>
457
+ <div class="body">狀態包含資金、剩余牌、Hi-Lo 真數與規則;模型在 min / standard / max 三種注碼中挑一個,並另外給出信心評分。</div>
458
+ </details>
459
+ ${steps?`<ol class="steps">${steps}</ol>`:`<div class="step-foot" style="margin-top:12px">莊家直接 Blackjack,玩家沒有動作機會。</div>`}
460
+ <div class="net"><span>本局結果</span><span class="amt ${cls(p.net)}">${money(p.net)} <small style="font-size:12px;color:var(--muted)">→ ${p.bankroll_after}</small></span></div>
461
+ </article>`;
462
+ }
463
+
464
+ function renderRound(){
465
+ const r=DATA.rounds[roundIdx];
466
+ if(!r){ return; }
467
+ const d=r.dealer||{};
468
+ const hideHole=!dealerOpen && r.dealer_revealed;
469
+ const dealerCards=(d.cards||[]).map((c,i)=>cardHTML(c, hideHole && i===1)).join("");
470
+ const totals=totalsFor(r);
471
+ const pillTone=roundTone(r);
472
+ $("#roundLabel").textContent=`第 ${r.n} / ${DATA.rounds.length} 局 · 剩餘 ${r.shoe_before.remaining_cards} 張 · 真數 ${r.shoe_before.true_count}`;
473
+ $("#roundView").innerHTML=`
474
+ <div class="round-head">
475
+ <h2>第 ${r.n} 局</h2>
476
+ <span class="shoe">洗牌前剩餘 ${r.shoe_before.remaining_cards} 張(穿透率 ${r.shoe_before.penetration_pct}%) · Hi-Lo 真數 ${r.shoe_before.true_count}</span>
477
+ </div>
478
+ <div class="dealer">
479
+ <div>
480
+ <div class="label">DEALER · 莊家</div>
481
+ <div class="total-badge">${hideHole?"?":d.total}${d.bust?' <span style="font-size:12px;color:var(--red)">BUST</span>':""}${d.blackjack?' <span style="font-size:12px;color:var(--gold)">BJ</span>':""}</div>
482
+ </div>
483
+ <div class="cards">${dealerCards}</div>
484
+ <div class="note">${r.events.join(" · ")||"莊家依規則補牌"}</div>
485
+ ${r.dealer_revealed?`<button class="icon-btn" id="revealBtn">${dealerOpen?"蓋回暗牌":"揭開暗牌"}</button>`:""}
486
+ </div>
487
+ <div class="players">${r.players.map(playerHTML).join("")}</div>
488
+ <div class="events"><b>牌桌事件</b><ul>${r.events.map(e=>`<li>${e}</li>`).join("")}</ul></div>
489
+ `;
490
+ const rb=$("#revealBtn");
491
+ if(rb) rb.onclick=()=>{dealerOpen=!dealerOpen;renderRound();};
492
+ $("#prevRound").disabled=roundIdx===0;
493
+ $("#nextRound").disabled=roundIdx===DATA.rounds.length-1;
494
+ document.querySelectorAll(".pill").forEach((b,i)=>b.classList.toggle("active",i===roundIdx));
495
+ }
496
+
497
+ function bind(){
498
+ $("#prevRound").onclick=()=>{stopAuto(); if(roundIdx>0){roundIdx--;dealerOpen=true;renderRound();}};
499
+ $("#nextRound").onclick=()=>{stopAuto(); if(roundIdx<DATA.rounds.length-1){roundIdx++;dealerOpen=true;renderRound();}};
500
+ $("#playBtn").onclick=()=>{
501
+ if(autoTimer){stopAuto();return;}
502
+ roundIdx=0;dealerOpen=true;renderRound();
503
+ $("#playBtn").textContent="⏸ 暫停";
504
+ autoTimer=setInterval(()=>{
505
+ if(roundIdx>=DATA.rounds.length-1){stopAuto();return;}
506
+ roundIdx++;dealerOpen=true;renderRound();
507
+ },4000);
508
+ };
509
+ document.querySelectorAll("nav button").forEach(b=>{
510
+ b.onclick=()=>{
511
+ document.querySelectorAll("nav button").forEach(x=>x.classList.toggle("active",x===b));
512
+ document.querySelectorAll(".view").forEach(v=>v.classList.toggle("active",v.id==="view-"+b.dataset.view));
513
+ window.scrollTo({top:0,behavior:"smooth"});
514
+ };
515
+ });
516
+ }
517
+ function stopAuto(){ if(autoTimer){clearInterval(autoTimer);autoTimer=null;} const b=$("#playBtn"); if(b)b.textContent="▶ 自動播放"; }
518
+
519
+ function statsFor(id){
520
+ const s={rounds:0,win:0,lose:0,push:0,bj:0,bust:0,double:0,net:0,wagered:0,steps:0,conf:0,lat:0,actions:{hit:0,stand:0,double:0},firstActions:[]};
521
+ for(const r of DATA.rounds){
522
+ const p=r.players.find(x=>x.id===id); if(!p) continue;
523
+ s.rounds++;
524
+ if(p.result==="win")s.win++;
525
+ else if(p.result==="lose")s.lose++;
526
+ else if(p.result==="push")s.push++;
527
+ else if(p.result==="blackjack")s.win++;
528
+ if(p.blackjack)s.bj++;
529
+ if(p.bust)s.bust++;
530
+ if(p.doubled)s.double++;
531
+ s.net+=p.net; s.wagered+=p.bet;
532
+ (p.steps||[]).forEach(st=>{
533
+ s.steps++; s.conf+=st.decision.confidence||0; s.lat+=st.latency_ms||0;
534
+ s.actions[st.decision.choice]=(s.actions[st.decision.choice]||0)+1;
535
+ });
536
+ const p1=(p.steps||[])[0];
537
+ if(p1) s.firstActions.push({round:r.n,action:p1.decision.choice});
538
+ }
539
+ s.avgConf=s.steps?s.conf/s.steps:0;
540
+ s.avgLat=s.steps?s.lat/s.steps:0;
541
+ return s;
542
+ }
543
+
544
+ function renderStats(){
545
+ const a=statsFor("a"), b=statsFor("b");
546
+ const agree=a.firstActions.filter(x=>{
547
+ const y=b.firstActions.find(z=>z.round===x.round);
548
+ return y && y.action===x.action;
549
+ }).length;
550
+ const compared=a.firstActions.filter(x=>b.firstActions.some(z=>z.round===x.round)).length;
551
+ const dealerBust=DATA.rounds.filter(r=>r.dealer&&r.dealer.bust).length;
552
+ const dealerBj=DATA.rounds.filter(r=>r.dealer&&r.dealer.blackjack).length;
553
+ const dealerAvg=DATA.rounds.reduce((sum,r)=>sum+((r.dealer||{}).total||0),0)/Math.max(DATA.rounds.length,1);
554
+ const totalNet=a.net+b.net;
555
+ const houseEdge=totalNet;
556
+
557
+ function statCard(id,s){
558
+ const m=agentMeta(id);
559
+ const totalActions=Math.max(s.steps,1);
560
+ return `<div class="stat-card" style="--accent:${agentColor(id)}">
561
+ <h3>${m.name}</h3><div class="style">${m.style}</div>
562
+ <div class="kv">
563
+ <div><div class="k">損益</div><div class="v ${cls(s.net)}">${money(s.net)}</div></div>
564
+ <div><div class="k">勝 / 負 / 和</div><div class="v">${s.win} / ${s.lose} / ${s.push}</div></div>
565
+ <div><div class="k">Blackjack</div><div class="v">${s.bj}</div></div>
566
+ <div><div class="k">爆牌</div><div class="v">${s.bust}</div></div>
567
+ <div><div class="k">加倍次數</div><div class="v">${s.double}</div></div>
568
+ <div><div class="k">總下注</div><div class="v">${s.wagered}</div></div>
569
+ <div><div class="k">平均信心</div><div class="v">${pct(s.avgConf)}</div></div>
570
+ <div><div class="k">平均推理</div><div class="v">${(s.avgLat/1000).toFixed(2)}s</div></div>
571
+ </div>
572
+ <div class="dist">
573
+ <div class="mini-title">動作分佈(${s.steps} 次)</div>
574
+ ${["hit","stand","double"].map(k=>`<div class="row"><span>${LABEL[k]}</span><span class="bar"><i style="--c:var(--${k});width:${(100*s.actions[k]/totalActions).toFixed(1)}%"></i></span><span class="pct">${s.actions[k]}</span></div>`).join("")}
575
+ </div>
576
+ </div>`;
577
+ }
578
+
579
+ const chart=bankrollChart();
580
+ $("#view-stats").innerHTML=`
581
+ <h2 class="section-title">決策統計</h2>
582
+ <div class="stats-grid">${statCard("a",a)}${statCard("b",b)}</div>
583
+ <div class="chart-wrap">
584
+ <div class="chart-legend">
585
+ <span><i style="background:var(--green)"></i>${agentMeta("a").name}</span>
586
+ <span><i style="background:var(--blue)"></i>${agentMeta("b").name}</span>
587
+ <span>起始資金 ${DATA.meta.start_bankroll}</span>
588
+ </div>
589
+ ${chart}
590
+ </div>
591
+ <div class="h2h">
592
+ <h3 style="margin:0 0 6px">兩個 d1 有多像?</h3>
593
+ <p style="color:var(--muted);margin:4px 0 10px">比較每一局雙方第一次動作(同一副牌、同一個莊家明牌、相同的剩餘牌資訊)。</p>
594
+ <div class="big">${compared?Math.round(100*agree/compared):0}%</div>
595
+ <div style="color:var(--muted);font-size:13px">首個動作一致率 · 共比較 ${compared} 局</div>
596
+ <div class="kv" style="grid-template-columns:repeat(3,1fr);margin-top:16px">
597
+ <div><div class="k">莊家爆牌率</div><div class="v">${Math.round(100*dealerBust/DATA.rounds.length)}%</div></div>
598
+ <div><div class="k">莊家 Blackjack</div><div class="v">${dealerBj} 局</div></div>
599
+ <div><div class="k">莊家平均點數</div><div class="v">${dealerAvg.toFixed(1)}</div></div>
600
+ </div>
601
+ </div>
602
+ <div class="data-box" style="margin-top:14px">
603
+ <h3>兩個模型合計對抗莊家</h3>
604
+ <p style="font-size:15px">總損益 <b class="${cls(totalNet)}" style="font-size:24px">${money(totalNet)}</b> · 每位玩家起始 ${DATA.meta.start_bankroll},合計起始 ${DATA.meta.start_bankroll*2}</p>
605
+ </div>
606
+ `;
607
+ }
608
+
609
+ function bankrollChart(){
610
+ const W=760,H=230,pad={l:52,r:14,t:14,b:26};
611
+ const agents=DATA.meta.agents;
612
+ const series=agents.map(a=>{
613
+ const vals=[DATA.meta.start_bankroll];
614
+ for(const r of DATA.rounds){
615
+ const p=r.players.find(x=>x.id===a.id);
616
+ vals.push(p?p.bankroll_after:DATA.meta.start_bankroll);
617
+ }
618
+ return {a,vals,color:a.id==="a"?"#39d98a":"#5cc8ff"};
619
+ });
620
+ const all=series.flatMap(s=>s.vals);
621
+ let min=Math.min(...all),max=Math.max(...all);
622
+ const span=Math.max(max-min,100);
623
+ min=Math.floor((min-span*0.1)/50)*50; max=Math.ceil((max+span*0.1)/50)*50;
624
+ const X=i=>pad.l+(W-pad.l-pad.r)*(i/(DATA.rounds.length||1));
625
+ const Y=v=>H-pad.b-(H-pad.t-pad.b)*((v-min)/(max-min||1));
626
+ const step=Math.max(50,Math.ceil((max-min)/4/50)*50);
627
+ min=Math.floor(min/step)*step; max=Math.ceil(max/step)*step;
628
+ const ticks=Math.round((max-min)/step);
629
+ let grid="";
630
+ for(let i=0;i<=ticks;i++){
631
+ const v=min+(max-min)*i/ticks, y=Y(v);
632
+ grid+=`<line x1="${pad.l}" y1="${y}" x2="${W-pad.r}" y2="${y}" stroke="rgba(255,255,255,.07)"/>`;
633
+ grid+=`<text x="${pad.l-8}" y="${y+4}" fill="#8fae9f" font-size="10" text-anchor="end">${Math.round(v)}</text>`;
634
+ }
635
+ const paths=series.map(s=>{
636
+ const d=s.vals.map((v,i)=>`${i?"L":"M"}${X(i).toFixed(1)},${Y(v).toFixed(1)}`).join(" ");
637
+ const dots=s.vals.map((v,i)=>`<circle cx="${X(i)}" cy="${Y(v)}" r="2.6" fill="${s.color}"><title>第 ${i} 局後:${v}</title></circle>`).join("");
638
+ return `<path d="${d}" fill="none" stroke="${s.color}" stroke-width="2.4" stroke-linejoin="round"/>${dots}`;
639
+ }).join("");
640
+ return `<svg viewBox="0 0 ${W} ${H}" width="100%" role="img" aria-label="資金曲線">${grid}${paths}</svg>`;
641
+ }
642
+
643
+ const CODE_FILES=[
644
+ {file:"blackjack.py",label:"blackjack.py · 對戰與記錄"},
645
+ {file:"build_site.py",label:"build_site.py · 網頁編譯"},
646
+ {file:"run_demo.sh",label:"run_demo.sh · 一鍵執行"},
647
+ ];
648
+ function renderCodeTabs(){
649
+ $("#codeTabs").innerHTML=CODE_FILES.map((f,i)=>`<button data-file="${f.file}" class="${i===0?"active":""}">${f.label}</button>`).join("");
650
+ $("#codeTabs").querySelectorAll("button").forEach(b=>b.onclick=()=>{
651
+ $("#codeTabs").querySelectorAll("button").forEach(x=>x.classList.toggle("active",x===b));
652
+ loadCode(b.dataset.file);
653
+ });
654
+ loadCode("blackjack.py");
655
+ $("#copyCode").onclick=async()=>{
656
+ try{await navigator.clipboard.writeText($("#codeBody").textContent);$("#copyCode").textContent="已複製";setTimeout(()=>$("#copyCode").textContent="複製",1200);}catch(e){}
657
+ };
658
+ $("#repro").textContent=`# 1) 下載模型與 llama.cpp(CPU 版)
659
+ llama-server -m d1-3B-Q8_0.gguf --port 8081 -c 2048 -np 1 --no-webui &
660
+ llama-server -m d1-3B-Q8_0.gguf --port 8082 -c 2048 -np 1 --no-webui &
661
+
662
+ # 2) 讓兩個 d1 打 12 局,並把每一步寫成 JSON
663
+ python3 blackjack.py --rounds 12 --seed ${DATA.meta.seed} --out battle.json
664
+
665
+ # 3) 把 JSON 編譯成這個靜態網站
666
+ python3 build_site.py --battle battle.json --out site
667
+
668
+ # 4) 上傳 static Space
669
+ python3 - <<'PY'
670
+ from huggingface_hub import HfApi
671
+ HfApi().upload_folder(folder_path="site", repo_id="HelloSun/d1demo", repo_type="space")
672
+ PY`;
673
+ $("#metaJson").textContent=JSON.stringify(DATA.meta,null,2);
674
+ }
675
+ function loadCode(file){
676
+ $("#codeName").textContent=file;
677
+ $("#codeDownload").href="source/"+file;
678
+ $("#codeBody").textContent="載入中…";
679
+ fetch("source/"+file).then(r=>{if(!r.ok)throw new Error(r.status);return r.text();})
680
+ .then(t=>{$("#codeBody").textContent=t;})
681
+ .catch(()=>{$("#codeBody").textContent="(無法載入 "+file+",請確認 source/ 目錄存在)";});
682
+ }
683
+
684
+ init();
685
+ </script>
686
+ </body>
687
+ </html>
688
+ """
689
+
690
+ README = """---
691
+ title: d1demo
692
+ emoji: 🃏
693
+ colorFrom: green
694
+ colorTo: yellow
695
+ sdk: static
696
+ pinned: false
697
+ license: mit
698
+ ---
699
+
700
+ # d1 × d1 21 點對戰實錄
701
+
702
+ 兩個 **LiquidAI d1-3B** 決策模型,各自跑在一個 llama.cpp server 上,在同一張 21 點牌桌上對打。每一次下注、補牌、停牌、加倍,都是模型輸出的機率分佈;這頁把每一步整理成可回放的視覺化紀錄。
703
+
704
+ - 模型:`LiquidAI/d1-3B-GGUF` (Q8_0),llama.cpp b11786
705
+ - API:`POST /v1/systemone`(d1 是 decision model,不是文字生成模型)
706
+ - 莊家:規則操作,S17,6 副牌,BJ 3:2
707
+ - 內容:對戰時間軸、決策機率、風險評分、信心度、延遲、資金曲線、動作統計
708
+ - 原始碼:`source/blackjack.py`(對戰 + 記錄)、`source/build_site.py`(靜態網頁編譯)
709
+ - 原始資料:`battle.json`
710
+
711
+ 頁面是靜態的,所有資料都在 `battle.json`;`build_site.py` 可重新編譯整站。
712
+ """
713
+
714
+
715
+ def build(battle_path, out_dir):
716
+ data = json.loads(Path(battle_path).read_text(encoding="utf-8"))
717
+ out = Path(out_dir)
718
+ out.mkdir(parents=True, exist_ok=True)
719
+ (out / "source").mkdir(exist_ok=True)
720
+
721
+ (out / "battle.json").write_text(
722
+ json.dumps(data, ensure_ascii=False, indent=1), encoding="utf-8"
723
+ )
724
+ shutil.copy(battle_path, out / "battle.json")
725
+
726
+ for name in ("blackjack.py", "build_site.py", "run_demo.sh"):
727
+ src = ROOT / name
728
+ if src.exists():
729
+ shutil.copy(src, out / "source" / name)
730
+
731
+ inline = "window.BATTLE_DATA = " + json.dumps(data, ensure_ascii=False) + ";"
732
+ html = HTML.replace("__TITLE__", data["meta"]["title"]).replace(
733
+ "__SUBTITLE__", data["meta"]["subtitle"]
734
+ )
735
+ html = html.replace('<script>\nconst SUIT_RED', '<script>\n' + inline + '\nconst SUIT_RED')
736
+ (out / "index.html").write_text(html, encoding="utf-8")
737
+ (out / "README.md").write_text(README, encoding="utf-8")
738
+
739
+ stamp = datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds")
740
+ print(f"[{stamp}] built {out/'index.html'} ({len(html)/1024:.1f} KiB), "
741
+ f"battle.json {len(json.dumps(data, ensure_ascii=False).encode())/1024:.1f} KiB")
742
+
743
+
744
+ def main():
745
+ ap = argparse.ArgumentParser()
746
+ ap.add_argument("--battle", default="battle.json")
747
+ ap.add_argument("--out", default="site")
748
+ args = ap.parse_args()
749
+ build(args.battle, args.out)
750
+
751
+
752
+ if __name__ == "__main__":
753
+ main()
source/run_demo.sh ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # One-shot demo: two d1 servers -> blackjack battle -> static site.
3
+ set -euo pipefail
4
+ cd "$(dirname "$0")"
5
+
6
+ MODEL="${MODEL:-models/d1-3B-Q8_0.gguf}"
7
+ ROUNDS="${ROUNDS:-12}"
8
+ SEED="${SEED:-20261021}"
9
+ LLAMA="${LLAMA:-./llama/llama-server}"
10
+
11
+ wait_health() {
12
+ local port="$1"
13
+ for _ in $(seq 1 120); do
14
+ if curl -sf "http://127.0.0.1:${port}/health" >/dev/null 2>&1; then
15
+ return 0
16
+ fi
17
+ sleep 0.5
18
+ done
19
+ echo "server on :${port} did not become healthy" >&2
20
+ return 1
21
+ }
22
+
23
+ start_server() {
24
+ local port="$1" log="$2"
25
+ if curl -sf "http://127.0.0.1:${port}/health" >/dev/null 2>&1; then
26
+ echo "reuse existing server on :${port}"
27
+ return
28
+ fi
29
+ setsid nohup "$LLAMA" -m "$MODEL" --host 127.0.0.1 --port "$port" \
30
+ -t "${THREADS:-8}" -c 2048 -np 1 --no-webui </dev/null >"$log" 2>&1 &
31
+ echo $! >"/tmp/d1demo_${port}.pid"
32
+ wait_health "$port"
33
+ echo "d1 server ready on :${port}"
34
+ }
35
+
36
+ start_server 8081 logs_server1.txt
37
+ start_server 8082 logs_server2.txt
38
+
39
+ python3 blackjack.py --rounds "$ROUNDS" --seed "$SEED" --out battle.json
40
+ python3 build_site.py --battle battle.json --out site
41
+
42
+ echo "site ready: $(pwd)/site/index.html"