Follow move of 'logs' into PublicGame and start implementing re-sync protocol on the server side

This commit is contained in:
Tissevert 2020-01-20 22:58:06 +01:00
parent ca30340aaa
commit 81ec84abaf
3 changed files with 40 additions and 23 deletions

View file

@ -5,8 +5,12 @@ module Automaton (
import qualified App (Context(..), T, exec, get, player, update) import qualified App (Context(..), T, exec, get, player, update)
import Control.Monad.Reader (asks) import Control.Monad.Reader (asks)
import qualified Game (new, play) import Data.Map ((!))
import qualified Hanafuda.Message as Message (FromClient(..), T(..)) import qualified Game (fromPublic, new, play, toPublic)
import qualified Hanafuda.KoiKoi as KoiKoi (Game(..))
import qualified Hanafuda.Message as Message (
FromClient(..), PublicGame(..), T(..)
)
import qualified Messaging ( import qualified Messaging (
broadcast, get, notifyPlayers, relay, send, sendTo broadcast, get, notifyPlayers, relay, send, sendTo
) )
@ -38,22 +42,31 @@ receive (Message.Tadaima {Message.myID, Message.name}) Nothing = do
receive (Message.Tadaima {}) (Just _) = sendError "You're already logged in" receive (Message.Tadaima {}) (Just _) = sendError "You're already logged in"
receive invitation@(Message.Invitation {Message.to}) (Just _) = receive invitation@(Message.Invitation {}) (Just _) = relay invitation
Messaging.relay invitation (Messaging.sendTo [to])
receive answer@(Message.Answer {Message.accept, Message.to}) (Just player) = receive answer@(Message.Answer {Message.accept, Message.to}) (Just player) =
if accept if accept
then do then do
publicGames <- Game.new (Player.playerID player, to) publicGames <- Game.new (Player.playerID player, to)
Messaging.relay answer (Messaging.sendTo [to]) Messaging.relay answer (Messaging.sendTo [to])
Messaging.notifyPlayers publicGames [] Messaging.notifyPlayers (publicGames, [])
else Messaging.relay answer (Messaging.sendTo [to]) else Messaging.relay answer (Messaging.sendTo [to])
receive (Message.Play {Message.move, Message.onGame}) (Just player) = do receive (Message.Play {Message.move, Message.onGame}) (Just player) =
result <- Game.play (Player.playerID player) move onGame Game.play (Player.playerID player) move onGame
case result of >>= either sendError Messaging.notifyPlayers
Left message -> sendError message
Right (newGame, logs) -> Messaging.notifyPlayers newGame logs receive sync@(Message.Sync {}) (Just _) = relay sync
receive yield@(Message.Yield {}) (Just _) = relay yield
receive (Message.Share {Message.gameSave}) (Just player) =
either sendError share =<< Game.fromPublic gameSave
where
logs = Message.logs gameSave
share game =
let recipientID = KoiKoi.nextPlayer game ! (Player.playerID player) in
Game.toPublic recipientID game logs
>>= Messaging.sendTo [recipientID] . Message.Game
receive message state = receive message state =
sendError $ "Invalid message " ++ show message ++ " in " ++ showState sendError $ "Invalid message " ++ show message ++ " in " ++ showState
@ -63,6 +76,9 @@ receive message state =
Nothing -> "disconnected state" Nothing -> "disconnected state"
Just _ -> "connected state" Just _ -> "connected state"
relay :: Message.FromClient -> App.T ()
relay message = Messaging.relay message (Messaging.sendTo [Message.to message])
sendError :: String -> App.T () sendError :: String -> App.T ()
sendError = Messaging.send . Message.Error sendError = Messaging.send . Message.Error

View file

@ -1,8 +1,9 @@
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
module Game ( module Game (
exportGame fromPublic
, new , new
, play , play
, toPublic
) where ) where
import qualified App (T, get) import qualified App (T, get)
@ -86,12 +87,13 @@ publicState coordinates game = PublicState {
, rounds = KoiKoi.rounds game , rounds = KoiKoi.rounds game
} }
exportGame :: PlayerID -> Game -> App.T PublicGame toPublic :: PlayerID -> Game -> [KoiKoi.Action] -> App.T PublicGame
exportGame playerID game = do toPublic playerID game logs = do
Keys.T {encrypt, sign} <- App.get Server.keys Keys.T {encrypt, sign} <- App.get Server.keys
n <- lift newNonce n <- lift newNonce
return $ PublicGame { return $ PublicGame {
nonce = Saltine.encode n nonce = Saltine.encode n
, logs
, playerHand = getHand playerID (KoiKoi.players game) , playerHand = getHand playerID (KoiKoi.players game)
, private = secretbox encrypt n $ toJSON private , private = secretbox encrypt n $ toJSON private
, public , public
@ -124,8 +126,8 @@ merge public private = KoiKoi.Game {
, KoiKoi.rounds = rounds public , KoiKoi.rounds = rounds public
} }
importGame :: PublicGame -> App.T (Either String Game) fromPublic :: PublicGame -> App.T (Either String Game)
importGame PublicGame {nonce, private, public, publicSignature} = fromPublic PublicGame {nonce, private, public, publicSignature} =
App.get Server.keys >>= \(Keys.T {encrypt, sign}) -> return $ do App.get Server.keys >>= \(Keys.T {encrypt, sign}) -> return $ do
check (signVerifyDetached (Keys.public sign) publicSignature (toJSON public)) check (signVerifyDetached (Keys.public sign) publicSignature (toJSON public))
`orDie` "The game state has been tampered with" `orDie` "The game state has been tampered with"
@ -145,8 +147,8 @@ importGame PublicGame {nonce, private, public, publicSignature} =
play :: PlayerID -> KoiKoi.Move -> PublicGame -> App.T (Either String (Game, [KoiKoi.Action])) play :: PlayerID -> KoiKoi.Move -> PublicGame -> App.T (Either String (Game, [KoiKoi.Action]))
play playerID move publicGame play playerID move publicGame
| playing (public publicGame) == playerID = do | playing (public publicGame) == playerID = do
imported <- importGame publicGame result <- fromPublic publicGame
case imported of case result of
Left errorMessage -> return $ Left errorMessage Left errorMessage -> return $ Left errorMessage
Right game -> lift . runExceptT . runWriterT $ KoiKoi.play move game Right game -> lift . runExceptT . runWriterT $ KoiKoi.play move game
| otherwise = return $ Left "Not your turn" | otherwise = return $ Left "Not your turn"

View file

@ -21,7 +21,7 @@ import Data.List (intercalate)
import Data.Map (elems, keys) import Data.Map (elems, keys)
import Data.Maybe (maybeToList) import Data.Maybe (maybeToList)
import qualified Data.Set as Set (fromList, member) import qualified Data.Set as Set (fromList, member)
import qualified Game (exportGame) import qualified Game (toPublic)
import qualified Hanafuda.KoiKoi as KoiKoi (Action, Game(..), PlayerID) import qualified Hanafuda.KoiKoi as KoiKoi (Action, Game(..), PlayerID)
import Hanafuda.Message (FromClient(..), T(..)) import Hanafuda.Message (FromClient(..), T(..))
import qualified Hanafuda.Message as Message (T) import qualified Hanafuda.Message as Message (T)
@ -78,8 +78,7 @@ get =
pong Ping = send Pong >> get pong Ping = send Pong >> get
pong m = return m pong m = return m
notifyPlayers :: KoiKoi.Game -> [KoiKoi.Action] -> App.T () notifyPlayers :: (KoiKoi.Game, [KoiKoi.Action]) -> App.T ()
notifyPlayers game logs = notifyPlayers (game, logs) =
forM_ (keys $ KoiKoi.scores game) $ \k -> do forM_ (keys $ KoiKoi.nextPlayer game) $ \k ->
state <- Game.exportGame k game sendTo [k] . Game =<< Game.toPublic k game logs
sendTo [k] $ Game {state, logs}