aboutsummaryrefslogtreecommitdiff
path: root/internal/game/hacking/liars_dice_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/hacking/liars_dice_test.go')
-rw-r--r--internal/game/hacking/liars_dice_test.go68
1 files changed, 68 insertions, 0 deletions
diff --git a/internal/game/hacking/liars_dice_test.go b/internal/game/hacking/liars_dice_test.go
new file mode 100644
index 0000000..59ebb92
--- /dev/null
+++ b/internal/game/hacking/liars_dice_test.go
@@ -0,0 +1,68 @@
+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")
+}