diff options
| author | historia <[not public]> | 2026-10-01 15:47:17 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-10-01 15:47:17 -0400 |
| commit | c94728c929045f7c1bdf0166df152d944e5f99bc (patch) | |
| tree | 3d677caf166da36cd12ae200cc4c5d7a789d5c8f /internal/game | |
| parent | 1082f3336085c15992ca83d5553c0ba52208d70a (diff) | |
| download | thehouseoficarus-c94728c929045f7c1bdf0166df152d944e5f99bc.tar.gz | |
feat: better casino game look output, more concise table instructions
Diffstat (limited to 'internal/game')
| -rw-r--r-- | internal/game/blackjack_rules.go | 39 | ||||
| -rw-r--r-- | internal/game/casino.go | 9 | ||||
| -rw-r--r-- | internal/game/casino_ux.go | 25 | ||||
| -rw-r--r-- | internal/game/look_casino.go | 128 | ||||
| -rw-r--r-- | internal/game/look_casino_test.go | 121 | ||||
| -rw-r--r-- | internal/game/look_entities.go | 2 | ||||
| -rw-r--r-- | internal/game/look_target.go | 2 | ||||
| -rw-r--r-- | internal/game/pai_gow_test.go | 53 | ||||
| -rw-r--r-- | internal/game/render_pai_gow.go | 21 |
9 files changed, 342 insertions, 58 deletions
diff --git a/internal/game/blackjack_rules.go b/internal/game/blackjack_rules.go index 045626c..7cea977 100644 --- a/internal/game/blackjack_rules.go +++ b/internal/game/blackjack_rules.go @@ -8,20 +8,26 @@ import ( ) // Tables are room fixtures in their own right, not authored scenery objects. +// Each game resolves through the shared details view; the felt games append +// their per-table rule card there. func (g *Game) lookBlackjackTable(sess *net.Session, target string) bool { - return g.lookCardTable(sess, target, casino.GameBlackjack) + return g.lookCasinoTable(sess, target, casino.GameBlackjack) } func (g *Game) lookBaccaratTable(sess *net.Session, target string) bool { - return g.lookCardTable(sess, target, casino.GameBaccarat) + return g.lookCasinoTable(sess, target, casino.GameBaccarat) } func (g *Game) lookCrapsTable(sess *net.Session, target string) bool { - return g.lookCardTable(sess, target, casino.GameCraps) + return g.lookCasinoTable(sess, target, casino.GameCraps) } func (g *Game) lookRouletteTable(sess *net.Session, target string) bool { - return g.lookCardTable(sess, target, casino.GameRoulette) + return g.lookCasinoTable(sess, target, casino.GameRoulette) +} + +func (g *Game) lookPaiGowTable(sess *net.Session, target string) bool { + return g.lookCasinoTable(sess, target, casino.GamePaiGow) } // A cabinet's published pay table is public; another player's cards are not. @@ -56,28 +62,3 @@ func (g *Game) lookVideoPokerCabinet(sess *net.Session, target string) bool { } return false } - -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 - } - sess.WriteLine(g.renderCasinoRules(sess, table.Config)) - return true - } - return false -} diff --git a/internal/game/casino.go b/internal/game/casino.go index e6c0f27..db5bfad 100644 --- a/internal/game/casino.go +++ b/internal/game/casino.go @@ -632,8 +632,8 @@ func (g *Game) casinoMenuParts(sess *net.Session, menu *casino.MenuView, message return balance, heading, commands } -// casinoBoardReminder splits roulette and craps menus so instructions can be -// printed under the betting board, which is the table the reminder refers to. +// casinoBoardReminder splits a persistent-board menu (craps, roulette, video +// poker, Pai Gow) so its instructions print under the board they describe. // Every other game, and compact displays, keep their menu in one block. func (g *Game) casinoBoardReminder(sess *net.Session, category string, menu *casino.MenuView, snapshot casino.Snapshot, message string) (header, reminder string, ok bool) { if category != "casino_menu" || menu == nil || sess.Player == nil { @@ -641,11 +641,12 @@ func (g *Game) casinoBoardReminder(sess *net.Session, category string, menu *cas } _, isCraps := snapshot.(*casino.CrapsSnapshot) _, isPoker := snapshot.(*casino.VideoPokerSnapshot) + _, isPaiGow := snapshot.(*casino.PaiGowSnapshot) s, isRoulette := snapshot.(*casino.RouletteSnapshot) - if !isPoker && !isCraps && (!isRoulette || s.Phase == "playing" || s.Phase == "result") { + if !isPoker && !isCraps && !isPaiGow && (!isRoulette || s.Phase == "playing" || s.Phase == "result") { return "", "", false } - if !isPoker && !isCraps && sess.Player.OptionString("casino_display") == "compact" && (menu.Repeat || !menu.ShowBalance) { + if !isPoker && !isCraps && !isPaiGow && sess.Player.OptionString("casino_display") == "compact" && (menu.Repeat || !menu.ShowBalance) { // The compact reminder is already a short block; leave it intact. return "", "", false } diff --git a/internal/game/casino_ux.go b/internal/game/casino_ux.go index 6f7964f..1d66f43 100644 --- a/internal/game/casino_ux.go +++ b/internal/game/casino_ux.go @@ -61,6 +61,21 @@ func resolveCasinoChoice(choices []casinoChoice, input string) (casinoChoice, bo return casinoChoice{}, false } +// casinoTableAliases lists the names the play, list and look commands accept +// for a multiplayer table, beyond its raw game name and fixture id. +func casinoTableAliases(cfg casino.TableConfig) []string { + names := []string{string(cfg.Game)} + switch cfg.Game { + case casino.GameBlackjack: + names = append(names, "bj") + case casino.GameBaccarat: + names = append(names, "bac") + case casino.GamePaiGow: + names = append(names, "pai", "pai gow", "pai gow poker") + } + return names +} + func (g *Game) casinoChoices(roomID int) []casinoChoice { room, err := g.World.LoadRoom(roomID) if err != nil { @@ -73,15 +88,7 @@ func (g *Game) casinoChoices(roomID int) []casinoChoice { choices = append(choices, casinoChoice{address: c.ID, names: names, table: true}) continue } - names := []string{string(c.Game)} - switch c.Game { - case casino.GameBlackjack: - names = append(names, "bj") - case casino.GameBaccarat: - names = append(names, "bac") - case casino.GamePaiGow: - names = append(names, "paigow", "pai", "pai gow", "pai gow poker") - } + names := casinoTableAliases(c) choices = append(choices, casinoChoice{address: string(c.Game), names: names, table: true}) } for _, c := range room.CasinoMachines { diff --git a/internal/game/look_casino.go b/internal/game/look_casino.go new file mode 100644 index 0000000..6fa2959 --- /dev/null +++ b/internal/game/look_casino.go @@ -0,0 +1,128 @@ +package game + +import ( + "fmt" + "strings" + + "thehouseoficarus/internal/casino" + "thehouseoficarus/internal/net" +) + +// lookCasinoTable renders a table fixture's details for a look target: the +// wager limits, the live state and seated players, and this table's rule +// variants. The invariant roulette and craps graphics and the generic rule +// dumps are deliberately omitted; blackjack, baccarat and Pai Gow keep their +// felt because it carries per-table rules and pay tables. It reports whether +// the target named this room's game. +func (g *Game) lookCasinoTable(sess *net.Session, target string, game casino.GameName) bool { + table, ok := g.resolveRoomTable(sess, target, game) + if !ok { + return false + } + // Use the live table's normalized config: the room fixture may omit + // defaults (seat count, bet limits) that EnsureTable fills in. + sess.WriteLine(g.renderCasinoTableDetails(sess, table.Config, table)) + return true +} + +// resolveRoomTable matches a look target against the room's table for game +// using the same names and prefix rules as the play command, plus the fixture +// id, an optional " table" suffix, and the bare "table" target when the room +// holds exactly one table. +func (g *Game) resolveRoomTable(sess *net.Session, target string, game casino.GameName) (*casino.Table, bool) { + if g.Casino == nil || sess.Player == nil { + return nil, false + } + room, err := g.World.LoadRoom(sess.Player.RoomID) + if err != nil { + return nil, false + } + target = strings.TrimSpace(strings.ToLower(target)) + target = strings.TrimSuffix(target, " table") + address := string(game) + if target == "table" { + if len(room.CasinoTables) != 1 || room.CasinoTables[0].Game != game { + return nil, false + } + } else { + var choices []casinoChoice + for _, cfg := range room.CasinoTables { + if cfg.Game != game { + continue + } + choices = append(choices, casinoChoice{address: string(cfg.Game), names: append(casinoTableAliases(cfg), cfg.ID), table: true}) + } + choice, ok := resolveCasinoChoice(choices, target) + if !ok { + return nil, false + } + address = choice.address + } + table, ok := g.casinoTable(room.ID, address) + if !ok { + return nil, false + } + return table, true +} + +// renderCasinoTableDetails formats the details block and, for the felt games, +// the per-table rule card. It never prints the craps or roulette board. +func (g *Game) renderCasinoTableDetails(sess *net.Session, cfg casino.TableConfig, table *casino.Table) string { + seats := table.Seats() + seated := "no one" + if len(seats) > 0 { + seated = strings.Join(seats, ", ") + } + lines := []string{ + g.colorize(sess, "casino_action", cfg.Label()), + fmt.Sprintf(" Limits %s", betLimitsText(cfg.MinBet, cfg.MaxBet)), + fmt.Sprintf(" Seats %d/%d", len(seats), cfg.MaxPlayers), + fmt.Sprintf(" State %s", table.State()), + fmt.Sprintf(" Rules %s", casinoTableSummary(cfg)), + fmt.Sprintf(" Seated %s", seated), + } + switch cfg.Game { + case casino.GameBlackjack: + lines = append(lines, "", g.renderCasinoSnapshot(sess, &casino.BlackjackRulesView{Config: cfg})) + case casino.GameBaccarat: + lines = append(lines, "", g.renderCasinoSnapshot(sess, &casino.BaccaratRulesView{Config: cfg})) + case casino.GamePaiGow: + lines = append(lines, "", renderPaiGowFelt(cfg, g.colorMode(sess), sess.Player.OptionBool("unicode"))) + } + return strings.Join(lines, "\n") +} + +// casinoTableSummary is the compact per-table rule description used by the +// look details block. The full wording stays in RulesText. +func casinoTableSummary(cfg casino.TableConfig) string { + switch cfg.Game { + case casino.GameBlackjack: + b := cfg.Blackjack.Normalize() + dealer := "dealer stands on soft 17" + if b.DealerHitsSoft17() { + dealer = "dealer hits soft 17" + } + return fmt.Sprintf("%d decks; %s; blackjack pays %s", b.Decks, dealer, ratioText(b.BlackjackPayout)) + case casino.GameBaccarat: + return fmt.Sprintf("%d decks", cfg.Baccarat.Normalize().Decks) + case casino.GameRoulette: + summary := "single-zero wheel" + if cfg.Roulette.DoubleZero { + summary = "double-zero wheel" + } + if cfg.Roulette.Racetrack { + summary += "; racetrack call bets" + } + return summary + case casino.GameCraps: + return cfg.Craps.Normalize().Title() + case casino.GamePaiGow: + p := cfg.PaiGow.Normalize() + envy := "Envy off" + if p.Envy { + envy = "Envy on" + } + return p.Label() + "; " + envy + } + return cfg.Label() +} diff --git a/internal/game/look_casino_test.go b/internal/game/look_casino_test.go new file mode 100644 index 0000000..37205cd --- /dev/null +++ b/internal/game/look_casino_test.go @@ -0,0 +1,121 @@ +package game + +import ( + "strings" + "testing" + + "thehouseoficarus/internal/casino" +) + +// A room with one table of each kind, so look targets and aliases resolve. +const lookCasinoRoom = `name: Look Room +casino_tables: + - id: bjtable + game: blackjack + blackjack: + decks: 2 + blackjack_payout: 2 + - id: crock + game: craps + craps: + variant: simplified + - id: wheel + game: roulette + - id: pgtable + game: paigow + pai_gow: + variant: classic + envy: true +` + +func TestLookCasinoTableShowsDetailsWithoutInvariantBoards(t *testing.T) { + g, sess, conn := casinoCrapsGame(t, lookCasinoRoom) + + // Craps: details only. The graphical felt and the full rule dump are gone. + conn.data = nil + if !g.lookCrapsTable(sess, "craps") { + t.Fatal("look craps did not match") + } + text := stripANSI(string(conn.data)) + for _, want := range []string{"Craps", "Limits", "Seats", "State", "Rules", "Simplified Craps", "Seated"} { + if !strings.Contains(text, want) { + t.Fatalf("look craps missing %q: %s", want, text) + } + } + for _, gone := range []string{"PASS ONLY", "bet <amount> on pass", "HOUSE EDITION"} { + if strings.Contains(text, gone) { + t.Fatalf("look craps showed invariant content %q: %s", gone, text) + } + } + + // Roulette: details only, no betting board or payout reference. + conn.data = nil + if !g.lookRouletteTable(sess, "roulette") { + t.Fatal("look roulette did not match") + } + text = stripANSI(string(conn.data)) + if !strings.Contains(text, "Roulette / Single zero") || !strings.Contains(text, "single-zero wheel") { + t.Fatalf("look roulette missing variant summary: %s", text) + } + for _, gone := range []string{"Roulette payouts", "History: last"} { + if strings.Contains(text, gone) { + t.Fatalf("look roulette showed invariant board %q: %s", gone, text) + } + } +} + +func TestLookCasinoTableAliasesAndFeltGames(t *testing.T) { + g, sess, conn := casinoCrapsGame(t, lookCasinoRoom) + + // Play shortcuts resolve through look too, and the felt games keep their + // per-table rule card. + for target, want := range map[string]string{"bj": "BLACKJACK PAYS 2 TO 1", "paigow": "FORTUNE", "pai": "FORTUNE"} { + conn.data = nil + g.doLookTarget(sess, target) + text := stripANSI(string(conn.data)) + if !strings.Contains(text, want) { + t.Fatalf("look %s missing %q: %s", target, want, text) + } + } + if g.lookCrapsTable(sess, "bj") { + t.Fatal("look craps matched a blackjack target") + } +} + +func TestLookCasinoUsesLiveNormalizedConfig(t *testing.T) { + // The room fixture omits max_players and limits; look must report the + // defaults EnsureTable fills in, not zero values. + g, sess, conn := casinoCrapsGame(t, "name: Bare\ncasino_tables:\n - id: wheel\n game: roulette\n") + conn.data = nil + if !g.lookRouletteTable(sess, "wheel") { + t.Fatal("look by fixture id did not match") + } + text := stripANSI(string(conn.data)) + if !strings.Contains(text, "Seats 0/8") || !strings.Contains(text, "Limits 1-") { + t.Fatalf("look missed normalized config: %s", text) + } +} + +func TestPaigowGameNameAndRoomListing(t *testing.T) { + g, sess, _ := casinoCrapsGame(t, "name: Pai Gow\ncasino_tables:\n - id: fortune\n game: paigow\n pai_gow:\n envy: true\n") + g.executePlay(sess, []string{"paigow"}, "play paigow") + table, ok := g.Casino.Participation(sess.Player.Name) + if !ok || table == nil || table.Config.Game != casino.GamePaiGow { + t.Fatalf("play paigow did not join: %v %v", ok, table) + } + if string(table.Config.Game) != "paigow" { + t.Fatalf("game name = %q, want paigow", table.Config.Game) + } + // The room listing advertises the short name and no longer leaks limits. + room, err := g.World.LoadRoom(sess.Player.RoomID) + if err != nil { + t.Fatal(err) + } + text := stripANSI(strings.Join(g.showRoomCasinos(sess, room), "\n")) + if !strings.Contains(text, "play paigow") || !strings.Contains(text, "Pai Gow Poker | 1/6 seated") { + t.Fatalf("room listing wrong: %s", text) + } + if strings.Contains(text, "Limits") { + t.Fatalf("room listing still shows limits: %s", text) + } +} diff --git a/internal/game/look_entities.go b/internal/game/look_entities.go index bfd36ca..4c3cb6e 100644 --- a/internal/game/look_entities.go +++ b/internal/game/look_entities.go @@ -235,7 +235,7 @@ func (g *Game) showRoomCasinos(sess *net.Session, room *world.Room) []string { lines = append(lines, g.colorize(sess, "casino_action", cfg.VideoPoker.CabinetName()+" machine"), " "+g.colorize(sess, "command", "play "+cfg.ID)) continue } - label := fmt.Sprintf("%s | Limits %d-%d | 0/%d seated", cfg.Label(), cfg.MinBet, cfg.MaxBet, cfg.MaxPlayers) + label := fmt.Sprintf("%s | 0/%d seated", cfg.Label(), cfg.MaxPlayers) if g.Casino != nil { if live := g.Casino.Table(room.ID, table.ID); live != nil { label = g.casinoTimeText(live.Overview()) diff --git a/internal/game/look_target.go b/internal/game/look_target.go index 3f8cd15..d296b35 100644 --- a/internal/game/look_target.go +++ b/internal/game/look_target.go @@ -47,7 +47,7 @@ func (g *Game) doLookTarget(sess *net.Session, input string) { return } - if g.lookBlackjackTable(sess, lower) || g.lookBaccaratTable(sess, lower) || g.lookRouletteTable(sess, lower) || g.lookCrapsTable(sess, lower) || g.lookVideoPokerCabinet(sess, lower) { + if g.lookBlackjackTable(sess, lower) || g.lookBaccaratTable(sess, lower) || g.lookRouletteTable(sess, lower) || g.lookCrapsTable(sess, lower) || g.lookPaiGowTable(sess, lower) || g.lookVideoPokerCabinet(sess, lower) { return } diff --git a/internal/game/pai_gow_test.go b/internal/game/pai_gow_test.go index 614d0dd..f13655b 100644 --- a/internal/game/pai_gow_test.go +++ b/internal/game/pai_gow_test.go @@ -11,7 +11,7 @@ import ( ) func TestPaiGowCommandsLookAndSettlement(t *testing.T) { - g, sess, conn := casinoCrapsGame(t, "name: Pai Gow\ncasino_tables:\n - id: fortune\n game: pai_gow\n pai_gow:\n envy: true\n") + g, sess, conn := casinoCrapsGame(t, "name: Pai Gow\ncasino_tables:\n - id: fortune\n game: paigow\n pai_gow:\n envy: true\n") g.executePlay(sess, []string{"pai"}, "play pai") table, playing := g.Casino.Participation(sess.Player.Name) if !playing || table.Config.Game != casino.GamePaiGow { @@ -20,7 +20,7 @@ func TestPaiGowCommandsLookAndSettlement(t *testing.T) { conn.data = nil g.executeTableStatus(sess, []string{"rules"}, "table rules") text := stripANSI(string(conn.data)) - for _, want := range []string{"FORTUNE", "ENVY (FIXED)", "750:1", "Three pair"} { + for _, want := range []string{"FORTUNE", "ENVY", "750:1", "Three pair"} { if !strings.Contains(text, want) { t.Fatalf("missing %s: %s", want, text) } @@ -102,10 +102,22 @@ func TestPaiGowFeltAndJokerAllModes(t *testing.T) { if !strings.Contains(felt, "Three pair") || !strings.Contains(felt, "1000:1") { t.Fatal(felt) } + // The felt shows only the limit and commission; the per-state command + // hints come from the live menu, not baked-in felt text. + for _, want := range []string{"LIMITS", "ODDS", "COMMISSION", "Copies lose"} { + if !strings.Contains(felt, want) { + t.Fatalf("missing %q: %s", want, felt) + } + } + for _, gone := range []string{"PROFIT ODDS", "FIVE HIGH / TWO LOW", "MAIN WIN", "24 sets cards", "Bet main, optional"} { + if strings.Contains(felt, gone) { + t.Fatalf("stale felt text %q: %s", gone, felt) + } + } if !envy && (!strings.Contains(felt, "ENVY OFF") || !strings.Contains(felt, "30:1")) { t.Fatal(felt) } - if cfg.PaiGow.Envy && (strings.Contains(felt, "ENVY OFF") || !strings.Contains(felt, "ENVY (FIXED)")) { + if cfg.PaiGow.Envy && (strings.Contains(felt, "ENVY OFF") || !strings.Contains(felt, "ENVY") || !strings.Contains(felt, "Envy pays when other players hit quads or better.")) { t.Fatal(felt) } } @@ -116,6 +128,41 @@ func TestPaiGowFeltAndJokerAllModes(t *testing.T) { t.Fatal(felt) } } +func TestPaiGowContextHintsFollowState(t *testing.T) { + g, sess, conn := casinoCrapsGame(t, "name: Pai Gow\ncasino_tables:\n - id: fortune\n game: paigow\n min_bet: 5\n max_bet: 1000\n pai_gow:\n envy: true\n") + g.executePlay(sess, []string{"pai"}, "play pai") + + // A fresh bettor is told how to place the main wager. + conn.data = nil + g.executeTableStatus(sess, nil, "table") + text := stripANSI(string(conn.data)) + if !strings.Contains(text, "bet <amount>") { + t.Fatalf("missing main-bet hint: %s", text) + } + + // After the main wager the live menu offers Fortune and deal, not a second + // main bet, and the shared tail is present. + conn.data = nil + g.executeBet(sess, []string{"20"}, "bet 20") + text = stripANSI(string(conn.data)) + for _, want := range []string{"bet <amount> fortune", "deal", "table rules", "rules", "stop"} { + if !strings.Contains(text, want) { + t.Fatalf("missing %q after main bet: %s", want, text) + } + } + + // Setting offers the split selectors and the house-way shortcut. + g.executeBet(sess, []string{"5", "fortune"}, "bet 5 fortune") + g.executeTableAction(sess, "deal") + conn.data = nil + g.executeTableStatus(sess, nil, "table") + text = stripANSI(string(conn.data)) + for _, want := range []string{"24 / 13456", "house"} { + if !strings.Contains(text, want) { + t.Fatalf("missing %q while setting: %s", want, text) + } + } +} func TestVestaPaiGowRooms(t *testing.T) { _, rooms, _ := loadVesta(t) for id, variant := range map[int]string{6195: casino.PaiGowClassic, 6196: casino.PaiGowEZ, 6197: casino.PaiGowNoPush, 6198: casino.PaiGowFaceUp} { diff --git a/internal/game/render_pai_gow.go b/internal/game/render_pai_gow.go index 2005daf..239ddb4 100644 --- a/internal/game/render_pai_gow.go +++ b/internal/game/render_pai_gow.go @@ -12,23 +12,23 @@ func renderPaiGowFelt(cfg casino.TableConfig, mode string, unicode bool) string c := newCrapsCanvas(25) c.box(0, 0, 76, 24) c.center(1, 75, 1, strings.ToUpper(cfg.PaiGow.Label()), crapsGold) - c.center(1, 75, 2, "FIVE HIGH / TWO LOW | COPIES LOSE | LIMITS "+betLimitsText(cfg.MinBet, cfg.MaxBet), crapsInk) - note := "MAIN WIN 0.95:1 | 5% COMMISSION" + c.center(1, 75, 2, "LIMITS "+betLimitsText(cfg.MinBet, cfg.MaxBet), crapsInk) + note := "5% COMMISSION" switch cfg.PaiGow.Normalize().Variant { case casino.PaiGowEZ: - note = "MAIN WIN 1:1 | DEALER QUEEN-HIGH PUSHES" + note = "NO COMMISSION | DEALER QUEEN-HIGH PUSHES" case casino.PaiGowNoPush: - note = "MAIN WIN 1:1 | SPLIT RESULT: EXTRA CARD HIGH WINS, TIES LOSE" + note = "NO COMMISSION | SPLIT: HIGHER EXTRA CARD WINS, TIES LOSE" case casino.PaiGowFaceUp: - note = "DEALER FACE UP | ACE-HIGH PUSHES | PUSH ACE HIGH " + strings.ReplaceAll(cfg.PaiGow.PushAceHigh, "_", "/") + ":1" + note = "NO COMMISSION | DEALER FACE UP | PUSH ACE HIGH " + strings.ReplaceAll(cfg.PaiGow.PushAceHigh, "_", "/") + ":1" } c.center(1, 75, 3, note, crapsInk) c.box(0, 4, 76, 19) c.text(3, 5, "FORTUNE - BEST OF ALL SEVEN CARDS", crapsGold) - c.text(48, 5, "PROFIT ODDS", crapsGold) + c.text(50, 5, "ODDS", crapsGold) envy := "ENVY OFF" if cfg.PaiGow.Envy { - envy = "ENVY (FIXED)" + envy = "ENVY" } c.text(62, 5, envy, crapsGold) for i, row := range casino.FortunePayTable(cfg.PaiGow.Envy) { @@ -41,12 +41,11 @@ func renderPaiGowFelt(cfg casino.TableConfig, mode string, unicode bool) string c.center(1, 75, 18, "Highest prize only. Winning Fortune wagers also return their stake.", crapsMuted) note = "Envy disabled: quads improved from 25:1 to 30:1." if cfg.PaiGow.Envy { - note = "Fortune 5+ earns Envy on other players, not yourself or dealer." + note = "Envy pays when other players hit quads or better." } c.center(1, 75, 20, note, crapsInk) - c.center(1, 75, 21, "24 sets cards 2/4 LOW; 13456 sets five HIGH; house sets house way.", crapsInk) - c.center(1, 75, 22, "High must outrank low. Joker: ace or completes straight/flush.", crapsMuted) - c.center(1, 75, 23, "Bet main, optional bet <amount> fortune, then deal.", crapsMuted) + c.center(1, 75, 21, "High must outrank low. Copies lose.", crapsMuted) + c.center(1, 75, 22, "Joker: ace or completes straight/flush.", crapsMuted) return c.render(mode, unicode) } func renderPaiGow(s *casino.PaiGowSnapshot, mode string, unicode bool, size int, style string, store *cardart.Store) string { |
