package casino import "fmt" // CrapsConfig selects explicit house rules and the shared wager limits. type CrapsConfig struct { Odds int `yaml:"odds,omitempty"` // 0 = 3-4-5x; positive = uniform Nx Field12 int `yaml:"field_12,omitempty"` Variant string `yaml:"variant,omitempty"` } func (c CrapsConfig) Normalize() CrapsConfig { if c.Field12 == 0 { c.Field12 = 2 } return c } func validateCraps(c TableConfig) error { if c.Craps.Odds < 0 || c.Craps.Odds > 1000000 { return fmt.Errorf("craps odds must be 0 (3-4-5x) or 1..1000000") } if c.Craps.Field12 != 0 && c.Craps.Field12 != 2 && c.Craps.Field12 != 3 { return fmt.Errorf("craps field_12 must be 2 or 3") } switch c.Craps.variant() { case "bank", "simplified", "high_point", "crapless", "new_york", "die_rich", "diceless": default: return fmt.Errorf("unknown craps variant %q", c.Craps.Variant) } if !c.Craps.allows("odds") && c.Craps.Odds != 0 { return fmt.Errorf("this craps variant does not support odds") } return nil } func crapsNumber(n int) bool { return n == 4 || n == 5 || n == 6 || n == 8 || n == 9 || n == 10 } func crapsTrueOdds(n int) (int, int) { switch n { case 2, 12: return 6, 1 case 3, 11: return 3, 1 case 4, 10: return 2, 1 case 5, 9: return 3, 2 default: return 6, 5 } } func (c CrapsConfig) oddsLimit(n int, against bool) int { if c.Odds > 0 { return c.Odds } if c.variant() == "crapless" && !crapsNumber(n) { return 1 } if against { return 6 } switch n { case 4, 10: return 3 case 5, 9: return 4 default: return 5 } } func (c TableConfig) CrapsRulesText() string { r := c.Craps.Normalize() if r.variant() != "bank" && r.variant() != "diceless" { return r.variantRulesText() } prefix := "" if r.variant() == "diceless" { prefix = "Diceless Craps — bank rules using cards, not dice. Two independent six-card packs labelled 1-6; draw one from each, replace and reshuffle BOTH after EVERY roll. All 36 ordered pairs are equally likely. No finite shoe or card counting.\n" } odds := "3-4-5x (don't: 6x laid)" if r.Odds > 0 { odds = fmt.Sprintf("%dx", r.Odds) } return prefix + fmt.Sprintf("Bank Craps | Free odds %s | Field 12 pays %d:1\n"+ "bet on pass / dontpass / come / dontcome / field\n"+ "bet on place 6 / buy 4 / lay 10 / hard 8 / hop 2 3\n"+ "bet on seven / craps / eleven / ace-deuce / aces / boxcars / horn\n"+ "bet on odds pass / odds come 6 (also dontpass/dontcome)\n"+ "bet on fire / small / tall / all (before shooter's first roll only)\n"+ "Pass/come 1:1; don't bars 12. Place 4/10 9:5, 5/9 7:5, 6/8 7:6.\n"+ "Buy/lay true odds; 5%% commission on buy STAKE / lay WIN, rounded up, wins only.\n"+ "Field 2 pays 2:1; 3/4/9/10/11 pay 1:1. Hard 4/10 7:1; 6/8 9:1.\n"+ "Seven 4:1; craps 7:1; 11/3 15:1; 2/12 30:1; hop easy 15:1, hard 30:1.\n"+ "Horn splits equally across 2/3/11/12. Fire: 4/5/6 distinct made points pay 24/249/999:1.\n"+ "Small (2-6) / Tall (8-12) pay 34:1; All pays 175:1. Any 7 resets ATS.\n"+ "Place/buy/hard and come odds are OFF on come-out; lay and don't odds work.\n"+ "Exact payout units required; no fractional-credit rounding except commission.\n"+ "Solo: roll anytime. Multiplayer: 10 seconds to bet, then 60 seconds for the shooter.\n"+ "Bets remain open until the roll. Dice rotate on seven-out.\n"+ "autoroll: repeat every 3 seconds on your turn (multiplayer waits 10 seconds).\n"+ "unbet: remove every non-contract bet and all free odds; pass/come contracts stay.\n"+ "stop cancels autoroll without leaving; in manual mode stop/sitout wait for bets.\n"+ "Disconnect forfeits live bets.\n"+ "Winning bets return stake and are removed (no automatic pressing).", odds, r.Field12) } // outcome returns resolved and profit numerator/denominator; -1 means loss. func crapsOutcome(kind string, n, a, b, point, field12 int) (bool, int, int) { s := a + b hard := a == b switch kind { case "pass", "come", "dontpass", "dontcome": against := kind == "dontpass" || kind == "dontcome" if n == 0 { if against { switch s { case 2, 3: return true, 1, 1 case 7, 11: return true, -1, 1 case 12: return true, 0, 1 } } else { switch s { case 7, 11: return true, 1, 1 case 2, 3, 12: return true, -1, 1 } } return false, 0, 1 } if s != 7 && s != n { return false, 0, 1 } if (s == n) != against { return true, 1, 1 } return true, -1, 1 case "place", "buy", "lay": if point == 0 && kind != "lay" { return false, 0, 1 } if s != 7 && s != n { return false, 0, 1 } if (s == 7) != (kind == "lay") { return true, -1, 1 } x, y := crapsTrueOdds(n) if kind == "lay" { x, y = y, x } if kind == "place" { x, y = crapsPlaceOdds(n) } return true, x, y case "hard": if point == 0 { return false, 0, 1 } if s == 7 || (s == n && !hard) { return true, -1, 1 } if s == n { if n == 4 || n == 10 { return true, 7, 1 } return true, 9, 1 } return false, 0, 1 case "field": switch s { case 2: return true, 2, 1 case 12: return true, field12, 1 case 3, 4, 9, 10, 11: return true, 1, 1 } return true, -1, 1 case "seven": if s == 7 { return true, 4, 1 } case "craps": if s == 2 || s == 3 || s == 12 { return true, 7, 1 } case "eleven", "ace-deuce": if (kind == "eleven" && s == 11) || (kind == "ace-deuce" && s == 3) { return true, 15, 1 } case "aces", "boxcars": if (kind == "aces" && s == 2) || (kind == "boxcars" && s == 12) { return true, 30, 1 } case "hop": if a*10+b == n || b*10+a == n { if hard { return true, 30, 1 } return true, 15, 1 } case "horn": switch s { case 2, 12: return true, 27, 4 case 3, 11: return true, 3, 1 } default: return false, 0, 1 } return true, -1, 1 }