aboutsummaryrefslogtreecommitdiff
path: root/internal/casino
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-03 17:36:06 -0400
committerhistoria <[not public]>2026-10-03 17:36:06 -0400
commitc6d4702c0be4d2b37e1d82ff96265c52847c3f9a (patch)
treec0a3d0638b961a9ba06e6fdb30ee5f1cd2061f2e /internal/casino
parent0fb83bdf433b9b48240f4ac7a5d4c912f6b808cc (diff)
downloadthehouseoficarus-c6d4702c0be4d2b37e1d82ff96265c52847c3f9a.tar.gz
feat: partial commands now always work
Diffstat (limited to 'internal/casino')
-rw-r--r--internal/casino/big_six.go43
-rw-r--r--internal/casino/big_six_test.go46
2 files changed, 85 insertions, 4 deletions
diff --git a/internal/casino/big_six.go b/internal/casino/big_six.go
index 6d16bbc..b17bde4 100644
--- a/internal/casino/big_six.go
+++ b/internal/casino/big_six.go
@@ -34,9 +34,43 @@ func bigSixHouseEdge(_ TableConfig, bet string) HouseEdge {
func bigSixTarget(s string) string {
return strings.TrimPrefix(strings.ToUpper(strings.TrimSpace(s)), "$")
}
+
+// bigSixResolveTarget maps a written target to a betting symbol. An exact
+// label always wins over a longer label that shares its prefix, so "1" is the
+// $1 symbol and never an ambiguous prefix of $10. Otherwise an unambiguous
+// prefix is accepted, so "SW", "SWO" and "SWORD" all name the sword and "SH"
+// names the shield. DOUBLE is not a betting target, so "DO" resolves to
+// nothing. It reports false when nothing or more than one symbol matches.
+func bigSixResolveTarget(s string) (string, bool) {
+ t := bigSixTarget(s)
+ if t == "" {
+ return "", false
+ }
+ match := ""
+ for _, spot := range BigSixSpots() {
+ if spot.Pays <= 0 {
+ continue
+ }
+ if spot.Label == t {
+ return spot.Label, true
+ }
+ if strings.HasPrefix(spot.Label, t) {
+ if match != "" {
+ return "", false
+ }
+ match = spot.Label
+ }
+ }
+ return match, match != ""
+}
+
func bigSixPayout(s string) int {
+ target, ok := bigSixResolveTarget(s)
+ if !ok {
+ return 0
+ }
for _, spot := range BigSixSpots() {
- if spot.Label == bigSixTarget(s) && spot.Pays > 0 {
+ if spot.Label == target {
return spot.Pays + 1
}
}
@@ -225,10 +259,11 @@ func (t *bigSixTable) bet(name, spot string, amount int, w Wallet) []Event {
if spot == "" {
spot = p.lastSpot
}
- spot = bigSixTarget(spot)
- if bigSixPayout(spot) == 0 {
- return []Event{t.private(name, "Bet on 1, 2, 5, 10, 20, SWORD or SHIELD. DOUBLE is automatic, not a bet.")}
+ resolved, ok := bigSixResolveTarget(spot)
+ if !ok {
+ return []Event{t.private(name, "Bet on 1, 2, 5, 10, 20, SWORD or SHIELD — an unambiguous prefix is fine. DOUBLE is automatic, not a bet.")}
}
+ spot = resolved
if amount < t.Config.MinBet || amount > t.Config.MaxBet {
return []Event{t.private(name, "Big 6 bets must be "+betRangeText(t.Config.MinBet, t.Config.MaxBet)+".")}
}
diff --git a/internal/casino/big_six_test.go b/internal/casino/big_six_test.go
index cccad77..04bc7e3 100644
--- a/internal/casino/big_six_test.go
+++ b/internal/casino/big_six_test.go
@@ -291,3 +291,49 @@ func TestBigSixSettlementFailureAndDeparture(t *testing.T) {
t.Fatal("departing player lost result visibility")
}
}
+
+func TestBigSixPartialTargets(t *testing.T) {
+ for _, tc := range []struct {
+ in string
+ want string
+ ok bool
+ }{
+ {"1", "1", true},
+ {"10", "10", true},
+ {"$20", "20", true},
+ {"sw", "SWORD", true},
+ {"SWO", "SWORD", true},
+ {"sword", "SWORD", true},
+ {"sh", "SHIELD", true},
+ {"shi", "SHIELD", true},
+ {"shield", "SHIELD", true},
+ {"s", "", false}, // SWORD or SHIELD
+ {"do", "", false}, // DOUBLE is automatic, not a wager
+ {"double", "", false}, // and its full name is not a wager either
+ {"", "", false},
+ {"99", "", false},
+ } {
+ got, ok := bigSixResolveTarget(tc.in)
+ if got != tc.want || ok != tc.ok {
+ t.Fatalf("bigSixResolveTarget(%q) = (%q,%v), want (%q,%v)", tc.in, got, ok, tc.want, tc.ok)
+ }
+ }
+ // A partial target must behave exactly like the full label at bet time,
+ // both in the stored wager and in the house-edge reference.
+ cfg := TableConfig{Game: GameBigSix}
+ if e := cfg.HouseEdge("sw"); len(e.Parts) != 1 || e.Parts[0].Loss != 1.0/49 {
+ t.Fatalf("partial house edge: %+v", e)
+ }
+ table := newBigSixTest()
+ table.join("a")
+ w := &recordingWallet{testWallet: testWallet{credits: 100}}
+ table.bet("a", "sw", 5, w)
+ table.bet("a", "sh", 7, w)
+ bets := table.players.get("a").bets
+ if len(w.open) != 2 || len(bets) != 2 || bets[0].spot != "SWORD" || bets[1].spot != "SHIELD" {
+ t.Fatalf("partial targets not canonicalized: %+v", bets)
+ }
+ if bigSixPayout("s") != 0 || bigSixPayout("sw") != 48 || bigSixPayout("sh") != 48 {
+ t.Fatal("partial payout")
+ }
+}