diff --git a/src/Game.hs b/src/Game.hs index eff01fd..bb48118 100644 --- a/src/Game.hs +++ b/src/Game.hs @@ -21,7 +21,10 @@ import Hanafuda.KoiKoi (Game, Mode(..), Player, PlayerID, Players) import qualified Hanafuda.KoiKoi as KoiKoi ( Action, Game(..), Move(..), play, new ) -import Hanafuda.Message (PrivateState(..), PublicGame(..), PublicPlayer(..), PublicState(..)) +import Hanafuda.Message ( + GameState(..), PrivateState(..), PublicGame(..), PublicPlayer(..) + , PublicState(..) + ) import qualified Hanafuda.Player as Player (Player(..), Players(..), get) import Keys (T(..)) import qualified Keys (public, secret) @@ -35,9 +38,16 @@ exportPlayers game = let (Player.Players players) = KoiKoi.players game in players -privateState :: Game -> PrivateState -privateState game = PrivateState { - hands = Player.hand <$> players +getGameState :: Game -> GameState +getGameState game = GameState { + gameID = KoiKoi.gameID game + , turns = 24 - length (KoiKoi.deck game) + } + +privateState :: GameState -> Game -> PrivateState +privateState link game = PrivateState { + link + , hands = Player.hand <$> players , deck = KoiKoi.deck game } where @@ -59,9 +69,10 @@ privatePlayer publicPlayers playerID hand = Player.Player { , Player.yakus = yakus (publicPlayers ! playerID) } -publicState :: Game -> PublicState -publicState game = PublicState { - mode = KoiKoi.mode game +publicState :: GameState -> Game -> PublicState +publicState gameState game = PublicState { + gameState + , mode = KoiKoi.mode game , scores = KoiKoi.scores game , month = KoiKoi.month game , nextPlayer = KoiKoi.nextPlayer game @@ -72,7 +83,6 @@ publicState game = PublicState { , river = KoiKoi.river game , step = KoiKoi.step game , trick = KoiKoi.trick game - , turns = 24 - length (KoiKoi.deck game) , rounds = KoiKoi.rounds game } @@ -88,15 +98,17 @@ exportGame playerID game = do , publicSignature = signDetached (Keys.secret sign) $ toJSON public } where - public = publicState game - private = privateState game + sharedState = getGameState game + public = publicState sharedState game + private = privateState sharedState game toJSON :: ToJSON a => a -> ByteString toJSON = toStrict . encode -gameOf :: PublicState -> PrivateState -> Game -gameOf public private = KoiKoi.Game { - KoiKoi.mode = mode public +merge :: PublicState -> PrivateState -> Game +merge public private = KoiKoi.Game { + KoiKoi.gameID = gameID $ gameState public + , KoiKoi.mode = mode public , KoiKoi.scores = scores public , KoiKoi.month = month public , KoiKoi.nextPlayer = nextPlayer public @@ -113,18 +125,22 @@ gameOf public private = KoiKoi.Game { } importGame :: PublicGame -> App.T (Either String Game) -importGame PublicGame {nonce, private, public, publicSignature} = do - Keys.T {encrypt, sign} <- App.get Server.keys - if signVerifyDetached (Keys.public sign) publicSignature $ toJSON public - then return $ do +importGame PublicGame {nonce, private, public, publicSignature} = + App.get Server.keys >>= \(Keys.T {encrypt, sign}) -> return $ do + check (signVerifyDetached (Keys.public sign) publicSignature (toJSON public)) + `orDie` "The game state has been tampered with" n <- Saltine.decode nonce `orDie` "Could not decode nonce" decrypted <- secretboxOpen encrypt n private `orDie` "Could not decrypt private state" - gameOf public <$> eitherDecode' (fromStrict decrypted) - else return $ Left "The game state has been tampered with" + decoded <- eitherDecode' (fromStrict decrypted) + check (link decoded == gameState public) + `orDie` "Private and public parts do not match" + return $ merge public decoded where orDie :: Maybe a -> String -> Either String a orDie m errorMessage = maybe (Left errorMessage) Right m + check :: Bool -> Maybe () + check test = if test then Just () else Nothing play :: PlayerID -> KoiKoi.Move -> PublicGame -> App.T (Either String (Game, [KoiKoi.Action])) play playerID move publicGame