aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/craps_variants.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-30 16:16:17 -0400
committerhistoria <[not public]>2026-09-30 16:16:17 -0400
commita4c09c2d38d1f3bc1d3ad4bdb0234bdea7c37e73 (patch)
tree00c9567e620f331a59dd44f1f1fa779ecd94a845 /internal/casino/craps_variants.go
parent724079f8de1ae69965c6267dca3d7fc3cc86afa2 (diff)
downloadthehouseoficarus-a4c09c2d38d1f3bc1d3ad4bdb0234bdea7c37e73.tar.gz
feat: additional craps variants added, standardizard casino command hints
Diffstat (limited to 'internal/casino/craps_variants.go')
-rw-r--r--internal/casino/craps_variants.go184
1 files changed, 184 insertions, 0 deletions
diff --git a/internal/casino/craps_variants.go b/internal/casino/craps_variants.go
new file mode 100644
index 0000000..b3be51b
--- /dev/null
+++ b/internal/casino/craps_variants.go
@@ -0,0 +1,184 @@
+package casino
+
+import "fmt"
+
+// Variants are explicit house editions; regional games with the same names
+// need not use these rules. Random sources never affect wallet/queue handling.
+func (c CrapsConfig) variant() string {
+ if c.Variant == "" {
+ return "bank"
+ }
+ return c.Variant
+}
+func (c CrapsConfig) title() string {
+ switch c.variant() {
+ case "simplified":
+ return "Simplified Craps"
+ case "high_point":
+ return "High Point Craps"
+ case "crapless":
+ return "Crapless Craps"
+ case "new_york":
+ return "New York Craps"
+ case "die_rich":
+ return "Die Rich Craps"
+ case "diceless":
+ return "Diceless Craps"
+ default:
+ return "Bank Craps"
+ }
+}
+func (c CrapsConfig) allows(kind string) bool {
+ switch c.variant() {
+ case "simplified", "high_point", "die_rich":
+ return kind == "pass"
+ case "new_york":
+ return kind == "pass" || kind == "dontpass" || kind == "buy" || kind == "lay" || kind == "odds"
+ case "crapless":
+ return kind != "dontpass" && kind != "dontcome" && kind != "fire" && kind != "small" && kind != "tall" && kind != "all"
+ default:
+ return true
+ }
+}
+func (c CrapsConfig) pointNumber(n int) bool {
+ switch c.variant() {
+ case "simplified":
+ return false
+ case "high_point":
+ return n >= 4 && n <= 10
+ case "die_rich":
+ return n >= 2 && n <= 5
+ case "crapless":
+ return n >= 2 && n <= 12 && n != 7
+ default:
+ return crapsNumber(n)
+ }
+}
+func (c CrapsConfig) number(n int) bool {
+ return crapsNumber(n) || (c.variant() == "crapless" && n >= 2 && n <= 12 && n != 7)
+}
+func crapsPlaceOdds(n int) (int, int) {
+ switch n {
+ case 2, 12:
+ return 11, 2
+ case 3, 11:
+ return 11, 4
+ case 4, 10:
+ return 9, 5
+ case 5, 9:
+ return 7, 5
+ default:
+ return 7, 6
+ }
+}
+func (c CrapsConfig) outcome(kind string, n, a, b, point int) (bool, int, int) {
+ s := a + b
+ switch c.variant() {
+ case "simplified":
+ if s == 2 || s == 3 || s == 4 || s == 10 || s == 11 || s == 12 {
+ return true, 1, 1
+ }
+ return true, -1, 1
+ case "high_point":
+ if n == 0 {
+ if s == 2 || s == 3 {
+ return false, 0, 1
+ }
+ if s >= 11 {
+ return true, 1, 1
+ }
+ return false, 0, 1
+ }
+ if s > n {
+ return true, 1, 1
+ }
+ return true, -1, 1
+ case "die_rich":
+ if n == 0 {
+ if s == 6 {
+ return true, 1, 1
+ }
+ if s == 1 {
+ return true, -1, 1
+ }
+ return false, 0, 1
+ }
+ if s == n {
+ return true, 1, 1
+ }
+ if s == 1 {
+ return true, -1, 1
+ }
+ return false, 0, 1
+ case "crapless":
+ if (kind == "pass" || kind == "come") && n == 0 {
+ if s == 7 {
+ return true, 1, 1
+ }
+ return false, 0, 1
+ }
+ }
+ return crapsOutcome(kind, n, a, b, point, c.Field12)
+}
+
+// outcomeFor includes bonus progress without mutating it. Both previews and
+// actual settlement call this before recording the new roll.
+func (t *crapsTable) outcomeFor(bet *crapsBet, a, b int) (bool, int, int) {
+ s := a + b
+ switch bet.kind {
+ case "fire":
+ if t.point == 0 || s != 7 {
+ return false, 0, 1
+ }
+ x := crapsFireProfit(len(t.made))
+ if x == 0 {
+ x = -1
+ }
+ return true, x, 1
+ case "small", "tall", "all":
+ if s == 7 {
+ return true, -1, 1
+ }
+ lo, hi := 2, 12
+ if bet.kind == "small" {
+ hi = 6
+ }
+ if bet.kind == "tall" {
+ lo = 8
+ }
+ if !crapsBonusComplete(lo, hi, t.seen, s) {
+ return false, 0, 1
+ }
+ if bet.kind == "all" {
+ return true, 175, 1
+ }
+ return true, 34, 1
+ default:
+ return t.Config.Craps.outcome(bet.kind, bet.number, a, b, t.point)
+ }
+}
+func (c CrapsConfig) variantRulesText() string {
+ rules := ""
+ switch c.variant() {
+ case "simplified":
+ rules = "Pass only, one roll: 2/3/4/10/11/12 win 1:1; 5/6/7/8/9 lose. No point. A loss passes the dice."
+ case "high_point":
+ rules = "Pass only, 1:1. Come-out 2/3 roll again, 11/12 win; 4-10 establish a point. The very next roll wins only if HIGHER than the point; equal/lower loses. Loss passes the dice."
+ case "die_rich":
+ rules = "Pass only, ONE six-sided die, 1:1. Come-out 6 wins, 1 loses; 2-5 establish a point. Repeat the point before 1 to win; other faces roll again. Any 1 passes the die. No free odds."
+ case "new_york":
+ rules = "House New York: bank pass/dontpass (bar 12), free odds, buy and lay only. No come, place, field, propositions, hardways or bonuses. Buy/lay use true odds with 5% commission on buy stake / lay profit, rounded up, wins only. Buy is off on come-out; lay works."
+ case "crapless":
+ rules = "Come-out 7 wins; ALL other totals become points, including 2/3/11/12. Pass/come only, no don't bets or bonuses. All bank side bets supported. Extra-number place: 2/12 pay 11:2; 3/11 pay 11:4. Buy and free odds: 2/12 pay 6:1; 3/11 pay 3:1. Lay pays inverse true odds. Default extra-number odds limit: 1x stake."
+ }
+ if c.variant() == "new_york" || c.variant() == "crapless" {
+ rules += "\nTrue odds on 4/10: 2:1; 5/9: 3:2; 6/8: 6:5. Lay and don't odds invert these ratios. Buy/lay commission: 5% buy STAKE / lay PROFIT, rounded up, on wins only; removals have no commission."
+ }
+ if c.variant() == "crapless" {
+ rules += fmt.Sprintf("\nPass/come pay 1:1 on their point before 7; 7 loses. Line bets only on come-out; come requires a table point.\nTargets: pass / come / odds pass / odds come <number> / place <number> / buy <number> / lay <number>.\nPlace 4/10 pays 9:5; 5/9 7:5; 6/8 7:6.\nfield: 2 pays 2:1, 12 pays %d:1, 3/4/9/10/11 pay 1:1; others lose.\nhard 4/10 pays 7:1; hard 6/8 pays 9:1; easy number or 7 loses.\nseven 4:1; craps 7:1; eleven / ace-deuce 15:1; aces / boxcars 30:1.\nhorn splits equally among 2/3/11/12; hop <a> <b> pays 15:1 easy / 30:1 double, unordered.\nPlace/buy/hardways and come odds are off on come-out; established come contracts and lay always work.\nNo Fire or ATS. Making a point retains the shooter; seven-out rotates.", c.Field12)
+ }
+ if c.variant() == "new_york" {
+ rules += "\nPass come-out 7/11 wins, 2/3/12 loses; 4/5/6/8/9/10 establish a point. Make it before 7 to win, all 1:1. Don't pass is opposite, except come-out 12 pushes. Line bets only on come-out. Making a point retains the shooter; seven-out rotates."
+ }
+ return fmt.Sprintf("%s — house rules\n%s\nBet: bet <amount> on pass; table rules is authoritative for this edition.\nFree odds: %d (0 = 3-4-5x on ordinary points, don't 6x laid). No odds on Simplified, High Point or Die Rich.\nEach wager obeys table limits and exact payout units. Wins return stake and remove the bet.\nPass/come contracts cannot be removed; optional bets/odds can: unbet <target> / unbet all.\nSolo roll anytime; multiplayer waits 10 seconds, then dealer rolls after 60 seconds.\nBets stay open until the roll. autoroll repeats every 3 seconds, respecting the betting window.\nstop cancels autoroll; manual stop/sitout waits for live bets. Disconnect forfeits; shutdown refunds.", c.title(), rules, c.Odds)
+}