diff --git a/README.md b/README.md index da8d22c..399f1f2 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,14 @@ stdDev: 28.28 95% CI: [5.80, 84.20] ``` +## example: passCome68 strategy + +``` +$ node hands.js 1 passCome68 +Simulating 1 Craps Hand(s) +Using betting strategy: passCome68 +``` + The standard deviation shows how widely the trials vary around the mean. Roughly 68% of results will fall within one standard deviation, 95% within two, and 99.7% within three. The 95% confidence interval is a range that is diff --git a/betting.js b/betting.js index a3df21a..0458432 100644 --- a/betting.js +++ b/betting.js @@ -133,6 +133,30 @@ function placeSixEightUnlessPoint (opts) { return bets } +function placeSixEightUnlessPassOrCome (opts) { + const { hand, bets: existingBets } = opts + + const bets = placeSixEight(opts) + const comePoints = Object.keys(existingBets?.come?.points || {}) + const coveredPoints = new Set([hand.point, ...comePoints.map(Number)]) + + if (coveredPoints.has(6) && bets.place?.six) { + bets.new -= bets.place.six.amount + delete bets.place.six + if (process.env.DEBUG) console.log('[decision] removed place 6 bet matching pass or come point') + } + + if (coveredPoints.has(8) && bets.place?.eight) { + bets.new -= bets.place.eight.amount + delete bets.place.eight + if (process.env.DEBUG) console.log('[decision] removed place 8 bet matching pass or come point') + } + + if (bets.place && Object.keys(bets.place).length === 0) delete bets.place + + return bets +} + function minPassLinePlaceSixEight (opts) { let bets = minPassLineOnly(opts) bets = placeSixEightUnlessPoint({ ...opts, bets }) @@ -145,7 +169,20 @@ function minPassLineMaxOddsPlaceSixEight (opts) { return bets } -function comeLineMaxOdds (opts) { +function minPassLineMaxOddsMinComeLineMaxOdds (opts) { + let bets = minPassLineMaxOdds(opts) + bets = minComeLineMaxOdds({ ...opts, bets }) + return bets +} + +function passCome68 (opts) { + let bets = minPassLineMaxOdds(opts) + bets = minComeLineMaxOdds({ ...opts, bets }) + bets = placeSixEightUnlessPassOrCome({ ...opts, bets }) + return bets +} + +function minComeLineMaxOdds (opts) { const { rules, bets: existingBets = {}, hand, maxComeBets = 1 } = opts const bets = Object.assign({ new: 0 }, existingBets) @@ -158,16 +195,14 @@ function comeLineMaxOdds (opts) { bets.come.pending = bets.come.pending || [] bets.come.points = bets.come.points || {} - let activeComeBets = bets.come.pending.length - - activeComeBets += Object.values(bets.come.points).reduce((memo, pointBets) => { + const pendingCount = bets.come.pending.length + const pointCount = Object.values(bets.come.points).reduce((memo, pointBets) => { return memo + pointBets.length }, 0) - while (activeComeBets < maxComeBets) { + if (pendingCount === 0 && pointCount < maxComeBets) { bets.come.pending.push({ amount: rules.minBet }) bets.new += rules.minBet - activeComeBets++ if (process.env.DEBUG) console.log(`[action] make come line bet $${rules.minBet}`) } @@ -192,7 +227,10 @@ module.exports = { minPassLineMaxOdds, placeSixEight, placeSixEightUnlessPoint, + placeSixEightUnlessPassOrCome, minPassLinePlaceSixEight, minPassLineMaxOddsPlaceSixEight, - comeLineMaxOdds + minPassLineMaxOddsMinComeLineMaxOdds, + minComeLineMaxOdds, + passCome68 } diff --git a/betting.test.js b/betting.test.js index 468eada..58e140a 100644 --- a/betting.test.js +++ b/betting.test.js @@ -158,7 +158,7 @@ tap.test('lineMaxOdds: add odds to existing line bet', (t) => { t.end() }) -tap.test('comeLineMaxOdds: create pending come bet and add odds', (t) => { +tap.test('minComeLineMaxOdds: create pending come bet and add odds', (t) => { const rules = { minBet: 5, 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) => { const hand = { isComeOut: false, point: 6 } const bets = { come: { points: { 5: [{ line: { amount: 5 } }] } } } - const updated = lib.comeLineMaxOdds({ rules, bets, hand, maxComeBets: 2 }) + const updated = lib.minComeLineMaxOdds({ rules, bets, hand, maxComeBets: 2 }) t.equal(updated.come.pending.length, 1, 'adds a new pending come bet') t.equal(updated.come.pending[0].amount, rules.minBet) @@ -177,6 +177,23 @@ tap.test('comeLineMaxOdds: create pending come bet and add odds', (t) => { t.end() }) +tap.test('minComeLineMaxOdds: only one pending come bet at a time', (t) => { + const rules = { + minBet: 5, + maxOddsMultiple: { 4: 3, 5: 4, 6: 5, 8: 5, 9: 4, 10: 3 } + } + + const hand = { isComeOut: false, point: 6 } + const bets = { come: { pending: [{ amount: 5 }], points: {} } } + + const updated = lib.minComeLineMaxOdds({ rules, bets, hand, maxComeBets: 3 }) + + t.equal(updated.come.pending.length, 1, 'does not stack pending come bets') + t.notOk(updated.new, 'no additional come bet added') + + t.end() +}) + tap.test('minPassLineMaxOdds: make new bet upon establishing point', (t) => { const rules = { minBet: 5, @@ -518,6 +535,102 @@ tap.test('minPassLinePlaceSixEight: existing bets remain unchanged', (t) => { t.end() }) +tap.test('minPassLineMaxOddsMinComeLineMaxOdds: adds come bet after point set', (t) => { + const rules = { + minBet: 5, + maxOddsMultiple: { + 4: 3, + 5: 4, + 6: 5, + 8: 5, + 9: 4, + 10: 3 + } + } + + const comeOut = { isComeOut: true } + const first = lib.minPassLineMaxOddsMinComeLineMaxOdds({ rules, hand: comeOut }) + + t.equal(first.pass.line.amount, rules.minBet) + t.notOk(first.come, 'no come bet on comeout') + t.equal(first.new, rules.minBet) + + delete first.new + + const pointSix = { isComeOut: false, result: 'point set', point: 6 } + const second = lib.minPassLineMaxOddsMinComeLineMaxOdds({ rules, bets: first, hand: pointSix }) + + t.equal(second.pass.odds.amount, rules.maxOddsMultiple['6'] * rules.minBet) + t.equal(second.come.pending.length, 1, 'adds one pending come bet after point set') + t.equal(second.come.pending[0].amount, rules.minBet) + t.equal(second.new, rules.maxOddsMultiple['6'] * rules.minBet + rules.minBet) + + t.end() +}) + +tap.test('passCome68: adds pass odds, come bet, and place bets not on pass point', (t) => { + const rules = { + minBet: 5, + maxOddsMultiple: { + 4: 3, + 5: 4, + 6: 5, + 8: 5, + 9: 4, + 10: 3 + } + } + + const comeOut = { isComeOut: true } + const first = lib.passCome68({ rules, hand: comeOut }) + + t.equal(first.pass.line.amount, rules.minBet) + t.notOk(first.come, 'no come bet on comeout') + t.notOk(first.place, 'no place bets on comeout') + t.equal(first.new, rules.minBet) + + delete first.new + + const pointSix = { isComeOut: false, result: 'point set', point: 6 } + const second = lib.passCome68({ rules, bets: first, hand: pointSix }) + + t.equal(second.pass.odds.amount, rules.maxOddsMultiple['6'] * rules.minBet) + t.equal(second.come.pending.length, 1, 'adds one pending come bet after point set') + t.notOk(second.place?.six, 'skip place 6 when 6 is the pass point') + t.equal(second.place.eight.amount, 6) + t.equal(second.new, second.pass.odds.amount + rules.minBet + 6) + + t.end() +}) + +tap.test('passCome68: skips place bets covered by come points', (t) => { + const rules = { + minBet: 5, + maxOddsMultiple: { + 4: 3, + 5: 4, + 6: 5, + 8: 5, + 9: 4, + 10: 3 + } + } + + const hand = { isComeOut: false, result: 'neutral', point: 5 } + const bets = { + pass: { line: { amount: 5, isContract: true }, odds: { amount: 20 } }, + come: { points: { 6: [{ line: { amount: 5 } }] } } + } + + const updated = lib.passCome68({ rules, bets, hand }) + + t.notOk(updated.place?.six, 'skip place 6 when come point is 6') + t.equal(updated.place.eight.amount, 6) + t.equal(updated.new, rules.maxOddsMultiple['6'] * rules.minBet + 6) + + t.end() +}) + // Priority 2: Test all points (4, 5, 6, 8, 9, 10) with odds calculations tap.test('minPassLineMaxOdds: all points have correct odds multiples', (t) => { const rules = {