Follow renaming (Key -> ID) in hanafuda lib, finish taking IDs declaration out of the webapp and simplify Automaton by putting some operations back into Game module
This commit is contained in:
parent
539b74990e
commit
3e7c0a88f1
8 changed files with 110 additions and 153 deletions
14
src/App.hs
14
src/App.hs
|
@ -16,13 +16,13 @@ import Data.Map ((!))
|
||||||
import Control.Concurrent (MVar, modifyMVar, putMVar, readMVar, takeMVar)
|
import Control.Concurrent (MVar, modifyMVar, putMVar, readMVar, takeMVar)
|
||||||
import Control.Monad.Reader (ReaderT(..), ask, asks, lift)
|
import Control.Monad.Reader (ReaderT(..), ask, asks, lift)
|
||||||
import Network.WebSockets (Connection)
|
import Network.WebSockets (Connection)
|
||||||
import Hanafuda.KoiKoi (PlayerKey)
|
import Hanafuda.KoiKoi (PlayerID)
|
||||||
import qualified Session (T(..))
|
import qualified Session (T(..))
|
||||||
import qualified Server (T(..))
|
import qualified Server (T(..))
|
||||||
|
|
||||||
data Context = Context {
|
data Context = Context {
|
||||||
mServer :: MVar Server.T
|
mServer :: MVar Server.T
|
||||||
, key :: PlayerKey
|
, playerID :: PlayerID
|
||||||
}
|
}
|
||||||
|
|
||||||
type T a = ReaderT Context IO a
|
type T a = ReaderT Context IO a
|
||||||
|
@ -30,20 +30,20 @@ type T a = ReaderT Context IO a
|
||||||
server :: T Server.T
|
server :: T Server.T
|
||||||
server = asks mServer >>= lift . readMVar
|
server = asks mServer >>= lift . readMVar
|
||||||
|
|
||||||
get :: PlayerKey -> T Session.T
|
get :: PlayerID -> T Session.T
|
||||||
get key =
|
get playerID =
|
||||||
(! key) . Server.sessions <$> server
|
(! playerID) . Server.sessions <$> server
|
||||||
|
|
||||||
current :: T Session.T
|
current :: T Session.T
|
||||||
current = do
|
current = do
|
||||||
asks key >>= get
|
asks playerID >>= get
|
||||||
|
|
||||||
connection :: T Connection
|
connection :: T Connection
|
||||||
connection = Session.connection <$> current
|
connection = Session.connection <$> current
|
||||||
|
|
||||||
debug :: String -> T ()
|
debug :: String -> T ()
|
||||||
debug message =
|
debug message =
|
||||||
show <$> asks key
|
show <$> asks playerID
|
||||||
>>= lift . putStrLn . (++ ' ':message)
|
>>= lift . putStrLn . (++ ' ':message)
|
||||||
|
|
||||||
try :: (Server.T -> Either String Server.T) -> T (Maybe String)
|
try :: (Server.T -> Either String Server.T) -> T (Maybe String)
|
||||||
|
|
|
@ -3,83 +3,85 @@ module Automaton (
|
||||||
start
|
start
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Monad.Except (runExceptT)
|
import Control.Monad.Reader (asks)
|
||||||
import Control.Monad.Reader (asks, lift)
|
|
||||||
import Control.Monad.Writer (runWriterT)
|
|
||||||
import qualified Data (RW(..))
|
import qualified Data (RW(..))
|
||||||
import Data.Map (Map, (!?))
|
import Data.Map (Map, (!?))
|
||||||
import qualified Game (Key, T, new, play)
|
import qualified Game (new, play)
|
||||||
import qualified Hanafuda.KoiKoi as KoiKoi (GameBlueprint(..), Step(..))
|
import qualified Hanafuda.KoiKoi as KoiKoi (
|
||||||
|
Game, GameBlueprint(..), GameID, Step(..)
|
||||||
|
)
|
||||||
import qualified Session (Status(..), T(..), Update)
|
import qualified Session (Status(..), T(..), Update)
|
||||||
import qualified Server (endGame, get, logIn, logOut, update, register, room)
|
import qualified Server (endGame, get, logIn, logOut, update, room)
|
||||||
import qualified App (Context(..), T, current, debug, get, server, try, update, update_)
|
import qualified App (Context(..), T, current, debug, get, server, try, update_)
|
||||||
import qualified Hanafuda.Message as Message (FromClient(..), T(..))
|
import qualified Hanafuda.Message as Message (FromClient(..), T(..))
|
||||||
import qualified Messaging (broadcast, get, notifyPlayers, relay, send, sendTo, update)
|
import qualified Messaging (
|
||||||
|
broadcast, get, notifyPlayers, relay, send, sendTo, update
|
||||||
|
)
|
||||||
|
|
||||||
receive :: Session.Status -> Message.FromClient -> App.T ()
|
receive :: Session.Status -> Message.FromClient -> App.T ()
|
||||||
|
|
||||||
receive (Session.LoggedIn False) logIn@(Message.LogIn login) =
|
receive (Session.LoggedIn False) logIn@(Message.LogIn login) =
|
||||||
asks App.key >>= App.try . (Server.logIn login)
|
asks App.playerID >>= App.try . (Server.logIn login)
|
||||||
>>= maybe
|
>>= maybe
|
||||||
(Messaging.relay logIn Messaging.broadcast >> setSessionStatus (Session.LoggedIn True))
|
(Messaging.relay logIn Messaging.broadcast >> setSessionStatus (Session.LoggedIn True))
|
||||||
sendError
|
sendError
|
||||||
|
|
||||||
receive (Session.LoggedIn True) logOut@Message.LogOut = do
|
receive (Session.LoggedIn True) logOut@Message.LogOut = do
|
||||||
Messaging.relay logOut Messaging.broadcast
|
Messaging.relay logOut Messaging.broadcast
|
||||||
asks App.key >>= App.update_ . Server.logOut
|
asks App.playerID >>= App.update_ . Server.logOut
|
||||||
setSessionStatus (Session.LoggedIn False)
|
setSessionStatus (Session.LoggedIn False)
|
||||||
|
|
||||||
receive (Session.LoggedIn True) invitation@(Message.Invitation {Message.to}) = do
|
receive (Session.LoggedIn True) invitation@(Message.Invitation {Message.to}) = do
|
||||||
session <- App.get to
|
session <- App.get to
|
||||||
case Session.status session of
|
case Session.status session of
|
||||||
Session.LoggedIn True -> do
|
Session.LoggedIn True -> do
|
||||||
key <- asks App.key
|
from <- asks App.playerID
|
||||||
App.update_ (Server.update to (Data.set $ Session.Answering key :: Session.Update))
|
App.update_ (Server.update to (Data.set $ Session.Answering from :: Session.Update))
|
||||||
Messaging.broadcast $ Messaging.update {Message.paired = [key, to]}
|
Messaging.broadcast $ Messaging.update {Message.paired = [from, to]}
|
||||||
(Messaging.relay invitation $ Messaging.sendTo [to])
|
(Messaging.relay invitation $ Messaging.sendTo [to])
|
||||||
setSessionStatus (Session.Waiting to)
|
setSessionStatus (Session.Waiting to)
|
||||||
_ -> sendError "They just left"
|
_ -> sendError "They just left"
|
||||||
|
|
||||||
receive (Session.Answering to) message@(Message.Answer {Message.accept}) = do
|
receive (Session.Answering to) message@(Message.Answer {Message.accept}) = do
|
||||||
session <- App.get to
|
session <- App.get to
|
||||||
key <- asks App.key
|
playerID <- asks App.playerID
|
||||||
case Session.status session of
|
case Session.status session of
|
||||||
Session.Waiting for | for == key -> do
|
Session.Waiting for | for == playerID -> do
|
||||||
Messaging.relay message $ Messaging.sendTo [to]
|
Messaging.relay message $ Messaging.sendTo [to]
|
||||||
newStatus <-
|
newStatus <-
|
||||||
if accept
|
if accept
|
||||||
then do
|
then do
|
||||||
gameKey <- Server.register <$> (lift $ Game.new for to) >>= App.update
|
gameID <- Game.new (for, to)
|
||||||
game <- Server.get gameKey <$> App.server
|
game <- Server.get gameID <$> App.server
|
||||||
Messaging.notifyPlayers game []
|
Messaging.notifyPlayers game []
|
||||||
return $ Session.Playing gameKey
|
return $ Session.Playing gameID
|
||||||
else do
|
else do
|
||||||
Messaging.broadcast $ Messaging.update {Message.alone = [key, to]}
|
Messaging.broadcast $ Messaging.update {Message.alone = [for, to]}
|
||||||
return $ Session.LoggedIn True
|
return $ Session.LoggedIn True
|
||||||
App.update_ $ Server.update to (Data.set newStatus :: Session.Update)
|
App.update_ $ Server.update to (Data.set newStatus :: Session.Update)
|
||||||
setSessionStatus newStatus
|
setSessionStatus newStatus
|
||||||
_ -> sendError "They're not waiting for your answer"
|
_ -> sendError "They're not waiting for your answer"
|
||||||
|
|
||||||
receive (Session.Playing gameKey) played@(Message.Play {}) = do
|
receive (Session.Playing gameID) played@(Message.Play {}) = do
|
||||||
key <- asks App.key
|
playerID <- asks App.playerID
|
||||||
game <- Server.get gameKey <$> App.server
|
game <- Server.get gameID <$> App.server
|
||||||
(result, logs) <- lift . runWriterT . runExceptT $ Game.play key (Message.move played) game
|
(result, logs) <- Game.play playerID (Message.move played) game
|
||||||
case result of
|
case result of
|
||||||
Left message -> sendError message
|
Left message -> sendError message
|
||||||
Right newGame -> do
|
Right newGame -> do
|
||||||
case KoiKoi.step newGame of
|
case KoiKoi.step newGame of
|
||||||
KoiKoi.Over -> do
|
KoiKoi.Over -> do
|
||||||
App.debug $ "Game " ++ show gameKey ++ " ended"
|
App.debug $ "Game " ++ show gameID ++ " ended"
|
||||||
App.update_ $ Server.endGame gameKey
|
App.update_ $ Server.endGame gameID
|
||||||
_ -> App.update_ $ Server.update gameKey (const newGame)
|
_ -> App.update_ $ Server.update gameID (const newGame)
|
||||||
Messaging.notifyPlayers newGame logs
|
Messaging.notifyPlayers newGame logs
|
||||||
|
|
||||||
receive (Session.Playing gameKey) Message.Quit = do
|
receive (Session.Playing gameID) Message.Quit = do
|
||||||
games <- (Data.get <$> App.server :: App.T (Map Game.Key Game.T))
|
games <- (Data.get <$> App.server :: App.T (Map KoiKoi.GameID KoiKoi.Game))
|
||||||
case games !? gameKey of
|
case games !? gameID of
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
key <- asks App.key
|
playerID <- asks App.playerID
|
||||||
Messaging.broadcast $ Messaging.update {Message.alone = [key]}
|
Messaging.broadcast $ Messaging.update {Message.alone = [playerID]}
|
||||||
setSessionStatus (Session.LoggedIn True)
|
setSessionStatus (Session.LoggedIn True)
|
||||||
_ -> sendError "Game is still running"
|
_ -> sendError "Game is still running"
|
||||||
|
|
||||||
|
@ -90,8 +92,8 @@ sendError = Messaging.send . Message.Error
|
||||||
|
|
||||||
setSessionStatus :: Session.Status -> App.T ()
|
setSessionStatus :: Session.Status -> App.T ()
|
||||||
setSessionStatus newStatus = do
|
setSessionStatus newStatus = do
|
||||||
key <- asks App.key
|
playerID <- asks App.playerID
|
||||||
App.update_ $ Server.update key $ (Data.set newStatus :: Session.Update)
|
App.update_ $ Server.update playerID $ (Data.set newStatus :: Session.Update)
|
||||||
App.debug $ show newStatus
|
App.debug $ show newStatus
|
||||||
|
|
||||||
loop :: App.T ()
|
loop :: App.T ()
|
||||||
|
@ -104,5 +106,5 @@ loop = do
|
||||||
start :: App.T ()
|
start :: App.T ()
|
||||||
start = do
|
start = do
|
||||||
App.debug "Initial state"
|
App.debug "Initial state"
|
||||||
Message.Welcome . Server.room <$> App.server <*> asks App.key >>= Messaging.send
|
Message.Welcome . Server.room <$> App.server <*> asks App.playerID >>= Messaging.send
|
||||||
loop
|
loop
|
||||||
|
|
21
src/Data.hs
21
src/Data.hs
|
@ -1,30 +1,11 @@
|
||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
|
||||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
|
||||||
module Data (
|
module Data (
|
||||||
Key(..)
|
RW(..)
|
||||||
, RW(..)
|
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Aeson (FromJSON(..), ToJSON(..), ToJSONKey(..), genericToEncoding)
|
|
||||||
import Data.Aeson.Types (toJSONKeyText)
|
|
||||||
import Data.Text (pack)
|
|
||||||
import GHC.Generics
|
|
||||||
import qualified JSON (defaultOptions)
|
|
||||||
|
|
||||||
class RW a b where
|
class RW a b where
|
||||||
get :: b -> a
|
get :: b -> a
|
||||||
set :: a -> b -> b
|
set :: a -> b -> b
|
||||||
update :: (a -> a) -> b -> b
|
update :: (a -> a) -> b -> b
|
||||||
update f v =
|
update f v =
|
||||||
set (f (get v)) v
|
set (f (get v)) v
|
||||||
|
|
||||||
newtype Key a = Key Int deriving (Eq, Ord, Enum, Read, Show, Generic)
|
|
||||||
|
|
||||||
instance FromJSON (Key a)
|
|
||||||
instance ToJSON (Key a) where
|
|
||||||
toEncoding = genericToEncoding JSON.defaultOptions
|
|
||||||
|
|
||||||
instance ToJSONKey (Key a) where
|
|
||||||
toJSONKey = toJSONKeyText (pack . \(Key n) -> show n)
|
|
||||||
|
|
51
src/Game.hs
51
src/Game.hs
|
@ -1,49 +1,40 @@
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
|
||||||
{-# LANGUAGE StandaloneDeriving #-}
|
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
|
||||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
module Game (
|
module Game (
|
||||||
Key
|
export
|
||||||
, T
|
|
||||||
, export
|
|
||||||
, new
|
, new
|
||||||
, play
|
, play
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Monad.Except (throwError)
|
import qualified App (T, update)
|
||||||
|
import Control.Monad.Except (runExceptT, throwError)
|
||||||
|
import Control.Monad.Reader (lift)
|
||||||
|
import Control.Monad.Writer (runWriterT)
|
||||||
import Data.Map (mapWithKey)
|
import Data.Map (mapWithKey)
|
||||||
import qualified Data (Key)
|
|
||||||
import qualified Hanafuda (empty)
|
import qualified Hanafuda (empty)
|
||||||
import qualified Hanafuda.KoiKoi (Game, Environment, Mode(..), Move(..), PlayerKey, new, play)
|
import qualified Hanafuda.KoiKoi as KoiKoi (
|
||||||
import Hanafuda.KoiKoi (GameBlueprint(..))
|
Action, Move(..), play, new
|
||||||
|
)
|
||||||
|
import Hanafuda.KoiKoi (Game, GameBlueprint(..), GameID, Mode(..), PlayerID)
|
||||||
import qualified Hanafuda.Player (Player(..), Players(..))
|
import qualified Hanafuda.Player (Player(..), Players(..))
|
||||||
import Hanafuda.Message (PublicGame)
|
import Hanafuda.Message (PublicGame)
|
||||||
|
import qualified Server (register)
|
||||||
|
|
||||||
|
new :: (PlayerID, PlayerID) -> App.T GameID
|
||||||
|
new (for, to) =
|
||||||
|
Server.register <$> (lift $ KoiKoi.new (for, to) WholeYear) >>= App.update
|
||||||
|
|
||||||
type T = Hanafuda.KoiKoi.Game
|
export :: PlayerID -> Game -> PublicGame
|
||||||
|
export playerID game = game {
|
||||||
type Key = Data.Key T
|
|
||||||
|
|
||||||
new :: Hanafuda.KoiKoi.PlayerKey -> Hanafuda.KoiKoi.PlayerKey -> IO T
|
|
||||||
new p1 p2 = do
|
|
||||||
Hanafuda.KoiKoi.new [p1, p2] $ Hanafuda.KoiKoi.WholeYear
|
|
||||||
|
|
||||||
export :: Hanafuda.KoiKoi.PlayerKey -> T -> PublicGame
|
|
||||||
export key game = game {
|
|
||||||
deck = length $ deck game
|
deck = length $ deck game
|
||||||
, players = Hanafuda.Player.Players $ mapWithKey maskOpponentsHand unfiltered
|
, players = Hanafuda.Player.Players $ mapWithKey maskOpponentsHand unfiltered
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
Hanafuda.Player.Players unfiltered = Hanafuda.KoiKoi.players game
|
Hanafuda.Player.Players unfiltered = Hanafuda.KoiKoi.players game
|
||||||
maskOpponentsHand k player
|
maskOpponentsHand k player
|
||||||
| k == key = player
|
| k == playerID = player
|
||||||
| otherwise = player {Hanafuda.Player.hand = Hanafuda.empty}
|
| otherwise = player {Hanafuda.Player.hand = Hanafuda.empty}
|
||||||
|
|
||||||
play :: Hanafuda.KoiKoi.Environment m => Hanafuda.KoiKoi.PlayerKey -> Hanafuda.KoiKoi.Move -> T -> m T
|
play :: PlayerID -> KoiKoi.Move -> Game -> App.T (Either String Game, [KoiKoi.Action])
|
||||||
play key move game
|
play playerID move game = lift . runWriterT . runExceptT $
|
||||||
| Hanafuda.KoiKoi.playing game == key =
|
if playing game == playerID
|
||||||
Hanafuda.KoiKoi.play move game
|
then KoiKoi.play move game
|
||||||
| otherwise = throwError "Not your turn"
|
else throwError "Not your turn"
|
||||||
|
|
|
@ -20,7 +20,7 @@ import qualified Automaton (start)
|
||||||
|
|
||||||
exit :: App.T ()
|
exit :: App.T ()
|
||||||
exit = do
|
exit = do
|
||||||
asks App.key >>= App.update_ . Server.disconnect
|
asks App.playerID >>= App.update_ . Server.disconnect
|
||||||
relay Message.LogOut broadcast
|
relay Message.LogOut broadcast
|
||||||
|
|
||||||
serverApp :: App.T () -> App.T () -> IO ServerApp
|
serverApp :: App.T () -> App.T () -> IO ServerApp
|
||||||
|
@ -28,8 +28,8 @@ serverApp onEnter onExit = do
|
||||||
mServer <- newMVar Server.new
|
mServer <- newMVar Server.new
|
||||||
return $ \pending -> do
|
return $ \pending -> do
|
||||||
session <- Session.open <$> acceptRequest pending
|
session <- Session.open <$> acceptRequest pending
|
||||||
key <- modifyMVar mServer (return . Server.register session)
|
playerID <- modifyMVar mServer (return . Server.register session)
|
||||||
let app = App.Context {App.mServer, App.key}
|
let app = App.Context {App.mServer, App.playerID}
|
||||||
finally
|
finally
|
||||||
(runReaderT onEnter app)
|
(runReaderT onEnter app)
|
||||||
(runReaderT onExit app)
|
(runReaderT onExit app)
|
||||||
|
|
|
@ -20,28 +20,28 @@ import Data.Aeson (eitherDecode', encode)
|
||||||
import Network.WebSockets (receiveData, sendTextData)
|
import Network.WebSockets (receiveData, sendTextData)
|
||||||
import Data.ByteString.Lazy.Char8 (unpack)
|
import Data.ByteString.Lazy.Char8 (unpack)
|
||||||
import Control.Monad.Reader (asks, lift)
|
import Control.Monad.Reader (asks, lift)
|
||||||
import qualified Game (T, export)
|
import qualified Game (export)
|
||||||
import qualified Session (T(..))
|
import qualified Session (T(..))
|
||||||
import qualified Server (T(..), get)
|
import qualified Server (T(..), get)
|
||||||
import qualified App (Context(..), T, connection, debug, server)
|
import qualified App (Context(..), T, connection, debug, server)
|
||||||
import qualified Hanafuda.KoiKoi as KoiKoi (Action, GameBlueprint(..), PlayerKey)
|
import qualified Hanafuda.KoiKoi as KoiKoi (Action, Game, GameBlueprint(..), PlayerID)
|
||||||
import qualified Hanafuda.Message as Message (T)
|
import qualified Hanafuda.Message as Message (T)
|
||||||
import Hanafuda.Message (FromClient(..), T(..))
|
import Hanafuda.Message (FromClient(..), T(..))
|
||||||
|
|
||||||
sendTo :: [KoiKoi.PlayerKey] -> Message.T -> App.T ()
|
sendTo :: [KoiKoi.PlayerID] -> Message.T -> App.T ()
|
||||||
sendTo playerKeys obj = do
|
sendTo playerIDs obj = do
|
||||||
sessions <- getSessions <$> App.server
|
sessions <- getSessions <$> App.server
|
||||||
App.debug $ '(' : intercalate ", " recipients ++ ") <" ++ (unpack encoded)
|
App.debug $ '(' : intercalate ", " recipients ++ ") <" ++ (unpack encoded)
|
||||||
lift $ forM_ (Session.connection <$> sessions) $ flip sendTextData encoded
|
lift $ forM_ (Session.connection <$> sessions) $ flip sendTextData encoded
|
||||||
where
|
where
|
||||||
encoded = encode $ obj
|
encoded = encode $ obj
|
||||||
getSessions server = (\key -> Server.get key server) <$> playerKeys
|
getSessions server = (\playerID -> Server.get playerID server) <$> playerIDs
|
||||||
recipients = show <$> playerKeys
|
recipients = show <$> playerIDs
|
||||||
|
|
||||||
send :: Message.T -> App.T ()
|
send :: Message.T -> App.T ()
|
||||||
send obj = do
|
send obj = do
|
||||||
key <- asks App.key
|
playerID <- asks App.playerID
|
||||||
sendTo [key] obj
|
sendTo [playerID] obj
|
||||||
|
|
||||||
broadcast :: Message.T -> App.T ()
|
broadcast :: Message.T -> App.T ()
|
||||||
broadcast obj =
|
broadcast obj =
|
||||||
|
@ -50,7 +50,7 @@ broadcast obj =
|
||||||
relay :: FromClient -> (Message.T -> App.T ()) -> App.T ()
|
relay :: FromClient -> (Message.T -> App.T ()) -> App.T ()
|
||||||
relay message f = do
|
relay message f = do
|
||||||
App.debug "Relaying"
|
App.debug "Relaying"
|
||||||
(\from -> f $ Relay {from, message}) =<< asks App.key
|
(\from -> f $ Relay {from, message}) =<< asks App.playerID
|
||||||
|
|
||||||
receive :: App.T FromClient
|
receive :: App.T FromClient
|
||||||
receive = do
|
receive = do
|
||||||
|
@ -70,7 +70,7 @@ get =
|
||||||
update :: T
|
update :: T
|
||||||
update = Update {alone = [], paired = []}
|
update = Update {alone = [], paired = []}
|
||||||
|
|
||||||
notifyPlayers :: Game.T -> [KoiKoi.Action] -> App.T ()
|
notifyPlayers :: KoiKoi.Game -> [KoiKoi.Action] -> App.T ()
|
||||||
notifyPlayers game logs =
|
notifyPlayers game logs =
|
||||||
forM_ (keys $ KoiKoi.scores game) $ \k ->
|
forM_ (keys $ KoiKoi.scores game) $ \k ->
|
||||||
sendTo [k] $ Game {game = Game.export k game, logs}
|
sendTo [k] $ Game {game = Game.export k game, logs}
|
||||||
|
|
|
@ -2,9 +2,7 @@
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
{-# LANGUAGE AllowAmbiguousTypes #-}
|
|
||||||
module Server (
|
module Server (
|
||||||
T(..)
|
T(..)
|
||||||
, disconnect
|
, disconnect
|
||||||
|
@ -23,17 +21,16 @@ import qualified Data.Map as Map (empty)
|
||||||
import Data.Set (Set, member)
|
import Data.Set (Set, member)
|
||||||
import qualified Data.Set as Set (delete, empty, insert)
|
import qualified Data.Set as Set (delete, empty, insert)
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Hanafuda.KoiKoi (PlayerKey)
|
import Hanafuda.KoiKoi (Game, GameID, PlayerID)
|
||||||
import Hanafuda.Message (PlayerStatus(..), Room)
|
import Hanafuda.Message (PlayerStatus(..), Room)
|
||||||
import qualified Data (RW(..))
|
import qualified Data (RW(..))
|
||||||
import qualified Game (Key, T)
|
|
||||||
import qualified Player (T(..))
|
import qualified Player (T(..))
|
||||||
import qualified Session (Status(..), T(..), Update)
|
import qualified Session (Status(..), T(..), Update)
|
||||||
|
|
||||||
type Names = Set Text
|
type Names = Set Text
|
||||||
type Players = Map PlayerKey Player.T
|
type Players = Map PlayerID Player.T
|
||||||
type Sessions = Map PlayerKey Session.T
|
type Sessions = Map PlayerID Session.T
|
||||||
type Games = Map Game.Key Game.T
|
type Games = Map GameID Game
|
||||||
data T = T {
|
data T = T {
|
||||||
names :: Names
|
names :: Names
|
||||||
, players :: Players
|
, players :: Players
|
||||||
|
@ -57,23 +54,17 @@ instance Data.RW Games T where
|
||||||
get = games
|
get = games
|
||||||
set games server = server {games}
|
set games server = server {games}
|
||||||
|
|
||||||
export :: Sessions -> PlayerKey -> Player.T -> PlayerStatus
|
export :: Sessions -> PlayerID -> Player.T -> PlayerStatus
|
||||||
export sessions key player = PlayerStatus (Player.name player, alone)
|
export sessions playerID player = PlayerStatus (Player.name player, alone)
|
||||||
where
|
where
|
||||||
alone =
|
alone =
|
||||||
case Session.status (sessions ! key) of
|
case Session.status (sessions ! playerID) of
|
||||||
Session.LoggedIn True -> True
|
Session.LoggedIn True -> True
|
||||||
_ -> False
|
_ -> False
|
||||||
|
|
||||||
room :: T -> Room
|
room :: T -> Room
|
||||||
room (T {players, sessions}) = mapWithKey (export sessions) players
|
room (T {players, sessions}) = mapWithKey (export sessions) players
|
||||||
|
|
||||||
{-
|
|
||||||
instance ToJSON T where
|
|
||||||
toJSON (T {players, sessions}) = toJSON $ mapWithKey (export sessions) players
|
|
||||||
toEncoding (T {players, sessions}) = toEncoding $ mapWithKey (export sessions) players
|
|
||||||
-}
|
|
||||||
|
|
||||||
new :: T
|
new :: T
|
||||||
new = T {
|
new = T {
|
||||||
names = Set.empty
|
names = Set.empty
|
||||||
|
@ -84,39 +75,39 @@ new = T {
|
||||||
|
|
||||||
register :: forall a b. (Enum a, Ord a, Data.RW (Map a b) T) => b -> T -> (T, a)
|
register :: forall a b. (Enum a, Ord a, Data.RW (Map a b) T) => b -> T -> (T, a)
|
||||||
register x server =
|
register x server =
|
||||||
let key = maybe (toEnum 0) (\(n, _) -> succ n) $ lookupMax $ (Data.get server :: Map a b) in
|
let playerID = maybe (toEnum 0) (\(n, _) -> succ n) $ lookupMax $ (Data.get server :: Map a b) in
|
||||||
(Data.update (insert key x) server, key)
|
(Data.update (insert playerID x) server, playerID)
|
||||||
|
|
||||||
get :: forall a b. (Ord a, Data.RW (Map a b) T) => a -> T -> b
|
get :: forall a b. (Ord a, Data.RW (Map a b) T) => a -> T -> b
|
||||||
get key server = (Data.get server :: Map a b) ! key
|
get playerID server = (Data.get server :: Map a b) ! playerID
|
||||||
|
|
||||||
update :: forall a b. (Ord a, Data.RW (Map a b) T) => a -> (b -> b) -> T -> T
|
update :: forall a b. (Ord a, Data.RW (Map a b) T) => a -> (b -> b) -> T -> T
|
||||||
update key updator =
|
update playerID updator =
|
||||||
Data.update (adjust updator key :: Map a b -> Map a b)
|
Data.update (adjust updator playerID :: Map a b -> Map a b)
|
||||||
|
|
||||||
disconnect :: PlayerKey -> T -> T
|
disconnect :: PlayerID -> T -> T
|
||||||
disconnect key =
|
disconnect playerID =
|
||||||
Data.update (delete key :: Sessions -> Sessions) . logOut key
|
Data.update (delete playerID :: Sessions -> Sessions) . logOut playerID
|
||||||
|
|
||||||
endGame :: Game.Key -> T -> T
|
endGame :: GameID -> T -> T
|
||||||
endGame key =
|
endGame playerID =
|
||||||
Data.update (delete key :: Games -> Games)
|
Data.update (delete playerID :: Games -> Games)
|
||||||
|
|
||||||
logIn :: Text -> PlayerKey -> T -> Either String T
|
logIn :: Text -> PlayerID -> T -> Either String T
|
||||||
logIn name key server =
|
logIn name playerID server =
|
||||||
Data.update (Set.insert name) .
|
Data.update (Set.insert name) .
|
||||||
Data.update (insert key $ Player.T {Player.name}) .
|
Data.update (insert playerID $ Player.T {Player.name}) .
|
||||||
update key (Data.set $ Session.LoggedIn True :: Session.Update) <$>
|
update playerID (Data.set $ Session.LoggedIn True :: Session.Update) <$>
|
||||||
if name `member` names server
|
if name `member` names server
|
||||||
then Left "This name is already registered"
|
then Left "This name is already registered"
|
||||||
else Right server
|
else Right server
|
||||||
|
|
||||||
logOut :: PlayerKey -> T -> T
|
logOut :: PlayerID -> T -> T
|
||||||
logOut key server =
|
logOut playerID server =
|
||||||
maybe
|
maybe
|
||||||
server
|
server
|
||||||
(\player ->
|
(\player ->
|
||||||
Data.update (delete key :: Players -> Players) $
|
Data.update (delete playerID :: Players -> Players) $
|
||||||
update key (Data.set $ Session.LoggedIn False :: Session.Update) $
|
update playerID (Data.set $ Session.LoggedIn False :: Session.Update) $
|
||||||
Data.update (Set.delete $ Player.name player :: Names -> Names) server)
|
Data.update (Set.delete $ Player.name player :: Names -> Names) server)
|
||||||
(players server !? key)
|
(players server !? playerID)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
|
||||||
module Session (
|
module Session (
|
||||||
Status(..)
|
Status(..)
|
||||||
, T(..)
|
, T(..)
|
||||||
|
@ -9,22 +8,15 @@ module Session (
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Network.WebSockets (Connection)
|
import Network.WebSockets (Connection)
|
||||||
import Data.Aeson (ToJSON(..), genericToEncoding)
|
import Hanafuda.KoiKoi (GameID, PlayerID)
|
||||||
import GHC.Generics (Generic)
|
|
||||||
import Hanafuda.KoiKoi (PlayerKey)
|
|
||||||
import qualified JSON (singleLCField)
|
|
||||||
import qualified Data (RW(..))
|
import qualified Data (RW(..))
|
||||||
import qualified Game (Key)
|
|
||||||
|
|
||||||
data Status =
|
data Status =
|
||||||
LoggedIn Bool
|
LoggedIn Bool
|
||||||
| Answering PlayerKey
|
| Answering PlayerID
|
||||||
| Waiting PlayerKey
|
| Waiting PlayerID
|
||||||
| Playing Game.Key
|
| Playing GameID
|
||||||
deriving (Show, Generic)
|
deriving (Show)
|
||||||
|
|
||||||
instance ToJSON Status where
|
|
||||||
toEncoding = genericToEncoding JSON.singleLCField
|
|
||||||
|
|
||||||
data T = T {
|
data T = T {
|
||||||
connection :: Connection
|
connection :: Connection
|
||||||
|
|
Loading…
Reference in a new issue