package lobby import ( "context" "github.com/google/uuid" "git.sunturtle.xyz/studio/shotgun/player" "git.sunturtle.xyz/studio/shotgun/serve" ) type GameID = serve.GameID type matchMade struct { match chan GameID chall player.ID } type match chan matchMade // Lobby is a matchmaking service. type Lobby struct { // matches is dealers waiting for a match. It MUST be unbuffered. matches chan match } func New() *Lobby { return &Lobby{ matches: make(chan match), } } // Queue waits for a match and returns a unique ID for it. func (l *Lobby) Queue(ctx context.Context, p player.ID) (id GameID, chall player.ID, deal bool) { select { case m := <-l.matches: // We found a dealer waiting for a match. r := matchMade{ match: make(chan GameID, 1), chall: p, } // We don't need to check the context here because the challenger // channel is buffered and we have exclusive send access on it. m <- r // We do need to check the context here in case they disappeared. select { case <-ctx.Done(): return GameID{}, player.ID{}, false case id := <-r.match: return id, p, false } default: // do nothing } // We're a new dealer. m := make(match, 1) select { case <-ctx.Done(): return GameID{}, player.ID{}, false 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. r := matchMade{ match: make(chan GameID, 1), chall: p, } m <- r select { case <-ctx.Done(): return GameID{}, player.ID{}, false case id := <-r.match: return id, p, false } } select { case <-ctx.Done(): return GameID{}, player.ID{}, false case r := <-m: // Got our challenger. Create the game and send the ID back. id := GameID(uuid.New()) // Don't need to check context because the match channel is buffered. r.match <- id return id, r.chall, true } }