Skip to content
Open
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
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,37 @@ Hand: 1
└─────────┴──────┴──────┴─────────┴─────────────┴───────────┴───────┘
```

## strategies

The `betting` module exposes a few simple strategy helpers:

- `minPassLineOnly` – always maintains a minimum pass line bet
- `minPassLineMaxOdds` – adds maximum odds on the pass line point
- `minComeLineMaxOdds` – once a point is set, places come bets with max odds

## table rules

`playHand` accepts a `rules` object that controls minimum bets and odds limits.
You can now also customize which numbers win or lose on the come out roll.

```js
const rules = {
minBet: 5,
maxOddsMultiple: { /* ... */ },
comeOutWin: [7, 11],
comeOutLoss: [2, 3, 12] // default
}
```

For example, to make boxcars (12) a come out win instead of a loss:

```js
const rules = {
comeOutLoss: [2, 3],
comeOutWin: [7, 11, 12]
}
```

## what? why?

I like to play craps sometimes. I have a handful of strategies I like to play. It is time consuming to play in an app. I'd like to play 5, 50, 500 hands very fast using various strategies. Which strategies are best is well understood, the variability comes in with how aggressive your strategies are and the level of risk you assume at any given moment. And of course the dice outcomes and their deviation from long term probabilities and how they interact with the strategies you employ is the fun part. This simulator lets me scratch my craps itch very quickly.
Expand Down
46 changes: 45 additions & 1 deletion betting.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,51 @@ function minPassLineMaxOdds (opts) {
return bets
}

function minComeLineMaxOdds (opts) {
const { rules, hand } = opts
const bets = minPassLineMaxOdds(opts)

if (!hand.isComeOut && !bets?.come?.line) {
bets.come = {
line: { amount: rules.minBet },
isComeOut: true
}
bets.new += rules.minBet
}

if (bets?.come?.line && !bets.come.isComeOut && !bets.come.odds) {
const oddsAmount = rules.maxOddsMultiple[bets.come.point] * bets.come.line.amount
bets.come.odds = { amount: oddsAmount }
bets.new += oddsAmount
}

return bets
}

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

if (hand.isComeOut) return bets

bets.place = bets.place || {}

if (!bets.place.six) {
bets.place.six = { amount: rules.minBet }
bets.new += bets.place.six.amount
}

if (!bets.place.eight) {
bets.place.eight = { amount: rules.minBet }
bets.new += bets.place.eight.amount
}

return bets
}

module.exports = {
minPassLineOnly,
minPassLineMaxOdds
minPassLineMaxOdds,
minComeLineMaxOdds,
placeSixEight
}
121 changes: 121 additions & 0 deletions betting.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,124 @@ tap.test('minPassLineMaxOdds: continue existing bet', (t) => {

t.end()
})

tap.test('minComeLineMaxOdds: place come bet after point set', (t) => {
const rules = {
minBet: 5,
maxOddsMultiple: {
4: 3,
5: 4,
6: 5,
8: 5,
9: 4,
10: 3
}
}

const hand = { isComeOut: false, point: 5 }
const bets = {
pass: { line: { amount: 5, isContract: true }, odds: { amount: 20 } },
new: 0
}

const updatedBets = lib.minComeLineMaxOdds({ rules, bets, hand })
t.equal(updatedBets.come.line.amount, rules.minBet)
t.ok(updatedBets.come.isComeOut)
t.equal(updatedBets.new, rules.minBet)
t.end()
})

tap.test('placeSixEight: make new place bets after point set', (t) => {
const rules = {
minBet: 6
}

const hand = {
isComeOut: false,
point: 5
}

const updatedBets = lib.placeSixEight({ rules, hand })

t.equal(updatedBets.place.six.amount, rules.minBet)
t.equal(updatedBets.place.eight.amount, rules.minBet)
t.equal(updatedBets.new, rules.minBet * 2)

t.end()
})

tap.test('minComeLineMaxOdds: add odds after come point', (t) => {
const rules = {
minBet: 5,
maxOddsMultiple: {
4: 3,
5: 4,
6: 5,
8: 5,
9: 4,
10: 3
}
}

const hand = { isComeOut: false, point: 5 }
const bets = {
pass: { line: { amount: 5, isContract: true }, odds: { amount: 20 } },
come: { line: { amount: 5 }, isComeOut: false, point: 4 },
new: 0
}

const updatedBets = lib.minComeLineMaxOdds({ rules, bets, hand })
t.equal(updatedBets.come.odds.amount, rules.maxOddsMultiple['4'] * 5)
t.equal(updatedBets.new, rules.maxOddsMultiple['4'] * 5)

t.end()
})

tap.test('placeSixEight: no new bets on comeout', (t) => {
const rules = { minBet: 6 }
const hand = { isComeOut: true }

const updatedBets = lib.placeSixEight({ rules, hand })

t.notOk(updatedBets.place)
t.notOk(updatedBets.new)

t.end()
})

tap.test('placeSixEight: existing bets remain', (t) => {
const rules = { minBet: 6 }
const hand = { isComeOut: false, point: 8 }

const bets = { place: { six: { amount: 6 }, eight: { amount: 6 } } }

const updatedBets = lib.placeSixEight({ rules, bets, hand })

t.equal(updatedBets.place.six.amount, 6)
t.equal(updatedBets.place.eight.amount, 6)
t.notOk(updatedBets.new)

t.end()
})

tap.test('placeSixEight: place bets even when point is 6 or 8', (t) => {
const rules = { minBet: 6 }
const handSix = { isComeOut: false, point: 6 }

const firstBets = lib.placeSixEight({ rules, hand: handSix })

t.equal(firstBets.place.six.amount, rules.minBet)
t.equal(firstBets.place.eight.amount, rules.minBet)
t.equal(firstBets.new, rules.minBet * 2)

delete firstBets.new

const handEight = { isComeOut: false, point: 8 }
const bets = lib.placeSixEight({ rules, bets: firstBets, hand: handEight })

t.equal(bets.place.six.amount, rules.minBet)
t.equal(bets.place.eight.amount, rules.minBet)
t.notOk(bets.new)

t.end()
})
6 changes: 3 additions & 3 deletions hands.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
'use strict'

const { playHand } = require('./index.js')
const { minPassLineMaxOdds } = require('./betting.js')
const { placeSixEight } = require('./betting.js')

const numHands = parseInt(process.argv.slice(2)[0], 10)
const showDetail = process.argv.slice(2)[1]

console.log(`Simulating ${numHands} Craps Hand(s)`)
console.log('Using betting strategy: minPassLineMaxOdds')
console.log('Using betting strategy: placeSixEight')

const summaryTemplate = {
balance: 0,
Expand Down Expand Up @@ -51,7 +51,7 @@ const rules = {
console.log(`[table rules] minimum bet: $${rules.minBet}`)

for (let i = 0; i < numHands; i++) {
const hand = playHand({ rules, bettingStrategy: minPassLineMaxOdds })
const hand = playHand({ rules, bettingStrategy: placeSixEight })
hand.summary = Object.assign({}, summaryTemplate)

sessionSummary.balance += hand.balance
Expand Down
18 changes: 13 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ function rollD6 () {
return 1 + Math.floor(Math.random() * 6)
}

function shoot (before, dice) {
const defaultRules = {
comeOutLoss: [2, 3, 12],
comeOutWin: [7, 11]
}

function shoot (before, dice, rules = defaultRules) {
rules = Object.assign({}, defaultRules, rules)
const sortedDice = dice.sort()

const after = {
Expand All @@ -19,10 +25,10 @@ function shoot (before, dice) {
// game logic based on: https://github.com/tphummel/dice-collector/blob/master/PyTom/Dice/logic.py

if (before.isComeOut) {
if ([2, 3, 12].indexOf(after.diceSum) !== -1) {
if (rules.comeOutLoss.includes(after.diceSum)) {
after.result = 'comeout loss'
after.isComeOut = true
} else if ([7, 11].indexOf(after.diceSum) !== -1) {
} else if (rules.comeOutWin.includes(after.diceSum)) {
after.result = 'comeout win'
after.isComeOut = true
} else {
Expand Down Expand Up @@ -65,7 +71,8 @@ function playHand ({ rules, bettingStrategy, roll = rollD6 }) {

hand = shoot(
hand,
[roll(), roll()]
[roll(), roll()],
rules
)

if (process.env.DEBUG) console.log(`[roll] ${hand.result} (${hand.diceSum})`)
Expand All @@ -88,5 +95,6 @@ module.exports = {
rollD6,
shoot,
playHand,
betting
betting,
defaultRules
}
61 changes: 61 additions & 0 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,27 @@ tap.test('comeout', function (suite) {
suite.end()
})

tap.test('comeout with custom rules', (t) => {
const handState = {
isComeOut: true
}

const rules = {
comeOutLoss: [2, 3],
comeOutWin: [7, 11, 12]
}

const result = lib.shoot(handState, [6, 6], rules)
t.equal(result.result, 'comeout win')
t.notOk(result.point)
t.equal(result.die1, 6)
t.equal(result.die2, 6)
t.equal(result.diceSum, 12)
t.equal(result.isComeOut, true)

t.end()
})

tap.test('point set', (suite) => {
suite.test('neutral 2', (t) => {
const handState = {
Expand Down Expand Up @@ -479,3 +500,43 @@ tap.test('integration: minPassLineMaxOdds, one hand with everything', (suite) =>

suite.end()
})

tap.test('integration: minComeLineMaxOdds, one hand with come bets', (suite) => {
let rollCount = -1
const fixedRolls = [
4, 3, // comeout win
5, 6, // comeout win
2, 2, // comeout loss
3, 3, // point set
5, 4, // come bet sets point
5, 4, // come bet point win
3, 4 // seven out
]

function testRoll () {
rollCount++
if (!fixedRolls[rollCount]) {
console.log('falsy return from fixed dice')
process.exit(1)
}
return fixedRolls[rollCount]
}

const rules = {
minBet: 5,
maxOddsMultiple: {
4: 3,
5: 4,
6: 5,
8: 5,
9: 4,
10: 3
}
}

const hand = lib.playHand({ rules, roll: testRoll, bettingStrategy: betting.minComeLineMaxOdds })
suite.ok(Array.isArray(hand.history))
suite.type(hand.balance, 'number')

suite.end()
})
Loading