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:
parent
754da18c99
commit
b70abac827
56
game/game.go
56
game/game.go
@ -19,10 +19,10 @@ import (
|
|||||||
|
|
||||||
type Match struct {
|
type Match struct {
|
||||||
// rng is the PRNG state used for this match.
|
// 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
|
// players are the players in the match. The first element is the dealer
|
||||||
// and the second is the challenger.
|
// 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
|
// shells is the list of remaining shells in the current game. It always
|
||||||
// uses shellArray as its backing array.
|
// uses shellArray as its backing array.
|
||||||
shells []bool
|
shells []bool
|
||||||
@ -50,8 +50,8 @@ type Match struct {
|
|||||||
// New creates a new match started at round 1.
|
// New creates a new match started at round 1.
|
||||||
func New(dealer, challenger player.ID) *Match {
|
func New(dealer, challenger player.ID) *Match {
|
||||||
g := &Match{
|
g := &Match{
|
||||||
rng: NewRNG(),
|
rng: newRNG(),
|
||||||
players: [2]Player{
|
players: [2]playerState{
|
||||||
{id: dealer},
|
{id: dealer},
|
||||||
{id: challenger},
|
{id: challenger},
|
||||||
},
|
},
|
||||||
@ -63,8 +63,8 @@ func New(dealer, challenger player.ID) *Match {
|
|||||||
// NextRound starts the next round of a match.
|
// NextRound starts the next round of a match.
|
||||||
func (g *Match) NextRound() {
|
func (g *Match) NextRound() {
|
||||||
g.hp = int8(g.rng.Intn(3) + 2)
|
g.hp = int8(g.rng.Intn(3) + 2)
|
||||||
g.players[0].StartRound(g.hp)
|
g.players[0].startRound(g.hp)
|
||||||
g.players[1].StartRound(g.hp)
|
g.players[1].startRound(g.hp)
|
||||||
g.round++
|
g.round++
|
||||||
g.NextGame()
|
g.NextGame()
|
||||||
}
|
}
|
||||||
@ -72,8 +72,8 @@ func (g *Match) NextRound() {
|
|||||||
// NextGame starts the next game of a round.
|
// NextGame starts the next game of a round.
|
||||||
func (g *Match) NextGame() {
|
func (g *Match) NextGame() {
|
||||||
items := g.rng.Intn(4) + 1
|
items := g.rng.Intn(4) + 1
|
||||||
g.players[0].StartGroup(&g.rng, items)
|
g.players[0].startGame(&g.rng, items)
|
||||||
g.players[1].StartGroup(&g.rng, items)
|
g.players[1].startGame(&g.rng, items)
|
||||||
shells := g.rng.Intn(6) + 2
|
shells := g.rng.Intn(6) + 2
|
||||||
for i := 0; i < shells/2; i++ {
|
for i := 0; i < shells/2; i++ {
|
||||||
g.shellArray[i] = true
|
g.shellArray[i] = true
|
||||||
@ -82,37 +82,37 @@ func (g *Match) NextGame() {
|
|||||||
g.shellArray[i] = false
|
g.shellArray[i] = false
|
||||||
}
|
}
|
||||||
g.shells = g.shellArray[:shells]
|
g.shells = g.shellArray[:shells]
|
||||||
ShuffleSlice(&g.rng, g.shells)
|
shuffle(&g.rng, g.shells)
|
||||||
g.turn = 0
|
g.turn = 0
|
||||||
g.prev = nil
|
g.prev = nil
|
||||||
g.NextTurn()
|
g.nextTurn()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextTurn advances the turn but not the match or round state.
|
// nextTurn advances the turn but not the match or round state.
|
||||||
func (g *Match) NextTurn() {
|
func (g *Match) nextTurn() {
|
||||||
g.turn++
|
g.turn++
|
||||||
g.ResetTurn()
|
g.resetTurn()
|
||||||
cur := g.CurrentPlayer()
|
cur := g.CurrentPlayer()
|
||||||
skip := cur.cuffs == CuffedSkip
|
skip := cur.cuffs == cuffedSkip
|
||||||
cur.cuffs = cur.cuffs.NextState()
|
cur.cuffs = cur.cuffs.nextState()
|
||||||
if skip {
|
if skip {
|
||||||
g.NextTurn()
|
g.nextTurn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetTurn performs start-of-turn model resets.
|
// resetTurn performs start-of-turn model resets.
|
||||||
func (g *Match) ResetTurn() {
|
func (g *Match) resetTurn() {
|
||||||
g.damage = 1
|
g.damage = 1
|
||||||
g.reveal = false
|
g.reveal = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurrentPlayer gets the current player.
|
// CurrentPlayer gets the current player.
|
||||||
func (g *Match) CurrentPlayer() *Player {
|
func (g *Match) CurrentPlayer() *playerState {
|
||||||
return &g.players[g.turn&1]
|
return &g.players[g.turn&1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opponent returns the player who is not the current player.
|
// 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]
|
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) {
|
if item < 0 || item >= len(cur.items) {
|
||||||
return errors.New("item index out of bounds")
|
return errors.New("item index out of bounds")
|
||||||
}
|
}
|
||||||
if cur.items[item].Apply(g) {
|
if cur.items[item].apply(g) {
|
||||||
cur.items[item] = ItemNone
|
cur.items[item] = itemNone
|
||||||
}
|
}
|
||||||
if g.Empty() {
|
if g.Empty() {
|
||||||
return ErrGameEnded
|
return ErrGameEnded
|
||||||
@ -162,7 +162,7 @@ func (g *Match) Empty() bool {
|
|||||||
|
|
||||||
// RoundWinner returns the player who won the current round, or nil if the
|
// RoundWinner returns the player who won the current round, or nil if the
|
||||||
// round is not over.
|
// round is not over.
|
||||||
func (g *Match) RoundWinner() *Player {
|
func (g *Match) RoundWinner() *playerState {
|
||||||
if g.players[0].hp <= 0 {
|
if g.players[0].hp <= 0 {
|
||||||
return &g.players[1]
|
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
|
// MatchWinner returns the player who has won the match, or nil if the match
|
||||||
// is not over.
|
// is not over.
|
||||||
func (g *Match) MatchWinner() *Player {
|
func (g *Match) MatchWinner() *playerState {
|
||||||
if g.round < 3 {
|
if g.round < 3 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -215,9 +215,9 @@ func (g *Match) Shoot(id player.ID, self bool) error {
|
|||||||
return ErrGameEnded
|
return ErrGameEnded
|
||||||
}
|
}
|
||||||
if !self || live {
|
if !self || live {
|
||||||
g.NextTurn()
|
g.nextTurn()
|
||||||
} else {
|
} else {
|
||||||
g.ResetTurn()
|
g.resetTurn()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -262,8 +262,8 @@ func (g *Match) DTO(id player.ID, deadline time.Time) serve.Game {
|
|||||||
}
|
}
|
||||||
return serve.Game{
|
return serve.Game{
|
||||||
Players: [2]serve.Player{
|
Players: [2]serve.Player{
|
||||||
g.players[0].DTO(),
|
g.players[0].dto(),
|
||||||
g.players[1].DTO(),
|
g.players[1].dto(),
|
||||||
},
|
},
|
||||||
Action: g.action.String(),
|
Action: g.action.String(),
|
||||||
Winner: w,
|
Winner: w,
|
||||||
|
@ -87,8 +87,8 @@ func TestGameStartGroup(t *testing.T) {
|
|||||||
msg string
|
msg string
|
||||||
args []any
|
args []any
|
||||||
}{
|
}{
|
||||||
{g.players[0].items[0] == ItemNone, "dealer has no first item: %v", []any{g.players[0].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}},
|
{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)}},
|
{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.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}},
|
{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.
|
// H*ck with the game state.
|
||||||
g.players[0].items[0] = ItemNone
|
g.players[0].items[0] = itemNone
|
||||||
g.players[1].items[0] = ItemNone
|
g.players[1].items[0] = itemNone
|
||||||
g.popShell()
|
g.popShell()
|
||||||
g.popShell()
|
g.popShell()
|
||||||
g.turn = 3
|
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.turn != i, "turn is %d, want %d", []any{g.turn, i}},
|
||||||
{g.damage != 1, "damage is %d, want 1", []any{g.damage}},
|
{g.damage != 1, "damage is %d, want 1", []any{g.damage}},
|
||||||
{g.reveal, "revealed at start", nil},
|
{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 {
|
for _, c := range checks {
|
||||||
if c.failed {
|
if c.failed {
|
||||||
@ -137,8 +137,8 @@ func TestGameNextTurn(t *testing.T) {
|
|||||||
}
|
}
|
||||||
g.damage = 2
|
g.damage = 2
|
||||||
g.reveal = true
|
g.reveal = true
|
||||||
g.Opponent().cuffs = Cuffed
|
g.Opponent().cuffs = cuffed
|
||||||
g.NextTurn()
|
g.nextTurn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,14 +155,14 @@ func TestGamePlayers(t *testing.T) {
|
|||||||
if g.Opponent().id != dealer {
|
if g.Opponent().id != dealer {
|
||||||
t.Errorf("dealer isn't opponent at start")
|
t.Errorf("dealer isn't opponent at start")
|
||||||
}
|
}
|
||||||
g.NextTurn()
|
g.nextTurn()
|
||||||
if g.CurrentPlayer().id != dealer {
|
if g.CurrentPlayer().id != dealer {
|
||||||
t.Errorf("dealer isn't current player after turn")
|
t.Errorf("dealer isn't current player after turn")
|
||||||
}
|
}
|
||||||
if g.Opponent().id != chall {
|
if g.Opponent().id != chall {
|
||||||
t.Errorf("challenger isn't opponent after turn")
|
t.Errorf("challenger isn't opponent after turn")
|
||||||
}
|
}
|
||||||
g.NextTurn()
|
g.nextTurn()
|
||||||
if g.CurrentPlayer().id != chall {
|
if g.CurrentPlayer().id != chall {
|
||||||
t.Errorf("challenger isn't current player after two turns")
|
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()
|
me, you := g.CurrentPlayer(), g.Opponent()
|
||||||
for i := 3; i < 1000; i++ {
|
for i := 3; i < 1000; i++ {
|
||||||
g.NextTurn()
|
g.nextTurn()
|
||||||
if g.CurrentPlayer() != you {
|
if g.CurrentPlayer() != you {
|
||||||
t.Errorf("wrong player after %d turns", i)
|
t.Errorf("wrong player after %d turns", i)
|
||||||
}
|
}
|
||||||
@ -240,7 +240,7 @@ func TestGamePeek(t *testing.T) {
|
|||||||
if g.Peek(chall) != &g.shellArray[0] {
|
if g.Peek(chall) != &g.shellArray[0] {
|
||||||
t.Errorf("challenger couldn't peek on own turn")
|
t.Errorf("challenger couldn't peek on own turn")
|
||||||
}
|
}
|
||||||
g.NextTurn()
|
g.nextTurn()
|
||||||
g.reveal = true
|
g.reveal = true
|
||||||
if g.Peek(dealer) != &g.shellArray[0] {
|
if g.Peek(dealer) != &g.shellArray[0] {
|
||||||
t.Errorf("dealer couldn't peek on own turn")
|
t.Errorf("dealer couldn't peek on own turn")
|
||||||
@ -444,8 +444,8 @@ func TestGameDTO(t *testing.T) {
|
|||||||
{
|
{
|
||||||
want := serve.Game{
|
want := serve.Game{
|
||||||
Players: [2]serve.Player{
|
Players: [2]serve.Player{
|
||||||
g.players[0].DTO(),
|
g.players[0].dto(),
|
||||||
g.players[1].DTO(),
|
g.players[1].dto(),
|
||||||
},
|
},
|
||||||
Action: "Start",
|
Action: "Start",
|
||||||
Round: g.round,
|
Round: g.round,
|
||||||
@ -470,8 +470,8 @@ func TestGameDTO(t *testing.T) {
|
|||||||
{
|
{
|
||||||
want := serve.Game{
|
want := serve.Game{
|
||||||
Players: [2]serve.Player{
|
Players: [2]serve.Player{
|
||||||
g.players[0].DTO(),
|
g.players[0].dto(),
|
||||||
g.players[1].DTO(),
|
g.players[1].dto(),
|
||||||
},
|
},
|
||||||
Action: "Shoot",
|
Action: "Shoot",
|
||||||
Round: g.round,
|
Round: g.round,
|
||||||
|
38
game/item.go
38
game/item.go
@ -1,30 +1,30 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
type Item int8
|
type item int8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ItemNone Item = iota
|
itemNone item = iota
|
||||||
ItemLens
|
itemLens
|
||||||
ItemCig
|
itemCig
|
||||||
ItemBeer
|
itemBeer
|
||||||
ItemCuff
|
itemCuff
|
||||||
ItemKnife
|
itemKnife
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewItem(rng *RNG) Item {
|
func newItem(rng *xoshiro) item {
|
||||||
return Item(rng.Intn(5)) + 1
|
return item(rng.Intn(5)) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i Item) Apply(g *Match) bool {
|
func (i item) apply(g *Match) bool {
|
||||||
switch i {
|
switch i {
|
||||||
case ItemNone:
|
case itemNone:
|
||||||
return false
|
return false
|
||||||
case ItemLens:
|
case itemLens:
|
||||||
// Lenses are always used, even if they have no effect.
|
// Lenses are always used, even if they have no effect.
|
||||||
g.action = actLens
|
g.action = actLens
|
||||||
g.reveal = true
|
g.reveal = true
|
||||||
return true
|
return true
|
||||||
case ItemCig:
|
case itemCig:
|
||||||
cur := g.CurrentPlayer()
|
cur := g.CurrentPlayer()
|
||||||
if cur.hp < g.hp {
|
if cur.hp < g.hp {
|
||||||
cur.hp++
|
cur.hp++
|
||||||
@ -32,7 +32,7 @@ func (i Item) Apply(g *Match) bool {
|
|||||||
}
|
}
|
||||||
g.action = actCig
|
g.action = actCig
|
||||||
return true
|
return true
|
||||||
case ItemBeer:
|
case itemBeer:
|
||||||
g.popShell()
|
g.popShell()
|
||||||
g.action = actBeer
|
g.action = actBeer
|
||||||
if g.Empty() {
|
if g.Empty() {
|
||||||
@ -40,15 +40,15 @@ func (i Item) Apply(g *Match) bool {
|
|||||||
g.NextGame()
|
g.NextGame()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
case ItemCuff:
|
case itemCuff:
|
||||||
opp := g.Opponent()
|
opp := g.Opponent()
|
||||||
if opp.cuffs != Uncuffed {
|
if opp.cuffs != uncuffed {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
g.action = actCuff
|
g.action = actCuff
|
||||||
opp.cuffs = CuffedSkip
|
opp.cuffs = cuffedSkip
|
||||||
return true
|
return true
|
||||||
case ItemKnife:
|
case itemKnife:
|
||||||
if g.damage != 1 {
|
if g.damage != 1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -62,6 +62,6 @@ func (i Item) Apply(g *Match) bool {
|
|||||||
|
|
||||||
var itemNames = [...]string{"", "🔍", "🚬", "🍺", "👮", "🔪"}
|
var itemNames = [...]string{"", "🔍", "🚬", "🍺", "👮", "🔪"}
|
||||||
|
|
||||||
func (i Item) String() string {
|
func (i item) String() string {
|
||||||
return itemNames[i]
|
return itemNames[i]
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,18 @@
|
|||||||
package game_test
|
package game
|
||||||
|
|
||||||
import (
|
import "testing"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.sunturtle.xyz/studio/shotgun/game"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestItemStrings(t *testing.T) {
|
func TestItemStrings(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
item game.Item
|
item item
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{game.ItemNone, ""},
|
{itemNone, ""},
|
||||||
{game.ItemLens, "🔍"},
|
{itemLens, "🔍"},
|
||||||
{game.ItemCig, "🚬"},
|
{itemCig, "🚬"},
|
||||||
{game.ItemBeer, "🍺"},
|
{itemBeer, "🍺"},
|
||||||
{game.ItemCuff, "👮"},
|
{itemCuff, "👮"},
|
||||||
{game.ItemKnife, "🔪"},
|
{itemKnife, "🔪"},
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
got := c.item.String()
|
got := c.item.String()
|
||||||
|
@ -7,33 +7,33 @@ import (
|
|||||||
"git.sunturtle.xyz/studio/shotgun/serve"
|
"git.sunturtle.xyz/studio/shotgun/serve"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Player struct {
|
type playerState struct {
|
||||||
id player.ID
|
id player.ID
|
||||||
hp int8
|
hp int8
|
||||||
items [8]Item
|
items [8]item
|
||||||
cuffs CuffState
|
cuffs cuffState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) StartRound(hp int8) {
|
func (p *playerState) startRound(hp int8) {
|
||||||
p.hp = hp
|
p.hp = hp
|
||||||
clear(p.items[:])
|
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++ {
|
for i := 0; i < items; i++ {
|
||||||
k := slices.Index(p.items[:], ItemNone)
|
k := slices.Index(p.items[:], itemNone)
|
||||||
if k < 0 {
|
if k < 0 {
|
||||||
break
|
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{
|
r := serve.Player{
|
||||||
HP: p.hp,
|
HP: p.hp,
|
||||||
Cuffs: p.cuffs != Uncuffed,
|
Cuffs: p.cuffs != uncuffed,
|
||||||
}
|
}
|
||||||
for k, i := range p.items {
|
for k, i := range p.items {
|
||||||
r.Items[k] = i.String()
|
r.Items[k] = i.String()
|
||||||
@ -41,22 +41,16 @@ func (p *Player) DTO() serve.Player {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is returns whether the player has the same ID as other.
|
type cuffState uint8
|
||||||
// This exists for tests.
|
|
||||||
func (p *Player) Is(other *Player) bool {
|
|
||||||
return p.id == other.id
|
|
||||||
}
|
|
||||||
|
|
||||||
type CuffState uint8
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Uncuffed CuffState = iota
|
uncuffed cuffState = iota
|
||||||
Cuffed
|
cuffed
|
||||||
CuffedSkip
|
cuffedSkip
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c CuffState) NextState() CuffState {
|
func (c cuffState) nextState() cuffState {
|
||||||
if c != Uncuffed {
|
if c != uncuffed {
|
||||||
c--
|
c--
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
|
16
game/rng.go
16
game/rng.go
@ -6,16 +6,16 @@ import (
|
|||||||
"math/bits"
|
"math/bits"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RNG is a random number generator for shotgun.
|
// xoshiro is a random number generator for shotgun.
|
||||||
//
|
//
|
||||||
// Currently xoshiro256**. Subject to change.
|
// Currently xoshiro256**. Subject to change.
|
||||||
type RNG struct {
|
type xoshiro struct {
|
||||||
w, x, y, z uint64
|
w, x, y, z uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRNG produces a new, uniquely seeded RNG.
|
// newRNG produces a new, uniquely seeded RNG.
|
||||||
func NewRNG() RNG {
|
func newRNG() xoshiro {
|
||||||
r := RNG{}
|
r := xoshiro{}
|
||||||
b := make([]byte, 8*4)
|
b := make([]byte, 8*4)
|
||||||
// Loop to ensure the state is never all-zero, which is an invalid state
|
// Loop to ensure the state is never all-zero, which is an invalid state
|
||||||
// for xoshiro.
|
// for xoshiro.
|
||||||
@ -30,7 +30,7 @@ func NewRNG() RNG {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Uint64 produces a 64-bit pseudo-random value.
|
// 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
|
w, x, y, z := rng.w, rng.x, rng.y, rng.z
|
||||||
r := bits.RotateLeft64(x*5, 7) * 9
|
r := bits.RotateLeft64(x*5, 7) * 9
|
||||||
t := x << 17
|
t := x << 17
|
||||||
@ -45,7 +45,7 @@ func (rng *RNG) Uint64() uint64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Intn produces an int in [0, n). Panics if n <= 0.
|
// 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 {
|
if n <= 0 {
|
||||||
panic("shotgun: rng.Intn max below zero")
|
panic("shotgun: rng.Intn max below zero")
|
||||||
}
|
}
|
||||||
@ -57,7 +57,7 @@ func (rng *RNG) Intn(n int) int {
|
|||||||
return int(x % uint(n))
|
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-- {
|
for i := len(s) - 1; i > 0; i-- {
|
||||||
j := rng.Intn(i + 1)
|
j := rng.Intn(i + 1)
|
||||||
s[i], s[j] = s[j], s[i]
|
s[i], s[j] = s[j], s[i]
|
||||||
|
10
main.go
10
main.go
@ -48,11 +48,11 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Post("/user/register", s.Register)
|
r.Post("/user/register", s.register)
|
||||||
r.Post("/user/login", s.Login)
|
r.Post("/user/login", s.login)
|
||||||
r.With(serve.WithSession(db)).Get("/user/logout", s.Logout)
|
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("/user/me", s.me)
|
||||||
r.With(serve.WithSession(db)).Get("/queue", s.Queue)
|
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)))
|
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)
|
slog.Info("listening", "addr", addr, "public", public)
|
||||||
http.ListenAndServe(addr, r)
|
http.ListenAndServe(addr, r)
|
||||||
|
@ -44,7 +44,7 @@ type Player struct {
|
|||||||
Cuffs bool `json:"cuffs,omitempty"`
|
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.
|
// GameStart is the JSON DTO given to each player when their game starts.
|
||||||
// Observers do not receive it.
|
// Observers do not receive it.
|
||||||
|
14
server.go
14
server.go
@ -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
|
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()
|
ctx := r.Context()
|
||||||
slog := slog.With(
|
slog := slog.With(
|
||||||
slog.String("remote", r.RemoteAddr),
|
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)
|
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()
|
ctx := r.Context()
|
||||||
slog := slog.With(
|
slog := slog.With(
|
||||||
slog.String("remote", r.RemoteAddr),
|
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)
|
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()
|
ctx := r.Context()
|
||||||
slog := slog.With(
|
slog := slog.With(
|
||||||
slog.String("remote", r.RemoteAddr),
|
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)
|
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())
|
id := serve.ReqPlayer(r.Context())
|
||||||
if id == (player.ID{}) {
|
if id == (player.ID{}) {
|
||||||
panic("missing 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)
|
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].
|
// 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())
|
p := serve.ReqPlayer(r.Context())
|
||||||
person, err := s.accept(w, r, p)
|
person, err := s.accept(w, r, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -201,7 +201,7 @@ func (s *Server) joinAndServe(p person) {
|
|||||||
}
|
}
|
||||||
// Reply with the game ID so they can share.
|
// Reply with the game ID so they can share.
|
||||||
r := serve.GameStart{
|
r := serve.GameStart{
|
||||||
ID: id,
|
ID: serve.GameID{UUID: id},
|
||||||
Dealer: deal,
|
Dealer: deal,
|
||||||
}
|
}
|
||||||
if err := wsjson.Write(context.TODO(), p.conn, r); err != nil {
|
if err := wsjson.Write(context.TODO(), p.conn, r); err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user