Repository: openai/gym

Issue:
[Bug Report] CarRacing module crashes when executed directly and no longer stops after closing the window
**Describe the bug**
When running the `car_racing.py` module directly, it fails with

```
  File "/tmp/venv/gym/gym/envs/box2d/car_racing.py", line 799, in <module>
    env.render()
  File "/tmp/venv/gym/gym/envs/box2d/car_racing.py", line 568, in render
    return self._render(self.render_mode)
  File "/tmp/venv/gym/gym/envs/box2d/car_racing.py", line 571, in _render
    assert mode in self.metadata["render_modes"]
AssertionError
```

This is caused by the recent rendering changes, because `car_racing.py` is constructing `CarRacing` instance directly in
https://github.com/openai/gym/blob/f4d668f9687ae417ff041cb1735966633d8ba759/gym/envs/box2d/car_racing.py#L798

The problem could be fixed by adding `render_mode="human"` to the `CarRacing` constructor.

There is also a second problem, however. The main cycle is
```
    isopen = True
    while isopen:
        env.reset()
        total_reward = 0.0
        steps = 0
        restart = False
        while True:
            register_input()
            s, r, terminated, truncated, info = env.step(a)
            total_reward += r
            if steps % 200 == 0 or terminated or truncated:
                print("\naction " + str([f"{x:+0.2f}" for x in a]))
                print(f"step {steps} total_reward {total_reward:+0.2f}")
            steps += 1
            isopen = env.render()
            if terminated or truncated or restart or isopen is False:
                break
```
Previously, `isopen` was set when the user closed the viewer, which is no longer the case with `pygame`. However:
- currently `isopen` is not set when closing the window -- the window cannot be closed, actually
- `render` is called twice -- once inside the `step`, and also manually in the cycle

I am not sure how to handle this use case:
- instead of
  ```python
  pygame.event.pump()
  ```
  in the `render` method one can use
  ```python
  if pygame.event.peek(pygame.QUIT):
      # handle close
  else:
      # original code
      pygame.event.pump()
  ```
  to detect that user tried to close the window
- however, it is not clear how to handle the close -- should only a flag be raised? Or the window actually closed?
  - If you only set the `isopen` flag and return it, then because `render` is called twice now (once in `step`), in 50% of the cases this flag will be returned from the `render` call in `step` method and will be ignored (because it will not be handled by the above cycle)
  - If you close the window as a response to the `QUIT` event, then:
    - maybe we could have some API place where this flag will be set, so that something like `env.isopen` is exported from all environments with a viewer?
    -  care must be taken so that a new window is not immediately created (if for example `self.surface` is `None`)

Previously there was a single `Viewer` class handling rendering of all `classic_control` and `box2d` environments, but right now the `pygame` logis is instead replicated in all of them. Maybe we could have again some `Viewer` class to provide basic functionality, so that changes like "allowing the user to close the window" would be implemented on one place, not identically in many modules.


Discussion:
Thanks, that's a good catch. I'd actually consider removing the whole functionality of running individual files, it seems to be a relic of the old versions of gym, and overall not a great pattern. 
@foxik Would you be able to make a PR to fix it? 

Resolving patch:
diff --git a/gym/envs/box2d/car_racing.py b/gym/envs/box2d/car_racing.py
--- a/gym/envs/box2d/car_racing.py
+++ b/gym/envs/box2d/car_racing.py
@@ -771,6 +771,7 @@ def close(self):
     a = np.array([0.0, 0.0, 0.0])
 
     def register_input():
+        global quit, restart
         for event in pygame.event.get():
             if event.type == pygame.KEYDOWN:
                 if event.key == pygame.K_LEFT:
@@ -782,8 +783,9 @@ def register_input():
                 if event.key == pygame.K_DOWN:
                     a[2] = +0.8  # set 1.0 for wheels to block to zero rotation
                 if event.key == pygame.K_RETURN:
-                    global restart
                     restart = True
+                if event.key == pygame.K_ESCAPE:
+                    quit = True
 
             if event.type == pygame.KEYUP:
                 if event.key == pygame.K_LEFT:
@@ -795,11 +797,13 @@ def register_input():
                 if event.key == pygame.K_DOWN:
                     a[2] = 0
 
-    env = CarRacing()
-    env.render()
+            if event.type == pygame.QUIT:
+                quit = True
 
-    isopen = True
-    while isopen:
+    env = CarRacing(render_mode="human")
+
+    quit = False
+    while not quit:
         env.reset()
         total_reward = 0.0
         steps = 0
@@ -812,7 +816,6 @@ def register_input():
                 print("\naction " + str([f"{x:+0.2f}" for x in a]))
                 print(f"step {steps} total_reward {total_reward:+0.2f}")
             steps += 1
-            isopen = env.render()
-            if terminated or truncated or restart or isopen is False:
+            if terminated or truncated or restart or quit:
                 break
     env.close()


Test patch: