package game import ( "fmt" "strconv" "strings" "thehouseoficarus/internal/casino" "thehouseoficarus/internal/color" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" ) type casinoWallet struct { g *Game p *player.Player } func (w casinoWallet) Wager(amount int) (casino.Wager, bool) { chips := countChips(w.p) if chips > amount { chips = amount } credits := amount - chips if w.p.Credits < credits { return casino.Wager{}, false } if chips > 0 { w.p.RemoveItem("chips", chips) } w.p.Credits -= credits w.g.AccountStore.SaveCharacter(w.p) return casino.Wager{Total: amount, Chips: chips, Credits: credits}, true } func (w casinoWallet) PayCredits(amount int) { w.p.Credits += amount w.g.AccountStore.SaveCharacter(w.p) } func (g *Game) casinoTable(roomID int, gameName string) (*casino.Table, bool) { room, err := g.World.LoadRoom(roomID) if err != nil { return nil, false } for _, cfg := range room.CasinoTables { if strings.EqualFold(string(cfg.Game), gameName) { return g.Casino.EnsureTable(roomID, cfg), true } } return nil, false } func (g *Game) casinoMachine(roomID int, gameName string) (*casino.Machine, bool) { room, err := g.World.LoadRoom(roomID) if err != nil { return nil, false } for _, cfg := range room.CasinoMachines { if strings.EqualFold(string(cfg.Game), gameName) || strings.EqualFold(cfg.Variant, gameName) { return g.Casino.EnsureMachine(roomID, cfg), true } } // Migrate older room definitions that declared slots under casino_tables; // slots are still always instantiated as one-player machines. if strings.EqualFold(gameName, "slots") { for _, cfg := range room.CasinoTables { if strings.EqualFold(string(cfg.Game), "slots") { return g.Casino.EnsureMachine(roomID, casino.MachineConfig{ ID: cfg.ID, Game: casino.GameSlots, MinBet: cfg.MinBet, MaxBet: cfg.MaxBet, }), true } } } return nil, false } func (g *Game) executePlay(sess *net.Session, args []string, rawInput string) { p := sess.Player if p == nil || len(args) == 0 { sess.WriteLine("Play what?") return } if _, playing := g.Casino.Participation(p.Name); playing { sess.WriteLine("You're already playing a game. Type \"stop\" between rounds to leave it.") return } g.startCasinoGame(sess, p, args[0]) } func (g *Game) startCasinoGame(sess *net.Session, p *player.Player, gameName string) { if p == nil || sess == nil { return } if _, playing := g.Casino.Participation(p.Name); playing { sess.WriteLine("You're already playing a game. Type \"stop\" between rounds to leave it.") return } if machine, ok := g.casinoMachine(p.RoomID, gameName); ok { g.emitCasinoEvents(g.Casino.JoinMachine(machine.RoomID, machine.Config, p.Name)) return } table, ok := g.casinoTable(p.RoomID, gameName) if !ok { sess.WriteLine(fmt.Sprintf("There is no %s table here.", gameName)) return } g.emitCasinoEvents(g.Casino.Join(table.RoomID, table.Config, p.Name)) } func (g *Game) executeBet(sess *net.Session, args []string, rawInput string) { p := sess.Player if p == nil { sess.WriteLine("Bet how much?") return } if machine, ok := g.Casino.MachineFor(p.Name); ok { if len(args) == 0 { g.emitCasinoEvents(g.Casino.StartMachine(machine.RoomID, machine.Config.ID, p.Name, casinoWallet{g: g, p: p})) return } if len(args) == 1 && args[0] == "max" { g.emitCasinoEvents(g.Casino.SetMachineBet(machine.RoomID, machine.Config.ID, p.Name, machine.Config.MaxBet)) return } if len(args) == 1 { if amount, err := strconv.Atoi(args[0]); err == nil && amount > 0 { g.emitCasinoEvents(g.Casino.SetMachineBet(machine.RoomID, machine.Config.ID, p.Name, amount)) return } } g.emitCasinoEvents(g.Casino.StartMachine(machine.RoomID, machine.Config.ID, p.Name, casinoWallet{g: g, p: p})) return } if len(args) == 0 { sess.WriteLine("Bet how much?") return } amount, err := strconv.Atoi(args[0]) if err != nil || amount <= 0 { sess.WriteLine("Your bet must be a positive whole number.") return } table, playing := g.Casino.Participation(p.Name) if !playing { sess.WriteLine("You're not playing a game.") return } g.emitCasinoEvents(g.Casino.Bet(table.RoomID, table.Config.ID, p.Name, amount, casinoWallet{g: g, p: p})) } func (g *Game) executeAutoBet(sess *net.Session, args []string, rawInput string) { p := sess.Player if p == nil { return } if machine, ok := g.Casino.MachineFor(p.Name); ok { g.emitCasinoEvents(g.Casino.EnableMachineAutoBet(machine.RoomID, machine.Config.ID, p.Name, casinoWallet{g: g, p: p})) return } sess.WriteLine("Autobet is only available at games without player choices.") } func (g *Game) CasinoTick() { if g.Casino != nil { g.emitCasinoEvents(g.Casino.Tick()) } } func (g *Game) emitCasinoEvents(events []casino.Event) { for _, event := range events { if event.Message == "" { continue } category := event.Color if category == "" { category = casinoEventColor(event.Type) } if event.Public && g.Hub != nil { for _, sess := range g.Hub.PlayersInRoom(event.RoomID) { if event.PrivateTo != "" && sess.Player != nil && sess.Player.Name == event.PrivateTo { continue } message := event.Message if category == "casino_menu" && sess.Player != nil && sess.Player.Name == event.PrivateTo { message = g.casinoMenuBalance(sess, message) } if event.Slot != nil && sess.Player != nil { message += "\n" + renderSlotSnapshot(event.Slot, g.colorMode(sess), sess.Player.OptionBool("unicode")) } sess.WriteLine(g.colorize(sess, category, message)) } } if event.PrivateMessage != "" && event.PrivateTo != "" { g.charsMu.Lock() sess := g.loggedInChars[event.PrivateTo] g.charsMu.Unlock() if sess != nil { privateMessage := event.PrivateMessage if category == "casino_menu" { privateMessage = g.casinoMenuBalance(sess, privateMessage) } sess.WriteLine(g.colorize(sess, category, privateMessage)) if category == "casino_menu" { g.writePrompt(sess) } } } if event.PrivateTo != "" { g.charsMu.Lock() sess := g.loggedInChars[event.PrivateTo] g.charsMu.Unlock() if sess != nil && event.PrivateMessage == "" { privateMessage := event.Message if category == "casino_menu" { privateMessage = g.casinoMenuBalance(sess, privateMessage) } sess.WriteLine(g.colorize(sess, category, privateMessage)) if category == "casino_menu" { g.writePrompt(sess) } } } } } func casinoEventColor(eventType casino.EventType) string { if eventType == casino.EventPayout { return "casino_win" } return "casino_action" } func (g *Game) casinoMenuBalance(sess *net.Session, message string) string { if sess == nil || sess.Player == nil { return message } chips := g.colorize(sess, "casino_chips", fmt.Sprintf("%d", countChips(sess.Player))) credits := g.colorize(sess, "casino_credits", fmt.Sprintf("%d", sess.Player.Credits)) line := fmt.Sprintf("You have %s chips and %s credits.", chips, credits) return strings.Replace(message, "Slot machine commands:", line+"\n\nSlot machine commands:", 1) } func renderSlotSnapshot(snapshot *casino.SlotSnapshot, mode string, unicode bool) string { if snapshot == nil { return "" } left, junction, right, horizontal, vertical := '+', '+', '+', '-', '|' bottomLeft, bottomJunction, bottomRight := '+', '+', '+' if unicode { left, junction, right, horizontal, vertical = '\u250c', '\u252c', '\u2510', '\u2500', '\u2502' bottomLeft, bottomJunction, bottomRight = '\u2514', '\u2534', '\u2518' } separator := string(left) for reel := 0; reel < 5; reel++ { if reel > 0 { separator += string(junction) } separator += strings.Repeat(string(horizontal), 9) } separator += string(right) lines := []string{casinoFrame(mode, separator)} frameVertical := casinoFrame(mode, string(vertical)) for row := 0; row < 3; row++ { line := frameVertical for reel := 0; reel < 5; reel++ { value := "..." if reel < snapshot.StoppedReels { value = snapshot.Grid[row][reel] } line += " " + colorSlotSymbol(mode, value) + strings.Repeat(" ", 7-len(value)) + " " + frameVertical } lines = append(lines, line) } bottom := string(bottomLeft) for reel := 0; reel < 5; reel++ { if reel > 0 { bottom += string(bottomJunction) } bottom += strings.Repeat(string(horizontal), 9) } bottom += string(bottomRight) lines = append(lines, casinoFrame(mode, bottom)) return strings.Join(lines, "\n") } func casinoFrame(mode, text string) string { spec := color.NoColor() spec.Fg = 244 return color.Render(mode, spec, text) } func colorSlotSymbol(mode, symbol string) string { spec := color.NoColor() spec.Fg = 250 switch symbol { case "straw": spec = color.NoColor() spec.Gradient = []int{220, 226} case "stick": spec = color.NoColor() spec.Gradient = []int{34, 46} case "brick": spec = color.NoColor() spec.Gradient = []int{160, 196} case "hat": spec = color.NoColor() spec.Gradient = []int{129, 201} case "wolf": spec = color.NoColor() spec.Gradient = []int{27, 39} case "scatter": spec = color.NoColor() spec.Gradient = []int{196, 220, 46, 51, 129, 201} default: spec.Dim = true } return color.Render(mode, spec, symbol) }