Skip to content

Commit a52e4ea

Browse files
hankhsu1996claude
andcommitted
Add interactive web visualization with Svelte
- Create Svelte + Tailwind + DaisyUI web app in web/ directory - Add Python script to generate 96 strategy JSON files for all rule combinations - Implement responsive layout with config sidebar and horizontal tables - Use HSL colors for easy customization of action cell colors - Update GitHub Actions workflow to build and deploy to GitHub Pages - Add documentation in docs/ folder 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 37018e6 commit a52e4ea

30 files changed

Lines changed: 3470 additions & 78 deletions

.github/workflows/pages.yml

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,31 @@ jobs:
2424
- name: Setup uv
2525
uses: astral-sh/setup-uv@v4
2626

27-
- name: Generate strategy tables
28-
run: |
29-
mkdir -p _site
30-
uv run main.py --format html --output _site/index.html
27+
- name: Setup Node.js
28+
uses: actions/setup-node@v4
29+
with:
30+
node-version: "20"
31+
cache: "npm"
32+
cache-dependency-path: web/package-lock.json
33+
34+
- name: Generate strategy JSON files
35+
run: uv run python -m scripts.generate_strategies
36+
37+
- name: Install npm dependencies
38+
working-directory: web
39+
run: npm ci
40+
41+
- name: Build Svelte app
42+
working-directory: web
43+
run: npm run build
3144

3245
- name: Setup Pages
3346
uses: actions/configure-pages@v5
3447

3548
- name: Upload artifact
3649
uses: actions/upload-pages-artifact@v3
3750
with:
38-
path: _site
51+
path: web/dist
3952

4053
deploy:
4154
environment:

.gitignore

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ dist/
1010
downloads/
1111
eggs/
1212
.eggs/
13-
lib/
14-
lib64/
13+
/lib/
14+
/lib64/
1515
parts/
1616
sdist/
1717
var/
@@ -38,6 +38,11 @@ htmlcov/
3838

3939
# Generated files
4040
strategy.html
41+
web/public/strategies/
42+
43+
# Web app
44+
web/node_modules/
45+
web/dist/
4146

4247
# Linting cache
4348
.ruff_cache/

CLAUDE.md

Lines changed: 90 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,76 +2,112 @@
22

33
## Project Overview
44

5-
A Python project that generates mathematically optimal blackjack basic strategy tables based on configurable game rules. The strategy is computed using Expected Value (EV) calculations.
5+
A Python project that generates mathematically optimal blackjack basic strategy tables based on configurable game rules. The strategy is computed using Expected Value (EV) calculations. Includes an interactive web app for visualization.
6+
7+
**Live Demo**: https://hankhsu1996.github.io/blackjack-basic-strategy/
68

79
## Quick Start
810

11+
### Web App (Recommended)
12+
13+
```bash
14+
# Generate strategy JSON files
15+
uv run python -m scripts.generate_strategies
16+
17+
# Start dev server
18+
cd web && npm run dev
19+
```
20+
21+
Open http://localhost:5173/blackjack-basic-strategy/
22+
23+
### CLI
24+
925
```bash
1026
# Text output to terminal
1127
uv run main.py
1228

1329
# HTML output (color-coded)
1430
uv run main.py --format html
15-
16-
# Custom output file
17-
uv run main.py --format html --output my-strategy.html
1831
```
1932

20-
## CLI Options
33+
## Project Structure
2134

22-
| Option | Description |
23-
|--------|-------------|
24-
| `-f, --format` | Output format: `text` (default) or `html` |
25-
| `-o, --output` | Output file path (default: stdout for text, strategy.html for HTML) |
35+
```
36+
├── src/blackjack/ # Python strategy calculation
37+
│ ├── config.py # GameConfig dataclass
38+
│ ├── cards.py # Card values, probabilities
39+
│ ├── dealer.py # Dealer outcome distribution
40+
│ ├── evaluator.py # EV calculations
41+
│ ├── strategy.py # Optimal action selection
42+
│ ├── tables.py # Strategy data generation
43+
│ └── renderers.py # Text/HTML renderers
44+
├── scripts/
45+
│ └── generate_strategies.py # Generate JSON for web app
46+
├── web/ # Svelte + Tailwind + DaisyUI
47+
│ ├── src/
48+
│ │ ├── App.svelte
49+
│ │ └── lib/
50+
│ │ ├── components/ # UI components
51+
│ │ ├── stores/ # Svelte stores
52+
│ │ ├── types/ # TypeScript types
53+
│ │ └── utils/ # Color utilities
54+
│ └── public/strategies/ # Pre-computed JSON (generated)
55+
└── main.py # CLI entry point
56+
```
2657

