|
| 1 | +#!/usr/bin/env node |
1 | 2 | 'use strict' |
2 | 3 |
|
3 | 4 | const { playHand } = require('./index.js') |
4 | 5 | const { minPassLineMaxOddsPlaceSixEight } = require('./betting.js') |
5 | 6 |
|
6 | | -const numHands = parseInt(process.argv.slice(2)[0], 10) |
7 | | -const showDetail = process.argv.slice(2)[1] |
8 | | - |
9 | | -console.log(`Simulating ${numHands} Craps Hand(s)`) |
10 | | -console.log('Using betting strategy: minPassLineMaxOddsPlaceSixEight') |
11 | | - |
12 | | -const summaryTemplate = { |
13 | | - balance: 0, |
14 | | - rollCount: 0, |
15 | | - pointsSet: 0, |
16 | | - pointsWon: 0, |
17 | | - comeOutWins: 0, |
18 | | - comeOutLosses: 0, |
19 | | - netComeOutWins: 0, |
20 | | - neutrals: 0, |
21 | | - placeSixWins: 0, |
22 | | - placeSixLosses: 0, |
23 | | - placeEightWins: 0, |
24 | | - placeEightLosses: 0, |
25 | | - dist: { |
26 | | - 2: { ct: 0, prob: 1 / 36 }, |
27 | | - 3: { ct: 0, prob: 2 / 36 }, |
28 | | - 4: { ct: 0, prob: 3 / 36 }, |
29 | | - 5: { ct: 0, prob: 4 / 36 }, |
30 | | - 6: { ct: 0, prob: 5 / 36 }, |
31 | | - 7: { ct: 0, prob: 6 / 36 }, |
32 | | - 8: { ct: 0, prob: 5 / 36 }, |
33 | | - 9: { ct: 0, prob: 4 / 36 }, |
34 | | - 10: { ct: 0, prob: 3 / 36 }, |
35 | | - 11: { ct: 0, prob: 2 / 36 }, |
36 | | - 12: { ct: 0, prob: 1 / 36 } |
37 | | - } |
38 | | -} |
39 | | - |
40 | | -const sessionSummary = Object.assign({}, summaryTemplate) |
41 | | - |
42 | | -const hands = [] |
43 | | -const rules = { |
44 | | - minBet: 10, |
45 | | - maxOddsMultiple: { |
46 | | - 4: 3, |
47 | | - 5: 4, |
48 | | - 6: 5, |
49 | | - 8: 5, |
50 | | - 9: 4, |
51 | | - 10: 3 |
| 7 | +function simulateHands (numHands, showDetail) { |
| 8 | + const summaryTemplate = { |
| 9 | + balance: 0, |
| 10 | + rollCount: 0, |
| 11 | + pointsSet: 0, |
| 12 | + pointsWon: 0, |
| 13 | + comeOutWins: 0, |
| 14 | + comeOutLosses: 0, |
| 15 | + netComeOutWins: 0, |
| 16 | + neutrals: 0, |
| 17 | + placeSixWins: 0, |
| 18 | + placeSixLosses: 0, |
| 19 | + placeEightWins: 0, |
| 20 | + placeEightLosses: 0, |
| 21 | + dist: { |
| 22 | + 2: { ct: 0, prob: 1 / 36 }, |
| 23 | + 3: { ct: 0, prob: 2 / 36 }, |
| 24 | + 4: { ct: 0, prob: 3 / 36 }, |
| 25 | + 5: { ct: 0, prob: 4 / 36 }, |
| 26 | + 6: { ct: 0, prob: 5 / 36 }, |
| 27 | + 7: { ct: 0, prob: 6 / 36 }, |
| 28 | + 8: { ct: 0, prob: 5 / 36 }, |
| 29 | + 9: { ct: 0, prob: 4 / 36 }, |
| 30 | + 10: { ct: 0, prob: 3 / 36 }, |
| 31 | + 11: { ct: 0, prob: 2 / 36 }, |
| 32 | + 12: { ct: 0, prob: 1 / 36 } |
| 33 | + } |
52 | 34 | } |
53 | | -} |
54 | | - |
55 | | -console.log(`[table rules] minimum bet: $${rules.minBet}`) |
56 | 35 |
|
57 | | -for (let i = 0; i < numHands; i++) { |
58 | | - if (process.env.DEBUG) { |
59 | | - console.log(`\n================ HAND ${i + 1} ================`) |
60 | | - } |
61 | | - const hand = playHand({ rules, bettingStrategy: minPassLineMaxOddsPlaceSixEight }) |
62 | | - hand.summary = Object.assign({}, summaryTemplate) |
63 | | - |
64 | | - sessionSummary.balance += hand.balance |
65 | | - hand.summary.balance = hand.balance |
66 | | - |
67 | | - hand.history.reduce((memo, roll) => { |
68 | | - memo.rollCount++ |
69 | | - hand.summary.rollCount++ |
70 | | - memo.dist[roll.diceSum].ct++ |
71 | | - |
72 | | - switch (roll.result) { |
73 | | - case 'neutral': |
74 | | - memo.neutrals++ |
75 | | - hand.summary.neutrals++ |
76 | | - break |
77 | | - case 'point set': |
78 | | - memo.pointsSet++ |
79 | | - hand.summary.pointsSet++ |
80 | | - break |
81 | | - case 'point win': |
82 | | - memo.pointsWon++ |
83 | | - hand.summary.pointsWon++ |
84 | | - break |
85 | | - case 'comeout win': |
86 | | - memo.comeOutWins++ |
87 | | - hand.summary.comeOutWins++ |
88 | | - memo.netComeOutWins++ |
89 | | - hand.summary.netComeOutWins++ |
90 | | - break |
91 | | - case 'comeout loss': |
92 | | - memo.comeOutLosses++ |
93 | | - hand.summary.comeOutLosses++ |
94 | | - memo.netComeOutWins-- |
95 | | - hand.summary.netComeOutWins-- |
96 | | - break |
| 36 | + const sessionSummary = Object.assign({}, summaryTemplate) |
| 37 | + const hands = [] |
| 38 | + const rules = { |
| 39 | + minBet: 10, |
| 40 | + maxOddsMultiple: { |
| 41 | + 4: 3, |
| 42 | + 5: 4, |
| 43 | + 6: 5, |
| 44 | + 8: 5, |
| 45 | + 9: 4, |
| 46 | + 10: 3 |
97 | 47 | } |
| 48 | + } |
98 | 49 |
|
99 | | - if (Array.isArray(roll.payouts)) { |
100 | | - roll.payouts.forEach(p => { |
101 | | - if (p.type === 'place 6 win') { |
102 | | - memo.placeSixWins++ |
103 | | - hand.summary.placeSixWins++ |
104 | | - } else if (p.type === 'place 8 win') { |
105 | | - memo.placeEightWins++ |
106 | | - hand.summary.placeEightWins++ |
107 | | - } |
108 | | - }) |
| 50 | + for (let i = 0; i < numHands; i++) { |
| 51 | + if (process.env.DEBUG) { |
| 52 | + console.log(`\n================ HAND ${i + 1} ================`) |
109 | 53 | } |
| 54 | + const hand = playHand({ rules, bettingStrategy: minPassLineMaxOddsPlaceSixEight }) |
| 55 | + hand.summary = Object.assign({}, summaryTemplate) |
| 56 | + |
| 57 | + sessionSummary.balance += hand.balance |
| 58 | + hand.summary.balance = hand.balance |
| 59 | + |
| 60 | + hand.history.reduce((memo, roll) => { |
| 61 | + memo.rollCount++ |
| 62 | + hand.summary.rollCount++ |
| 63 | + memo.dist[roll.diceSum].ct++ |
| 64 | + |
| 65 | + switch (roll.result) { |
| 66 | + case 'neutral': |
| 67 | + memo.neutrals++ |
| 68 | + hand.summary.neutrals++ |
| 69 | + break |
| 70 | + case 'point set': |
| 71 | + memo.pointsSet++ |
| 72 | + hand.summary.pointsSet++ |
| 73 | + break |
| 74 | + case 'point win': |
| 75 | + memo.pointsWon++ |
| 76 | + hand.summary.pointsWon++ |
| 77 | + break |
| 78 | + case 'comeout win': |
| 79 | + memo.comeOutWins++ |
| 80 | + hand.summary.comeOutWins++ |
| 81 | + memo.netComeOutWins++ |
| 82 | + hand.summary.netComeOutWins++ |
| 83 | + break |
| 84 | + case 'comeout loss': |
| 85 | + memo.comeOutLosses++ |
| 86 | + hand.summary.comeOutLosses++ |
| 87 | + memo.netComeOutWins-- |
| 88 | + hand.summary.netComeOutWins-- |
| 89 | + break |
| 90 | + } |
110 | 91 |
|
111 | | - if (roll.result === 'seven out') { |
112 | | - if (roll.betsBefore?.place?.six) { |
113 | | - memo.placeSixLosses++ |
114 | | - hand.summary.placeSixLosses++ |
| 92 | + if (Array.isArray(roll.payouts)) { |
| 93 | + roll.payouts.forEach(p => { |
| 94 | + if (p.type === 'place 6 win') { |
| 95 | + memo.placeSixWins++ |
| 96 | + hand.summary.placeSixWins++ |
| 97 | + } else if (p.type === 'place 8 win') { |
| 98 | + memo.placeEightWins++ |
| 99 | + hand.summary.placeEightWins++ |
| 100 | + } |
| 101 | + }) |
115 | 102 | } |
116 | | - if (roll.betsBefore?.place?.eight) { |
117 | | - memo.placeEightLosses++ |
118 | | - hand.summary.placeEightLosses++ |
| 103 | + |
| 104 | + if (roll.result === 'seven out') { |
| 105 | + if (roll.betsBefore?.place?.six) { |
| 106 | + memo.placeSixLosses++ |
| 107 | + hand.summary.placeSixLosses++ |
| 108 | + } |
| 109 | + if (roll.betsBefore?.place?.eight) { |
| 110 | + memo.placeEightLosses++ |
| 111 | + hand.summary.placeEightLosses++ |
| 112 | + } |
119 | 113 | } |
120 | | - } |
121 | 114 |
|
122 | | - return memo |
123 | | - }, sessionSummary) |
| 115 | + return memo |
| 116 | + }, sessionSummary) |
| 117 | + |
| 118 | + hands.push(hand) |
| 119 | + } |
| 120 | + |
| 121 | + sessionSummary.handCount = hands.length |
| 122 | + |
| 123 | + for (const k of Object.keys(sessionSummary.dist)) { |
| 124 | + sessionSummary.dist[k].ref = Number((sessionSummary.dist[k].prob * sessionSummary.rollCount).toFixed(1)) |
| 125 | + sessionSummary.dist[k].diff = Number((sessionSummary.dist[k].ct - sessionSummary.dist[k].ref).toFixed(1)) |
| 126 | + sessionSummary.dist[k].diff_pct = Number((((sessionSummary.dist[k].ct - sessionSummary.dist[k].ref) / sessionSummary.dist[k].ref) * 100).toFixed(1)) |
| 127 | + if (showDetail) { |
| 128 | + sessionSummary.dist[k].ref_work = `${(sessionSummary.dist[k].prob * sessionSummary.rollCount).toFixed(1)} (${sessionSummary.rollCount} * ${sessionSummary.dist[k].prob.toFixed(2)})` |
| 129 | + } |
| 130 | + delete sessionSummary.dist[k].prob |
| 131 | + } |
124 | 132 |
|
125 | | - hands.push(hand) |
| 133 | + return { sessionSummary, hands, rules } |
126 | 134 | } |
127 | 135 |
|
128 | | -sessionSummary.handCount = hands.length |
| 136 | +function printResults ({ sessionSummary, hands, showDetail, rules }) { |
| 137 | + console.log(`[table rules] minimum bet: $${rules.minBet}`) |
| 138 | + |
| 139 | + console.log('\nDice Roll Distribution') |
| 140 | + console.table(sessionSummary.dist) |
| 141 | + delete sessionSummary.dist |
| 142 | + |
| 143 | + console.log('\nSession Summary') |
| 144 | + console.table(sessionSummary) |
| 145 | + |
| 146 | + console.log('\nHands Summary') |
| 147 | + console.table(hands.map(hand => { |
| 148 | + delete hand.summary.dist |
| 149 | + return hand.summary |
| 150 | + })) |
129 | 151 |
|
130 | | -for (const k of Object.keys(sessionSummary.dist)) { |
131 | | - sessionSummary.dist[k].ref = Number((sessionSummary.dist[k].prob * sessionSummary.rollCount).toFixed(1)) |
132 | | - sessionSummary.dist[k].diff = Number((sessionSummary.dist[k].ct - sessionSummary.dist[k].ref).toFixed(1)) |
133 | | - sessionSummary.dist[k].diff_pct = Number((((sessionSummary.dist[k].ct - sessionSummary.dist[k].ref) / sessionSummary.dist[k].ref) * 100).toFixed(1)) |
134 | 152 | if (showDetail) { |
135 | | - sessionSummary.dist[k].ref_work = `${(sessionSummary.dist[k].prob * sessionSummary.rollCount).toFixed(1)} (${sessionSummary.rollCount} * ${sessionSummary.dist[k].prob.toFixed(2)})` |
| 153 | + console.log('\nHands') |
| 154 | + hands.forEach((hand, handNum) => { |
| 155 | + console.log(`\nHand: ${handNum + 1}, Balance: $${hand.balance}`) |
| 156 | + console.table(hand.history) |
| 157 | + }) |
136 | 158 | } |
137 | | - delete sessionSummary.dist[k].prob |
138 | 159 | } |
139 | | -console.log('\nDice Roll Distribution') |
140 | | -console.table(sessionSummary.dist) |
141 | | -delete sessionSummary.dist |
142 | | - |
143 | | -console.log('\nSession Summary') |
144 | | -console.table(sessionSummary) |
145 | | - |
146 | | -console.log('\nHands Summary') |
147 | | -console.table(hands.map(hand => { |
148 | | - delete hand.summary.dist |
149 | | - return hand.summary |
150 | | -})) |
151 | | - |
152 | | -if (showDetail) { |
153 | | - console.log('\nHands') |
154 | | - hands.forEach((hand, handNum) => { |
155 | | - console.log(`\nHand: ${handNum + 1}, Balance: $${hand.balance}`) |
156 | | - console.table(hand.history) |
157 | | - }) |
| 160 | + |
| 161 | +if (require.main === module) { |
| 162 | + const numHands = parseInt(process.argv[2], 10) |
| 163 | + const showDetail = process.argv[3] |
| 164 | + console.log(`Simulating ${numHands} Craps Hand(s)`) |
| 165 | + console.log('Using betting strategy: minPassLineMaxOddsPlaceSixEight') |
| 166 | + const result = simulateHands(numHands, showDetail) |
| 167 | + printResults({ ...result, showDetail }) |
| 168 | +} else { |
| 169 | + module.exports = simulateHands |
158 | 170 | } |
0 commit comments