module SoccerFun.MatchControl where
import Prelude.Unicode
import SoccerFun.Prelude
import SoccerFun.Geometry
import System.Random
import SoccerFun.Types
import SoccerFun.Referee
import SoccerFun.Random
import SoccerFun.Team
import SoccerFun.Ball
import SoccerFun.Player
import Control.Monad.Identity
import Control.Monad.State (State, runState)
import Data.Maybe
import Data.List
import SoccerFun.Field
data Match = Match
{team1 ∷ Team,
team2 ∷ Team,
theBall ∷ BallState,
theField ∷ Field,
theReferee ∷ Referee,
playingHalf ∷ Half,
playingTime ∷ PlayingTime,
score ∷ Score,
seed ∷ StdGen,
unittime ∷ TimeUnit
} deriving Show
type Score = (NrOfGoals,NrOfGoals)
type NrOfGoals = Int
lookupPlayer ∷ PlayerID → Match → Maybe Player
lookupPlayer pid Match {team1 = t1, team2 = t2} = find ((==) pid ∘ playerID) (t1 ⧺ t2)
setMatchStart ∷ Team → Team → Field → Referee → PlayingTime → StdGen → Match
setMatchStart fstTeam sndTeam field referee time rs
= Match { team1 = validateTeam fstTeam
, team2 = validateTeam sndTeam
, theBall = Free (ballAtCenter field)
, theField = field
, theReferee = referee
, playingHalf = FirstHalf
, playingTime = time
, unittime = 0.05
, score = (0,0)
, seed = rs
}
type Step = (([RefereeAction],[PlayerWithAction]),Match)
stepMatch ∷ Match → Step
stepMatch match = runIdentity $ do
(refereeActions, match) ← return $ refereeTurn match
match ← return $ performRefereeActions refereeActions match
(intendedActions, match) ← return $ playersTurn refereeActions match
(succeededActions,match) ← return $ selectActions intendedActions match
match ← return $ performPlayerActions intendedActions succeededActions match
match ← return $ advanceTime match
return $ ((refereeActions,succeededActions),match)
where
playersTurn ∷ [RefereeAction] → Match → ([PlayerWithAction],Match)
playersTurn refereeActions match= (intendedActions,newMatch)
where
actionsOfTeam1 = map (think refereeActions (theBall match) (team2 match)) (singleOutElems (team1 match))
actionsOfTeam2 = map (think refereeActions (theBall match) (team1 match)) (singleOutElems (team2 match))
newMatch = match { team1 = map snd actionsOfTeam1,team2 = map snd actionsOfTeam2}
intendedActions = [(action,playerID) | (action,Player{playerID=playerID}) <- actionsOfTeam1 ++ actionsOfTeam2]
think ∷ [RefereeAction] → BallState → [Player] → (Player,[Player]) → (PlayerAction,Player)
think refereeActions ballstate opponents (me@Player{ effect=effect,brain=brain@Brain{ai=ai,m=m}},ownTeam)
| isNothing effect = (action,newMe)
| otherwise = checkIfNotOnGround (fromJust effect) action newMe
where
(action,newM) = runState (ai $ BrainInput{referee=refereeActions,ball=ballstate,others=ownTeam ++ opponents,me=me}) m
newMe = clonePlayer (brain{ai=ai,m=newM}) me { effect=effect}
checkIfNotOnGround ∷ PlayerEffect → PlayerAction → Player → (PlayerAction,Player)
checkIfNotOnGround (OnTheGround i) action fb
| i <= 0 = (action, fb { effect = Nothing})
| otherwise = (allowOnlyPlayTheater,fb { effect = Just (OnTheGround (i1))
, stamina= alterStamina ballstate fb (zero) (zero)})
where
allowOnlyPlayTheater= case action of
PlayTheater → action
_ → Move (zero) { direction = (direction (speed fb))} (zero)
checkIfNotOnGround _ action fb
= (action,fb)
selectActions ∷ [PlayerWithAction] → Match → ([PlayerWithAction],Match)
selectActions actions match@Match{seed=seed} = runIdentity $ do
(seed,succeededActions) ← return $ validActions match seed actions
((successTackles,failedTackles),seed) ← return $ analyseTackleActions match succeededActions seed
succeededActions ← return $ filter (isNoTackleVictim successTackles) (removeMembers succeededActions failedTackles)
return $ (succeededActions,match { seed=seed})
where
validActions ∷ Match → StdGen → [PlayerWithAction] → (StdGen,[PlayerWithAction])
validActions match seed [] = (seed,[])
validActions match seed actions
| isJust ballAction = (seed1,(fromJust ballAction:otherActions))
| otherwise = (seed1,otherActions)
where
allPlayers = (team1 match) ++ (team2 match)
(ballActions,otherActions) = spanfilter (isActionOnBall ∘ fst) actions
(seed1,ballAction) = selectBallAction (theBall match) allPlayers seed ballActions
selectBallAction ∷ BallState → [Player] → StdGen → [PlayerWithAction] → (StdGen,Maybe PlayerWithAction)
selectBallAction ballstate allPlayers seed desiredActions = runIdentity $ do
(ps,seed) ← return $ iterateStn (length desiredActions) random seed
odds ← return $ [ (successOfAction ballstate allPlayers action (if (p==fromIntegral 1) then p else (makeRandomRealistic p)),action)
| (action,p) <- zip desiredActions ps
]
okOdds ← return $ filter (\(p,a) → p > (zero)) odds
if null okOdds then return $ (seed,Nothing) else do
maxOddProb ← return $ maximum (map fst okOdds)
bestActions ← return $ [a | (p,a) <- okOdds, p >= maxOddProb]
(p,seed) ← return $ random seed
if p==fromIntegral 1 then return $ (seed,Just (head bestActions)) else do
let l = fromIntegral $ length bestActions ∷ Float
let m = p * l
let idx = floor m
return (seed,Just (bestActions !! idx))
where
successOfAction ∷ BallState → [Player] → PlayerWithAction → Float → Float
successOfAction ballstate allPlayers (action,who) p
= myFatigue * myHealth * p * successOfAction
where
successOfAction = if (isGainBall action && ballGainable && ballAtGainSpeed) then successGaining else
(if (isCatchBall action && ballCatchable && ballAtCatchSpeed) then successCatching else
(if (isKickBall action && ballKickable) then successKicking else
(if (isHeadBall action && ballHeadable) then successHeading else
(zero)
)))
Just me = find (identifyPlayer who) allPlayers
myFatigue = (stamina me)
myHealth = (health me)
mySkills = skillsAsList me
myLength = (height me)
iGainWell = elem Gaining mySkills
iKickWell = elem Kicking mySkills
iHeadWell = elem Heading mySkills
iCatchWell = elem Catching mySkills
ballGainable = dPlayerBall <= maxGainReach me
ballKickable = dPlayerBall <= maxKickReach me
ballHeadable = dPlayerBall <= maxHeadReach me
ballCatchable = dPlayerBall <= maxCatchReach me
ballAtGainSpeed = dVelocity <= maxGainVelocityDifference me dPlayerBall
ballAtCatchSpeed = dVelocity <= maxCatchVelocityDifference me dPlayerBall
dSpeed = (zero) { dxy = scaleVector (velocity (speed me)) (toRVector (direction (speed me)))}
RVector3D { dxy = scaleVector (velocity (vxy (ballSpeed theBall))) (toRVector (direction (vxy (ballSpeed theBall))))
, dz = (vz (ballSpeed theBall))
}
dVelocity = sizeVector3D dSpeed
theBall = getBall ballstate allPlayers
dPlayerBall = dist (toPosition3D (pos me)) (ballPos theBall)
othersWithBall = [fb | fb <- allPlayers, ballIsGainedBy (playerID fb) ballstate && not (identifyPlayer who fb)]
otherHasBall = not (null othersWithBall)
otherDribblesWell = elem Dribbling (skillsAsList (head othersWithBall))
successGaining = if (ballIsFree ballstate) then (lengthPenalty * if iGainWell then 0.95 else 0.8) else
(if otherHasBall then (lengthPenalty * if iGainWell then 0.75 else 0.3 * if otherDribblesWell then 0.6 else 1.0)
else 1.0)
successKicking = if (ballIsFree ballstate) then (lengthBonus * if iKickWell then 0.95 else 0.85) else
(if otherHasBall then (lengthBonus * if iKickWell then 0.80 else 0.70 * if otherDribblesWell then 0.7 else 1.0)
else 1.0)
successHeading = if iHeadWell then 0.95 else 0.9
successCatching = if iCatchWell then 1.0 else 0.95
lengthBonus = (myLength1.2) ** 0.15
lengthPenalty = (2.6myLength) ** 0.1
analyseTackleActions ∷ Match → [PlayerWithAction] → StdGen → (([PlayerWithAction],[PlayerWithAction]),StdGen)
analyseTackleActions match performedActions seed
= spanfilterSt (isPossibleTackle match) [action | action <- performedActions, isPlayerTackle (fst action)] seed
where
isPossibleTackle ∷ Match → PlayerWithAction → StdGen → (Bool,StdGen)
isPossibleTackle match@Match{team1=team1,team2=team2} (Tackle victimID _,playerID) seed
| dMeVictim > maxTackleReach offender
= (False,seed)
| otherwise = (((p + chanceOfSuccess) / 2) > 0.5,seed')
where
(p,seed') = random seed
allPlayers = team1 ++ team2
Just offender = find (identifyPlayer playerID) allPlayers
Just victim = find (identifyPlayer victimID) allPlayers
dMeVictim = dist (pos offender) (pos victim)
chanceOfSuccess = (1.0 dMeVictim + if (elem Tackling (skillsAsList offender)) then 0.9 else 0.7) /2
isNoTackleVictim ∷ [PlayerWithAction] → PlayerWithAction → Bool
isNoTackleVictim tackles (action,playerID)
= isSchwalbe action
||
isPlayTheater action
||
null [victim | (Tackle victim _,_) <- tackles, victim==playerID]
refereeTurn ∷ Match → ([RefereeAction],Match)
refereeTurn match@Match{theReferee=referee@Referee{ rbrain=brain@Brain{ai=ai,m=m}},theBall=theBall,playingHalf=playingHalf,team1=team1,team2=team2,playingTime=playingTime,unittime=unittime,seed=seed}
= (refereeActions,match { theReferee=newReferee,seed=newSeed})
where
(refereeActions,(newM,newSeed)) = ai playingTime unittime theBall playingHalf team1 team2 (m,seed)
newReferee = cloneReferee (Brain{ai=ai,m=newM}) referee
performRefereeActions ∷ [RefereeAction] → Match → Match
performRefereeActions refActions match = foldl doRefereeEvent match refActions
where
doRefereeEvent ∷ Match → RefereeAction → Match
doRefereeEvent theMatch@Match{playingHalf=playingHalf,theField=theField,team1=team1,team2=team2} refereeAction
| isAlterMatchBallAndTeams = theMatch { theBall=Free (mkBall pos (zero))}
| isGameProgressEvent = gameProgress theMatch
| isDisplaceTeamsEvent = theMatch { team1=map (displacePlayer ds) team1,team2=map (displacePlayer ds) team2}
| isReprimandEvent = let (team1',team2') = reprimandPlayer (nameOf team1) tef repr (team1,team2) in theMatch { team1=team1',team2=team2'}
| otherwise = theMatch
where
(isAlterMatchBallAndTeams,pos) = case refereeAction of
DirectFreeKick _ pos → (True,pos)
ThrowIn _ pos → (True,pos)
Corner _ _ → (True,fromJust (getKickPos theField playingHalf refereeAction))
GoalKick _ → (True,fromJust (getKickPos theField playingHalf refereeAction))
Penalty _ → (True,fromJust (getKickPos theField playingHalf refereeAction))
CenterKick _ → (True,fromJust (getKickPos theField playingHalf refereeAction))
otherwise → (False,error "UNDEF pos")
(isGameProgressEvent,gameProgress)
= case refereeAction of
GameOver → (True,\m → m { playingTime=(zero)})
AddTime t → (True,\m → m { playingTime=(playingTime m+)t})
EndHalf → (True,\m → m { playingHalf=SecondHalf})
Goal t → (True,\m@Match{score=(w,e)} → m { score=if (t==Team1) then (w+1,e) else (w,e+1)})
otherwise → (False,error "UNDEF gameProgress")
(isDisplaceTeamsEvent,ds) = case refereeAction of
DisplacePlayers ds → (True, ds)
otherwise → (False,error "UNDEF ds")
(isReprimandEvent,(tef,repr)) = case refereeAction of
ReprimandPlayer p r → (True, (p,r))
otherwise → (False,(error "UNDEF tef", error "UNDEF repr"))
displacePlayer ∷ Displacements → Player → Player
displacePlayer displacements fb
= case lookup (playerID fb) displacements of
Just pos → fb { pos=pos}
nothing → fb
reprimandPlayer ∷ ClubName → PlayerID → Reprimand → ([Player],[Player]) → ([Player],[Player])
reprimandPlayer club1 playerID RedCard (team1,team2)
= splitAt (nrPlayers1 if ((clubName playerID) == club1) then 1 else 0) (uneq1++uneq2)
where
(uneq1,_,uneq2) = break1 (identifyPlayer playerID) (team1++team2)
nrPlayers1 = length team1
reprimandPlayer _ _ _ teams = teams
performPlayerActions ∷ [PlayerWithAction] → [PlayerWithAction] → Match → Match
performPlayerActions actions succeededActions match@Match{theField=theField,theBall=theBall,team1=team1,team2=team2,seed=seed,unittime=unittime} = runIdentity $ do
(seed,ball,newPlayers1,newPlayers2)
← return $ foldl (flip (performAction succeededActions)) (seed,theBall,team1,team2) actions
(ball,seed) ← return $ moveBall theField (newPlayers1++newPlayers2) (ball,seed)
match ← return $ match { team1=newPlayers1, team2=newPlayers2, theBall=ball, seed=seed }
return $ match
where
performAction ∷ [PlayerWithAction] → PlayerWithAction → (StdGen,BallState,[Player],[Player])
→ (StdGen,BallState,[Player],[Player])
performAction succeededActions initiative (seed,ball,allPlayers1,allPlayers2)
| elem initiative succeededActions
= performAction' initiative (seed,ball,allPlayers1,allPlayers2)
| otherwise
= (seed,ball,map (failThisPlayerAction initiative) allPlayers1,map (failThisPlayerAction initiative) allPlayers2)
where
failThisPlayerAction ∷ PlayerWithAction → Player → Player
failThisPlayerAction (idea,playerID) fb
| identifyPlayer playerID fb
= fb { effect=Just (failPlayerAction idea)}
| otherwise = fb
performAction' ∷ PlayerWithAction → (StdGen,BallState,[Player],[Player])
→ (StdGen,BallState,[Player],[Player])
performAction' (Move sp angle,playerID) (seed,ball,team1,team2) = runIdentity $ do
(team1,team2) ← return $ splitAt (length team1) (unbreak1 (uneq1,newFb,uneq2))
return (seed1,ball,team1,team2)
where
(uneq1,fb,uneq2) = break1 (identifyPlayer playerID) (team1 ++ team2)
curNose = (nose fb)
curSpeed = (speed fb)
skills = skillsAsList fb
playerHasBall = ballIsGainedBy playerID ball
(p,seed1) = random seed
feasibleAngle = ((signum angle)) * (abs angle `boundedBy` (0, maxRotateAngle fb))
newNose = curNose + feasibleAngle
angleDifficulty = angleHowFarFromPi ((direction sp)newNose)
angleDifference = angleHowFarFromAngle (direction sp) newNose
newStamina = alterStamina ball fb angleDifficulty angleDifference
newHealth = alterHealth ball fb p angleDifficulty angleDifference
healthFat = getHealthStaminaFactor newHealth newStamina
newVel = healthFat * (velocity sp `boundedBy` (0, maxVelocity fb angleDifficulty angleDifference))
newSpeed = sp { velocity=newVel}
newPosition' = movePoint (scaleVector (unittime * newVel) (toRVector (direction newSpeed))) (pos fb)
newPosition = pointToRectangle ((zero),Position{px=(flength theField),py=(fwidth theField)}) newPosition'
newFb = fb { stamina = newStamina
, health = newHealth
, speed = newSpeed
, pos = newPosition
, nose = newNose
, effect = Just (Moved newSpeed feasibleAngle)
}
performAction' (GainBall,playerID) (seed,ball,team1,team2) = runIdentity $ do
(team1,team2) ← return $ splitAt (length team1) (unbreak1 (uneq1,newFb,uneq2))
return $ (seed,GainedBy playerID,team1,team2)
where
(uneq1,fb,uneq2) = break1 (identifyPlayer playerID) (team1 ++ team2)
newFb = fb { effect = Just (GainedBall Success)}
performAction' (KickBall (Speed3D{vxy=Speed{velocity=v,direction=d},vz=vz}),playerID) (seed,ball,team1,team2) = runIdentity $ do
(team1,team2) ← return $ splitAt (length team1) (unbreak1 (uneq1,newFb,uneq2))
return $ (seed2,Free newBall,team1,team2)
where
(uneq1,fb,uneq2) = break1 (identifyPlayer playerID) (team1 ++ team2)
(p1,seed1) = random seed
(p2,seed2) = random seed1
newFb = fb { stamina=newStamina,effect=Just (KickedBall (Just newSpeed))}
theBall = getBall ball (team1 ++ team2)
skills = skillsAsList fb
fatHealth = getHealthStaminaFactor (health fb) (stamina fb)
maxV = maxVelocityBallKick fb
newV = speedFactor * (v `boundedBy` (0,maxV))
newVz = speedFactor * (vz `boundedBy` (0, maxV))
newSpeed = Speed3D{vxy=Speed{velocity=newV,direction=newDirection},vz=newVz}
newStamina = kickingPenalty fb newV * (stamina fb)
speedFactor = oppositeKickPenalty fb d
newBall = theBall { ballSpeed=newSpeed}
newDirection = runIdentity $ do
if p2 == (fromIntegral 1) then return d else do
failure ← return $ (fromIntegral 1) if (elem Kicking skills) then (makeRandomRealisticSkilled p2) else (makeRandomRealistic p2)
if p1 `mod` (2::Int) ≡ 0 then do
newD ← return $ d failure * maxKickingDeviation fb
return $ if (newD < (zero)) then (newD + 2.0*pi) else newD
else do
newD ← return $ d + failure * maxKickingDeviation fb
return $ if (newD > 2.0*pi) then (newD 2.0*pi) else newD
performAction' (HeadBall (Speed3D{vxy=Speed{velocity=v,direction=d},vz=vz}),playerID) (seed,ballstate,team1,team2) = runIdentity $ do
(team1,team2) ← return $ splitAt (length team1) (unbreak1 (uneq1,newFb,uneq2))
return $ (seed2,Free newBall,team1,team2)
where
(uneq1,fb,uneq2) = break1 (identifyPlayer playerID) (team1 ++ team2)
(p1,seed1) = random seed
(p2,seed2) = random seed1
skills = skillsAsList fb
fatHealth = getHealthStaminaFactor (health fb) (stamina fb)
ball = getBall ballstate (team1 ++ team2)
ballSpeed' = (velocity (vxy (ballSpeed ball)))
maxV = maxVelocityBallHead fb ballSpeed'
newV = v `boundedBy` (zero, maxV)
newVz = 0.25 * (vz `boundedBy` (0, maxV))
newDirection = runIdentity $ do
if p2 == (fromIntegral 1) then return d else do
failure ← return $ (fromIntegral 1) if (elem Heading skills) then makeRandomRealisticSkilled p2 else makeRandomRealistic p2
if p1 `mod` (2::Int) ≡ 0 then do
newD ← return $ d failure * maxHeadingDeviation fb
return $ if (newD < (zero)) then (newD + 2.0*pi) else newD
else do
newD ← return $ d + failure * maxHeadingDeviation fb
return $ if (newD > 2.0*pi) then (newD 2.0*pi) else newD
newSpeed = Speed3D{vxy=Speed{velocity=newV,direction=newDirection},vz=newVz}
newStamina = headingPenalty fb newV ballSpeed' * (stamina fb)
newFb = fb { stamina=newStamina,effect=Just (HeadedBall (Just newSpeed))}
newBall = ball { ballSpeed=newSpeed}
performAction' (Feint d,playerID) (seed,ball,team1,team2) = runIdentity $ do
(team1,team2) ← return $ splitAt (length team1) (unbreak1 (uneq1,newFb,uneq2))
return $ (seed,ball,team1,team2)
where
(uneq1,fb,uneq2) = break1 (identifyPlayer playerID) (team1 ++ team2)
playerHasBall = ballIsGainedBy playerID ball
newStamina = maxFatigueLossAtFeint fb * (stamina fb)
fatHealth = getHealthStaminaFactor (health fb) (stamina fb)
newVelocity = fatHealth * (velocity (speed fb)) * maxVelocityLossAtFeint fb
newSpeed = (speed fb) { velocity=newVelocity}
(leftv,rightv) = orthogonal (direction (speed fb))
sidestep = case d of FeintLeft → leftv; _ → rightv
newPosition' = movePoint ((scaleVector (maxFeintStep fb) (toRVector sidestep))
+
(scaleVector (unittime * newVelocity) (toRVector (direction (speed fb))))
) (pos fb)
newPosition = pointToRectangle ((zero),Position{px=(flength theField),py=(fwidth theField)}) newPosition'
newFb = fb { pos=newPosition,speed=newSpeed,stamina=newStamina,effect=Just (Feinted d)}
performAction' (Tackle victimID ve,playerID) (seed,ball,team1,team2) = runIdentity $ do
return $ (seed1,newBall,team1T,team2T)
where
nrPlayersTeam1 = length team1
(uneq1,fb,uneq2) = break1 (identifyPlayer playerID) (team1 ++ team2)
(team1N,team2N) = splitAt nrPlayersTeam1 (unbreak1 (uneq1,newFb,uneq2))
(uneq1T,fbT,uneq2T) = break1 (identifyPlayer victimID) (team1N ++ team2N)
(team1T,team2T) = splitAt nrPlayersTeam1 (unbreak1 (uneq1T,newTarget,uneq2T))
newStaminaSelf = maxFatigueLossAtTackle fb * (stamina fb)
fatHealthSelf = getHealthStaminaFactor (health fb) (stamina fb)
newFb = fb { stamina = newStaminaSelf, effect = Just (Tackled victimID ve Success)}
targetHasBall = ballIsGainedBy victimID ball
(p,seed1) = random seed
newV' = min maxTackleVelocity ve
maxTackleVelocity = 10.0
newV = newV'/10.0
healthDamageTarget = newV * fatHealthSelf * (0.5*p + 0.1) + ((height fbT)minLength)/2.0
newHealthTarget = max 0.0 (health fbT) healthDamageTarget
newTarget = fbT { health = newHealthTarget, effect = Just (OnTheGround 3) }
newBall = if targetHasBall then (Free (mkBall (pos fbT) (speed fbT))) else ball
performAction' (Schwalbe,playerID) (seed,ball,team1,team2) = runIdentity $ do
(team1,team2) ← return $ splitAt (length team1) (unbreak1 (uneq1,newFb,uneq2))
return $ (seed,ball,team1,team2)
where
(uneq1,fb,uneq2) = break1 (identifyPlayer playerID) (team1 ++ team2)
newFb = fb { effect = Just (OnTheGround 1)}
performAction' (CatchBall,playerID) (seed,ball,team1,team2) = runIdentity $ do
(team1,team2) ← return $ splitAt (length team1) (unbreak1 (uneq1,newFb,uneq2))
return $ (seed,GainedBy playerID,team1,team2)
where
(uneq1,fb,uneq2) = break1 (identifyPlayer playerID) (team1 ++ team2)
newFb = fb { effect=Just (CaughtBall Success)}
performAction' (PlayTheater,playerID) (seed,ball,team1,team2) = runIdentity $ do
(team1,team2) ← return $ splitAt (length team1) (unbreak1 (uneq1,newFb,uneq2))
return $ (seed,ball,team1,team2)
where
(uneq1,fb,uneq2) = break1 (identifyPlayer playerID) (team1 ++ team2)
e = (effect fb)
wasOnTheGround = if (isNothing e) then False else (isOnTheGround (fromJust e))
newEvent = if (isOnTheGround (fromJust e)) then (fromJust e) else PlayedTheater
newFb = fb { effect = Just newEvent}
moveBumpedPlayers ∷ Speed → [PlayerID] → (StdGen,[Player],[Player])
→ (StdGen,[Player],[Player])
moveBumpedPlayers newSpeed bumpedInto (seed,team1,team2)
= foldl (moveBumpedPlayer newSpeed) (seed,team1,team2) bumpedInto
where
moveBumpedPlayer ∷ Speed → (StdGen,[Player],[Player]) → PlayerID
→ (StdGen,[Player],[Player])
moveBumpedPlayer newSpeed (seed,team1,team2) playerID = runIdentity $ do
(team1,team2) ← return $ splitAt (length team1) (unbreak1 (uneq1,newFb,uneq2))
return $ (seed1,team1,team2)
where
(uneq1,fb,uneq2) = break1 (identifyPlayer playerID) (team1 ++ team2)
(p,seed1) = random seed
newPos = let a = p*2.0*pi in movePoint RVector {dx=0.5*cos a,dy=0.5*sin a} (pos fb)
newFb = fb { speed=newSpeed, pos=newPos}
moveBall ∷ Field → [Player] → (BallState,StdGen) → (BallState,StdGen)
moveBall _ _ gained@(GainedBy playerID,seed)
= gained
moveBall field allPlayers (Free ball@Ball{ballSpeed=Speed3D{vxy=Speed{velocity=v,direction=d},vz=vz},ballPos=ballPos},seed)
= (Free ball { ballSpeed=newSpeed,ballPos=newBallpos},seed1)
where
inTheAir = (pz ballPos) > (zero)
resistance = if inTheAir then airResistance else surfaceResistance
surfaceMovement = scaleVector (unittime * v) (toRVector d)
newSpeed2D = let newV = resistance*v in Speed{direction = d, velocity = if (newV <= 0.05) then (zero) else newV}
newVz' = vz unittime*accellerationSec
newHeight' = (pz ballPos) + vz
(newHeight,newVz) = if (newHeight' < (zero)) then (0.5*(abs newHeight'),let newV = 0.33*(abs newVz') in if (newV <= 0.8) then (zero) else newV) else
(newHeight',newVz')
newSpeed' = Speed3D{vxy=newSpeed2D, vz=newVz}
newBallpos = Position3D{pxy=movePoint surfaceMovement (pxy ballPos),pz=newHeight}
fieldDimensions = ((zero),Position{px=(flength field),py=(fwidth field)})
(newSpeed,seed1) = ballBouncesAgainst field newBallpos newSpeed' allPlayers seed
ballBouncesAgainst ∷ Field → Position3D → Speed3D → [Player] → StdGen → (Speed3D,StdGen)
ballBouncesAgainst field newBallpos newSpeed@Speed3D{vxy=Speed{velocity=v,direction=d},vz=s3d} allPlayers seed
| againstGoalWestNorthPole || againstGoalWestSouthPole || againstGoalEastNorthPole || againstGoalEastSouthPole
= (newSpeed { vxy = (vxy newSpeed) { direction = if (p1<=0.5) then (dp2*pi) else (d+p2*pi), velocity = resistance*v}},seed2)
| againstGoalWestPoleUpper || againstGoalEastPoleUpper = runIdentity $ do
forwardOrBack ← return $ if (p1<=0.5) then Forward else Back
upOrDown ← return $ if ((pz newBallpos) < goalHeight+(goalPoleWidth/2.0)) then Down else Up
return $ bounceBall upOrDown (bounceBall forwardOrBack (newSpeed,seed2))
| any (\fb → inRadiusOfPlayer (pxy newBallpos) fb && (height fb) >= (pz newBallpos)) allPlayers
= (newSpeed { vxy = (vxy newSpeed) { direction = p2*2.0*pi, velocity = resistance*v}, vz=p1*s3d},seed2)
| otherwise
= (newSpeed,seed2)
where
(p1,seed1) = random seed
(p2,seed2) = random seed1
(northPole,southPole) = goalPoles field
againstGoalWestNorthPole = inCircleRadiusOfPosition newBallpos (goalPoleWidth/2.0) goalHeight Position {px=0.0, py=northPole goalPoleWidth/2.0}
againstGoalWestSouthPole = inCircleRadiusOfPosition newBallpos (goalPoleWidth/2.0) goalHeight Position {px=0.0, py=southPole + goalPoleWidth/2.0}
againstGoalEastNorthPole = inCircleRadiusOfPosition newBallpos (goalPoleWidth/2.0) goalHeight Position {px=(flength field),py=northPole goalPoleWidth/2.0}
againstGoalEastSouthPole = inCircleRadiusOfPosition newBallpos (goalPoleWidth/2.0) goalHeight Position {px=(flength field),py=southPole + goalPoleWidth/2.0}
againstGoalWestPoleUpper = (isbetween (py (pxy newBallpos)) (northPolegoalPoleWidth/2.0) (southPole+goalPoleWidth/2.0))
&&
(isbetween (pz newBallpos) goalHeight (goalHeight+goalPoleWidth))
&&
((px (pxy newBallpos)) <= (zero))
againstGoalEastPoleUpper = (isbetween (py (pxy newBallpos)) (northPolegoalPoleWidth/2.0) (southPole+goalPoleWidth/2.0))
&&
(isbetween (pz newBallpos) goalHeight (goalHeight+goalPoleWidth))
&&
((px (pxy newBallpos)) >= (flength field))
advanceTime ∷ Match → Match
advanceTime match@Match{playingTime=playingTime, unittime=unittime}
= match { playingTime = max (zero) ((playingTime*160.0 unittime)/160.0)}
alterStamina ∷ BallState → Player → Angle → Angle → Stamina
alterStamina ballState fb angleDifficulty angleDifference
| v <= rfv
= if s < minimumFatigue then minimumFatigue else s**0.8
| otherwise
= if s > maximumFatigue then maximumFatigue * ((fromIntegral 1) angleDifficulty/(4.0*pi)) else fv * ((fromIntegral 1) angleDifficulty/(4.0*pi))
where
v = (velocity (speed fb))
h = (height fb)
s = (stamina fb)
rfv = restoreStaminaVelocity ballState fb angleDifficulty angleDifference
diff = vrfv
fv = if (diff >= 6.0) then (s**(s**(1.6 + 2.0*h/100.0))) else
(if (diff >= 4.0) then (s**(1.5 + h/100.0)) else
(if (diff >= 2.0) then (s**(1.4 h/100.0)) else
(s**(1.3 2.0*h/100.0))))
alterHealth ∷ BallState → Player → Float → Angle → Angle → Health
alterHealth ballState fb p angleDifficulty angleDifference
| (velocity (speed fb)) <= rfv = min (h+p/10.0) 1.0
| otherwise = h
where
h = (health fb)
rfv = restoreStaminaVelocity ballState fb angleDifficulty angleDifference
restoreStaminaVelocity ∷ BallState → Player → Angle → Angle → Velocity
restoreStaminaVelocity ballState fb angleDifficulty angleDifference
| ballIsGainedBy (playerID fb) ballState
= maxV / (if (elem Running skills) then 1.6 else 2.6)
| elem Running skills = maxV / (if (elem Dribbling skills) then 2.0 else 3.0) * 1.22
| otherwise = maxV / (if (elem Dribbling skills) then 2.0 else 3.0)
where
skills = skillsAsList fb
maxV = maxVelocity fb angleDifficulty angleDifference
maxVelocity ∷ Player → Angle → Angle → Velocity
maxVelocity fb angleDifficulty angleDifference
= dribblingPenalty * runningPenalty * baseVelocity
where
skills = skillsAsList fb
baseVelocity = 10.0
dribblingPenalty = if (elem Dribbling skills) then 0.95 else 0.85
runningPenalty = if (elem Running skills) then 1.0 else 0.85
minimumFatigue = 0.05
maximumFatigue = 0.985
type PenaltyFactor = Float
kickingPenalty ∷ Player → Velocity → PenaltyFactor
kickingPenalty fb newV = 1.0 (if (elem Kicking (skillsAsList fb)) then 0.3 else 0.6) * (newV/maxV)**2.0
where
maxV = maxVelocityBallKick fb
headingPenalty ∷ Player → Velocity → Velocity → PenaltyFactor
headingPenalty fb newV ballV = 1.0 (if (elem Heading (skillsAsList fb)) then 0.08 else 0.13) * (newV/maxV)**2.0
where
maxV = maxVelocityBallHead fb ballV
maxFatigueLossAtTackle ∷ Player → PenaltyFactor
maxFatigueLossAtTackle fb = if (elem Tackling (skillsAsList fb)) then 0.99 else 0.9
maxFatigueLossAtFeint ∷ Player → PenaltyFactor
maxFatigueLossAtFeint fb = if (elem Feinting (skillsAsList fb)) then 0.92 else 0.77
maxVelocityLossAtFeint ∷ Player → PenaltyFactor
maxVelocityLossAtFeint fb = if (elem Feinting (skillsAsList fb)) then 0.99 else 0.75
oppositeKickPenalty ∷ Player → Angle → PenaltyFactor
oppositeKickPenalty fb kickTo = 1.0 skillPenaltyFactor * (angleHowFarFromPi angle)/pi
where
angle = abs ((nose fb) kickTo)
skills = skillsAsList fb
skillPenaltyFactor = if (all (`elem` skills) [Rotating,Kicking]) then 0.3
else (if (any (`elem` skills) [Rotating,Kicking]) then 0.5
else 0.9)