2758
## Development
2859

29-
Setup (installs dependencies automatically):
30-
```bash
31-
uv sync
32-
```
60+
### Python
3361

34-
Run:
3562
```bash
36-
uv run main.py
63+
uv sync # Install dependencies
64+
uv run main.py # Run CLI
65+
uv run ruff check . # Lint
66+
uv run ruff format . # Format
3767
```
3868

39-
Lint:
40-
```bash
41-
uv run ruff check .
42-
```
69+
### Web App
4370

44-
Format:
4571
```bash
46-
uv run ruff format .
72+
cd web
73+
npm install # Install dependencies
74+
npm run dev # Dev server
75+
npm run build # Production build
4776
```
4877

49-
Lint and fix auto-fixable issues:
78+
### Generate Strategy JSON
79+
5080
```bash
51-
uv run ruff check --fix .
81+
uv run python -m scripts.generate_strategies
5282
```
5383

54-
## Project Structure
84+
Generates 96 JSON files (6 deck options × 2⁴ boolean options) to `web/public/strategies/`.
5585

56-
```
57-
src/blackjack/
58-
├── config.py # GameConfig dataclass - all rule variations
59-
├── cards.py # Card values, probabilities, hand_value()
60-
├── dealer.py # DealerProbabilities - dealer outcome distribution
61-
├── evaluator.py # EVCalculator - EV for stand/hit/double/split
62-
├── strategy.py # BasicStrategy - optimal action selection
63-
├── tables.py # StrategyTables - data generation
64-
└── renderers.py # TextRenderer, HTMLRenderer - output formatting
65-
```
86+
## Configuration Options
87+
88+
| Parameter | Default | Description |
89+
|-----------|---------|-------------|
90+
| num_decks | 6 | Number of decks (0 = infinite) |
91+
| dealer_hits_soft_17 | False | H17 vs S17 rule |
92+
| double_after_split | True | DAS allowed |
93+
| resplit_aces | False | RSA allowed |
94+
| dealer_peeks | True | Dealer checks for blackjack |
95+
96+
## Action Codes
97+
98+
- `S` - Stand
99+
- `H` - Hit
100+
- `D` / `Dh` / `Ds` - Double (or Hit/Stand if not allowed)
101+
- `P` / `Ph` - Split (or Hit if not allowed)
66102

67103
## Architecture
68104

69105
### Data Flow
70106

71107
```
72-
GameConfig → EVCalculator → BasicStrategy → StrategyTables → Renderer → Output
73-
74-
DealerProbabilities StrategyData
108+
GameConfig → EVCalculator → BasicStrategy → StrategyTables → JSON/Renderer
109+
110+
DealerProbabilities
75111
```
76112

77113
### Key Classes
@@ -107,51 +143,34 @@ For finite decks (num_decks > 0), the calculator uses composition-dependent prob
107143

108144
This matches real-world 4-8 deck basic strategy charts. For infinite deck (num_decks = 0), standard probabilities are used.
109145

110-
## Configuration Options
146+
### Web App Stack
111147

112-
| Parameter | Default | Description |
113-
|-----------|---------|-------------|
114-
| num_decks | 6 | Number of decks (0 = infinite) |
115-
| dealer_hits_soft_17 | False | H17 vs S17 rule |
116-
| double_after_split | True | DAS allowed |
117-
| resplit_aces | False | RSA allowed |
118-
| max_splits | 3 | Max splits (3 = up to 4 hands) |
119-
| blackjack_pays | 1.5 | 3:2 = 1.5, 6:5 = 1.2 |
120-
| dealer_peeks | True | Dealer checks for blackjack |
148+
- **Svelte 5** - Reactive UI framework
149+
- **Tailwind CSS** - Utility-first styling
150+
- **DaisyUI** - Component library
151+
- **Vite** - Build tool
121152

122-
## Action Codes
153+
### Key Design Decisions
123154

