<commit_before>module Engine where

import System.Random
import Control.Monad.Random
import Control.Monad.Random.Class
import Control.Monad.Loops
import Data.List
import Data.Label.Pure
import Debug.Trace

-- Vectors
--import Data.Vector.Fancy 
import Data.Vector.V2 
import Data.Vector.Transform.T2 
import Data.Angle 
import Data.Vector.Transform.Fancy 
import Data.Vector.Class

import Control.Applicative

import Core
import GeometryUtils
import Data.Label.Pure
import WorldRules
import TupleUtils

-- TODO randomR
-- | Random instances used for generating bot starting positions
instance Random Point where
  random g = runRand build g
    where build = Vector2 <$> getRandom <*> getRandom

instance Random (Double, Double) where
  random g = runRand build g
    where build = do
            v1 <- getRandom
            v2 <- getRandom
            return (v1,v2)
            
instance Random BotState where
  random g = runRand build g
    where build = do
            position <- getRandom
            let zero = Vector2 0 0
            return $ BotState position zero zero zero NoAction

-- |Generates non overlapping bot states, we don't want to start with collisions
instance Random [BotState] where
  random g =  (nubBy botBotCollision states, head gs)
    where 
      (states, gs) = unzip $ unfoldr f g 
      f g = Just ((next, gNext), gNext)
        where (next, gNext) = random g
              
-- | Ensure that the bot can't turn by more then permitted by the rules.
sanitizeCommand :: Command -> Command
sanitizeCommand = id

-- | Create a new BotState given a command issued by the bot
stepBotState :: Command -> BotState -> BotState
stepBotState NoAction = id
stepBotState (Accelerate delta)   = modify botVelocity $ vmap (+delta) 
stepBotState (Decelerate delta)   = modify botVelocity $ vmap (+ (-delta))
stepBotState (Turn       degrees) = modify botVelocity $ rotate degrees   
stepBotState (MoveTurret degrees) = modify botTurret   $ rotate degrees     
stepBotState (MoveRadar  degrees) = modify botRadar    $ rotate degrees 

bulletsFired :: [(Step, BotState)] -> [Bullet]
bulletsFired bots = map (fire . snd) $ filter hasFired bots
  where hasFired (step, _) = stepCmd step == Fire

fire :: BotState -> Bullet
fire state = Bullet position velocity 
  where position = get botPosition state
        velocity = vnormalise (get botTurret state) |* (fromInteger bulletSpeed)

-- TODO
botBotCollision :: BotState -> BotState -> Bool
botBotCollision bot1 bot2 = False

-- TODO
botWallCollision :: BotState -> Bool
botWallCollision state = False

-- TODO scan results and collision results
newDashBoard :: [BotState] -> BotState -> DashBoard
newDashBoard otherBots bot = DashBoard NothingFound NoCollision (get botVelocity bot) 

-- TODO Hit test bots - for now just kill off one bot per tick
pruneDeadBots :: World -> World
pruneDeadBots =  modify worldBots tail 

stepWorld :: World -> World
stepWorld (World bots bullets bbox) = World (zip newSteps newStates) newBullets bbox
  where         
    steps      = map mkSteps bots
    newBullets = map stepBullet $ bullets ++ bulletsFired steps
    commands   = map (stepCmd . fst) steps
    newStates  = map (uncurry stepBotState ) $ zip commands $ map snd bots
    newSteps   = map (stepNext . fst) steps
    mkSteps bot@(automaton, state) = (step dashboard automaton, state)
      where dashboard = newDashBoard otherBots state
            otherBots = filter (== state) . map snd $ bots
            
stepBullet :: Bullet -> Bullet                  
stepBullet bullet = modify bulletPosition (+ get bulletVelocity bullet) bullet

matchIsOver :: World -> Bool  
matchIsOver (World bots _  _) = length bots < 2
  
newWorld :: RandomGen g => g -> [Bot a] -> World
newWorld gen bots = World (zip automata states) [] arenaBBox
  where states   = take (length bots) . fst $ random gen
        automata = map start bots                       
        
  
  
  
  <commit_msg>Added randomR to Random instances and made the BotState return positions bounded by the arena size.<commit_after>module Engine where

import System.Random
import Control.Monad.Random
import Control.Monad.Random.Class
import Control.Monad.Loops
import Data.List
import Data.Label.Pure
import Debug.Trace

-- Vectors
--import Data.Vector.Fancy 
import Data.Vector.V2 
import Data.Vector.Transform.T2 
import Data.Angle 
import Data.Vector.Transform.Fancy 
import Data.Vector.Class

import Control.Applicative

