bullscows_test.gno
3.69 Kb · 141 lines
1package bullscows
2
3import (
4 "strconv"
5 "strings"
6 "testing"
7
8 "gno.land/p/nt/testutils/v0"
9)
10
11func TestParseGuess(t *testing.T) {
12 cases := []struct {
13 in string
14 ok bool
15 }{
16 {"1972", true},
17 {"0123", true},
18 {"11 2", false},
19 {"123", false},
20 {"12345", false},
21 {"1123", false}, // repeated digit
22 {"12a3", false},
23 }
24 for _, c := range cases {
25 if _, ok := parseGuess(c.in); ok != c.ok {
26 t.Fatalf("parseGuess(%q) ok = %v, want %v", c.in, ok, c.ok)
27 }
28 }
29}
30
31func TestScore(t *testing.T) {
32 target := [4]int{1, 9, 7, 2}
33
34 if b, c := score(target, [4]int{1, 9, 7, 2}); b != 4 || c != 0 {
35 t.Fatalf("exact match: got bulls=%d cows=%d, want 4/0", b, c)
36 }
37 if b, c := score(target, [4]int{2, 7, 9, 1}); b != 0 || c != 4 {
38 t.Fatalf("full rotation: got bulls=%d cows=%d, want 0/4", b, c)
39 }
40 if b, c := score(target, [4]int{1, 3, 4, 5}); b != 1 || c != 0 {
41 t.Fatalf("one bull: got bulls=%d cows=%d, want 1/0", b, c)
42 }
43 if b, c := score(target, [4]int{5, 6, 7, 8}); b != 1 || c != 0 {
44 t.Fatalf("no overlap but position 7: got bulls=%d cows=%d, want 1/0", b, c)
45 }
46}
47
48func TestNewSecretDistinctDigits(t *testing.T) {
49 for seed := int64(0); seed < 50; seed++ {
50 d := newSecret(seed)
51 var seen [10]bool
52 for _, v := range d {
53 if v < 0 || v > 9 {
54 t.Fatalf("digit out of range: %d", v)
55 }
56 if seen[v] {
57 t.Fatalf("newSecret(%d) = %v has repeated digit", seed, d)
58 }
59 seen[v] = true
60 }
61 }
62}
63
64func TestAddRecordKeepsFastestSorted(t *testing.T) {
65 records = nil
66 addRecord(record{player: "a", attempts: 10, round: 1})
67 addRecord(record{player: "b", attempts: 3, round: 2})
68 addRecord(record{player: "c", attempts: 7, round: 3})
69 addRecord(record{player: "d", attempts: 1, round: 4})
70 addRecord(record{player: "e", attempts: 9, round: 5})
71 addRecord(record{player: "f", attempts: 2, round: 6}) // pushes out the worst
72
73 if len(records) != maxRecords {
74 t.Fatalf("len(records) = %d, want %d", len(records), maxRecords)
75 }
76 want := []int{1, 2, 3, 7, 9}
77 for i, w := range want {
78 if records[i].attempts != w {
79 t.Fatalf("records[%d].attempts = %d, want %d", i, records[i].attempts, w)
80 }
81 }
82}
83
84func TestAddHistoryCaps(t *testing.T) {
85 history = nil
86 for i := 0; i < maxHistory+5; i++ {
87 addHistory(guessLog{player: "p", guess: strconv.Itoa(i), bulls: 0, cows: 0})
88 }
89 if len(history) != maxHistory {
90 t.Fatalf("len(history) = %d, want %d", len(history), maxHistory)
91 }
92 if history[len(history)-1].guess != strconv.Itoa(maxHistory+4) {
93 t.Fatalf("history did not keep the most recent entries")
94 }
95}
96
97// Crossing: Guess mutates realm state, needs cur realm + cross(cur).
98func TestGuessRoundtrip(cur realm, t *testing.T) {
99 alice := testutils.TestAddress("alice")
100 testing.SetRealm(testing.NewUserRealm(alice))
101
102 round = 1
103 attempts = 0
104 history = nil
105 records = nil
106 secret = [4]int{1, 9, 7, 2}
107
108 out := Guess(cross(cur), "1234")
109 if !strings.Contains(out, "bulls") {
110 t.Fatalf("Guess() = %q, want bulls/cows feedback", out)
111 }
112 if attempts != 1 {
113 t.Fatalf("attempts = %d, want 1", attempts)
114 }
115
116 testing.SetRealm(testing.NewUserRealm(alice))
117 out = Guess(cross(cur), "1972")
118 if !strings.Contains(out, "SOLVED") {
119 t.Fatalf("Guess() = %q, want a solved message", out)
120 }
121 if round != 2 {
122 t.Fatalf("round = %d, want 2 (new round should start on solve)", round)
123 }
124 if len(records) != 1 || records[0].attempts != 2 {
125 t.Fatalf("records = %v, want one entry with attempts=2", records)
126 }
127}
128
129func TestGiveUpStartsNewRound(cur realm, t *testing.T) {
130 round = 1
131 attempts = 3
132 secret = [4]int{4, 5, 6, 7}
133
134 out := GiveUp(cross(cur))
135 if !strings.Contains(out, "4567") {
136 t.Fatalf("GiveUp() = %q, want it to reveal 4567", out)
137 }
138 if round != 2 || attempts != 0 {
139 t.Fatalf("round=%d attempts=%d, want round=2 attempts=0", round, attempts)
140 }
141}