initial framework added
[fp1415-soccerfun.git] / src / StdReferee / Umpire.icl
1 implementation module Umpire
2
3 import RefereeFunctions
4
5 umpire :: !FootballField -> Referee
6 umpire field = { name = "Umpire"
7 , brain = {memory = Nothing, ai = brain field}
8 , refActionPics = []
9 }
10
11 :: Memory = { total_time :: !PlayingTime // the playing time
12 , current_half :: !Half // the current playing half (initially FirstHalf)
13 , placing1 :: !Displacements // the placing of team1 at the start of the match
14 , placing2 :: !Displacements // the placing of team2 at the start of the match
15 , forbidden :: !Maybe Home // players from (Just home) are not allowed to play the ball
16 , offside :: ![FootballerID] // players that are offside and thus should not play the ball
17 , reprimands :: !AssocList FootballerID [Reprimand] // the reprimands collected by each player
18 , situation :: !Maybe (Situation,Pending) // the situation of the game
19 }
20 :: Situation = IsCenterKick | IsCornerKick | IsDirectFreeKick | IsGoalKick | IsPenaltyKick | IsThrowIn | IsKeeperBall !Home
21 :: Pending = IsPending !Deadline | IsExecuted
22 :: Deadline :== Seconds
23
24 brain :: !FootballField !(!RefereeInput,!(!Maybe Memory,!RandomSeed)) -> (!RefereeOutput,!(!Maybe Memory,!RandomSeed))
25 // Referee enters the game, so (s)he needs to be filled in on the game details:
26 brain field (input=:{RefereeInput | team1,team2,playingTime},(Nothing,seed))
27 | team1_ok && team2_ok = ([CenterKick West,DisplacePlayers ds], (Just memory,seed))
28 | otherwise = ([TellMessage wrong_team,GameOver], (Nothing, seed))
29 where
30 team1_ok = isValidTeam team1 && allPlayersAtHome field West team1
31 team2_ok = isValidTeam team2 && allPlayersAtHome field East team2
32 wrong_team = if (not team1_ok) (nameOf team1 +++ " is invalid. ") "" +++
33 if (not team2_ok) (nameOf team2 +++ " is invalid. ") ""
34 memory = { total_time = playingTime
35 , current_half = FirstHalf
36 , placing1 = displacements team1
37 , placing2 = displacements team2
38 , forbidden = Just East
39 , offside = []
40 , reprimands = []
41 , situation = Just (IsCenterKick,IsPending center_kick_deadline)
42 }
43 ds = center_kick_positions field West memory
44
45 // Referee stops the game when a team has less than 7 players:
46 brain field (input=:{RefereeInput | team1, team2},(Just memory,seed))
47 | too_few_players = ([GameCancelled winner,TellMessage ("Game cancelled." <+++ msg)],(Just memory,seed))
48 where
49 too_few_players = too_few_team1 || too_few_team2
50 too_few_team1 = length team1 < 7
51 too_few_team2 = length team2 < 7
52 (winner,msg) = if (too_few_team1 && too_few_team2) (Nothing, "Both teams have less than 7 players left.")
53 (if too_few_team1 (Just East, nameOf team1 +++> " has less than 7 players left.")
54 (Just West, nameOf team2 +++> " has less than 7 players left.")
55 )
56
57 // Referee checks whether the game is at the first half, second half, or completely over:
58 brain field (input=:{RefereeInput | playingTime},(Just memory=:{current_half,total_time},seed))
59 | playingTime <= zero = ([GameOver], (Just memory_no_offside,seed))
60 | current_half <> half = ([EndHalf,CenterKick West,DisplacePlayers ds], (Just memory_2nd_half, seed))
61 where
62 half = half_of_game total_time input
63 memory_no_offside = { memory & situation = Nothing, offside = [] }
64 memory_2nd_half = { memory_no_offside & situation = Just (IsCenterKick,IsPending center_kick_deadline), current_half = half, forbidden = Just East }
65 ds = center_kick_positions field West memory_2nd_half
66
67 // Referee checks whether a team has scored a goal:
68 brain field (input,(Just memory=:{current_half},seed))
69 | isJust in_goal = ([Goal scoring_team,CenterKick (other scoring_team),DisplacePlayers ds],(Just memory_goal,seed))
70 where
71 in_goal = ball_in_goal field input
72 goal = fromJust in_goal
73 scoring_team = other goal
74 ds = center_kick_positions field goal memory
75 memory_goal = { memory & forbidden = Just scoring_team, offside = [], situation = Just (IsCenterKick,IsPending center_kick_deadline) }
76
77 // Referee checks whether the keeper has caught the ball.
78 // In that case the keeper is obliged to play the ball within 6 seconds.
79 brain field (input=:{RefereeInput | team1, team2},(Just memory=:{situation},seed))
80 | keeper_catches_ball = ([],(Just memory_keeper_ball,seed))
81 where
82 keeper_catches_ball = not (isEmpty catchers) && catcher.playerNr == 1 && ball_was_uncaught
83 ball_was_uncaught = isNothing situation || isJust situation && fst (fromJust situation) <> IsKeeperBall team_of_catcher
84 catchers = [playerID \\ {playerID,effect=Just action} <- team1 ++ team2 | isCaughtBall action]
85 catcher = hd catchers
86 team_of_catcher = home_of_player catcher input
87 memory_keeper_ball = { memory & situation = Just (IsKeeperBall team_of_catcher,IsPending keeper_deadline)
88 , forbidden = Just (other team_of_catcher)
89 , offside = []
90 }
91
92 // Referee checks whether the ball has exited the football field.
93 // If the last player who played the ball is not known, then the ball is thrown in by the team playing on the half of the field where the ball left the field.
94 brain field (input=:{RefereeInput | lastContact},(Just memory=:{current_half},seed))
95 | isJust ball_exit = if is_throw_in ([ThrowIn (other team) exit_pos], (Just {memory_enter & situation = Just (IsThrowIn, IsPending restart_deadline)},seed))
96 (if is_corner ([Corner (other team) edge], (Just {memory_enter & situation = Just (IsCornerKick,IsPending restart_deadline)},seed))
97 ([GoalKick (other team)], (Just {memory_enter & situation = Just (IsGoalKick, IsPending keeper_deadline )},seed))
98 )
99 where
100 ball_exit = ball_left_field_at field input
101 exit_pos = fromJust ball_exit
102 team = case lastContact of
103 Just fID = home_of_player fID input
104 unknown = if (home == West) (if (current_half == FirstHalf) West East) (if (current_half == FirstHalf) East West)
105 is_throw_in = abs exit_pos.py >= scale 0.5 field.fwidth
106 is_corner = exit_pos.px <= scale -0.5 field.flength && team == West || exit_pos.px >= scale 0.5 field.flength && team == East
107 home = if (exit_pos.px < zero) West East
108 edge = if (exit_pos.py > zero) North South
109 memory_enter = { memory & forbidden = Just team, offside = [] }
110
111 // Referee checks whether the ball is not played correctly:
112 brain field (input=:{RefereeInput | team1, team2, playingHalf},(Just memory=:{forbidden},seed))
113 | improper_team = ([OwnBallIllegally ball_player,DirectFreeKick (other team) player_pos,DisplacePlayers ds:map (ReprimandPlayer ball_player) new_reprimands]
114 ,(Just memory_illegal,seed)
115 )
116 where
117 improper_team = isJust forbidden && ball_is_played && team_of_ball_player == team
118 ball_players = [(playerID,pos) \\ {playerID,pos,effect=Just action} <- team1 ++ team2 | isBallAction action]
119 ball_is_played = not (isEmpty ball_players)
120 (ball_player,player_pos)= hd ball_players
121 team_of_ball_player = home_of_player ball_player input
122 team = fromJust forbidden
123 ds = direct_free_kick_positions team player_pos input
124 (new_reprimands,memory_reprimanded)
125 = reprimand_player ball_player Warning { memory & offside = [], situation = Just (IsDirectFreeKick,IsPending free_kick_deadline) }
126 expel = isMember RedCard new_reprimands
127 memory_illegal = if expel (expel_player ball_player team_of_ball_player playingHalf memory_reprimanded) memory_reprimanded
128
129 // Referee checks whether the hands-rule has been offended:
130 brain field (input=:{RefereeInput | team1, team2, playingHalf},(Just memory=:{current_half},seed))
131 | hands_offense = ([Hands catcher,DirectFreeKick (other team) catcher_pos,DisplacePlayers ds:map (ReprimandPlayer catcher) new_reprimands]
132 ,(Just memory_hands,seed)
133 )
134 where
135 hands_offense = ball_is_caught && (catcher.playerNr <> 1 || not (inPenaltyArea field team catcher_pos))
136 catchers = [(playerID,pos) \\ {playerID,pos,effect=Just action} <- team1 ++ team2 | isCaughtBall action]
137 (catcher,catcher_pos) = hd catchers
138 ball_is_caught = not (isEmpty catchers)
139 team = home_of_player catcher input
140 ds = direct_free_kick_positions team catcher_pos input
141 (new_reprimands,memory_reprimanded)
142 = reprimand_player catcher YellowCard { memory & forbidden = Just team, offside = [], situation = Just (IsDirectFreeKick,IsPending free_kick_deadline) }
143 expel = isMember RedCard new_reprimands
144 memory_hands = if expel (expel_player catcher team playingHalf memory_reprimanded) memory_reprimanded
145
146 // Referee checks whether the offside-rule has been offended:
147 brain field (input=:{RefereeInput | team1, team2},(Just memory=:{offside},seed))
148 | offside_offense = ([Offside offender,DirectFreeKick (other team) player_pos,DisplacePlayers ds],(Just memory_offside_lifted,seed)) // this should really be an indirect free kick, but that is not implemented yet
149 where
150 offside_offense = ball_is_played && isMember offender offside // offside is activated by a player in offside position playing the ball
151 ball_players = [(playerID,pos) \\ {playerID,pos,effect=Just action} <- team1 ++ team2 | isPlayBallAction action]
152 ball_is_played = not (isEmpty ball_players)
153 (offender,player_pos) = hd ball_players
154 team = home_of_player offender input
155 ds = direct_free_kick_positions team player_pos input
156 memory_offside_lifted = { memory & forbidden = Just team, offside = [], situation = Just (IsDirectFreeKick,IsPending free_kick_deadline) }
157
158 // Referee checks whether a team is passive:
159 brain field (input=:{RefereeInput | theBall, team1, team2, playingHalf},(Just memory=:{forbidden=Just team,situation=Just (state,IsPending dt)},seed))
160 | passivity = ([TellMessage msg,DirectFreeKick team ball.ballPos.pxy,DisplacePlayers ds],(Just memory_passive,seed))
161 where
162 passivity = dt < zero
163 memory_passive = { memory & forbidden = Just (other team), offside = [], situation = Just (IsDirectFreeKick,IsPending free_kick_deadline)}
164 ball = getFootball theBall (team1 ++ team2)
165 ds = direct_free_kick_positions (other team) ball.ballPos.pxy input
166 msg = "Passive play by " <+++ nameOf (if (team == West && playingHalf == FirstHalf || team == East && playingHalf == SecondHalf) team2 team1)
167
168 // Referee checks the status of the rules and remains silent to let the game continue:
169 brain field (input=:{RefereeInput | theBall, team1, team2},(Just memory=:{situation,forbidden},seed))
170 # memory = decrease_pending_time input memory
171 # memory = if ball_is_played { memory & situation = new_situation } memory
172 # memory = if (ball_is_played && not no_offside_situation) { memory & offside = at_offside } memory
173 # memory = if (ball_is_played && lift_forbidden_rule) { memory & forbidden = Nothing } memory
174 = ([],(Just memory,seed))
175 where
176 new_situation = if (isJust situation) (case pending of
177 IsPending _ = Just (state,IsExecuted)
178 _ = Nothing
179 ) Nothing
180 no_offside_situation = isJust situation && isMember state [IsCornerKick,IsGoalKick,IsThrowIn]
181 lift_forbidden_rule = isJust forbidden && team_of_ball_player == other forbidden_team
182 (state,pending) = fromJust situation
183 forbidden_team = fromJust forbidden
184 ball_players = [playerID \\ {playerID,effect=Just action} <- team1 ++ team2 | isBallAction action]
185 ball_is_played = not (isEmpty ball_players)
186 ball_player = hd ball_players
187 team_of_ball_player = home_of_player ball_player input
188 at_offside = [playerID \\ (playerID,_) <- players_in_offside_position field team_of_ball_player input | playerID <> ball_player]
189
190 decrease_pending_time :: !RefereeInput !Memory -> Memory
191 decrease_pending_time input=:{RefereeInput | unittime} memory=:{situation = Just (state,IsPending dt)}
192 = { memory & situation = Just (state,IsPending (dt - unittime)) }
193 decrease_pending_time _ memory
194 = memory
195
196 expel_player :: !FootballerID !Home !Half !Memory -> Memory
197 expel_player player team half memory=:{placing1,placing2}
198 | team == West && half == FirstHalf || team == East && half == SecondHalf
199 = { memory & placing1 = deletekeyvalue player placing1 }
200 | otherwise = { memory & placing2 = deletekeyvalue player placing2 }
201
202 reprimand_player :: !FootballerID !Reprimand !Memory -> (![Reprimand],!Memory)
203 reprimand_player player reprimand memory=:{reprimands}
204 # new_reprimands = [reprimand]
205 # new_reprimands = if (length (filter ((==) Warning) (new_reprimands ++ player_reprimands)) >= 3) (new_reprimands ++ [YellowCard]) new_reprimands
206 # new_reprimands = if (length (filter ((==) YellowCard) (new_reprimands ++ player_reprimands)) >= 2) (new_reprimands ++ [RedCard]) new_reprimands
207 = (new_reprimands, {memory & reprimands = addkeyvalue (player,player_reprimands ++ new_reprimands) reprimands})
208 where
209 player_reprimands = lookupd [] player reprimands
210
211 home_of_player :: !FootballerID !RefereeInput -> Home
212 home_of_player player {RefereeInput | playingHalf,team1}
213 | sameClub player (hd team1).playerID
214 = if (playingHalf == FirstHalf) West East
215 | otherwise = if (playingHalf == FirstHalf) East West
216
217 isPlayBallAction :: !FootballerEffect -> Bool
218 isPlayBallAction action = isKickedBall action || isHeadedBall action
219
220 isBallAction :: !FootballerEffect -> Bool
221 isBallAction action = isKickedBall action || isHeadedBall action || isGainedBall action || isCaughtBall action
222
223 center_kick_positions :: !FootballField !Home !Memory -> Displacements
224 center_kick_positions field home_kicking_off {placing1,placing2,current_half}
225 | home_kicking_off == West = kick_off positions1 ++ map repell_from_center positions2
226 | otherwise = map repell_from_center positions1 ++ kick_off positions2
227 where
228 (positions1,positions2) = if (current_half == FirstHalf) (placing1,placing2)
229 ([(playerID,mirror field pos) \\ (playerID,pos) <- placing2]
230 ,[(playerID,mirror field pos) \\ (playerID,pos) <- placing1]
231 )
232 center = zero
233 repell_from_center = \(player,pos) -> (player,repell radius_centre_circle center pos)
234 attract_to_center = \(player,pos) -> (player,attract (m 0.5) center pos)
235 kick_off placement = map attract_to_center closest ++ map repell_from_center others
236 where
237 sorted = sortBy (\(_,pos1) (_,pos2) -> dist pos1 center < dist pos2 center) placement
238 (closest,others) = splitAt 2 sorted
239
240 /** direct_free_kick_positions home pos input:
241 move players of @home away from @pos, and attract the closest fielder of (other @home) to @pos.
242 */
243 direct_free_kick_positions :: !Home !Position !RefereeInput -> Displacements
244 direct_free_kick_positions team free_kick_pos input=:{RefereeInput | team1, team2, playingHalf}
245 = [attract_kicker : push_away_offenders]
246 where
247 (offenders,free_kickers)= if (team == West && playingHalf == FirstHalf || team == East && playingHalf == SecondHalf) (team1,team2) (team2,team1)
248 push_away_offenders = map (\{playerID,pos} -> (playerID,repell repell_distance free_kick_pos pos)) offenders
249 closest_player = snd (hd (sortBy (\(d1,_) (d2,_) -> d1 < d2) [(dist free_kick_pos player,player) \\ player <- free_kickers | isFielder player]))
250 attract_kicker = (closest_player.playerID,attract (m 1.0) free_kick_pos closest_player.pos)
251
252 keeper_deadline :== s 6.0
253 center_kick_deadline :== s 1.0
254 free_kick_deadline :== s 1.0
255 restart_deadline :== s 20.0
256
257 instance == Situation where == IsCenterKick IsCenterKick = True
258 == IsCornerKick IsCornerKick = True
259 == IsDirectFreeKick IsDirectFreeKick = True
260 == IsGoalKick IsGoalKick = True
261 == IsPenaltyKick IsPenaltyKick = True
262 == IsThrowIn IsThrowIn = True
263 == (IsKeeperBall t1) (IsKeeperBall t2) = t1 == t2
264 == _ _ = False
265 instance == Pending where == (IsPending t1) (IsPending t2) = t1 == t2
266 == IsExecuted IsExecuted = True
267 == _ _ = False
268 instance toString Situation where toString IsCenterKick = "IsCenterKick"
269 toString IsCornerKick = "IsCornerKick"
270 toString IsDirectFreeKick = "IsDirectFreeKick"
271 toString IsGoalKick = "IsGoalKick"
272 toString IsPenaltyKick = "IsPenaltyKick"
273 toString IsThrowIn = "IsThrowIn"
274 toString (IsKeeperBall h) = "(IsKeeperBall " <+++ h <+++ ")"
275 instance toString Pending where toString (IsPending t) = "(IsPending " <+++ t <+++ ")"
276 toString IsExecuted = "IsExecuted"