import Core
import GeometryUtils
import Data.Label.Pure
import WorldRules
import TupleUtils

-- TODO randomR
-- | Random instances used for generating bot starting positions


instance Random Point where
  random               = runRand $ Vector2 <$> getRandom <*> getRandom
  randomR (from, to)   = runRand $ Vector2 <$> (getRandomR ((v2x from), (v2x to)))
                                           <*> (getRandomR ((v2y from), (v2y to)))

instance Random (Double, Double) where
  random = runRand $ do v1 <- getRandom
                        v2 <- getRandom
                        return (v1,v2)
  
  randomR (from, to) = runRand $ do v1 <- getRandomR (fst from, fst to)
                                    v2 <- getRandomR (snd from, snd to)
                                    return (v1,v2)
  
instance Random BotState where
  random = runRand $ do 
    position <- getRandomR (Vector2 0 0, Vector2 arenaWidth arenaHeight)
    let zero = Vector2 0 0
      in return $ BotState position zero zero zero NoAction
                          
  randomR (from, to)   = runRand $ let zero  = Vector2 0 0
                                       fromP = get botPosition from :: Point
                                       toP   = get botPosition to
                                   in do 
                                     position <- getRandomR (fromP, toP)
                                     return $ BotState position zero zero zero NoAction
                                   
-- |Generates non overlapping bot states, we don't want to start with collisions
instance Random [BotState] where
  random g =  (nubBy botBotCollision states, head gs)
    where 
      (states, gs) = unzip $ unfoldr f g 
      f g = Just ((next, gNext), gNext)
        where (next, gNext) = random g
              
-- | Ensure that the bot can't turn by more then permitted by the rules.
sanitizeCommand :: Command -> Command
sanitizeCommand = id

-- | Create a new BotState given a command issued by the bot
stepBotState :: Command -> BotState -> BotState
stepBotState NoAction = id
stepBotState (Accelerate delta)   = modify botVelocity $ vmap (+delta) 
stepBotState (Decelerate delta)   = modify botVelocity $ vmap (+ (-delta))
stepBotState (Turn       degrees) = modify botVelocity $ rotate degrees   
stepBotState (MoveTurret degrees) = modify botTurret   $ rotate degrees     
stepBotState (MoveRadar  degrees) = modify botRadar    $ rotate degrees 

bulletsFired :: [(Step, BotState)] -> [Bullet]
bulletsFired bots = map (fire . snd) $ filter hasFired bots
  where hasFired (step, _) = stepCmd step == Fire

fire :: BotState -> Bullet
fire state = Bullet position velocity 
  where position = get botPosition state
        velocity = vnormalise (get botTurret state) |* (fromInteger bulletSpeed)

-- TODO
botBotCollision :: BotState -> BotState -> Bool
botBotCollision bot1 bot2 = False

-- TODO
botWallCollision :: BotState -> Bool
botWallCollision state = False

-- TODO scan results and collision results
newDashBoard :: [BotState] -> BotState -> DashBoard
newDashBoard otherBots bot = DashBoard NothingFound NoCollision (get botVelocity bot) 

-- TODO Hit test bots - for now just kill off one bot per tick
pruneDeadBots :: World -> World
pruneDeadBots =  modify worldBots tail 

stepWorld :: World -> World
stepWorld (World bots bullets bbox) = World (zip newSteps newStates) newBullets bbox
  where         
    steps      = map mkSteps bots
    newBullets = map stepBullet $ bullets ++ bulletsFired steps
    commands   = map (stepCmd . fst) steps
    newStates  = map (uncurry stepBotState ) $ zip commands $ map snd bots
    newSteps   = map (stepNext . fst) steps
    mkSteps bot@(automaton, state) = (step dashboard automaton, state)
      where dashboard = newDashBoard otherBots state
            otherBots = filter (== state) . map snd $ bots
            
stepBullet :: Bullet -> Bullet                  
stepBullet bullet = modify bulletPosition (+ get bulletVelocity bullet) bullet

matchIsOver :: World -> Bool  
matchIsOver (World bots _  _) = length bots < 2
  
newWorld :: RandomGen g => g -> [Bot a] -> World
newWorld gen bots = World (zip automata states) [] arenaBBox
  where states   = take (length bots) . fst $ random gen
        automata = map start bots                       
        zero     = Vector2 0 0

  
  
  
  
{'fraction_non_alphanumeric': 0.07465574143621259, 'fraction_numerical': 0.005121201775349949, 'mean_word_length': 2.601639344262295, 'pattern_counts': {'":': 0, '<': 20, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1721168', 'n_tokens_mistral': 2544, 'n_tokens_neox': 2445, 'n_words': 926}