// queue.go package main import ( "encoding/json" "log" "sync" "time" "os" ) type WaitingGame struct { Pin string `json:"pin"` HostID string `json:"host_id"` Required int `json:"required"` CreatedAt int64 `json:"created_at"` } type SlotManager struct { mu sync.Mutex capacity int available int waiting []WaitingGame active map[string]int savePath string } func NewSlotManager(cap int, savePath string) *SlotManager { return &SlotManager{capacity: cap, available: cap, waiting: make([]WaitingGame, 0), active: make(map[string]int), savePath: savePath} } func (sm *SlotManager) Load() { data, err := os.ReadFile(sm.savePath) if err != nil { return } var state struct { Available int `json:"available"` Waiting []WaitingGame `json:"waiting"` Active map[string]int `json:"active"` } if json.Unmarshal(data, &state) == nil { sm.mu.Lock() sm.available = state.Available sm.waiting = state.Waiting sm.active = state.Active sm.mu.Unlock() Info("Queue State Restored: Avail=%d, Waiting=%d, Active=%d", sm.available, len(sm.waiting), len(sm.active)) } } func (sm *SlotManager) Save() { sm.mu.Lock() defer sm.mu.Unlock() state := struct { Available int `json:"available"` Waiting []WaitingGame `json:"waiting"` Active map[string]int `json:"active"` }{sm.available, sm.waiting, sm.active} data, _ := json.Marshal(state) os.WriteFile(sm.savePath, data, 0644) } func (sm *SlotManager) PersistLoop(interval time.Duration) { ticker := time.NewTicker(interval) for range ticker.C { sm.Save() } } // RequestSlots : appelé par Host (HTTP) quand il clique "Start" func (sm *SlotManager) RequestSlots(pin, hostID string, required int) (bool, chan struct{}) { sm.mu.Lock() defer sm.mu.Unlock() if sm.available >= required { sm.available -= required sm.active[pin] = required sm.Save() return true, nil } ch := make(chan struct{}, 1) wg := WaitingGame{Pin: pin, HostID: hostID, Required: required, CreatedAt: time.Now().Unix()} sm.waiting = append(sm.waiting, wg) sm.Save() return false, ch } // ReleaseSlots : appelé quand partie finie (Game Engine) func (sm *SlotManager) ReleaseSlots(pin string, count int) { sm.mu.Lock() defer sm.mu.Unlock() reserved := sm.active[pin] if reserved == 0 { return } releaseCount := min(count, reserved) sm.available += releaseCount if releaseCount >= reserved { delete(sm.active, pin) } else { sm.active[pin] = reserved - releaseCount } // Traiter file d'attente newWaiting := make([]WaitingGame, 0, len(sm.waiting)) for _, wg := range sm.waiting { if sm.available >= wg.Required { sm.available -= wg.Required sm.active[wg.Pin] = wg.Required // NOTIFIER LE GAME ENGINE (via fonction globale) go notifyQueueReady(wg.Pin) // Goroutine pour ne pas bloquer le lock } else { newWaiting = append(newWaiting, wg) } } sm.waiting = newWaiting sm.Save() } func (sm *SlotManager) GetQueuePosition(pin string) int { sm.mu.Lock() defer sm.mu.Unlock() for i, wg := range sm.waiting { if wg.Pin == pin { return i + 1 } } return -1 } // notifyQueueReady est appelé par Queue Manager quand des slots se libèrent // Elle doit déclencher le démarrage de la partie dans game.go func notifyQueueReady(pin string) { Info("Queue Slot Granted -> Starting Game: %s", pin) // Appelle la fonction de game.go (package main) StartGameLoop(hub, store, pin) }