// FILE: internal/service/service.go package service import ( "fmt" "strings" "chess/internal/board" "chess/internal/core" "chess/internal/engine" "chess/internal/game" ) type Service struct { games map[string]*game.Game engine *engine.UCI } func New() (*Service, error) { eng, err := engine.New() if err != nil { return nil, fmt.Errorf("failed to initialize engine: %v", err) } return &Service{ games: make(map[string]*game.Game), engine: eng, }, nil } func (s *Service) NewGame(id string, whiteType, blackType core.PlayerType, fen ...string) error { initialFEN := board.StartingFEN if len(fen) > 0 && fen[0] != "" { initialFEN = fen[0] } // Use the engine to validate and canonicalize the FEN s.engine.NewGame() s.engine.SetPosition(initialFEN, []string{}) validatedFEN, err := s.engine.GetFEN() if err != nil { return fmt.Errorf("could not get FEN from engine: %v", err) } b, err := board.FEN(validatedFEN) if err != nil { return fmt.Errorf("engine returned invalid FEN: %v", err) } startingTurn := b.Turn() // Setup players based on types whitePlayer := &core.Player{ ID: "white", Type: whiteType, } if whiteType == core.PlayerComputer { whitePlayer.ID = "stockfish-white" } blackPlayer := &core.Player{ ID: "black", Type: blackType, } if blackType == core.PlayerComputer { blackPlayer.ID = "stockfish-black" } s.games[id] = game.New(validatedFEN, whitePlayer, blackPlayer, startingTurn) return nil } func (s *Service) MakeHumanMove(gameID, uci string) error { // Basic move format validation uci = strings.ToLower(strings.TrimSpace(uci)) if len(uci) < 4 || len(uci) > 5 { return fmt.Errorf("invalid move format: expected e2e4 or e7e8q") } g, ok := s.games[gameID] if !ok { return fmt.Errorf("game not found") } // Check if it's human's turn if g.NextPlayer().Type != core.PlayerHuman { return fmt.Errorf("not a human player's turn") } currentFEN := g.CurrentFEN() humanColor := g.NextTurn() // Try to apply human move s.engine.SetPosition(currentFEN, []string{uci}) // Get FEN after human move to check if move was legal humanMoveFEN, err := s.engine.GetFEN() if err != nil { return fmt.Errorf("failed to get position: %v", err) } // If position didn't change, move was illegal if humanMoveFEN == currentFEN { return fmt.Errorf("illegal move") } // Record human move g.AddSnapshot(humanMoveFEN, uci, core.OppositeColor(humanColor)) // Check if opponent has any legal moves s.engine.SetPosition(humanMoveFEN, []string{}) search, _ := s.engine.Search(100) // Quick search to check for legal moves result := &game.MoveResult{ Move: uci, Player: humanColor, GameState: core.StateOngoing, } if search.BestMove == "" || search.BestMove == "(none)" { // Human checkmated the opponent if humanColor == core.ColorWhite { g.SetState(core.StateWhiteWins) } else { g.SetState(core.StateBlackWins) } result.GameState = g.State() } // Store result in game instead of service g.SetLastResult(result) return nil } func (s *Service) MakeComputerMove(gameID string) (*game.MoveResult, error) { g, ok := s.games[gameID] if !ok { return nil, fmt.Errorf("game not found: %s", gameID) } if g.NextPlayer().Type != core.PlayerComputer { return nil, fmt.Errorf("not computer's turn") } currentColor := g.NextTurn() s.engine.SetPosition(g.CurrentFEN(), []string{}) search, err := s.engine.Search(1000) if err != nil { return nil, fmt.Errorf("engine error: %v", err) } result := &game.MoveResult{ Player: currentColor, Score: search.Score, Depth: search.Depth, GameState: core.StateOngoing, } if search.BestMove == "" || search.BestMove == "(none)" { // No legal moves - computer is checkmated if currentColor == core.ColorWhite { g.SetState(core.StateBlackWins) } else { g.SetState(core.StateWhiteWins) } result.GameState = g.State() g.SetLastResult(result) return result, nil } result.Move = search.BestMove // Apply move and get resulting FEN s.engine.SetPosition(g.CurrentFEN(), []string{search.BestMove}) newFEN, err := s.engine.GetFEN() if err != nil { return nil, fmt.Errorf("failed to get position: %v", err) } g.AddSnapshot(newFEN, search.BestMove, core.OppositeColor(currentColor)) // Check if opponent has any legal moves s.engine.SetPosition(newFEN, []string{}) testSearch, _ := s.engine.Search(100) if testSearch.BestMove == "" || testSearch.BestMove == "(none)" { // Computer checkmated the opponent if currentColor == core.ColorWhite { g.SetState(core.StateWhiteWins) } else { g.SetState(core.StateBlackWins) } result.GameState = g.State() } // Store result in game g.SetLastResult(result) return result, nil } func (s *Service) Undo(gameID string, count int) error { g, ok := s.games[gameID] if !ok { return fmt.Errorf("game not found: %s", gameID) } return g.UndoMoves(count) } func (s *Service) GetCurrentBoard(gameID string) (*board.Board, error) { g, ok := s.games[gameID] if !ok { return nil, fmt.Errorf("game not found: %s", gameID) } return board.FEN(g.CurrentFEN()) } func (s *Service) GetGame(gameID string) (*game.Game, error) { g, ok := s.games[gameID] if !ok { return nil, fmt.Errorf("game not found: %s", gameID) } return g, nil } func (s *Service) Close() error { if s.engine != nil { return s.engine.Close() } return nil }