packages feed

chessIO-0.3.0.0: app/polyplay.hs

module Main where

import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad
import Control.Monad.Random
import Data.IORef
import Data.List
import Data.String
import Data.Text.Prettyprint.Doc.Render.Text
import Data.Time.Clock
import Data.Tree
import Game.Chess
import Game.Chess.PGN
import Game.Chess.Polyglot.Book
import Game.Chess.UCI
import Options.Applicative
import System.Environment
import Time.Units

data Clock = Clock !Color !NominalDiffTime !NominalDiffTime !UTCTime

newClock :: Int -> IO Clock
newClock s = Clock White (fromIntegral s') (fromIntegral s') <$!> getCurrentTime
  where
 s' = s `div` 2

flipClock :: Clock -> IO Clock
flipClock clock = upd clock <$!> getCurrentTime where
  upd (Clock White w b t) t' = Clock Black (w - (t' `diffUTCTime` t)) b t'
  upd (Clock Black w b t) t' = Clock White w (b - (t' `diffUTCTime` t)) t'

clockRemaining :: Clock -> Color -> IO (Maybe (Time Millisecond))
clockRemaining (Clock c w b t) c'
  | c == c' = case c of
    White -> (\t' -> f $ w - (t' `diffUTCTime` t)) <$!> getCurrentTime
    Black -> (\t' -> f $ b - (t' `diffUTCTime` t)) <$!> getCurrentTime
  | otherwise = pure $ case c' of
    White -> f w
    Black -> f b
 where
  f x | x <= 0 = Nothing
      | otherwise = Just . ms . fromRational . toRational $ x * 1000

clockTimes :: Clock -> (Maybe (Time Millisecond), Maybe (Time Millisecond))
clockTimes (Clock _ w b _) = (f w, f b) where
  f x = if x <= 0 then Nothing else Just . ms . fromRational . toRational $ x * 1000

data Polyplay = Polyplay {
  hashSize :: Int
, threadCount :: Int
, tbPath :: Maybe FilePath
, timeControl :: Int
, bookFile :: FilePath
, engineProgram :: FilePath
, engineArgs :: [String]
}

opts :: Parser Polyplay
opts = Polyplay <$> option auto (long "hash" <> metavar "MB" <> value 1024)
                <*> option auto (long "threads" <> metavar "N" <> value 1)
                <*> optional (strOption $ long "tbpath" <> metavar "PATH")
                <*> option auto (long "time" <> metavar "SECONDS" <> value 600)
                <*> argument str (metavar "BOOK")
                <*> argument str (metavar "ENGINE")
                <*> many (argument str (metavar "ARG"))

main :: IO ()
main = run =<< execParser (info (opts <**> helper) mempty)

run :: Polyplay -> IO ()
run Polyplay{..} = do
  b <- readPolyglotFile bookFile
  start engineProgram engineArgs >>= \case
    Nothing -> putStrLn "Engine failed to start."
    Just e -> do
      _ <- setOptionSpinButton "Hash" hashSize e
      _ <- setOptionSpinButton "Threads" threadCount e
      case tbPath of
        Just fp -> void $ setOptionString "SyzygyPath" (fromString fp) e
        Nothing -> pure ()
      isready e
      (h, o) <- play b e =<< newClock timeControl
      let g = gameFromForest [ ("White", "Stockfish")
                             , ("Black", "Stockfish")
                             ] (toForest h) o
      putDoc (gameDoc breadthFirst g)
      pure ()

play :: PolyglotBook -> Engine -> Clock -> IO ([Ply], Outcome)
play b e !c = do
  pos <- currentPosition e
  case legalPlies pos of
    [] -> lost e
    _ -> case bookPly b pos of
      Nothing -> do
        let (Just wt, Just bt) = clockTimes c
        (bmc, ic) <- search e [timeleft White wt, timeleft Black bt]
        sc <- newIORef Nothing
        itid <- liftIO . forkIO . forever $ do
          info <- atomically . readTChan $ ic
          case find isScore info of
            Just (Score s Nothing) -> writeIORef sc (Just s)
            _ -> pure ()
        (bm, _) <- atomically . readTChan $ bmc
        killThread itid
        c' <- flipClock c
        clockRemaining c' (color pos) >>= \case
          Nothing -> lost e
          Just _ -> do
            addPly e bm
            s <- readIORef sc
            putStrLn $ toSAN pos bm <> " " <> show s
            play b e c'
      Just r -> do
        pl <- evalRandIO r
        putStrLn $ toSAN pos pl
        addPly e pl
        play b e =<< flipClock c

lost :: Engine -> IO ([Ply], Outcome)
lost e = do
  pos <- currentPosition e
  (_, h) <- setPosition e startpos
  pure (h, Win . opponent . color $ pos)

toForest :: [Ply] -> Forest Ply
toForest [] = []
toForest (x:xs) = [Node x $ toForest xs]

isScore :: Info -> Bool
isScore Score{} = True
isScore _ = False