summaryrefslogtreecommitdiffstats
path: root/src/Simulation.hs
blob: 7b2505f3153c6d9a9cb2100343fc7aa2ae667322 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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
      
      sg = if dir < 0 then -1 else 1
      (newx, dir2, bounces2) = if x < 0 then (-x, sg*180 - dir, bounces-1) else if x > lw then (2*lw-x, sg*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
  
  (p, t, s) <- liftIO $ liftM unzip3 $ mapM (updateTank' game) $ zip oldplayers oldtanks
  let 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
          newtanks2 = map (\tank -> tank {tankLife = (tankLife tank) - (sum . map (\(c, _, t) -> if (t == tank && c) then 1 else 0) $ bt)}) newtanks
          
          thetanks = map (\(tank, n) -> tank {tankBulletsLeft = (tankBulletsLeft tank) + (countLostTankBullets n leftbullets2)}) $ zip newtanks2 [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) = do
                   (p, angle, move, aangle, shoot) <- playerUpdate player tank
                   let t = execState (updateTank game angle move aangle) tank
                   return $ if (tankLife tank > 0) then (p, t, shoot) else (player, tank, False)
      countLostTankBullets n (x:xs) = (if ((not . fst $ x) && (n == (bulletTank . snd $ x))) then 1 else 0) + (countLostTankBullets n xs)
      countLostTankBullets n []     = 0