aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-24 19:31:05 -0400
committerhistoria <[not public]>2026-09-24 19:31:05 -0400
commitd114c747a7672c7e4daab3a5b5f1b2f066ebc912 (patch)
tree81cc03354561f2f631d68f0de28bc742b1f49114 /internal/game
parentf606db40c3290fd9ce40b53ef9bbe135b1e6f6e2 (diff)
downloadthehouseoficarus-d114c747a7672c7e4daab3a5b5f1b2f066ebc912.tar.gz
feat: additional baca and bj rules variants
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/baccarat_rules_test.go84
-rw-r--r--internal/game/blackjack_rules.go46
-rw-r--r--internal/game/blackjack_rules_test.go41
-rw-r--r--internal/game/look_target.go4
4 files changed, 175 insertions, 0 deletions
diff --git a/internal/game/baccarat_rules_test.go b/internal/game/baccarat_rules_test.go
new file mode 100644
index 0000000..14328c1
--- /dev/null
+++ b/internal/game/baccarat_rules_test.go
@@ -0,0 +1,84 @@
+package game
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "thehouseoficarus/internal/casino"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
+)
+
+func TestLookBaccaratFixtureWithoutScenery(t *testing.T) {
+ dir := t.TempDir()
+ if err := os.MkdirAll(filepath.Join(dir, "rooms"), 0755); err != nil {
+ t.Fatal(err)
+ }
+ room := "name: Anywhere\ncasino_tables:\n- id: test\n game: baccarat\n baccarat:\n variant: ez_baccarat\n decks: 6\n dragon_7: true\n"
+ if err := os.WriteFile(filepath.Join(dir, "rooms", "1.yaml"), []byte(room), 0644); err != nil {
+ t.Fatal(err)
+ }
+ g := &Game{Deps: Deps{World: world.New(dir)}, Casino: casino.NewManager(1)}
+ conn := &casinoTestConn{}
+ sess := &net.Session{Player: player.New("alice"), Conn: conn}
+ sess.Player.RoomID = 1
+ for _, target := range []string{"baccarat", "baccarat table", "table", "test", "test table"} {
+ conn.data = nil
+ g.doLookTarget(sess, target)
+ for _, want := range []string{"Variant: EZ Baccarat", "Number of decks: 6", "Dragon 7 (dragon_7): Enabled; 40:1", "Panda 8 (panda_8): Not offered"} {
+ if !strings.Contains(string(conn.data), want) {
+ t.Fatalf("look %s missing %q: %s", target, want, conn.data)
+ }
+ }
+ }
+ if g.lookBaccaratTable(sess, "other") {
+ t.Fatal("matched unrelated target")
+ }
+}
+
+func TestCasinoFloorBaccaratProfiles(t *testing.T) {
+ g := &Game{Deps: Deps{World: world.New("../../data")}, Casino: casino.NewManager(1)}
+ cases := []struct {
+ room int
+ variant string
+ decks int
+ tie float64
+ spot string
+ }{
+ {6127, casino.BaccaratCommission, 8, 8, "player_pair"},
+ {6128, casino.BaccaratSuper6, 6, 9, "super_6"},
+ {6129, casino.BaccaratEZ, 8, 8, "dragon_7"},
+ }
+ for _, tc := range cases {
+ table, ok := g.casinoTable(tc.room, "baccarat")
+ if !ok {
+ t.Fatalf("room %d: table unavailable", tc.room)
+ }
+ cfg := table.Config
+ if cfg.Baccarat.Variant != tc.variant || cfg.Baccarat.Decks != tc.decks || cfg.Baccarat.TiePayout != tc.tie {
+ t.Fatalf("room %d: %+v", tc.room, cfg.Baccarat)
+ }
+ if err := cfg.Validate(); err != nil {
+ t.Fatal(err)
+ }
+ events := g.Casino.Join(tc.room, cfg, "alice")
+ found := false
+ for _, e := range events {
+ if e.Message == cfg.BaccaratRulesText() {
+ found = true
+ }
+ }
+ if !found {
+ t.Fatalf("room %d: join missing rule card", tc.room)
+ }
+ if !strings.Contains(cfg.BaccaratRulesText(), tc.spot+"): Enabled") {
+ t.Fatalf("room %d: missing optional bet", tc.room)
+ }
+ // Each fixture gets a fresh manager so participation ownership does not
+ // prevent the same test player from joining the next table.
+ g.Casino = casino.NewManager(1)
+ }
+}
diff --git a/internal/game/blackjack_rules.go b/internal/game/blackjack_rules.go
new file mode 100644
index 0000000..61aab47
--- /dev/null
+++ b/internal/game/blackjack_rules.go
@@ -0,0 +1,46 @@
+package game
+
+import (
+ "strings"
+
+ "thehouseoficarus/internal/casino"
+ "thehouseoficarus/internal/net"
+)
+
+// Tables are room fixtures in their own right, not authored scenery objects.
+func (g *Game) lookBlackjackTable(sess *net.Session, target string) bool {
+ return g.lookCardTable(sess, target, casino.GameBlackjack)
+}
+
+func (g *Game) lookBaccaratTable(sess *net.Session, target string) bool {
+ return g.lookCardTable(sess, target, casino.GameBaccarat)
+}
+
+func (g *Game) lookCardTable(sess *net.Session, target string, game casino.GameName) bool {
+ room, err := g.World.LoadRoom(sess.Player.RoomID)
+ if err != nil {
+ return false
+ }
+ target = strings.TrimSpace(strings.ToLower(target))
+ for _, cfg := range room.CasinoTables {
+ if cfg.Game != game {
+ continue
+ }
+ id := strings.ToLower(cfg.ID)
+ if target != string(game) && target != string(game)+" table" && target != id && target != id+" table" && !(target == "table" && len(room.CasinoTables) == 1) {
+ continue
+ }
+ // Use the active configuration: live edits may be pending until a round ends.
+ table, ok := g.casinoTable(room.ID, string(cfg.Game))
+ if !ok {
+ return false
+ }
+ if game == casino.GameBaccarat {
+ sess.WriteLine(table.Config.BaccaratRulesText())
+ } else {
+ sess.WriteLine(table.Config.BlackjackRulesText())
+ }
+ return true
+ }
+ return false
+}
diff --git a/internal/game/blackjack_rules_test.go b/internal/game/blackjack_rules_test.go
new file mode 100644
index 0000000..33598c3
--- /dev/null
+++ b/internal/game/blackjack_rules_test.go
@@ -0,0 +1,41 @@
+package game
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "thehouseoficarus/internal/casino"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
+)
+
+func TestLookBlackjackFixtureWithoutScenery(t *testing.T) {
+ dir := t.TempDir()
+ if err := os.MkdirAll(filepath.Join(dir, "rooms"), 0755); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(filepath.Join(dir, "rooms", "1.yaml"), []byte("name: Anywhere\ncasino_tables:\n- id: test\n game: blackjack\n blackjack:\n decks: 1\n blackjack_payout: 1.2\n"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ g := &Game{Deps: Deps{World: world.New(dir)}, Casino: casino.NewManager(1)}
+ conn := &casinoTestConn{}
+ sess := &net.Session{Player: player.New("alice"), Conn: conn}
+ sess.Player.RoomID = 1
+ for _, target := range []string{"blackjack", "blackjack table", "table", "test", "test table"} {
+ conn.data = nil
+ if !g.lookBlackjackTable(sess, target) {
+ t.Fatalf("did not match %q", target)
+ }
+ for _, want := range []string{"Number of decks: 1", "Blackjack pays 6:5", "Double after split: Yes"} {
+ if !strings.Contains(string(conn.data), want) {
+ t.Fatalf("look %s missing %q: %s", target, want, conn.data)
+ }
+ }
+ }
+ if g.lookBlackjackTable(sess, "other") {
+ t.Fatal("matched unrelated target")
+ }
+}
diff --git a/internal/game/look_target.go b/internal/game/look_target.go
index 49a2ec7..76836d6 100644
--- a/internal/game/look_target.go
+++ b/internal/game/look_target.go
@@ -47,6 +47,10 @@ func (g *Game) doLookTarget(sess *net.Session, input string) {
return
}
+ if g.lookBlackjackTable(sess, lower) || g.lookBaccaratTable(sess, lower) {
+ return
+ }
+
var best *world.MobInstance
bestQ := world.MatchNone
for _, m := range g.MobStore.MobsInRoom(p.RoomID) {