aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/craps_projection.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/casino/craps_projection.go')
-rw-r--r--internal/casino/craps_projection.go141
1 files changed, 41 insertions, 100 deletions
diff --git a/internal/casino/craps_projection.go b/internal/casino/craps_projection.go
index 5be2f28..96d64b7 100644
--- a/internal/casino/craps_projection.go
+++ b/internal/casino/craps_projection.go
@@ -6,22 +6,17 @@ import (
"strings"
)
-// crapsReturn is the pure payout math shared by settlement and by the
-// player-facing "what could win on the next roll" projection, so the two can
-// never disagree. It mirrors the money path of crapsTable.pay exactly.
+// crapsReturn is shared by wallet settlement and next-roll previews.
func crapsReturn(b *crapsBet, profit, den, oddsProfit int, refundOdds bool) int {
payout := 0
if profit >= 0 {
payout = b.stake.Total + b.stake.Total/den*profit
- if b.kind == "buy" || b.kind == "lay" {
+ if (b.kind == "buy" || b.kind == "lay") && profit > 0 {
base := payout - b.stake.Total
if b.kind == "buy" {
base = b.stake.Total
}
- // Removals (zero profit) return the full stake without commission.
- if profit > 0 {
- payout -= (base + 19) / 20
- }
+ payout -= (base + 19) / 20
}
}
if b.odds.Total > 0 {
@@ -37,69 +32,50 @@ func crapsReturn(b *crapsBet, profit, den, oddsProfit int, refundOdds bool) int
}
return payout
}
-
-// crapsNet returns settlement profit: returns (stake included) less commit.
func crapsNet(b *crapsBet, profit, den, oddsProfit int, refundOdds bool) int {
- return crapsReturn(b, profit, den, oddsProfit, refundOdds) - (b.stake.Total + b.odds.Total)
+ return crapsReturn(b, profit, den, oddsProfit, refundOdds) - b.stake.Total - b.odds.Total
}
-
-// crapsRefundOdds mirrors come bets' come-out refundOdds policy.
func crapsRefundOdds(b *crapsBet, point int) bool { return point == 0 && b.kind == "come" }
-// crapsRollView is one possible dice pair: its total, the pair shown, and the
-// player's combined settlement profit if it rolled next.
type CrapsRollView struct {
Total int
Rolls string
Net int
}
-// projectionFor evaluates one player's live bets as if the listed pair rolled
-// with the current table point, via the same rules engine used for settlement.
+func (t *crapsTable) rollPairs() [][2]int {
+ var pairs [][2]int
+ for a := 1; a <= 6; a++ {
+ if t.Config.Craps.variant() == "die_rich" {
+ pairs = append(pairs, [2]int{a, 0})
+ continue
+ }
+ for b := a; b <= 6; b++ {
+ pairs = append(pairs, [2]int{a, b})
+ }
+ }
+ return pairs
+}
func (t *crapsTable) projectionFor(p *crapsPlayer) []CrapsRollView {
var rows []CrapsRollView
- for total := 2; total <= 12; total++ {
- for a := 1; a <= 6; a++ {
- b := total - a
- if b < 1 || b > 6 || a > b {
- continue
- }
- net := 0
- for _, bet := range p.bets {
- done, x, y := crapsOutcome(bet.kind, bet.number, a, b, t.point, t.Config.Craps.Field12)
- if !done {
- // An unresolved contract keeps its stake; only bonuses
- // can complete within a single non-settling roll.
- if bet.kind == "fire" {
- x = crapsFireProfit(len(t.made))
- } else if bet.kind == "small" || bet.kind == "tall" || bet.kind == "all" {
- lo, hi := 2, 12
- if bet.kind == "small" {
- hi = 6
- }
- if bet.kind == "tall" {
- lo = 8
- }
- if crapsBonusComplete(lo, hi, t.seen, a+b) {
- x = 34
- if bet.kind == "all" {
- x = 175
- }
- } else {
- continue
- }
- } else {
- continue
- }
- }
+ for _, pair := range t.rollPairs() {
+ a, b := pair[0], pair[1]
+ net := 0
+ for _, bet := range p.bets {
+ done, x, y := t.outcomeFor(bet, a, b)
+ if done {
net += crapsNet(bet, x, y, x, crapsRefundOdds(bet, t.point))
}
- rows = append(rows, CrapsRollView{total, fmt.Sprintf("%d+%d", a, b), net})
}
+ label := fmt.Sprintf("%d+%d", a, b)
+ if b == 0 {
+ label = fmt.Sprint(a)
+ }
+ rows = append(rows, CrapsRollView{a + b, label, net})
}
+ sort.SliceStable(rows, func(i, j int) bool { return rows[i].Total < rows[j].Total })
return rows
}
-
func crapsFireProfit(made int) int {
switch made {
case 4:
@@ -111,7 +87,6 @@ func crapsFireProfit(made int) int {
}
return 0
}
-
func crapsBonusComplete(lo, hi int, seen map[int]bool, next int) bool {
for n := lo; n <= hi; n++ {
if n != 7 && !seen[n] && n != next {
@@ -120,9 +95,6 @@ func crapsBonusComplete(lo, hi int, seen map[int]bool, next int) bool {
}
return true
}
-
-// crapsRollLabel names the roll result a bet cares about: hardways and hop
-// bets distinguish their exact pair ("4+4"); every other bet reads by total.
func crapsRollLabel(bet *crapsBet, a, b int) string {
s := a + b
switch bet.kind {
@@ -142,59 +114,28 @@ func crapsRollLabel(bet *crapsBet, a, b int) string {
}
return fmt.Sprint(s)
}
-
-// rollTable lists each roll result and its combined payout across the
-// player's bets, one row per roll. Dice-distinguishing bets (hardways, hop)
-// name their pair; everything else reads by total. Pairs that share a
-// label and outcome collapse; across bets, same-roll payouts add. Tags
-// stay single-level: the tag expander does not nest.
func (t *crapsTable) rollTable(p *crapsPlayer) string {
agg := map[string]int{}
+ pairsMatter := false
for _, bet := range p.bets {
- perLabel := map[string]int{}
- for a := 1; a <= 6; a++ {
- for b := a; b <= 6; b++ {
- done, x, y := crapsOutcome(bet.kind, bet.number, a, b, t.point, t.Config.Craps.Field12)
- if !done {
- // Unresolved contracts stay put; only bonuses can
- // complete within a single non-settling roll.
- switch bet.kind {
- case "fire":
- x = crapsFireProfit(len(t.made))
- case "small", "tall", "all":
- lo, hi := 2, 12
- if bet.kind == "small" {
- hi = 6
- }
- if bet.kind == "tall" {
- lo = 8
- }
- if !crapsBonusComplete(lo, hi, t.seen, a+b) {
- continue
- }
- x = 34
- if bet.kind == "all" {
- x = 175
- }
- default:
- continue
- }
- }
- net := crapsNet(bet, x, y, x, crapsRefundOdds(bet, t.point))
- if net != 0 {
- perLabel[crapsRollLabel(bet, a, b)] = net
- }
- }
+ if bet.kind == "hard" || bet.kind == "hop" {
+ pairsMatter = true
+ }
+ }
+ for _, row := range t.projectionFor(p) {
+ label := fmt.Sprint(row.Total)
+ if pairsMatter {
+ label = row.Rolls
}
- for label, net := range perLabel {
- agg[label] += net
+ if row.Net != 0 {
+ agg[label] = row.Net
}
}
if len(agg) == 0 {
if len(p.bets) == 0 {
return "No open wagers."
}
- return "{casino_action}Your place, buy and hardway bets are OFF on the come-out — nothing pays until a point is set.{/}"
+ return "{casino_action}No wager settles on the next roll; contracts remain live. Place/buy/hardways are OFF on the come-out.{/}"
}
labels := []string{}
for label := range agg {