Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 74 additions & 1 deletion betting.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,75 @@ function fourBetGrindContinuous (opts) {
return bets
}

function fourBetGrindLimit (opts) {
const { rules, bets: existingBets = {}, hand, playerMind } = opts
const bets = Object.assign({ new: 0 }, existingBets)

// Initialize per-hand state
if (!playerMind.fourBetGrindLimit) {
playerMind.fourBetGrindLimit = { unitsRemaining: 4 }
}
const state = playerMind.fourBetGrindLimit

// Credit wins from the previous roll
if (hand.payouts) {
for (const payout of hand.payouts) {
if (payout.profit > 0) {
state.unitsRemaining += 1
if (process.env.DEBUG) console.log(`[four-bet-grind] win credited, unitsRemaining: ${state.unitsRemaining}`)
}
}
}

// No budget left — sit out the rest of the hand
if (state.unitsRemaining <= 0) {
if (process.env.DEBUG) console.log('[four-bet-grind] no units remaining, skipping bet')
return bets
}

// 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
state.unitsRemaining -= 1
if (process.env.DEBUG) console.log(`[four-bet-grind] make dont pass line bet $${rules.minBet}, unitsRemaining: ${state.unitsRemaining}`)
}
return bets
}

// Point phase: work through the same sequence as the continuous variant
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) {
bets.come.pending.push({ amount: rules.minBet })
bets.new += rules.minBet
state.unitsRemaining -= 1
if (process.env.DEBUG) console.log(`[four-bet-grind] make come line bet $${rules.minBet}, unitsRemaining: ${state.unitsRemaining}`)
return bets
}

if (comePointCount >= 2 && comePendingCount === 0 && dontComePointCount === 0 && dontComePendingCount === 0) {
bets.dontCome.pending.push({ amount: rules.minBet })
bets.new += rules.minBet
state.unitsRemaining -= 1
if (process.env.DEBUG) console.log(`[four-bet-grind] make dont come line bet $${rules.minBet}, unitsRemaining: ${state.unitsRemaining}`)
return bets
}

return bets
}

function noBetting () {
return { new: 0 }
}
Expand Down Expand Up @@ -478,6 +547,9 @@ minDontPassOnly.description = "Bet the minimum on the don't pass line each come-
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."

fourBetGrindLimit.title = 'Four Bet Grind Limit'
fourBetGrindLimit.description = "Same sequence as Four Bet Grind Continuous (don't pass, come, come, don't come) but with a per-hand budget of four units at table minimum. Each new bet costs one unit. Each win earns one unit back. Once the budget reaches zero, no further bets are placed for the remainder of the hand."

export {
noBetting,
minDontPassOnly,
Expand All @@ -498,5 +570,6 @@ export {
minComeLineMaxOdds,
passCome68,
passcome2place68,
fourBetGrindContinuous
fourBetGrindContinuous,
fourBetGrindLimit
}
132 changes: 132 additions & 0 deletions betting.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1574,3 +1574,135 @@ tap.test('fourBetGrindContinuous: new come-out after point win places dont pass'
t.equal(bets.new, fourBetRules.minBet)
t.end()
})

tap.test('fourBetGrindLimit: come-out places dont pass and decrements budget', (t) => {
const hand = { isComeOut: true }
const playerMind = {}
const bets = lib.fourBetGrindLimit({ rules: fourBetRules, hand, playerMind })

t.equal(bets.dontPass.line.amount, fourBetRules.minBet, "don't pass placed at minBet")
t.equal(bets.new, fourBetRules.minBet)
t.equal(playerMind.fourBetGrindLimit.unitsRemaining, 3, 'budget decremented to 3')
t.end()
})

tap.test('fourBetGrindLimit: come-out with existing dont pass does nothing', (t) => {
const hand = { isComeOut: true }
const playerMind = { fourBetGrindLimit: { unitsRemaining: 3 } }
const bets = lib.fourBetGrindLimit({
rules: fourBetRules,
hand,
playerMind,
bets: { dontPass: { line: { amount: 5 } } }
})

t.equal(bets.new, 0, 'no new bets when dont pass already placed')
t.equal(playerMind.fourBetGrindLimit.unitsRemaining, 3, 'budget unchanged')
t.end()
})

