package casino import ( "fmt" "strings" ) // 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 } // helpTopic names the help page the "rules" command opens: every house // // edition has its own page (Simplified Craps is a two-screen quick guide, // not a link into the bank-craps manual), except diceless which plays full // bank rules with cards instead of dice. func (c CrapsConfig) helpTopic() string { switch c.variant() { case "simplified": return "simplified craps" case "high_point": return "high point craps" case "die_rich": return "die rich craps" case "new_york": return "new york craps" case "crapless": return "crapless craps" case "diceless": return "diceless craps" default: return "craps" } } 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" } } // Title is the human-readable edition name shown by the look command. func (c CrapsConfig) Title() string { return c.title() } 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": switch s { case 2: return true, 3, 1 case 3, 4: return true, 1, 1 case 10, 11: return true, 2, 1 case 12: return true, 5, 1 default: 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 of two dice. Payouts: 2 pays 3:1; 3/4 pay 1:1; 10/11 pay 2:1; 12 pays 5:1. Totals 5/6/7/8/9 lose. Winning bets also return their stake. No point, odds, come, place, side bets or multi-roll wagers. The dice roll automatically 3 seconds after the first bet; there is no manual roll. House edge: 1/36 (approximately 2.78%). 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 / place / buy / lay .\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 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." } cadence := "Solo roll anytime; multiplayer waits 10 seconds, then dealer rolls after 60 seconds." autoroll := "Bets stay open until the roll. autoroll repeats every 3 seconds, respecting the betting window." betLine := "Bet: bet on pass; table rules is authoritative for this edition." stopLine := "stop cancels autoroll; manual stop/sitout waits for live bets." if c.variant() == "simplified" { betLine = "Bet: bet — always on pass; bare bet rebets your last wager." cadence = "The dice roll automatically 3 seconds after the first bet." autoroll = "" stopLine = "stop/sitout waits for live bets." } lines := []string{c.title() + " — house rules", rules, betLine, fmt.Sprintf("Free odds: %d (0 = 3-4-5x on ordinary points, don't 6x laid). No odds on Simplified, High Point or Die Rich.", c.Odds), "Each wager obeys table limits and exact payout units. Wins return stake and remove the bet.", "Pass/come contracts cannot be removed; optional bets/odds can: unbet / unbet all.", cadence, stopLine + " Disconnect forfeits; shutdown refunds."} if autoroll != "" { lines = append(lines[:7], append([]string{autoroll}, lines[7:]...)...) } return strings.Join(lines, "\n") }