aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/craps.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/casino/craps.go')
-rw-r--r--internal/casino/craps.go53
1 files changed, 40 insertions, 13 deletions
diff --git a/internal/casino/craps.go b/internal/casino/craps.go
index 286fe0a..90c95c9 100644
--- a/internal/casino/craps.go
+++ b/internal/casino/craps.go
@@ -57,6 +57,7 @@ type CrapsSnapshot struct {
AutoRoll bool
AutoIn int
Field12 int
+ Odds string // free-odds cap label, e.g. "3x-4x-5x" or "100x"
}
func (*CrapsSnapshot) snapshot() {}
@@ -73,7 +74,7 @@ func crapsTarget(b *crapsBet) string {
return fmt.Sprintf("%s %d", b.kind, b.number)
}
func (t *crapsTable) view() *CrapsSnapshot {
- s := &CrapsSnapshot{Variant: t.Config.Craps.variant(), Phase: string(t.phase), Point: t.point, Dice: t.dice, Queue: append([]string(nil), t.queue...), Field12: t.Config.Craps.Field12}
+ s := &CrapsSnapshot{Variant: t.Config.Craps.variant(), Phase: string(t.phase), Point: t.point, Dice: t.dice, Queue: append([]string(nil), t.queue...), Field12: t.Config.Craps.Field12, Odds: t.Config.Craps.oddsDisplay()}
if t.hasLiveBets() {
if t.Config.Craps.variant() == "simplified" {
// One automatic roll: the snapshot counts down the shared window.
@@ -246,9 +247,11 @@ func (t *crapsTable) bet(name, spot string, amount int, w Wallet) []Event {
if b.odds.Total > 0 {
continue
}
+ requested := amount
+ amount = roundUpTo(amount, payoutDen)
// Compare without multiplying a potentially very large stake by Nx.
- if (amount-1)/limit >= b.stake.Total || amount%payoutDen != 0 {
- return reject("Odds already placed, above the free-odds limit, or not in exact payout units.")
+ if (amount-1)/limit >= b.stake.Total {
+ return reject("Odds already placed or above the free-odds limit.")
}
stake, err := w.Wager(amount)
if err != nil {
@@ -257,7 +260,11 @@ func (t *crapsTable) bet(name, spot string, amount int, w Wallet) []Event {
b.odds = stake
p.Wallet = w
p.lastSpot, p.lastBet = spot, amount
- return []Event{t.private(name, fmt.Sprintf("You add {casino_chips}%d{/} free odds on %s.", amount, target)), t.betSummary(p), t.reminder(name)}
+ msg := fmt.Sprintf("You add {casino_chips}%d{/} free odds on %s.", amount, target)
+ if amount != requested {
+ msg += fmt.Sprintf(" {casino_emphasis}Rounded up from %d for exact odds units.{/}", requested)
+ }
+ return []Event{t.private(name, msg), t.betSummary(p), t.reminder(name)}
}
return reject("Place an established line/come bet before taking odds.")
}
@@ -283,15 +290,19 @@ func (t *crapsTable) bet(name, spot string, amount int, w Wallet) []Event {
switch kind {
case "place":
_, unit = crapsPlaceOdds(n)
- case "buy":
- _, unit = crapsTrueOdds(n)
- case "lay":
- unit, _ = crapsTrueOdds(n)
+ case "buy", "lay":
+ // Weird odds must land on whole-credit payouts and a whole-credit vig.
+ unit = crapsVigUnit(kind, n)
case "horn":
unit = 4
}
- if amount%unit != 0 {
- return reject(fmt.Sprintf("Use multiples of %d for exact payouts.", unit))
+ requested := amount
+ amount = roundUpTo(amount, unit)
+ if amount > t.Config.MaxBet {
+ return reject(fmt.Sprintf("That wager rounds up to %d, above the table maximum.", amount))
+ }
+ if _, err := MulPayout(amount, 1000); err != nil {
+ return reject("That potential payout is too large.")
}
stake, err := w.Wager(amount)
if err != nil {
@@ -302,7 +313,11 @@ func (t *crapsTable) bet(name, spot string, amount int, w Wallet) []Event {
p.HasBet = true
p.SittingOut = false
p.lastSpot, p.lastBet = spot, amount
- events := []Event{t.playerAction(name, fmt.Sprintf("%s bets %d on %s.", name, amount, spot), fmt.Sprintf("You bet {casino_chips}%d{/} on %s.", amount, spot))}
+ private := fmt.Sprintf("You bet {casino_chips}%d{/} on %s.", amount, spot)
+ if amount != requested {
+ private += fmt.Sprintf(" {casino_emphasis}Rounded up from %d for exact payout units.{/}", requested)
+ }
+ events := []Event{t.playerAction(name, fmt.Sprintf("%s bets %d on %s.", name, amount, spot), private)}
events = append(events, t.syncRoundClock()...)
return append(events, t.betSummary(p), t.reminder(name))
}
@@ -348,7 +363,9 @@ func (t *crapsTable) resolveRoll(a, b int) []Event {
if len(t.queue) > 0 {
shooter = t.queue[0]
}
- events := []Event{t.event(fmt.Sprintf("{casino_emphasis}%s shoots{/} {casino_payout}%d{/}", shooter, s), true)}
+ // The dice tile already shows the total, so the roll leads with the felt
+ // itself. Diceless has no dice tile to carry the cards, so it keeps its line.
+ events := []Event{t.event("", true)}
if r.variant() == "diceless" {
events[0].Message = fmt.Sprintf("{casino_emphasis}%s draws cards{/} [%d] [%d] = %d", shooter, a, b, s)
}
@@ -393,7 +410,17 @@ func (t *crapsTable) resolveRoll(a, b int) []Event {
}
if oldPoint == 0 && r.pointNumber(s) {
t.point = s
- events = append(events, t.event(fmt.Sprintf("{casino_emphasis}POINT %d{/} / Puck ON. Make your point!", s), true))
+ events = append(events, t.event(fmt.Sprintf("{casino_emphasis}POINT %d{/} / Puck ON.", s), true))
+ // "Make your point!" only speaks to the shooter and players with a
+ // pass-line contract; everyone else just sees the puck move.
+ for _, p := range t.players.ordered() {
+ if !p.Connected || p.SittingOut {
+ continue
+ }
+ if p.Name == shooter || crapsHasPassLine(p) {
+ events = append(events, t.private(p.Name, "{casino_emphasis}Make your point!{/}"))
+ }
+ }
} else if pointMade || sevenOut {
if pointMade {
events = append(events, t.event(fmt.Sprintf("{casino_result_win}POINT %d MADE!{/} / Puck OFF. Same shooter, coming out.", oldPoint), true))