package service import ( "fmt" "time" "chess/internal/server/core" "chess/internal/server/game" "chess/internal/server/storage" "github.com/google/uuid" ) // CreateGame registers a new game with pre-constructed players func (s *Service) CreateGame(id string, whitePlayer, blackPlayer *core.Player, initialFEN string, startingTurn core.Color) error { s.mu.Lock() defer s.mu.Unlock() if _, exists := s.games[id]; exists { return fmt.Errorf("game %s already exists", id) } // Check computer game limit hasComputer := whitePlayer.Type == core.PlayerComputer || blackPlayer.Type == core.PlayerComputer if hasComputer { if s.computerGames.Load() >= MaxComputerGames { return fmt.Errorf("computer game limit reached (%d/%d)", s.computerGames.Load(), MaxComputerGames) } s.computerGames.Add(1) } // Store game with provided players s.games[id] = game.New(initialFEN, whitePlayer, blackPlayer, startingTurn) // Persist if storage enabled if s.store != nil { record := storage.GameRecord{ GameID: id, InitialFEN: initialFEN, WhitePlayerID: whitePlayer.ID, WhiteType: int(whitePlayer.Type), WhiteLevel: whitePlayer.Level, WhiteSearchTime: whitePlayer.SearchTime, BlackPlayerID: blackPlayer.ID, BlackType: int(blackPlayer.Type), BlackLevel: blackPlayer.Level, BlackSearchTime: blackPlayer.SearchTime, StartTimeUTC: time.Now().UTC(), } s.store.RecordNewGame(record) } return nil } // UpdatePlayers replaces players in an existing game func (s *Service) UpdatePlayers(gameID string, whitePlayer, blackPlayer *core.Player) error { s.mu.Lock() defer s.mu.Unlock() g, ok := s.games[gameID] if !ok { return fmt.Errorf("game not found: %s", gameID) } // Update the game's players g.UpdatePlayers(whitePlayer, blackPlayer) return nil } // GetGame retrieves a game by ID func (s *Service) GetGame(gameID string) (*game.Game, error) { s.mu.RLock() defer s.mu.RUnlock() g, ok := s.games[gameID] if !ok { return nil, fmt.Errorf("game not found: %s", gameID) } return g, nil } // GenerateGameID creates a new unique game ID func (s *Service) GenerateGameID() string { s.mu.RLock() defer s.mu.RUnlock() // Ensure UUID uniqueness (handle potential conflicts) for { id := uuid.New().String() if _, exists := s.games[id]; !exists { return id } } } // ApplyMove adds a validated move to the game history func (s *Service) ApplyMove(gameID, moveUCI, newFEN string) error { s.mu.Lock() defer s.mu.Unlock() g, ok := s.games[gameID] if !ok { return fmt.Errorf("game not found: %s", gameID) } // Determine whose turn it was before this move currentTurn := g.NextTurnColor() nextTurn := core.OppositeColor(currentTurn) // Add the new position to game history g.AddSnapshot(newFEN, moveUCI, nextTurn) // Notify waiting clients about the state change s.waiter.NotifyGame(gameID, len(g.Moves())) // Persist if storage enabled if s.store != nil { moveNumber := len(g.Moves()) record := storage.MoveRecord{ GameID: gameID, MoveNumber: moveNumber, MoveUCI: moveUCI, FENAfterMove: newFEN, PlayerColor: currentTurn.String(), MoveTimeUTC: time.Now().UTC(), } s.store.RecordMove(record) } return nil } // UpdateGameState sets the game's end state (checkmate, stalemate, etc) func (s *Service) UpdateGameState(gameID string, state core.State) error { s.mu.Lock() defer s.mu.Unlock() g, ok := s.games[gameID] if !ok { return fmt.Errorf("game not found: %s", gameID) } g.SetState(state) // Notify if game ended if state != core.StateOngoing && state != core.StatePending { s.waiter.NotifyGame(gameID, len(g.Moves())) } return nil } // SetLastMoveResult stores metadata about the last move func (s *Service) SetLastMoveResult(gameID string, result *game.MoveResult) error { s.mu.Lock() defer s.mu.Unlock() g, ok := s.games[gameID] if !ok { return fmt.Errorf("game not found: %s", gameID) } g.SetLastResult(result) return nil } // UndoMoves removes the specified number of moves from game history func (s *Service) UndoMoves(gameID string, count int) error { s.mu.Lock() defer s.mu.Unlock() g, ok := s.games[gameID] if !ok { return fmt.Errorf("game not found: %s", gameID) } originalMoveCount := len(g.Moves()) if err := g.UndoMoves(count); err != nil { return err } // Notify waiting clients about the undo s.waiter.NotifyGame(gameID, len(g.Moves())) // Delete undone moves from storage if enabled if s.store != nil { remainingMoves := originalMoveCount - count s.store.DeleteUndoneMoves(gameID, remainingMoves) } return nil } // DeleteGame removes a game from the service func (s *Service) DeleteGame(gameID string) error { s.mu.Lock() defer s.mu.Unlock() g, ok := s.games[gameID] if !ok { return fmt.Errorf("game not found: %s", gameID) } // Decrement computer game count if applicable if g.HasComputerPlayer() { s.computerGames.Add(-1) } // Remove from wait registry s.waiter.RemoveGame(gameID) delete(s.games, gameID) return nil }