Skip to content

Commit ba5a90e

Browse files
authored
Merge pull request #24 from tphummel/6rwm6p-codex/add-cli-entrypoint-for-monte-carlo-simulation
Improve Monte Carlo CLI output
2 parents cabe25c + 218ad71 commit ba5a90e

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,58 @@ const result = simulateHands(1)
9696
console.log(result.sessionSummary)
9797
```
9898

99+
## monte carlo simulation
100+
101+
Run many trials to see how a strategy performs over time.
102+
103+
```bash
104+
$ node monte-carlo.js 2 5 500 5 3-4-5
105+
Running 2 trials with 5 hand(s) each
106+
[table rules] minimum bet: $5, odds 3-4-5
107+
108+
Trial Results
109+
┌─────────┬───────┬─────────┬───────┐
110+
│ (index) │ trial │ balance │ rolls │
111+
├─────────┼───────┼─────────┼───────┤
112+
│ 0 │ 1 │ 614 │ 65 │
113+
│ 1 │ 2 │ 416 │ 25 │
114+
└─────────┴───────┴─────────┴───────┘
115+
116+
Final Balance Summary
117+
┌─────────┬────────┐
118+
│ (index) │ Values │
119+
├─────────┼────────┤
120+
│ min │ 416 │
121+
│ p1 │ 416 │
122+
│ p5 │ 416 │
123+
│ p10 │ 416 │
124+
│ p25 │ 416 │
125+
│ p50 │ 416 │
126+
│ p75 │ 614 │
127+
│ p90 │ 614 │
128+
│ p95 │ 614 │
129+
│ p99 │ 614 │
130+
│ max │ 614 │
131+
└─────────┴────────┘
132+
133+
Roll Count Summary
134+
┌─────────┬────────┐
135+
│ (index) │ Values │
136+
├─────────┼────────┤
137+
│ min │ 25 │
138+
│ p1 │ 25 │
139+
│ p5 │ 25 │
140+
│ p10 │ 25 │
141+
│ p25 │ 25 │
142+
│ p50 │ 25 │
143+
│ p75 │ 65 │
144+
│ p90 │ 65 │
145+
│ p95 │ 65 │
146+
│ p99 │ 65 │
147+
│ max │ 65 │
148+
└─────────┴────────┘
149+
```
150+
99151
## table rules
100152

101153
`playHand` accepts a `rules` object that controls minimum bets and odds limits.

monte-carlo.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/usr/bin/env node
2+
'use strict'
3+
4+
const { playHand } = require('./index.js')
5+
const { minPassLineMaxOddsPlaceSixEight } = require('./betting.js')
6+
7+
function parseOdds (str) {
8+
const parts = String(str).split('-').map(n => parseInt(n, 10))
9+
if (parts.length !== 3 || parts.some(n => isNaN(n))) return {}
10+
return {
11+
4: parts[0],
12+
5: parts[1],
13+
6: parts[2],
14+
8: parts[2],
15+
9: parts[1],
16+
10: parts[0]
17+
}
18+
}
19+
20+
function percentile (sorted, p) {
21+
if (sorted.length === 0) return 0
22+
const idx = Math.ceil((p / 100) * sorted.length) - 1
23+
return sorted[Math.min(Math.max(idx, 0), sorted.length - 1)]
24+
}
25+
26+
function summary (arr) {
27+
const sorted = [...arr].sort((a, b) => a - b)
28+
return {
29+
min: sorted[0],
30+
max: sorted[sorted.length - 1],
31+
p1: percentile(sorted, 1),
32+
p5: percentile(sorted, 5),
33+
p10: percentile(sorted, 10),
34+
p25: percentile(sorted, 25),
35+
p50: percentile(sorted, 50),
36+
p75: percentile(sorted, 75),
37+
p90: percentile(sorted, 90),
38+
p95: percentile(sorted, 95),
39+
p99: percentile(sorted, 99)
40+
}
41+
}
42+
43+
function summaryTable (arr) {
44+
const obj = summary(arr)
45+
const order = ['min', 'p1', 'p5', 'p10', 'p25', 'p50', 'p75', 'p90', 'p95', 'p99', 'max']
46+
return order.map(k => ({ stat: k, value: obj[k] }))
47+
}
48+
49+
function simulateTrial ({ handsPerTrial, startingBankroll, rules }) {
50+
let balance = startingBankroll
51+
let rolls = 0
52+
for (let i = 0; i < handsPerTrial; i++) {
53+
const { history, balance: result } = playHand({
54+
rules,
55+
bettingStrategy: minPassLineMaxOddsPlaceSixEight
56+
})
57+
balance += result
58+
rolls += history.length
59+
}
60+
return { balance, rolls }
61+
}
62+
63+
function monteCarlo ({ trials, handsPerTrial, startingBankroll, rules }) {
64+
const results = []
65+
for (let i = 0; i < trials; i++) {
66+
results.push(simulateTrial({ handsPerTrial, startingBankroll, rules }))
67+
}
68+
return results
69+
}
70+
71+
function printResults (results) {
72+
console.log('\nTrial Results')
73+
console.table(results.map((r, i) => ({ trial: i + 1, balance: r.balance, rolls: r.rolls })))
74+
75+
console.log('\nFinal Balance Summary')
76+
console.table(summaryTable(results.map(r => r.balance)))
77+
78+
console.log('\nRoll Count Summary')
79+
console.table(summaryTable(results.map(r => r.rolls)))
80+
}
81+
82+
if (require.main === module) {
83+
const trials = parseInt(process.argv[2], 10)
84+
const handsPerTrial = parseInt(process.argv[3], 10)
85+
const startingBankroll = parseInt(process.argv[4], 10)
86+
const minBet = parseInt(process.argv[5], 10)
87+
const oddsInput = process.argv[6] || '3-4-5'
88+
89+
const rules = {
90+
minBet,
91+
maxOddsMultiple: parseOdds(oddsInput)
92+
}
93+
94+
console.log(`Running ${trials} trials with ${handsPerTrial} hand(s) each`)
95+
console.log(`[table rules] minimum bet: $${minBet}, odds ${oddsInput}`)
96+
const results = monteCarlo({ trials, handsPerTrial, startingBankroll, rules })
97+
printResults(results)
98+
} else {
99+
module.exports = { monteCarlo, simulateTrial, summary }
100+
}

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,9 @@
2727
"devDependencies": {
2828
"standard": "^16.0.3",
2929
"tap": "^15.0.9"
30+
},
31+
"bin": {
32+
"craps-monte": "./monte-carlo.js",
33+
"craps-hands": "./hands.js"
3034
}
3135
}

0 commit comments

Comments
 (0)