aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/craps_variants.go
blob: e64fd0f18d86c2b5d6c95738e9c0a09a1f4dd933 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
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":
		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. Net 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. 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 <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)
}