First version of Pong

Signed-off-by: magic_rb <richard@brezak.sk>
This commit is contained in:
magic_rb 2023-10-11 22:14:07 +02:00
parent 7080653ca1
commit 9c658ce9d4
No known key found for this signature in database
GPG key ID: 08D5287CC5DDCA0E
11 changed files with 306 additions and 24 deletions

View file

@ -63,6 +63,16 @@ executables:
dependencies: dependencies:
- rpg - rpg
pong:
main: Main.hs
source-dirs: pong
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- rpg
tests: tests:
rpg-test: rpg-test:
main: Spec.hs main: Spec.hs

6
rpg/pong/Main.hs Normal file
View file

@ -0,0 +1,6 @@
module Main where
import Pong
main :: IO ()
main = pongGame

View file

@ -25,13 +25,17 @@ source-repository head
library library
exposed-modules: exposed-modules:
Common
Component.Box Component.Box
Component.Camera Component.Camera
Component.Player Component.Player
Component.Position Component.Position
Component.Velocity
Effectful.Raylib Effectful.Raylib
Effectful.State.Static.Local.Lens Effectful.State.Static.Local.Lens
Lib Lib
Pong
System.Physics
System.Renderer System.Renderer
World World
other-modules: other-modules:
@ -60,6 +64,35 @@ library
, text , text
default-language: GHC2021 default-language: GHC2021
executable pong
main-is: Main.hs
other-modules:
Paths_rpg
autogen-modules:
Paths_rpg
hs-source-dirs:
pong
default-extensions:
OverloadedStrings
DuplicateRecordFields
BlockArguments
OverloadedRecordDot
NoFieldSelectors
TemplateHaskell
LambdaCase
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
build-depends:
apecs-effectful
, base >=4.7 && <5
, bytestring
, effectful
, extra
, h-raylib
, lens
, rpg
, text
default-language: GHC2021
executable rpg-exe executable rpg-exe
main-is: Main.hs main-is: Main.hs
other-modules: other-modules:

22
rpg/src/Common.hs Normal file
View file

@ -0,0 +1,22 @@
module Common ( getCamera ) where
import qualified Apecs.Effectful as AE
import World
import Effectful
import qualified Raylib.Types as RL
import GHC.Float
getCamera
:: ( AE.ECS World :> es )
=> Eff es AE.Entity
-> (Int, Int)
-> Eff es RL.Camera2D
getCamera eff (dimX, dimY) = do
entity <- eff
(c, p) <- AE.get @World @(CameraComponent, PositionComponent) entity
pure $ RL.Camera2D
{ RL.camera2D'offset = RL.Vector2 (int2Float dimX / 2) (int2Float dimY / 2)
, RL.camera2D'target = RL.Vector2 (p.x + fst c.offset) (p.y + snd c.offset)
, RL.camera2D'rotation = 0.0
, RL.camera2D'zoom = int2Float (min dimX dimY) / int2Float c.zoom
}

View file

