LMLK commited on
Commit
1e43f0c
·
verified ·
1 Parent(s): 2316e7b

Add 3 files

Browse files
Files changed (3) hide show
  1. README.md +7 -5
  2. index.html +793 -19
  3. prompts.txt +0 -0
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Blackjack Probability Calculator
3
- emoji: 🦀
4
- colorFrom: green
5
- colorTo: blue
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: blackjack-probability-calculator
3
+ emoji: 🐳
4
+ colorFrom: pink
5
+ colorTo: pink
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,793 @@
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="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Blackjack Probability Calculator</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ .card {
11
+ width: 60px;
12
+ height: 90px;
13
+ border-radius: 5px;
14
+ display: inline-flex;
15
+ justify-content: center;
16
+ align-items: center;
17
+ margin: 0 5px;
18
+ font-weight: bold;
19
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
20
+ }
21
+ /* Card color is purely cosmetic for rank display; not tracking actual suits. */
22
+ .card-red {
23
+ background: linear-gradient(135deg, #fff, #ffdddd);
24
+ color: #d00;
25
+ border: 1px solid #d00;
26
+ }
27
+ .card-black {
28
+ background: linear-gradient(135deg, #fff, #eeeeee);
29
+ color: #000;
30
+ border: 1px solid #000;
31
+ }
32
+ .progress-bar {
33
+ height: 20px;
34
+ border-radius: 10px;
35
+ overflow: hidden;
36
+ background-color: #e5e7eb;
37
+ }
38
+ .progress-fill {
39
+ height: 100%;
40
+ transition: width 0.3s ease;
41
+ }
42
+ .strategy-action {
43
+ animation: pulse 2s infinite;
44
+ }
45
+ @keyframes pulse {
46
+ 0% { transform: scale(1); }
47
+ 50% { transform: scale(1.05); }
48
+ 100% { transform: scale(1); }
49
+ }
50
+ .tooltip {
51
+ position: relative;
52
+ display: inline-block;
53
+ }
54
+ .tooltip .tooltiptext {
55
+ visibility: hidden;
56
+ width: 200px;
57
+ background-color: #333;
58
+ color: #fff;
59
+ text-align: center;
60
+ border-radius: 6px;
61
+ padding: 5px;
62
+ position: absolute;
63
+ z-index: 1;
64
+ bottom: 125%;
65
+ left: 50%;
66
+ margin-left: -100px;
67
+ opacity: 0;
68
+ transition: opacity 0.3s;
69
+ }
70
+ .tooltip:hover .tooltiptext {
71
+ visibility: visible;
72
+ opacity: 1;
73
+ }
74
+ </style>
75
+ </head>
76
+ <body class="bg-gray-100 min-h-screen">
77
+ <div class="container mx-auto px-4 py-8">
78
+ <!-- Header -->
79
+ <header class="mb-8 text-center">
80
+ <h1 class="text-4xl font-bold text-gray-800 mb-2">
81
+ <i class="fas fa-calculator text-blue-600"></i> Blackjack Probability Calculator
82
+ </h1>
83
+ <p class="text-gray-600 max-w-2xl mx-auto">
84
+ Calculate probabilities and optimal strategy for any blackjack situation. Perfect for players who want to make mathematically sound decisions.
85
+ </p>
86
+ </header>
87
+
88
+ <!-- Main Calculator -->
89
+ <div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8">
90
+ <!-- Configuration Section -->
91
+ <div class="p-6 border-b border-gray-200">
92
+ <h2 class="text-xl font-semibold text-gray-800 mb-4">
93
+ <i class="fas fa-cog text-blue-500 mr-2"></i>Game Configuration
94
+ </h2>
95
+
96
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
97
+ <div>
98
+ <label for="numDecks" class="block text-sm font-medium text-gray-700 mb-1">Number of Decks (1-8)</label>
99
+ <div class="flex items-center">
100
+ <input type="number" id="numDecks" min="1" max="8" value="1"
101
+ class="w-20 px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
102
+ </div>
103
+ </div>
104
+
105
+ <div class="flex items-center pt-5">
106
+ <input type="checkbox" id="h17Rule" checked class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
107
+ <label for="h17Rule" class="ml-2 block text-sm text-gray-700">
108
+ Dealer Hits on Soft 17 (H17)
109
+ </label>
110
+ </div>
111
+
112
+ <div class="flex items-center pt-5">
113
+ <input type="checkbox" id="dasRule" checked class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
114
+ <label for="dasRule" class="ml-2 block text-sm text-gray-700">
115
+ Double After Split Allowed (DAS)
116
+ </label>
117
+ </div>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- Player's Hand Section -->
122
+ <div class="p-6 border-b border-gray-200">
123
+ <h2 class="text-xl font-semibold text-gray-800 mb-4">
124
+ <i class="fas fa-user text-green-500 mr-2"></i>Player's Hand
125
+ </h2>
126
+
127
+ <div class="mb-4">
128
+ <label for="playerCards" class="block text-sm font-medium text-gray-700 mb-1">
129
+ Your Cards (comma separated, e.g. A, 10, 3)
130
+ </label>
131
+ <input type="text" id="playerCards" value="10, 5"
132
+ class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
133
+ </div>
134
+
135
+ <div class="flex flex-wrap gap-2" id="playerCardsDisplay">
136
+ <!-- Cards will be displayed here -->
137
+ </div>
138
+ </div>
139
+
140
+ <!-- Dealer's Up Card Section -->
141
+ <div class="p-6">
142
+ <h2 class="text-xl font-semibold text-gray-800 mb-4">
143
+ <i class="fas fa-user-tie text-red-500 mr-2"></i>Dealer's Up Card
144
+ </h2>
145
+
146
+ <div class="mb-4">
147
+ <label for="dealerCard" class="block text-sm font-medium text-gray-700 mb-1">
148
+ Dealer's Face-Up Card
149
+ </label>
150
+ <input type="text" id="dealerCard" value="A"
151
+ class="mt-1 block w-20 px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
152
+ </div>
153
+
154
+ <div class="flex" id="dealerCardDisplay">
155
+ <!-- Dealer card will be displayed here -->
156
+ </div>
157
+ </div>
158
+ </div>
159
+
160
+ <!-- Strategy Advice -->
161
+ <div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8">
162
+ <div class="p-6">
163
+ <h2 class="text-xl font-semibold text-gray-800 mb-4">
164
+ <i class="fas fa-chess-knight text-purple-500 mr-2"></i>Optimal Strategy Advice
165
+ </h2>
166
+
167
+ <div id="strategyResult" class="text-center py-4">
168
+ <div class="text-lg font-bold text-blue-600 mb-2">Recommended Action: --</div>
169
+ <div class="text-sm text-gray-600">
170
+ H: Hit, S: Stand, D: Double (if allowed), P: Split (if allowed)<br>
171
+ Note: Strategy for 4-8 decks. D/P generally on first 2 cards. DAS considered.
172
+ </div>
173
+ </div>
174
+ </div>
175
+ </div>
176
+
177
+ <!-- Calculation Buttons -->
178
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
179
+ <button id="calcPlayerBtn" class="w-full bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200">
180
+ <i class="fas fa-user-cog mr-2"></i>Calculate Player Probabilities
181
+ </button>
182
+ <button id="calcDealerBtn" class="w-full bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200">
183
+ <i class="fas fa-user-tie mr-2"></i>Calculate Dealer Probabilities
184
+ </button>
185
+ </div>
186
+
187
+ <!-- Results Section -->
188
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
189
+ <!-- Player Results -->
190
+ <div class="bg-white rounded-xl shadow-lg overflow-hidden">
191
+ <div class="p-6">
192
+ <h2 class="text-xl font-semibold text-gray-800 mb-4">
193
+ <i class="fas fa-chart-line text-green-500 mr-2"></i>Player Next Card Probabilities
194
+ </h2>
195
+
196
+ <div id="playerResults" class="space-y-4">
197
+ <div>
198
+ <div class="text-gray-700">Current Hand Value:</div>
199
+ <div id="playerHandValue" class="text-2xl font-bold">--</div>
200
+ </div>
201
+
202
+ <div>
203
+ <div class="flex justify-between text-sm text-gray-600 mb-1">
204
+ <span>Probability of Safe Card (≤21)</span>
205
+ <span id="safeProbText">--%</span>
206
+ </div>
207
+ <div class="progress-bar">
208
+ <div id="safeProbBar" class="progress-fill bg-green-500" style="width: 0%"></div>
209
+ </div>
210
+ </div>
211
+
212
+ <div>
213
+ <div class="flex justify-between text-sm text-gray-600 mb-1">
214
+ <span>Probability of Bust Card (>21)</span>
215
+ <span id="bustProbText">--%</span>
216
+ </div>
217
+ <div class="progress-bar">
218
+ <div id="bustProbBar" class="progress-fill bg-red-500" style="width: 0%"></div>
219
+ </div>
220
+ </div>
221
+
222
+ <div id="playerStatus" class="text-sm italic text-gray-500"></div>
223
+ </div>
224
+ </div>
225
+ </div>
226
+
227
+ <!-- Dealer Results -->
228
+ <div class="bg-white rounded-xl shadow-lg overflow-hidden">
229
+ <div class="p-6">
230
+ <h2 class="text-xl font-semibold text-gray-800 mb-4">
231
+ <i class="fas fa-chart-pie text-red-500 mr-2"></i>Dealer Outcome Probabilities
232
+ </h2>
233
+
234
+ <div id="dealerResults" class="space-y-2">
235
+ <div class="bg-gray-50 p-4 rounded-lg h-64 overflow-y-auto">
236
+ <div id="dealerResultsText" class="text-sm">
237
+ Dealer probabilities will appear here.
238
+ </div>
239
+ </div>
240
+ <div id="dealerStatus" class="text-sm italic text-gray-500"></div>
241
+ </div>
242
+ </div>
243
+ </div>
244
+ </div>
245
+
246
+ <!-- Legend Section -->
247
+ <div class="mt-8 bg-white rounded-xl shadow-lg overflow-hidden">
248
+ <div class="p-6">
249
+ <h2 class="text-xl font-semibold text-gray-800 mb-4">
250
+ <i class="fas fa-info-circle text-yellow-500 mr-2"></i>Blackjack Strategy Legend
251
+ </h2>
252
+
253
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
254
+ <div class="bg-blue-50 p-4 rounded-lg">
255
+ <div class="font-bold text-blue-800 mb-1">H: Hit</div>
256
+ <div class="text-sm text-gray-600">Take another card from the dealer</div>
257
+ </div>
258
+ <div class="bg-green-50 p-4 rounded-lg">
259
+ <div class="font-bold text-green-800 mb-1">S: Stand</div>
260
+ <div class="text-sm text-gray-600">Keep your current hand and end your turn</div>
261
+ </div>
262
+ <div class="bg-purple-50 p-4 rounded-lg">
263
+ <div class="font-bold text-purple-800 mb-1">D: Double</div>
264
+ <div class="text-sm text-gray-600">Double your bet and take exactly one more card</div>
265
+ </div>
266
+ <div class="bg-yellow-50 p-4 rounded-lg">
267
+ <div class="font-bold text-yellow-800 mb-1">P: Split</div>
268
+ <div class="text-sm text-gray-600">Split your pair into two separate hands (when allowed)</div>
269
+ </div>
270
+ </div>
271
+ </div>
272
+ </div>
273
+ </div>
274
+
275
+ <script>
276
+ // Card values and deck logic
277
+ const CARD_VALUES = {
278
+ 'A': 11, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8,
279
+ '9': 9, '10': 10, 'J': 10, 'Q': 10, 'K': 10
280
+ };
281
+ const VALID_CARD_INPUTS = Object.keys(CARD_VALUES);
282
+ const DEALER_CARD_MAP = Object.fromEntries(
283
+ VALID_CARD_INPUTS.map(k => [k, ['J', 'Q', 'K'].includes(k) ? '10' : k])
284
+ );
285
+
286
+ // Strategy tables (H17 and S17)
287
+ const STRATEGY_TABLES = { /* Copied from previous HTML, no change here */
288
+ 'H17': {
289
+ 'hard': { 9: {'2':'H', '3':'D', '4':'D', '5':'D', '6':'D', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 10: {'2':'D', '3':'D', '4':'D', '5':'D', '6':'D', '7':'D', '8':'D', '9':'D', '10':'H', 'A':'H'}, 11: {'2':'D', '3':'D', '4':'D', '5':'D', '6':'D', '7':'D', '8':'D', '9':'D', '10':'D', 'A':'H'}, 12: {'2':'H', '3':'H', '4':'S', '5':'S', '6':'S', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 13: {'2':'S', '3':'S', '4':'S', '5':'S', '6':'S', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 14: {'2':'S', '3':'S', '4':'S', '5':'S', '6':'S', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 15: {'2':'S', '3':'S', '4':'S', '5':'S', '6':'S', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 16: {'2':'S', '3':'S', '4':'S', '5':'S', '6':'S', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}},
290
+ 'soft': { 13: {'2':'H', '3':'H', '4':'H', '5':'D', '6':'D', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 14: {'2':'H', '3':'H', '4':'H', '5':'D', '6':'D', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 15: {'2':'H', '3':'H', '4':'D', '5':'D', '6':'D', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 16: {'2':'H', '3':'H', '4':'D', '5':'D', '6':'D', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 17: {'2':'H', '3':'D', '4':'D', '5':'D', '6':'D', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 18: {'2':'S', '3':'D', '4':'D', '5':'D', '6':'D', '7':'S', '8':'S', '9':'H', '10':'H', 'A':'H'}, 19: {'2':'S', '3':'S', '4':'S', '5':'S', '6':'S', '7':'S', '8':'S', '9':'S', '10':'S', 'A':'S'}},
291
+ 'pairs': {'A': {'2':'P', '3':'P', '4':'P', '5':'P', '6':'P', '7':'P', '8':'P', '9':'P', '10':'P', 'A':'P'}, '10': {'2':'S', '3':'S', '4':'S', '5':'S', '6':'S', '7':'S', '8':'S', '9':'S', '10':'S', 'A':'S'}, '9': {'2':'P', '3':'P', '4':'P', '5':'P', '6':'P', '7':'S', '8':'P', '9':'P', '10':'S', 'A':'S'}, '8': {'2':'P', '3':'P', '4':'P', '5':'P', '6':'P', '7':'P', '8':'P', '9':'P', '10':'P', 'A':'P'}, '7': {'2':'P', '3':'P', '4':'P', '5':'P', '6':'P', '7':'P', '8':'H', '9':'H', '10':'H', 'A':'H'}, '6': {'2':'P', '3':'P', '4':'P', '5':'P', '6':'P', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, '5': {}, '4': {'2':'H', '3':'H', '4':'H', '5':'P', '6':'P', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, '3': {'2':'P', '3':'P', '4':'P', '5':'P', '6':'P', '7':'P', '8':'H', '9':'H', '10':'H', 'A':'H'}, '2': {'2':'P', '3':'P', '4':'P', '5':'P', '6':'P', '7':'P', '8':'H', '9':'H', '10':'H', 'A':'H'}}
292
+ },
293
+ 'S17': {
294
+ 'hard': { 9: {'2':'H', '3':'D', '4':'D', '5':'D', '6':'D', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 10: {'2':'D', '3':'D', '4':'D', '5':'D', '6':'D', '7':'D', '8':'D', '9':'D', '10':'H', 'A':'H'}, 11: {'2':'D', '3':'D', '4':'D', '5':'D', '6':'D', '7':'D', '8':'D', '9':'D', '10':'D', 'A':'D'}, 12: {'2':'H', '3':'H', '4':'S', '5':'S', '6':'S', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 13: {'2':'S', '3':'S', '4':'S', '5':'S', '6':'S', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 14: {'2':'S', '3':'S', '4':'S', '5':'S', '6':'S', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 15: {'2':'S', '3':'S', '4':'S', '5':'S', '6':'S', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 16: {'2':'S', '3':'S', '4':'S', '5':'S', '6':'S', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}},
295
+ 'soft': { 13: {'2':'H', '3':'H', '4':'H', '5':'D', '6':'D', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 14: {'2':'H', '3':'H', '4':'H', '5':'D', '6':'D', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 15: {'2':'H', '3':'H', '4':'D', '5':'D', '6':'D', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 16: {'2':'H', '3':'H', '4':'D', '5':'D', '6':'D', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 17: {'2':'H', '3':'D', '4':'D', '5':'D', '6':'D', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, 18: {'2':'S', '3':'D', '4':'D', '5':'D', '6':'D', '7':'S', '8':'S', '9':'S', '10':'S', 'A':'S'}, 19: {'2':'S', '3':'S', '4':'S', '5':'S', '6':'D', '7':'S', '8':'S', '9':'S', '10':'S', 'A':'S'}},
296
+ 'pairs': {'A': {'2':'P', '3':'P', '4':'P', '5':'P', '6':'P', '7':'P', '8':'P', '9':'P', '10':'P', 'A':'P'}, '10': {'2':'S', '3':'S', '4':'S', '5':'S', '6':'S', '7':'S', '8':'S', '9':'S', '10':'S', 'A':'S'}, '9': {'2':'P', '3':'P', '4':'P', '5':'P', '6':'P', '7':'S', '8':'P', '9':'P', '10':'S', 'A':'S'}, '8': {'2':'P', '3':'P', '4':'P', '5':'P', '6':'P', '7':'P', '8':'P', '9':'P', '10':'P', 'A':'P'}, '7': {'2':'P', '3':'P', '4':'P', '5':'P', '6':'P', '7':'P', '8':'H', '9':'H', '10':'H', 'A':'H'}, '6': {'2':'P', '3':'P', '4':'P', '5':'P', '6':'P', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, '5': {}, '4': {'2':'H', '3':'H', '4':'H', '5':'P', '6':'P', '7':'H', '8':'H', '9':'H', '10':'H', 'A':'H'}, '3': {'2':'P', '3':'P', '4':'P', '5':'P', '6':'P', '7':'P', '8':'H', '9':'H', '10':'H', 'A':'H'}, '2': {'2':'P', '3':'P', '4':'P', '5':'P', '6':'P', '7':'P', '8':'H', '9':'H', '10':'H', 'A':'H'}}
297
+ }
298
+ };
299
+
300
+ // Complete strategy tables (auto-fill common cases)
301
+ const uniqueDealerColumnKeys = [...new Set(Object.values(DEALER_CARD_MAP))].sort();
302
+ for (const ruleSet of Object.values(STRATEGY_TABLES)) {
303
+ for (let i = 5; i < 9; i++) { // Hard 5-8
304
+ if (!ruleSet.hard[i]) ruleSet.hard[i] = {};
305
+ uniqueDealerColumnKeys.forEach(k => { if (!ruleSet.hard[i][k]) ruleSet.hard[i][k] = 'H'; });
306
+ }
307
+ for (let i = 17; i < 22; i++) { // Hard 17-21
308
+ if (!ruleSet.hard[i]) ruleSet.hard[i] = {};
309
+ uniqueDealerColumnKeys.forEach(k => { if (!ruleSet.hard[i][k]) ruleSet.hard[i][k] = 'S'; });
310
+ }
311
+ [20, 21].forEach(i => { // Soft 20, 21
312
+ if (!ruleSet.soft[i]) ruleSet.soft[i] = {};
313
+ uniqueDealerColumnKeys.forEach(k => { if (!ruleSet.soft[i][k]) ruleSet.soft[i][k] = 'S'; });
314
+ });
315
+ }
316
+
317
+
318
+ // Initialize the UI
319
+ document.addEventListener('DOMContentLoaded', function() {
320
+ updateCardDisplays();
321
+ updateStrategyAdvice(); // Initial strategy update
322
+
323
+ document.getElementById('playerCards').addEventListener('input', function() {
324
+ updateCardDisplays();
325
+ updateStrategyAdvice();
326
+ });
327
+ document.getElementById('dealerCard').addEventListener('input', function() {
328
+ updateCardDisplays();
329
+ updateStrategyAdvice();
330
+ });
331
+ document.getElementById('numDecks').addEventListener('change', updateStrategyAdvice);
332
+ document.getElementById('h17Rule').addEventListener('change', updateStrategyAdvice);
333
+ document.getElementById('dasRule').addEventListener('change', updateStrategyAdvice); // DAS rule listener
334
+
335
+ document.getElementById('calcPlayerBtn').addEventListener('click', calculatePlayerProbabilities);
336
+ document.getElementById('calcDealerBtn').addEventListener('click', calculateDealerProbabilities);
337
+ });
338
+
339
+ // Helper functions
340
+ function parsePlayerCards() {
341
+ const cardsStr = document.getElementById('playerCards').value.trim();
342
+ if (!cardsStr) return { cards: [], error: "Player cards field is empty." };
343
+
344
+ const playerCardsList = cardsStr.split(',').map(c => c.trim().toUpperCase()).filter(c => c);
345
+
346
+ if (!playerCardsList.length) {
347
+ return { cards: [], error: "Player cards field is empty or invalid." };
348
+ }
349
+
350
+ for (const card of playerCardsList) {
351
+ if (!VALID_CARD_INPUTS.includes(card)) {
352
+ return { cards: null, error: `Invalid player card: '${card}'. Use A,K,Q,J,10-2.` };
353
+ }
354
+ }
355
+ return { cards: playerCardsList, error: null };
356
+ }
357
+
358
+ function calculateHandValueDetailed(handCardsStrings) {
359
+ if (!handCardsStrings || !handCardsStrings.length) {
360
+ return { value: 0, isSoft: false, isBlackjack: false };
361
+ }
362
+ let value = 0;
363
+ let numAcesInHand = 0;
364
+ for (const cardStr of handCardsStrings) {
365
+ const cardVal = CARD_VALUES[cardStr];
366
+ if (cardStr === 'A') numAcesInHand++;
367
+ value += cardVal;
368
+ }
369
+ let acesCountedAs11 = numAcesInHand;
370
+ while (value > 21 && acesCountedAs11 > 0) {
371
+ value -= 10;
372
+ acesCountedAs11--;
373
+ }
374
+ const isBlackjack = (value === 21 && handCardsStrings.length === 2);
375
+ const isSoft = (value <= 21 && acesCountedAs11 > 0);
376
+ return { value, isSoft, isBlackjack };
377
+ }
378
+
379
+ function createDeck(numDecks = 1) {
380
+ const deck = [];
381
+ for (let i = 0; i < numDecks; i++) {
382
+ for (const cardFace of Object.keys(CARD_VALUES)) {
383
+ deck.push(...Array(4).fill(cardFace));
384
+ }
385
+ }
386
+ return deck;
387
+ }
388
+
389
+ function getEffectiveRemainingDeck(playerHandStrings, dealerUpCardString, numDecks) {
390
+ const fullDeck = createDeck(numDecks);
391
+ const allKnownCards = [];
392
+ if (playerHandStrings) allKnownCards.push(...playerHandStrings.map(c => c.toUpperCase()));
393
+ if (dealerUpCardString) allKnownCards.push(dealerUpCardString.toUpperCase());
394
+
395
+ for (const cardStr of allKnownCards) {
396
+ if (!VALID_CARD_INPUTS.includes(cardStr)) {
397
+ return { deck: null, error: `Invalid card '${cardStr}' in known cards.` };
398
+ }
399
+ }
400
+ const knownCardCounts = allKnownCards.reduce((acc, card) => { acc[card] = (acc[card] || 0) + 1; return acc; }, {});
401
+ const fullDeckCounts = fullDeck.reduce((acc, card) => { acc[card] = (acc[card] || 0) + 1; return acc; }, {});
402
+
403
+ for (const [card, count] of Object.entries(knownCardCounts)) {
404
+ if (count > (fullDeckCounts[card] || 0)) {
405
+ return { deck: null, error: `Too many '${card}' cards specified (${count}) than available in ${numDecks} deck(s) (${fullDeckCounts[card] || 0}).` };
406
+ }
407
+ }
408
+ const remainingDeck = [...fullDeck];
409
+ for (const cardToRemove of allKnownCards) {
410
+ const index = remainingDeck.indexOf(cardToRemove);
411
+ if (index === -1) return { deck: null, error: `Error removing '${cardToRemove}' from deck. Card count mismatch.` };
412
+ remainingDeck.splice(index, 1);
413
+ }
414
+ return { deck: remainingDeck, error: null };
415
+ }
416
+
417
+ function getOptimalAction(playerHandStrings, dealerUpCardStr, isH17Rule, dasAllowed) { // Added dasAllowed
418
+ if (!playerHandStrings || playerHandStrings.length < 2) {
419
+ return { action: null, message: "Player hand must have at least 2 cards for strategy." };
420
+ }
421
+ if (!dealerUpCardStr) {
422
+ return { action: null, message: "Dealer up-card is required for strategy." };
423
+ }
424
+ dealerUpCardStr = dealerUpCardStr.toUpperCase();
425
+ if (!VALID_CARD_INPUTS.includes(dealerUpCardStr)) {
426
+ return { action: null, message: `Invalid dealer up-card: ${dealerUpCardStr}` };
427
+ }
428
+
429
+ const mappedDealerCard = DEALER_CARD_MAP[dealerUpCardStr] || dealerUpCardStr;
430
+ const { value: playerValue, isSoft } = calculateHandValueDetailed(playerHandStrings);
431
+
432
+ if (playerValue > 21) return { action: 'S', message: "Player busts, stands (no action)." };
433
+ if (playerValue === 21) return { action: 'S', message: "Player has 21, stands." };
434
+
435
+ const ruleType = isH17Rule ? 'H17' : 'S17';
436
+ const strategy = STRATEGY_TABLES[ruleType];
437
+ let action = null;
438
+ const isTwoCardHand = playerHandStrings.length === 2;
439
+
440
+ if (isTwoCardHand && playerHandStrings[0] === playerHandStrings[1]) {
441
+ const pairCardValStr = playerHandStrings[0];
442
+ const pairLookupKey = DEALER_CARD_MAP[pairCardValStr] || pairCardValStr;
443
+
444
+ if (pairLookupKey === '5') { // 5,5 is Hard 10
445
+ action = strategy.hard[10]?.[mappedDealerCard];
446
+ } else if (strategy.pairs[pairLookupKey] && strategy.pairs[pairLookupKey][mappedDealerCard]) {
447
+ action = strategy.pairs[pairLookupKey][mappedDealerCard];
448
+
449
+ // DAS-dependent strategy adjustment: 4,4 vs 5 or 6
450
+ if (pairLookupKey === '4' && (mappedDealerCard === '5' || mappedDealerCard === '6') && action === 'P' && !dasAllowed) {
451
+ action = 'H'; // Hit if can't split 4,4 vs 5,6 with DAS
452
+ }
453
+ // Add other DAS-dependent rules here if needed
454
+ }
455
+ }
456
+
457
+ if (action === null && isSoft) {
458
+ if (strategy.soft[playerValue] && strategy.soft[playerValue][mappedDealerCard]) {
459
+ action = strategy.soft[playerValue][mappedDealerCard];
460
+ }
461
+ }
462
+
463
+ if (action === null) { // Hard total or fallback
464
+ if (playerValue < 9) action = 'H';
465
+ else if (playerValue >= 17) action = 'S';
466
+ else if (strategy.hard[playerValue] && strategy.hard[playerValue][mappedDealerCard]) {
467
+ action = strategy.hard[playerValue][mappedDealerCard];
468
+ }
469
+ }
470
+ if (action === null) { // Ultimate fallback if table somehow missed a case
471
+ action = playerValue < 17 ? 'H' : 'S';
472
+ }
473
+
474
+ // Adjust for >2 cards: D becomes H, P becomes H/S based on value after hit
475
+ if (!isTwoCardHand) {
476
+ if (action === 'D') {
477
+ // More nuanced: Some Doubles (e.g. Soft 18 vs dealer 3-6) become Stand
478
+ if (isSoft && playerValue === 18 && ['3','4','5','6'].includes(mappedDealerCard)) {
479
+ action = 'S';
480
+ } else if (isSoft && playerValue === 19 && ruleType === 'S17' && mappedDealerCard === '6') {
481
+ action = 'S';
482
+ }
483
+ else {
484
+ action = 'H';
485
+ }
486
+ } else if (action === 'P') {
487
+ // If split was recommended, but >2 cards, play current hand as non-pair hard/soft total.
488
+ if (isSoft) {
489
+ const softAction = strategy.soft[playerValue]?.[mappedDealerCard] || 'H';
490
+ action = softAction === 'D' ? 'H' : softAction;
491
+ } else {
492
+ if (playerValue < 9) action = 'H';
493
+ else if (playerValue >= 17) action = 'S';
494
+ else {
495
+ const hardAction = strategy.hard[playerValue]?.[mappedDealerCard] || 'H';
496
+ action = hardAction === 'D' ? 'H' : hardAction;
497
+ }
498
+ }
499
+ }
500
+ }
501
+
502
+ const actionMap = { 'H': "Hit", 'S': "Stand", 'D': "Double", 'P': "Split" };
503
+ return {
504
+ action: actionMap[action] || action,
505
+ message: `Optimal action: ${actionMap[action] || 'Unknown'}`
506
+ };
507
+ }
508
+
509
+ function calculatePlayerNextCardProbabilities(playerHandStrings, dealerUpCardString, numDecks) {
510
+ const { value: currentPlayerValue } = calculateHandValueDetailed(playerHandStrings);
511
+ if (currentPlayerValue > 21) {
512
+ return { currentValue: currentPlayerValue, probSafe: 0, probBust: 100, error: null };
513
+ }
514
+ const { deck: remainingDeck, error: deckError } = getEffectiveRemainingDeck(playerHandStrings, dealerUpCardString, numDecks);
515
+ if (deckError) {
516
+ return { currentValue: currentPlayerValue, probSafe: 0, probBust: 0, error: deckError };
517
+ }
518
+ if (!remainingDeck || !remainingDeck.length) {
519
+ return { currentValue: currentPlayerValue, probSafe: currentPlayerValue <= 21 ? 100 : 0, probBust: currentPlayerValue <= 21 ? 0 : 100, error: "No cards left in deck for player to draw." };
520
+ }
521
+ let safeOutcomes = 0;
522
+ let bustOutcomes = 0;
523
+ for (const nextCardCandidate of remainingDeck) {
524
+ const tempHand = [...playerHandStrings, nextCardCandidate];
525
+ const { value: newValue } = calculateHandValueDetailed(tempHand);
526
+ if (newValue <= 21) safeOutcomes++;
527
+ else bustOutcomes++;
528
+ }
529
+ const totalRemainingCards = remainingDeck.length;
530
+ const probSafePercent = totalRemainingCards > 0 ? (safeOutcomes / totalRemainingCards) * 100 : 0;
531
+ const probBustPercent = totalRemainingCards > 0 ? (bustOutcomes / totalRemainingCards) * 100 : 0;
532
+ return { currentValue: currentPlayerValue, probSafe: probSafePercent, probBust: probBustPercent, error: null };
533
+ }
534
+
535
+ const memoDealerDist = {};
536
+ function getDealerFinalValueDistribution(currentDealerHandStrings, currentDeckState, hitSoft17) {
537
+ const handTuple = [...currentDealerHandStrings].sort().join(',');
538
+ const deckCounts = currentDeckState.reduce((acc, card) => { acc[card] = (acc[card] || 0) + 1; return acc; }, {});
539
+ const deckCountsTuple = Object.entries(deckCounts).sort().map(([k, v]) => `${k}:${v}`).join('|');
540
+ const memoKey = `${handTuple}|${deckCountsTuple}|${hitSoft17}`;
541
+ if (memoDealerDist[memoKey]) return memoDealerDist[memoKey];
542
+
543
+ const { value, isSoft, isBlackjack } = calculateHandValueDetailed(currentDealerHandStrings);
544
+ if (isBlackjack) { const result = { 'BJ': 1.0 }; memoDealerDist[memoKey] = result; return result; }
545
+ if (value > 21) { const result = { 'Bust': 1.0 }; memoDealerDist[memoKey] = result; return result; }
546
+ if (value >= 17) {
547
+ if (value === 17 && isSoft && hitSoft17) { /* Continue */ }
548
+ else { const result = { [value]: 1.0 }; memoDealerDist[memoKey] = result; return result; }
549
+ }
550
+ if (!currentDeckState.length) { const result = value <= 21 ? { [value]: 1.0 } : { 'Bust': 1.0 }; memoDealerDist[memoKey] = result; return result; }
551
+
552
+ const finalDistribution = {};
553
+ const totalCardsInDeck = currentDeckState.length;
554
+ const uniqueCardsInDeckCounts = currentDeckState.reduce((acc, card) => { acc[card] = (acc[card] || 0) + 1; return acc; }, {});
555
+
556
+ for (const [cardDrawn, count] of Object.entries(uniqueCardsInDeckCounts)) {
557
+ const probDrawingThisCard = count / totalCardsInDeck;
558
+ const newHandStrings = [...currentDealerHandStrings, cardDrawn];
559
+ const newDeckState = [...currentDeckState]; // Create a copy
560
+ const indexToRemove = newDeckState.indexOf(cardDrawn);
561
+ if (indexToRemove > -1) newDeckState.splice(indexToRemove, 1); // Remove one instance
562
+
563
+ const recursiveDistribution = getDealerFinalValueDistribution(newHandStrings, newDeckState, hitSoft17);
564
+ for (const [outcome, prob] of Object.entries(recursiveDistribution)) {
565
+ finalDistribution[outcome] = (finalDistribution[outcome] || 0) + probDrawingThisCard * prob;
566
+ }
567
+ }
568
+ memoDealerDist[memoKey] = finalDistribution;
569
+ return finalDistribution;
570
+ }
571
+
572
+ function calculateDealerOutcomeProbabilities(dealerUpCardString, playerHandStrings, numDecks, hitSoft17) {
573
+ Object.keys(memoDealerDist).forEach(key => delete memoDealerDist[key]);
574
+ if (!dealerUpCardString) return { probabilities: null, error: "Dealer up-card must be provided." };
575
+ dealerUpCardString = dealerUpCardString.toUpperCase();
576
+ if (!VALID_CARD_INPUTS.includes(dealerUpCardString)) return { probabilities: null, error: `Invalid dealer up-card: ${dealerUpCardString}` };
577
+
578
+ const { deck: deckForHoleCard, error: deckError } = getEffectiveRemainingDeck(playerHandStrings, dealerUpCardString, numDecks);
579
+ if (deckError) return { probabilities: null, error: deckError };
580
+ if (!deckForHoleCard || !deckForHoleCard.length) return { probabilities: null, error: "No cards left in deck for dealer's hole card." };
581
+
582
+ const overallDealerOutcomeDistribution = {};
583
+ const totalPossibleHoleCards = deckForHoleCard.length;
584
+ const holeCardCounts = deckForHoleCard.reduce((acc, card) => { acc[card] = (acc[card] || 0) + 1; return acc; }, {});
585
+
586
+ for (const [holeCardCandidate, count] of Object.entries(holeCardCounts)) {
587
+ const probThisHoleCardIsDrawn = count / totalPossibleHoleCards;
588
+ const dealerInitialHand = [dealerUpCardString, holeCardCandidate];
589
+
590
+ const deckForDealerPlay = [...deckForHoleCard]; // Create a copy
591
+ const indexToRemove = deckForDealerPlay.indexOf(holeCardCandidate);
592
+ if (indexToRemove > -1) deckForDealerPlay.splice(indexToRemove, 1); // Remove one instance
593
+
594
+ const distributionForThisHoleCard = getDealerFinalValueDistribution(dealerInitialHand, deckForDealerPlay, hitSoft17);
595
+ for (const [outcome, prob] of Object.entries(distributionForThisHoleCard)) {
596
+ overallDealerOutcomeDistribution[outcome] = (overallDealerOutcomeDistribution[outcome] || 0) + probThisHoleCardIsDrawn * prob;
597
+ }
598
+ }
599
+ const finalProbabilitiesPercent = Object.fromEntries(Object.entries(overallDealerOutcomeDistribution).map(([k, v]) => [k, v * 100]));
600
+ return { probabilities: finalProbabilitiesPercent, error: null };
601
+ }
602
+
603
+ // UI Update Functions
604
+ function updateCardDisplays() {
605
+ const { cards: playerCards } = parsePlayerCards();
606
+ const dealerCard = document.getElementById('dealerCard').value.trim().toUpperCase();
607
+ const playerCardsDisplay = document.getElementById('playerCardsDisplay');
608
+ playerCardsDisplay.innerHTML = '';
609
+ if (playerCards && playerCards.length) {
610
+ playerCards.forEach(card => {
611
+ // Simplified red/black for display, not true suit tracking.
612
+ const isRedSuitChar = ['H', 'D'].includes(card.slice(-1)) && card.length > 1; // Avoids 'H' from 'HIT'
613
+ const cardEl = document.createElement('div');
614
+ cardEl.className = `card ${isRedSuitChar ? 'card-red' : 'card-black'}`;
615
+ cardEl.textContent = card;
616
+ playerCardsDisplay.appendChild(cardEl);
617
+ });
618
+ }
619
+ const dealerCardDisplay = document.getElementById('dealerCardDisplay');
620
+ dealerCardDisplay.innerHTML = '';
621
+ if (dealerCard && VALID_CARD_INPUTS.includes(dealerCard)) {
622
+ const isRedSuitChar = ['H', 'D'].includes(dealerCard.slice(-1)) && dealerCard.length > 1;
623
+ const cardEl = document.createElement('div');
624
+ cardEl.className = `card ${isRedSuitChar ? 'card-red' : 'card-black'}`;
625
+ cardEl.textContent = dealerCard;
626
+ dealerCardDisplay.appendChild(cardEl);
627
+ const faceDownCard = document.createElement('div');
628
+ faceDownCard.className = 'card bg-gray-800 text-white';
629
+ faceDownCard.innerHTML = '<i class="fas fa-question"></i>';
630
+ dealerCardDisplay.appendChild(faceDownCard);
631
+ }
632
+ }
633
+
634
+ function updateStrategyAdvice() {
635
+ const { cards: playerCards, error: playerError } = parsePlayerCards();
636
+ const dealerCard = document.getElementById('dealerCard').value.trim().toUpperCase();
637
+ const isH17 = document.getElementById('h17Rule').checked;
638
+ const dasAllowed = document.getElementById('dasRule').checked; // Get DAS rule
639
+ const strategyResultDiv = document.getElementById('strategyResult');
640
+
641
+ if (playerError && playerCards === null) {
642
+ strategyResultDiv.innerHTML = `<div class="text-red-500">${playerError}</div>`; return;
643
+ }
644
+ if (!playerCards || !playerCards.length || !dealerCard) {
645
+ strategyResultDiv.innerHTML = '<div class="text-lg font-bold text-blue-600 mb-2">Recommended Action: --</div><div class="text-sm text-gray-500">Need player & dealer cards.</div>'; return;
646
+ }
647
+ if (!VALID_CARD_INPUTS.includes(dealerCard)) {
648
+ strategyResultDiv.innerHTML = `<div class="text-red-500">Invalid dealer card '${dealerCard}'</div>`; return;
649
+ }
650
+ if (playerCards.length < 2) {
651
+ strategyResultDiv.innerHTML = '<div class="text-lg font-bold text-blue-600 mb-2">Recommended Action: --</div><div class="text-sm text-gray-500">Need at least 2 player cards.</div>'; return;
652
+ }
653
+
654
+ const { action, message } = getOptimalAction(playerCards, dealerCard, isH17, dasAllowed); // Pass dasAllowed
655
+ if (action) {
656
+ let actionClass = 'text-blue-600'; // Default for Stand
657
+ if (action === 'Hit') actionClass = 'text-green-600';
658
+ else if (action === 'Double') actionClass = 'text-purple-600';
659
+ else if (action === 'Split') actionClass = 'text-yellow-600';
660
+
661
+ strategyResultDiv.innerHTML = `
662
+ <div class="text-lg font-bold ${actionClass} mb-2 strategy-action">Recommended Action: ${action}</div>
663
+ <div class="text-sm text-gray-600">${message}</div>
664
+ `;
665
+ } else {
666
+ strategyResultDiv.innerHTML = `<div class="text-gray-500">${message || 'Could not determine action.'}</div>`;
667
+ }
668
+ }
669
+
670
+ function calculatePlayerProbabilities() {
671
+ const playerStatus = document.getElementById('playerStatus');
672
+ playerStatus.textContent = '';
673
+ updateStrategyAdvice();
674
+ const { cards: playerCards, error } = parsePlayerCards();
675
+ const playerHandValueEl = document.getElementById('playerHandValue');
676
+ const safeProbTextEl = document.getElementById('safeProbText');
677
+ const bustProbTextEl = document.getElementById('bustProbText');
678
+ const safeProbBarEl = document.getElementById('safeProbBar');
679
+ const bustProbBarEl = document.getElementById('bustProbBar');
680
+
681
+ function resetPlayerProbs() {
682
+ playerHandValueEl.textContent = '--';
683
+ safeProbTextEl.textContent = '--%';
684
+ bustProbTextEl.textContent = '--%';
685
+ safeProbBarEl.style.width = '0%';
686
+ bustProbBarEl.style.width = '0%';
687
+ }
688
+
689
+ if (error && playerCards === null) {
690
+ playerStatus.textContent = error; resetPlayerProbs(); return;
691
+ }
692
+ if (!playerCards || !playerCards.length) {
693
+ playerStatus.textContent = "Player cards are empty for probability calculation."; resetPlayerProbs(); return;
694
+ }
695
+
696
+ const numDecks = parseInt(document.getElementById('numDecks').value);
697
+ const dealerUpCard = document.getElementById('dealerCard').value.trim().toUpperCase();
698
+ let effectiveDealerCard = null;
699
+ if (dealerUpCard && VALID_CARD_INPUTS.includes(dealerUpCard)) effectiveDealerCard = dealerUpCard;
700
+ else if (dealerUpCard) playerStatus.textContent = `Warning: Invalid dealer up-card '${dealerUpCard}'. Ignored for card removal in player prob calc.`;
701
+
702
+ const { currentValue, probSafe, probBust, error: calcError } = calculatePlayerNextCardProbabilities(playerCards, effectiveDealerCard, numDecks);
703
+ if (calcError) {
704
+ const currentStatus = playerStatus.textContent;
705
+ playerStatus.textContent = `${currentStatus} Error: ${calcError}`.trim(); resetPlayerProbs();
706
+ } else {
707
+ playerHandValueEl.textContent = currentValue;
708
+ safeProbTextEl.textContent = `${probSafe.toFixed(2)}%`;
709
+ bustProbTextEl.textContent = `${probBust.toFixed(2)}%`;
710
+ safeProbBarEl.style.width = `${probSafe}%`;
711
+ bustProbBarEl.style.width = `${probBust}%`;
712
+ const currentStatus = playerStatus.textContent;
713
+ const calcMessage = currentValue > 21 ? "Player is Busted." : "Player probabilities calculated.";
714
+ playerStatus.textContent = currentStatus.includes("Warning") ? `${currentStatus} ${calcMessage}` : calcMessage;
715
+ }
716
+ }
717
+
718
+ function calculateDealerProbabilities() {
719
+ const dealerStatus = document.getElementById('dealerStatus');
720
+ dealerStatus.textContent = '';
721
+ updateStrategyAdvice();
722
+ const { cards: playerCards, error: playerError } = parsePlayerCards();
723
+ if (playerError && playerCards === null) { dealerStatus.textContent = `Player card error: ${playerError}`; return; }
724
+
725
+ const numDecks = parseInt(document.getElementById('numDecks').value);
726
+ const dealerUpCard = document.getElementById('dealerCard').value.trim().toUpperCase();
727
+ const hitSoft17 = document.getElementById('h17Rule').checked;
728
+
729
+ if (!dealerUpCard) { dealerStatus.textContent = "Dealer up-card is required for dealer calculation."; return; }
730
+ if (!VALID_CARD_INPUTS.includes(dealerUpCard)) { dealerStatus.textContent = `Invalid dealer up-card: '${dealerUpCard}'.`; return; }
731
+
732
+ dealerStatus.textContent = "Calculating dealer probabilities... Please wait.";
733
+
734
+ setTimeout(() => {
735
+ const { probabilities, error } = calculateDealerOutcomeProbabilities(dealerUpCard, playerCards || [], numDecks, hitSoft17);
736
+ const dealerResultsTextEl = document.getElementById('dealerResultsText');
737
+ dealerResultsTextEl.innerHTML = '';
738
+
739
+ if (error) {
740
+ dealerStatus.textContent = error;
741
+ dealerResultsTextEl.innerHTML = "Error during calculation.";
742
+ } else if (probabilities) {
743
+ dealerStatus.textContent = "Dealer probabilities calculated.";
744
+ // Expanded display order
745
+ const displayOrder = ['BJ', 21, 20, 19, 18, 17, 16, 15, 'Bust'];
746
+ let outputHTML = "<div class='space-y-1'><div class='font-bold mb-2'>Dealer Final Hand Probabilities:</div>";
747
+ const processedOutcomes = new Set();
748
+
749
+ for (const outcomeKey of displayOrder) {
750
+ if (probabilities[outcomeKey] !== undefined) {
751
+ const displayKeyStr = outcomeKey === 'BJ' ? "Blackjack" : outcomeKey.toString();
752
+ const probVal = probabilities[outcomeKey];
753
+ outputHTML += `<div class="flex justify-between"><span>P(Dealer gets ${displayKeyStr}):</span><span class="font-bold">${probVal.toFixed(2)}%</span></div>`;
754
+ processedOutcomes.add(outcomeKey);
755
+ }
756
+ }
757
+
758
+ const remainingItems = [];
759
+ for (const key in probabilities) {
760
+ if (!processedOutcomes.has(key) && !processedOutcomes.has(parseInt(key))) { // Check both string and int versions for safety
761
+ remainingItems.push([key, probabilities[key]]);
762
+ }
763
+ }
764
+ // Sort remaining: Numbers desc (except Bust/BJ), then BJ, then Bust
765
+ remainingItems.sort((a, b) => {
766
+ const keyA = a[0]; const keyB = b[0];
767
+ const isNumA = !isNaN(parseFloat(keyA)); const isNumB = !isNaN(parseFloat(keyB));
768
+ if (keyA === 'BJ') return -1; if (keyB === 'BJ') return 1;
769
+ if (keyA === 'Bust') return 1; if (keyB === 'Bust') return -1;
770
+ if (isNumA && isNumB) return parseFloat(keyB) - parseFloat(keyA); // Numbers descending
771
+ if (isNumA) return -1; // Numbers before other strings
772
+ if (isNumB) return 1;
773
+ return keyA.localeCompare(keyB); // Other strings alphabetically
774
+ });
775
+
776
+ for (const [outcomeKey, probVal] of remainingItems) {
777
+ const displayKeyStr = outcomeKey === 'BJ' ? "Blackjack" : outcomeKey.toString();
778
+ outputHTML += `<div class="flex justify-between"><span>P(Dealer gets ${displayKeyStr}):</span><span class="font-bold">${probVal.toFixed(2)}%</span></div>`;
779
+ }
780
+
781
+ const totalProbCheck = Object.values(probabilities).reduce((sum, val) => sum + val, 0);
782
+ outputHTML += `<div class="mt-2 pt-2 border-t border-gray-200 text-sm"><div class="flex justify-between"><span>Total Probability Sum:</span><span class="font-bold">${totalProbCheck.toFixed(2)}%</span></div><div class="text-xs text-gray-500">(should be close to 100%)</div></div>`;
783
+ outputHTML += "</div>";
784
+ dealerResultsTextEl.innerHTML = outputHTML;
785
+ } else {
786
+ dealerStatus.textContent = "No results from dealer calculation.";
787
+ dealerResultsTextEl.innerHTML = "No results.";
788
+ }
789
+ }, 50); // Reduced timeout slightly, 100ms is quite long for simple UI update.
790
+ }
791
+ </script>
792
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=LMLK/blackjack-probability-calculator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
793
+ </html>
prompts.txt ADDED
File without changes