Files
ad_fun/chessstars/internal/game/manager.go
T
2026-07-15 13:29:08 +03:00

169 lines
3.2 KiB
Go

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
}