gore-and-ash-demo (empty) → 1.0.0.0
raw patch · 27 files changed
+1534/−0 lines, 27 filesdep +GLFW-bdep +OpenGLdep +basesetup-changed
Dependencies added: GLFW-b, OpenGL, base, bytestring, cereal, containers, deepseq, exception-transformers, exceptions, gore-and-ash, gore-and-ash-actor, gore-and-ash-logging, gore-and-ash-network, gore-and-ash-sdl, gore-and-ash-sync, hashable, lens, linear, network, sdl2, text, text-show, time, transformers, unordered-containers, vector
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- gore-and-ash-demo.cabal +136/−0
- src/client/Consts.hs +6/−0
- src/client/Game.hs +124/−0
- src/client/Game/Bullet.hs +45/−0
- src/client/Game/Bullet/Data.hs +43/−0
- src/client/Game/Camera.hs +80/−0
- src/client/Game/Core.hs +58/−0
- src/client/Game/Data.hs +42/−0
- src/client/Game/Player.hs +95/−0
- src/client/Game/Player/Data.hs +50/−0
- src/client/Graphics/Bullet.hs +31/−0
- src/client/Graphics/Square.hs +36/−0
- src/client/Main.hs +87/−0
- src/server/Game.hs +105/−0
- src/server/Game/Bullet.hs +72/−0
- src/server/Game/Bullet/Data.hs +55/−0
- src/server/Game/Core.hs +57/−0
- src/server/Game/Data.hs +68/−0
- src/server/Game/Player.hs +77/−0
- src/server/Game/Player/Data.hs +63/−0
- src/server/Main.hs +76/−0
- src/shared/FPS.hs +27/−0
- src/shared/Game/Bullet/Shared.hs +5/−0
- src/shared/Game/Player/Shared.hs +15/−0
- src/shared/Game/Shared.hs +49/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Gushcha Anton (c) 2015-2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ gore-and-ash-demo.cabal view
@@ -0,0 +1,136 @@+name: gore-and-ash-demo+version: 1.0.0.0+synopsis: Demonstration game for Gore&Ash game engine+description: Please see README.md+homepage: https://github.com/Teaspot-Studio/gore-and-ash-demo+license: BSD3+license-file: LICENSE+author: Anton Gushcha+maintainer: ncrashed@gmail.com+copyright: 2015-2016 Anton Gushcha+category: Game+build-type: Simple+cabal-version: >=1.10++executable gore-and-ash-demo-client+ hs-source-dirs: src/client+ src/shared+ main-is: Main.hs+ default-language: Haskell2010+ + other-modules: + Consts+ FPS+ Game+ Game.Bullet+ Game.Bullet.Data+ Game.Bullet.Shared+ Game.Camera+ Game.Core+ Game.Data+ Game.Player+ Game.Player.Data+ Game.Player.Shared+ Game.Shared+ Graphics.Bullet+ Graphics.Square+ + build-depends: base >= 4.7 && < 5+ , bytestring >= 0.10.6.0+ , cereal >= 0.4.1.1+ , containers >= 0.5.6.2+ , deepseq >= 1.4+ , exception-transformers >= 0.4+ , exceptions >= 0.8.0.2+ , GLFW-b >= 1.4.7.3+ , gore-and-ash >= 1.1.0.0+ , gore-and-ash-actor >= 1.1.0.0+ , gore-and-ash-logging >= 1.1.0.0+ , gore-and-ash-network >= 1.1.0.0+ , gore-and-ash-sdl >= 1.1.0.0+ , gore-and-ash-sync >= 1.1.0.0+ , hashable >= 1.2.3.3+ , lens >= 4.12.3+ , linear >= 1.19+ , network >= 2.6.2.1+ , OpenGL >= 3.0.0.0+ , sdl2 >= 2.1.1 + , text >= 1.2.1+ , text-show >= 2+ , transformers >= 0.4.2+ , unordered-containers >= 0.2.5.1+ , vector >= 0.11.0.0++ default-extensions: + Arrows+ DataKinds+ DeriveGeneric+ FlexibleContexts+ FlexibleInstances+ GeneralizedNewtypeDeriving+ MultiParamTypeClasses+ OverloadedStrings+ RankNTypes+ RecordWildCards+ TypeFamilies+ ++ ghc-options: -Wall -threaded++executable gore-and-ash-demo-server+ hs-source-dirs: src/server+ src/shared+ main-is: Main.hs+ default-language: Haskell2010+ + other-modules: + FPS+ Game+ Game.Bullet+ Game.Bullet.Data+ Game.Bullet.Shared+ Game.Core+ Game.Data+ Game.Player+ Game.Player.Data+ Game.Player.Shared+ Game.Shared+ + build-depends: base >= 4.7 && < 5+ , bytestring >= 0.10.6.0+ , cereal >= 0.4.1.1+ , containers >= 0.5.6.2+ , deepseq >= 1.4+ , exception-transformers >= 0.4+ , exceptions >= 0.8.0.2 + , gore-and-ash >= 1.1.0.0+ , gore-and-ash-actor >= 1.1.0.0+ , gore-and-ash-logging >= 1.1.0.0+ , gore-and-ash-network >= 1.1.0.0+ , gore-and-ash-sync >= 1.1.0.0+ , hashable >= 1.2.3.3+ , lens >= 4.12.3+ , linear >= 1.19+ , network >= 2.6.2.1+ , text >= 1.2.1+ , text-show >= 2+ , time >= 1.5.0.1+ , transformers >= 0.4.2+ , unordered-containers >= 0.2.5.1+ + default-extensions: + Arrows+ DataKinds+ DeriveGeneric+ FlexibleContexts+ FlexibleInstances+ GeneralizedNewtypeDeriving+ MultiParamTypeClasses+ OverloadedStrings+ RankNTypes+ RecordWildCards+ ScopedTypeVariables+ TupleSections+ TypeFamilies+ + ghc-options: -Wall -threaded
+ src/client/Consts.hs view
@@ -0,0 +1,6 @@+module Consts where++import Data.Text ++mainWindowName :: Text+mainWindowName = "mainWindow"
+ src/client/Game.hs view
@@ -0,0 +1,124 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Game(+ mainWire+ , Player(..)+ , Camera(..)+ , Game(..)+ , AppMonad+ ) where++import Control.Wire+import Control.Wire.Unsafe.Event (event)+import Data.Text (pack)+import Prelude hiding (id, (.))+import qualified Data.HashMap.Strict as H +import qualified Data.Sequence as S ++import Game.GoreAndAsh+import Game.GoreAndAsh.Actor +import Game.GoreAndAsh.Logging+import Game.GoreAndAsh.Network+import Game.GoreAndAsh.SDL+import Game.GoreAndAsh.Sync ++import Consts+import Game.Bullet +import Game.Camera +import Game.Core+import Game.Data+import Game.Player+import Game.Shared++-- | Entry point of the game, controls global game stages+mainWire :: AppWire a (Maybe Game)+mainWire = waitConnection+ where+ -- | Game stage before connection established+ waitConnection = dSwitch $ proc _ -> do + e <- mapE seqLeftHead . peersConnected -< ()+ traceEvent (const "Connected to server") -< e+ returnA -< (Nothing, waitPlayerId <$> e) ++ -- | Helper to get left most element from sequence or die+ seqLeftHead s = case S.viewl s of + S.EmptyL -> error "seqLeftHead: empty sequence"+ (h S.:< _) -> h ++ -- | After connection succeded, get important data to begin game+ waitPlayerId peer = switch $ proc _ -> do + -- Send request for player id+ emsg <- now -< PlayerRequestId+ peerSendIndexed peer (ChannelID 0) globalGameId ReliableMessage -< emsg+ traceEvent (const "Waiting for player id") -< emsg+ + -- Waiting for respond with player id+ e <- mapE seqLeftHead . filterMsgs isPlayerResponseId . peerIndexedMessages peer (ChannelID 0) globalGameId -< () + traceEvent (\(PlayerResponseId i _ _) -> "Got player id: " <> pack (show i)) -< e+ + -- When recieved switch to next stage+ let nextWire = (\(PlayerResponseId i bulletsColId playersColId) -> untilDisconnected peer (PlayerId i) (fromCounter bulletsColId) (fromCounter playersColId)) <$> e+ returnA -< (Nothing, nextWire)++ -- | Main game stage, actual playing+ untilDisconnected peer pid bulletsColId playersColId = switch $ proc _ -> do + e <- peerDisconnected peer -< ()+ traceEvent (const "Disconnected from server") -< e+ g <- runActor' (playGame pid peer bulletsColId playersColId) -< ()+ returnA -< (g, const disconnected <$> e)++ -- | Final stage, we are disconnected+ disconnected = pure Nothing++-- | Controller of main game stage when actual playing is happen+playGame :: PlayerId -> Peer -> RemActorCollId -> RemActorCollId -> AppActor GameId a (Maybe Game)+playGame pid peer bulletsColId playersColId = makeFixedActor globalGameId $ stateWire Nothing $ proc (_, mg) -> do + c <- runActor' $ cameraWire initialCamera -< ()+ ex <- exitCheck -< ()+ (ps, bs) <- case mg of + Nothing -> returnA -< (H.empty, H.empty)+ Just g -> do + ps <- processPlayers playersColId -< g+ bs <- processBullets bulletsColId -< g+ returnA -< (ps, bs)++ forceNF -< Just $! case mg of + Nothing -> Game {+ gameId = globalGameId+ , gamePlayer = H.lookup pid ps+ , gameCamera = c+ , gamePlayers = ps+ , gameBullets = bs+ , gameExit = ex+ }+ Just g -> g {+ gamePlayer = H.lookup pid ps+ , gameCamera = c+ , gamePlayers = ps+ , gameBullets = bs+ , gameExit = ex+ }+ where+ -- | Check if user or system wants us to die+ exitCheck = proc _ -> do + e <- windowClosed mainWindowName -< ()+ q <- liftGameMonad sdlQuitEventM -< ()+ returnA -< event False (const True) e || q++ -- | Maker of startup camera+ initialCamera i = Camera i 0 0.1++ -- | Handles spawing/despawing of other players+ processPlayers :: RemActorCollId -> AppWire Game PlayerMap+ processPlayers cid = proc g -> do+ ps <- runActor' $ remoteActorCollectionClient cid peer makePlayerActor -< gameCamera g+ returnA -< mapFromSeq $ fmap playerId ps `S.zip` ps+ where+ makePlayerActor i = if i == pid + then playerActor peer pid+ else remotePlayerActor peer i++ -- | Handles spawing/despawing of bullets+ processBullets :: RemActorCollId -> AppWire Game BulletMap+ processBullets cid = proc g -> do + bs <- runActor' $ remoteActorCollectionClient cid peer (bulletActor peer) -< g+ returnA -< mapFromSeq $ fmap bulletId bs `S.zip` bs
+ src/client/Game/Bullet.hs view
@@ -0,0 +1,45 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Game.Bullet(+ module ReExport+ , bulletActor+ ) where++import Control.Wire +import Prelude hiding (id, (.))++import Game.Bullet.Data as ReExport+import Game.Core +import Game.Data ++import Game.GoreAndAsh+import Game.GoreAndAsh.Sync +import Game.GoreAndAsh.Actor+import Game.GoreAndAsh.Network ++import Graphics.Bullet++instance RemoteActor BulletId Bullet where+ type RemoteActorState BulletId = Bullet+ type RemoteActorId Bullet = BulletId++bulletActor :: Peer -> BulletId -> AppActor BulletId Game Bullet +bulletActor peer i = makeFixedActor i $ stateWire initalBullet mainController+ where+ initalBullet = Bullet {+ bulletId = i+ , bulletPos = 0+ , bulletVel = 0+ , bulletOwner = fromCounter (-1)+ }++ mainController :: AppWire (Game, Bullet) Bullet+ mainController = proc (g, b) -> do+ liftGameMonad3 renderBullet -< (bulletPos b, bulletVel b, gameCamera g)+ forceNF . clientSync bulletSync peer i -< b++ bulletSync :: FullSync AppMonad BulletId Bullet+ bulletSync = Bullet + <$> pure i + <*> serverSide 0 bulletPos + <*> serverSide 1 bulletVel + <*> serverSide 2 bulletOwner
+ src/client/Game/Bullet/Data.hs view
@@ -0,0 +1,43 @@+module Game.Bullet.Data(+ BulletId(..)+ , Bullet(..)+ , BulletMessage(..)+ , BulletNetMessage+ ) where++import Control.DeepSeq+import Data.Hashable+import Data.Typeable +import GHC.Generics (Generic)+import Linear+import Prelude hiding (id, (.))++import Game.GoreAndAsh.Actor+import Game.GoreAndAsh.Sync++import Game.Player.Data +import Game.Bullet.Shared++data Bullet = Bullet {+ bulletId :: !BulletId+, bulletPos :: !(V2 Double)+, bulletVel :: !(V2 Double)+, bulletOwner :: !PlayerId+} deriving (Generic)++instance NFData Bullet ++newtype BulletId = BulletId { unBulletId :: Int } deriving (Eq, Show, Generic) +instance NFData BulletId +instance Hashable BulletId ++data BulletMessage = BulletMessageStub deriving (Typeable, Generic)+instance NFData BulletMessage ++instance ActorMessage BulletId where+ type ActorMessageType BulletId = BulletMessage+ toCounter = unBulletId+ fromCounter = BulletId+ +instance NetworkMessage BulletId where + type NetworkMessageType BulletId = BulletNetMessage
+ src/client/Game/Camera.hs view
@@ -0,0 +1,80 @@+module Game.Camera(+ Camera(..)+ , CameraId(..)+ , cameraWire+ , cameraMatrix+ , cameraToWorld+ , cameraFromWorld+ ) where++import Control.DeepSeq+import Control.Lens +import Control.Wire+import Control.Wire.Unsafe.Event+import GHC.Generics (Generic)+import Linear+import Prelude hiding (id, (.))++import Game.Core+import Game.GoreAndAsh+import Game.GoreAndAsh.Actor+import Game.GoreAndAsh.SDL++data Camera = Camera {+ cameraId :: !CameraId+, cameraPos :: !(V2 Double)+, cameraZoom :: !Double+} deriving (Generic)++instance NFData Camera ++newtype CameraId = CameraId { unCameraId :: Int } deriving (Eq, Show, Generic)+instance NFData CameraId ++data CameraMessage++instance ActorMessage CameraId where+ type ActorMessageType CameraId = CameraMessage+ toCounter = unCameraId+ fromCounter = CameraId++cameraWire :: (CameraId -> Camera) -> AppActor CameraId a Camera +cameraWire initialCamera = makeActor $ \i -> stateWire (initialCamera i) $ proc (_, c) -> do + forceNF+ . moveCamera (V2 0 (-cameraSpeed)) ScancodeS + . moveCamera (V2 0 cameraSpeed) ScancodeW + . moveCamera (V2 cameraSpeed 0) ScancodeD+ . moveCamera (V2 (-cameraSpeed) 0) ScancodeA+ . zoomCamera 0.1 -< c+ where + cameraSpeed :: Double + cameraSpeed = 0.1++ moveCamera :: V2 Double -> Scancode -> AppWire Camera Camera+ moveCamera dv k = proc c -> do + e <- keyPressing k -< ()+ let newCam = c {+ cameraPos = cameraPos c + dv + }+ returnA -< event c (const newCam) e++ zoomCamera :: Double -> AppWire Camera Camera + zoomCamera z = proc c -> do + e <- mouseScrollY -< ()+ let newCam k = c {+ cameraZoom = min 3 $ max 0.01 $ cameraZoom c + 0.2 * z * k+ }+ returnA -< event c (newCam . fromIntegral) e ++-- | Calculate transformation matrix for camera+cameraMatrix :: Camera -> M33 Double+cameraMatrix Camera{..} = translate2D (V2 (-cameraPos^._x) (-cameraPos^._y))+ !*! scale2D (V2 (-cameraZoom) (-cameraZoom))++-- | Transform camera local coords to world+cameraToWorld :: Camera -> V2 Double -> V2 Double+cameraToWorld c v = inv33 (cameraMatrix c) `applyTransform2D` v++-- | Transform world coords to camera coords+cameraFromWorld :: Camera -> V2 Double -> V2 Double +cameraFromWorld c v = cameraMatrix c `applyTransform2D` v
+ src/client/Game/Core.hs view
@@ -0,0 +1,58 @@+module Game.Core(+ AppMonad(..)+ , AppWire+ , AppActor+ ) where++import Control.DeepSeq+import Control.Monad.Catch+import Control.Monad.Fix+import Control.Monad.IO.Class+import Control.Wire+import Data.Proxy +import Game.GoreAndAsh+import GHC.Generics (Generic)+import Prelude hiding (id, (.))++import Game.GoreAndAsh.Actor+import Game.GoreAndAsh.Logging+import Game.GoreAndAsh.Network+import Game.GoreAndAsh.SDL +import Game.GoreAndAsh.Sync ++-- | Application monad is monad stack build from given list of modules over base monad (IO)+type AppStack = ModuleStack [LoggingT, ActorT, NetworkT, SyncT, SDLT] IO+newtype AppState = AppState (ModuleState AppStack)+ deriving (Generic)++instance NFData AppState ++-- | Wrapper around type family+newtype AppMonad a = AppMonad (AppStack a)+ deriving (Functor, Applicative, Monad, MonadFix, MonadIO, LoggingMonad, NetworkMonad, ActorMonad, MonadCatch, MonadThrow, MonadSDL)++-- | Current GHC (7.10.3) isn't able to derive this+instance SyncMonad AppMonad where + getSyncIdM = AppMonad . getSyncIdM+ getSyncTypeRepM = AppMonad . getSyncTypeRepM+ registerSyncIdM = AppMonad . registerSyncIdM+ addSyncTypeRepM a b = AppMonad $ addSyncTypeRepM a b+ syncScheduleMessageM peer ch i mt msg = AppMonad $ syncScheduleMessageM peer ch i mt msg+ syncSetLoggingM = AppMonad . syncSetLoggingM+ syncSetRoleM = AppMonad . syncSetRoleM+ syncGetRoleM = AppMonad syncGetRoleM+ syncRequestIdM a b = AppMonad $ syncRequestIdM a b + +instance GameModule AppMonad AppState where + type ModuleState AppMonad = AppState+ runModule (AppMonad m) (AppState s) = do + (a, s') <- runModule m s + return (a, AppState s')+ newModuleState = AppState <$> newModuleState+ withModule _ = withModule (Proxy :: Proxy AppStack)+ cleanupModule (AppState s) = cleanupModule s ++-- | Arrow that is build over the monad stack+type AppWire a b = GameWire AppMonad a b+-- | App actor that is monadic action to create indexed wire+type AppActor i a b = GameActor AppMonad i a b
+ src/client/Game/Data.hs view
@@ -0,0 +1,42 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Game.Data(+ BulletMap+ , PlayerMap+ , Game(..)+ ) where++import Control.DeepSeq+import Game.Shared+import GHC.Generics+import qualified Data.HashMap.Strict as H ++import Game.Bullet.Data+import Game.Camera +import Game.Player.Data ++import Game.GoreAndAsh.Actor +import Game.GoreAndAsh.Sync ++type BulletMap = H.HashMap BulletId Bullet+type PlayerMap = H.HashMap PlayerId Player ++data Game = Game {+ gameId :: !GameId+, gamePlayer :: !(Maybe Player)+, gameCamera :: !Camera+, gamePlayers :: !PlayerMap+, gameBullets :: !BulletMap+, gameExit :: !Bool+} deriving (Generic)++instance NFData Game ++data GameMessage ++instance ActorMessage GameId where+ type ActorMessageType GameId = GameMessage + toCounter = unGameId+ fromCounter = GameId++instance NetworkMessage GameId where + type NetworkMessageType GameId = GameNetMessage
+ src/client/Game/Player.hs view
@@ -0,0 +1,95 @@+module Game.Player(+ module ReExport+ , playerActor+ , remotePlayerActor+ ) where++import Control.Wire+import Control.Wire.Unsafe.Event+import Linear+import Prelude hiding (id, (.))++import Game.Core+import Game.GoreAndAsh+import Game.GoreAndAsh.Actor+import Game.GoreAndAsh.Network+import Game.GoreAndAsh.SDL +import Game.GoreAndAsh.Sync++import Game.Camera +import Game.Player.Data as ReExport+import Graphics.Square++playerActor :: Peer -> PlayerId -> AppActor PlayerId Camera Player +playerActor peer i = makeFixedActor i $ stateWire initialPlayer $ proc (c, p) -> do + processFire -< (c, p)+ liftGameMonad4 renderSquare -< (playerSize p, playerPos p, playerColor p, c)+ forceNF . clientSync playerSync peer i . controlPlayer -< p+ where+ initialPlayer = Player {+ playerId = i + , playerPos = 0+ , playerColor = V3 1 0 0+ , playerRot = 0+ , playerSpeed = 0.5+ , playerSize = 1+ }+ + controlPlayer :: AppWire Player Player+ controlPlayer = + movePlayer (V2 (-1) 0) ScancodeRight+ . movePlayer (V2 1 0) ScancodeLeft+ . movePlayer (V2 0 1) ScancodeDown+ . movePlayer (V2 0 (-1)) ScancodeUp++ movePlayer :: V2 Double -> Scancode -> AppWire Player Player+ movePlayer dv k = proc p -> do + e <- keyPressing k -< ()+ dt <- deltaTime -< ()+ let newPlayer = p {+ playerPos = playerPos p + dv * v2 (dt * playerSpeed p)+ }+ v2 a = V2 a a+ returnA -< event p (const newPlayer) e++ processFire :: AppWire (Camera, Player) ()+ processFire = proc (c, p) -> do + e <- mouseClick ButtonLeft -< ()+ let wpos = cameraToWorld c <$> e+ let edir = (\v -> normalize $ v - playerPos p) <$> wpos + let emsg = NetMsgPlayerFire <$> edir+ peerSendIndexed peer (ChannelID 0) i ReliableMessage -< emsg+ returnA -< ()++ playerSync :: FullSync AppMonad PlayerId Player + playerSync = Player + <$> pure i + <*> clientSide peer 0 playerPos+ <*> clientSide peer 1 playerColor+ <*> clientSide peer 2 playerRot+ <*> clientSide peer 3 playerSpeed+ <*> clientSide peer 4 playerSize++-- | Actor for updating local state of remote player on server+remotePlayerActor :: Peer -> PlayerId -> AppActor PlayerId Camera Player+remotePlayerActor peer i = makeFixedActor i $ stateWire initPlayer $ proc (c, p) -> do + liftGameMonad4 renderSquare -< (playerSize p, playerPos p, playerColor p, c)+ forceNF . clientSync playerSync peer i -< p+ where+ initPlayer = Player {+ playerId = i + , playerPos = 0 + , playerColor = 0+ , playerRot = 0+ , playerSpeed = 0+ , playerSize = 1+ }++ playerSync :: FullSync AppMonad PlayerId Player + playerSync = Player + <$> pure i + <*> serverSide 0 playerPos+ <*> serverSide 1 playerColor+ <*> serverSide 2 playerRot+ <*> serverSide 3 playerSpeed+ <*> serverSide 4 playerSize
+ src/client/Game/Player/Data.hs view
@@ -0,0 +1,50 @@+module Game.Player.Data(+ Player(..)+ , PlayerId(..)+ , PlayerMessage(..)+ , PlayerNetMessage(..)+ ) where++import Control.DeepSeq+import Data.Hashable+import Data.Serialize+import Data.Typeable+import GHC.Generics (Generic)+import Linear+import Prelude hiding (id, (.))++import Game.GoreAndAsh.Actor+import Game.GoreAndAsh.Sync++import Game.Player.Shared++data Player = Player {+ playerId :: !PlayerId+, playerPos :: !(V2 Double)+, playerColor :: !(V3 Double) +, playerRot :: !Double+, playerSpeed :: !Double+, playerSize :: !Double+} deriving (Generic, Show)++instance NFData Player ++newtype PlayerId = PlayerId { unPlayerId :: Int } deriving (Eq, Show, Generic)+instance NFData PlayerId +instance Serialize PlayerId +instance Hashable PlayerId ++data PlayerMessage = PlayerMessageStub deriving (Typeable, Generic)+instance NFData PlayerMessage ++instance ActorMessage PlayerId where+ type ActorMessageType PlayerId = PlayerMessage+ toCounter = unPlayerId+ fromCounter = PlayerId++instance NetworkMessage PlayerId where + type NetworkMessageType PlayerId = PlayerNetMessage++instance RemoteActor PlayerId Player where+ type RemoteActorState PlayerId = Player+ type RemoteActorId Player = PlayerId
+ src/client/Graphics/Bullet.hs view
@@ -0,0 +1,31 @@+module Graphics.Bullet(+ renderBullet+ ) where++import Consts+import Game.Camera +import Game.GoreAndAsh+import Game.GoreAndAsh.SDL +import Linear+import Linear.Affine+import SDL ++-- | Function of rendering player+renderBullet :: MonadSDL m => V2 Double -> V2 Double -> Camera -> GameMonadT m ()+renderBullet pos vel c = do + mwr <- sdlGetWindowM mainWindowName+ case mwr of + Nothing -> return ()+ Just (w, r) -> do + wsize <- fmap (fmap fromIntegral) . get $ windowSize w+ rendererDrawColor r $= V4 0 0 0 255+ drawLine r (apply wsize startPoint) (apply wsize endPoint)+ where+ velScaleFactor = 0.04+ startPoint = negate (velScaleFactor * 0.5 * vel)+ endPoint = velScaleFactor * 0.5 * vel++ apply wsize = P . fmap round . applyTransform2D (modelMtx wsize)++ modelMtx :: V2 Double -> M33 Double + modelMtx wsize = viewportTransform2D 0 wsize !*! cameraMatrix c !*! translate2D pos
+ src/client/Graphics/Square.hs view
@@ -0,0 +1,36 @@+module Graphics.Square(+ renderSquare+ ) where++import Consts+import Data.Word+import Foreign.C.Types+import Game.Camera +import Game.GoreAndAsh+import Game.GoreAndAsh.SDL +import Linear+import Linear.Affine+import SDL ++-- | Function of rendering player+renderSquare :: MonadSDL m => Double -> V2 Double -> V3 Double -> Camera -> GameMonadT m ()+renderSquare size pos col c = do + mwr <- sdlGetWindowM mainWindowName+ case mwr of + Nothing -> return ()+ Just (w, r) -> do + wsize <- fmap (fmap fromIntegral) . get $ windowSize w+ rendererDrawColor r $= transColor col + fillRect r $ Just $ transformedSquare wsize+ where+ transColor :: V3 Double -> V4 Word8+ transColor (V3 r g b) = V4 (round $ r * 255) (round $ g * 255) (round $ b * 255) 255++ modelMtx :: V2 Double -> M33 Double + modelMtx wsize = viewportTransform2D 0 wsize !*! cameraMatrix c !*! translate2D pos++ transformedSquare :: V2 Double -> Rectangle CInt+ transformedSquare wsize = Rectangle (P topleft) (botright - topleft) + where+ topleft = fmap round . applyTransform2D (modelMtx wsize) $ V2 (-size/2) (-size/2)+ botright = fmap round . applyTransform2D (modelMtx wsize) $ V2 (size/2) (size/2)
+ src/client/Main.hs view
@@ -0,0 +1,87 @@+module Main where ++import Consts+import Control.DeepSeq +import Control.Monad (join)+import Control.Monad.IO.Class+import Data.Maybe (fromMaybe)+import Data.Proxy +import FPS+import Game +import Game.GoreAndAsh+import Game.GoreAndAsh.Network+import Game.GoreAndAsh.SDL+import Game.GoreAndAsh.Sync+import Network.BSD (getHostByName, hostAddress)+import Network.Socket (SockAddr(..))+import System.Environment+import Text.Read ++import Linear (V4(..))++gameFPS :: Int +gameFPS = 60 ++parseArgs :: IO (String, Int)+parseArgs = do + args <- getArgs + case args of + [h, p] -> case readMaybe p of + Nothing -> fail "Failed to parse port"+ Just pint -> return (h, pint)+ _ -> fail "Misuse of arguments: gore-and-ash-client HOST PORT"+++main :: IO ()+main = withModule (Proxy :: Proxy AppMonad) $ do+ gs <- newGameState mainWire+ (host, port) <- liftIO parseArgs+ fps <- makeFPSBounder 60+ firstLoop fps host port gs + where + -- | Resolve given hostname and port+ getAddr s p = do+ he <- getHostByName s+ return $ SockAddrInet p $ hostAddress he++ firstLoop fps host port gs = do + (_, gs') <- stepGame gs $ do + networkSetDetailedLoggingM False+ syncSetLoggingM False+ syncSetRoleM SyncSlave+ networkBind Nothing 1 2 0 0+ addr <- liftIO $ getAddr host (fromIntegral port)+ _ <- networkConnect addr 2 0+ _ <- sdlCreateWindowM mainWindowName "Gore&Ash Client" defaultWindow defaultRenderer+ sdlSetBackColor mainWindowName $ V4 200 200 200 255+ gameLoop fps gs'++ gameLoop fps gs = do+ waitFPSBound fps + (mg, gs') <- stepGame gs (return ())+ mg `deepseq` if fromMaybe False $ gameExit <$> join mg+ then cleanupGameState gs'+ else gameLoop fps gs'++{-+main :: IO ()+main = do+ initializeAll+ window <- createWindow "My SDL Application" defaultWindow+ renderer <- createRenderer window (-1) defaultRenderer+ appLoop renderer++appLoop :: Renderer -> IO ()+appLoop renderer = do+ events <- pollEvents+ let eventIsQPress event =+ case eventPayload event of+ KeyboardEvent keyboardEvent ->+ keyboardEventKeyMotion keyboardEvent == Pressed &&+ keysymKeycode (keyboardEventKeysym keyboardEvent) == KeycodeQ+ _ -> False+ qPressed = not (null (filter eventIsQPress events))+ rendererDrawColor renderer $= V4 0 0 255 255+ clear renderer+ present renderer+ unless qPressed (appLoop renderer) -}
+ src/server/Game.hs view
@@ -0,0 +1,105 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Game(+ mainWire+ , Game(..)+ ) where++import Control.Wire+import Data.Maybe +import Data.Text (pack)+import Linear+import Prelude hiding (id, (.))+import qualified Data.HashMap.Strict as H +import qualified Data.Sequence as S ++import Game.Bullet +import Game.Core+import Game.Data+import Game.Player+import Game.Shared++import Game.GoreAndAsh+import Game.GoreAndAsh.Actor +import Game.GoreAndAsh.Logging+import Game.GoreAndAsh.Network+import Game.GoreAndAsh.Sync ++-- | Wire that returns next player color+playerColors :: AppWire (Event a) (V3 Double)+playerColors = dDispense [+ V3 1 0 0+ , V3 0 1 0+ , V3 0 0 1+ , V3 1 1 0+ , V3 1 0 1+ , V3 0 1 1+ ]++mainWire :: AppActor GameId a Game+mainWire = makeFixedActor globalGameId $ stateWire initGame $ proc (_, g) -> do + forceNF . processBullets . processPlayers -< g+ where + -- | Game at start of the simulation+ initGame = Game {+ gameId = globalGameId+ , gamePlayers = H.empty+ , gamePlayerPeers = H.empty+ , gameBullets = H.empty+ , gameBulletColId = fromCounter (-1)+ , gamePlayerColId = fromCounter (-1)+ }++ -- | Handles process of players connection and disconnections+ processPlayers :: AppWire Game Game+ processPlayers = proc g -> do + conEvent <- peersConnected -< ()+ col <- playerColors -< conEvent+ let addEvent = fmap (spawnPlayer col) <$> conEvent++ disEvent <- peersDisconnected -< ()+ remEvent <- filterJustLE -< fmap (despawnPlayer g) <$> disEvent++ traceEvent (const "New player connected") -< addEvent -- Player id is not ready yet :(+ traceEvent (\i -> "Player " <> (pack . show) i <> " disconnected") -< remEvent++ (ps, i) <- runActor $ remoteActorCollectionServer S.empty -< (g, addEvent, remEvent)+ returnA -< g {+ gamePlayers = mapFromSeq $ fmap playerId ps `S.zip` ps+ , gamePlayerPeers = mapFromSeq $ fmap playerPeer ps `S.zip` fmap playerId ps+ , gamePlayerColId = i+ }++ -- | Spawns new player from peer (creates new arrow)+ spawnPlayer :: V3 Double -> Peer -> AppActor PlayerId Game Player+ spawnPlayer c p = playerActor $ \i -> Player {+ playerId = i+ , playerPos = 0+ , playerColor = c+ , playerRot = 0+ , playerPeer = p+ , playerSpeed = 15+ , playerSize = 1+ }++ -- | Detects player id by peer for despawning+ despawnPlayer :: Game -> Peer -> Maybe PlayerId+ despawnPlayer Game{..} p = H.lookup p gamePlayerPeers++ -- | Handle bullets actors+ processBullets :: AppWire Game Game + processBullets = proc g -> do + addEvent <- mapE (fmap (bulletActor . newBullet)) . actorMessages globalGameId isGameSpawnBullet -< ()+ remEvent <- mapE (fmap $ \(GameDeleteBullet i) -> i) . actorMessages globalGameId isGameDeleteBullet -< ()+ (bs, i) <- runActor $ remoteActorCollectionServer S.empty -< (g, addEvent, remEvent)+ returnA -< g {+ gameBullets = mapFromSeq $ fmap bulletId bs `S.zip` bs+ , gameBulletColId = i+ }+ where+ newBullet (GameSpawnBullet pos vel owner) i = Bullet {+ bulletId = i + , bulletPos = pos+ , bulletVel = vel+ , bulletOwner = owner+ }+ newBullet _ _ = error "newBullet: wrong message"
+ src/server/Game/Bullet.hs view
@@ -0,0 +1,72 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Game.Bullet(+ module ReExport+ , bulletActor+ ) where++import Control.Wire +import Data.Serialize+import Data.Word +import Linear +import Prelude hiding (id, (.))+import qualified Data.HashMap.Strict as H++import Game.Bullet.Data as ReExport+import Game.Core +import Game.Data +import Game.Player.Data+import Game.Shared ++import Game.GoreAndAsh+import Game.GoreAndAsh.Actor +import Game.GoreAndAsh.Sync ++instance RemoteActor BulletId Bullet where+ type RemoteActorState BulletId = Bullet+ type RemoteActorId Bullet = BulletId++bulletActor :: (BulletId -> Bullet) -> AppActor BulletId Game Bullet +bulletActor initalBullet = makeActor $ \i -> stateWire (initalBullet i) $ mainController i+ where++ mainController :: BulletId -> AppWire (Game, Bullet) Bullet+ mainController i = proc (g, b) -> do+ forceNF . serverSync bulletSync i . processBullet -< (b, g)+ where++ -- | Actual bullet logic+ processBullet :: AppWire (Bullet, Game) Bullet + processBullet = proc (b, g) -> do + actorSend globalGameId . at bulletLifespan -< GameDeleteBullet i+ playersShot -< (b, gamePlayers g)+ dt <- deltaTime -< ()+ let newPos = bulletPos b + V2 dt dt * bulletVel b + returnA -< b {+ bulletPos = newPos+ } + where + -- | Test all players was shot+ playersShot :: AppWire (Bullet, PlayerMap) ()+ playersShot = liftGameMonad2 $ \b ps -> mapM_ (playerShot b) . H.elems $! ps ++ -- | Test single player was shot+ playerShot :: Bullet -> Player -> GameMonadT AppMonad ()+ playerShot b p = do + let V2 px py = playerPos p + V2 bx by = bulletPos b+ cond = abs (px - bx) <= playerSize p && abs (py - by) <= playerSize p+ if cond then do + actorSendM globalGameId . GameDeleteBullet $! i+ actorSendM (playerId p) . PlayerShotMessage . bulletOwner $! b+ else return ()++ bulletSync :: FullSync AppMonad BulletId Bullet+ bulletSync = Bullet + <$> pure i + <*> fsync 0 bulletPos + <*> fsync 1 bulletVel + <*> fsync 2 bulletOwner+ where+ fsync :: (Eq a, Serialize a) + => Word64 -> (Bullet -> a) -> Sync AppMonad BulletId Bullet a+ fsync fi f = condSync (fieldChanges f &> periodic 4 . arr f) f $ serverSide fi f
+ src/server/Game/Bullet/Data.hs view
@@ -0,0 +1,55 @@+module Game.Bullet.Data(+ BulletId(..)+ , Bullet(..)+ , BulletMessage(..)+ , BulletNetMessage+ , bulletSpeed+ , bulletLifespan+ ) where++import Control.DeepSeq+import Data.Hashable+import Data.Time.Clock (NominalDiffTime)+import Data.Typeable +import GHC.Generics (Generic)+import Linear+import Prelude hiding (id, (.))++import Game.GoreAndAsh.Actor+import Game.GoreAndAsh.Sync++import Game.Player.Data +import Game.Bullet.Shared ++data Bullet = Bullet {+ bulletId :: !BulletId+, bulletPos :: !(V2 Double)+, bulletVel :: !(V2 Double)+, bulletOwner :: !PlayerId+} deriving (Generic)++instance NFData Bullet ++newtype BulletId = BulletId { unBulletId :: Int } deriving (Eq, Show, Generic) +instance NFData BulletId +instance Hashable BulletId ++data BulletMessage = BulletMessageStub deriving (Typeable, Generic)+instance NFData BulletMessage ++instance ActorMessage BulletId where+ type ActorMessageType BulletId = BulletMessage+ toCounter = unBulletId+ fromCounter = BulletId+ +instance NetworkMessage BulletId where + type NetworkMessageType BulletId = BulletNetMessage++instance ElementWithId Bullet BulletId where+ elementId = bulletId++bulletSpeed :: Double +bulletSpeed = 20++bulletLifespan :: NominalDiffTime +bulletLifespan = 5
+ src/server/Game/Core.hs view
@@ -0,0 +1,57 @@+module Game.Core(+ AppMonad+ , AppWire+ , AppActor+ ) where++import Control.DeepSeq+import Control.Monad.Catch+import Control.Monad.Fix +import Control.Monad.IO.Class+import Control.Wire+import Data.Proxy+import Game.GoreAndAsh+import GHC.Generics (Generic)+import Prelude hiding (id, (.))++import Game.GoreAndAsh.Actor+import Game.GoreAndAsh.Logging+import Game.GoreAndAsh.Network+import Game.GoreAndAsh.Sync++-- | Application monad is monad stack build from given list of modules over base monad (IO)+type AppStack = ModuleStack [LoggingT, ActorT, NetworkT, SyncT] IO+newtype AppState = AppState (ModuleState AppStack)+ deriving (Generic)++instance NFData AppState ++-- | Wrapper around type family+newtype AppMonad a = AppMonad (AppStack a)+ deriving (Functor, Applicative, Monad, MonadFix, MonadIO, LoggingMonad, NetworkMonad, ActorMonad, MonadThrow, MonadCatch)++-- | Current GHC (7.10.3) isn't able to derive this+instance SyncMonad AppMonad where + getSyncIdM = AppMonad . getSyncIdM+ getSyncTypeRepM = AppMonad . getSyncTypeRepM+ registerSyncIdM = AppMonad . registerSyncIdM+ addSyncTypeRepM a b = AppMonad $ addSyncTypeRepM a b+ syncScheduleMessageM peer ch i mt msg = AppMonad $ syncScheduleMessageM peer ch i mt msg+ syncSetLoggingM = AppMonad . syncSetLoggingM+ syncSetRoleM = AppMonad . syncSetRoleM+ syncGetRoleM = AppMonad syncGetRoleM+ syncRequestIdM a b = AppMonad $ syncRequestIdM a b + +instance GameModule AppMonad AppState where + type ModuleState AppMonad = AppState+ runModule (AppMonad m) (AppState s) = do + (a, s') <- runModule m s + return (a, AppState s')+ newModuleState = AppState <$> newModuleState+ withModule _ = withModule (Proxy :: Proxy AppStack)+ cleanupModule (AppState s) = cleanupModule s ++-- | Arrow that is build over the monad stack+type AppWire a b = GameWire AppMonad a b+-- | Action that makes indexed app wire+type AppActor i a b = GameActor AppMonad i a b
+ src/server/Game/Data.hs view
@@ -0,0 +1,68 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Game.Data(+ Game(..)+ , GameMessage(..)+ , PlayerMap+ , PlayerPeerMap+ , BulletsMap+ -- | Helpers+ , isGameSpawnBullet+ , isGameDeleteBullet+ ) where++import Control.DeepSeq+import GHC.Generics (Generic)+import Prelude hiding (id, (.))+import qualified Data.HashMap.Strict as H +import Linear ++import Game.Bullet.Data+import Game.Player.Data+import Game.Shared++import Game.GoreAndAsh.Actor+import Game.GoreAndAsh.Network+import Game.GoreAndAsh.Sync ++type PlayerMap = H.HashMap PlayerId Player+type PlayerPeerMap = H.HashMap Peer PlayerId +type BulletsMap = H.HashMap BulletId Bullet ++-- | Server local messages to game actor+data GameMessage = + -- | Spawn bullet at given pos with given velocity and owner+ GameSpawnBullet !(V2 Double) !(V2 Double) !PlayerId + -- | Deletes bullet with specified id+ | GameDeleteBullet !BulletId+ deriving (Generic, Show)++instance ActorMessage GameId where+ type ActorMessageType GameId = GameMessage + toCounter = unGameId+ fromCounter = GameId++instance NetworkMessage GameId where + type NetworkMessageType GameId = GameNetMessage++data Game = Game {+ gameId :: !GameId+, gamePlayers :: !PlayerMap+, gamePlayerPeers :: !PlayerPeerMap+, gameBullets :: !BulletsMap+, gameBulletColId :: !RemActorCollId +, gamePlayerColId :: !RemActorCollId+} deriving (Generic)++instance NFData Game ++-- | Detect specific message+isGameSpawnBullet :: GameMessage -> Bool+isGameSpawnBullet m = case m of + GameSpawnBullet _ _ _ -> True+ _ -> False++-- | Detect specific message+isGameDeleteBullet :: GameMessage -> Bool+isGameDeleteBullet m = case m of + GameDeleteBullet _ -> True+ _ -> False
+ src/server/Game/Player.hs view
@@ -0,0 +1,77 @@+module Game.Player(+ Player(..)+ , PlayerId(..)+ , PlayerMessage+ , playerActor+ ) where++import Control.Wire+import Control.Wire.Unsafe.Event (event)+import Data.Text (pack)+import Linear+import Prelude hiding (id, (.))++import Game.Bullet.Data+import Game.Core+import Game.Data+import Game.Player.Data+import Game.Shared++import Game.GoreAndAsh+import Game.GoreAndAsh.Actor+import Game.GoreAndAsh.Logging+import Game.GoreAndAsh.Network +import Game.GoreAndAsh.Sync++playerActor :: (PlayerId -> Player) -> AppActor PlayerId Game Player +playerActor initialPlayer = makeActor $ \i -> stateWire (initialPlayer i) $ mainController i+ where+ mainController i = proc (g, p) -> do+ p2 <- peerProcessIndexedM peer (ChannelID 0) i netProcess -< p+ (_, p3) <- peerProcessIndexedM peer (ChannelID 0) globalGameId globalNetProcess -< (g, p2)+ forceNF . serverSync playerSync i . playerShot -< p3+ where+ -- | Shortcut for peer+ peer = playerPeer $ initialPlayer i++ -- | Handle when player is shot+ playerShot :: AppWire Player Player+ playerShot = proc p -> do + emsg <- actorMessages i isPlayerShotMessage -< ()+ let newPlayer = p {+ playerPos = 0+ }+ returnA -< event p (const newPlayer) emsg++ -- | Process player specific net messages+ netProcess :: Player -> PlayerNetMessage -> GameMonadT AppMonad Player + netProcess p msg = case msg of + NetMsgPlayerFire v -> do + let d = normalize v + v2 a = V2 a a+ pos = playerPos p + d * v2 (playerSize p * 1.5)+ vel = d * v2 bulletSpeed+ putMsgLnM $ "Fire bullet at " <> pack (show pos) <> " with velocity " <> pack (show vel)+ actorSendM globalGameId $ GameSpawnBullet pos vel $ playerId p+ return p ++ -- | Process global net messages from given peer (player)+ globalNetProcess :: (Game, Player) -> GameNetMessage -> GameMonadT AppMonad (Game, Player)+ globalNetProcess (g, p) msg = case msg of + PlayerRequestId -> do+ peerSendIndexedM peer (ChannelID 0) globalGameId ReliableMessage $ + PlayerResponseId (toCounter i) (toCounter $ gameBulletColId g) (toCounter $ gamePlayerColId g)+ return (g, p)+ _ -> do + putMsgLnM $ pack $ show msg+ return (g, p) ++ playerSync :: FullSync AppMonad PlayerId Player + playerSync = Player + <$> pure i + <*> clientSide peer 0 playerPos+ <*> clientSide peer 1 playerColor+ <*> clientSide peer 2 playerRot+ <*> clientSide peer 3 playerSpeed+ <*> clientSide peer 4 playerSize+ <*> pure peer
+ src/server/Game/Player/Data.hs view
@@ -0,0 +1,63 @@+module Game.Player.Data(+ Player(..)+ , PlayerId(..)+ , PlayerMessage(..)+ , PlayerNetMessage(..)+ , isPlayerShotMessage+ ) where++import Control.DeepSeq+import Data.Hashable+import Data.Typeable +import Data.Serialize+import GHC.Generics (Generic)+import Linear+import Prelude hiding (id, (.))++import Game.Player.Shared++import Game.GoreAndAsh.Actor+import Game.GoreAndAsh.Network +import Game.GoreAndAsh.Sync++data Player = Player {+ playerId :: !PlayerId+, playerPos :: !(V2 Double)+, playerColor :: !(V3 Double) +, playerRot :: !Double +, playerSpeed :: !Double +, playerSize :: !Double+, playerPeer :: !Peer+} deriving (Generic)++instance NFData Player ++newtype PlayerId = PlayerId { unPlayerId :: Int } deriving (Eq, Show, Generic) +instance NFData PlayerId +instance Hashable PlayerId +instance Serialize PlayerId++data PlayerMessage =+ -- | The player was shot by specified player+ PlayerShotMessage !PlayerId + deriving (Typeable, Generic)++instance NFData PlayerMessage ++instance ActorMessage PlayerId where+ type ActorMessageType PlayerId = PlayerMessage+ toCounter = unPlayerId+ fromCounter = PlayerId+ +instance NetworkMessage PlayerId where + type NetworkMessageType PlayerId = PlayerNetMessage++instance RemoteActor PlayerId Player where+ type RemoteActorState PlayerId = Player+ type RemoteActorId Player = PlayerId+ +-- | Check specific type of message+isPlayerShotMessage :: PlayerMessage -> Bool+isPlayerShotMessage m = case m of + PlayerShotMessage _ -> True+-- _ -> False
+ src/server/Main.hs view
@@ -0,0 +1,76 @@+module Main where ++import Control.Exception+import Control.Monad.IO.Class+import Data.Proxy +import Game +import Game.Core+import Game.GoreAndAsh+import Game.GoreAndAsh.Actor+import Game.GoreAndAsh.Network+import Game.GoreAndAsh.Sync+import Network.BSD (getHostByName, hostAddress)+import Network.Socket (SockAddr(..))+import System.Exit+import Data.IORef +import System.Environment+import Text.Read ++import FPS ++simulationFPS :: Int +simulationFPS = 60 ++parseArgs :: IO (String, Int)+parseArgs = do + args <- getArgs + case args of + [h, p] -> case readMaybe p of + Nothing -> fail "Failed to parse port"+ Just pint -> return (h, pint)+ _ -> fail "Misuse of arguments: gore-and-ash-server HOST PORT"++main :: IO ()+main = withModule (Proxy :: Proxy AppMonad) $ do+ gs <- newGameState $ runActor' mainWire+ gsRef <- newIORef gs+ (host, port) <- liftIO parseArgs+ fps <- makeFPSBounder simulationFPS+ firstStep fps host port gs gsRef `onCtrlC` exitHandler gsRef+ where+ -- | What to do on emergency exit+ exitHandler gsRef = do + gs <- readIORef gsRef + cleanupGameState gs+ exitSuccess++ -- | Resolve given hostname and port+ getAddr s p = do+ he <- getHostByName s+ return $ SockAddrInet p $ hostAddress he++ -- | Initialization step+ firstStep fps host port gs gsRef = do + (_, gs') <- stepGame gs $ do + networkSetDetailedLoggingM False+ syncSetLoggingM True+ syncSetRoleM SyncMaster+ addr <- liftIO $ getAddr host (fromIntegral port)+ networkBind (Just addr) 100 2 0 0+ writeIORef gsRef gs'+ gameLoop fps gs' gsRef++ -- | Normal game loop+ gameLoop fps gs gsRef = do + waitFPSBound fps+ (_, gs') <- stepGame gs (return ())+ writeIORef gsRef gs'+ gameLoop fps gs' gsRef++-- | Executes given handler on Ctrl-C pressing+onCtrlC :: IO a -> IO () -> IO a+p `onCtrlC` q = catchJust isUserInterrupt p (const $ q >> p `onCtrlC` q)+ where+ isUserInterrupt :: AsyncException -> Maybe ()+ isUserInterrupt UserInterrupt = Just ()+ isUserInterrupt _ = Nothing
@@ -0,0 +1,27 @@+module FPS(+ makeFPSBounder+ , waitFPSBound+ ) where++import GHC.Event+import Control.Concurrent+import Control.Monad ++type FPSBound = MVar ()++-- | Creates mvar that fills periodically with given fps+makeFPSBounder :: Int -> IO FPSBound+makeFPSBounder fps = do+ v <- newEmptyMVar + tm <- getSystemTimerManager+ let t = ceiling ((1000000 :: Double) / fromIntegral fps)+ callback v tm t+ return v+ where+ callback v tm t = do + putMVar v ()+ void $ registerTimeout tm t $ callback v tm t++-- | Wait until next FPS value is reached when the function unblocks+waitFPSBound :: FPSBound -> IO ()+waitFPSBound = takeMVar
@@ -0,0 +1,5 @@+module Game.Bullet.Shared(+ BulletNetMessage+ ) where++data BulletNetMessage
@@ -0,0 +1,15 @@+module Game.Player.Shared(+ PlayerNetMessage(..)+ ) where++import Control.DeepSeq+import Data.Serialize +import GHC.Generics (Generic)+import Linear ++data PlayerNetMessage = + NetMsgPlayerFire !(V2 Double)+ deriving (Generic, Show)++instance NFData PlayerNetMessage+instance Serialize PlayerNetMessage
@@ -0,0 +1,49 @@+module Game.Shared(+ GameId(..)+ , GameNetMessage(..)+ , globalGameId+ , isPlayerRequestId+ , isPlayerResponseId+ -- | Helpers+ , mapFromSeq+ ) where++import Control.DeepSeq+import Data.Hashable +import Data.Serialize+import GHC.Generics+import qualified Data.Foldable as F +import qualified Data.HashMap.Strict as H +import qualified Data.Sequence as S ++-- | Fake id for game global space+newtype GameId = GameId { unGameId :: Int } deriving (Eq, Generic)++instance NFData GameId ++-- | Statically known game id+globalGameId :: GameId +globalGameId = GameId 0 ++-- | Generic messages that are not binded to specific actor+data GameNetMessage =+ PlayerRequestId+ | PlayerResponseId !Int !Int !Int -- ^ Id of player and bullets, players collections+ deriving (Generic, Show)++instance NFData GameNetMessage+instance Serialize GameNetMessage++isPlayerRequestId :: GameNetMessage -> Bool +isPlayerRequestId m = case m of + PlayerRequestId -> True + _ -> False++isPlayerResponseId :: GameNetMessage -> Bool +isPlayerResponseId m = case m of + PlayerResponseId _ _ _ -> True + _ -> False++-- | Construct HashMap from sequence+mapFromSeq :: (Hashable i, Eq i) => S.Seq (i, a) -> H.HashMap i a +mapFromSeq = F.foldl' (\acc (i, a) -> H.insert i a acc) H.empty