parent
935fe8e2e4
commit
e95d526266
@ -41,6 +41,12 @@ 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
|
||||
|
||||
const (
|
||||
|
@ -15,11 +15,14 @@ type Lobby struct {
|
||||
mu sync.Mutex
|
||||
// games is the set of all active games in the lobby.
|
||||
games map[GameID]*game.Game
|
||||
// matches is dealers waiting for a match. It MUST be unbuffered.
|
||||
matches chan match
|
||||
}
|
||||
|
||||
func New() *Lobby {
|
||||
return &Lobby{
|
||||
games: make(map[GameID]*game.Game),
|
||||
matches: make(chan match),
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,12 +36,11 @@ func (l *Lobby) Game(id GameID) *game.Game {
|
||||
// Start begins a new game in the lobby.
|
||||
// The caller must be able to distinguish the dealer's and challenger's conns
|
||||
// in order to provide correct game start DTOs to each.
|
||||
func (l *Lobby) Start(id GameID, dealer, challenger player.ID) GameID {
|
||||
func (l *Lobby) Start(id GameID, dealer, challenger player.ID) {
|
||||
g := game.New(dealer, challenger)
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.games[id] = g
|
||||
return id
|
||||
}
|
||||
|
||||
// Finish removes a game from the lobby.
|
||||
|
70
lobby/match.go
Normal file
70
lobby/match.go
Normal file
@ -0,0 +1,70 @@
|
||||
package lobby
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.sunturtle.xyz/studio/shotgun/player"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type match struct {
|
||||
dealer player.ID
|
||||
chall chan player.ID
|
||||
match chan GameID
|
||||
}
|
||||
|
||||
// Queue waits for a match.
|
||||
// This may cause a new match to be created.
|
||||
func (l *Lobby) Queue(ctx context.Context, p player.ID) GameID {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return GameID{}
|
||||
case m := <-l.matches:
|
||||
// We found a dealer waiting for a match.
|
||||
// We don't need to check the context here because the challenger
|
||||
// channel is buffered and we have exclusive send access on it.
|
||||
m.chall <- p
|
||||
// We do need to check the context here in case they disappeared.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return GameID{}
|
||||
case id := <-m.match:
|
||||
return id
|
||||
}
|
||||
default: // do nothing
|
||||
}
|
||||
// We're a new dealer.
|
||||
m := match{
|
||||
dealer: p,
|
||||
chall: make(chan player.ID, 1),
|
||||
match: make(chan GameID, 1),
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return GameID{}
|
||||
case l.matches <- m:
|
||||
// Our match is submitted. Move on.
|
||||
case m := <-l.matches:
|
||||
// We might have become a dealer at the same time someone else did.
|
||||
// We created our match, but we can try to get theirs as well and
|
||||
// never send ours, since l.matches is unbuffered.
|
||||
m.chall <- p
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return GameID{}
|
||||
case id := <-m.match:
|
||||
return id
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return GameID{}
|
||||
case chall := <-m.chall:
|
||||
// Got our challenger. Create the game and send the ID back.
|
||||
id := uuid.New()
|
||||
l.Start(id, p, chall)
|
||||
// Don't need to check context because the match channel is buffered.
|
||||
m.match <- id
|
||||
return id
|
||||
}
|
||||
}
|
49
lobby/match_test.go
Normal file
49
lobby/match_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
package lobby_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"git.sunturtle.xyz/studio/shotgun/lobby"
|
||||
"git.sunturtle.xyz/studio/shotgun/player"
|
||||
)
|
||||
|
||||
func TestQueue(t *testing.T) {
|
||||
const N = 1000 // must be even
|
||||
games := make([]lobby.GameID, 0, N)
|
||||
ch := make(chan lobby.GameID)
|
||||
l := lobby.New()
|
||||
for i := 0; i < 100; i++ {
|
||||
games = games[:0]
|
||||
for i := 0; i < N; i++ {
|
||||
i := i
|
||||
go func() {
|
||||
ch <- l.Queue(context.Background(), player.ID{uint8(i), uint8(i >> 8)})
|
||||
}()
|
||||
}
|
||||
for i := 0; i < N; i++ {
|
||||
games = append(games, <-ch)
|
||||
}
|
||||
// Every unique game ID should appear exactly twice.
|
||||
counts := make(map[lobby.GameID]int, N/2)
|
||||
for _, id := range games {
|
||||
counts[id]++
|
||||
}
|
||||
for id, c := range counts {
|
||||
if c != 2 {
|
||||
t.Errorf("game %v appears %d times", id, c)
|
||||
}
|
||||
}
|
||||
// Every game should have two different players.
|
||||
for _, id := range games {
|
||||
g := l.Game(id)
|
||||
if g == nil {
|
||||
t.Errorf("game %v was created but doesn't exist", g)
|
||||
continue
|
||||
}
|
||||
if g.CurrentPlayer().Is(g.Opponent()) {
|
||||
t.Errorf("game %v matched with self", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user