diff options
| author | historia <[not public]> | 2026-10-01 22:47:41 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-10-01 22:47:41 -0400 |
| commit | 8eea0e4e06a4aaa6a18edabd9897d3bea1ea2ca0 (patch) | |
| tree | 63698c0618f382a553d4558a33e3d2087039c378 /internal/game/render_casino_settlement.go | |
| parent | 6d9908b29c8187196d23b126b1165c7e0bd0fc76 (diff) | |
| download | thehouseoficarus-8eea0e4e06a4aaa6a18edabd9897d3bea1ea2ca0.tar.gz | |
feat: consistent wager outcome table across casino games
Diffstat (limited to 'internal/game/render_casino_settlement.go')
| -rw-r--r-- | internal/game/render_casino_settlement.go | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/internal/game/render_casino_settlement.go b/internal/game/render_casino_settlement.go new file mode 100644 index 0000000..bfe101c --- /dev/null +++ b/internal/game/render_casino_settlement.go @@ -0,0 +1,185 @@ +package game + +import ( + "fmt" + "strconv" + "strings" + + "thehouseoficarus/internal/casino" + "thehouseoficarus/internal/color" +) + +// renderCasinoSettlement is shared by every game. Paint is supplied by the +// recipient's palette; widths are measured before adding ANSI sequences. +// The receipt fits the MUD's minimum 80-column wrap width. Long labels wrap +// rather than losing the hand number or wager identity through truncation. +func renderCasinoSettlement(lines []casino.SettlementLine, unicode bool, paint func(string, string) string) string { + if len(lines) == 0 { + return "" + } + dash := "—" + h, v, tl, tr, bl, br, tj, bj, lj, rj, cross := "─", "│", "╭", "╮", "╰", "╯", "┬", "┴", "├", "┤", "┼" + if !unicode { + dash = "-" + h, v, tl, tr, bl, br, tj, bj, lj, rj, cross = "-", "|", "+", "+", "+", "+", "+", "+", "+", "+", "+" + } + type row struct { + cells [5]string + resultTag, netTag string + } + rows := make([]row, 0, len(lines)) + widths := [5]int{5, 6, 4, 6, 3} + var staked, returned, active int64 + settled := 0 + netTag := func(net int64) string { + if net > 0 { + return "casino_result_win" + } + if net < 0 { + return "casino_result_loss" + } + return "casino_result_push" + } + for _, line := range lines { + label, odds := line.Label, line.Odds + if label == "" { + label = "Wager" + } + if odds == "" { + odds = "Varies" + } + if !unicode { + label = strings.NewReplacer("·", "-", "—", "-").Replace(label) + odds = strings.ReplaceAll(odds, "—", "-") + } + net := int64(line.Returned) - int64(line.Stake) + result, rt := "LOSS", "casino_result_loss" + switch { + case strings.HasPrefix(line.Result, "win"), line.Result == "blackjack": + result, rt = "WIN", "casino_result_win" + case strings.HasPrefix(line.Result, "push"): + result, rt = "PUSH", "casino_result_push" + case line.Result == "insurance": + rt = netTag(net) + if net > 0 { + result = "WIN" + } else if net == 0 { + result = "PUSH" + } + } + nt, amount := netTag(net), fmt.Sprintf("%+d", net) + if net == 0 { + amount = "0" + } + if line.Unresolved { + result, rt, nt, amount = "UNRESOLVED", "warning", "warning", dash + active += int64(line.Stake) + } else { + staked += int64(line.Stake) + returned += int64(line.Returned) + settled++ + } + r := row{[5]string{label, strconv.Itoa(line.Stake), odds, result, amount}, rt, nt} + rows = append(rows, r) + for i, s := range r.cells { + widths[i] = max(widths[i], color.VisibleLen(s)) + } + } + widths[0] = min(widths[0], 24) + widths[2] = min(widths[2], 10) + // Borders and cell padding occupy sixteen columns. + widths[0] = min(widths[0], 79-16-widths[1]-widths[2]-widths[3]-widths[4]) + widths[0] = max(widths[0], 5) + inner := 4 + for _, w := range widths { + inner += w + 2 + } + // A readable footer without forcing tiny one-wager receipts into a wide box. + inner = max(inner, 50) + sum := 4 + for _, w := range widths { + sum += w + 2 + } + widths[0] += inner - sum + border := func(left, join, right string) string { + parts := make([]string, 5) + for i, w := range widths { + parts[i] = strings.Repeat(h, w+2) + } + return paint("dim", left+strings.Join(parts, join)+right) + } + pad := func(s string, w int, right bool) string { + spaces := strings.Repeat(" ", max(0, w-color.VisibleLen(s))) + if right { + return spaces + s + } + return s + spaces + } + formatRow := func(cells [5]string, rt, nt string) string { + parts := make([]string, 5) + for i, s := range cells { + tag := "casino_action" + if i == 3 { + tag = rt + } + if i == 4 { + tag = nt + } + parts[i] = " " + paint(tag, pad(s, widths[i], i == 1 || i == 2 || i == 4)) + " " + } + return paint("dim", v) + strings.Join(parts, paint("dim", v)) + paint("dim", v) + } + out := []string{border(tl, tj, tr), formatRow([5]string{"Wager", "Staked", "Odds", "Result", "Net"}, "casino_action", "casino_action"), border(lj, cross, rj)} + for _, r := range rows { + // Wrapping uses runes, matching the existing MUD visible-width contract. + remaining := r.cells + for { + var cells [5]string + more := false + for i, s := range remaining { + runes := []rune(strings.Join(strings.Fields(s), " ")) + n := min(len(runes), widths[i]) + cells[i] = string(runes[:n]) + remaining[i] = string(runes[n:]) + more = more || len(runes) > n + } + out = append(out, formatRow(cells, r.resultTag, r.netTag)) + if !more { + break + } + } + } + out = append(out, border(lj, bj, rj)) + footer := func(text, tag string) { + out = append(out, paint("dim", v)+" "+paint(tag, pad(text, inner-2, false))+" "+paint("dim", v)) + } + summary := fmt.Sprintf("Staked: %d Returned: %d", staked, returned) + if active > 0 { + summary = fmt.Sprintf("Settled: %d Returned: %d Active: %d", staked, returned, active) + } + // Large multi-hand totals can exceed one footer line; never wrap the frame. + for color.VisibleLen(summary) > inner-2 { + cut := strings.LastIndex(summary[:inner-2], " ") + if cut < 0 { + cut = inner - 2 + } + footer(summary[:cut], "casino_action") + summary = strings.TrimSpace(summary[cut:]) + } + footer(summary, "casino_action") + net := returned - staked + title, amount := "NET EVEN", "0 credits" + if net > 0 { + title, amount = "NET PROFIT", fmt.Sprintf("+%d credits", net) + } + if net < 0 { + title, amount = "NET LOSS", fmt.Sprintf("%d credits", net) + } + tag := netTag(net) + if settled == 0 { + title, amount, tag = "NET PENDING", dash, "warning" + } + footer(title+strings.Repeat(" ", max(1, inner-2-len(title)-color.VisibleLen(amount)))+amount, tag) + out = append(out, paint("dim", bl+strings.Repeat(h, inner)+br)) + return strings.Join(out, "\n") +} |