tap.test('fourBetGrindLimit: point phase places first come bet', (t) => {
const hand = { isComeOut: false, point: 6 }
const playerMind = { fourBetGrindLimit: { unitsRemaining: 3 } }
const existingBets = { dontPass: { line: { amount: 5 } } }
const bets = lib.fourBetGrindLimit({ rules: fourBetRules, hand, playerMind, 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.equal(playerMind.fourBetGrindLimit.unitsRemaining, 2, 'budget decremented to 2')
t.end()
})

tap.test('fourBetGrindLimit: stops betting when budget reaches zero', (t) => {
const hand = { isComeOut: false, point: 6 }
const playerMind = { fourBetGrindLimit: { unitsRemaining: 0 } }
const existingBets = { dontPass: { line: { amount: 5 } } }
const bets = lib.fourBetGrindLimit({ rules: fourBetRules, hand, playerMind, bets: existingBets })

t.equal(bets.new, 0, 'no bets placed when budget is zero')
t.equal(bets.come, undefined, 'no come bet structure added')
t.end()
})

tap.test('fourBetGrindLimit: win credits one unit back to budget', (t) => {
const hand = {
isComeOut: false,
point: 6,
payouts: [{ type: 'come point win', principal: 5, profit: 5 }]
}
const playerMind = { fourBetGrindLimit: { unitsRemaining: 0 } }
const existingBets = {
dontPass: { line: { amount: 5 } },
come: { pending: [], points: { 9: [{ line: { amount: 5 } }] } }
}
const bets = lib.fourBetGrindLimit({ rules: fourBetRules, hand, playerMind, bets: existingBets })

t.equal(playerMind.fourBetGrindLimit.unitsRemaining, 0, 'win added 1, then bet placed cost 1 — net zero')
t.equal(bets.come.pending.length, 1, 'was able to place a come bet after win credited')
t.equal(bets.new, fourBetRules.minBet)
t.end()
})

tap.test('fourBetGrindLimit: multiple wins in one roll credit multiple units', (t) => {
const hand = {
isComeOut: false,
point: 6,
payouts: [
{ type: 'come point win', principal: 5, profit: 5 },
{ type: 'dont come win', principal: 5, profit: 5 }
]
}
const playerMind = { fourBetGrindLimit: { unitsRemaining: 0 } }
const existingBets = { dontPass: { line: { amount: 5 } } }
const bets = lib.fourBetGrindLimit({ rules: fourBetRules, hand, playerMind, bets: existingBets })

t.equal(playerMind.fourBetGrindLimit.unitsRemaining, 1, '2 wins credited, 1 bet placed — net 1 remaining')
t.equal(bets.new, fourBetRules.minBet, 'one bet placed')
t.end()
})

tap.test('fourBetGrindLimit: push (profit=0) does not credit a unit', (t) => {
const hand = {
isComeOut: false,
point: 6,
payouts: [{ type: 'push', principal: 5, profit: 0 }]
}
const playerMind = { fourBetGrindLimit: { unitsRemaining: 0 } }
const existingBets = { dontPass: { line: { amount: 5 } } }
const bets = lib.fourBetGrindLimit({ rules: fourBetRules, hand, playerMind, bets: existingBets })

t.equal(playerMind.fourBetGrindLimit.unitsRemaining, 0, 'push does not increase budget')
t.equal(bets.new, 0, 'no bet placed')
t.end()
})

tap.test('fourBetGrindLimit: all four established - no new bets', (t) => {
const hand = { isComeOut: false, point: 6 }
const playerMind = { fourBetGrindLimit: { unitsRemaining: 0 } }
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.fourBetGrindLimit({ rules: fourBetRules, hand, playerMind, bets: existingBets })

t.equal(bets.new, 0, 'no bets when all four established and budget zero')
t.end()
})

tap.test('fourBetGrindLimit: does not replace lost come bet when budget exhausted', (t) => {
const hand = { isComeOut: false, point: 6 }
const playerMind = { fourBetGrindLimit: { unitsRemaining: 0 } }
// One come bet was lost, only one remains — but budget is zero
const existingBets = {
dontPass: { line: { amount: 5 } },
come: { pending: [], points: { 4: [{ line: { amount: 5 } }] } },
dontCome: { pending: [], points: { 8: [{ line: { amount: 5 } }] } }
}
const bets = lib.fourBetGrindLimit({ rules: fourBetRules, hand, playerMind, bets: existingBets })

t.equal(bets.come.pending.length, 0, 'no replacement come bet when budget is zero')
t.equal(bets.new, 0)
t.end()
})
Loading