1 implementation module matchControl
4 import Gui2D // we choose the 2D GUI version of SoccerFun
7 :: Seconds = Seconds !Real
9 s :: !Real -> Seconds // (s x) represents x seconds of time
12 instance zero Seconds where zero = Seconds zero
13 instance == Seconds where == (Seconds s1) (Seconds s2)= s1 == s2
14 instance < Seconds where < (Seconds s1) (Seconds s2)= s1 < s2
15 instance + Seconds where + (Seconds s1) (Seconds s2)= Seconds (s1 + s2)
16 instance - Seconds where - (Seconds s1) (Seconds s2)= Seconds (s1 - s2)
17 instance minutes Seconds where minutes (Seconds s) = minutes (s/60.0)
18 instance toReal Seconds where toReal (Seconds s) = s
19 instance scale Seconds where scale k (Seconds s) = Seconds (k * s)
20 instance toString Seconds where toString (Seconds s) = s +++> " sec."
22 doSoccerFun :: !*World -> *World
23 doSoccerFun world = SoccerFunGUI2D world
25 setMatchStart :: !Team !Team !FootballField !Referee !PlayingTime !RandomSeed -> Match
26 setMatchStart fstTeam sndTeam field referee time rs
27 = { team1 = validateTeam fstTeam
28 , team2 = validateTeam sndTeam
31 , theReferee = referee
32 , playingHalf = FirstHalf
36 , nextRandomP = nextRandomP
38 , lastContact = Nothing
41 stepMatch :: !Match -> (!(![RefereeAction],!AssocList FootballerID FootballerAction),!Match)
43 # (refereeActions, match) = refereeTurn match
44 # match = performRefereeActions refereeActions match
45 # (intendedActions, match) = playersThink refereeActions match
46 # (okActions, match) = successfulActions intendedActions match
47 # match = doFootballerActions intendedActions okActions match
48 # match = moveFootball match
49 # match = advanceTime match
50 = ((refereeActions,okActions),match)
53 determines whether the rules of soccer are adhered to and yields a list of referee actions.
54 */ refereeTurn :: !Match -> (![RefereeAction],!Match)
55 refereeTurn match=:{theReferee=referee=:{Referee | brain=brain=:{ai,memory}},theBall,playingHalf,team1,team2,playingTime,unittime,seed,lastContact}
56 = (refereeActions,{match & theReferee=new_referee,seed=new_seed})
58 (refereeActions,(memory`,new_seed)) = ai ({RefereeInput | playingTime = playingTime
61 , playingHalf = playingHalf
64 , lastContact = lastContact
68 new_referee = {Referee | referee & brain={Brain | brain & memory=memory`}}
70 /* performRefereeActions refereeActions match
71 performs for each football player in match his succeededAction, informs them about the referee actions, and moves the ball.
72 */ performRefereeActions :: ![RefereeAction] !Match -> Match
73 performRefereeActions refActions match = foldl doRefereeEvent match refActions
75 doRefereeEvent :: !Match !RefereeAction -> Match
76 doRefereeEvent theMatch=:{Match | playingHalf,theField,team1,team2} refereeAction
77 | isAlterMatchBallAndTeams = {Match | theMatch & theBall=Free (mkFootball pos zero),lastContact=Nothing}
78 | isProgressEvent = gameProgress theMatch
79 | isDisplaceTeamsEvent = {Match | theMatch & team1=map (displacePlayer ds) team1,team2=map (displacePlayer ds) team2}
80 | isReprimandEvent = let (team1`,team2`) = reprimandPlayer rep (team1,team2) in {Match | theMatch & team1=team1`,team2=team2`}
81 | otherwise = theMatch
83 (isAlterMatchBallAndTeams,pos) = case refereeAction of
84 DirectFreeKick _ pos = (True,pos)
85 ThrowIn _ pos = (True,pos)
86 Corner _ _ = (True,fromJust (getKickPos theField playingHalf refereeAction))
87 GoalKick _ = (True,fromJust (getKickPos theField playingHalf refereeAction))
88 Penalty _ = (True,fromJust (getKickPos theField playingHalf refereeAction))
89 CenterKick _ = (True,fromJust (getKickPos theField playingHalf refereeAction))
90 otherwise = (False,undef)
91 (isProgressEvent,gameProgress) = case refereeAction of
92 GameOver = (True,\m -> {Match | m & playingTime=zero})
93 GameCancelled mt = (True,\m -> {Match | m & playingTime=zero,score=case mt of
95 Just West = if (playingHalf==FirstHalf) (1,0) (0,1)
96 just_east = if (playingHalf==FirstHalf) (0,1) (1,0)
98 AddTime t = (True,\m -> {Match | m & playingTime=m.Match.playingTime+t})
99 EndHalf = (True,\m -> {Match | m & playingHalf=SecondHalf})
100 Goal h = (True,\m=:{score=(w,e)} -> {Match | m & score=if (h==West && playingHalf==FirstHalf || h==East && playingHalf==SecondHalf) (w+1,e) (w,e+1)})
101 otherwise = (False,undef)
102 (isDisplaceTeamsEvent,ds) = case refereeAction of
103 DisplacePlayers ds = (True, ds)
104 otherwise = (False,undef)
105 (isReprimandEvent,rep) = case refereeAction of
106 ReprimandPlayer p r = (True, (p,r))
107 otherwise = (False,undef)
109 displacePlayer :: !Displacements !Footballer -> Footballer
110 displacePlayer displacements fb = case lookup fb.playerID displacements of
111 Just pos = {fb & pos=pos}
114 reprimandPlayer :: !(!FootballerID,!Reprimand) !(![Footballer],![Footballer]) -> (![Footballer],![Footballer])
115 reprimandPlayer (playerID,RedCard) (team1,team2)
116 = splitAt (nr_players_1 - if (playerID.clubName == club1) 1 0) (uneq1++uneq2)
119 (uneq1,_,uneq2) = break1 (identify_player playerID) (team1++team2)
120 nr_players_1 = length team1
121 reprimandPlayer _ teams = teams
123 /* playersThink match
124 lets every footballer player conjure an initiative.
125 */ playersThink :: ![RefereeAction] !Match -> (!AssocList FootballerID FootballerAction,!Match)
126 playersThink refereeActions match=:{Match | theBall,team1,team2}
127 = (intendedActions,new_match)
129 actionsOfTeam1 = map (think refereeActions theBall team2) (singleOutElems team1)
130 actionsOfTeam2 = map (think refereeActions theBall team1) (singleOutElems team2)
131 new_match = {Match | match & team1 = map snd actionsOfTeam1,team2 = map snd actionsOfTeam2}
132 intendedActions = [(playerID,action) \\ (action,{playerID}) <- actionsOfTeam1 ++ actionsOfTeam2]
134 think :: ![RefereeAction] !FootballState ![Footballer] !(!Footballer,![Footballer]) -> (!FootballerAction,!Footballer)
135 think refereeActions ballstate opponents (me=:{Footballer | brain=brain=:{ai,memory}},ownTeam)
136 # (action,memory) = ai ({referee=refereeActions,football=ballstate,others=ownTeam ++ opponents,me=me},memory)
137 # me = {Footballer | me & brain = {Brain | brain & memory=memory}}
140 /* successfulActions intendedActions match
141 removes all failing intended actions, and returns the list of remaining succeeding actions.
142 Players who are successfully tackled fail their action.
143 Players who are (still) lying on the ground fail their action.
144 At most one action of {GainBall, KickBall, HeadBall, CatchBall} succeeds.
145 If another player has successfully played the ball then his/her playerID is registered in Match.
146 */ successfulActions :: !(AssocList FootballerID FootballerAction) !Match -> (!AssocList FootballerID FootballerAction,!Match)
147 successfulActions intendedActions match=:{seed,lastContact,nextRandomP,team1,team2,theBall}
148 # otherActions = filter (\(playerID,_) -> not (isMember playerID groundVictims)) intendedActions
149 # (tackleActions,otherActions) = spanfilter (isFootballerTackle o snd) intendedActions
150 # (okTackleActions,seed) = selectTackleActions tackleActions seed
151 # tackleVictims = [victim \\ (_,Tackle victim _) <- okTackleActions]
152 # otherActions = filter (\(playerID,action) -> not (isMember playerID tackleVictims)) otherActions
153 # (ballActions,otherActions) = spanfilter (isActionOnBall o snd) otherActions
154 # (okBallAction,seed) = selectBallAction ballActions seed
155 # (okActions,newContact) = case okBallAction of
156 Just (player,action) = ([(player,action):okTackleActions ++ otherActions],Just player)
157 nope = ( okTackleActions ++ otherActions ,lastContact)
158 = (okActions,{match & seed=seed, lastContact=newContact})
160 all_players = team1 ++ team2
161 ball = getFootball theBall all_players
162 groundVictims = [playerID \\ {playerID,effect=Just (OnTheGround frames)} <- all_players | frames >= 0]
164 /* selectBallAction picks at most one action of {GainBall, KickBall, HeadBall, CatchBall} intentions.
165 The association list is assumed to contain only these actions.
166 */ selectBallAction :: !(AssocList FootballerID FootballerAction) !RandomSeed -> (!Maybe (FootballerID,FootballerAction),!RandomSeed)
167 selectBallAction intendedActions seed
168 # (ps,seed) = iterateStn (length intendedActions) nextRandomP seed
169 = selectMostProbableAction [ (successOfAction action (if (p==one) p (makeRandomRealistic p)),action) \\ action <- intendedActions & p <- ps ] seed
171 successOfAction :: !(!FootballerID,!FootballerAction) !P -> P
172 successOfAction (who,action) p = me.stamina * me.health * p * success_of_action
174 success_of_action = if (isGainBall action && ballGainable && ballAtGainSpeed) success_gaining
175 (if (isCatchBall action && ballCatchable && ballAtCatchSpeed) success_catching
176 (if (isKickBall action && ballKickable) success_kicking
177 (if (isHeadBall action && ballHeadable) success_heading
180 me = find1 (identify_player who) all_players
181 mySkills = skillsAsList me
183 iGainWell = isMember Gaining mySkills
184 iKickWell = isMember Kicking mySkills
185 iHeadWell = isMember Heading mySkills
186 iCatchWell = isMember Catching mySkills
187 ballGainable = d_player_ball <= maxGainReach me && ball_height <= scale 0.8 length + scale (if iGainWell 0.2 0.0) length
188 ballKickable = d_player_ball <= maxKickReach me && ball_height <= scale 0.4 length + scale (if iKickWell 0.6 0.0) length
189 ballCatchable = d_player_ball <= maxCatchReach me && ball_height <= length + scale (if iCatchWell 1.0 0.5) length
190 ballHeadable = d_player_ball <= maxHeadReach me && ball_height <= length + scale (if iHeadWell 0.5 0.0) length && ball_height >= scale 0.8 length
191 ballAtGainSpeed = d_velocity <= maxGainVelocityDifference me d_player_ball
192 ballAtCatchSpeed = d_velocity <= maxCatchVelocityDifference me d_player_ball
193 d_speed = {zero & dxy = scale (toReal me.speed.velocity) (toRVector me.speed.direction)}
195 {dxy = scale (toReal ball.ballSpeed.vxy.velocity) (toRVector ball.ballSpeed.vxy.direction),dz = m (toReal ball.ballSpeed.vz)}
196 d_velocity = ms (toReal (size_vector3D d_speed))
197 ball_height = ball.ballPos.pz
198 d_player_ball = dist me ball
199 others_with_ball = case theBall of
200 GainedBy playerID = if (playerID <> who) (filter (identify_player playerID) all_players) []
202 other_has_ball = not (isEmpty others_with_ball)
203 otherDribblesWell = isMember Dribbling (skillsAsList (hd others_with_ball))
204 success_gaining = if (ballIsFree theBall) (if iGainWell 0.95 0.8)
205 (if other_has_ball (if iGainWell 0.75 0.3 * if otherDribblesWell 0.6 1.0)
207 success_kicking = if (ballIsFree theBall) (if iKickWell 0.95 0.85)
208 (if other_has_ball (if iKickWell 0.80 0.70 * if otherDribblesWell 0.7 1.0)
210 success_heading = if iHeadWell 0.95 0.90
211 success_catching = if iCatchWell 1.00 0.95
213 /** selectTackleActions removes impossible tackle actions and, by chance, ignores some of the possible tackle actions.
214 */ selectTackleActions :: !(AssocList FootballerID FootballerAction) !RandomSeed -> (!AssocList FootballerID FootballerAction,!RandomSeed)
215 selectTackleActions performedActions seed
216 = filterSt isPossibleTackle [action \\ action <- performedActions | isFootballerTackle (snd action)] seed
218 isPossibleTackle :: !(!FootballerID,!FootballerAction) !RandomSeed -> (!Bool,!RandomSeed)
219 isPossibleTackle (playerID,Tackle victimID _) seed
220 | d_me_victim > maxTackleReach offender // victim is out of reach
222 # (p,seed) = nextRandomP seed
223 | otherwise = (avg [p,chanceOfSuccess] > 0.5,seed) // victim is within reach, but tackle may fail
225 offender = find1 (identify_player playerID) all_players
226 victim = find1 (identify_player victimID) all_players
227 d_me_victim = dist offender victim
228 chanceOfSuccess = avg [1.0 - toReal d_me_victim, if (isMember Tackling (skillsAsList offender)) 0.9 0.7]
230 /* doFootballerActions intendedActions okActions match
231 performs for each football player in match his succeededAction.
232 */ doFootballerActions :: !(AssocList FootballerID FootballerAction) !(AssocList FootballerID FootballerAction) !Match -> Match
233 doFootballerActions intendedActions okActions match=:{theField,theBall,team1,team2,seed,nextRandomP}
234 # (seed,ball,new_players1,new_players2) = foldl (flip doAction) (seed,theBall,team1,team2) intendedActions
235 = { match & team1 = new_players1, team2 = new_players2, theBall = ball, seed = seed }
237 dt = toReal match.Match.unittime // duration, in seconds, of one step
238 {fwidth,flength} = theField
240 doAction :: !(!FootballerID,!FootballerAction) !(!RandomSeed,!FootballState,![Footballer],![Footballer])
241 -> (!RandomSeed,!FootballState,![Footballer],![Footballer])
242 doAction intendedAction (seed,ball,allPlayers1,allPlayers2)
243 | isMember intendedAction okActions = act intendedAction (seed,ball,allPlayers1,allPlayers2)
244 | otherwise = (seed,ball,map (failThisPlayerAction intendedAction) allPlayers1,map (failThisPlayerAction intendedAction) allPlayers2)
246 failThisPlayerAction :: !(!FootballerID,!FootballerAction) !Footballer -> Footballer
247 failThisPlayerAction (id,idea) fb=:{playerID,effect}
248 | id <> playerID = fb
249 | otherwise = {fb & effect = new_effect}
251 new_effect = case effect of
252 Just (OnTheGround nr_of_frames) = if (nr_of_frames < 0) Nothing (Just (OnTheGround (nr_of_frames-1)))
253 _ = Just (failFootballerAction idea)
255 act :: !(!FootballerID,!FootballerAction) !(!RandomSeed,!FootballState,![Footballer],![Footballer])
256 -> (!RandomSeed,!FootballState,![Footballer],![Footballer])
258 /** Rules for moving:
259 */ act (playerID,Move speed angle) (seed,ball,team1,team2)
260 # (team1,team2) = splitAt (length team1) (unbreak1 (uneq1,new_fb,uneq2))
261 = (seed,ball,team1,team2)
263 (uneq1,fb,uneq2) = break1 (identify_player playerID) (team1 ++ team2)
264 feasible_angle = scale (fromInt (sign angle)) (setbetween (abs angle) zero (maxRotateAngle fb))
265 new_nose = fb.nose + feasible_angle
266 angleDifficulty = angleHowFarFromPi (speed.direction-new_nose)
267 angleDifference = angleHowFarFromAngle speed.direction new_nose
268 new_stamina = alter_stamina ball fb angleDifficulty angleDifference
269 new_vel = scale (1.4 * fb.health * new_stamina) (setbetween speed.velocity zero (maxVelocity (skillsAsList fb) angleDifficulty angleDifference))
270 new_speed = {speed & velocity=new_vel}
271 new_position` = move_point (scale (dt * (toReal new_vel)) (toRVector new_speed.direction)) fb.pos
272 new_position = point_to_rectangle ({px=scale -0.5 flength, py=scale -0.5 fwidth},{px=scale 0.5 flength,py=scale 0.5 fwidth}) new_position`
273 new_fb = {fb & stamina = new_stamina
277 , effect = Just (Moved new_speed feasible_angle)
280 /** Rules for gaining ball:
281 (1) ball obtains position and surface speed of obtaining player
282 */ act (playerID,GainBall) (seed,ball,team1,team2)
283 # (team1,team2) = splitAt (length team1) (unbreak1 (uneq1,new_fb,uneq2))
284 = (seed,GainedBy playerID,team1,team2)
286 (uneq1,fb,uneq2) = break1 (identify_player playerID) (team1 ++ team2)
287 new_fb = {fb & effect = Just (GainedBall Success)}
289 /** Rules for kicking ball:
290 (1) kicking decreases stamina
291 (2) kicking is more effective towards your direction, and least effective in opposite direction
292 (3) being taller, you can kick harder
293 (4) a low stamina/health lower your max kickspeed
294 (5) todo: kicking a ball held/gained by a keeper, may damage the keeper
295 */ act (playerID,KickBall {vxy={velocity=v,direction=d},vz}) (seed,ball,team1,team2)
296 # (team1,team2) = splitAt (length team1) (unbreak1 (uneq1,new_fb,uneq2))
297 = (seed1,Free new_ball,team1,team2)
299 (uneq1,fb,uneq2) = break1 (identify_player playerID) (team1 ++ team2)
300 new_fb = {fb & stamina=new_stamina,effect=Just (KickedBall (Just new_speed))}
301 theBall = getFootball ball (team1 ++ team2)
302 skills = skillsAsList fb
303 max_v = maxVelocityBallKick fb
304 new_v = scale speed_factor (setbetween v zero max_v)
305 new_vz = scale speed_factor (setbetween vz zero max_v)
306 new_speed = {vxy={velocity=new_v,direction=new_d},vz=new_vz}
307 new_stamina = kickingPenalty fb new_v * fb.stamina
308 speed_factor = oppositeKickPenalty fb d
309 new_ball = {theBall & ballSpeed=new_speed}
310 (new_d,seed1) = new_ball_direction Kicking fb d seed
312 /** Rules for heading ball:
313 (1) heading decreases stamina, but less than kicking
314 (2) kicking is more effective towards your direction, and least effective in opposite direction
315 (3) a low stamina/health lower your max headspeed, but less than kicking
316 (4) heading is less harder than kicking, but is not effected by your length
317 (5) todo: heading a ball held/gained by a keeper, may damage the keeper (less than with kicking)
318 */ act (playerID,HeadBall {vxy={velocity=v,direction=d},vz}) (seed,ballstate,team1,team2)
319 # (team1,team2) = splitAt (length team1) (unbreak1 (uneq1,new_fb,uneq2))
320 = (seed1,Free new_ball,team1,team2)
322 (uneq1,fb,uneq2) = break1 (identify_player playerID) (team1 ++ team2)
323 skills = skillsAsList fb
324 ball = getFootball ballstate (team1 ++ team2)
325 ball_speed = ball.ballSpeed.vxy.velocity
326 max_v = maxVelocityBallHead fb ball_speed
327 new_v = setbetween v zero max_v
328 new_vz = scale 0.25 (setbetween vz zero max_v)
329 new_speed = {vxy={velocity=new_v,direction=new_d},vz=new_vz}
330 new_stamina = headingPenalty fb new_v ball_speed * fb.stamina
331 new_fb = {fb & stamina=new_stamina,effect=Just (HeadedBall (Just new_speed))}
332 new_ball = {ball & ballSpeed=new_speed}
333 (new_d,seed1) = new_ball_direction Heading fb d seed
335 /** Rules for feinting:
336 (1) you must have velocity in order to feint manouvre.
337 (2) a feint manouvre changes your position, and decreases your velocity (depends on Feinting skill)
338 */ act (playerID,Feint d) (seed,ball,team1,team2)
339 # (team1,team2) = splitAt (length team1) (unbreak1 (uneq1,new_fb,uneq2))
340 = (seed,ball,team1,team2)
342 (uneq1,fb,uneq2) = break1 (identify_player playerID) (team1 ++ team2)
343 new_stamina = (maxFatigueLossAtFeint fb) * fb.stamina
344 new_velocity = scale (fb.health * fb.stamina * (maxVelocityLossAtFeint fb)) fb.speed.velocity
345 new_speed = {fb.speed & velocity=new_velocity}
346 (leftv,rightv) = orthogonal fb.speed.direction
347 sidestep = case d of FeintLeft -> leftv; _ -> rightv
348 new_position` = move_point ((scale (toReal (maxFeintStep fb)) (toRVector sidestep))
350 (scale (dt * toReal new_velocity) (toRVector fb.speed.direction))
352 new_position = point_to_rectangle ({px=scale -0.5 flength,py=scale -0.5 fwidth},{px=scale 0.5 flength,py=scale 0.5 fwidth}) new_position`
353 new_fb = {fb & pos=new_position,speed=new_speed,stamina=new_stamina,effect=Just (Feinted d)}
355 /** Rules for Tackling
356 (1) tackling may lower the health of the victim but increases his stamina (last is because he lies on the ground the next rounds)
357 (2) tackling costs stamina
358 */ act (playerID,Tackle victimID ve) (seed,ball,team1,team2)
359 = (seed1,new_ball,team1T,team2T)
361 nrPlayersTeam1 = length team1
362 (uneq1,fb,uneq2) = break1 (identify_player playerID) (team1 ++ team2)
363 (team1N,team2N) = splitAt nrPlayersTeam1 (unbreak1 (uneq1,new_fb,uneq2))
364 (uneq1T,fbT,uneq2T) = break1 (identify_player victimID) (team1N ++ team2N)
365 (team1T,team2T) = splitAt nrPlayersTeam1 (unbreak1 (uneq1T,new_target,uneq2T))
366 new_stamina_self = maxFatigueLossAtTackle fb * fb.stamina
367 new_fb = {fb & stamina = new_stamina_self, effect = Just (Tackled victimID ve Success)}
368 target_has_ball = ballIsGainedBy victimID ball
369 (p,seed1) = nextRandomP seed
370 new_v` = min max_tackle_velocity ve
371 max_tackle_velocity = ms 10.0
372 max_ground_time = s 30.0
373 ground_frames = toInt ((((toReal new_v`) / (toReal max_tackle_velocity)) * (toReal max_ground_time)) / dt)
374 new_v = scale 0.1 new_v`
375 healthDamageTarget = (toReal new_v) * fb.health * fb.stamina * (0.5*p + 0.1) + (toReal (fbT.length-min_length))/2.0
376 new_health_target = max zero (fbT.health - healthDamageTarget)
377 new_target = {fbT & health = new_health_target, effect = Just (OnTheGround ground_frames) }
378 new_ball = if target_has_ball (Free (mkFootball fbT.pos fbT.speed)) ball
380 /** Rules for catching
381 (1) ball optains speed and distance of player
382 */ act (playerID,CatchBall) (seed,ball,team1,team2)
383 # (team1,team2) = splitAt (length team1) (unbreak1 (uneq1,new_fb,uneq2))
384 = (seed,GainedBy playerID,team1,team2)
386 (uneq1,fb,uneq2) = break1 (identify_player playerID) (team1 ++ team2)
387 new_fb = {fb & effect=Just (CaughtBall Success)}
389 new_ball_direction :: !Skill !Footballer !Angle !RandomSeed -> (!Angle,!RandomSeed)
390 new_ball_direction skill fb d seed
391 # (p1,seed) = nextRandomP seed
392 # (p2,seed) = nextRandomP seed
393 | p2 == one = (d,seed)
394 # failure = one - if (isMember skill (skillsAsList fb)) makeRandomRealisticSkilled makeRandomRealistic p2
395 # diff = scale failure (maxHeadingDeviation fb)
396 | p1 <= 0.5 = (d - diff, seed)
397 | otherwise = (d + diff, seed)
399 /** moveFootball match
400 makes the free ball move (a gained ball moves along with its player).
401 */ moveFootball :: !Match -> Match
402 moveFootball match=:{Match | theBall=Free football=:{ballSpeed={vxy={velocity=v,direction=d},vz},ballPos},theField,team1,team2,seed,lastContact,unittime}
403 = { match & theBall = Free {football & ballSpeed=new_speed,ballPos=new_ballpos}, seed = seed1, lastContact = if (isJust hit_player) hit_player lastContact }
405 old_height = ballPos.pz
406 in_the_air = old_height > zero
407 resistance = if in_the_air air_resistance surface_resistance
409 surface_movement = scale (dt * (toReal v)) (toRVector d)
410 new_speed2D = let new_v = scale resistance v in {direction = d, velocity = if (new_v <= ms 0.05) zero new_v}
411 new_vz` = if in_the_air (vz - scale dt accelleration_sec) zero
412 new_height` = ballPos.pz + m (toReal vz)
413 (new_height,new_vz) = if (in_the_air && new_height` <= zero) // the ball bounces on the field
414 (scale 0.5 (abs new_height`),let new_vz`` = scale 0.33 (abs new_vz`) in if (new_vz`` <= ms 0.8) zero new_vz``)
415 (new_height`, new_vz`)
416 new_speed` = {vxy=new_speed2D, vz=new_vz}
417 new_ballpos = {pxy=move_point surface_movement ballPos.pxy,pz=new_height}
418 all_players = team1 ++ team2
419 (hit_player,new_speed,seed1) = ballBounces new_ballpos new_speed` seed
421 // the direction of the ball changes after a bounce and its velocity may reduce in case of bouncing against a player
422 ballBounces :: !Position3D !Speed3D !RandomSeed -> (!Maybe FootballerID,!Speed3D,!RandomSeed)
423 ballBounces new_ballpos new_speed=:{vxy={velocity=v,direction=d},vz=s3d} seed
424 | hit_west_goal = (Nothing,{new_speed & vxy = {new_speed.vxy & direction = if (d <= rad pi) (d - rad (0.5*pi)) (d + rad (0.5*pi)), velocity = v}},seed)
425 | hit_east_goal = (Nothing,{new_speed & vxy = {new_speed.vxy & direction = if (d <= rad pi) (d + rad (0.5*pi)) (d - rad (0.5*pi)), velocity = v}},seed)
426 | isEmpty hit_players = (Nothing, new_speed, seed)
427 # (p1,seed) = nextRandomP seed
428 # (p2,seed) = nextRandomP seed
429 # (p3,seed) = nextRandomP seed
430 | otherwise = (Just (hd hit_players),{vxy = {direction = rad (p2*2.0*pi), velocity = scale p3 v}, vz=scale p1 s3d},seed)
432 half_length = scale 0.5 theField.flength
433 goal_pole_r = scale 0.5 goal_pole_width
434 (northPole,southPole) = goal_poles theField
435 hit_west_goal = againstGoalWestNorthPole || againstGoalWestSouthPole || againstGoalWestPoleUpper
436 hit_east_goal = againstGoalEastNorthPole || againstGoalEastSouthPole || againstGoalEastPoleUpper
437 hit_players = [playerID \\ fb=:{length,playerID} <- all_players | inRadiusOfFootballer new_ballpos.pxy fb && length >= new_ballpos.pz]
438 againstGoalWestNorthPole = inCircleRadiusOfPosition new_ballpos goal_pole_r goal_height {px = ~half_length, py = northPole + goal_pole_r}
439 againstGoalWestSouthPole = inCircleRadiusOfPosition new_ballpos goal_pole_r goal_height {px = ~half_length, py = southPole - goal_pole_r}
440 againstGoalEastNorthPole = inCircleRadiusOfPosition new_ballpos goal_pole_r goal_height {px = half_length, py = northPole + goal_pole_r}
441 againstGoalEastSouthPole = inCircleRadiusOfPosition new_ballpos goal_pole_r goal_height {px = half_length, py = southPole - goal_pole_r}
442 againstGoalWestPoleUpper = (isbetween new_ballpos.pxy.py (southPole - goal_pole_r) (northPole + goal_pole_r))
444 (isbetween new_ballpos.pz goal_height (goal_height+goal_pole_width))
446 (new_ballpos.pxy.px <= ~half_length)
447 againstGoalEastPoleUpper = (isbetween new_ballpos.pxy.py (southPole - goal_pole_r) (northPole + goal_pole_r))
449 (isbetween new_ballpos.pz goal_height (goal_height+goal_pole_width))
451 (new_ballpos.pxy.px >= half_length)
452 inCircleRadiusOfPosition {pxy,pz} r zr pos
453 = dist pxy pos <= r && pz <= zr
458 /** advanceTime match
459 decreases the time to play with unittime.
460 */ advanceTime :: !Match -> Match
461 advanceTime match=:{Match | playingTime, unittime}
462 = {Match | match & playingTime = max zero (playingTime - minutes unittime)}
464 /* Attribute altering functions depending on angles:
466 Angle :: between zero and pi, how much the player is running backwards (pi is backwards).
467 Angle :: between zero and pi, the difference between the desired angle and the angle the player previously ran to.
469 alter_stamina :: !FootballState !Footballer !Angle !Angle -> Stamina
470 alter_stamina ballState fb angleDifficulty angleDifference
471 | velocity <= rfv // increase stamina
472 | stamina < MinimumFatigue = MinimumFatigue
473 | otherwise = stamina^0.8
474 | otherwise = fatigue * factor
476 velocity = fb.speed.velocity
479 rfv = restore_stamina_velocity (ballIsGainedBy fb.playerID ballState) (skillsAsList fb) angleDifficulty angleDifference
480 diff = velocity - rfv
481 fv = if (diff >= ms 6.0) (stamina^(stamina^(1.6 + 0.02 * toReal length)))
482 (if (diff >= ms 4.0) (stamina^( 1.5 + 0.01 * toReal length))
483 (if (diff >= ms 2.0) (stamina^( 1.4 - 0.01 * toReal length))
484 (stamina^( 1.3 - 0.02 * toReal length))))
485 factor = one - (toReal angleDifficulty)/(4.0*pi)
486 fatigue = if (stamina > MaximumFatigue) MaximumFatigue fv
488 restore_stamina_velocity :: !Bool ![Skill] !Angle !Angle -> Velocity
489 restore_stamina_velocity gained_ball skills angleDifficulty angleDifference
490 | gained_ball = scale ( one / if (isMember Running skills) 1.6 2.6) max_v
491 | isMember Running skills = scale ((one / if (isMember Dribbling skills) 2.0 3.0) * 1.22) max_v
492 | otherwise = scale ( one / if (isMember Dribbling skills) 2.0 3.0) max_v
494 max_v = maxVelocity skills angleDifficulty angleDifference
496 maxVelocity :: ![Skill] !Angle !Angle -> Velocity
497 maxVelocity skills angleDifficulty angleDifference
498 = scale (dribblingPenalty * runningPenalty) base_velocity
500 base_velocity = ms 10.0
501 dribblingPenalty = if (isMember Dribbling skills) 0.95 0.85
502 runningPenalty = if (isMember Running skills) 1.0 0.85
504 MinimumFatigue :== 0.05
505 MaximumFatigue :== 0.985
508 /** The functions below defines the penalty factor: values between 0.0 and 1.0 that define the loss of an attribute of an action.
510 :: PenaltyFactor :== Real // a value between 0.0 and 1.0
512 kickingPenalty :: !Footballer !Velocity -> PenaltyFactor
513 kickingPenalty fb new_v = 1.0 - (if (isMember Kicking (skillsAsList fb)) 0.3 0.6) * ((toReal new_v)/(toReal max_v))^2.0
515 max_v = maxVelocityBallKick fb
517 headingPenalty :: !Footballer !Velocity !Velocity -> PenaltyFactor
518 headingPenalty fb new_v ball_v = 1.0 - (if (isMember Heading (skillsAsList fb)) 0.08 0.13) * ((toReal new_v)/(toReal max_v))^2.0
520 max_v = maxVelocityBallHead fb ball_v
522 maxFatigueLossAtTackle :: !Footballer -> PenaltyFactor
523 maxFatigueLossAtTackle fb = if (isMember Tackling (skillsAsList fb)) 0.99 0.9
525 maxFatigueLossAtFeint :: !Footballer -> PenaltyFactor
526 maxFatigueLossAtFeint fb = if (isMember Feinting (skillsAsList fb)) 0.92 0.77
528 maxVelocityLossAtFeint :: !Footballer -> PenaltyFactor
529 maxVelocityLossAtFeint fb = if (isMember Feinting (skillsAsList fb)) 0.99 0.75
531 oppositeKickPenalty :: !Footballer !Angle -> PenaltyFactor
532 oppositeKickPenalty fb kick_to = 1.0 - toReal (scale (skillPenaltyFactor/pi) (angleHowFarFromPi angle))
534 angle = abs (fb.nose - kick_to)
535 skills = skillsAsList fb
536 skillPenaltyFactor = if (isAllMember [Rotating,Kicking] skills) 0.3
537 (if (isAnyMember [Rotating,Kicking] skills) 0.5
540 angleHowFarFromPi :: !Angle -> Angle
542 | a` > rad pi = rad (2.0*pi) - a`
547 angleHowFarFromAngle :: !Angle !Angle -> Angle
548 angleHowFarFromAngle a b
550 | a` - b` > rad pi = b` - a` + rad (2.0*pi)
551 | otherwise = a` - b`
553 | b` - a` > rad pi = a` - b` + rad (2.0*pi)
554 | otherwise = b` - a`