unexport things that don't need to be exported

Most of all, this lets staticcheck point out things that are unused.
Also delete an unused thing found as a result of this.
This commit is contained in:
Branden J Brown 2024-02-04 10:37:15 -06:00
parent 754da18c99
commit b70abac827
9 changed files with 108 additions and 118 deletions

View File

@ -19,10 +19,10 @@ import (
type Match struct {
// rng is the PRNG state used for this match.
rng RNG
rng xoshiro
// players are the players in the match. The first element is the dealer
// and the second is the challenger.
players [2]Player
players [2]playerState
// shells is the list of remaining shells in the current game. It always
// uses shellArray as its backing array.
shells []bool
@ -50,8 +50,8 @@ type Match struct {
// New creates a new match started at round 1.
func New(dealer, challenger player.ID) *Match {
g := &Match{
rng: NewRNG(),
players: [2]Player{
rng: newRNG(),
players: [2]playerState{
{id: dealer},
{id: challenger},
},
@ -63,8 +63,8 @@ func New(dealer, challenger player.ID) *Match {
// NextRound starts the next round of a match.
func (g *Match) NextRound() {
g.hp = int8(g.rng.Intn(3) + 2)
g.players[0].StartRound(g.hp)
g.players[1].StartRound(g.hp)
g.players[0].startRound(g.hp)
g.players[1].startRound(g.hp)
g.round++
g.NextGame()
}
@ -72,8 +72,8 @@ func (g *Match) NextRound() {
// NextGame starts the next game of a round.
func (g *Match) NextGame() {
items := g.rng.Intn(4) + 1
g.players[0].StartGroup(&g.rng, items)
g.players[1].StartGroup(&g.rng, items)
g.players[0].startGame(&g.rng, items)
g.players[1].startGame(&g.rng, items)
shells := g.rng.Intn(6) + 2
for i := 0; i < shells/2; i++ {
g.shellArray[i] = true
@ -82,37 +82,37 @@ func (g *Match) NextGame() {
g.shellArray[i] = false
}
g.shells = g.shellArray[:shells]
ShuffleSlice(&g.rng, g.shells)
shuffle(&g.rng, g.shells)
g.turn = 0
g.prev = nil
g.NextTurn()
g.nextTurn()
}
// NextTurn advances the turn but not the match or round state.
func (g *Match) NextTurn() {
// nextTurn advances the turn but not the match or round state.
func (g *Match) nextTurn() {
g.turn++
g.ResetTurn()
g.resetTurn()
cur := g.CurrentPlayer()
skip := cur.cuffs == CuffedSkip
cur.cuffs = cur.cuffs.NextState()
skip := cur.cuffs == cuffedSkip
cur.cuffs = cur.cuffs.nextState()
if skip {
g.NextTurn()
g.nextTurn()
}
}
// ResetTurn performs start-of-turn model resets.
func (g *Match) ResetTurn() {
// resetTurn performs start-of-turn model resets.
func (g *Match) resetTurn() {
g.damage = 1
g.reveal = false
}
// CurrentPlayer gets the current player.
func (g *Match) CurrentPlayer() *Player {
func (g *Match) CurrentPlayer() *playerState {
return &g.players[g.turn&1]
}
// Opponent returns the player who is not the current player.
func (g *Match) Opponent() *Player {
func (g *Match) Opponent() *playerState {
return &g.players[g.turn&1^1]
}
@ -128,8 +128,8 @@ func (g *Match) Apply(id player.ID, item int) error {
if item < 0 || item >= len(cur.items) {
return errors.New("item index out of bounds")
}
if cur.items[item].Apply(g) {
cur.items[item] = ItemNone
if cur.items[item].apply(g) {
cur.items[item] = itemNone
}
if g.Empty() {
return ErrGameEnded
@ -162,7 +162,7 @@ func (g *Match) Empty() bool {
// RoundWinner returns the player who won the current round, or nil if the
// round is not over.
func (g *Match) RoundWinner() *Player {
func (g *Match) RoundWinner() *playerState {
if g.players[0].hp <= 0 {
return &g.players[1]
}
@ -174,7 +174,7 @@ func (g *Match) RoundWinner() *Player {
// MatchWinner returns the player who has won the match, or nil if the match
// is not over.
func (g *Match) MatchWinner() *Player {
func (g *Match) MatchWinner() *playerState {
if g.round < 3 {
return nil
}
@ -215,9 +215,9 @@ func (g *Match) Shoot(id player.ID, self bool) error {
return ErrGameEnded
}
if !self || live {
g.NextTurn()
g.nextTurn()
} else {
g.ResetTurn()
g.resetTurn()
}
return nil
}
@ -262,8 +262,8 @@ func (g *Match) DTO(id player.ID, deadline time.Time) serve.Game {
}
return serve.Game{
Players: [2]serve.Player{
g.players[0].DTO(),
g.players[1].DTO(),
g.players[0].dto(),
g.players[1].dto(),
},
Action: g.action.String(),
Winner: w,

View File

@ -87,8 +87,8 @@ func TestGameStartGroup(t *testing.T) {
msg string
args []any
}{
{g.players[0].items[0] == ItemNone, "dealer has no first item: %v", []any{g.players[0].items}},
{g.players[1].items[0] == ItemNone, "challenger has no first item: %v", []any{g.players[1].items}},
{g.players[0].items[0] == itemNone, "dealer has no first item: %v", []any{g.players[0].items}},
{g.players[1].items[0] == itemNone, "challenger has no first item: %v", []any{g.players[1].items}},
{len(g.shells) < 2 || len(g.shells) > 8, "bad shells count %d, want 2-8", []any{len(g.shells)}},
{&g.shells[0] != &g.shellArray[0], "shells[0] is %p, want %p", []any{&g.shells[0], &g.shellArray[0]}},
{g.round != 1, "round is %d, want 1", []any{g.round}},
@ -103,8 +103,8 @@ func TestGameStartGroup(t *testing.T) {
}
}
// H*ck with the game state.
g.players[0].items[0] = ItemNone
g.players[1].items[0] = ItemNone
g.players[0].items[0] = itemNone
g.players[1].items[0] = itemNone
g.popShell()
g.popShell()
g.turn = 3
@ -128,7 +128,7 @@ func TestGameNextTurn(t *testing.T) {
{g.turn != i, "turn is %d, want %d", []any{g.turn, i}},
{g.damage != 1, "damage is %d, want 1", []any{g.damage}},
{g.reveal, "revealed at start", nil},
{g.CurrentPlayer().cuffs != Uncuffed, "cuffs is %d, want %d", []any{g.CurrentPlayer().cuffs, Uncuffed}},
{g.CurrentPlayer().cuffs != uncuffed, "cuffs is %d, want %d", []any{g.CurrentPlayer().cuffs, uncuffed}},
}
for _, c := range checks {
if c.failed {
@ -137,8 +137,8 @@ func TestGameNextTurn(t *testing.T) {
}
g.damage = 2
g.reveal = true
g.Opponent().cuffs = Cuffed
g.NextTurn()
g.Opponent().cuffs = cuffed
g.nextTurn()
}
}
@ -155,14 +155,14 @@ func TestGamePlayers(t *testing.T) {
if g.Opponent().id != dealer {
t.Errorf("dealer isn't opponent at start")
}
g.NextTurn()
g.nextTurn()
if g.CurrentPlayer().id != dealer {
t.Errorf("dealer isn't current player after turn")
}
if g.Opponent().id != chall {
t.Errorf("challenger isn't opponent after turn")
}
g.NextTurn()
g.nextTurn()
if g.CurrentPlayer().id != chall {
t.Errorf("challenger isn't current player after two turns")
}
@ -171,7 +171,7 @@ func TestGamePlayers(t *testing.T) {
}
me, you := g.CurrentPlayer(), g.Opponent()
for i := 3; i < 1000; i++ {
g.NextTurn()
g.nextTurn()
if g.CurrentPlayer() != you {
t.Errorf("wrong player after %d turns", i)
}
@ -240,7 +240,7 @@ func TestGamePeek(t *testing.T) {
if g.Peek(chall) != &g.shellArray[0] {
t.Errorf("challenger couldn't peek on own turn")
}
g.NextTurn()
g.nextTurn()
g.reveal = true
if g.Peek(dealer) != &g.shellArray[0] {
t.Errorf("dealer couldn't peek on own turn")
@ -444,8 +444,8 @@ func TestGameDTO(t *testing.T) {
{
want := serve.Game{
Players: [2]serve.Player{
g.players[0].DTO(),
g.players[1].DTO(),
g.players[0].dto(),
g.players[1].dto(),
},
Action: "Start",
Round: g.round,
@ -470,8 +470,8 @@ func TestGameDTO(t *testing.T) {
{
want := serve.Game{
Players: [2]serve.Player{
g.players[0].DTO(),
g.players[1].DTO(),
g.players[0].dto(),
g.players[1].dto(),
},
Action: "Shoot",
Round: g.round,

View File

@ -1,30 +1,30 @@
package game
type Item int8
type item int8
const (
ItemNone Item = iota
ItemLens
ItemCig
ItemBeer
ItemCuff
ItemKnife
itemNone item = iota
itemLens
itemCig
itemBeer
itemCuff
itemKnife
)
func NewItem(rng *RNG) Item {
return Item(rng.Intn(5)) + 1
func newItem(rng *xoshiro) item {
return item(rng.Intn(5)) + 1
}
func (i Item) Apply(g *Match) bool {
func (i item) apply(g *Match) bool {
switch i {
case ItemNone:
case itemNone:
return false
case ItemLens:
case itemLens:
// Lenses are always used, even if they have no effect.
g.action = actLens
g.reveal = true
return true
case ItemCig:
case itemCig:
cur := g.CurrentPlayer()
if cur.hp < g.hp {
cur.hp++
@ -32,7 +32,7 @@ func (i Item) Apply(g *Match) bool {
}
g.action = actCig
return true
case ItemBeer:
case itemBeer:
g.popShell()
g.action = actBeer
if g.Empty() {
@ -40,15 +40,15 @@ func (i Item) Apply(g *Match) bool {
g.NextGame()
}
return true
case ItemCuff:
case itemCuff:
opp := g.Opponent()
if opp.cuffs != Uncuffed {
if opp.cuffs != uncuffed {
return false
}
g.action = actCuff
opp.cuffs = CuffedSkip
opp.cuffs = cuffedSkip
return true
case ItemKnife:
case itemKnife:
if g.damage != 1 {
return false
}
@ -62,6 +62,6 @@ func (i Item) Apply(g *Match) bool {
var itemNames = [...]string{"", "🔍", "🚬", "🍺", "👮", "🔪"}
func (i Item) String() string {
func (i item) String() string {
return itemNames[i]
}

View File

@ -1,22 +1,18 @@
package game_test
package game
import (
"testing"
"git.sunturtle.xyz/studio/shotgun/game"
)
import "testing"
func TestItemStrings(t *testing.T) {
cases := []struct {
item game.Item
item item
want string
}{
{game.ItemNone, ""},
{game.ItemLens, "🔍"},
{game.ItemCig, "🚬"},
{game.ItemBeer, "🍺"},
{game.ItemCuff, "👮"},
{game.ItemKnife, "🔪"},
{itemNone, ""},
{itemLens, "🔍"},
{itemCig, "🚬"},
{itemBeer, "🍺"},
{itemCuff, "👮"},
{itemKnife, "🔪"},
}
for _, c := range cases {
got := c.item.String()

View File

@ -7,33 +7,33 @@ import (
"git.sunturtle.xyz/studio/shotgun/serve"
)
type Player struct {
type playerState struct {
id player.ID
hp int8
items [8]Item
cuffs CuffState
items [8]item
cuffs cuffState
}
func (p *Player) StartRound(hp int8) {
func (p *playerState) startRound(hp int8) {
p.hp = hp
clear(p.items[:])
}
func (p *Player) StartGroup(rng *RNG, items int) {
func (p *playerState) startGame(rng *xoshiro, items int) {
for i := 0; i < items; i++ {
k := slices.Index(p.items[:], ItemNone)
k := slices.Index(p.items[:], itemNone)
if k < 0 {
break
}
p.items[k] = NewItem(rng)
p.items[k] = newItem(rng)
}
p.cuffs = Uncuffed
p.cuffs = uncuffed
}
func (p *Player) DTO() serve.Player {
func (p *playerState) dto() serve.Player {
r := serve.Player{
HP: p.hp,
Cuffs: p.cuffs != Uncuffed,
Cuffs: p.cuffs != uncuffed,
}
for k, i := range p.items {
r.Items[k] = i.String()
@ -41,22 +41,16 @@ func (p *Player) DTO() serve.Player {
return r
}
// Is returns whether the player has the same ID as other.
// This exists for tests.
func (p *Player) Is(other *Player) bool {
return p.id == other.id
}
type CuffState uint8
type cuffState uint8
const (
Uncuffed CuffState = iota
Cuffed
CuffedSkip
uncuffed cuffState = iota
cuffed
cuffedSkip
)
func (c CuffState) NextState() CuffState {
if c != Uncuffed {
func (c cuffState) nextState() cuffState {
if c != uncuffed {
c--
}
return c

View File

@ -6,16 +6,16 @@ import (
"math/bits"
)
// RNG is a random number generator for shotgun.
// xoshiro is a random number generator for shotgun.
//
// Currently xoshiro256**. Subject to change.
type RNG struct {
type xoshiro struct {
w, x, y, z uint64
}
// NewRNG produces a new, uniquely seeded RNG.
func NewRNG() RNG {
r := RNG{}
// newRNG produces a new, uniquely seeded RNG.
func newRNG() xoshiro {
r := xoshiro{}
b := make([]byte, 8*4)
// Loop to ensure the state is never all-zero, which is an invalid state
// for xoshiro.
@ -30,7 +30,7 @@ func NewRNG() RNG {
}
// Uint64 produces a 64-bit pseudo-random value.
func (rng *RNG) Uint64() uint64 {
func (rng *xoshiro) Uint64() uint64 {
w, x, y, z := rng.w, rng.x, rng.y, rng.z
r := bits.RotateLeft64(x*5, 7) * 9
t := x << 17
@ -45,7 +45,7 @@ func (rng *RNG) Uint64() uint64 {
}
// Intn produces an int in [0, n). Panics if n <= 0.
func (rng *RNG) Intn(n int) int {
func (rng *xoshiro) Intn(n int) int {
if n <= 0 {
panic("shotgun: rng.Intn max below zero")
}
@ -57,7 +57,7 @@ func (rng *RNG) Intn(n int) int {
return int(x % uint(n))
}
func ShuffleSlice[E any, S ~[]E](rng *RNG, s S) {
func shuffle[E any, S ~[]E](rng *xoshiro, s S) {
for i := len(s) - 1; i > 0; i-- {
j := rng.Intn(i + 1)
s[i], s[j] = s[j], s[i]

10
main.go
View File

@ -48,11 +48,11 @@ func main() {
}
r := chi.NewRouter()
r.Post("/user/register", s.Register)
r.Post("/user/login", s.Login)
r.With(serve.WithSession(db)).Get("/user/logout", s.Logout)
r.With(serve.WithSession(db)).Get("/user/me", s.Me)
r.With(serve.WithSession(db)).Get("/queue", s.Queue)
r.Post("/user/register", s.register)
r.Post("/user/login", s.login)
r.With(serve.WithSession(db)).Get("/user/logout", s.logout)
r.With(serve.WithSession(db)).Get("/user/me", s.me)
r.With(serve.WithSession(db)).Get("/queue", s.queue)
r.With(middleware.Compress(5, "text/html", "text/javascript", "text/css")).Method("GET", "/*", http.FileServer(http.Dir(public)))
slog.Info("listening", "addr", addr, "public", public)
http.ListenAndServe(addr, r)

View File

@ -44,7 +44,7 @@ type Player struct {
Cuffs bool `json:"cuffs,omitempty"`
}
type GameID = uuid.UUID
type GameID struct{ uuid.UUID }
// GameStart is the JSON DTO given to each player when their game starts.
// Observers do not receive it.

View File

@ -49,7 +49,7 @@ func (s *Server) accept(w http.ResponseWriter, r *http.Request, p player.ID) (pe
return person{conn: conn, id: p}, nil
}
func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
func (s *Server) register(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
slog := slog.With(
slog.String("remote", r.RemoteAddr),
@ -90,7 +90,7 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
slog.InfoContext(ctx, "registered", "user", user)
}
func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
func (s *Server) login(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
slog := slog.With(
slog.String("remote", r.RemoteAddr),
@ -125,7 +125,7 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
slog.InfoContext(ctx, "logged in", "player", p, "id", id)
}
func (s *Server) Logout(w http.ResponseWriter, r *http.Request) {
func (s *Server) logout(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
slog := slog.With(
slog.String("remote", r.RemoteAddr),
@ -149,7 +149,7 @@ func (s *Server) Logout(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func (s *Server) Me(w http.ResponseWriter, r *http.Request) {
func (s *Server) me(w http.ResponseWriter, r *http.Request) {
id := serve.ReqPlayer(r.Context())
if id == (player.ID{}) {
panic("missing player ID")
@ -160,9 +160,9 @@ func (s *Server) Me(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(q)
}
// Queue connects players to games. The connection immediately upgrades.
// queue connects players to games. The connection immediately upgrades.
// This handler MUST be wrapped in [serve.WithPlayerID].
func (s *Server) Queue(w http.ResponseWriter, r *http.Request) {
func (s *Server) queue(w http.ResponseWriter, r *http.Request) {
p := serve.ReqPlayer(r.Context())
person, err := s.accept(w, r, p)
if err != nil {
@ -201,7 +201,7 @@ func (s *Server) joinAndServe(p person) {
}
// Reply with the game ID so they can share.
r := serve.GameStart{
ID: id,
ID: serve.GameID{UUID: id},
Dealer: deal,
}
if err := wsjson.Write(context.TODO(), p.conn, r); err != nil {