package coordinator import ( "fmt" "log" "sync" "time" "git.saintnet.tech/stryan/snengame/internal/game" "github.com/google/uuid" ) type Coordinator struct { Matches map[uuid.UUID]*Session MatchLock *sync.Mutex PlayerQueueChan chan uuid.UUID 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), CallbackChan: make(map[uuid.UUID]chan uuid.UUID), } } func (c *Coordinator) Start() { go func() { for { m := NewSession() var p1, p2 uuid.UUID p1 = <-c.PlayerQueueChan fmt.Println("p1 join") p2 = <-c.PlayerQueueChan fmt.Println("p2 join") m.Active = true m.Game = game.NewGame() c.MatchLock.Lock() c.Matches[m.ID] = m c.MatchLock.Unlock() c.CallbackChan[p1] <- m.ID c.CallbackChan[p2] <- m.ID go MatchWatcher(m) } }() go func() { for { time.Sleep(10 * time.Second) c.MatchLock.Lock() for _, v := range c.Matches { if v.Game == nil && v.Active { log.Println("clearing match with no game") delete(c.Matches, v.ID) v.Watcher <- true } } c.MatchLock.Unlock() } }() } func (c *Coordinator) Coordinate(cmd *SessionCommand) *SessionCommandResult { switch cmd.Command { case SessionCmdQuery: c.CallbackChan[cmd.ID] = make(chan uuid.UUID) c.PlayerQueueChan <- cmd.ID m := <-c.CallbackChan[cmd.ID] return &SessionCommandResult{ ID: cmd.ID, MatchID: m, Result: SessionRespFound, } case SessionCmdJoin: m, exists := c.Matches[cmd.MatchID] if !exists { return &SessionCommandResult{ ID: cmd.ID, MatchID: uuid.Nil, Result: SessionRespJoinError, } } resp := m.Join(cmd.ID) return &SessionCommandResult{ ID: cmd.ID, MatchID: m.ID, Result: resp, } case SessionCmdLeave: m, exists := c.Matches[cmd.MatchID] if !exists || !m.PlayerIn(cmd.ID) { return &SessionCommandResult{ ID: cmd.ID, MatchID: uuid.Nil, Result: SessionRespLeft, } } m.Leave(cmd.ID) return &SessionCommandResult{ ID: cmd.ID, MatchID: uuid.Nil, Result: SessionRespLeft, } case SessionCmdPlay: m, exists := c.Matches[cmd.MatchID] if !exists || !m.PlayerIn(cmd.ID) { return &SessionCommandResult{ ID: cmd.ID, MatchID: uuid.Nil, Result: SessionRespLeft, } } 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, } } func MatchWatcher(m *Session) { ticker := time.NewTicker(1 * time.Second) old_turn := -1 var old_board *game.Board old_sen_hand := -1 old_sco_hand := -1 old_sen_life := -1 old_sco_life := -1 sen_ready := false sco_ready := false for { select { case <-ticker.C: if m.Active && m.Game != nil && len(m.Broadcasts) > 0 { if m.Game.Status == game.StatusLobby && !sen_ready && m.Game.SentinalPlayer.Ready { for _, v := range m.Broadcasts { v <- SessionRespBroadcastSenReady } sen_ready = true } if m.Game.Status == game.StatusLobby && !sco_ready && m.Game.ScourgePlayer.Ready { for _, v := range m.Broadcasts { v <- SessionRespBroadcastScoReady } sco_ready = true } if m.Game.Status == game.StatusSentinalWin { for _, v := range m.Broadcasts { v <- SessionRespBroadcastSenWin } } if m.Game.Status == game.StatusScourgeWin { for _, v := range m.Broadcasts { v <- SessionRespBroadcastScoWin } } if m.p1 == uuid.Nil && sen_ready { for _, v := range m.Broadcasts { v <- SessionRespBroadcastSenLeft } sen_ready = false } if m.p2 == uuid.Nil && sco_ready { for _, v := range m.Broadcasts { v <- SessionRespBroadcastScoLeft } sco_ready = false } if old_board == nil || old_board.Sentinal != m.Game.GameBoard.Sentinal || old_board.Scourge != m.Game.GameBoard.Scourge || old_sen_hand != len(m.Game.SentinalPlayer.Hand) || old_sco_hand != len(m.Game.ScourgePlayer.Hand) || old_sen_life != m.Game.SentinalPlayer.Life || old_sco_life != m.Game.ScourgePlayer.Life { if old_board == nil { old_board = m.Game.GameBoard } else { old_board.Sentinal = m.Game.GameBoard.Sentinal old_board.Scourge = m.Game.GameBoard.Scourge } old_sen_hand = len(m.Game.SentinalPlayer.Hand) old_sco_hand = len(m.Game.ScourgePlayer.Hand) old_sen_life = m.Game.SentinalPlayer.Life old_sco_life = m.Game.ScourgePlayer.Life for _, v := range m.Broadcasts { v <- SessionRespBroadcastUpdate } } if old_turn != m.Game.CurrentTurn { old_turn = m.Game.CurrentTurn if old_turn == game.SentinalID { for k, v := range m.pMap { if v == game.SentinalID { m.Broadcasts[k] <- SessionRespBroadcastSenTurn } } } else if old_turn == game.ScourgeID { for k, v := range m.pMap { if v == game.ScourgeID { m.Broadcasts[k] <- SessionRespBroadcastScoTrun } } } } } case <-m.Watcher: close(m.Watcher) return } } }