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. Roulette adds its live wheel; craps never prints a // board here. 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.GameRoulette: // Look, like sitting down and the table command, shows the live // wheel: tracker, racetrack, and betting layout. if rendered := strings.TrimRight(g.renderCasinoSnapshot(sess, table.Board(sess.Player.Name)), "\n"); rendered != "" { lines = append(lines, "", rendered) } 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 lives in the rules help page. 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() }