hannah/src/Automaton.hs

135 lines
4.2 KiB
Haskell
Raw Normal View History

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns #-}
module Automaton (
2019-08-19 18:48:11 +02:00
start
) where
import qualified AI (move)
2019-08-19 18:48:11 +02:00
import Control.Concurrent (forkIO, threadDelay)
import Data.Aeson (encode, eitherDecode')
import Data.ByteString.Lazy.Char8 (ByteString, append, pack, putStrLn)
import qualified Data.ByteString.Lazy.Char8 as ByteString (concat)
import Data.Map ((!))
2020-01-31 09:15:19 +01:00
import qualified Data.Map as Map (empty, filter, lookup)
import Control.Monad.Reader (ReaderT, ask)
import Control.Monad.Trans (lift)
import Hanafuda.KoiKoi (Step(..))
import Hanafuda.Message (FromClient(..), T(..), orderCoordinates)
import qualified Hanafuda.Message as Message (
Coordinates(..), FromClient, PublicGame(..), PublicState(..), T(..)
)
2019-08-19 18:48:11 +02:00
import Network.WebSockets (Connection, receiveData, sendTextData)
import Prelude hiding (error, putStrLn)
2020-01-31 09:15:19 +01:00
import Session (State(..), deleteGame, initial, storeID, storeGame)
type App a = ReaderT Connection IO a
2019-08-19 18:48:11 +02:00
sendIO :: Message.FromClient -> Connection -> IO ()
sendIO message connection = do
putStrLn $ ">" `append` encoded
sendTextData connection encoded
where
encoded = encode message
send :: Message.FromClient -> App ()
2019-08-19 18:48:11 +02:00
send message =
ask >>= lift . sendIO message
receive :: App Message.T
2019-08-19 18:48:11 +02:00
receive = do
received <- ask >>= lift . receiveData
debug $ "<" `append` received
case eitherDecode' received of
Left errorMessage -> debug (pack errorMessage) >> receive
Right message -> return message
debug :: ByteString -> App ()
debug message = lift $ putStrLn message
answer :: State -> Message.T -> App State
answer New (Welcome {key}) = do
2020-01-31 09:15:19 +01:00
lift $ Session.storeID key
lift $ putStrLn "Stored"
return $ Connected {playerID = key, games = Map.empty}
answer state@(Connected {}) (Relay {from, message = Invitation {}}) =
send (Answer {accept = True, to = from}) >> return state
2020-01-31 09:15:19 +01:00
answer state@(Connected {playerID}) message@(Game {}) = do
case Message.step $ Message.public game of
2020-01-31 09:15:19 +01:00
Over -> deleteGame gameID state
_ -> do
if Message.playing (Message.public game) == playerID
then
2020-01-31 09:15:19 +01:00
send (Play {move = AI.move playerID game, onGame = game})
else return ()
storeGame gameID game state
where
game = Message.state message
2020-01-31 09:15:19 +01:00
gameID = Message.gameID . Message.coordinates $ Message.public game
answer state Pong = ping >> return state
answer state (Error {error}) = do
2019-08-19 18:48:11 +02:00
debug $ "Received error from server : " `append` pack error
return state
2020-01-31 09:15:19 +01:00
answer state@(Connected {playerID, games}) (LogIn {from}) =
(mapM_ sync . Map.filter isAgainst $ Message.public <$> games) >> return state
where
isAgainst publicState = Message.nextPlayer publicState ! playerID == from
sync publicState =
send $ Sync {latestKnown = Message.coordinates $ publicState, to = from}
answer state@(Connected {games}) (Relay {from, message = Sync {latestKnown}}) =
case Map.lookup gameID games of
Nothing -> send $ Yield {onGameID = gameID, to = from}
Just game ->
let latestKnownHere = Message.coordinates $ Message.public game in
case orderCoordinates latestKnown latestKnownHere of
Just LT -> send $ Share {gameSave = game}
Just GT -> send $ Yield {onGameID = gameID, to = from}
_ -> return ()
>> return state
where
gameID = Message.gameID latestKnown
answer state@(Connected {games}) (Relay {message = Yield {onGameID}}) =
send (Share {gameSave = games ! onGameID}) >> return state
{-
- Ignore
-}
answer state@(Connected {}) (Okaeri {}) = return state
answer state (LogIn {}) = return state
answer state (LogOut {}) = return state
answer state (Relay {}) = return state
answer state message = do
2019-08-19 18:48:11 +02:00
debug $ ByteString.concat [
"Unexpected message : ", encode message, " in state ", pack $ show state
]
return state
ping :: App ()
2019-08-19 18:48:11 +02:00
ping = do
connection <- ask
setTimeout (20*s) (sendIO Ping connection)
2019-08-19 18:48:11 +02:00
where
setTimeout delay callback =
const () <$> (lift $ forkIO (threadDelay delay >> callback))
ms = 1000
s = 1000 * ms
start :: App ()
start = do
ping
initialState <- lift Session.initial
case initialState of
New -> send $ Hello {name = "Hannah"}
Connected {playerID} -> send $ Tadaima {myID = playerID, name = "Hannah"}
loop initialState
where
loop state = receive >>= answer state >>= loop