// Package state provides game state machine management with event callbacks. package state import ( "image" "sync" "time" "git.cloonar.com/openclawd/iso-bot/pkg/plugin" ) // Transition records a state change. type Transition struct { From plugin.GameState To plugin.GameState Timestamp time.Time } // Manager tracks game state and fires callbacks on transitions. type Manager struct { mu sync.RWMutex current plugin.GameState previous plugin.GameState enterTime time.Time history []Transition callbacks map[plugin.GameState][]func(Transition) detector plugin.GameDetector } // NewManager creates a state manager with the given detector. func NewManager(detector plugin.GameDetector) *Manager { return &Manager{ current: plugin.StateUnknown, enterTime: time.Now(), callbacks: make(map[plugin.GameState][]func(Transition)), detector: detector, } } // Update analyzes the current frame and updates state. func (m *Manager) Update(frame image.Image) plugin.GameState { newState := m.detector.DetectState(frame) m.mu.Lock() defer m.mu.Unlock() if newState != m.current { t := Transition{ From: m.current, To: newState, Timestamp: time.Now(), } m.history = append(m.history, t) m.previous = m.current m.current = newState m.enterTime = time.Now() // Fire callbacks outside lock? For simplicity, keep in lock for now. for _, cb := range m.callbacks[newState] { cb(t) } } return m.current } // Current returns the current game state. func (m *Manager) Current() plugin.GameState { m.mu.RLock() defer m.mu.RUnlock() return m.current } // TimeInState returns how long we've been in the current state. func (m *Manager) TimeInState() time.Duration { m.mu.RLock() defer m.mu.RUnlock() return time.Since(m.enterTime) } // OnState registers a callback for entering a specific state. func (m *Manager) OnState(state plugin.GameState, cb func(Transition)) { m.mu.Lock() defer m.mu.Unlock() m.callbacks[state] = append(m.callbacks[state], cb) }