module Game where import qualified Data.Map as M import qualified Point as P gameWidth, gameHeight :: Int gameWidth = 800 gameHeight = 600 data Status = Running | Won | Lost deriving (Eq) data Item = Item { _siz :: P.Point , _pos :: P.Point , _vel :: P.Point } data Game = Game { _status :: Status , _inputLeft :: Bool , _inputRight :: Bool , _inputFire :: Bool , _rands :: [Double] , _firetime :: Double , _paddle :: Item , _bullets :: [Item] , _invaders :: [Item] } createGame :: [Double] -> Game createGame rands0 = Game Running False False False rands1 0 myPaddle [] myInvaders where myPaddle = Item (70, 20) (0, -250) (0, 0) ([mag, dir], rands1) = splitAt 2 rands0 vx = (150 + 200 * mag) * (if dir < 0.5 then 1 else -1) myInvaders = [ Item (70, 20) (fromIntegral x * 100, fromIntegral y * 50 + 150) (vx, 0) | x<-[-2..(2::Int)], y<-[0..(2::Int)] ] updatePaddle :: Double -> Game -> Game updatePaddle time g = firePaddleBullet time g1 where dx = time * 200 dl = if _inputLeft g then -dx else 0 dr = if _inputRight g then dx else 0 p0 = _paddle g (x0, y) = _pos p0 x1 = max (-400) $ min 400 $ dl + dr + x0 p1 = p0 { _pos = (x1, y) } g1 = g { _paddle = p1 } firePaddleBullet :: Double -> Game -> Game firePaddleBullet time g = if canFire then mFire else mNofire where canFire = _inputFire g && _firetime g > 0.9 (x, y) = _pos $ _paddle g bullet = Item (3, 9) (x, y+20) (0, 200) mFire = g { _bullets = bullet : _bullets g, _firetime = 0 } mNofire = g { _firetime = time + _firetime g } updateInvaders :: Double -> Game -> Game updateInvaders time g = if null myInvaders then g else g3 where myInvaders = _invaders g i1 = map (autoUpdateItem time) myInvaders xs = map (fst . _pos) myInvaders x1min = minimum xs x1max = maximum xs v@(vx, _) = _vel $ head myInvaders move v0 v1 i = i { _pos = _pos i P.+ time P.* v0, _vel = v1 } i2 | vx>0 && x1max>380 = map (move (380-x1max, 0) (P.negate v)) i1 | vx<0 && x1min<(-380) = map (move (-380-x1min, 0) (P.negate v)) i1 | otherwise = i1 g2 = g { _invaders = i2 } g3 = fireInvadersBullets g2 fireInvadersBullets :: Game -> Game fireInvadersBullets g = g { _bullets = _bullets g ++ bs, _rands = rands3 } where invadersPos = map _pos $ _invaders g fInsert pMap (x,y) = M.insertWith min x y pMap fighters0 = M.toList $ foldl fInsert M.empty invadersPos (rands0, rands1) = splitAt (length fighters0) (_rands g) (rands2, rands3) = splitAt (length fighters0) rands1 difficulty = 0.9 + fromIntegral (length invadersPos) * (0.99 - 0.9) / 15 fighters1 = [ (p, v) | (p, r, v) <- zip3 fighters0 rands0 rands2, r > difficulty ] createBullet ((x, y), v) = Item (3, 9) (x, y-20) (0, -(300-v*200)) bs = map createBullet fighters1 autoUpdateItem :: Double -> Item -> Item autoUpdateItem t i@(Item _ pos vel) = i { _pos = pos P.+ t P.* vel } updateBullets :: Double -> Game -> Game updateBullets time g = g { _bullets = b2 } where b1 = map (autoUpdateItem time) (_bullets g) b2 = filter (\b -> snd (_pos b) < 300 && snd (_pos b) > -300) b1 updateCollisions :: Game -> Game updateCollisions g = g1 { _invaders = i1, _bullets = b2, _status = st } where (b1, i1) = runCollisions (_bullets g) (_invaders g) (b2, p2) = runCollisions b1 [_paddle g] st | null i1 = Won | null p2 = Lost | otherwise = Running g1 = if st == Running then g else g { _inputLeft = False, _inputRight = False, _inputFire = False } testCollision :: Item -> Item -> Bool testCollision (Item as ap _) (Item bs bp _) = ((bx0 < ax0 && ax0 < bx1) || (bx0 < ax1 && ax1 < bx1)) && ((by0 < ay0 && ay0 < by1) || (by0 < ay1 && ay1 < by1)) where (ax0, ay0) = ap P.- 0.5 P.* as (ax1, ay1) = ap P.+ 0.5 P.* as (bx0, by0) = bp P.- 0.5 P.* bs (bx1, by1) = bp P.+ 0.5 P.* bs runCollisions :: [Item] -> [Item] -> ([Item], [Item]) runCollisions [] is = ([], is) runCollisions (b:bs) is = (bs1++bs2, is2) where is1 = filter (not . testCollision b) is bs1 = [b | length is1 == length is] (bs2, is2) = runCollisions bs is1 step :: Double -> Game -> Game step time g = if _status g == Running then mRunning else mEnd where mRunning = updatePaddle time $ updateInvaders time $ updateBullets time $ updateCollisions g mEnd = if _inputFire g then createGame (_rands g) else g