package game import ( "fmt" "strings" "testing" "thehouseoficarus/internal/color" "thehouseoficarus/internal/net" "thehouseoficarus/internal/prediction" "thehouseoficarus/internal/world" ) func TestPredictionOneComplementaryBarPerMarket(t *testing.T) { for _, unicode := range []bool{true, false} { t.Run(fmt.Sprint(unicode), func(t *testing.T) { g, s, conn, m := predictionFixture(t) s.Player.Options["unicode"] = unicode for _, detail := range []bool{false, true} { conn.data = nil if detail { g.predictionView(s, m, nil) } else { g.predictionList(s, []prediction.Market{m}, 1, "") } output := string(conn.data) bars := 0 for _, line := range strings.Split(output, "\n") { if strings.Contains(line, "█") || strings.Contains(line, "#") && strings.Contains(line, ".") { bars++ } } if bars != 1 { t.Fatalf("detail=%v: got %d bars, want one\n%s", detail, bars, output) } if !strings.Contains(output, "YES") || !strings.Contains(output, "42.1%") || !strings.Contains(output, "57.9%") || !strings.Contains(output, "NO") { t.Fatal("missing two-sided odds labels") } if strings.Contains(output, m.Description) || strings.Contains(output, "RESOLUTION RULES") { t.Fatal("resolution text shown by default") } if detail { for _, quote := range []string{"CHOOSE YOUR OUTCOME", "Outcome", "Buy", "Sell", "43", "42", "58", "57"} { if !strings.Contains(output, quote) { t.Fatalf("missing integer quote %q", quote) } } } } }) } } func TestPredictionRulesFramePreservesTextWithoutCarriageReturns(t *testing.T) { for _, width := range []int{40, 80} { t.Run(fmt.Sprint(width), func(t *testing.T) { g, s, conn, m := predictionFixture(t) s.Player.Options["wrap_width"] = width url := "https://example.org/" + strings.Repeat("long-resolution-source", 10) m.Description = strings.Repeat("This market resolves according to the party controlling the chamber. ", 12) + "\r\n\r\n" + url predictionTestServer(t, g, m) g.doUse(s, "market") g.handleGameCommand(s, "1") if strings.Contains(string(conn.data), "RESOLUTION RULES") { t.Fatal("rules appeared before request") } conn.data = nil g.handleGameCommand(s, "rules") text := strings.ReplaceAll(string(conn.data), "\r\n", "\n") if strings.Contains(text, "\r") { t.Fatal("bare CR inside frame can overwrite text") } var body strings.Builder for _, line := range strings.Split(text, "\n") { if strings.HasPrefix(line, "║") { if !strings.HasSuffix(line, "║") { t.Fatalf("missing right edge: %q", line) } if color.VisibleLen(line) != width { t.Fatalf("row width %d, want %d: %q", color.VisibleLen(line), width, line) } body.WriteString(strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "║"), "║"))) } } if !strings.Contains(text, "RESOLUTION RULES") || !strings.Contains(body.String(), url) { t.Fatal("rules or unbroken URL was lost during wrapping") } if s.PredictionScreen != "view" || s.PredictionSelected != m.ID { t.Fatal("reading rules lost selected market") } }) } } func TestPredictionLookUsesCasinoMenuAndRoomFirstOrder(t *testing.T) { g, s, conn, m := predictionFixture(t) g.Hub = net.NewHub() g.MobStore = world.NewMobStore(t.TempDir()) s.Player.Options["tiny_map"] = "off" s.Player.Options["casino_hints"] = "once" predictionTestServer(t, g, m) g.doUse(s, "counter") g.handleGameCommand(s, "1") conn.data = nil g.handleGameCommand(s, "look") text := strings.ReplaceAll(string(conn.data), "\r\n", "\n") room, screen := strings.Index(text, "Test Counter"), strings.Index(text, "THE MARKET") if room < 0 || screen < room { t.Fatalf("look not room first: %s", text) } expected := g.renderCasinoCommands(s, "Available 10000 credits", "", g.predictionCommands(s)) if !strings.Contains(text, expected) { t.Fatalf("look did not use the shared casino menu\nwant: %s\ngot: %s", expected, text) } for _, unwanted := range []string{"YOUR NEXT MOVE", "what -", "what /", "RESOLUTION RULES"} { if strings.Contains(text, unwanted) { t.Fatalf("look leaked %q", unwanted) } } conn.data = nil g.handleGameCommand(s, "what") text = strings.ReplaceAll(string(conn.data), "\r\n", "\n") if !strings.Contains(text, g.casinoMenuCommands(s, g.predictionCommands(s))) { t.Fatal("normal what did not use casino hint renderer") } if g.predictionInputOwned(s, "look") || g.predictionInputOwned(s, "what") { t.Fatal("counter hijacked normal world command") } conn.data = nil s.Player.Options["casino_hints"] = "off" g.handleGameCommand(s, "look") if strings.Contains(string(conn.data), "view N") { t.Fatal("look ignored casino_hints off") } } func TestPredictionFrameLongColoredToken(t *testing.T) { g, s, _, _ := predictionFixture(t) s.Player.Options["color"] = "xterm256" token := g.colorize(s, "casino_action", strings.Repeat("x", 160)) lines := predictionFrameLines(token, 36) for _, line := range lines { if strings.Contains(line, "\r") || color.VisibleLen(line) > 36 { t.Fatalf("unsafe wrapped row %q", line) } } } func TestPredictionOddsBarsAlign(t *testing.T) { g, s, _, _ := predictionFixture(t) s.Player.Options["color"] = "none" target := 36 pairs := [][2]string{{"Yes", "No"}, {"M80", "TYLOO"}, {"9z", "Natus Vincere"}, {"Falcons", "Saints"}} cells := make([]string, 0, len(pairs)) for _, pair := range pairs { m := prediction.Market{ID: "1", Outcomes: `["` + pair[0] + `","` + pair[1] + `"]`, Tokens: `["a","b"]`, Prices: `["0.421","0.579"]`} cells = append(cells, g.predictionOddsCell(s, m, target)) } start := strings.Index(cells[0], "█") if start < 0 { t.Fatalf("no bar: %q", cells[0]) } for _, cell := range cells { if got := strings.Index(cell, "█"); got != start { t.Fatalf("bar offset %d, want %d: %q", got, start, cell) } if color.VisibleLen(cell) != target { t.Fatalf("cell width %d, want %d: %q", color.VisibleLen(cell), target, cell) } } }