124-
- `S` - Stand
125-
- `H` - Hit
126-
- `D` - Double
127-
- `Dh` - Double if allowed, otherwise Hit
128-
- `Ds` - Double if allowed, otherwise Stand
129-
- `P` - Split
155+
1. **Pre-computed strategies**: 96 JSON files cover all rule combinations. Faster than runtime calculation.
156+
2. **HSL colors**: Easy to adjust whiteness/saturation for accessibility.
157+
3. **Responsive layout**: Desktop shows sidebar + horizontal tables; mobile uses collapsible config.
158+
159+
## Deployment
160+
161+
GitHub Actions automatically:
162+
1. Generates strategy JSON files
163+
2. Builds Svelte app
164+
3. Deploys to GitHub Pages
130165

131166
## Commit Format
132167

133168
```
134169
<Summary starting with verb, 50 chars or less>
135170
136-
- First change description (wrap at 72 chars)
137-
- Second change description
138-
- 2-5 bullets based on change size
171+
- Bullet points (2-5)
139172
140173
🤖 Generated with [Claude Code](https://claude.com/claude-code)
141174
142175
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
143176
```
144-
145-
## Testing
146-
147-
```bash
148-
python3 -m pytest tests/
149-
```
150-
151-
## Dependencies
152-
153-
Runtime: `tabulate` for table formatting
154-
155-
Dev: `ruff` for linting and formatting
156-
157-
Managed with `uv` - see https://docs.astral.sh/uv/

docs/strategy-calculation.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Strategy Calculation
2+
3+
This document explains how the blackjack basic strategy is calculated using Expected Value (EV) analysis.
4+
5+
## Overview
6+
7+
Basic strategy is the mathematically optimal way to play every hand in blackjack. For each combination of player hand and dealer upcard, we calculate the expected value of each possible action (stand, hit, double, split) and choose the one with the highest EV.
8+
9+
## Expected Value Calculations
10+
11+
### Stand EV
12+
13+
When standing, the outcome depends entirely on the dealer's final hand:
14+
15+
```
16+
EV(stand) = P(dealer busts) × 1
17+
+ Σ P(dealer gets X) × result(player_total vs X)
18+
```
19+
20+
Where `result` is +1 (win), 0 (push), or -1 (lose).
21+
22+
### Hit EV
23+
24+
Hitting is recursive - after receiving a card, we again choose the best action:
25+
26+
```
27+
EV(hit) = Σ P(draw card C) × [
28+
if busted: -1
29+
else: max(EV(stand), EV(hit)) # after adding card C
30+
]
31+
```
32+
33+
### Double EV
34+
35+
Doubling means doubling the bet, taking exactly one card, then standing:
36+
37+
```
38+
EV(double) = 2 × Σ P(draw card C) × [
39+
if busted: -1
40+
else: EV(stand after adding C)
41+
]
42+
```
43+
44+
### Split EV
45+
46+
Splitting creates two hands, each starting with one of the paired cards:
47+
48+
```
49+
EV(split) = 2 × Σ P(draw card C) × EV(optimal play of pair_card + C)
50+
```
51+
52+
Special rules apply for split aces (often only one card allowed).
53+
54+
## State Representation
55+
56+
Instead of tracking exact card combinations, we use `(total, soft_aces)` tuples:
57+
58+
- `total`: Current hand value (4-21)
59+
- `soft_aces`: Number of aces counted as 11 (0 or 1)
60+
61+
This reduces the state space from exponential to ~40 states while preserving all information needed for optimal decisions.
62+
63+
## Composition-Dependent Strategy
64+
65+
For finite decks, the probability of drawing each card changes based on cards already seen:
66+
67+
```python
68+
# Adjust probabilities for removed cards
69+
removed = player_cards + (dealer_upcard,)
70+
adj_probs = get_card_probabilities(num_decks, removed)
71+
```
72+
73+
This creates slightly different strategies depending on the exact cards in play, matching real-world casino conditions.
74+
75+
## Dealer Probabilities
76+
77+
The dealer follows fixed rules (hit until 17+, stand on hard 17), so we can pre-compute outcome probabilities:
78+
79+
```
80+
P(dealer final = X | upcard) for X in {17, 18, 19, 20, 21, bust}
81+
```
82+
83+
For H17 rules, the dealer hits on soft 17, changing these probabilities.
84+
85+
## Conditional on No Blackjack
86+
87+
When the dealer peeks for blackjack (US rules), if the hand continues, we know the dealer doesn't have blackjack:
88+
89+
```
90+
P(outcome | no blackjack) = P(outcome) / P(no blackjack)
91+
```
92+
93+
This affects EVs when dealer shows 10 or Ace.

0 commit comments

Comments
 (0)