module Simulation ( simulationStep ) where import Collision import Game import Level import MainLoop import Player import Tank import Control.Monad.State import Data.Fixed import Data.List import Data.Maybe import Data.Ratio updateAngle :: Micro -> State Tank () updateAngle angle = do oldangle <- gets tankDir tspeed <- liftM (/100) $ gets tankTurnspeed let diff = angle - oldangle let diff360 = if (diff > 180) then (diff-360) else if (diff <= -180) then (diff+360) else diff let (diff180, angle180) = if (diff360 > 90) then (diff360-180, oldangle+180) else if (diff360 <= -90) then (diff360+180, oldangle-180) else (diff360, oldangle) let turn = if (diff180 > tspeed) then tspeed else if (diff180 < -tspeed) then (-tspeed) else diff180 let newangle = angle180 + turn let newangle180 = if (newangle > 180) then (newangle-360) else if (newangle <= -180) then (newangle+360) else newangle modify $ \tank -> tank {tankDir = newangle180} updateTank :: GameState -> Maybe Micro -> Bool -> Maybe Micro -> State Tank () updateTank game angle move aangle = do when (isJust angle) $ updateAngle $ fromJust angle when (isJust aangle) $ modify $ \tank -> tank {tankAim = fromJust aangle} when move $ do tank <- get let tdir = tankDir tank tspeed = tankSpeed tank moved = tankMoving tank when (isNothing angle || (isJust angle && (tdir == fromJust angle)) || moved) $ do let anglej = (fromRational . toRational $ tdir)*pi/180 dx = tspeed * fromRational (round ((cos anglej)*1000)%100000) dy = tspeed * fromRational (round ((sin anglej)*1000)%100000) put tank {tankX = dx + tankX tank, tankY = dy + tankY tank, tankMoving = True} when (not move) $ do modify $ \tank -> tank {tankMoving = False} let lw = fromIntegral . levelWidth . level $ game lh = fromIntegral . levelHeight . level $ game modify $ collisionTankBorder lw lh updateBullet :: GameState -> State Bullet Bool updateBullet game = do bullet <- get let angle = (fromRational . toRational . bulletDir $ bullet)*pi/180 speed = bulletSpeed bullet dx = speed * fromRational (round ((cos angle)*1000)%100000) dy = speed * fromRational (round ((sin angle)*1000)%100000) x = dx + bulletX bullet y = dy + bulletY bullet lw = fromIntegral . levelWidth . level $ game lh = fromIntegral . levelHeight . level $ game dir = bulletDir bullet bounces = bulletBouncesLeft bullet (newx, dir2, bounces2) = if x < 0 then (-x, (signum dir)*180 - dir, bounces-1) else if x > lw then (2*lw-x, (signum dir)*180 - dir, bounces-1) else (x, dir, bounces) (newy, dir3, bounces3) = if y < 0 then (-y, -dir2, bounces2-1) else if y > lh then (2*lh-y, -dir2, bounces2-1) else (y, dir2, bounces2) put bullet {bulletX = newx, bulletY = newy, bulletDir = dir3, bulletBouncesLeft = bounces3} return (bounces3 >= 0) simulationStep :: Main () simulationStep = do oldplayers <- gets players game <- lift get let oldtanks = tanks game let (p, t, s) = unzip3 $ map (updateTank' game) $ zip oldplayers oldtanks ts = zip3 t s [0..] shootingtanks = map (\(tank, _, n) -> (tank, n)) $ filter (\(tank, shoot, _) -> shoot && (tankBulletsLeft tank) > 0) $ ts newtanks = map (\(tank, shoot, _) -> if (shoot && (tankBulletsLeft tank) > 0) then tank {tankBulletsLeft = (tankBulletsLeft tank) - 1} else tank) $ ts newbullets = map (\(tank, n) -> Bullet { bulletX = tankX tank , bulletY = tankY tank , bulletDir = tankAim tank , bulletSpeed = tankBulletSpeed tank , bulletBouncesLeft = tankBulletBounces tank , bulletTank = n }) shootingtanks modify $ \state -> state {players = p} lift $ modify $ \state -> let thebullets = map (runState $ updateBullet state) $ bullets state leftbullets = collideBullets $ zipWith (\(left, bullet') bullet -> (left, bullet, bullet')) thebullets $ bullets state bt = hitBullets $ liftM2 (\(b, (_, b')) (t, t') -> (b, b', t, t')) (zip (bullets state) leftbullets) (zip (tanks state) newtanks) leftbullets2 = map (\(left, bullet) -> (left && (all (\(c, b, _) -> (b /= bullet) || (not c)) bt), bullet)) leftbullets thetanks = map (\(tank, n) -> tank {tankBulletsLeft = (tankBulletsLeft tank) + (countLostTankBullets n leftbullets2)}) $ zip newtanks [0..] in state {tanks = thetanks, bullets = newbullets ++ (map snd . filter fst $ leftbullets2)} where collideBullets [] = [] collideBullets ((left, bullet, bullet'):bs) = let (c, ls) = collideBullet bullet bullet' bs in (left && not c, bullet'):(collideBullets ls) collideBullet bullet bullet' bs = let cs = map (\(left, b, b') -> (left, collisionBulletBullet (bullet, bullet') (b, b'), b, b')) bs collided = any (\(_,c,_,_) -> c) cs left = map (\(left, c, b, b') -> (left && not c, b, b')) $ cs in (collided, left) hitBullets :: [(Bullet, Bullet, Tank, Tank)] -> [(Bool, Bullet, Tank)] hitBullets [] = [] hitBullets ((b, b', t, t'):xs) = (collisionBulletTank (b, b') (t, t'), b', t'):(hitBullets xs) updateTank' game (player, tank) = let (p, angle, move, aangle, shoot) = playerUpdate player tank t = execState (updateTank game angle move aangle) tank in (p, t, shoot) countLostTankBullets n (x:xs) = (if ((not . fst $ x) && (n == (bulletTank . snd $ x))) then 1 else 0) + (countLostTankBullets n xs) countLostTankBullets n [] = 0