151 lines
6.4 KiB
Haskell
151 lines
6.4 KiB
Haskell
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
|