We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Go environment does not clean up after itself
We should implement a `_close` method on the Go environment, and close all open resources. @hojonathanho mind taking?


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `gym/envs/board_game/go.py`
Content:
```
1 from gym import error
2 try:
3     import pachi_py
4 except ImportError as e:
5     # The dependency group [pachi] should match the name is setup.py.
6     raise error.DependencyNotInstalled('{}. (HINT: you may need to install the Go dependencies via "pip install gym[pachi]".)'.format(e))
7 
8 import numpy as np
9 import gym
10 from gym import spaces
11 from gym.utils import seeding
12 from six import StringIO
13 import sys
14 import six
15 
16 
17 # The coordinate representation of Pachi (and pachi_py) is defined on a board
18 # with extra rows and columns on the margin of the board, so positions on the board
19 # are not numbers in [0, board_size**2) as one would expect. For this Go env, we instead
20 # use an action representation that does fall in this more natural range.
21 
22 def _pass_action(board_size):
23     return board_size**2
24 
25 def _resign_action(board_size):
26     return board_size**2 + 1
27 
28 def _coord_to_action(board, c):
29     '''Converts Pachi coordinates to actions'''
30     if c == pachi_py.PASS_COORD: return _pass_action(board.size)
31     if c == pachi_py.RESIGN_COORD: return _resign_action(board.size)
32     i, j = board.coord_to_ij(c)
33     return i*board.size + j
34 
35 def _action_to_coord(board, a):
36     '''Converts actions to Pachi coordinates'''
37     if a == _pass_action(board.size): return pachi_py.PASS_COORD
38     if a == _resign_action(board.size): return pachi_py.RESIGN_COORD
39     return board.ij_to_coord(a // board.size, a % board.size)
40 
41 def str_to_action(board, s):
42     return _coord_to_action(board, board.str_to_coord(s.encode()))
43 
44 class GoState(object):
45     '''
46     Go game state. Consists of a current player and a board.
47     Actions are exposed as integers in [0, num_actions), which is different
48     from Pachi's internal "coord_t" encoding.
49     '''
50     def __init__(self, board, color):
51         '''
52         Args:
53             board: current board
54             color: color of current player
55         '''
56         assert color in [pachi_py.BLACK, pachi_py.WHITE], 'Invalid player color'
57         self.board, self.color = board, color
58 
59     def act(self, action):
60         '''
61         Executes an action for the current player
62 
63         Returns:
64             a new GoState with the new board and the player switched
65         '''
66         return GoState(
67             self.board.play(_action_to_coord(self.board, action), self.color),
68             pachi_py.stone_other(self.color))
69 
70     def __repr__(self):
71         return 'To play: {}\n{}'.format(six.u(pachi_py.color_to_str(self.color)), self.board.__repr__().decode())
72 
73 
74 ### Adversary policies ###
75 def make_random_policy(np_random):
76     def random_policy(curr_state, prev_state, prev_action):
77         b = curr_state.board
78         legal_coords = b.get_legal_coords(curr_state.color)
79         return _coord_to_action(b, np_random.choice(legal_coords))
80     return random_policy
81 
82 def make_pachi_policy(board, engine_type='uct', threads=1, pachi_timestr=''):
83     engine = pachi_py.PyPachiEngine(board, engine_type, six.b('threads=%d' % threads))
84 
85     def pachi_policy(curr_state, prev_state, prev_action):
86         if prev_state is not None:
87             assert engine.curr_board == prev_state.board, 'Engine internal board is inconsistent with provided board. The Pachi engine must be called consistently as the game progresses.'
88             prev_coord = _action_to_coord(prev_state.board, prev_action)
89             engine.notify(prev_coord, prev_state.color)
90             engine.curr_board.play_inplace(prev_coord, prev_state.color)
91         out_coord = engine.genmove(curr_state.color, pachi_timestr)
92         out_action = _coord_to_action(curr_state.board, out_coord)
93         engine.curr_board.play_inplace(out_coord, curr_state.color)
94         return out_action
95 
96     return pachi_policy
97 
98 
99 def _play(black_policy_fn, white_policy_fn, board_size=19):
100     '''
101     Samples a trajectory for two player policies.
102     Args:
103         black_policy_fn, white_policy_fn: functions that maps a GoState to a move coord (int)
104     '''
105     moves = []
106 
107     prev_state, prev_action = None, None
108     curr_state = GoState(pachi_py.CreateBoard(board_size), BLACK)
109 
110     while not curr_state.board.is_terminal:
111         a = (black_policy_fn if curr_state.color == BLACK else white_policy_fn)(curr_state, prev_state, prev_action)
112         next_state = curr_state.act(a)
113         moves.append((curr_state, a, next_state))
114 
115         prev_state, prev_action = curr_state, a
116         curr_state = next_state
117 
118     return moves
119 
120 
121 class GoEnv(gym.Env):
122     '''
123     Go environment. Play against a fixed opponent.
124     '''
125     metadata = {"render.modes": ["human", "ansi"]}
126 
127     def __init__(self, player_color, opponent, observation_type, illegal_move_mode, board_size):
128         """
129         Args:
130             player_color: Stone color for the agent. Either 'black' or 'white'
131             opponent: An opponent policy
132             observation_type: State encoding
133             illegal_move_mode: What to do when the agent makes an illegal move. Choices: 'raise' or 'lose'
134         """
135         assert isinstance(board_size, int) and board_size >= 1, 'Invalid board size: {}'.format(board_size)
136         self.board_size = board_size
137 
138         self._seed()
139 
140         colormap = {
141             'black': pachi_py.BLACK,
142             'white': pachi_py.WHITE,
143         }
144         try:
145             self.player_color = colormap[player_color]
146         except KeyError:
147             raise error.Error("player_color must be 'black' or 'white', not {}".format(player_color))
148 
149         self.opponent_policy = None
150         self.opponent = opponent
151 
152         assert observation_type in ['image3c']
153         self.observation_type = observation_type
154 
155         assert illegal_move_mode in ['lose', 'raise']
156         self.illegal_move_mode = illegal_move_mode
157 
158         if self.observation_type != 'image3c':
159             raise error.Error('Unsupported observation type: {}'.format(self.observation_type))
160         self.reset()
161 
162         shape = pachi_py.CreateBoard(self.board_size).encode().shape
163         self.observation_space = spaces.Box(np.zeros(shape), np.ones(shape))
164         # One action for each board position, pass, and resign
165         self.action_space = spaces.Discrete(self.board_size**2 + 2)
166 
167     def _seed(self, seed=None):
168         self.np_random, seed1 = seeding.np_random(seed)
169         # Derive a random seed.
170         seed2 = seeding.hash_seed(seed1 + 1) % 2**32
171         pachi_py.pachi_srand(seed2)
172         return [seed1, seed2]
173 
174     def _reset(self):
175         self.state = GoState(pachi_py.CreateBoard(self.board_size), pachi_py.BLACK)
176 
177         # (re-initialize) the opponent
178         # necessary because a pachi engine is attached to a game via internal data in a board
179         # so with a fresh game, we need a fresh engine
180         self._reset_opponent(self.state.board)
181 
182         # Let the opponent play if it's not the agent's turn
183         opponent_resigned = False
184         if self.state.color != self.player_color:
185             self.state, opponent_resigned = self._exec_opponent_play(self.state, None, None)
186 
187         # We should be back to the agent color
188         assert self.state.color == self.player_color
189 
190         self.done = self.state.board.is_terminal or opponent_resigned
191         return self.state.board.encode()
192 
193     def _render(self, mode="human", close=False):
194         if close:
195             return
196         outfile = StringIO() if mode == 'ansi' else sys.stdout
197         outfile.write(repr(self.state) + '\n')
198         return outfile
199 
200     def _step(self, action):
201         assert self.state.color == self.player_color
202 
203         # If already terminal, then don't do anything
204         if self.done:
205             return self.state.board.encode(), 0., True, {'state': self.state}
206 
207         # If resigned, then we're done
208         if action == _resign_action(self.board_size):
209             self.done = True
210             return self.state.board.encode(), -1., True, {'state': self.state}
211 
212         # Play
213         prev_state = self.state
214         try:
215             self.state = self.state.act(action)
216         except pachi_py.IllegalMove:
217             if self.illegal_move_mode == 'raise':
218                 six.reraise(*sys.exc_info())
219             elif self.illegal_move_mode == 'lose':
220                 # Automatic loss on illegal move
221                 self.done = True
222                 return self.state.board.encode(), -1., True, {'state': self.state}
223             else:
224                 raise error.Error('Unsupported illegal move action: {}'.format(self.illegal_move_mode))
225 
226         # Opponent play
227         if not self.state.board.is_terminal:
228             self.state, opponent_resigned = self._exec_opponent_play(self.state, prev_state, action)
229             # After opponent play, we should be back to the original color
230             assert self.state.color == self.player_color
231 
232             # If the opponent resigns, then the agent wins
233             if opponent_resigned:
234                 self.done = True
235                 return self.state.board.encode(), 1., True, {'state': self.state}
236 
237         # Reward: if nonterminal, then the reward is 0
238         if not self.state.board.is_terminal:
239             self.done = False
240             return self.state.board.encode(), 0., False, {'state': self.state}
241 
242         # We're in a terminal state. Reward is 1 if won, -1 if lost
243         assert self.state.board.is_terminal
244         self.done = True
245         white_wins = self.state.board.official_score > 0
246         black_wins = self.state.board.official_score < 0
247         player_wins = (white_wins and self.player_color == pachi_py.WHITE) or (black_wins and self.player_color == pachi_py.BLACK)
248         reward = 1. if player_wins else -1. if (white_wins or black_wins) else 0.
249         return self.state.board.encode(), reward, True, {'state': self.state}
250 
251     def _exec_opponent_play(self, curr_state, prev_state, prev_action):
252         assert curr_state.color != self.player_color
253         opponent_action = self.opponent_policy(curr_state, prev_state, prev_action)
254         opponent_resigned = opponent_action == _resign_action(self.board_size)
255         return curr_state.act(opponent_action), opponent_resigned
256 
257     @property
258     def _state(self):
259         return self.state
260 
261     def _reset_opponent(self, board):
262         if self.opponent == 'random':
263             self.opponent_policy = make_random_policy(self.np_random)
264         elif self.opponent == 'pachi:uct:_2400':
265             self.opponent_policy = make_pachi_policy(board=board, engine_type=six.b('uct'), pachi_timestr=six.b('_2400')) # TODO: strength as argument
266         else:
267             raise error.Error('Unrecognized opponent policy {}'.format(self.opponent))
268
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/gym/envs/board_game/go.py b/gym/envs/board_game/go.py
--- a/gym/envs/board_game/go.py
+++ b/gym/envs/board_game/go.py
@@ -157,13 +157,16 @@
 
         if self.observation_type != 'image3c':
             raise error.Error('Unsupported observation type: {}'.format(self.observation_type))
-        self.reset()
 
         shape = pachi_py.CreateBoard(self.board_size).encode().shape
         self.observation_space = spaces.Box(np.zeros(shape), np.ones(shape))
         # One action for each board position, pass, and resign
         self.action_space = spaces.Discrete(self.board_size**2 + 2)
 
+        # Filled in by _reset()
+        self.state = None
+        self.done = True
+
     def _seed(self, seed=None):
         self.np_random, seed1 = seeding.np_random(seed)
         # Derive a random seed.
@@ -190,6 +193,10 @@
         self.done = self.state.board.is_terminal or opponent_resigned
         return self.state.board.encode()
 
+    def _close(self):
+        self.opponent_policy = None
+        self.state = None
+
     def _render(self, mode="human", close=False):
         if close:
             return

{"golden_diff": "diff --git a/gym/envs/board_game/go.py b/gym/envs/board_game/go.py\n--- a/gym/envs/board_game/go.py\n+++ b/gym/envs/board_game/go.py\n@@ -157,13 +157,16 @@\n \n         if self.observation_type != 'image3c':\n             raise error.Error('Unsupported observation type: {}'.format(self.observation_type))\n-        self.reset()\n \n         shape = pachi_py.CreateBoard(self.board_size).encode().shape\n         self.observation_space = spaces.Box(np.zeros(shape), np.ones(shape))\n         # One action for each board position, pass, and resign\n         self.action_space = spaces.Discrete(self.board_size**2 + 2)\n \n+        # Filled in by _reset()\n+        self.state = None\n+        self.done = True\n+\n     def _seed(self, seed=None):\n         self.np_random, seed1 = seeding.np_random(seed)\n         # Derive a random seed.\n@@ -190,6 +193,10 @@\n         self.done = self.state.board.is_terminal or opponent_resigned\n         return self.state.board.encode()\n \n+    def _close(self):\n+        self.opponent_policy = None\n+        self.state = None\n+\n     def _render(self, mode=\"human\", close=False):\n         if close:\n             return\n", "issue": "Go environment does not clean up after itself\nWe should implement a `_close` method on the Go environment, and close all open resources. @hojonathanho mind taking?\n\n", "before_files": [{"content": "from gym import error\ntry:\n    import pachi_py\nexcept ImportError as e:\n    # The dependency group [pachi] should match the name is setup.py.\n    raise error.DependencyNotInstalled('{}. (HINT: you may need to install the Go dependencies via \"pip install gym[pachi]\".)'.format(e))\n\nimport numpy as np\nimport gym\nfrom gym import spaces\nfrom gym.utils import seeding\nfrom six import StringIO\nimport sys\nimport six\n\n\n# The coordinate representation of Pachi (and pachi_py) is defined on a board\n# with extra rows and columns on the margin of the board, so positions on the board\n# are not numbers in [0, board_size**2) as one would expect. For this Go env, we instead\n# use an action representation that does fall in this more natural range.\n\ndef _pass_action(board_size):\n    return board_size**2\n\ndef _resign_action(board_size):\n    return board_size**2 + 1\n\ndef _coord_to_action(board, c):\n    '''Converts Pachi coordinates to actions'''\n    if c == pachi_py.PASS_COORD: return _pass_action(board.size)\n    if c == pachi_py.RESIGN_COORD: return _resign_action(board.size)\n    i, j = board.coord_to_ij(c)\n    return i*board.size + j\n\ndef _action_to_coord(board, a):\n    '''Converts actions to Pachi coordinates'''\n    if a == _pass_action(board.size): return pachi_py.PASS_COORD\n    if a == _resign_action(board.size): return pachi_py.RESIGN_COORD\n    return board.ij_to_coord(a // board.size, a % board.size)\n\ndef str_to_action(board, s):\n    return _coord_to_action(board, board.str_to_coord(s.encode()))\n\nclass GoState(object):\n    '''\n    Go game state. Consists of a current player and a board.\n    Actions are exposed as integers in [0, num_actions), which is different\n    from Pachi's internal \"coord_t\" encoding.\n    '''\n    def __init__(self, board, color):\n        '''\n        Args:\n            board: current board\n            color: color of current player\n        '''\n        assert color in [pachi_py.BLACK, pachi_py.WHITE], 'Invalid player color'\n        self.board, self.color = board, color\n\n    def act(self, action):\n        '''\n        Executes an action for the current player\n\n        Returns:\n            a new GoState with the new board and the player switched\n        '''\n        return GoState(\n            self.board.play(_action_to_coord(self.board, action), self.color),\n            pachi_py.stone_other(self.color))\n\n    def __repr__(self):\n        return 'To play: {}\\n{}'.format(six.u(pachi_py.color_to_str(self.color)), self.board.__repr__().decode())\n\n\n### Adversary policies ###\ndef make_random_policy(np_random):\n    def random_policy(curr_state, prev_state, prev_action):\n        b = curr_state.board\n        legal_coords = b.get_legal_coords(curr_state.color)\n        return _coord_to_action(b, np_random.choice(legal_coords))\n    return random_policy\n\ndef make_pachi_policy(board, engine_type='uct', threads=1, pachi_timestr=''):\n    engine = pachi_py.PyPachiEngine(board, engine_type, six.b('threads=%d' % threads))\n\n    def pachi_policy(curr_state, prev_state, prev_action):\n        if prev_state is not None:\n            assert engine.curr_board == prev_state.board, 'Engine internal board is inconsistent with provided board. The Pachi engine must be called consistently as the game progresses.'\n            prev_coord = _action_to_coord(prev_state.board, prev_action)\n            engine.notify(prev_coord, prev_state.color)\n            engine.curr_board.play_inplace(prev_coord, prev_state.color)\n        out_coord = engine.genmove(curr_state.color, pachi_timestr)\n        out_action = _coord_to_action(curr_state.board, out_coord)\n        engine.curr_board.play_inplace(out_coord, curr_state.color)\n        return out_action\n\n    return pachi_policy\n\n\ndef _play(black_policy_fn, white_policy_fn, board_size=19):\n    '''\n    Samples a trajectory for two player policies.\n    Args:\n        black_policy_fn, white_policy_fn: functions that maps a GoState to a move coord (int)\n    '''\n    moves = []\n\n    prev_state, prev_action = None, None\n    curr_state = GoState(pachi_py.CreateBoard(board_size), BLACK)\n\n    while not curr_state.board.is_terminal:\n        a = (black_policy_fn if curr_state.color == BLACK else white_policy_fn)(curr_state, prev_state, prev_action)\n        next_state = curr_state.act(a)\n        moves.append((curr_state, a, next_state))\n\n        prev_state, prev_action = curr_state, a\n        curr_state = next_state\n\n    return moves\n\n\nclass GoEnv(gym.Env):\n    '''\n    Go environment. Play against a fixed opponent.\n    '''\n    metadata = {\"render.modes\": [\"human\", \"ansi\"]}\n\n    def __init__(self, player_color, opponent, observation_type, illegal_move_mode, board_size):\n        \"\"\"\n        Args:\n            player_color: Stone color for the agent. Either 'black' or 'white'\n            opponent: An opponent policy\n            observation_type: State encoding\n            illegal_move_mode: What to do when the agent makes an illegal move. Choices: 'raise' or 'lose'\n        \"\"\"\n        assert isinstance(board_size, int) and board_size >= 1, 'Invalid board size: {}'.format(board_size)\n        self.board_size = board_size\n\n        self._seed()\n\n        colormap = {\n            'black': pachi_py.BLACK,\n            'white': pachi_py.WHITE,\n        }\n        try:\n            self.player_color = colormap[player_color]\n        except KeyError:\n            raise error.Error(\"player_color must be 'black' or 'white', not {}\".format(player_color))\n\n        self.opponent_policy = None\n        self.opponent = opponent\n\n        assert observation_type in ['image3c']\n        self.observation_type = observation_type\n\n        assert illegal_move_mode in ['lose', 'raise']\n        self.illegal_move_mode = illegal_move_mode\n\n        if self.observation_type != 'image3c':\n            raise error.Error('Unsupported observation type: {}'.format(self.observation_type))\n        self.reset()\n\n        shape = pachi_py.CreateBoard(self.board_size).encode().shape\n        self.observation_space = spaces.Box(np.zeros(shape), np.ones(shape))\n        # One action for each board position, pass, and resign\n        self.action_space = spaces.Discrete(self.board_size**2 + 2)\n\n    def _seed(self, seed=None):\n        self.np_random, seed1 = seeding.np_random(seed)\n        # Derive a random seed.\n        seed2 = seeding.hash_seed(seed1 + 1) % 2**32\n        pachi_py.pachi_srand(seed2)\n        return [seed1, seed2]\n\n    def _reset(self):\n        self.state = GoState(pachi_py.CreateBoard(self.board_size), pachi_py.BLACK)\n\n        # (re-initialize) the opponent\n        # necessary because a pachi engine is attached to a game via internal data in a board\n        # so with a fresh game, we need a fresh engine\n        self._reset_opponent(self.state.board)\n\n        # Let the opponent play if it's not the agent's turn\n        opponent_resigned = False\n        if self.state.color != self.player_color:\n            self.state, opponent_resigned = self._exec_opponent_play(self.state, None, None)\n\n        # We should be back to the agent color\n        assert self.state.color == self.player_color\n\n        self.done = self.state.board.is_terminal or opponent_resigned\n        return self.state.board.encode()\n\n    def _render(self, mode=\"human\", close=False):\n        if close:\n            return\n        outfile = StringIO() if mode == 'ansi' else sys.stdout\n        outfile.write(repr(self.state) + '\\n')\n        return outfile\n\n    def _step(self, action):\n        assert self.state.color == self.player_color\n\n        # If already terminal, then don't do anything\n        if self.done:\n            return self.state.board.encode(), 0., True, {'state': self.state}\n\n        # If resigned, then we're done\n        if action == _resign_action(self.board_size):\n            self.done = True\n            return self.state.board.encode(), -1., True, {'state': self.state}\n\n        # Play\n        prev_state = self.state\n        try:\n            self.state = self.state.act(action)\n        except pachi_py.IllegalMove:\n            if self.illegal_move_mode == 'raise':\n                six.reraise(*sys.exc_info())\n            elif self.illegal_move_mode == 'lose':\n                # Automatic loss on illegal move\n                self.done = True\n                return self.state.board.encode(), -1., True, {'state': self.state}\n            else:\n                raise error.Error('Unsupported illegal move action: {}'.format(self.illegal_move_mode))\n\n        # Opponent play\n        if not self.state.board.is_terminal:\n            self.state, opponent_resigned = self._exec_opponent_play(self.state, prev_state, action)\n            # After opponent play, we should be back to the original color\n            assert self.state.color == self.player_color\n\n            # If the opponent resigns, then the agent wins\n            if opponent_resigned:\n                self.done = True\n                return self.state.board.encode(), 1., True, {'state': self.state}\n\n        # Reward: if nonterminal, then the reward is 0\n        if not self.state.board.is_terminal:\n            self.done = False\n            return self.state.board.encode(), 0., False, {'state': self.state}\n\n        # We're in a terminal state. Reward is 1 if won, -1 if lost\n        assert self.state.board.is_terminal\n        self.done = True\n        white_wins = self.state.board.official_score > 0\n        black_wins = self.state.board.official_score < 0\n        player_wins = (white_wins and self.player_color == pachi_py.WHITE) or (black_wins and self.player_color == pachi_py.BLACK)\n        reward = 1. if player_wins else -1. if (white_wins or black_wins) else 0.\n        return self.state.board.encode(), reward, True, {'state': self.state}\n\n    def _exec_opponent_play(self, curr_state, prev_state, prev_action):\n        assert curr_state.color != self.player_color\n        opponent_action = self.opponent_policy(curr_state, prev_state, prev_action)\n        opponent_resigned = opponent_action == _resign_action(self.board_size)\n        return curr_state.act(opponent_action), opponent_resigned\n\n    @property\n    def _state(self):\n        return self.state\n\n    def _reset_opponent(self, board):\n        if self.opponent == 'random':\n            self.opponent_policy = make_random_policy(self.np_random)\n        elif self.opponent == 'pachi:uct:_2400':\n            self.opponent_policy = make_pachi_policy(board=board, engine_type=six.b('uct'), pachi_timestr=six.b('_2400')) # TODO: strength as argument\n        else:\n            raise error.Error('Unrecognized opponent policy {}'.format(self.opponent))\n", "path": "gym/envs/board_game/go.py"}], "after_files": [{"content": "from gym import error\ntry:\n    import pachi_py\nexcept ImportError as e:\n    # The dependency group [pachi] should match the name is setup.py.\n    raise error.DependencyNotInstalled('{}. (HINT: you may need to install the Go dependencies via \"pip install gym[pachi]\".)'.format(e))\n\nimport numpy as np\nimport gym\nfrom gym import spaces\nfrom gym.utils import seeding\nfrom six import StringIO\nimport sys\nimport six\n\n\n# The coordinate representation of Pachi (and pachi_py) is defined on a board\n# with extra rows and columns on the margin of the board, so positions on the board\n# are not numbers in [0, board_size**2) as one would expect. For this Go env, we instead\n# use an action representation that does fall in this more natural range.\n\ndef _pass_action(board_size):\n    return board_size**2\n\ndef _resign_action(board_size):\n    return board_size**2 + 1\n\ndef _coord_to_action(board, c):\n    '''Converts Pachi coordinates to actions'''\n    if c == pachi_py.PASS_COORD: return _pass_action(board.size)\n    if c == pachi_py.RESIGN_COORD: return _resign_action(board.size)\n    i, j = board.coord_to_ij(c)\n    return i*board.size + j\n\ndef _action_to_coord(board, a):\n    '''Converts actions to Pachi coordinates'''\n    if a == _pass_action(board.size): return pachi_py.PASS_COORD\n    if a == _resign_action(board.size): return pachi_py.RESIGN_COORD\n    return board.ij_to_coord(a // board.size, a % board.size)\n\ndef str_to_action(board, s):\n    return _coord_to_action(board, board.str_to_coord(s.encode()))\n\nclass GoState(object):\n    '''\n    Go game state. Consists of a current player and a board.\n    Actions are exposed as integers in [0, num_actions), which is different\n    from Pachi's internal \"coord_t\" encoding.\n    '''\n    def __init__(self, board, color):\n        '''\n        Args:\n            board: current board\n            color: color of current player\n        '''\n        assert color in [pachi_py.BLACK, pachi_py.WHITE], 'Invalid player color'\n        self.board, self.color = board, color\n\n    def act(self, action):\n        '''\n        Executes an action for the current player\n\n        Returns:\n            a new GoState with the new board and the player switched\n        '''\n        return GoState(\n            self.board.play(_action_to_coord(self.board, action), self.color),\n            pachi_py.stone_other(self.color))\n\n    def __repr__(self):\n        return 'To play: {}\\n{}'.format(six.u(pachi_py.color_to_str(self.color)), self.board.__repr__().decode())\n\n\n### Adversary policies ###\ndef make_random_policy(np_random):\n    def random_policy(curr_state, prev_state, prev_action):\n        b = curr_state.board\n        legal_coords = b.get_legal_coords(curr_state.color)\n        return _coord_to_action(b, np_random.choice(legal_coords))\n    return random_policy\n\ndef make_pachi_policy(board, engine_type='uct', threads=1, pachi_timestr=''):\n    engine = pachi_py.PyPachiEngine(board, engine_type, six.b('threads=%d' % threads))\n\n    def pachi_policy(curr_state, prev_state, prev_action):\n        if prev_state is not None:\n            assert engine.curr_board == prev_state.board, 'Engine internal board is inconsistent with provided board. The Pachi engine must be called consistently as the game progresses.'\n            prev_coord = _action_to_coord(prev_state.board, prev_action)\n            engine.notify(prev_coord, prev_state.color)\n            engine.curr_board.play_inplace(prev_coord, prev_state.color)\n        out_coord = engine.genmove(curr_state.color, pachi_timestr)\n        out_action = _coord_to_action(curr_state.board, out_coord)\n        engine.curr_board.play_inplace(out_coord, curr_state.color)\n        return out_action\n\n    return pachi_policy\n\n\ndef _play(black_policy_fn, white_policy_fn, board_size=19):\n    '''\n    Samples a trajectory for two player policies.\n    Args:\n        black_policy_fn, white_policy_fn: functions that maps a GoState to a move coord (int)\n    '''\n    moves = []\n\n    prev_state, prev_action = None, None\n    curr_state = GoState(pachi_py.CreateBoard(board_size), BLACK)\n\n    while not curr_state.board.is_terminal:\n        a = (black_policy_fn if curr_state.color == BLACK else white_policy_fn)(curr_state, prev_state, prev_action)\n        next_state = curr_state.act(a)\n        moves.append((curr_state, a, next_state))\n\n        prev_state, prev_action = curr_state, a\n        curr_state = next_state\n\n    return moves\n\n\nclass GoEnv(gym.Env):\n    '''\n    Go environment. Play against a fixed opponent.\n    '''\n    metadata = {\"render.modes\": [\"human\", \"ansi\"]}\n\n    def __init__(self, player_color, opponent, observation_type, illegal_move_mode, board_size):\n        \"\"\"\n        Args:\n            player_color: Stone color for the agent. Either 'black' or 'white'\n            opponent: An opponent policy\n            observation_type: State encoding\n            illegal_move_mode: What to do when the agent makes an illegal move. Choices: 'raise' or 'lose'\n        \"\"\"\n        assert isinstance(board_size, int) and board_size >= 1, 'Invalid board size: {}'.format(board_size)\n        self.board_size = board_size\n\n        self._seed()\n\n        colormap = {\n            'black': pachi_py.BLACK,\n            'white': pachi_py.WHITE,\n        }\n        try:\n            self.player_color = colormap[player_color]\n        except KeyError:\n            raise error.Error(\"player_color must be 'black' or 'white', not {}\".format(player_color))\n\n        self.opponent_policy = None\n        self.opponent = opponent\n\n        assert observation_type in ['image3c']\n        self.observation_type = observation_type\n\n        assert illegal_move_mode in ['lose', 'raise']\n        self.illegal_move_mode = illegal_move_mode\n\n        if self.observation_type != 'image3c':\n            raise error.Error('Unsupported observation type: {}'.format(self.observation_type))\n\n        shape = pachi_py.CreateBoard(self.board_size).encode().shape\n        self.observation_space = spaces.Box(np.zeros(shape), np.ones(shape))\n        # One action for each board position, pass, and resign\n        self.action_space = spaces.Discrete(self.board_size**2 + 2)\n\n        # Filled in by _reset()\n        self.state = None\n        self.done = True\n\n    def _seed(self, seed=None):\n        self.np_random, seed1 = seeding.np_random(seed)\n        # Derive a random seed.\n        seed2 = seeding.hash_seed(seed1 + 1) % 2**32\n        pachi_py.pachi_srand(seed2)\n        return [seed1, seed2]\n\n    def _reset(self):\n        self.state = GoState(pachi_py.CreateBoard(self.board_size), pachi_py.BLACK)\n\n        # (re-initialize) the opponent\n        # necessary because a pachi engine is attached to a game via internal data in a board\n        # so with a fresh game, we need a fresh engine\n        self._reset_opponent(self.state.board)\n\n        # Let the opponent play if it's not the agent's turn\n        opponent_resigned = False\n        if self.state.color != self.player_color:\n            self.state, opponent_resigned = self._exec_opponent_play(self.state, None, None)\n\n        # We should be back to the agent color\n        assert self.state.color == self.player_color\n\n        self.done = self.state.board.is_terminal or opponent_resigned\n        return self.state.board.encode()\n\n    def _close(self):\n        self.opponent_policy = None\n        self.state = None\n\n    def _render(self, mode=\"human\", close=False):\n        if close:\n            return\n        outfile = StringIO() if mode == 'ansi' else sys.stdout\n        outfile.write(repr(self.state) + '\\n')\n        return outfile\n\n    def _step(self, action):\n        assert self.state.color == self.player_color\n\n        # If already terminal, then don't do anything\n        if self.done:\n            return self.state.board.encode(), 0., True, {'state': self.state}\n\n        # If resigned, then we're done\n        if action == _resign_action(self.board_size):\n            self.done = True\n            return self.state.board.encode(), -1., True, {'state': self.state}\n\n        # Play\n        prev_state = self.state\n        try:\n            self.state = self.state.act(action)\n        except pachi_py.IllegalMove:\n            if self.illegal_move_mode == 'raise':\n                six.reraise(*sys.exc_info())\n            elif self.illegal_move_mode == 'lose':\n                # Automatic loss on illegal move\n                self.done = True\n                return self.state.board.encode(), -1., True, {'state': self.state}\n            else:\n                raise error.Error('Unsupported illegal move action: {}'.format(self.illegal_move_mode))\n\n        # Opponent play\n        if not self.state.board.is_terminal:\n            self.state, opponent_resigned = self._exec_opponent_play(self.state, prev_state, action)\n            # After opponent play, we should be back to the original color\n            assert self.state.color == self.player_color\n\n            # If the opponent resigns, then the agent wins\n            if opponent_resigned:\n                self.done = True\n                return self.state.board.encode(), 1., True, {'state': self.state}\n\n        # Reward: if nonterminal, then the reward is 0\n        if not self.state.board.is_terminal:\n            self.done = False\n            return self.state.board.encode(), 0., False, {'state': self.state}\n\n        # We're in a terminal state. Reward is 1 if won, -1 if lost\n        assert self.state.board.is_terminal\n        self.done = True\n        white_wins = self.state.board.official_score > 0\n        black_wins = self.state.board.official_score < 0\n        player_wins = (white_wins and self.player_color == pachi_py.WHITE) or (black_wins and self.player_color == pachi_py.BLACK)\n        reward = 1. if player_wins else -1. if (white_wins or black_wins) else 0.\n        return self.state.board.encode(), reward, True, {'state': self.state}\n\n    def _exec_opponent_play(self, curr_state, prev_state, prev_action):\n        assert curr_state.color != self.player_color\n        opponent_action = self.opponent_policy(curr_state, prev_state, prev_action)\n        opponent_resigned = opponent_action == _resign_action(self.board_size)\n        return curr_state.act(opponent_action), opponent_resigned\n\n    @property\n    def _state(self):\n        return self.state\n\n    def _reset_opponent(self, board):\n        if self.opponent == 'random':\n            self.opponent_policy = make_random_policy(self.np_random)\n        elif self.opponent == 'pachi:uct:_2400':\n            self.opponent_policy = make_pachi_policy(board=board, engine_type=six.b('uct'), pachi_timestr=six.b('_2400')) # TODO: strength as argument\n        else:\n            raise error.Error('Unrecognized opponent policy {}'.format(self.opponent))\n", "path": "gym/envs/board_game/go.py"}]}