chessIO-0.3.1.2: 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 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' = getCurrentTime >>= \t' -> pure $ case c of
White -> f $ w - (t' `diffUTCTime` t)
Black -> f $ b - (t' `diffUTCTime` t)
| 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]
}
data Runtime = Runtime {
book :: PolyglotBook
, engine :: Engine
, clock :: !Clock
}
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 polyplay =<< execParser (info (opts <**> helper) mempty)
run :: (Runtime -> IO ()) -> Polyplay -> IO ()
run f Polyplay{..} = do
book <- readPolyglotFile bookFile
start engineProgram engineArgs >>= \case
Nothing -> putStrLn "Engine failed to start."
Just engine -> do
_ <- setOptionSpinButton "Hash" hashSize engine
_ <- setOptionSpinButton "Threads" threadCount engine
case tbPath of
Just fp -> void $ setOptionString "SyzygyPath" (fromString fp) engine
Nothing -> pure ()
isready engine
clock <- newClock timeControl
f Runtime { book, engine, clock }
polyplay :: Runtime -> IO ()
polyplay rt = do
(h, o) <- play rt
let g = gameFromForest [ ("White", "Stockfish")
, ("Black", "Stockfish")
] (toForest h) o
putDoc (gameDoc breadthFirst g)
pure ()
play :: Runtime -> IO ([Ply], Outcome)
play Runtime{..} = do
pos <- currentPosition engine
case legalPlies pos of
[] -> lost engine
_ -> case bookPly book pos of
Nothing -> do
let (Just wt, Just bt) = clockTimes clock
(bmc, ic) <- search engine [timeleft White wt, timeleft Black bt]
sc <- newIORef Nothing
itid <- liftIO . forkIO . forever $ do
i <- atomically . readTChan $ ic
case find isScore i of
Just (Score s Nothing) -> writeIORef sc (Just s)
_ -> pure ()
(bm, _) <- atomically . readTChan $ bmc
killThread itid
clock' <- flipClock clock
clockRemaining clock' (color pos) >>= \case
Nothing -> lost engine
Just _ -> do
addPly engine bm
s <- readIORef sc
putStrLn $ toSAN pos bm <> " " <> show s
play Runtime { book, engine, clock = clock'}
Just r -> do
pl <- evalRandIO r
putStrLn $ toSAN pos pl
addPly engine pl
clock' <- flipClock clock
play Runtime { book, engine, clock = clock' }
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