package coordinator import ( "log" "sync" "time" "git.saintnet.tech/tomecraft/tome_game" . "git.saintnet.tech/tomecraft/tome_lib" "github.com/google/uuid" ) type Coordinator struct { Matches map[uuid.UUID]*Session MatchLock *sync.Mutex PlayerQueueChan chan uuid.UUID PlayerPool map[MMRPlayer]bool CallbackChan map[uuid.UUID]chan uuid.UUID } func NewCoordinator() *Coordinator { return &Coordinator{ Matches: make(map[uuid.UUID]*Session), MatchLock: &sync.Mutex{}, PlayerQueueChan: make(chan uuid.UUID), PlayerPool: make(map[MMRPlayer]bool), CallbackChan: make(map[uuid.UUID]chan uuid.UUID), } } func (c *Coordinator) Start() { go MatchMaker(c) go MatchCleaner(c) go QueueCleaner(c) } func (c *Coordinator) Coordinate(cmd *SessionCommand) *SessionCommandResult { switch cmd.Command { case SessionCmdQuery: c.CallbackChan[cmd.ID] = make(chan uuid.UUID) c.PlayerQueueChan <- cmd.ID ticker := time.NewTicker(5 * time.Minute) select { case m := <-c.CallbackChan[cmd.ID]: return &SessionCommandResult{ ID: cmd.ID, MatchID: m, Result: SessionRespFound, } case <-ticker.C: return &SessionCommandResult{ ID: cmd.ID, MatchID: uuid.Nil, Result: SessionRespError, } } case SessionCmdJoin: m, exists := c.Matches[cmd.MatchID] if !exists { log.Printf("player %v tried to join non-existent match %v", cmd.ID, cmd.MatchID) return &SessionCommandResult{ ID: cmd.ID, MatchID: uuid.Nil, Result: SessionRespJoinError, } } resp := m.Join(cmd.ID) if m.p1 != uuid.Nil && m.p2 != uuid.Nil { log.Printf("Starting game for %v and %v\n", m.p1, m.p2) d1 := []int{} d2 := []int{} if m.p1Deck != nil { d1 = m.p1Deck } if m.p2Deck != nil { d2 = m.p2Deck } m.Game = tome_game.NewGame(d1, d2) m.Active = true } return &SessionCommandResult{ ID: cmd.ID, MatchID: m.ID, Result: resp, } case SessionCmdReady: m, exists := c.Matches[cmd.MatchID] if !exists { return &SessionCommandResult{ ID: cmd.ID, MatchID: uuid.Nil, Result: SessionRespError, } } if m.p1 != uuid.Nil && m.p2 != uuid.Nil { return &SessionCommandResult{ ID: cmd.ID, MatchID: m.ID, Result: SessionRespReady, } } else { return &SessionCommandResult{ ID: cmd.ID, MatchID: uuid.Nil, Result: SessionRespError, } } case SessionCmdLoadDeck: m, exists := c.Matches[cmd.MatchID] if !exists || !m.PlayerIn(cmd.ID) { return &SessionCommandResult{ ID: cmd.ID, MatchID: uuid.Nil, Result: SessionRespError, } } if m.Game != nil && m.Game.Status != StatusLobby { return &SessionCommandResult{ ID: cmd.ID, MatchID: m.ID, Result: SessionRespLoadDeckError, } } resp := m.LoadDeck(cmd.ID, cmd.Data) return &SessionCommandResult{ ID: cmd.ID, MatchID: m.ID, Result: resp, } case SessionCmdLeave: m, exists := c.Matches[cmd.MatchID] if exists && m.PlayerIn(cmd.ID) { m.Leave(cmd.ID) } else { for k, _ := range c.PlayerPool { if k.Id == m.ID { delete(c.PlayerPool, k) } } } return &SessionCommandResult{ ID: cmd.ID, MatchID: uuid.Nil, Result: SessionRespLeft, } case SessionCmdPlay: m, exists := c.Matches[cmd.MatchID] if !exists || !m.PlayerIn(cmd.ID) || m.Game == nil { return &SessionCommandResult{ ID: cmd.ID, MatchID: uuid.Nil, Result: SessionRespError, } } resp := m.Play(cmd.ID, cmd.GameCommand) return &SessionCommandResult{ ID: cmd.ID, MatchID: m.ID, Result: SessionRespPlayed, GameResult: resp, } case SessionCmdPoll: m, exists := c.Matches[cmd.MatchID] if exists { _, exists = m.Broadcasts[cmd.ID] if !exists { log.Printf("%v has opted in to polling", cmd.ID) m.Broadcasts[cmd.ID] = make(chan SessionResp, 10) } select { case res := <-m.Broadcasts[cmd.ID]: return &SessionCommandResult{ ID: cmd.ID, MatchID: m.ID, Result: res, } default: return &SessionCommandResult{ ID: cmd.ID, MatchID: m.ID, Result: SessionRespBroadcastNone, } } } } return &SessionCommandResult{ ID: cmd.ID, MatchID: uuid.Nil, Result: SessionRespError, } }