gym_soccer/envs/soccer_env.py (112 lines of code) (raw):

import os, subprocess, time, signal import gym from gym import error, spaces from gym import utils from gym.utils import seeding try: import hfo_py except ImportError as e: raise error.DependencyNotInstalled("{}. (HINT: you can install HFO dependencies with 'pip install gym[soccer].)'".format(e)) import logging logger = logging.getLogger(__name__) class SoccerEnv(gym.Env, utils.EzPickle): metadata = {'render.modes': ['human']} def __init__(self): self.viewer = None self.server_process = None self.server_port = None self.hfo_path = hfo_py.get_hfo_path() self._configure_environment() self.env = hfo_py.HFOEnvironment() self.env.connectToServer(config_dir=hfo_py.get_config_path()) self.observation_space = spaces.Box(low=-1, high=1, shape=(self.env.getStateSize())) # Action space omits the Tackle/Catch actions, which are useful on defense self.action_space = spaces.Tuple((spaces.Discrete(3), spaces.Box(low=0, high=100, shape=1), spaces.Box(low=-180, high=180, shape=1), spaces.Box(low=-180, high=180, shape=1), spaces.Box(low=0, high=100, shape=1), spaces.Box(low=-180, high=180, shape=1))) self.status = hfo_py.IN_GAME def __del__(self): self.env.act(hfo_py.QUIT) self.env.step() os.kill(self.server_process.pid, signal.SIGINT) if self.viewer is not None: os.kill(self.viewer.pid, signal.SIGKILL) def _configure_environment(self): """ Provides a chance for subclasses to override this method and supply a different server configuration. By default, we initialize one offense agent against no defenders. """ self._start_hfo_server() def _start_hfo_server(self, frames_per_trial=500, untouched_time=100, offense_agents=1, defense_agents=0, offense_npcs=0, defense_npcs=0, sync_mode=True, port=6000, offense_on_ball=0, fullstate=True, seed=-1, ball_x_min=0.0, ball_x_max=0.2, verbose=False, log_game=False, log_dir="log"): """ Starts the Half-Field-Offense server. frames_per_trial: Episodes end after this many steps. untouched_time: Episodes end if the ball is untouched for this many steps. offense_agents: Number of user-controlled offensive players. defense_agents: Number of user-controlled defenders. offense_npcs: Number of offensive bots. defense_npcs: Number of defense bots. sync_mode: Disabling sync mode runs server in real time (SLOW!). port: Port to start the server on. offense_on_ball: Player to give the ball to at beginning of episode. fullstate: Enable noise-free perception. seed: Seed the starting positions of the players and ball. ball_x_[min/max]: Initialize the ball this far downfield: [0,1] verbose: Verbose server messages. log_game: Enable game logging. Logs can be used for replay + visualization. log_dir: Directory to place game logs (*.rcg). """ self.server_port = port cmd = self.hfo_path + \ " --headless --frames-per-trial %i --untouched-time %i --offense-agents %i"\ " --defense-agents %i --offense-npcs %i --defense-npcs %i"\ " --port %i --offense-on-ball %i --seed %i --ball-x-min %f"\ " --ball-x-max %f --log-dir %s"\ % (frames_per_trial, untouched_time, offense_agents, defense_agents, offense_npcs, defense_npcs, port, offense_on_ball, seed, ball_x_min, ball_x_max, log_dir) if not sync_mode: cmd += " --no-sync" if fullstate: cmd += " --fullstate" if verbose: cmd += " --verbose" if not log_game: cmd += " --no-logging" print('Starting server with command: %s' % cmd) self.server_process = subprocess.Popen(cmd.split(' '), shell=False) time.sleep(10) # Wait for server to startup before connecting a player def _start_viewer(self): """ Starts the SoccerWindow visualizer. Note the viewer may also be used with a *.rcg logfile to replay a game. See details at https://github.com/LARG/HFO/blob/master/doc/manual.pdf. """ cmd = hfo_py.get_viewer_path() +\ " --connect --port %d" % (self.server_port) self.viewer = subprocess.Popen(cmd.split(' '), shell=False) def _step(self, action): self._take_action(action) self.status = self.env.step() reward = self._get_reward() ob = self.env.getState() episode_over = self.status != hfo_py.IN_GAME return ob, reward, episode_over, {} def _take_action(self, action): """ Converts the action space into an HFO action. """ action_type = ACTION_LOOKUP[action[0]] if action_type == hfo_py.DASH: self.env.act(action_type, action[1], action[2]) elif action_type == hfo_py.TURN: self.env.act(action_type, action[3]) elif action_type == hfo_py.KICK: self.env.act(action_type, action[4], action[5]) else: print('Unrecognized action %d' % action_type) self.env.act(hfo_py.NOOP) def _get_reward(self): """ Reward is given for scoring a goal. """ if self.status == hfo_py.GOAL: return 1 else: return 0 def _reset(self): """ Repeats NO-OP action until a new episode begins. """ while self.status == hfo_py.IN_GAME: self.env.act(hfo_py.NOOP) self.status = self.env.step() while self.status != hfo_py.IN_GAME: self.env.act(hfo_py.NOOP) self.status = self.env.step() return self.env.getState() def _render(self, mode='human', close=False): """ Viewer only supports human mode currently. """ if close: if self.viewer is not None: os.kill(self.viewer.pid, signal.SIGKILL) else: if self.viewer is None: self._start_viewer() ACTION_LOOKUP = { 0 : hfo_py.DASH, 1 : hfo_py.TURN, 2 : hfo_py.KICK, 3 : hfo_py.TACKLE, # Used on defense to slide tackle the ball 4 : hfo_py.CATCH, # Used only by goalie to catch the ball }