@ -0,0 +1,13 @@
{-# LANGUAGE TypeFamilies #-}
module Component.Velocity (VelocityComponent(..)) where
import Apecs.Effectful
data VelocityComponent
= Velocity
{ x :: Float
, y :: Float
}
deriving Show
instance Component VelocityComponent where type Storage VelocityComponent = Map VelocityComponent

View file

@ -9,6 +9,7 @@ module Effectful.Raylib
, runDraw2D , runDraw2D
, drawText , drawText
, drawRectangle , drawRectangle
, drawLine
, runRaylibWindow , runRaylibWindow
, Raylib , Raylib
, RaylibDraw , RaylibDraw
@ -40,6 +41,7 @@ type instance DispatchOf RaylibDraw = Dynamic
data RaylibDraw2D :: Effect where data RaylibDraw2D :: Effect where
DrawText :: Text -> Int -> Int -> Int -> RL.Color -> RaylibDraw2D (Eff es) () DrawText :: Text -> Int -> Int -> Int -> RL.Color -> RaylibDraw2D (Eff es) ()
DrawRectangle :: Float -> Float -> Float -> Float -> RL.Color -> RaylibDraw2D (Eff es) () DrawRectangle :: Float -> Float -> Float -> Float -> RL.Color -> RaylibDraw2D (Eff es) ()
DrawLine :: Float -> Float -> Float -> Float -> RL.Color -> RaylibDraw2D (Eff es) ()
type instance DispatchOf RaylibDraw2D = Dynamic type instance DispatchOf RaylibDraw2D = Dynamic
setTargetFPS :: (HasCallStack, Raylib :> es) => Int -> Eff es () setTargetFPS :: (HasCallStack, Raylib :> es) => Int -> Eff es ()
@ -66,6 +68,9 @@ drawText text posX posY fontSize color = send (DrawText text posX posY fontSize
drawRectangle :: (HasCallStack, RaylibDraw2D :> es) => Float -> Float -> Float -> Float -> RL.Color -> Eff es () drawRectangle :: (HasCallStack, RaylibDraw2D :> es) => Float -> Float -> Float -> Float -> RL.Color -> Eff es ()
drawRectangle posX posY width height color = send (DrawRectangle posX posY width height color) drawRectangle posX posY width height color = send (DrawRectangle posX posY width height color)
drawLine :: (HasCallStack, RaylibDraw2D :> es) => Float -> Float -> Float -> Float -> RL.Color -> Eff es ()
drawLine posX posY endX endY color = send (DrawLine posX posY endX endY color)
runRaylibWindow :: (IOE :> es) => Int -> Int -> Text -> Eff (Raylib : es) () -> Eff es () runRaylibWindow :: (IOE :> es) => Int -> Int -> Text -> Eff (Raylib : es) () -> Eff es ()
runRaylibWindow width height name effect = do runRaylibWindow width height name effect = do
window <- liftIO $ RL.initWindow width height (T.unpack name) window <- liftIO $ RL.initWindow width height (T.unpack name)
@ -96,6 +101,7 @@ runRaylibWindow width height name effect = do
case eff of case eff of
DrawText text posX posY fontSize color -> liftIO $ RL.drawText (T.unpack text) posX posY fontSize color DrawText text posX posY fontSize color -> liftIO $ RL.drawText (T.unpack text) posX posY fontSize color
DrawRectangle posX posY width height color -> liftIO $ RL.drawRectangleV (RL.Vector2 posX posY) (RL.Vector2 width height) color DrawRectangle posX posY width height color -> liftIO $ RL.drawRectangleV (RL.Vector2 posX posY) (RL.Vector2 width height) color
DrawLine startX startY endX endY color -> liftIO $ RL.drawLineV (RL.Vector2 startX startY) (RL.Vector2 endX endY) color
liftIO RL.endMode2D liftIO RL.endMode2D
pure res pure res

View file

@ -38,6 +38,7 @@ import Component.Box
import Effectful.State.Static.Local.Lens import Effectful.State.Static.Local.Lens
import Effectful.Raylib import Effectful.Raylib
import System.Renderer import System.Renderer
import Common
data GameConfig data GameConfig
= GameConfig = GameConfig
@ -84,26 +85,6 @@ spawnBox
-> Eff es AE.Entity -> Eff es AE.Entity
spawnBox (posx, posy) color size = AE.newEntity @World (Box color size (0, 0), Position posx posy) spawnBox (posx, posy) color size = AE.newEntity @World (Box color size (0, 0), Position posx posy)
getCamera
:: ( AE.ECS World :> es
, State GameState :> es
, Reader GameConfig :> es
)
=> Eff es AE.Entity
-> Eff es RL.Camera2D
getCamera eff = do
entity <- eff
(c, p) <- AE.get @World @(CameraComponent, PositionComponent) entity
gameState <- get @GameState
gameConfig <- ask @GameConfig
pure $ RL.Camera2D
{ RL.camera2D'offset = RL.Vector2 (int2Float gameState.dimX / 2) (int2Float gameState.dimY / 2)
, RL.camera2D'target = RL.Vector2 (p.x + fst c.offset) (p.y + snd c.offset)
, RL.camera2D'rotation = 0.0
, RL.camera2D'zoom = int2Float (min gameState.dimX gameState.dimY) / int2Float c.zoom
}
initialise initialise
:: ( Raylib :> es :: ( Raylib :> es
, State GameState :> es , State GameState :> es
@ -143,7 +124,8 @@ runGame = do
isKeyDown RL.KeyKpAdd >>= flip when ( AE.modify @World @CameraComponent @CameraComponent cameraEntity (\c -> c { zoom = c.zoom + 1})) isKeyDown RL.KeyKpAdd >>= flip when ( AE.modify @World @CameraComponent @CameraComponent cameraEntity (\c -> c { zoom = c.zoom + 1}))
isKeyDown RL.KeyKpSubtract >>= flip when ( AE.modify @World @CameraComponent @CameraComponent cameraEntity (\c -> c { zoom = c.zoom - 1})) isKeyDown RL.KeyKpSubtract >>= flip when ( AE.modify @World @CameraComponent @CameraComponent cameraEntity (\c -> c { zoom = c.zoom - 1}))
c <- getCamera (gets @GameState (\s -> s.cameraEntity)) dims <- gets @GameState (\s -> (s.dimX, s.dimY))
c <- getCamera (gets @GameState (\s -> s.cameraEntity)) dims
liftIO $ print c liftIO $ print c
runDraw . runDraw2D c $ do runDraw . runDraw2D c $ do

177
rpg/src/Pong.hs Normal file
View file

@ -0,0 +1,177 @@
module Pong (pongGame) where
import Effectful.State.Static.Local
import qualified Apecs.Effectful as AE
import Effectful
import qualified Raylib.Core as RL
import qualified Raylib.Types as RL
import Effectful.Reader.Static
import Effectful.Raylib
import Control.Monad.Extra
import World
import qualified Raylib.Util.Colors as RL
import Common
import Effectful.State.Static.Local.Lens
import Control.Lens hiding ((.=), (%=))
import System.Renderer
import GHC.Float
import System.Physics
data GameState
= GameState
{ dimX :: Int
, dimY :: Int
, camera :: AE.Entity
, player1 :: AE.Entity
, player2 :: AE.Entity
, ball :: AE.Entity
}
deriving Show
makeLensesFor [("dimX", "dimX"), ("dimY", "dimY"), ("camera", "camera"), ("player1", "player1"), ("player2", "player2"), ("ball", "ball")] ''GameState
data GameConfig
= GameConfig
{ playArea :: Int
}
deriving Show
makeLensesFor [("playArea", "playArea")] ''GameConfig
playerMovement
:: ( Raylib :> es
, AE.ECS World :> es
, Reader GameConfig :> es
)
=> (RL.KeyboardKey, Float)
-> (RL.KeyboardKey, Float)
-> Eff es AE.Entity
-> Eff es ()
playerMovement (up, upSpeed) (down, downSpeed) entity = do
playArea <- asks @GameConfig (\c -> c.playArea)
isKeyDown up >>= flip when
(entity >>= flip (AE.modify @World @PositionComponent) (\p -> clampPosition playArea $ Position p.x (p.y + upSpeed)))
isKeyDown down >>= flip when
(entity >>= flip (AE.modify @World @PositionComponent) (\p -> clampPosition playArea $ Position p.x (p.y + downSpeed)))
where
clampPosition
:: Int
-> PositionComponent
-> PositionComponent
clampPosition playArea (Position x y)
| y > int2Float playArea / 2 - 1 = Position x (int2Float playArea / 2 - 1)
| y < int2Float playArea / 2 * (-1) + 1 = Position x (int2Float playArea / 2 * (-1) + 1)
| otherwise = Position x y
ballMovement
:: ( AE.ECS World :> es
, Reader GameConfig :> es
)
=> Eff es AE.Entity
-> Eff es AE.Entity
-> Eff es AE.Entity
-> Eff es ()
ballMovement player1 player2 ball = do
ballEntity <- ball
playArea <- asks @GameConfig (\c -> c.playArea)
player1Position <- player1 >>= AE.get @World
player2Position <- player2 >>= AE.get @World
ballPosition <- AE.get @World ballEntity
ballVelocity <- AE.get @World ballEntity
let newVelocity = ballMovement'
playArea
player1Position
player2Position
(ballPosition, ballVelocity)
AE.set @World ballEntity newVelocity
where
invertYVelocity
:: VelocityComponent
-> VelocityComponent
invertYVelocity (Velocity x y) = Velocity x (-y)
invertXVelocity
:: VelocityComponent
-> VelocityComponent
invertXVelocity (Velocity x y) = Velocity (-x) y
ballMovement'
:: Int
-> PositionComponent
-> PositionComponent
-> (PositionComponent, VelocityComponent)
-> VelocityComponent
ballMovement' playArea player1 player2 (ballPosition, ballVelocity)
| ballPosition.y + 0.25 > int2Float playArea / 2 = invertYVelocity ballVelocity
| ballPosition.y - 0.25 < int2Float playArea / 2 * (-1) = invertYVelocity ballVelocity
| ballPosition.x + 0.75 > player2.x && ballPosition.x + 0.5 < player2.x && ballPosition.y >= player2.y - 1.25 && ballPosition.y <= player2.y + 1.25 = invertXVelocity ballVelocity
| ballPosition.x - 0.75 < player1.x && ballPosition.x - 0.5 > player1.x && ballPosition.y >= player1.y - 1.25 && ballPosition.y <= player1.y + 1.25 = invertXVelocity ballVelocity
| otherwise = ballVelocity
initialise
:: ( Raylib :> es
, State GameState :> es
, AE.ECS World :> es
)
=> Eff es ()
initialise = do
setTargetFPS 60
cameraEntity <- AE.newEntity @World (Camera 20 (0, 0), Position 0 0)
camera .= cameraEntity
player1Entity <- AE.newEntity @World (Player, Position (-10) 0, Box RL.white (0, -1) (0.5, 2))
player1 .= player1Entity
player2Entity <- AE.newEntity @World (Player, Position 10 0, Box RL.white (-0.5, -1) (0.5, 2))
player2 .= player2Entity
ballEntity <- AE.newEntity @World (Position 0 0, Velocity 0.1 0.1, Box RL.white (-0.25, -0.25) (0.5, 0.5))
ball .= ballEntity
pure ()
pongGame :: IO ()
pongGame = do
let gameState
= GameState
{ dimX = 600
, dimY = 500
, player1 = undefined
, player2 = undefined
, camera = undefined
, ball = undefined
}
gameConfig
= GameConfig
{ playArea = 20
}
-- RL.setTraceLogLevel RL.LogWarning
runEff . AE.runECS initWorld . evalState gameState . runReader gameConfig . runRaylibWindow gameState.dimX gameState.dimY "App" $ initialise >> whileM do
ballMovement
(gets @GameState (\s -> s.player1))
(gets @GameState (\s -> s.player2))
(gets @GameState (\s -> s.ball))
applyVelocity
playerMovement
(RL.KeyW, -0.2)
(RL.KeyS, 0.2)
(gets @GameState (\s -> s.player1))
playerMovement
(RL.KeyUp, -0.2)
(RL.KeyDown, 0.2)
(gets @GameState (\s -> s.player2))
(gets @GameState (\s -> s.ball)) >>= AE.get @World @(PositionComponent, VelocityComponent) >>= liftIO . print
dims <- gets @GameState (\s -> (s.dimX, s.dimY))
camera <- getCamera (gets @GameState (\s -> s.camera)) dims
runDraw . runDraw2D camera $ do
clearBackground RL.gray
render
renderOrigins
not <$> windowShouldClose

13
rpg/src/System/Physics.hs Normal file
View file

@ -0,0 +1,13 @@
module System.Physics (applyVelocity) where
import World
import qualified Apecs.Effectful as AE
import Effectful
applyVelocity
:: ( AE.ECS World :> es
)
=> Eff es ()
applyVelocity = do
AE.cmap @World @(PositionComponent, VelocityComponent) @_
\(position, velocity) -> Position (position.x + velocity.x) (position.y + velocity.y)

View file

@ -1,4 +1,4 @@
module System.Renderer (render) where module System.Renderer (render, renderOrigins) where
import Effectful import Effectful
import qualified Apecs.Effectful as AE import qualified Apecs.Effectful as AE
@ -6,6 +6,7 @@ import World
import Component.Position import Component.Position
import Component.Box import Component.Box
import Effectful.Raylib import Effectful.Raylib
import qualified Raylib.Util.Colors as RL
render render
:: ( AE.ECS World :> es :: ( AE.ECS World :> es
@ -15,3 +16,13 @@ render = do
AE.cmapM_ @World @(PositionComponent, BoxComponent) AE.cmapM_ @World @(PositionComponent, BoxComponent)
\(pos, Box color offset size) -> drawRectangle (pos.x + fst offset) (pos.y + snd offset) (fst size) (snd size) color \(pos, Box color offset size) -> drawRectangle (pos.x + fst offset) (pos.y + snd offset) (fst size) (snd size) color
pure () pure ()
renderOrigins
:: ( AE.ECS World :> es
, RaylibDraw2D :> es
)
=> Eff es ()
renderOrigins = do
AE.cmapM_ @World @PositionComponent
\pos -> drawLine (pos.x - 0.1) (pos.y - 0.1) (pos.x + 0.1) (pos.y + 0.1) RL.red >>
drawLine (pos.x + 0.1) (pos.y - 0.1) (pos.x - 0.1) (pos.y + 0.1) RL.red

View file

@ -1,12 +1,21 @@
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FieldSelectors #-} {-# LANGUAGE FieldSelectors #-}
module World (World, initWorld) where module World
( World
, initWorld
, module Component.Player
, module Component.Position
, module Component.Camera
, module Component.Box
, module Component.Velocity
) where
import Apecs.Effectful import Apecs.Effectful
import Component.Position import Component.Position
import Component.Player import Component.Player
import Component.Camera import Component.Camera
import Component.Box import Component.Box
import Component.Velocity
makeWorld "World" [''PositionComponent, ''PlayerComponent, ''CameraComponent, ''BoxComponent] makeWorld "World" [''PositionComponent, ''PlayerComponent, ''CameraComponent, ''BoxComponent, ''VelocityComponent]