Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: a3645caa2d
Fetching contributors…

Cannot retrieve contributors at this time

376 lines (306 sloc) 12.451 kB
{-
The MIT License
Copyright (c) 2010 Korcan Hussein.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-}
{-# LANGUAGE FlexibleContexts, ForeignFunctionInterface #-}
module Main where
import Prelude hiding (id, (.), mod, init)
import Foreign
import Foreign.C.Types
import Control.Category
import Control.Monad
import Control.Monad.State hiding (get)
import qualified Control.Monad.State as MState (get)
import Control.Monad.Reader
import Control.Monad.Trans
import Control.Exception
import Data.Maybe
import Data.Either
import Data.Record.Label
import Data.List hiding (init, intersect)
import Graphics.UI.SDL hiding (init, quit, flip, update, getTicks)
import qualified Graphics.UI.SDL as SDL (flip)
import qualified Graphics.UI.SDL.Utilities as Util
import Graphics.UI.SDL.TTF hiding (init, quit)
import qualified Graphics.UI.SDL.TTF as TTFG (init, quit)
import Graphics.UI.SDL.Mixer
import Labels
import Timer
import Vector2
import Collision
import Surface
import GameState
import GameEnv
import Consts
import Sound
import Draw
import Paths_YACPong
-- SDL_GetKeyState is not defined in Graphic.UI.SDL
foreign import ccall unsafe "SDL_GetKeyState" sdlGetKeyState :: Ptr CInt -> IO (Ptr Word8)
type KeyProc = SDLKey -> Bool
-- this function comes from mokehehe's super nario bros: http://github.com/mokehehe/monao in the file "AppUtil.hs"
getKeyState :: MonadIO m => m KeyProc
getKeyState = liftIO $ alloca $ \numkeysPtr -> do
keysPtr <- sdlGetKeyState numkeysPtr
return $ \k -> (/= 0) $ unsafePerformIO $ (peekByteOff keysPtr $ fromIntegral $ Util.fromEnum k :: IO Word8)
init :: IO (GameConfig, GameData)
init = do
screen <- setVideoMode (truncate screenWidth) (truncate screenHeight) screenBpp [HWSurface, DoubleBuf]
setCaption "YACPong" []
enableUnicode True
fontFileName <- getDataFileName "Crysta.ttf"
wallFileName <- getDataFileName "wall.wav"
paddleFileName <- getDataFileName "paddle.wav"
winFileName <- getDataFileName "win.wav"
font <- openFont fontFileName 36
wallBounce <- loadWAV wallFileName
paddleBounce <- loadWAV paddleFileName
winSound <- loadWAV winFileName
paddleSprite <- createRGBSurfaceEndian [HWSurface] paddleW' paddleH' screenBpp
ballSprite <- createRGBSurfaceEndian [HWSurface] ballW' ballH' screenBpp
paddleColor <- mapRGB' paddleSprite 0xFF 0xFF 0x00
ballColor <- mapRGB' ballSprite 0x3F 0xFF 0xF5
fillRect paddleSprite (Just $ Rect 0 0 paddleW' paddleH') paddleColor
fillRect ballSprite (Just $ Rect 0 0 ballW' ballH') ballColor
paused <- renderTextSolid font "PAUSED" textColor
currTicks <- start timer
return (GameConfig font screen ballSprite paddleSprite paused wallBounce paddleBounce winSound, gameData pPos1 pPos2 bPos currTicks)
where paddleW' = truncate paddleW
paddleH' = truncate paddleH
ballW' = truncate ballW
ballH' = truncate ballH
--from (x,y) = (fromIntegral x, fromIntegral y)
pPos1 = (10, halfHeight - paddleH / 2)
pPos2 = (screenWidth - 10 - paddleW, halfHeight - paddleH / 2)
bPos = (halfWidth, halfHeight)
--whichSide :: Bounds -> Bounds -> Vector2f
--Bounds (lx,ly,lw,lh) `whichSide` Bounds (rx,ry,rw,rh) = flip execState (0,0) $ do
-- when ((lx + lw) <= rx) $ do
-- modify $ \(_,ny) -> (-1,ny)
--
-- when (lx >= (rx + rw)) $ do
-- modify $ \(_,ny) -> (1,ny)
--
-- when ((ly + lh) <= ry) $ do
-- modify $ \(nx,_) -> (nx,-1)
--
-- when (ly >= (ry + rh)) $ do
-- modify $ \(nx,_) -> (nx,1)
collide :: Float -> Paddle -> Ball -> Maybe (Float,Float)
collide dt Paddle { _pPos=(px,py), _yVel=yVel } b@Ball { _pos=(x,y), _vel=v } =
intersectMoving (Bounds (px,py,pW,pH)) (Bounds (x,y,bW,bH)) (0, dt * yVel) (dt `mul` v)
where bW = ballW
bH = ballH
pW = paddleW
pH = paddleH
data CollisionType =
CtWall (Either Paddle Ball)
| CtPaddle Paddle
| CtLeftNet
| CtRightNet
| CtNone
type CollisionEvent = (CollisionType, Float)
paddleInside :: Float -> Paddle -> Bool
paddleInside dt paddle = not $ y' < 0 || y' + paddleH > screenHeight
where (_,y) = get pPos paddle
vel = get yVel paddle
y' = vel * dt + y
findBallCollision :: Float -> [Paddle] -> Ball -> CollisionEvent
findBallCollision dt paddles b@Ball { _pos=bp@(x,y), _vel=v@(dx,dy) }
| x' > 0 && x' < screenWidth && y' > 0 && y' < screenHeight = --b { _pos=(x',y') }
let intersecting (_, Nothing) = False
intersecting (_, Just (0,_)) = False
intersecting _ = True
collided = find intersecting $ map (\p -> (p, collide dt p b)) paddles
in case collided of
Just (p, Just (t1,_)) -> (CtPaddle p, t1)
_ -> (CtNone, 0)
| x' < 0 = (CtLeftNet, dt)
| x' > screenWidth = (CtRightNet, dt)
| y' < 0 = (CtWall $ Right b, dt)
| y' > screenHeight = (CtWall $ Right b, dt)
| otherwise = (CtNone, 0)
where x' = dx * dt + x
y' = dy * dt + y
findCollisions :: Float -> GameEnv [CollisionEvent]
findCollisions dt = do
p1 <- getM paddle1
p2 <- getM paddle2
let paddles = filter (not . paddleInside dt) [p1,p2]
let wallPaddles = map (\p -> (CtWall $ Left p,dt)) paddles
result <- findBallCollision dt [p1,p2] `liftM` getM ball
case result of
(CtNone,_) -> return wallPaddles
_ -> return $ result : wallPaddles
think :: Ball -> Vector2f -> Paddle -> Paddle
think (Ball (bx,by) bv) pv p@Paddle { _pPos=(x,y), _yVel=pVel, _lastPos=oldY }
| ang < 0 =
let newVel = if by < y then -paddleVel
else if by > y + paddleH
then paddleVel
else 0
p' = saveLastPos newVel p -- capture the first y-pos of the paddle.
in p' { _yVel=newVel }
| otherwise = set yVel 0 p
where ang = bv `dot` pv
react :: CollisionEvent -> GameEnv ()
react (CtWall (Left p), _) = do
p1 <- getM paddle1
let playerL = if p1 == p then paddle1 else paddle2
modM playerL $ \p@Paddle { _pPos=(x,y) } ->
p { _pPos=(min w $ max x 0, min h $ max y 0) }
where w = screenWidth
h = screenHeight
react (CtWall (Right b@Ball { _pos=(x,y), _vel=(dx,dy) }), dt) = do
setM ball b { _pos=pos', _vel=reflect (dx,dy) wallNormal }
playWallSound
where x' = x + dt * dx
y' = y + dt * dy
(pos', wallNormal) = execState collideWall ((x',y'),(0,0))
collideWall :: State (Vector2f,Vector2f) ()
collideWall = do
--modify $ \k@((u,v),(_,ny)) -> if u < 0 then ((0,v),(1,ny)) else k
--modify $ \k@((u,v),(_,ny)) -> if u > fromIntegral screenWidth then ((fromIntegral $ screenWidth - 1, v),(-1,ny)) else k
modify $ \k@((u,v),(nx,_)) -> if v < 0 then ((u, 0),(nx,1)) else k
modify $ \k@((u,v),(nx,_)) -> if v > screenHeight then ((u, screenHeight - 1),(nx,-1)) else k
react (CtPaddle p, t) = do
modM ball $ \b@Ball { _pos=bp, _vel=v } ->
let v' = t `mul` normalize v
p' = bp `add` v'
--n = whichSide (i,j,bW,bH) (px,py + dt * yVel,pW,pH)
--in b { _pos=p', _vel=(0, paddleVel) `add` reflect v (1,0) }
in b { _pos=p', _vel=paddleInflunceVec `add` reflect v (1,0) }
playPaddleSound
where currY = snd $ get pPos p
paddleVel = get yVel p
lastY = get lastPos p
paddleInflunceVec = if lastY == 0 then (0,0) else (0, currY - lastY)
react (CtLeftNet,_) = do
state =: Win Player1
modM (player2Count . stats) (+1)
playWinSound
react (CtRightNet, _) = do
state =: Win Player2
modM (player1Count . stats) (+1)
playWinSound
react (CtNone, _) = return ()
updatePaddle :: Float -> Paddle -> Paddle
updatePaddle dt paddle = set pPos (x,y') paddle
where (x,y) = get pPos paddle
vel = get yVel paddle
y' = vel * dt + y
updateBall :: Float -> Ball -> Ball
updateBall dt b@Ball { _pos=bp@(x,y), _vel=v@(dx,dy) } = b { _pos=(x',y') }
where x' = dx * dt + x
y' = dy * dt + y
saveLastPos :: Float -> Paddle -> Paddle
saveLastPos nextVel p
| currVel == 0 && nextVel /= 0 = p { _lastPos = snd $ get pPos p }
| currVel /= 0 && nextVel == 0 = p { _lastPos = 0 }
| otherwise = p
where currVel = get yVel p
update :: Float -> GameEnv ()
update dt = do
keyState <- getKeyState
let pVel = if keyState SDLK_w
then -paddleVel
else if (keyState SDLK_s)
then paddleVel
else 0.0
-- capture the first y-pos of the player's paddle.
modM paddle1 $ saveLastPos pVel
setM (yVel . paddle1) pVel
b <- getM ball
modM paddle2 $ think b (-1,0)
collisions <- findCollisions dt
forM_ collisions react
when (all ballNotHit collisions) $
modM ball $ updateBall dt
p1 <- getM paddle1
when (all (paddleNotHit p1) collisions) $
modM paddle1 $ updatePaddle dt
p2 <- getM paddle2
when (all (paddleNotHit p2) collisions) $
modM paddle2 $ updatePaddle dt
where ballNotHit :: CollisionEvent -> Bool
ballNotHit (CtWall (Left _), _) = True
ballNotHit (CtNone, _) = True
ballNotHit _ = False
paddleNotHit :: Paddle -> CollisionEvent -> Bool
paddleNotHit p (CtWall (Left c), _) = p /= c
paddleNotHit _ (CtWall (Right _), _) = True
paddleNotHit _ (CtNone, _) = True
paddleNotHit _ _ = False
nextState_ :: GameLoopState -> GameEnv ()
nextState_ w@(Win player) = do
renderWin
isWinPlay <- isPlayingWin
stopTicks
let result = if isWinPlay then w else Init player
state =: result
nextState_ (Init player) = do
setM ball =<< newBallM player (halfWidth, halfHeight)
state =: Play
nextState_ Play = do
dt <- getTicksM
update $ secs * fromIntegral dt
resetTicks
render
nextState_ Paused = do
renderPaused
stopTicks
nextState :: GameEnv ()
nextState = getM state >>= nextState_
loop :: GameEnv ()
loop = do
quit <- whileEvents $ \event -> do
gs <- getM state
case (event, gs) of
(KeyDown (Keysym SDLK_p _ _), Play) -> setM state Paused
(KeyDown (Keysym SDLK_p _ _), Paused) -> setM state Play
_ -> return ()
nextState
-- TODO: use high res performance timer.
liftIO $ delay 1
unless quit loop
whileEvents :: MonadIO m => (Event -> m ()) -> m Bool
whileEvents act = do
event <- liftIO pollEvent
case event of
Quit -> return True
KeyDown (Keysym SDLK_ESCAPE _ _) -> return True
NoEvent -> return False
_ -> do
act event
whileEvents act
main :: IO ()
main = withSDL $ do
(config, state) <- init
runGame config state loop
return ()
withSDL :: IO () -> IO ()
withSDL act = withInit [InitEverything] $ do -- withInit calls quit for us.
bracket_ sdlInit sdlQuit act
where sdlInit :: IO ()
sdlInit = do
success <- TTFG.init
when (not success) $
throwIO $ userError "Failed to init ttf\n"
openAudio 44100 AudioS16Sys 2 4096
sdlQuit :: IO ()
sdlQuit = closeAudio >> TTFG.quit
Jump to Line
Something went wrong with that request. Please try again.