import numpy as np class FeatureEncoder: """ Feature encoder referred to [football-pairs](https://github.com/seungeunrho/football-paris/blob/main/encoders/encoder_basic.py) """ def __init__(self): self.active = -1 self.player_pos_x, self.player_pos_y = 0, 0 self.n_player = 10 def get_feature_dims(self): dims = { 'player': 36, 'ball': 18, 'left_team': 7, 'left_team_closest': 7, 'right_team': 7, 'right_team_closest': 7, } return dims def encode(self, obs): player_num = obs['active'] player_pos_x, player_pos_y = obs['left_team'][player_num] player_direction = np.array(obs['left_team_direction'][player_num]) player_speed = np.linalg.norm(player_direction) player_role = obs['left_team_roles'][player_num] player_role_onehot = np.eye(self.n_player)[player_role] player_tired = obs['left_team_tired_factor'][player_num] is_dribbling = obs['sticky_actions'][9] is_sprinting = obs['sticky_actions'][8] ball_x, ball_y, ball_z = obs['ball'] ball_x_relative = ball_x - player_pos_x ball_y_relative = ball_y - player_pos_y ball_x_speed, ball_y_speed, _ = obs['ball_direction'] ball_distance = np.linalg.norm([ball_x_relative, ball_y_relative]) ball_speed = np.linalg.norm([ball_x_speed, ball_y_speed]) ball_owned = 0.0 if obs['ball_owned_team'] == -1: ball_owned = 0.0 else: ball_owned = 1.0 ball_owned_by_us = 0.0 if obs['ball_owned_team'] == 0: ball_owned_by_us = 1.0 elif obs['ball_owned_team'] == 1: ball_owned_by_us = 0.0 else: ball_owned_by_us = 0.0 ball_which_zone = self._encode_ball_which_zone(ball_x, ball_y) if ball_distance > 0.03: ball_far = 1.0 else: ball_far = 0.0 avail = self._get_avail(obs, ball_distance) player_state = np.concatenate( ( avail[2:], obs['left_team'][player_num], player_direction * 100, [player_speed * 100], player_role_onehot, [ball_far, player_tired, is_dribbling, is_sprinting] ) ) ball_state = np.concatenate( ( np.array(obs['ball']), np.array(ball_which_zone), np.array([ball_x_relative, ball_y_relative]), np.array(obs['ball_direction']) * 20, np.array([ball_speed * 20, ball_distance, ball_owned, ball_owned_by_us]) ) ) obs_left_team = np.delete(obs['left_team'], player_num, axis=0) obs_left_team_direction = np.delete(obs['left_team_direction'], player_num, axis=0) left_team_relative = obs_left_team left_team_distance = np.linalg.norm(left_team_relative - obs['left_team'][player_num], axis=1, keepdims=True) left_team_speed = np.linalg.norm(obs_left_team_direction, axis=1, keepdims=True) left_team_tired = np.delete(obs['left_team_tired_factor'], player_num, axis=0).reshape(-1, 1) left_team_state = np.concatenate((left_team_relative*2, obs_left_team_direction*100, left_team_speed*100, \ left_team_distance*2, left_team_tired), axis=1) left_closest_idx = np.argmin(left_team_distance) left_closest_state = left_team_state[left_closest_idx] obs_right_team = np.array(obs['right_team']) obs_right_team_direction = np.array(obs['right_team_direction']) right_team_distance = np.linalg.norm(obs_right_team - obs['left_team'][player_num], axis=1, keepdims=True) right_team_speed = np.linalg.norm(obs_right_team_direction, axis=1, keepdims=True) right_team_tired = np.array(obs['right_team_tired_factor']).reshape(-1, 1) right_team_state = np.concatenate((obs_right_team*2, obs_right_team_direction*100, right_team_speed*100, \ right_team_distance*2, right_team_tired), axis=1) right_closest_idx = np.argmin(right_team_distance) right_closest_state = right_team_state[right_closest_idx] state_dict = { "player": player_state, "ball": ball_state, "left_team": left_team_state, "left_closest": left_closest_state, "right_team": right_team_state, "right_closest": right_closest_state, "avail": avail } return state_dict def _get_avail(self, obs, ball_distance): avail = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] NO_OP, MOVE, LONG_PASS, HIGH_PASS, SHORT_PASS, SHOT, SPRINT, RELEASE_MOVE, \ RELEASE_SPRINT, SLIDE, DRIBBLE, RELEASE_DRIBBLE = 0, 1, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 if obs['ball_owned_team'] == 1: # opponents owning ball avail[LONG_PASS], avail[HIGH_PASS], avail[SHORT_PASS], avail[SHOT], avail[DRIBBLE] = 0, 0, 0, 0, 0 elif obs['ball_owned_team'] == -1 and ball_distance > 0.03 and obs['game_mode' ] == 0: # Ground ball and far from me avail[LONG_PASS], avail[HIGH_PASS], avail[SHORT_PASS], avail[SHOT], avail[DRIBBLE] = 0, 0, 0, 0, 0 else: # my team owning ball avail[SLIDE] = 0 # Dealing with sticky actions sticky_actions = obs['sticky_actions'] if sticky_actions[8] == 0: # sprinting avail[RELEASE_SPRINT] = 0 if sticky_actions[9] == 1: # dribbling avail[SLIDE] = 0 else: avail[RELEASE_DRIBBLE] = 0 if np.sum(sticky_actions[:8]) == 0: avail[RELEASE_MOVE] = 0 # if too far, no shot ball_x, ball_y, _ = obs['ball'] if ball_x < 0.64 or ball_y < -0.27 or 0.27 < ball_y: avail[SHOT] = 0 elif (0.64 <= ball_x and ball_x <= 1.0) and (-0.27 <= ball_y and ball_y <= 0.27): avail[HIGH_PASS], avail[LONG_PASS] = 0, 0 if obs['game_mode'] == 2 and ball_x < -0.7: # Our GoalKick avail = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] avail[LONG_PASS], avail[HIGH_PASS], avail[SHORT_PASS] = 1, 1, 1 return np.array(avail) elif obs['game_mode'] == 4 and ball_x > 0.9: # Our CornerKick avail = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] avail[LONG_PASS], avail[HIGH_PASS], avail[SHORT_PASS] = 1, 1, 1 return np.array(avail) elif obs['game_mode'] == 6 and ball_x > 0.6: # Our PenaltyKick avail = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] avail[SHOT] = 1 return np.array(avail) return np.array(avail) def _encode_ball_which_zone(self, ball_x, ball_y): MIDDLE_X, PENALTY_X, END_X = 0.2, 0.64, 1.0 LEFT_PENALTY, LEFT_HALF, HALF, RIGHT_PENALTY, RIGHT_HALF, OTHERS = 0, 1, 2, 3, 4, 5 PENALTY_Y, END_Y = 0.27, 0.42 res = np.eye(6) if (-END_X <= ball_x and ball_x < -PENALTY_X) and (-PENALTY_Y < ball_y and ball_y < PENALTY_Y): return res[LEFT_PENALTY] elif (-END_X <= ball_x and ball_x < -MIDDLE_X) and (-END_Y < ball_y and ball_y < END_Y): return res[LEFT_HALF] elif (-MIDDLE_X <= ball_x and ball_x <= MIDDLE_X) and (-END_Y < ball_y and ball_y < END_Y): return res[HALF] elif (PENALTY_X < ball_x and ball_x <= END_X) and (-PENALTY_Y < ball_y and ball_y < PENALTY_Y): return res[RIGHT_PENALTY] elif (MIDDLE_X < ball_x and ball_x <= END_X) and (-END_Y < ball_y and ball_y < END_Y): return res[RIGHT_HALF] else: return res[OTHERS]