Download index.html from HelloSun/d1demo: direct link, hf CLI and curl.
- Browser
- Download file 81 kB
-
https://huggingface.co/spaces/HelloSun/d1demo/resolve/main/index.html
- Command line
-
hf download hf://spaces/HelloSun/d1demo/index.html
-
curl -L -o index.html https://huggingface.co/spaces/HelloSun/d1demo/resolve/main/index.html
81 kB
| <html lang="zh-Hant"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>d1 × d1 21 點:破產對決</title> | |
| <meta name="description" content="兩個 LiquidAI d1-3B 決策模型,用 llama.cpp 一路打到雙方資金歸零"> | |
| <style> | |
| :root{ | |
| --bg:#07120e; --bg2:#0b1c16; --panel:#0e241c; --panel2:#123026; | |
| --line:rgba(255,255,255,.09); --text:#e8f3ed; --muted:#8fae9f; | |
| --gold:#f6c453; --green:#39d98a; --red:#ff6b6b; --blue:#5cc8ff; --amber:#ffb020; | |
| --hit:#ff6b6b; --stand:#5cc8ff; --double:#f6c453; | |
| --shadow:0 18px 50px rgba(0,0,0,.45); | |
| } | |
| *{box-sizing:border-box} | |
| html,body{margin:0;padding:0} | |
| body{ | |
| background: | |
| radial-gradient(1100px 700px at 12% -10%, rgba(57,217,138,.13), transparent 60%), | |
| radial-gradient(900px 600px at 92% 0%, rgba(246,196,83,.10), transparent 55%), | |
| var(--bg); | |
| color:var(--text); | |
| font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans TC","PingFang TC","Microsoft JhengHei",sans-serif; | |
| line-height:1.6; min-height:100vh; | |
| } | |
| a{color:var(--gold)} | |
| button{font:inherit;color:inherit;cursor:pointer} | |
| .topbar{ | |
| position:sticky;top:0;z-index:50;backdrop-filter:blur(14px); | |
| background:rgba(7,18,14,.8);border-bottom:1px solid var(--line); | |
| } | |
| .topbar-inner{max-width:1180px;margin:0 auto;padding:12px 20px;display:flex;gap:18px;align-items:center;flex-wrap:wrap} | |
| .brand{display:flex;gap:10px;align-items:center;font-weight:800;letter-spacing:.02em} | |
| .brand .logo{ | |
| width:30px;height:30px;border-radius:9px;display:grid;place-items:center; | |
| background:linear-gradient(145deg,#f6c453,#b8860b);color:#1b1200;font-weight:900; | |
| } | |
| nav{display:flex;gap:4px;margin-left:auto;flex-wrap:wrap} | |
| nav button{ | |
| border:1px solid transparent;background:transparent;padding:8px 14px;border-radius:999px; | |
| color:var(--muted);font-weight:600; | |
| } | |
| nav button:hover{color:var(--text);background:rgba(255,255,255,.05)} | |
| nav button.active{color:#1b1200;background:var(--gold);border-color:var(--gold)} | |
| main{max-width:1180px;margin:0 auto;padding:28px 20px 60px} | |
| .view{display:none} | |
| .view.active{display:block;animation:fade .25s ease} | |
| @keyframes fade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}} | |
| .hero{padding:18px 0 6px} | |
| .hero h1{font-size:clamp(30px,4vw,50px);line-height:1.15;margin:0 0 10px;letter-spacing:-.01em} | |
| .hero h1 em{font-style:normal;color:var(--gold)} | |
| .hero p{color:var(--muted);margin:0;max-width:760px;font-size:16px} | |
| .chips{display:flex;gap:8px;flex-wrap:wrap;margin-top:18px} | |
| .chip{ | |
| font-size:12.5px;color:#cfe4d9;background:rgba(255,255,255,.045); | |
| border:1px solid var(--line);border-radius:999px;padding:5px 11px; | |
| } | |
| .chip b{color:var(--gold)} | |
| .scoreboard{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin:22px 0 24px} | |
| .score{ | |
| position:relative;overflow:hidden;border:1px solid var(--line);border-radius:18px; | |
| padding:18px 18px 16px;background:linear-gradient(160deg,var(--panel2),var(--panel)); | |
| box-shadow:var(--shadow); | |
| } | |
| .score:before{content:"";position:absolute;inset:0 auto 0 0;width:4px;background:var(--accent,var(--gold))} | |
| .score .id{font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:var(--muted)} | |
| .score .name{font-size:22px;font-weight:800;margin:2px 0 0} | |
| .score .style{font-size:13px;color:var(--accent,var(--gold))} | |
| .score .bank{font-size:34px;font-weight:900;letter-spacing:-.02em;margin-top:10px;font-variant-numeric:tabular-nums} | |
| .score .sub{font-size:13px;color:var(--muted)} | |
| .big-delta{font-weight:800} | |
| .pos{color:var(--green)}.neg{color:var(--red)}.neutral{color:var(--muted)} | |
| .panel{ | |
| border:1px solid var(--line);border-radius:20px;background:linear-gradient(180deg,rgba(255,255,255,.025),rgba(0,0,0,.12)); | |
| box-shadow:var(--shadow); | |
| } | |
| .table-panel{padding:16px} | |
| .round-toolbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:14px} | |
| .icon-btn{ | |
| border:1px solid var(--line);background:rgba(255,255,255,.05);border-radius:10px; | |
| padding:8px 13px;font-weight:700;color:var(--text); | |
| } | |
| .icon-btn:hover{background:rgba(255,255,255,.1)} | |
| .icon-btn:disabled{opacity:.35;cursor:not-allowed} | |
| .round-pills{display:flex;gap:6px;overflow-x:auto;padding:2px;max-width:100%;scrollbar-width:thin} | |
| .pill{ | |
| flex:0 0 auto;min-width:38px;height:34px;border-radius:9px;border:1px solid var(--line); | |
| background:rgba(255,255,255,.04);font-weight:800;font-size:13px;color:var(--muted); | |
| display:grid;place-items:center;padding:0 8px; | |
| } | |
| .pill:hover{border-color:rgba(246,196,83,.5);color:var(--text)} | |
| .pill.active{background:var(--gold);border-color:var(--gold);color:#1b1200} | |
| .pill .dot{width:6px;height:6px;border-radius:50%;display:block;margin:0 auto 2px} | |
| .pill.win .dot{background:var(--green)} | |
| .pill.lose .dot{background:var(--red)} | |
| .pill.mixed .dot{background:var(--amber)} | |
| .pill.push .dot{background:#93a89d} | |
| .round-label{margin-left:auto;color:var(--muted);font-size:13px;font-variant-numeric:tabular-nums} | |
| .round-head{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin-bottom:10px} | |
| .round-head h2{margin:0;font-size:20px} | |
| .round-head .shoe{color:var(--muted);font-size:12.5px} | |
| .dealer{ | |
| border:1px solid rgba(246,196,83,.25);border-radius:16px;padding:14px 16px;margin-bottom:14px; | |
| background:radial-gradient(700px 200px at 20% 0%,rgba(246,196,83,.1),transparent 70%),rgba(0,0,0,.18); | |
| display:flex;gap:16px;align-items:center;flex-wrap:wrap; | |
| } | |
| .dealer .label{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--gold);font-weight:800} | |
| .dealer .total-badge{font-size:26px;font-weight:900;font-variant-numeric:tabular-nums} | |
| .dealer .note{color:var(--muted);font-size:13px;flex:1;min-width:180px} | |
| .players{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px} | |
| .player{ | |
| border:1px solid var(--line);border-radius:16px;padding:15px;background:rgba(255,255,255,.025); | |
| } | |
| .player.win{border-color:rgba(57,217,138,.45);box-shadow:inset 0 0 0 1px rgba(57,217,138,.12)} | |
| .player.lose{border-color:rgba(255,107,107,.35)} | |
| .player.blackjack{border-color:rgba(246,196,83,.6)} | |
| .p-head{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;flex-wrap:wrap} | |
| .p-head h3{margin:0;font-size:18px} | |
| .p-head .style{font-size:12.5px;color:var(--accent,var(--gold))} | |
| .p-head .bet{ | |
| font-size:12.5px;color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:4px 10px; | |
| background:rgba(0,0,0,.2);white-space:nowrap; | |
| } | |
| .p-head .bet b{color:var(--gold);font-size:15px} | |
| .hand{display:flex;gap:14px;align-items:center;margin:12px 0 8px;flex-wrap:wrap} | |
| .cards{display:flex;gap:6px;flex-wrap:wrap} | |
| .card{ | |
| width:52px;height:72px;border-radius:8px;background:#f7f7f2;color:#141414; | |
| display:flex;flex-direction:column;align-items:center;justify-content:center; | |
| font-weight:900;box-shadow:0 6px 14px rgba(0,0,0,.35);border:1px solid rgba(0,0,0,.15); | |
| line-height:1;user-select:none; | |
| } | |
| .card .r{font-size:21px} | |
| .card .s{font-size:17px;margin-top:2px} | |
| .card.red{color:#c0182b} | |
| .card.down{background:repeating-linear-gradient(45deg,#123c6b,#123c6b 5px,#0b2a4d 5px,#0b2a4d 10px);color:transparent;border-color:#0a2340} | |
| .total{ | |
| font-size:30px;font-weight:900;font-variant-numeric:tabular-nums;display:flex;align-items:baseline;gap:6px; | |
| } | |
| .total small{font-size:11px;font-weight:700;color:var(--muted);border:1px solid var(--line);border-radius:5px;padding:1px 5px} | |
| .result{ | |
| display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:800; | |
| padding:3px 10px;border-radius:999px;border:1px solid var(--line); | |
| } | |
| .result.win{color:var(--green);border-color:rgba(57,217,138,.4);background:rgba(57,217,138,.1)} | |
| .result.lose{color:var(--red);border-color:rgba(255,107,107,.4);background:rgba(255,107,107,.08)} | |
| .result.push{color:#b9c9c0;background:rgba(255,255,255,.05)} | |
| .result.blackjack{color:var(--gold);border-color:rgba(246,196,83,.5);background:rgba(246,196,83,.1)} | |
| .bet-line{display:flex;gap:8px;align-items:center;flex-wrap:wrap;font-size:12.5px;color:var(--muted);margin:6px 0 2px} | |
| .bet-line .tag{border:1px solid var(--line);border-radius:6px;padding:1px 7px;color:#cfe4d9} | |
| details{margin-top:8px} | |
| summary{cursor:pointer;color:var(--muted);font-size:12.5px;font-weight:700} | |
| summary:hover{color:var(--text)} | |
| details .body{padding:8px 0 0;color:var(--muted);font-size:13px} | |
| .steps{list-style:none;margin:12px 0 0;padding:0;display:flex;flex-direction:column;gap:8px} | |
| .step{ | |
| border:1px solid var(--line);border-radius:11px;padding:9px 11px;background:rgba(0,0,0,.2); | |
| } | |
| .step.enter{animation:slide .3s ease} | |
| @keyframes slide{from{opacity:0;transform:translateX(-6px)}to{opacity:1;transform:none}} | |
| .step-head{display:flex;gap:8px;align-items:center;flex-wrap:wrap;font-size:12.5px} | |
| .step-no{color:var(--muted);font-weight:800;font-variant-numeric:tabular-nums} | |
| .mini{font-weight:800;font-variant-numeric:tabular-nums} | |
| .action{ | |
| font-weight:900;font-size:12px;letter-spacing:.04em;padding:2px 9px;border-radius:6px;color:#0b0f0d; | |
| } | |
| .action.hit{background:var(--hit)}.action.stand{background:var(--stand)}.action.double{background:var(--double)} | |
| .action.unknown{background:#7a8a81} | |
| .step .meta{margin-left:auto;color:var(--muted);font-size:11.5px;display:flex;gap:8px;flex-wrap:wrap} | |
| .probs{display:grid;grid-template-columns:64px 1fr 46px;gap:6px;align-items:center;margin-top:7px;font-size:11.5px;color:var(--muted)} | |
| .bar{height:7px;border-radius:4px;background:rgba(255,255,255,.07);overflow:hidden} | |
| .bar>i{display:block;height:100%;border-radius:4px;background:var(--c,var(--gold));width:0;transition:width .5s ease} | |
| .probs .pct{text-align:right;font-variant-numeric:tabular-nums;color:#d6e7de} | |
| .step-foot{display:flex;gap:10px;flex-wrap:wrap;color:var(--muted);font-size:11.5px;margin-top:7px} | |
| .step-foot b{color:#d6e7de} | |
| .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} | |
| .net .amt{font-size:19px;font-weight:900;font-variant-numeric:tabular-nums} | |
| .events{margin-top:14px;border-top:1px dashed var(--line);padding-top:10px;color:var(--muted);font-size:12.5px} | |
| .events ul{margin:6px 0 0;padding-left:18px} | |
| .events li{margin:2px 0} | |
| .arena-note{margin-top:14px;color:var(--muted);font-size:12px} | |
| .section-title{font-size:22px;margin:26px 0 12px} | |
| .stats-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px} | |
| .stat-card{border:1px solid var(--line);border-radius:16px;padding:16px;background:rgba(255,255,255,.025)} | |
| .stat-card h3{margin:0 0 4px;font-size:17px} | |
| .stat-card .style{font-size:12.5px;color:var(--accent,var(--gold))} | |
| .kv{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin-top:12px} | |
| .kv .k{font-size:11.5px;color:var(--muted)} | |
| .kv .v{font-size:19px;font-weight:800;font-variant-numeric:tabular-nums} | |
| .dist{margin-top:12px} | |
| .dist .row{display:grid;grid-template-columns:66px 1fr 44px;gap:7px;align-items:center;font-size:12px;margin:5px 0;color:var(--muted)} | |
| .mini-title{font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin-top:14px} | |
| .chart-wrap{margin-top:14px;border:1px solid var(--line);border-radius:16px;padding:14px;background:rgba(0,0,0,.18)} | |
| .chart-legend{display:flex;gap:14px;font-size:12px;color:var(--muted);flex-wrap:wrap;margin-bottom:6px} | |
| .chart-legend i{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:5px} | |
| .h2h{margin-top:14px;border:1px solid var(--line);border-radius:16px;padding:16px;background:rgba(255,255,255,.025)} | |
| .h2h .big{font-size:30px;font-weight:900;color:var(--gold)} | |
| .code-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px} | |
| .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)} | |
| .code-tabs button.active{background:var(--gold);color:#1b1200;border-color:var(--gold);font-weight:800} | |
| .code-bar{display:flex;gap:10px;align-items:center;margin-bottom:8px;color:var(--muted);font-size:12.5px;flex-wrap:wrap} | |
| pre.code{ | |
| margin:0;border:1px solid var(--line);border-radius:14px;background:#04100c;padding:16px; | |
| overflow:auto;max-height:70vh;font-size:12.5px;line-height:1.55; | |
| } | |
| pre.code code{font-family:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,monospace} | |
| .data-box{border:1px solid var(--line);border-radius:16px;padding:16px;background:rgba(255,255,255,.025);margin-bottom:14px} | |
| .data-box h3{margin:0 0 8px;font-size:17px} | |
| .data-box p{color:var(--muted);margin:6px 0;font-size:14px} | |
| .btn{ | |
| display:inline-flex;align-items:center;gap:7px;border:1px solid rgba(246,196,83,.45); | |
| background:rgba(246,196,83,.1);color:var(--gold);padding:7px 13px;border-radius:9px; | |
| text-decoration:none;font-weight:700;font-size:13px; | |
| } | |
| .btn:hover{background:rgba(246,196,83,.18)} | |
| .btn.ghost{border-color:var(--line);background:rgba(255,255,255,.04);color:var(--text)} | |
| footer{border-top:1px solid var(--line);margin-top:30px;padding:22px 20px 40px;color:var(--muted);font-size:12.5px;text-align:center} | |
| .loading{padding:40px;text-align:center;color:var(--muted)} | |
| .err{border:1px solid rgba(255,107,107,.4);background:rgba(255,107,107,.08);padding:16px;border-radius:12px;color:#ffd4d4} | |
| @media (max-width:760px){ | |
| .scoreboard,.stats-grid,.players{grid-template-columns:1fr} | |
| .card{width:44px;height:62px} | |
| .card .r{font-size:18px} | |
| .total{font-size:25px} | |
| .round-label{margin-left:0} | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header class="topbar"> | |
| <div class="topbar-inner"> | |
| <div class="brand"><span class="logo">21</span><span>d1 × d1 對戰實驗室</span></div> | |
| <nav> | |
| <button data-view="battle" class="active">對戰</button> | |
| <button data-view="stats">統計</button> | |
| <button data-view="code">整理程式</button> | |
| <button data-view="data">原始資料</button> | |
| </nav> | |
| </div> | |
| </header> | |
| <main> | |
| <div id="app"><div class="loading">載入對戰資料中…</div></div> | |
| </main> | |
| <footer> | |
| <div>由 llama.cpp 上的兩個 <b>LiquidAI d1-3B</b> 決策模型自動產生 · 頁面由 build_site.py 靜態編譯</div> | |
| <div style="margin-top:6px">Hosted on Hugging Face Static Space · <a href="https://huggingface.co/spaces/HelloSun/d1demo">HelloSun/d1demo</a></div> | |
| </footer> | |
| <script> | |
| window.BATTLE_DATA = {"meta": {"title": "d1 × d1 21 點:破產對決", "subtitle": "兩個 LiquidAI d1-3B 決策模型,用 llama.cpp 一路打到雙方資金歸零", "created_at": "2026-10-21T11:32:55+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, "betting": "注碼依當前資金比例:小注 50%、大注 70%、全押 100%(取整到 10)", "rebuy": "無補充籌碼,兩邊都歸零才結束"}, "start_bankroll": 1000, "bet_fractions": {"min": 0.5, "standard": 0.7, "max": 1.0}, "end_reason": "both_broke", "agents": [{"id": "a", "name": "阿保", "style": "保守算牌派", "description": "以保住本金為第一優先,只有在牌面明顯有利時才加注,遇到風險寧可停牌。", "url": "http://127.0.0.1:8081", "final_bankroll": 0, "peak_bankroll": 1000, "bust_round": 10, "rounds_played": 10}, {"id": "b", "name": "阿衝", "style": "激進壓迫派", "description": "相信進攻是最好的防守,喜歡加倍與大注,願意為了期望值承擔波動。", "url": "http://127.0.0.1:8082", "final_bankroll": 0, "peak_bankroll": 1000, "bust_round": 11, "rounds_played": 11}], "rounds_played": 11, "duration_sec": 138.93, "inference": {"a": {"calls": 23, "avg_latency_ms": 3012}, "b": {"calls": 22, "avg_latency_ms": 3165}}}, "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": 500, "doubled": false, "blackjack": false, "bust": false, "total": 10, "soft": false, "result": "lose", "net": -500, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.7495426029302982, "standard": 0.19354882832047426, "max": 0.05690856874922748}, "confidence": 0.6243139043954472}, "bet": 500, "confidence": {"type": "score", "score": 1.1935795415714954, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.19777645506034436, "1": 0.4973906472646646, "2": 0.21830979871814207, "3": 0.08652309895684891}, "confidence": 0.4108675483078158}, "latency_ms": 2694}, "bankroll_after": 500, "id": "a", "name": "阿保", "style": "保守算牌派", "peak_bankroll": 1000}, {"cards": ["Q♦", "6♠"], "bet": 700, "doubled": false, "blackjack": false, "bust": false, "total": 16, "soft": false, "result": "lose", "net": -700, "bet_decision": {"decision": {"type": "choice", "choice": "standard", "probabilities": {"min": 0.23967294369887637, "standard": 0.5817249980285547, "max": 0.17860205827256884}, "confidence": 0.37258749704283206}, "bet": 700, "confidence": {"type": "score", "score": 1.507238814141059, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.14445230077935894, "1": 0.3625481640958943, "2": 0.33430795532907537, "3": 0.15869157979567128}, "confidence": 0.20385658430022313}, "latency_ms": 2499}, "bankroll_after": 300, "id": "b", "name": "阿衝", "style": "激進壓迫派", "peak_bankroll": 1000}], "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": 250, "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.7888347603567517, "stand": 0.2111652396432483}, "confidence": 0.5776695207135034}, "risk": {"type": "score", "score": 2.2652645474088886, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.03996224301656414, "1": 0.10200571269114653, "2": 0.41083729815912584, "3": 0.4471947461331635}, "confidence": 0.2652645474088887}, "latency_ms": 3151, "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.37302117687837494, "stand": 0.626978823121625}, "confidence": 0.25395764624325}, "risk": {"type": "score", "score": 1.4085793790866217, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.13348583156305185, "1": 0.3936259060555138, "2": 0.4037113141131949, "3": 0.06917694826823936}, "confidence": 0.2702254825501431}, "latency_ms": 3468, "drawn": null, "total_after": 17}], "player_id": "a", "result": "lose", "net": -250, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.6968753406570245, "standard": 0.25124407766170376, "max": 0.05188058168127172}, "confidence": 0.5453130109855368}, "bet": 250, "confidence": {"type": "score", "score": 1.153562244852418, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.19629014603126313, "1": 0.5138686227524958, "2": 0.229830071548801, "3": 0.0600111596674401}, "confidence": 0.4538574630850557}, "latency_ms": 2709}, "bankroll_after": 250, "id": "a", "name": "阿保", "style": "保守算牌派", "peak_bankroll": 1000}, {"cards": ["K♦", "10♣"], "bet": 210, "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.30708643375615396, "stand": 0.6929135662438459}, "confidence": 0.38582713248769185}, "risk": {"type": "score", "score": 1.1558612595776556, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.27435705365012364, "1": 0.35797206875513765, "2": 0.30512344196169827, "3": 0.06254743563304048}, "confidence": 0.2954246331220971}, "latency_ms": 2990, "drawn": null, "total_after": 20}], "player_id": "b", "result": "win", "net": 210, "bet_decision": {"decision": {"type": "choice", "choice": "standard", "probabilities": {"min": 0.2589391915119814, "standard": 0.6021647784241172, "max": 0.13889603006390153}, "confidence": 0.4032471676361758}, "bet": 210, "confidence": {"type": "score", "score": 1.6222850123507153, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.12053751205265624, "1": 0.3180917564832067, "2": 0.37991893852490216, "3": 0.18145179293923472}, "confidence": 0.25938142647224605}, "latency_ms": 2426}, "bankroll_after": 510, "id": "b", "name": "阿衝", "style": "激進壓迫派", "peak_bankroll": 1000}], "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": 120, "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.6558900475681183, "stand": 0.34410995243188175}, "confidence": 0.3117800951362366}, "risk": {"type": "score", "score": 1.769234618561999, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.07876321188385403, "1": 0.2653560066817271, "2": 0.4637637324229848, "3": 0.1921170490114341}, "confidence": 0.3850005205391307}, "latency_ms": 3146, "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.2298190158896557, "stand": 0.7701809841103443}, "confidence": 0.5403619682206886}, "risk": {"type": "score", "score": 1.2032165044695071, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.21444016577017114, "1": 0.4154791783036063, "2": 0.3225046416127668, "3": 0.04757601431345572}, "confidence": 0.36790316399015066}, "latency_ms": 3084, "drawn": null, "total_after": 20}], "player_id": "a", "result": "lose", "net": -120, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.7021353615602709, "standard": 0.24459731120528871, "max": 0.053267327234440304}, "confidence": 0.5532030423404063}, "bet": 120, "confidence": {"type": "score", "score": 1.243907468048795, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.1647109410528787, "1": 0.49476116179321544, "2": 0.2724373852061379, "3": 0.06809051194776787}, "confidence": 0.42667064984544767}, "latency_ms": 2681}, "bankroll_after": 130, "id": "a", "name": "阿保", "style": "保守算牌派", "peak_bankroll": 1000}, {"cards": ["6♦", "10♠", "4♠"], "bet": 360, "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.6360780916514454, "stand": 0.3639219083485547}, "confidence": 0.2721561833028907}, "risk": {"type": "score", "score": 1.496846018692059, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.13424558064122727, "1": 0.3356707776533976, "2": 0.4290756840774641, "3": 0.10100795762791105}, "confidence": 0.29483010343623683}, "latency_ms": 3970, "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.3360724625093578, "stand": 0.6639275374906422}, "confidence": 0.3278550749812843}, "risk": {"type": "score", "score": 1.3092554578091316, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.2074752250590991, "1": 0.35679367170712645, "2": 0.35473152359931803, "3": 0.08099957963445631}, "confidence": 0.27579409207267025}, "latency_ms": 3577, "drawn": null, "total_after": 20}], "player_id": "b", "result": "lose", "net": -360, "bet_decision": {"decision": {"type": "choice", "choice": "standard", "probabilities": {"min": 0.256591764511596, "standard": 0.6231699492197297, "max": 0.12023828626867433}, "confidence": 0.4347549238295945}, "bet": 360, "confidence": {"type": "score", "score": 1.637742613857751, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.12226879726860378, "1": 0.306868514496184, "2": 0.3817139653440694, "3": 0.1891487228911428}, "confidence": 0.2594451680754656}, "latency_ms": 2819}, "bankroll_after": 150, "id": "b", "name": "阿衝", "style": "激進壓迫派", "peak_bankroll": 1000}], "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": 60, "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.30128956481413705, "stand": 0.698710435185863}, "confidence": 0.3974208703717259}, "risk": {"type": "score", "score": 1.1084635753875032, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.25095460756718285, "1": 0.42334035297948597, "2": 0.2919918959519758, "3": 0.03371314350135523}, "confidence": 0.3896272094781309}, "latency_ms": 3396, "drawn": null, "total_after": 19}], "player_id": "a", "result": "win", "net": 60, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.6926127915764545, "standard": 0.23626710905817105, "max": 0.07112009936537435}, "confidence": 0.5389191873646817}, "bet": 60, "confidence": {"type": "score", "score": 1.146617901430409, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.21917876559390995, "1": 0.47945507834068335, "2": 0.23693564510649448, "3": 0.06443051095891222}, "confidence": 0.4150245673817712}, "latency_ms": 2582}, "bankroll_after": 190, "id": "a", "name": "阿保", "style": "保守算牌派", "peak_bankroll": 1000}, {"cards": ["9♠", "3♦", "K♥"], "bet": 100, "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.734403302613461, "stand": 0.265596697386539}, "confidence": 0.46880660522692197}, "risk": {"type": "score", "score": 1.5869452568879086, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.11743767228218839, "1": 0.3123418203798986, "2": 0.43605808550572894, "3": 0.13416242183218405}, "confidence": 0.3186204132235405}, "latency_ms": 3680, "drawn": "K♥", "total_after": 22}], "player_id": "b", "result": "lose", "net": -100, "bet_decision": {"decision": {"type": "choice", "choice": "standard", "probabilities": {"min": 0.291170385524682, "standard": 0.5778208078311198, "max": 0.1310088066441981}, "confidence": 0.36673121174667966}, "bet": 100, "confidence": {"type": "score", "score": 1.6308974422103073, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.11805143814025988, "1": 0.3116761645734334, "2": 0.3915959142220468, "3": 0.17867648306426004}, "confidence": 0.2735444760817869}, "latency_ms": 2598}, "bankroll_after": 50, "id": "b", "name": "阿衝", "style": "激進壓迫派", "peak_bankroll": 1000}], "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": 100, "doubled": false, "blackjack": false, "bust": false, "total": 12, "soft": false, "result": "lose", "net": -100, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.7169821239824508, "standard": 0.22839151917086004, "max": 0.05462635684668915}, "confidence": 0.5754731859736761}, "bet": 100, "confidence": {"type": "score", "score": 1.2329249170160193, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.18594652365117773, "1": 0.47498597711682466, "2": 0.25926355779679827, "3": 0.07980394143519932}, "confidence": 0.39518203568162535}, "latency_ms": 2717}, "bankroll_after": 90, "id": "a", "name": "阿保", "style": "保守算牌派", "peak_bankroll": 1000}, {"cards": ["7♠", "6♦"], "bet": 40, "doubled": false, "blackjack": false, "bust": false, "total": 13, "soft": false, "result": "lose", "net": -40, "bet_decision": {"decision": {"type": "choice", "choice": "standard", "probabilities": {"min": 0.33016281821538473, "standard": 0.5556542153278266, "max": 0.11418296645678869}, "confidence": 0.3334813229917398}, "bet": 40, "confidence": {"type": "score", "score": 1.7355514551814188, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.11075179507713576, "1": 0.26294508405667505, "2": 0.40630299147382376, "3": 0.22000012939236546}, "confidence": 0.2955511963966879}, "latency_ms": 2743}, "bankroll_after": 10, "id": "b", "name": "阿衝", "style": "激進壓迫派", "peak_bankroll": 1000}], "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": 40, "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.4651867184249835, "stand": 0.2528327433499288, "double": 0.2819805382250877}, "confidence": 0.19778007763747524}, "risk": {"type": "score", "score": 1.661289775579295, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.0810802022698142, "1": 0.30672360983786334, "2": 0.48202239793553586, "3": 0.13017378995678672}, "confidence": 0.40094219566572153}, "latency_ms": 3258, "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.5558925013659549, "stand": 0.444107498634045}, "confidence": 0.11178500273190983}, "risk": {"type": "score", "score": 1.7006468099820728, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.09792126538793425, "1": 0.2820300499548643, "2": 0.4415292939443961, "3": 0.17851939071280543}, "confidence": 0.3436080285564618}, "latency_ms": 3196, "drawn": "7♠", "total_after": 21}], "player_id": "a", "result": "win", "net": 40, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.7430508242637351, "standard": 0.21044654186705883, "max": 0.04650263386920589}, "confidence": 0.6145762363956027}, "bet": 40, "confidence": {"type": "score", "score": 1.2067890088652962, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.17395648436116945, "1": 0.5092699103248166, "2": 0.2528017174015624, "3": 0.06397188791245155}, "confidence": 0.44529802241236505}, "latency_ms": 2700}, "bankroll_after": 130, "id": "a", "name": "阿保", "style": "保守算牌派", "peak_bankroll": 1000}, {"cards": ["A♠", "K♣"], "bet": 10, "doubled": false, "total": 21, "soft": true, "bust": false, "blackjack": true, "steps": [], "player_id": "b", "result": "blackjack", "net": 15, "bet_decision": {"decision": {"type": "choice", "choice": "standard", "probabilities": {"min": 0.3639541492057857, "standard": 0.45853490874439773, "max": 0.17751094204981663}, "confidence": 0.1878023631165966}, "bet": 10, "confidence": {"type": "score", "score": 1.7830460880445884, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.11593409029821067, "1": 0.23620551558754246, "2": 0.3967406098856946, "3": 0.25111978422855225}, "confidence": 0.28080651958748404}, "latency_ms": 3523}, "bankroll_after": 25, "id": "b", "name": "阿衝", "style": "激進壓迫派", "peak_bankroll": 1000}], "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": 60, "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.6460448859018025, "stand": 0.35395511409819747}, "confidence": 0.29208977180360507}, "risk": {"type": "score", "score": 1.6362591849140533, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.09561823679794927, "1": 0.3051076825522483, "2": 0.4666707395876024, "3": 0.1326033410622001}, "confidence": 0.37105250278965307}, "latency_ms": 3078, "drawn": "K♠", "total_after": 22}], "player_id": "a", "result": "lose", "net": -60, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.6935360880721767, "standard": 0.23786889435416095, "max": 0.06859501757366232}, "confidence": 0.540304132108265}, "bet": 60, "confidence": {"type": "score", "score": 1.2270464612686922, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.18249082291867635, "1": 0.47888782719333944, "2": 0.2677054155885997, "3": 0.07091593429938442}, "confidence": 0.4079718928939551}, "latency_ms": 2817}, "bankroll_after": 70, "id": "a", "name": "阿保", "style": "保守算牌派", "peak_bankroll": 1000}, {"cards": ["10♠", "A♠"], "bet": 20, "doubled": false, "total": 21, "soft": true, "bust": false, "blackjack": true, "steps": [], "player_id": "b", "result": "blackjack", "net": 30, "bet_decision": {"decision": {"type": "choice", "choice": "standard", "probabilities": {"min": 0.3162061736048218, "standard": 0.5628196491147833, "max": 0.12097417728039492}, "confidence": 0.344229473672175}, "bet": 20, "confidence": {"type": "score", "score": 1.7782719673372593, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.09884107421643187, "1": 0.25211204418752536, "2": 0.42098072163839456, "3": 0.22806615995764823}, "confidence": 0.32213964742196266}, "latency_ms": 3509}, "bankroll_after": 55, "id": "b", "name": "阿衝", "style": "激進壓迫派", "peak_bankroll": 1000}], "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": 40, "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.4424722777749632, "stand": 0.5575277222250369}, "confidence": 0.11505544445007376}, "risk": {"type": "score", "score": 1.5409442966500433, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.1296583923426123, "1": 0.32512322804031185, "2": 0.41983407024149666, "3": 0.12538430937557934}, "confidence": 0.2901756778988842}, "latency_ms": 3108, "drawn": null, "total_after": 13}], "player_id": "a", "result": "lose", "net": -40, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.7478566569390724, "standard": 0.2078704395438535, "max": 0.044272903517074004}, "confidence": 0.6217849854086086}, "bet": 40, "confidence": {"type": "score", "score": 1.2114043087298945, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.18940221712773983, "1": 0.4777441109615313, "2": 0.26490081796382325, "3": 0.0679528539469056}, "confidence": 0.4097912570146257}, "latency_ms": 2546}, "bankroll_after": 30, "id": "a", "name": "阿保", "style": "保守算牌派", "peak_bankroll": 1000}, {"cards": ["5♦", "9♣", "3♠"], "bet": 40, "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.6366657698736238, "stand": 0.3633342301263762}, "confidence": 0.27333153974724755}, "risk": {"type": "score", "score": 1.5978170782853303, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.12817755378927442, "1": 0.30062624017468276, "2": 0.416397779997481, "3": 0.1547984260385618}, "confidence": 0.28822022620820653}, "latency_ms": 3025, "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.36328371279258476, "stand": 0.6367162872074151}, "confidence": 0.27343257441483027}, "risk": {"type": "score", "score": 1.2671422123346068, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.23159921542459727, "1": 0.35512809851530586, "2": 0.32780394436098953, "3": 0.08546874169910729}, "confidence": 0.26965935681619857}, "latency_ms": 3556, "drawn": null, "total_after": 17}], "player_id": "b", "result": "lose", "net": -40, "bet_decision": {"decision": {"type": "choice", "choice": "standard", "probabilities": {"min": 0.3279833978847047, "standard": 0.5686003179254366, "max": 0.10341628418985868}, "confidence": 0.35290047688815496}, "bet": 40, "confidence": {"type": "score", "score": 1.7173934276050429, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.11675764826951873, "1": 0.2680269086363328, "2": 0.39627981031373566, "3": 0.2189356327804129}, "confidence": 0.279522162044217}, "latency_ms": 2841}, "bankroll_after": 15, "id": "b", "name": "阿衝", "style": "激進壓迫派", "peak_bankroll": 1000}], "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": 20, "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.40606821706912394, "stand": 0.5939317829308761}, "confidence": 0.18786356586175224}, "risk": {"type": "score", "score": 1.437199237330251, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.16489995037620225, "1": 0.343417591465515, "2": 0.38126572861011254, "3": 0.11041672954817025}, "confidence": 0.21636577823391023}, "latency_ms": 3109, "drawn": null, "total_after": 15}], "player_id": "a", "result": "lose", "net": -20, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.7689639297520218, "standard": 0.17759980649282997, "max": 0.053436263755148254}, "confidence": 0.6534458946280326}, "bet": 20, "confidence": {"type": "score", "score": 1.2731228885649526, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.16945966343355875, "1": 0.4645385057129478, "2": 0.2894211097084756, "3": 0.07658072114501789}, "confidence": 0.38795778456792984}, "latency_ms": 2842}, "bankroll_after": 10, "id": "a", "name": "阿保", "style": "保守算牌派", "peak_bankroll": 1000}, {"cards": ["A♥", "A♠", "Q♠", "8♣"], "bet": 10, "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.5659932347237522, "stand": 0.4340067652762479}, "confidence": 0.13198646944750436}, "risk": {"type": "score", "score": 1.4485356769211377, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.1924393543370901, "1": 0.30915360571570805, "2": 0.35583904863617616, "3": 0.14256799131102574}, "confidence": 0.16339969429908596}, "latency_ms": 3556, "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.5899106356195244, "stand": 0.41008936438047566}, "confidence": 0.17982127123904879}, "risk": {"type": "score", "score": 1.5111841800652206, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.1735935846414281, "1": 0.3039357858868495, "2": 0.36016349423679583, "3": 0.16230713523492646}, "confidence": 0.1865699095953678}, "latency_ms": 3376, "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.2939866919333886, "stand": 0.7060133080666113}, "confidence": 0.41202661613322267}, "risk": {"type": "score", "score": 1.2513615989988762, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.25479942083547175, "1": 0.33969869996162716, "2": 0.30484273857145383, "3": 0.10065914063144715}, "confidence": 0.23903955933018006}, "latency_ms": 3218, "drawn": null, "total_after": 20}], "player_id": "b", "result": "win", "net": 10, "bet_decision": {"decision": {"type": "choice", "choice": "standard", "probabilities": {"min": 0.3413432398179133, "standard": 0.5195029054186392, "max": 0.13915385476344747}, "confidence": 0.2792543581279588}, "bet": 10, "confidence": {"type": "score", "score": 1.8336160013048923, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.09634988931490898, "1": 0.22764980821676772, "2": 0.4220347143168453, "3": 0.253965588151478}, "confidence": 0.3256848250019363}, "latency_ms": 3391}, "bankroll_after": 25, "id": "b", "name": "阿衝", "style": "激進壓迫派", "peak_bankroll": 1000}], "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.8228014784139931, "stand": 0.17719852158600685}, "confidence": 0.6456029568279862}, "risk": {"type": "score", "score": 2.0828155414615814, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.05554269378511206, "1": 0.15559576403255418, "2": 0.43936484911797424, "3": 0.3494966930643596}, "confidence": 0.3838221553328621}, "latency_ms": 3285, "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.7921569733596483, "stand": 0.20784302664035162}, "confidence": 0.5843139467192966}, "risk": {"type": "score", "score": 1.6480482983213403, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.0896576388732094, "1": 0.3050623635674854, "2": 0.47285405792406077, "3": 0.1324259396352445}, "confidence": 0.38319641905085133}, "latency_ms": 3331, "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.6126276995310727, "stand": 0.3873723004689274}, "confidence": 0.2252553990621453}, "risk": {"type": "score", "score": 1.4291449486846561, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.12795107433644726, "1": 0.3840434516073026, "2": 0.4189149250913974, "3": 0.06909054896485294}, "confidence": 0.29096385075495}, "latency_ms": 3193, "drawn": "Q♠", "total_after": 25}], "player_id": "a", "result": "lose", "net": -10, "bet_decision": {"decision": {"type": "choice", "choice": "min", "probabilities": {"min": 0.795570618394425, "standard": 0.12263428043646388, "max": 0.08179510116911126}, "confidence": 0.6933559275916374}, "bet": 10, "confidence": {"type": "score", "score": 1.180788745938273, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.2499900936043853, "1": 0.40658705109017834, "2": 0.25606687106821446, "3": 0.08735598423722198}, "confidence": 0.3192310668529563}, "latency_ms": 3191}, "bankroll_after": 0, "id": "a", "name": "阿保", "style": "保守算牌派", "peak_bankroll": 1000}, {"cards": ["K♠", "9♥"], "bet": 20, "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.2774787174955811, "stand": 0.7225212825044188}, "confidence": 0.44504256500883765}, "risk": {"type": "score", "score": 0.98858174892492, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.3667697022460044, "1": 0.33877008990969004, "2": 0.23356896451768655, "3": 0.06089124332661898}, "confidence": 0.011418251075079988}, "latency_ms": 3675, "drawn": null, "total_after": 19}], "player_id": "b", "result": "lose", "net": -20, "bet_decision": {"decision": {"type": "choice", "choice": "standard", "probabilities": {"min": 0.3162865016372793, "standard": 0.5560665356529187, "max": 0.12764696270980197}, "confidence": 0.3340998034793781}, "bet": 20, "confidence": {"type": "score", "score": 1.7996943182411123, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.10475256570473437, "1": 0.2378297236005541, "2": 0.4103885374435761, "3": 0.24702917325113533}, "confidence": 0.3056359717388418}, "latency_ms": 3009}, "bankroll_after": 5, "id": "b", "name": "阿衝", "style": "激進壓迫派", "peak_bankroll": 1000}], "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": 229, "penetration_pct": 27, "cards_left_by_rank": {"A": 13, "2": 17, "3": 13, "4": 22, "5": 18, "6": 20, "7": 18, "8": 18, "9": 17, "10": 17, "J": 23, "Q": 20, "K": 13}, "hi_lo_running_count": -4, "true_count": -0.91}, "dealer_up": "2♦", "dealer_hole": "3♥", "dealer_revealed": true, "dealer": {"cards": ["2♦", "3♥", "J♥", "6♦"], "total": 21, "soft": false, "blackjack": false, "bust": false}, "events": ["阿保 資金為 0,本局無法下注", "莊家補牌 J♥ → 15 點", "莊家補牌 6♦ → 21 點", "莊家最終 21 點", "阿衝 資金歸零,破產出局"], "players": [{"cards": ["K♦", "K♠"], "bet": 5, "doubled": false, "total": 20, "soft": false, "bust": false, "blackjack": false, "steps": [{"type": "action", "cards": ["K♦", "K♠"], "total": 20, "soft": false, "dealer_up": "2♦", "allowed": ["hit", "stand"], "decision": {"type": "choice", "choice": "stand", "probabilities": {"hit": 0.27488101924853414, "stand": 0.7251189807514659}, "confidence": 0.4502379615029317}, "risk": {"type": "score", "score": 0.845673517325465, "legend": {"0": "非常有利", "1": "稍微有利", "2": "有點不利", "3": "非常不利"}, "probabilities": {"0": 0.4556057466582429, "1": 0.2881020785069146, "2": 0.21130508568597703, "3": 0.04498708914886543}, "confidence": 0.15432648267453497}, "latency_ms": 2968, "drawn": null, "total_after": 20}], "player_id": "b", "result": "lose", "net": -5, "bet_decision": {"decision": {"type": "choice", "choice": "standard", "probabilities": {"min": 0.28565455669419343, "standard": 0.5302136995077115, "max": 0.18413174379809508}, "confidence": 0.2953205492615672}, "bet": 5, "confidence": {"type": "score", "score": 1.799522969300636, "legend": {"0": "沒信心", "1": "普通", "2": "有信心", "3": "非常有信心"}, "probabilities": {"0": 0.11793350579907283, "1": 0.2275882904104801, "2": 0.3914999324811851, "3": 0.26297827130926194}, "confidence": 0.2735664266821123}, "latency_ms": 2672}, "bankroll_after": 0, "id": "b", "name": "阿衝", "style": "激進壓迫派", "peak_bankroll": 1000}], "shoe_after": {"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}}]}; | |
| const SUIT_RED = new Set(["♥","♦"]); | |
| const LABEL = {hit:"補牌", stand:"停牌", double:"加倍", min:"小注", standard:"大注", max:"全押"}; | |
| 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}; | |
| const RESULT_LABEL = {win:"勝", lose:"負", push:"和局", blackjack:"Blackjack"}; | |
| const $ = (s,el=document)=>el.querySelector(s); | |
| let DATA=null, roundIdx=0, autoTimer=null, dealerOpen=true; | |
| function money(n){ return (n>0?"+":"") + n.toLocaleString("en-US"); } | |
| function cls(n){ return n>0?"pos":(n<0?"neg":"neutral"); } | |
| function pct(x){ return (Math.max(0,Math.min(1,x||0))*100).toFixed(0)+"%"; } | |
| function cardHTML(card, hidden){ | |
| if(hidden) return '<div class="card down">?</div>'; | |
| const r=card.slice(0,-1), s=card.slice(-1); | |
| return '<div class="card '+(SUIT_RED.has(s)?"red":"")+'"><span class="r">'+r+'</span><span class="s">'+s+'</span></div>'; | |
| } | |
| function cardsHTML(cards, opts={}){ | |
| return '<div class="cards">'+cards.map((c,i)=>cardHTML(c, opts.hideIndex===i)).join("")+'</div>'; | |
| } | |
| function agentColor(id){ return id==="a" ? "var(--green)" : "var(--blue)"; } | |
| function agentMeta(id){ return DATA.meta.agents.find(a=>a.id===id) || {name:id,style:""}; } | |
| function totalsFor(round){ | |
| const out={}; | |
| for(const p of round.players){ | |
| out[p.id]=out[p.id]||{bet:0,net:0}; | |
| out[p.id].bet+=p.bet; out[p.id].net+=p.net; | |
| } | |
| return out; | |
| } | |
| function init(){ | |
| DATA = window.BATTLE_DATA; | |
| if(!DATA){ $("#app").innerHTML='<div class="err">找不到 battle.json,請用 HTTP 伺服器開啟此頁。</div>'; return; } | |
| document.title = DATA.meta.title + " · d1demo"; | |
| $("#app").innerHTML = ` | |
| <section class="view active" id="view-battle"> | |
| <div class="hero"> | |
| <h1>${DATA.meta.title.split(" ")[0]} <em>×</em> d1:21 點決策對戰</h1> | |
| <p>${DATA.meta.subtitle}。每一注、每一次補牌/停牌/加倍,都是模型即時輸出的機率分佈,不是寫死的策略表。</p> | |
| <div class="chips" id="metaChips"></div> | |
| </div> | |
| <div class="scoreboard" id="scoreboard"></div> | |
| <div class="panel table-panel"> | |
| <div class="round-toolbar"> | |
| <button class="icon-btn" id="prevRound">← 上一局</button> | |
| <div class="round-pills" id="roundPills"></div> | |
| <button class="icon-btn" id="nextRound">下一局 →</button> | |
| <button class="icon-btn" id="playBtn">▶ 自動播放</button> | |
| <span class="round-label" id="roundLabel"></span> | |
| </div> | |
| <div id="roundView"></div> | |
| <div class="arena-note">莊家由固定規則操作(S17),兩個玩家各自連到一個獨立的 llama.cpp server;模型只知道牌面、剩餘牌組成、真數與資金,不知道對手的手牌。</div> | |
| </div> | |
| </section> | |
| <section class="view" id="view-stats"></section> | |
| <section class="view" id="view-code"> | |
| <h2 class="section-title">整理程式</h2> | |
| <div class="data-box"> | |
| <p>這一頁不是手寫的靜態稿:<b>blackjack.py</b> 負責發牌、呼叫兩個 d1 server 做決策並記錄每一步;<b>build_site.py</b> 把 battle.json 編譯成你現在看到的網站。原始碼同步放在這個 Space 的 <code>source/</code> 目錄。</p> | |
| </div> | |
| <div class="code-tabs" id="codeTabs"></div> | |
| <div class="code-bar"> | |
| <span id="codeName">blackjack.py</span> | |
| <a class="btn ghost" id="codeDownload" href="source/blackjack.py" download>下載此檔</a> | |
| <button class="icon-btn" id="copyCode">複製</button> | |
| </div> | |
| <pre class="code"><code id="codeBody">載入中…</code></pre> | |
| </section> | |
| <section class="view" id="view-data"> | |
| <h2 class="section-title">原始資料</h2> | |
| <div class="data-box"> | |
| <h3>battle.json</h3> | |
| <p>完整對戰紀錄:每一局的牌、注碼、決策機率、信心度、風險評分、延遲、莊家補牌與資金變化。</p> | |
| <p><a class="btn" href="battle.json" download>下載 battle.json</a> | |
| <a class="btn ghost" href="battle.json" target="_blank" rel="noopener">在瀏覽器開啟</a></p> | |
| </div> | |
| <div class="data-box"> | |
| <h3>重現方式</h3> | |
| <p>需要 llama.cpp b11786 以上版本、LiquidAI/d1-3B-GGUF(Q8_0)與 Python 3。</p> | |
| <pre class="code"><code id="repro"></code></pre> | |
| </div> | |
| <div class="data-box"> | |
| <h3>本次執行摘要</h3> | |
| <pre class="code"><code id="metaJson"></code></pre> | |
| </div> | |
| </section> | |
| <footer style="border:0;padding-bottom:0"></footer> | |
| `; | |
| renderChips(); | |
| renderScoreboard(); | |
| renderPills(); | |
| renderRound(); | |
| renderStats(); | |
| renderCodeTabs(); | |
| bind(); | |
| } | |
| function renderChips(){ | |
| const m=DATA.meta; | |
| const chips=[ | |
| ["模型",m.model],["量化",m.quant],["llama.cpp",m.llama_build], | |
| ["局數",m.rounds_played],["隨機種子",m.seed],["耗時",m.duration_sec+"s"], | |
| ["賽制","打到歸零"],["莊家","S17"],["BJ 賠率","3:2"] | |
| ]; | |
| $("#metaChips").innerHTML=chips.map(([k,v])=>`<span class="chip">${k} <b>${v}</b></span>`).join(""); | |
| } | |
| function renderScoreboard(){ | |
| const agents=DATA.meta.agents; | |
| $("#scoreboard").innerHTML=agents.map(a=>{ | |
| const profit=a.final_bankroll-DATA.meta.start_bankroll; | |
| const color=agentColor(a.id); | |
| const status=a.bust_round?`第 ${a.bust_round} 局破產`:`出賽 ${a.rounds_played} 局`; | |
| return `<div class="score" style="--accent:${color}"> | |
| <div class="id">PLAYER ${a.id.toUpperCase()}</div> | |
| <div class="name">${a.name}</div> | |
| <div class="style">${a.style}</div> | |
| <div class="bank" style="color:${color}">${a.final_bankroll.toLocaleString("en-US")}</div> | |
| <div class="sub">起始 ${DATA.meta.start_bankroll} · 峰值 ${a.peak_bankroll} · 損益 <span class="big-delta ${cls(profit)}">${money(profit)}</span></div> | |
| <div class="sub">${status} · ${a.bust_round?"資金歸零,破產出局":"仍在牌桌上"}</div> | |
| </div>`; | |
| }).join(""); | |
| } | |
| function roundTone(r){ | |
| const res=r.players.map(p=>p.result); | |
| const wins=res.filter(x=>x==="win"||x==="blackjack").length; | |
| const loses=res.filter(x=>x==="lose").length; | |
| if(wins===res.length) return "win"; | |
| if(loses===res.length) return "lose"; | |
| if(res.every(x=>x==="push")) return "push"; | |
| return "mixed"; | |
| } | |
| function renderPills(){ | |
| $("#roundPills").innerHTML=DATA.rounds.map((r,i)=>{ | |
| return `<button class="pill ${roundTone(r)} ${i===roundIdx?"active":""}" data-round="${i}" title="第 ${r.n} 局"> | |
| <span class="dot"></span>${r.n}</button>`; | |
| }).join(""); | |
| $("#roundPills").querySelectorAll(".pill").forEach(b=>b.onclick=()=>{stopAuto();roundIdx=+b.dataset.round;dealerOpen=true;renderRound();renderPills();}); | |
| } | |
| function stepHTML(step, i){ | |
| const d=step.decision||{}; | |
| const probs=d.probabilities||{}; | |
| const keys=Object.keys(probs).sort((a,b)=>probs[b]-probs[a]); | |
| const risk=step.risk; | |
| const riskText=risk ? `風險評分 <b>${risk.score.toFixed(2)}</b>/3 · 信心 ${pct(risk.confidence)}` : ""; | |
| 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> 點`); | |
| return `<li class="step"> | |
| <div class="step-head"> | |
| <span class="step-no">#${i+1}</span> | |
| <span class="mini">${step.cards.join(" ")}</span> | |
| <span class="mini" style="color:var(--muted)">(${step.total}${step.soft?" 軟":""})</span> | |
| <span class="action ${LABEL[d.choice]?d.choice:"unknown"}">${LABEL[d.choice]||d.choice||"?"}</span> | |
| <span class="meta">信心 ${pct(d.confidence)} · ${((step.latency_ms||0)/1000).toFixed(2)}s</span> | |
| </div> | |
| <div class="probs"> | |
| ${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("")} | |
| </div> | |
| <div class="step-foot"><span>${drawn}</span>${riskText?`<span>${riskText}</span>`:""}${d.fallback?`<span style="color:var(--red)">伺服器無回應,改用規則備援</span>`:""}</div> | |
| </li>`; | |
| } | |
| function playerHTML(p){ | |
| const steps=(p.steps||[]).map(stepHTML).join(""); | |
| const bd=p.bet_decision||{}; | |
| const betDecision=bd.decision||{}; | |
| const betProbs=betDecision.probabilities||{}; | |
| const betLabel=LABEL[betDecision.choice]||betDecision.choice||""; | |
| return `<article class="player ${p.result} ${p.blackjack?"blackjack":""}" style="--accent:${agentColor(p.id)}"> | |
| <div class="p-head"> | |
| <div><h3>${p.name}</h3><div class="style">${p.style||""}</div></div> | |
| <div class="bet">注碼 <b>${p.bet}</b>${p.doubled?" · 已加倍":""}</div> | |
| </div> | |
| <div class="hand"> | |
| ${cardsHTML(p.cards)} | |
| <div class="total">${p.total}<small>${p.bust?"BUST":p.soft?"軟牌":p.blackjack?"BJ":"硬牌"}</small></div> | |
| <span class="result ${p.result}">${RESULT_LABEL[p.result]||p.result}</span> | |
| </div> | |
| <div class="bet-line"> | |
| <span class="tag">注碼決策 ${betLabel}</span> | |
| ${p.rebuy?`<span class="tag" style="color:var(--amber)">補充籌碼 +${p.rebuy}</span>`:""} | |
| <span>信心 ${pct(betDecision.confidence)}</span> | |
| ${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>`:""} | |
| </div> | |
| <details><summary>看模型怎麼決定注碼</summary> | |
| <div class="body">狀態包含資金、剩余牌、Hi-Lo 真數與規則;模型在 min / standard / max 三種注碼中挑一個,並另外給出信心評分。</div> | |
| </details> | |
| ${steps?`<ol class="steps">${steps}</ol>`:`<div class="step-foot" style="margin-top:12px">莊家直接 Blackjack,玩家沒有動作機會。</div>`} | |
| <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> | |
| </article>`; | |
| } | |
| function brokeHTML(a){ | |
| const m=agentMeta(a.id); | |
| const round=a.bust_round?`第 ${a.bust_round} 局`:"本局"; | |
| return `<article class="player lose" style="--accent:${agentColor(a.id)}"> | |
| <div class="p-head"> | |
| <div><h3>${a.name}</h3><div class="style">${a.style||""}</div></div> | |
| <div class="bet">注碼 <b>0</b></div> | |
| </div> | |
| <div class="hand"> | |
| <div class="total">0<small>BROKE</small></div> | |
| <span class="result lose">已破產</span> | |
| </div> | |
| <div class="step-foot" style="margin-top:12px">${round}資金歸零,之後無法再下注;只能在桌邊看對手打完。</div> | |
| <div class="net"><span>本局結果</span><span class="amt neutral">—</span></div> | |
| </article>`; | |
| } | |
| function renderRound(){ | |
| const r=DATA.rounds[roundIdx]; | |
| if(!r){ return; } | |
| const d=r.dealer||{}; | |
| const hideHole=!dealerOpen && r.dealer_revealed; | |
| const dealerCards=(d.cards||[]).map((c,i)=>cardHTML(c, hideHole && i===1)).join(""); | |
| const totals=totalsFor(r); | |
| const pillTone=roundTone(r); | |
| $("#roundLabel").textContent=`第 ${r.n} / ${DATA.rounds.length} 局 · 剩餘 ${r.shoe_before.remaining_cards} 張 · 真數 ${r.shoe_before.true_count}`; | |
| $("#roundView").innerHTML=` | |
| <div class="round-head"> | |
| <h2>第 ${r.n} 局</h2> | |
| <span class="shoe">洗牌前剩餘 ${r.shoe_before.remaining_cards} 張(穿透率 ${r.shoe_before.penetration_pct}%) · Hi-Lo 真數 ${r.shoe_before.true_count}</span> | |
| </div> | |
| <div class="dealer"> | |
| <div> | |
| <div class="label">DEALER · 莊家</div> | |
| <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> | |
| </div> | |
| <div class="cards">${dealerCards}</div> | |
| <div class="note">${r.events.join(" · ")||"莊家依規則補牌"}</div> | |
| ${r.dealer_revealed?`<button class="icon-btn" id="revealBtn">${dealerOpen?"蓋回暗牌":"揭開暗牌"}</button>`:""} | |
| </div> | |
| <div class="players">${DATA.meta.agents.map(a=>{ | |
| const p=r.players.find(x=>x.id===a.id); | |
| return p?playerHTML(p):brokeHTML(a); | |
| }).join("")}</div> | |
| <div class="events"><b>牌桌事件</b><ul>${r.events.map(e=>`<li>${e}</li>`).join("")}</ul></div> | |
| `; | |
| const rb=$("#revealBtn"); | |
| if(rb) rb.onclick=()=>{dealerOpen=!dealerOpen;renderRound();}; | |
| $("#prevRound").disabled=roundIdx===0; | |
| $("#nextRound").disabled=roundIdx===DATA.rounds.length-1; | |
| document.querySelectorAll(".pill").forEach((b,i)=>b.classList.toggle("active",i===roundIdx)); | |
| } | |
| function bind(){ | |
| $("#prevRound").onclick=()=>{stopAuto(); if(roundIdx>0){roundIdx--;dealerOpen=true;renderRound();}}; | |
| $("#nextRound").onclick=()=>{stopAuto(); if(roundIdx<DATA.rounds.length-1){roundIdx++;dealerOpen=true;renderRound();}}; | |
| $("#playBtn").onclick=()=>{ | |
| if(autoTimer){stopAuto();return;} | |
| roundIdx=0;dealerOpen=true;renderRound(); | |
| $("#playBtn").textContent="⏸ 暫停"; | |
| autoTimer=setInterval(()=>{ | |
| if(roundIdx>=DATA.rounds.length-1){stopAuto();return;} | |
| roundIdx++;dealerOpen=true;renderRound(); | |
| },4000); | |
| }; | |
| document.querySelectorAll("nav button").forEach(b=>{ | |
| b.onclick=()=>{ | |
| document.querySelectorAll("nav button").forEach(x=>x.classList.toggle("active",x===b)); | |
| document.querySelectorAll(".view").forEach(v=>v.classList.toggle("active",v.id==="view-"+b.dataset.view)); | |
| window.scrollTo({top:0,behavior:"smooth"}); | |
| }; | |
| }); | |
| } | |
| function stopAuto(){ if(autoTimer){clearInterval(autoTimer);autoTimer=null;} const b=$("#playBtn"); if(b)b.textContent="▶ 自動播放"; } | |
| function statsFor(id){ | |
| 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:[]}; | |
| for(const r of DATA.rounds){ | |
| const p=r.players.find(x=>x.id===id); if(!p) continue; | |
| s.rounds++; | |
| if(p.result==="win")s.win++; | |
| else if(p.result==="lose")s.lose++; | |
| else if(p.result==="push")s.push++; | |
| else if(p.result==="blackjack")s.win++; | |
| if(p.blackjack)s.bj++; | |
| if(p.bust)s.bust++; | |
| if(p.doubled)s.double++; | |
| s.net+=p.net; s.wagered+=p.bet; | |
| (p.steps||[]).forEach(st=>{ | |
| s.steps++; s.conf+=st.decision.confidence||0; s.lat+=st.latency_ms||0; | |
| s.actions[st.decision.choice]=(s.actions[st.decision.choice]||0)+1; | |
| }); | |
| const p1=(p.steps||[])[0]; | |
| if(p1) s.firstActions.push({round:r.n,action:p1.decision.choice}); | |
| } | |
| s.avgConf=s.steps?s.conf/s.steps:0; | |
| s.avgLat=s.steps?s.lat/s.steps:0; | |
| return s; | |
| } | |
| function renderStats(){ | |
| const a=statsFor("a"), b=statsFor("b"); | |
| const agree=a.firstActions.filter(x=>{ | |
| const y=b.firstActions.find(z=>z.round===x.round); | |
| return y && y.action===x.action; | |
| }).length; | |
| const compared=a.firstActions.filter(x=>b.firstActions.some(z=>z.round===x.round)).length; | |
| const dealerBust=DATA.rounds.filter(r=>r.dealer&&r.dealer.bust).length; | |
| const dealerBj=DATA.rounds.filter(r=>r.dealer&&r.dealer.blackjack).length; | |
| const dealerAvg=DATA.rounds.reduce((sum,r)=>sum+((r.dealer||{}).total||0),0)/Math.max(DATA.rounds.length,1); | |
| const totalNet=a.net+b.net; | |
| const houseEdge=totalNet; | |
| function statCard(id,s){ | |
| const m=agentMeta(id); | |
| const totalActions=Math.max(s.steps,1); | |
| return `<div class="stat-card" style="--accent:${agentColor(id)}"> | |
| <h3>${m.name}</h3><div class="style">${m.style}</div> | |
| <div class="kv"> | |
| <div><div class="k">損益</div><div class="v ${cls(s.net)}">${money(s.net)}</div></div> | |
| <div><div class="k">勝 / 負 / 和</div><div class="v">${s.win} / ${s.lose} / ${s.push}</div></div> | |
| <div><div class="k">Blackjack</div><div class="v">${s.bj}</div></div> | |
| <div><div class="k">爆牌</div><div class="v">${s.bust}</div></div> | |
| <div><div class="k">加倍次數</div><div class="v">${s.double}</div></div> | |
| <div><div class="k">總下注</div><div class="v">${s.wagered}</div></div> | |
| <div><div class="k">出賽局數</div><div class="v">${m.rounds_played}</div></div> | |
| <div><div class="k">峰值資金</div><div class="v">${m.peak_bankroll}</div></div> | |
| <div><div class="k">破產局數</div><div class="v">${m.bust_round?"第 "+m.bust_round+" 局":"未破產"}</div></div> | |
| <div><div class="k">平均信心</div><div class="v">${pct(s.avgConf)}</div></div> | |
| <div><div class="k">平均推理</div><div class="v">${(s.avgLat/1000).toFixed(2)}s</div></div> | |
| </div> | |
| <div class="dist"> | |
| <div class="mini-title">動作分佈(${s.steps} 次)</div> | |
| ${["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("")} | |
| </div> | |
| </div>`; | |
| } | |
| const chart=bankrollChart(); | |
| $("#view-stats").innerHTML=` | |
| <h2 class="section-title">決策統計</h2> | |
| <div class="stats-grid">${statCard("a",a)}${statCard("b",b)}</div> | |
| <div class="chart-wrap"> | |
| <div class="chart-legend"> | |
| <span><i style="background:var(--green)"></i>${agentMeta("a").name}</span> | |
| <span><i style="background:var(--blue)"></i>${agentMeta("b").name}</span> | |
| <span>起始資金 ${DATA.meta.start_bankroll}</span> | |
| </div> | |
| ${chart} | |
| </div> | |
| <div class="h2h"> | |
| <h3 style="margin:0 0 6px">兩個 d1 有多像?</h3> | |
| <p style="color:var(--muted);margin:4px 0 10px">比較每一局雙方第一次動作(同一副牌、同一個莊家明牌、相同的剩餘牌資訊)。</p> | |
| <div class="big">${compared?Math.round(100*agree/compared):0}%</div> | |
| <div style="color:var(--muted);font-size:13px">首個動作一致率 · 共比較 ${compared} 局</div> | |
| <div class="kv" style="grid-template-columns:repeat(3,1fr);margin-top:16px"> | |
| <div><div class="k">莊家爆牌率</div><div class="v">${Math.round(100*dealerBust/DATA.rounds.length)}%</div></div> | |
| <div><div class="k">莊家 Blackjack</div><div class="v">${dealerBj} 局</div></div> | |
| <div><div class="k">莊家平均點數</div><div class="v">${dealerAvg.toFixed(1)}</div></div> | |
| </div> | |
| </div> | |
| <div class="data-box" style="margin-top:14px"> | |
| <h3>終局:牌桌關閉</h3> | |
| <p style="font-size:15px">${DATA.meta.end_reason==="both_broke"?"兩邊資金都歸零,遊戲在第 "+DATA.meta.rounds_played+" 局結束。": "達到局數上限,遊戲結束。"} | |
| ${DATA.meta.agents.map(a=>`${a.name} 在第 ${a.bust_round||"—"} 局破產(峰值 ${a.peak_bankroll})`).join(" · ")}</p> | |
| </div> | |
| <div class="data-box" style="margin-top:14px"> | |
| <h3>兩個模型合計對抗莊家</h3> | |
| <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> | |
| </div> | |
| `; | |
| } | |
| function bankrollChart(){ | |
| const W=760,H=230,pad={l:52,r:14,t:14,b:26}; | |
| const agents=DATA.meta.agents; | |
| const series=agents.map(a=>{ | |
| const vals=[DATA.meta.start_bankroll]; | |
| for(const r of DATA.rounds){ | |
| const p=r.players.find(x=>x.id===a.id); | |
| vals.push(p?p.bankroll_after:vals[vals.length-1]); | |
| } | |
| return {a,vals,color:a.id==="a"?"#39d98a":"#5cc8ff"}; | |
| }); | |
| const all=series.flatMap(s=>s.vals); | |
| let min=Math.min(...all),max=Math.max(...all); | |
| const span=Math.max(max-min,100); | |
| min=Math.max(0,Math.floor((min-span*0.1)/50)*50); max=Math.ceil((max+span*0.1)/50)*50; | |
| const X=i=>pad.l+(W-pad.l-pad.r)*(i/(DATA.rounds.length||1)); | |
| const Y=v=>H-pad.b-(H-pad.t-pad.b)*((v-min)/(max-min||1)); | |
| const step=Math.max(50,Math.ceil((max-min)/4/50)*50); | |
| min=Math.floor(min/step)*step; max=Math.ceil(max/step)*step; | |
| const ticks=Math.round((max-min)/step); | |
| let grid=""; | |
| for(let i=0;i<=ticks;i++){ | |
| const v=min+(max-min)*i/ticks, y=Y(v); | |
| grid+=`<line x1="${pad.l}" y1="${y}" x2="${W-pad.r}" y2="${y}" stroke="rgba(255,255,255,.07)"/>`; | |
| grid+=`<text x="${pad.l-8}" y="${y+4}" fill="#8fae9f" font-size="10" text-anchor="end">${Math.round(v)}</text>`; | |
| } | |
| const paths=series.map(s=>{ | |
| const d=s.vals.map((v,i)=>`${i?"L":"M"}${X(i).toFixed(1)},${Y(v).toFixed(1)}`).join(" "); | |
| 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(""); | |
| return `<path d="${d}" fill="none" stroke="${s.color}" stroke-width="2.4" stroke-linejoin="round"/>${dots}`; | |
| }).join(""); | |
| const stepX=Math.max(1,Math.ceil(DATA.rounds.length/8)); | |
| let xlabels=""; | |
| for(let i=0;i<=DATA.rounds.length;i+=stepX){ | |
| xlabels+=`<text x="${X(i)}" y="${H-6}" fill="#8fae9f" font-size="10" text-anchor="middle">${i}</text>`; | |
| } | |
| return `<svg viewBox="0 0 ${W} ${H}" width="100%" role="img" aria-label="資金曲線">${grid}${paths}${xlabels}</svg>`; | |
| } | |
| const CODE_FILES=[ | |
| {file:"blackjack.py",label:"blackjack.py · 對戰與記錄"}, | |
| {file:"build_site.py",label:"build_site.py · 網頁編譯"}, | |
| {file:"run_demo.sh",label:"run_demo.sh · 一鍵執行"}, | |
| ]; | |
| function renderCodeTabs(){ | |
| $("#codeTabs").innerHTML=CODE_FILES.map((f,i)=>`<button data-file="${f.file}" class="${i===0?"active":""}">${f.label}</button>`).join(""); | |
| $("#codeTabs").querySelectorAll("button").forEach(b=>b.onclick=()=>{ | |
| $("#codeTabs").querySelectorAll("button").forEach(x=>x.classList.toggle("active",x===b)); | |
| loadCode(b.dataset.file); | |
| }); | |
| loadCode("blackjack.py"); | |
| $("#copyCode").onclick=async()=>{ | |
| try{await navigator.clipboard.writeText($("#codeBody").textContent);$("#copyCode").textContent="已複製";setTimeout(()=>$("#copyCode").textContent="複製",1200);}catch(e){} | |
| }; | |
| $("#repro").textContent=`# 1) 下載模型與 llama.cpp(CPU 版) | |
| llama-server -m d1-3B-Q8_0.gguf --port 8081 -c 2048 -np 1 --no-webui & | |
| llama-server -m d1-3B-Q8_0.gguf --port 8082 -c 2048 -np 1 --no-webui & | |
| # 2) 讓兩個 d1 打 12 局,並把每一步寫成 JSON | |
| python3 blackjack.py --rounds 12 --seed ${DATA.meta.seed} --out battle.json | |
| # 3) 把 JSON 編譯成這個靜態網站 | |
| python3 build_site.py --battle battle.json --out site | |
| # 4) 上傳 static Space | |
| python3 - <<'PY' | |
| from huggingface_hub import HfApi | |
| HfApi().upload_folder(folder_path="site", repo_id="HelloSun/d1demo", repo_type="space") | |
| PY`; | |
| $("#metaJson").textContent=JSON.stringify(DATA.meta,null,2); | |
| } | |
| function loadCode(file){ | |
| $("#codeName").textContent=file; | |
| $("#codeDownload").href="source/"+file; | |
| $("#codeBody").textContent="載入中…"; | |
| fetch("source/"+file).then(r=>{if(!r.ok)throw new Error(r.status);return r.text();}) | |
| .then(t=>{$("#codeBody").textContent=t;}) | |
| .catch(()=>{$("#codeBody").textContent="(無法載入 "+file+",請確認 source/ 目錄存在)";}); | |
| } | |
| init(); | |
| </script> | |
| </body> | |
| </html> | |