1 {-# LANGUAGE FlexibleContexts #-}
6 import Control.Monad.State
7 import Control.Monad.Reader
8 import Control.Monad.Writer
16 data Hint = PartyAt Place
22 data PlayerInformation = PlayerInformation
24 , piHints :: [(Day,Hint)]
27 data Score = NoScore | ToFew | Scored
30 data IncomingMessage = InternalMessage InternalMessage
31 | DayMessage DayMessage
32 | NightMessage NightMessage
33 | UnknownMessage String
36 -- Events that we want to send to the user code and that
37 -- can be reacted upon
38 data DayMessage = DayStarts
41 | PlayerAt Player Place
44 -- Events that we want to send to the user code and that
45 -- can NOT be reacted upon
46 data NightMessage = Initialize
53 -- Pure bookkeeping events
54 data InternalMessage = MyPlayerId Player
59 | PlayerEnter Player Place
60 | PlayerLeave Player Place
67 | PlayerJoined Player -- the game
69 | PlayerEnters Player Place
70 | PlayerLeaves Player Place
73 | PlayerScore [(Player, Int)]
74 | PlayerNames [(Player, String)]
84 | PlayerPosition Player Place -- wann bekommt man das?
89 data ClientMessage = Tell Player Hint
93 type DayCallback d = DayMessage -> DayMonad d ()
94 type NightCallback d = NightMessage -> NightMonad d ()
96 data ParttyState = ParttyState
99 , psPlayerInfo :: Map Player PlayerInformation
100 , psPartyHistory:: [(Day,Place)]
102 , psPlayers :: [Player]
105 type DayMonad d = WriterT [ClientMessage] (ReaderT ParttyState (StateT d IO))
106 type NightMonad d = ReaderT ParttyState (StateT d IO)
111 -- Hostname, Port, Playername, Password, Callback
112 runPartty :: String -> Int -> String -> String -> DayCallback d -> NightCallback d -> d -> IO ()
113 runPartty host port username password dcb ncb initialData = do
114 h <- connectTo host (PortNumber (fromIntegral port))
115 hSetBuffering h LineBuffering
116 putStrLn $ "Connected to " ++ host
117 InternalMessage Welcome <- parseIncomingMessage `liftM` hGetLine h
118 hPutStrLn h $ unwords ["login", username, password]
119 InternalMessage (PlayerJoined myName) <- parseIncomingMessage `liftM` hGetLine h
120 when (myName /= username) $ error "Server returned a different name for me"
122 -- login has been successful, wait for the server start event
124 msg <- parseIncomingMessage `liftM` hGetLine h
126 (InternalMessage GameStarting) -> return ()
128 putStrLn "Waiting for game start..."
131 -- We expect two more fixed events. If the server changes, this breaks!
133 InternalMessage (MyIdIs myId) <- parseIncomingMessage `liftM` hGetLine h
134 InternalMessage (PlayerEnter player startPlace) <- parseIncomingMessage `liftM` hGetLine h
135 when (player /= myId) $ error "Server did not tell us where we start"
136 putStrLn $ "Starting at " ++ show startPlace
138 let initialState = ParttyState
141 , psPlayerInfo = empty
143 , psPlace = startPlace
147 -- The rest of the events are handled in the regular loop. Run the initial event
148 -- and call the runner
150 d' <- callNightCallback ncb initialState initialData Initialize
152 runner h dcb ncb initialState d'
154 callDayCallback :: DayCallback d -> ParttyState -> d -> DayMessage -> IO ([ClientMessage], d)
155 callDayCallback dcb ps d e = do
157 flip runReaderT ps $ do
161 callNightCallback :: NightCallback d -> ParttyState -> d -> NightMessage -> IO d
162 callNightCallback ncb ps d e = do
164 flip runReaderT ps $ do
168 runner :: Handle -> DayCallback d -> NightCallback d -> ParttyState -> d -> IO ()
169 runner h dcb ncb = fix $ \loop ps d -> do
170 (e,ps') <- getNextUserMessage h ps
172 Left dm -> do (client_messages, d') <- callDayCallback dcb ps' d dm
173 mapM_ (sendClientMessage h) client_messages
175 Right nm -> callNightCallback ncb ps' d nm
178 getNextUserMessage :: Handle -> ParttyState -> IO (Either DayMessage NightMessage, ParttyState)
179 getNextUserMessage h ps = do
181 let msg = parseIncomingMessage line
182 let ps' = updateState msg ps
184 InternalMessage ue -> getNextUserMessage h ps'
185 UnknownMessage s-> do putStrLn $"Got an unknown message:\n"++s
186 getNextUserMessage h ps'
187 DayMessage dm -> return (Left dm, ps')
188 NightMessage nm -> return (Right nm, ps')
192 parseIncomingMessage :: String -> IncomingMessage
193 parseIncomingMessage msg = case id of
194 -- Errors, no proper handling for now
195 07 -> error "Game is already in progress, try again later..."
198 71 -> InternalMessage (Welcome)
199 20 -> InternalMessage (PlayerJoined s1)
200 70 -> InternalMessage (Noise) -- welcome
201 40 -> InternalMessage (Noise) -- waiting for at least .. players
202 41 -> InternalMessage (Noise) -- game will start in...
203 42 -> InternalMessage (GameStarting)
204 43 -> InternalMessage (MyIdIs s1)
205 22 -> InternalMessage (PlayerEnter s1 int2)
206 23 -> InternalMessage (PlayerLeave s1 int2)
207 65 -> InternalMessage (ActionsLeft int1)
209 50 -> DayMessage (DayStarts)
210 51 -> DayMessage (DayEndsIn int1)
211 52 -> DayMessage (DayEnds)
212 61 -> DayMessage (PlayerAt s1 int2)
214 53 -> NightMessage (NightStarts)
215 54 -> NightMessage (NightEndsIn int1)
216 55 -> NightMessage (NightEnds)
217 62 -> NightMessage (GotScore NoScore)
218 63 -> NightMessage (GotScore ToFew)
219 64 -> NightMessage (GotScore Scored)
221 60 -> NightMessage (PartyHint int1)
224 _ -> UnknownMessage msg
225 --_ -> error $ "Unkown or unparseable message:\n" ++ msg
226 where (id_s : num_params : rest) = words msg
228 param_s = take (read num_params) rest
229 s1 = read (param_s !! 0) :: String
230 int1 = read (param_s !! 0) :: Int
231 s2 = read (param_s !! 1) :: String
232 int2 = read (param_s !! 1) :: Int
234 sendClientMessage :: Handle -> ClientMessage -> IO ()
235 sendClientMessage h msg = do putStrLn $ "Client says: " ++ (show msg)
236 hPutStrLn h (toString msg)
237 where toString (Goto place) = unwords ["goto", show place]
238 toString (Tell player (PartyAt place)) = unwords ["party", show player, show place]
239 toString (Tell player (NoPartyAt place)) = unwords ["no_party", show player, show place]
240 toString (Tell player (Liar other)) = unwords ["liar", show player, show other]
241 toString (Tell player (NoLiar other)) = unwords ["recommand",show player, show other]
245 updateState :: IncomingMessage -> ParttyState -> ParttyState
246 updateState msg ps = ps -- TODO: this is a stub
249 -- Convenience functions for accessing the state
251 whoIsHere :: (MonadReader ParttyState m) => m [Player]
252 whoIsHere = asks psPlayers
254 gameDay :: (MonadReader ParttyState m) => m Day
259 send :: (MonadWriter [ClientMessage] m) => ClientMessage -> m ()
260 send msg = tell [msg]