packages feed

quoridor-hs-0.1.1.0: src/Quoridor/Cmdline/Network/Client.hs

{-# LANGUAGE BangPatterns #-}

module Quoridor.Cmdline.Network.Client
  ( connectClient
  ) where

import           Control.Concurrent.Async        (race)
import           Control.Concurrent.MVar         (MVar, newMVar, withMVar)
import           Control.Monad                   (unless, when)
import           Data.Functor                    (void)
import           Data.Maybe                      (isJust)
import           System.IO                       (BufferMode (LineBuffering),
                                                  hFlush, hReady,
                                                  hSetBuffering, stdin, stdout)

import           Network.Simple.TCP              (Socket, connect)

import           Quoridor
import           Quoridor.Cmdline.Messages
import           Quoridor.Cmdline.Network.Common
import           Quoridor.Cmdline.Parse          (parseMessage)
import           Quoridor.Cmdline.Render         (putChatMessageJson,
                                                  putColoredBoardHtml,
                                                  putColoredBoardTerm,
                                                  runRenderColor)


-- | Given an address, joins a game server that listens
-- on that address.
connectClient :: Bool -> String -> Int -> IO ()
connectClient isProxy addr port = connect addr (show port) $
  \(sock, _) -> do
    -- In case the client is invoked as a proxy
    -- because the default is BlockBuffering
    hSetBuffering stdout LineBuffering
    let go = do
          msg <- recvMsg sock
          case msg of
            WaitMsg str -> flushStrLn str >> go
            FstGameMsg gc c -> do
              writeLock <- newMVar ()
              void $ race (handleServerInput isProxy gc c sock writeLock)
                          (handleUserInput c sock writeLock)
            _ -> error "connectClient - unexpected Message"
    go

    {-(gc, c) <- recvFromServer connSock-}
    {-playClient connSock isProxy gc c-}

{-playClient :: Socket -> Bool -> GameConfig -> Color -> IO ()-}
{-playClient connSock isProxy gc myColor = play-}
  {-where-}
    {-play = do-}
      {-(gs, vm, msg) <- recvFromServer connSock-}
      {-[>emptyInput<] -- unneccessary if we're concurrent-}
      {-(if isProxy then putColoredStrHtml-}
                  {-else (putChar '\n' >>) . putColoredStrTerm) $-}
        {-runRenderColor gs gc vm-}
      {-flushStrLn msg-}
      {-hFlush stdout-}
      {-case winner gs of-}
        {-Just c  ->-}
          {-flushStrLn $ msgGameEnd c-}
        {-Nothing -> do-}
          {-let currPC = color $ currP gs-}
          {-if currPC /= myColor-}
            {-then do-}
              {-flushStrLn $ msgAwaitingTurn currPC-}
              {-play-}
            {-else do-}
              {-strTurn <- getLine-}
              {-sendToSock strTurn connSock-}
              {-play-}

handleServerInput :: Bool -> GameConfig -> Color -> Socket -> MVar () -> IO ()
handleServerInput isProxy gc myColor sock writeLock = go
 where
  go = do
    msg <- recvMsg sock
    quit <- case msg of
      GameMsg gs vm str -> handleGameMsg gs vm str
      ChatMsg col str   -> handleChatMsg col str >> return False
      _                 -> error "handleServerInput - unexpected Message"
    unless quit go

  handleGameMsg :: GameState -> ValidMoves -> String -> IO Bool
  handleGameMsg gs vm msg = do
    --  WHNF to avoid computing the board string inside withMVar
    let !boardStr = runRenderColor gs gc vm
        putBoardStrFunc = if isProxy then putColoredBoardHtml
                               else (putChar '\n' >>) . putColoredBoardTerm
    withMVar writeLock $ \_ -> do
      putBoardStrFunc boardStr
      flushStrLn msg
      flushStrLn $ case (winner gs, color $ currP gs) of
                   (Just c, _) -> msgGameEnd c
                   (Nothing, c) | c == myColor -> msgYourTurn
                                | otherwise    -> msgAwaitingTurn c
      return $ isJust $ winner gs
  handleChatMsg :: Color -> String -> IO ()
  handleChatMsg col msg = when isProxy $ putChatMessageJson col msg

handleUserInput :: Color -> Socket -> MVar () -> IO ()
handleUserInput c sock writeLock = go
 where
  go = do
    input <- getLine
    either
      (\err -> withMVar writeLock $ \_ -> putStrLn err)
      (`sendMsg` sock)
      $ parseMessage c input
    go

{-recvFromServer :: (Functor m, MonadIO m) => Socket -> m Message-}
{-recvFromServer = recvMsg-}

-- | Like putStrLn, but flushes right afterwards.
flushStrLn :: String -> IO ()
flushStrLn = (hFlush stdout <<) . putStrLn
  where (<<) = flip (>>)

-- | This empties command line input that was buffered
-- while it wasn't the player's turn.
-- Otherwise, garbage that was being fed to input
-- while it wasn't the player's turn, will
-- be fed to the server and generate an error message per line,
-- or even play a turn, which may or may not be intentional.
{-emptyInput :: IO ()-}
{-emptyInput = do-}
  {-inputExists <- hReady stdin-}
  {-when inputExists $ getLine >> emptyInput-}