Pong spel behöver hjälp med logik [Haskell]
Lär mej lite Haskell, så tänkte jag göra ett simpelt Pong spel i spelmotorns "Helm" som ska göra denna upgift lätt.
Det gick rätt bra tills jag fixat alla typ-error och det kompilerade, då insåg jag att min logik var fel.
Spelet ska starta när du klickar på space, sedan så ska det fungera som ett vanligt pong-spel. När bollen åker utanför plan så ska den den personen på den sidan få poäng.
Problem: Koden kompilerar o allt, men när spelet startas och man klickar på space så åker bollen direkt till höger och fastnar där, paddlarna laggar dessutom upp och ner.
Koden under "Update" kommentaren är den jag behöver hjälp med, men förbättringar och förslag på resten av koden är uppskattat.
{-# LANGUAGE TemplateHaskell #-}
module Pong where
import Control.Lens
import FRP.Elerea.Simple hiding (delay)
import FRP.Helm
import FRP.Helm.Time
import qualified FRP.Helm.Keyboard as Keyboard
import qualified FRP.Helm.Window as Window
import qualified FRP.Helm.Text as Text
-- Model
data Input = Input Bool Int Int deriving Show
input :: SignalGen (Signal (Time, Input))
input = lift2 (,) delta' keys
where
delta' = delay $ fps 60
keys = lift3 Input Keyboard.space
(lift (flip (^.) _2) Keyboard.arrows)
(lift (flip (^.) _2) Keyboard.wasd)
data Vector =
Vector {
_x :: Double,
_y :: Double } deriving Show
data Ball =
Ball {
_pos :: Vector,
_vel :: Vector } deriving Show
data Score =
Score {
_left :: Int,
_right :: Int } deriving Show
data MatchState = Play | Pause deriving (Show, Eq)
data State =
State {
_padL :: Double,
_padR :: Double,
_ball :: Ball,
_score :: Score,
_state :: MatchState } deriving Show
makeLenses ''Vector
makeLenses ''Ball
makeLenses ''Score
makeLenses ''State
-- Update
clamp :: Ord a => a -> a -> a -> a
clamp x y z
| z < x = x
| x <= z && z < y = z
| otherwise = y
stepPad :: Time -> Int -> Double -> Double
stepPad t i p = clamp 20 380 $ p - fromIntegral i * 200 * t
collision :: State -> Bool
collision s = bX >= 20 && bX <= 30 && bY >= (pL - 40) || bY <= pL || bY >= (pR - 40) || bY <= pR
where
bX = s^.ball.pos.x
bY = s^.ball.pos.y
pL = s^.padL
pR = s^.padR
stepVelocity :: State -> Vector
stepVelocity s
| collision s = Vector {
_x = opposite $ s^.ball.vel.x,
_y = opposite $ s^.ball.vel.y }
| otherwise = s^.ball.vel
where
opposite n = if n < 0 then abs n else negate n
stepBall :: Time -> State -> State
stepBall t s = let s' = ball.vel .~ stepVel $ s in ball.pos .~ stepPos $ s'
where
stepVel = stepVelocity s
stepPos = Vector {
_x = (s^.ball.pos.x) + (s^.ball.vel.x) * t,
_y = (s^.ball.pos.y) + (s^.ball.vel.y) * t }
ballReset :: Ball
ballReset = Ball {
_pos = Vector {
_x = 300,
_y = 200 },
_vel = Vector {
_x = 2,
_y = 2 } }
stepGame :: Time -> (Int, Int) -> State -> State
stepGame t (l, r) s = let s1 = stepBall t s; s2 = padL .~ pL $ s1; s3 = padR .~ pR $ s2 in
case s3^.ball.pos.x >= 600 of
True -> let s4 = score.right +~ 1 $ s3; s5 = ball .~ ballReset $ s4 in
state .~ Pause $ s5
False -> case s3^.ball.pos.x <= 0 of
True -> let s4 = score.left +~ 1 $ s3; s5 = ball .~ ballReset $ s4 in
state .~ Pause $ s5
False -> s3
where
pL = stepPad t l $ s^.padL
pR = stepPad t r $ s^.padR
step :: (Time, Input) -> State -> State
step (t, (Input sp l r)) s
| s^.state == Play = stepGame t (l, r) s
| s^.state == Pause && sp = let s' = ball .~ ballReset $ s in stepGame t (l, r) s'
| otherwise = s
-- View
render :: (Int, Int) -> State -> Element
render (w, h) s =
collage w h [move (half w, half h) bg,
move (20, s^.padL) pad,
move (fromIntegral w - 20, s^.padR) pad,
move (s^.ball.pos.x, s^.ball.pos.y) ball',
move (half w, 40) txt]
where
half = (/ 2) . fromIntegral
bg = filled teal $ rect (fromIntegral w) (fromIntegral h)
pad = filled white $ rect 10 40
ball' = filled white $ oval 15 15
txt = toForm $ Text.text $ Text.defaultText {
textUTF8 = (show $ s^.score.left) ++ " " ++ (show $ s^.score.right),
textColor = cyan,
textTypeface = "monospace",
textHeight = 32 }
-- Main
main = run config $ lift2 render Window.dimensions $ foldp step gameInit input
config :: EngineConfig
config = defaultConfig {
windowDimensions = (600,400),
windowIsResizable = False,
windowTitle = "Pong" }
gameInit :: State
gameInit = State {
_padL = 200,
_padR = 200,
_ball = Ball {
_pos = Vector {
_x = 300,
_y = 200 },
_vel = Vector {
_x = 0,
_y = 0 } },
_score = Score {
_left = 0,
_right = 0 },
_state = Pause }
Tack i förväg.
Nybörjar guide: Xonotic 1on1 | FX-4100 Black Edition X4 @ 3,6GHz, MSI GTX650 1GB OC, Crossair VENGEANCE 8GB @ 1600 MHz, och lite annat skrot ;)