package hacking import ( "strings" "testing" ) // Regression: round resolution (challenge and exact calls) passed an // already-used strings.Builder BY VALUE into checkGameOver, panicking with // "illegal use of non-zero Builder copied by value" on ordinary input — a // forced challenge resolves on the very first bid. A panic here runs on the // game owner goroutine, which has no recovery, so it terminated the server. // See building_guide/concurrency.md for the owner's no-recovery guarantee. func TestLiarsDiceForcedChallengeDoesNotPanic(t *testing.T) { g := NewLiarsDiceGame() g.Init(1) // An impossible bid deterministically forces an immediate AI challenge. out, _, _ := g.HandleInput("bid 100 6") if !strings.Contains(out, "Actual count") { t.Fatalf("challenge output missing the reveal: %q", out) } if !strings.Contains(out, "You lose a fragment") && !strings.Contains(out, "System loses a fragment") { t.Fatalf("challenge did not resolve a fragment loss: %q", out) } } func TestLiarsDiceCallAndExactResolveWithoutPanic(t *testing.T) { // Call path: bid, let the system raise, then challenge it. g := NewLiarsDiceGame() g.Init(1) if out, _, _ := g.HandleInput("bid 2 3"); out == "" { t.Fatal("opening bid produced no output") } if out, _, _ := g.HandleInput("call"); out == "" { t.Fatal("call produced no output") } // Exact path: bid, let the system raise, then claim exact. g = NewLiarsDiceGame() g.Init(1) if out, _, _ := g.HandleInput("bid 2 3"); out == "" { t.Fatal("opening bid produced no output") } if out, _, _ := g.HandleInput("exact"); out == "" { t.Fatal("exact produced no output") } } func TestLiarsDiceFullLossGameResolvesWithoutPanic(t *testing.T) { // Bid high enough that the system always calls and the player always // loses a fragment, driving the game to its terminal resolution. g := NewLiarsDiceGame() g.Init(1) for i := 0; i < 10; i++ { out, done, won := g.HandleInput("bid 100 6") if done { if won { t.Fatal("all-in losing bids cannot win") } return } if out == "" { t.Fatal("round produced no output") } } t.Fatal("game did not terminate within 10 forced-loss rounds") }