Skip to content

Commit 7798547

Browse files
authored
Merge pull request #31 from tphummel/ai/implement-composite-betting-strategy
2 parents 99d05c0 + 4ef70f5 commit 7798547

File tree

3 files changed

+168
-9
lines changed

3 files changed

+168
-9
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,14 @@ stdDev: 28.28
154154
95% CI: [5.80, 84.20]
155155
```
156156

157+
## example: passCome68 strategy
158+
159+
```
160+
$ node hands.js 1 passCome68
161+
Simulating 1 Craps Hand(s)
162+
Using betting strategy: passCome68
163+
```
164+
157165
The standard deviation shows how widely the trials vary around the mean.
158166
Roughly 68% of results will fall within one standard deviation, 95% within
159167
two, and 99.7% within three. The 95% confidence interval is a range that is

betting.js

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,30 @@ function placeSixEightUnlessPoint (opts) {
133133
return bets
134134
}
135135

136+
function placeSixEightUnlessPassOrCome (opts) {
137+
const { hand, bets: existingBets } = opts
138+
139+
const bets = placeSixEight(opts)
140+
const comePoints = Object.keys(existingBets?.come?.points || {})
141+
const coveredPoints = new Set([hand.point, ...comePoints.map(Number)])
142+
143+
if (coveredPoints.has(6) && bets.place?.six) {
144+
bets.new -= bets.place.six.amount
145+
delete bets.place.six
146+
if (process.env.DEBUG) console.log('[decision] removed place 6 bet matching pass or come point')
147+
}
148+
149+
if (coveredPoints.has(8) && bets.place?.eight) {
150+
bets.new -= bets.place.eight.amount
151+
delete bets.place.eight
152+
if (process.env.DEBUG) console.log('[decision] removed place 8 bet matching pass or come point')
153+
}
154+
155+
if (bets.place && Object.keys(bets.place).length === 0) delete bets.place
156+
157+
return bets
158+
}
159+
136160
function minPassLinePlaceSixEight (opts) {
137161
let bets = minPassLineOnly(opts)
138162
bets = placeSixEightUnlessPoint({ ...opts, bets })
@@ -145,7 +169,20 @@ function minPassLineMaxOddsPlaceSixEight (opts) {
145169
return bets
146170
}
147171

148-
function comeLineMaxOdds (opts) {
172+
function minPassLineMaxOddsMinComeLineMaxOdds (opts) {
173+
let bets = minPassLineMaxOdds(opts)
174+
bets = minComeLineMaxOdds({ ...opts, bets })
175+
return bets
176+
}
177+
178+
function passCome68 (opts) {
179+
let bets = minPassLineMaxOdds(opts)
180+
bets = minComeLineMaxOdds({ ...opts, bets })
181+
bets = placeSixEightUnlessPassOrCome({ ...opts, bets })
182+
return bets
183+
}
184+
185+
function minComeLineMaxOdds (opts) {
149186
const { rules, bets: existingBets = {}, hand, maxComeBets = 1 } = opts
150187
const bets = Object.assign({ new: 0 }, existingBets)
151188

@@ -158,16 +195,14 @@ function comeLineMaxOdds (opts) {
158195
bets.come.pending = bets.come.pending || []
159196
bets.come.points = bets.come.points || {}
160197

161-
let activeComeBets = bets.come.pending.length
162-
163-
activeComeBets += Object.values(bets.come.points).reduce((memo, pointBets) => {
198+
const pendingCount = bets.come.pending.length
199+
const pointCount = Object.values(bets.come.points).reduce((memo, pointBets) => {
164200
return memo + pointBets.length
165201
}, 0)
166202

167-
while (activeComeBets < maxComeBets) {
203+
if (pendingCount === 0 && pointCount < maxComeBets) {
168204
bets.come.pending.push({ amount: rules.minBet })
169205
bets.new += rules.minBet
170-
activeComeBets++
171206
if (process.env.DEBUG) console.log(`[action] make come line bet $${rules.minBet}`)
172207
}
173208

@@ -192,7 +227,10 @@ module.exports = {
192227
minPassLineMaxOdds,
193228
placeSixEight,
194229
placeSixEightUnlessPoint,
230+
placeSixEightUnlessPassOrCome,
195231
minPassLinePlaceSixEight,
196232
minPassLineMaxOddsPlaceSixEight,
197-
comeLineMaxOdds
233+
minPassLineMaxOddsMinComeLineMaxOdds,
234+
minComeLineMaxOdds,
235+
passCome68
198236
}

betting.test.js

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ tap.test('lineMaxOdds: add odds to existing line bet', (t) => {
158158
t.end()
159159
})
160160

161-
tap.test('comeLineMaxOdds: create pending come bet and add odds', (t) => {
161+
tap.test('minComeLineMaxOdds: create pending come bet and add odds', (t) => {
162162
const rules = {
163163
minBet: 5,
164164
maxOddsMultiple: { 4: 3, 5: 4, 6: 5, 8: 5, 9: 4, 10: 3 }
@@ -167,7 +167,7 @@ tap.test('comeLineMaxOdds: create pending come bet and add odds', (t) => {
167167
const hand = { isComeOut: false, point: 6 }
168168
const bets = { come: { points: { 5: [{ line: { amount: 5 } }] } } }
169169

170-
const updated = lib.comeLineMaxOdds({ rules, bets, hand, maxComeBets: 2 })
170+
const updated = lib.minComeLineMaxOdds({ rules, bets, hand, maxComeBets: 2 })
171171

172172
t.equal(updated.come.pending.length, 1, 'adds a new pending come bet')
173173
t.equal(updated.come.pending[0].amount, rules.minBet)
@@ -177,6 +177,23 @@ tap.test('comeLineMaxOdds: create pending come bet and add odds', (t) => {
177177
t.end()
178178
})
179179

180+
tap.test('minComeLineMaxOdds: only one pending come bet at a time', (t) => {
181+
const rules = {
182+
minBet: 5,
183+
maxOddsMultiple: { 4: 3, 5: 4, 6: 5, 8: 5, 9: 4, 10: 3 }
184+
}
185+
186+
const hand = { isComeOut: false, point: 6 }
187+
const bets = { come: { pending: [{ amount: 5 }], points: {} } }
188+
189+
const updated = lib.minComeLineMaxOdds({ rules, bets, hand, maxComeBets: 3 })
190+
191+
t.equal(updated.come.pending.length, 1, 'does not stack pending come bets')
192+
t.notOk(updated.new, 'no additional come bet added')
193+
194+
t.end()
195+
})
196+
180197
tap.test('minPassLineMaxOdds: make new bet upon establishing point', (t) => {
181198
const rules = {
182199
minBet: 5,
@@ -518,6 +535,102 @@ tap.test('minPassLinePlaceSixEight: existing bets remain unchanged', (t) => {
518535
t.end()
519536
})
520537

538+
tap.test('minPassLineMaxOddsMinComeLineMaxOdds: adds come bet after point set', (t) => {
539+
const rules = {
540+
minBet: 5,
541+
maxOddsMultiple: {
542+
4: 3,
543+
5: 4,
544+
6: 5,
545+
8: 5,
546+
9: 4,
547+
10: 3
548+
}
549+
}
550+
551+
const comeOut = { isComeOut: true }
552+
const first = lib.minPassLineMaxOddsMinComeLineMaxOdds({ rules, hand: comeOut })
553+
554+
t.equal(first.pass.line.amount, rules.minBet)
555+
t.notOk(first.come, 'no come bet on comeout')
556+
t.equal(first.new, rules.minBet)
557+
558+
delete first.new
559+
560+
const pointSix = { isComeOut: false, result: 'point set', point: 6 }
561+
const second = lib.minPassLineMaxOddsMinComeLineMaxOdds({ rules, bets: first, hand: pointSix })
562+
563+
t.equal(second.pass.odds.amount, rules.maxOddsMultiple['6'] * rules.minBet)
564+
t.equal(second.come.pending.length, 1, 'adds one pending come bet after point set')
565+
t.equal(second.come.pending[0].amount, rules.minBet)
566+
t.equal(second.new, rules.maxOddsMultiple['6'] * rules.minBet + rules.minBet)
567+
568+
t.end()
569+
})
570+
571+
tap.test('passCome68: adds pass odds, come bet, and place bets not on pass point', (t) => {
572+
const rules = {
573+
minBet: 5,
574+
maxOddsMultiple: {
575+
4: 3,
576+
5: 4,
577+
6: 5,
578+
8: 5,
579+
9: 4,
580+
10: 3
581+
}
582+
}
583+
584+
const comeOut = { isComeOut: true }
585+
const first = lib.passCome68({ rules, hand: comeOut })
586+
587+
t.equal(first.pass.line.amount, rules.minBet)
588+
t.notOk(first.come, 'no come bet on comeout')
589+
t.notOk(first.place, 'no place bets on comeout')
590+
t.equal(first.new, rules.minBet)
591+
592+
delete first.new
593+
594+
const pointSix = { isComeOut: false, result: 'point set', point: 6 }
595+
const second = lib.passCome68({ rules, bets: first, hand: pointSix })
596+
597+
t.equal(second.pass.odds.amount, rules.maxOddsMultiple['6'] * rules.minBet)
598+
t.equal(second.come.pending.length, 1, 'adds one pending come bet after point set')
599+
t.notOk(second.place?.six, 'skip place 6 when 6 is the pass point')
600+
t.equal(second.place.eight.amount, 6)
601+
t.equal(second.new, second.pass.odds.amount + rules.minBet + 6)
602+
603+
t.end()
604+
})
605+
606+
tap.test('passCome68: skips place bets covered by come points', (t) => {
607+
const rules = {
608+
minBet: 5,
609+
maxOddsMultiple: {
610+
4: 3,
611+
5: 4,
612+
6: 5,
613+
8: 5,
614+
9: 4,
615+
10: 3
616+
}
617+
}
618+
619+
const hand = { isComeOut: false, result: 'neutral', point: 5 }
620+
const bets = {
621+
pass: { line: { amount: 5, isContract: true }, odds: { amount: 20 } },
622+
come: { points: { 6: [{ line: { amount: 5 } }] } }
623+
}
624+
625+
const updated = lib.passCome68({ rules, bets, hand })
626+
627+
t.notOk(updated.place?.six, 'skip place 6 when come point is 6')
628+
t.equal(updated.place.eight.amount, 6)
629+
t.equal(updated.new, rules.maxOddsMultiple['6'] * rules.minBet + 6)
630+
631+
t.end()
632+
})
633+
521634
// Priority 2: Test all points (4, 5, 6, 8, 9, 10) with odds calculations
522635
tap.test('minPassLineMaxOdds: all points have correct odds multiples', (t) => {
523636
const rules = {

0 commit comments

Comments
 (0)