From 0fb83bdf433b9b48240f4ac7a5d4c912f6b808cc Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Sat, 3 Oct 2026 07:11:58 -0400 Subject: feat: added big 6 wheel to the casino --- internal/game/render_big_six.go | 209 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 internal/game/render_big_six.go (limited to 'internal/game/render_big_six.go') diff --git a/internal/game/render_big_six.go b/internal/game/render_big_six.go new file mode 100644 index 0000000..19579f0 --- /dev/null +++ b/internal/game/render_big_six.go @@ -0,0 +1,209 @@ +package game + +import ( + "fmt" + "strings" + + "thehouseoficarus/internal/casino" + "thehouseoficarus/internal/color" +) + +func bigSixInk(mode, label, text string, dim bool) string { + fg := 2 + switch label { + case "2": + fg = 6 + case "5": + fg = 4 + case "10": + fg = 5 + case "20": + fg = 3 + case "SWORD": + fg = 15 + case "SHIELD": + fg = 6 + case "DOUBLE": + fg = 11 + } + return color.Render(mode, color.ColorSpec{Fg: fg, Bg: -1, Bold: !dim, Dim: dim}, text) +} +func bigSixLabel(s string) string { + if s == "SWORD" || s == "SHIELD" || s == "DOUBLE" { + return s + } + return "$" + s +} + +func bigSixPanel(spot casino.BigSixSpot, width int, mode string, unicode bool) []string { + b := rouletteBorders(unicode) + label := bigSixLabel(spot.Label) + art := []string{"$ $", ".--------.", "|" + rouletteCenter(label, 8) + "|", "'--------'", "$ $"} + if unicode { + art = []string{"$ $", "╭────────╮", "│" + rouletteCenter(label, 8) + "│", "╰────────╯", "$ $"} + } + switch spot.Label { + case "SWORD": + // Two full blade rows below the tip; the guard sits just above the grip. + art = []string{"/\\", "||", "||", "===++===", "()"} + if unicode { + art = []string{"▲", "║", "║", "╘══╬══╛", "╩"} + } + case "SHIELD": + art = []string{".--------.", "| /\\ |", "| \\/ |", "\\ /", "'------'"} + if unicode { + // Keep a fixed-width canvas: centering each tapered row separately + // would shift the corners apart and break the shield's outline. + art = []string{"╭───────╮", "│ ◇ │", "╰╮ ╭╯", " ╰╮ ╭╯ ", " ╰───╯ "} + } + } + lines := []string{b.tl + strings.Repeat(b.h, width) + b.tr, b.v + rouletteCenter(label, width) + b.v} + for _, a := range art { + lines = append(lines, b.v+rouletteCenter(a, width)+b.v) + } + lines = append(lines, b.bl+strings.Repeat(b.h, width)+b.br) + // The payout text is dim, but its box walls keep the same bold weight as + // the rest of the panel; otherwise the row's verticals look thin. + wall := bigSixInk(mode, spot.Label, b.v, false) + oddsLine := wall + bigSixInk(mode, spot.Label, rouletteCenter(fmt.Sprintf("pays %d:1", spot.Pays), width), true) + wall + lines = append(lines[:len(lines)-1], oddsLine, lines[len(lines)-1]) + for i := range lines { + if i == len(lines)-2 { + continue // oddsLine is already colored: bold walls, dim payout + } + lines[i] = bigSixInk(mode, spot.Label, lines[i], false) + } + return lines +} + +// Self-contained scrollback art, always within 80 columns. The last two +// betting panels expand into the space formerly occupied by DOUBLE; DOUBLE +// is an automatic feature, advertised in a shallow full-width banner instead. +func renderBigSixSnapshot(s *casino.BigSixSnapshot, mode string, unicode bool) string { + if len(s.Pockets) > 0 { + return renderBigSixStrip(s, mode, unicode) + } + if s.Phase == "playing" { + return "Big 6 wheel spinning | live stakes " + s.Multiplier + "x" + } + var out strings.Builder + spots := casino.BigSixSpots() + for _, row := range [][]casino.BigSixSpot{spots[:4], spots[4:7]} { + var panels [][]string + for _, spot := range row { + width := 15 + if spot.Label == "SWORD" || spot.Label == "SHIELD" { + width = 24 + } + panels = append(panels, bigSixPanel(spot, width, mode, unicode)) + } + for line := range panels[0] { + out.WriteString(" ") + for col, panel := range panels { + if col > 0 { + out.WriteByte(' ') + } + out.WriteString(panel[line]) + } + out.WriteByte('\n') + } + } + b := rouletteBorders(unicode) + const bannerWidth = 69 // 71 columns including borders, like both betting rows. + banner := []string{ + b.tl + strings.Repeat(b.h, bannerWidth) + b.tr, + b.v + rouletteCenter("DOUBLE", bannerWidth) + b.v, + b.v + rouletteCenter("Every live bet doubles and the wheel spins again", bannerWidth) + b.v, + b.v + rouletteCenter("Double again. And again. No cap.", bannerWidth) + b.v, + b.bl + strings.Repeat(b.h, bannerWidth) + b.br, + } + for _, line := range banner { + out.WriteString(" " + bigSixInk(mode, "DOUBLE", line, false) + "\n") + } + fmt.Fprintf(&out, " Bets: %s", betLimitsText(s.MinBet, s.MaxBet)) + return out.String() +} + +func renderBigSixStrip(s *casino.BigSixSnapshot, mode string, unicode bool) string { + result := s.Phase == "result" + width := 6 + if result { + width = 7 + } + n := len(s.Pockets) + center := n / 2 + pointer := 1 + center*(width+1) + width/2 + b := rouletteBorders(unicode) + wall := "|" + if unicode { + wall = "│" + } + // Every rail and separator on the wheel uses the same dim-yellow border + // ink; the per-pocket colors paint only the interiors. + borderInk := func(text string) string { return bigSixInk(mode, "20", text, true) } + var out strings.Builder + tip := "\\/" + if unicode { + tip = "▼" + } + // No leading/trailing newline: WriteLine supplies the sole frame separator. + out.WriteString(strings.Repeat(" ", pointer) + bigSixInk(mode, "DOUBLE", tip, false) + "\n") + top := b.tl + strings.Repeat(strings.Repeat(b.h, width)+b.teeTop, n-1) + strings.Repeat(b.h, width) + b.tr + bottom := b.bl + strings.Repeat(strings.Repeat(b.h, width)+b.teeBottom, n-1) + strings.Repeat(b.h, width) + b.br + out.WriteString(borderInk(top) + "\n") + rows := 3 + if result { + rows = 4 + } + for row := 0; row < rows; row++ { + out.WriteString(borderInk(b.v)) + for i, pocket := range s.Pockets { + label := bigSixLabel(pocket) + var art []string + if result { + // The bill's side walls continue through the number row so the + // outline stays complete once the wheel stops. + art = []string{".-----.", "| $ |", wall + rouletteCenter(label, 5) + wall, "'-----'"} + if unicode { + art = []string{"╭─────╮", "│ $ │", wall + rouletteCenter(label, 5) + wall, "╰─────╯"} + } + switch pocket { + case "SWORD": + art = []string{"/\\", "||", "--+--", label} + if unicode { + art = []string{"▲", "╘═╬═╛", "╩", label} + } + case "SHIELD": + art = []string{"/---\\", "| + |", "\\___/", label} + if unicode { + art = []string{"╭─────╮", "╰╮ ◇ ╭╯", " ╰───╯ ", label} + } + case "DOUBLE": + art = []string{"", "x 2", label, "RESPIN"} + if unicode { + art[1] = "× 2" + } + } + } else { + // Only the dollar symbols are drawn as banknotes; the logo and + // DOUBLE spaces show their name without the bill walls. + art = []string{".----.", wall + rouletteCenter(label, 4) + wall, "'----'"} + if unicode { + art = []string{"╭────╮", wall + rouletteCenter(label, 4) + wall, "╰────╯"} + } + switch pocket { + case "SWORD", "SHIELD", "DOUBLE": + art = []string{"", label, ""} + } + } + out.WriteString(bigSixInk(mode, pocket, rouletteCenter(art[row], width), result && i != center)) + out.WriteString(borderInk(b.v)) + } + out.WriteByte('\n') + } + out.WriteString(borderInk(bottom)) + if result { + out.WriteString("\n" + strings.Repeat(" ", pointer-3) + bigSixInk(mode, s.Pockets[center], "WINNER", false)) + } + return out.String() +} -- cgit v1.2.3