diff --git a/betting.js b/betting.js index 34da04d..44228f3 100644 --- a/betting.js +++ b/betting.js @@ -345,6 +345,53 @@ function pressPlaceSixEight ({ rules, bets: existingBets, hand, playerMind }) { return bets } +function fourBetGrindContinuous (opts) { + const { rules, bets: existingBets = {}, hand } = opts + const bets = Object.assign({ new: 0 }, existingBets) + + // On come-out: place don't pass if not present + if (hand.isComeOut) { + if (!bets?.dontPass?.line) { + bets.dontPass = { line: { amount: rules.minBet } } + bets.new += rules.minBet + if (process.env.DEBUG) console.log(`[action] make dont pass line bet $${rules.minBet}`) + } + return bets + } + + // Point phase: don't pass is established, work through the sequence + bets.come = bets.come || {} + bets.come.pending = bets.come.pending || [] + bets.come.points = bets.come.points || {} + bets.dontCome = bets.dontCome || {} + bets.dontCome.pending = bets.dontCome.pending || [] + bets.dontCome.points = bets.dontCome.points || {} + + const comePointCount = Object.values(bets.come.points).reduce((sum, arr) => sum + arr.length, 0) + const comePendingCount = bets.come.pending.length + const dontComePointCount = Object.values(bets.dontCome.points).reduce((sum, arr) => sum + arr.length, 0) + const dontComePendingCount = bets.dontCome.pending.length + + if (comePendingCount === 0 && comePointCount < 2) { + // Place next come bet (first or second) once none are pending + bets.come.pending.push({ amount: rules.minBet }) + bets.new += rules.minBet + if (process.env.DEBUG) console.log(`[action] make come line bet $${rules.minBet} (come point count: ${comePointCount})`) + return bets + } + + if (comePointCount >= 2 && comePendingCount === 0 && dontComePointCount === 0 && dontComePendingCount === 0) { + // Both come bets established, now place don't come + bets.dontCome.pending.push({ amount: rules.minBet }) + bets.new += rules.minBet + if (process.env.DEBUG) console.log(`[action] make dont come line bet $${rules.minBet}`) + return bets + } + + // All four established (or waiting for pending bets to establish) - no new bets + return bets +} + function noBetting () { return { new: 0 } } @@ -428,6 +475,9 @@ function minDontPassOnly (opts) { minDontPassOnly.title = "Don't Pass Line Only" minDontPassOnly.description = "Bet the minimum on the don't pass line each come-out roll." +fourBetGrindContinuous.title = 'Four Bet Grind Continuous' +fourBetGrindContinuous.description = "Places four bets in sequence at table minimum: don't pass, come, come, don't come. Each bet is placed only after the previous is established (moved to a point). Once all four are established, no new bets are placed unless one needs to be replaced. Continuous — no per-hand limit on replacement." + export { noBetting, minDontPassOnly, @@ -447,5 +497,6 @@ export { minPassLineMaxOddsMinComeLineMaxOdds, minComeLineMaxOdds, passCome68, - passcome2place68 + passcome2place68, + fourBetGrindContinuous } diff --git a/betting.test.js b/betting.test.js index 046b3c7..e0f0cdc 100644 --- a/betting.test.js +++ b/betting.test.js @@ -1413,3 +1413,164 @@ tap.test('minPassLineMidOdds: no odds on come-out', (t) => { t.notOk(result.pass?.odds, 'no odds on come-out') t.end() }) + +const fourBetRules = { + minBet: 5, + maxOddsMultiple: { 4: 3, 5: 4, 6: 5, 8: 5, 9: 4, 10: 3 } +} + +tap.test('fourBetGrindContinuous: come-out places dont pass', (t) => { + const hand = { isComeOut: true } + const bets = lib.fourBetGrindContinuous({ rules: fourBetRules, hand }) + + t.equal(bets.dontPass.line.amount, fourBetRules.minBet, "don't pass placed at minBet") + t.equal(bets.new, fourBetRules.minBet) + t.end() +}) + +tap.test('fourBetGrindContinuous: come-out with existing dont pass does nothing', (t) => { + const hand = { isComeOut: true } + const bets = lib.fourBetGrindContinuous({ + rules: fourBetRules, + hand, + bets: { dontPass: { line: { amount: 5 } } } + }) + + t.equal(bets.new, 0, 'no new bets when dont pass already placed') + t.end() +}) + +tap.test('fourBetGrindContinuous: point phase places first come bet', (t) => { + const hand = { isComeOut: false, point: 6 } + const existingBets = { dontPass: { line: { amount: 5 } } } + const bets = lib.fourBetGrindContinuous({ rules: fourBetRules, hand, bets: existingBets }) + + t.equal(bets.come.pending.length, 1, 'one come bet pending') + t.equal(bets.come.pending[0].amount, fourBetRules.minBet) + t.equal(bets.new, fourBetRules.minBet) + t.end() +}) + +tap.test('fourBetGrindContinuous: waits while come bet is pending', (t) => { + const hand = { isComeOut: false, point: 6 } + const existingBets = { + dontPass: { line: { amount: 5 } }, + come: { pending: [{ amount: 5 }], points: {} } + } + const bets = lib.fourBetGrindContinuous({ rules: fourBetRules, hand, bets: existingBets }) + + t.equal(bets.come.pending.length, 1, 'still only one pending come bet') + t.equal(bets.new, 0, 'no new bets while pending') + t.end() +}) + +tap.test('fourBetGrindContinuous: one come established places second come bet', (t) => { + const hand = { isComeOut: false, point: 6 } + const existingBets = { + dontPass: { line: { amount: 5 } }, + come: { pending: [], points: { 4: [{ line: { amount: 5 } }] } } + } + const bets = lib.fourBetGrindContinuous({ rules: fourBetRules, hand, bets: existingBets }) + + t.equal(bets.come.pending.length, 1, 'second come bet pending') + t.equal(bets.new, fourBetRules.minBet) + t.end() +}) + +tap.test('fourBetGrindContinuous: waits while second come bet is pending', (t) => { + const hand = { isComeOut: false, point: 6 } + const existingBets = { + dontPass: { line: { amount: 5 } }, + come: { pending: [{ amount: 5 }], points: { 4: [{ line: { amount: 5 } }] } } + } + const bets = lib.fourBetGrindContinuous({ rules: fourBetRules, hand, bets: existingBets }) + + t.equal(bets.come.pending.length, 1, 'still only one pending') + t.equal(bets.new, 0) + t.end() +}) + +tap.test('fourBetGrindContinuous: two come bets established places dont come', (t) => { + const hand = { isComeOut: false, point: 6 } + const existingBets = { + dontPass: { line: { amount: 5 } }, + come: { pending: [], points: { 4: [{ line: { amount: 5 } }], 9: [{ line: { amount: 5 } }] } } + } + const bets = lib.fourBetGrindContinuous({ rules: fourBetRules, hand, bets: existingBets }) + + t.equal(bets.dontCome.pending.length, 1, "don't come pending") + t.equal(bets.dontCome.pending[0].amount, fourBetRules.minBet) + t.equal(bets.new, fourBetRules.minBet) + t.end() +}) + +tap.test('fourBetGrindContinuous: waits while dont come is pending', (t) => { + const hand = { isComeOut: false, point: 6 } + const existingBets = { + dontPass: { line: { amount: 5 } }, + come: { pending: [], points: { 4: [{ line: { amount: 5 } }], 9: [{ line: { amount: 5 } }] } }, + dontCome: { pending: [{ amount: 5 }], points: {} } + } + const bets = lib.fourBetGrindContinuous({ rules: fourBetRules, hand, bets: existingBets }) + + t.equal(bets.dontCome.pending.length, 1, 'still only one pending dont come') + t.equal(bets.new, 0) + t.end() +}) + +tap.test('fourBetGrindContinuous: all four established - no new bets', (t) => { + const hand = { isComeOut: false, point: 6 } + const existingBets = { + dontPass: { line: { amount: 5 } }, + come: { pending: [], points: { 4: [{ line: { amount: 5 } }], 9: [{ line: { amount: 5 } }] } }, + dontCome: { pending: [], points: { 8: [{ line: { amount: 5 } }] } } + } + const bets = lib.fourBetGrindContinuous({ rules: fourBetRules, hand, bets: existingBets }) + + t.equal(bets.new, 0, 'no bets when all four established') + t.end() +}) + +tap.test('fourBetGrindContinuous: replaces lost come bet', (t) => { + const hand = { isComeOut: false, point: 6 } + // One come bet was won/lost, only one remains + const existingBets = { + dontPass: { line: { amount: 5 } }, + come: { pending: [], points: { 4: [{ line: { amount: 5 } }] } }, + dontCome: { pending: [], points: { 8: [{ line: { amount: 5 } }] } } + } + const bets = lib.fourBetGrindContinuous({ rules: fourBetRules, hand, bets: existingBets }) + + t.equal(bets.come.pending.length, 1, 'replacement come bet placed') + t.equal(bets.new, fourBetRules.minBet) + t.end() +}) + +tap.test('fourBetGrindContinuous: replaces lost dont come bet', (t) => { + const hand = { isComeOut: false, point: 6 } + // Don't come was lost (its point was rolled), two come bets still established + const existingBets = { + dontPass: { line: { amount: 5 } }, + come: { pending: [], points: { 4: [{ line: { amount: 5 } }], 9: [{ line: { amount: 5 } }] } }, + dontCome: { pending: [], points: {} } + } + const bets = lib.fourBetGrindContinuous({ rules: fourBetRules, hand, bets: existingBets }) + + t.equal(bets.dontCome.pending.length, 1, "replacement don't come placed") + t.equal(bets.new, fourBetRules.minBet) + t.end() +}) + +tap.test('fourBetGrindContinuous: new come-out after point win places dont pass', (t) => { + const hand = { isComeOut: true } + // After a point win: dont pass was settled out, come bets may remain + const existingBets = { + come: { pending: [], points: { 9: [{ line: { amount: 5 } }] } }, + dontCome: { pending: [], points: { 8: [{ line: { amount: 5 } }] } } + } + const bets = lib.fourBetGrindContinuous({ rules: fourBetRules, hand, bets: existingBets }) + + t.equal(bets.dontPass.line.amount, fourBetRules.minBet, "don't pass re-placed on new come-out") + t.equal(bets.new, fourBetRules.minBet) + t.end() +})