package game import ( "crypto/rand" "encoding/hex" "errors" "sync" "time" "chessstars/internal/engine" ) var ErrNotFound = errors.New("game: not found") var ErrAlreadyJoined = errors.New("game: already has an opponent") var ErrSelfJoin = errors.New("game: can't join your own game") type Entry struct { mu sync.Mutex Engine *engine.Engine ID string WhiteID int64 WhiteName string BlackID int64 BlackName string VsBot bool BotDepth int Scored bool CreatedAt time.Time Variant engine.Variant StartFEN string RuleFlags engine.RuleFlags } func (e *Entry) Lock() { e.mu.Lock() } func (e *Entry) Unlock() { e.mu.Unlock() } func (e *Entry) Open() bool { return !e.VsBot && e.BlackID == 0 } type Manager struct { mu sync.Mutex games map[string]*Entry } func NewManager() *Manager { return &Manager{games: make(map[string]*Entry)} } func newID() string { b := make([]byte, 16) _, _ = rand.Read(b) return hex.EncodeToString(b) } func (m *Manager) Create(variant engine.Variant, fen string, rules engine.RuleFlags, whiteID int64, whiteName string, blackID int64, blackName string, vsBot bool, botDepth int) (*Entry, error) { id := newID() eng, err := engine.New(variant, fen, rules, id) if err != nil { return nil, err } entry := &Entry{ Engine: eng, ID: id, WhiteID: whiteID, WhiteName: whiteName, BlackID: blackID, BlackName: blackName, VsBot: vsBot, BotDepth: botDepth, CreatedAt: time.Now(), Variant: variant, StartFEN: fen, RuleFlags: rules, } m.mu.Lock() m.games[id] = entry m.mu.Unlock() return entry, nil } func (m *Manager) Get(id string) (*Entry, error) { m.mu.Lock() defer m.mu.Unlock() e, ok := m.games[id] if !ok { return nil, ErrNotFound } return e, nil } func (m *Manager) Join(id string, userID int64, userName string) (*Entry, error) { m.mu.Lock() e, ok := m.games[id] m.mu.Unlock() if !ok { return nil, ErrNotFound } e.Lock() defer e.Unlock() if !e.Open() { return nil, ErrAlreadyJoined } if e.WhiteID == userID { return nil, ErrSelfJoin } e.BlackID = userID e.BlackName = userName return e, nil } func (m *Manager) ListOpen() []*Entry { m.mu.Lock() defer m.mu.Unlock() var out []*Entry for _, e := range m.games { e.Lock() open := e.Open() e.Unlock() if open { out = append(out, e) } } return out } func (m *Manager) ListForUser(userID int64) []*Entry { m.mu.Lock() defer m.mu.Unlock() var out []*Entry for _, e := range m.games { e.Lock() mine := e.WhiteID == userID || e.BlackID == userID e.Unlock() if mine { out = append(out, e) } } return out } func (m *Manager) ListAll() []*Entry { m.mu.Lock() defer m.mu.Unlock() out := make([]*Entry, 0, len(m.games)) for _, e := range m.games { out = append(out, e) } return out } func (m *Manager) Cleanup(finishedTTL, openTTL time.Duration) int { now := time.Now() m.mu.Lock() defer m.mu.Unlock() removed := 0 for id, e := range m.games { e.Lock() age := now.Sub(e.CreatedAt) status := e.Engine.Status() shouldRemove := (status != engine.StatusOngoing && age > finishedTTL) || (e.Open() && age > openTTL) e.Unlock() if shouldRemove { delete(m.games, id) removed++ } } return removed }