mirror of
https://github.com/Farama-Foundation/Gymnasium.git
synced 2025-08-23 23:12:46 +00:00
Lunar lander code clarity (#354)
This commit is contained in:
@@ -47,8 +47,11 @@ LEG_DOWN = 18
|
|||||||
LEG_W, LEG_H = 2, 8
|
LEG_W, LEG_H = 2, 8
|
||||||
LEG_SPRING_TORQUE = 40
|
LEG_SPRING_TORQUE = 40
|
||||||
|
|
||||||
SIDE_ENGINE_HEIGHT = 14.0
|
SIDE_ENGINE_HEIGHT = 14
|
||||||
SIDE_ENGINE_AWAY = 12.0
|
SIDE_ENGINE_AWAY = 12
|
||||||
|
MAIN_ENGINE_Y_LOCATION = (
|
||||||
|
4 # The Y location of the main engine on the body of the Lander.
|
||||||
|
)
|
||||||
|
|
||||||
VIEWPORT_W = 600
|
VIEWPORT_W = 600
|
||||||
VIEWPORT_H = 400
|
VIEWPORT_H = 400
|
||||||
@@ -183,6 +186,34 @@ class LunarLander(gym.Env, EzPickle):
|
|||||||
renormalized to 200; harder initial random push.
|
renormalized to 200; harder initial random push.
|
||||||
- v0: Initial version
|
- v0: Initial version
|
||||||
|
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
There are several unexpected bugs with the implementation of the environment.
|
||||||
|
|
||||||
|
1. The position of the side thursters on the body of the lander changes, depending on the orientation of the lander.
|
||||||
|
This in turn results in an orientation depentant torque being applied to the lander.
|
||||||
|
|
||||||
|
2. The units of the state are not consistent. I.e.
|
||||||
|
* The angular velocity is in units of 0.4 radians per second. In order to convert to radians per second, the value needs to be multiplied by a factor of 2.5.
|
||||||
|
|
||||||
|
For the default values of VIEWPORT_W, VIEWPORT_H, SCALE, and FPS, the scale factors equal:
|
||||||
|
'x': 10
|
||||||
|
'y': 6.666
|
||||||
|
'vx': 5
|
||||||
|
'vy': 7.5
|
||||||
|
'angle': 1
|
||||||
|
'angular velocity': 2.5
|
||||||
|
|
||||||
|
After the correction has been made, the units of the state are as follows:
|
||||||
|
'x': (units)
|
||||||
|
'y': (units)
|
||||||
|
'vx': (units/second)
|
||||||
|
'vy': (units/second)
|
||||||
|
'angle': (radians)
|
||||||
|
'angular velocity': (radians/second)
|
||||||
|
|
||||||
|
|
||||||
<!-- ## References -->
|
<!-- ## References -->
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
@@ -327,7 +358,7 @@ class LunarLander(gym.Env, EzPickle):
|
|||||||
W = VIEWPORT_W / SCALE
|
W = VIEWPORT_W / SCALE
|
||||||
H = VIEWPORT_H / SCALE
|
H = VIEWPORT_H / SCALE
|
||||||
|
|
||||||
# terrain
|
# Create Terrain
|
||||||
CHUNKS = 11
|
CHUNKS = 11
|
||||||
height = self.np_random.uniform(0, H / 2, size=(CHUNKS + 1,))
|
height = self.np_random.uniform(0, H / 2, size=(CHUNKS + 1,))
|
||||||
chunk_x = [W / (CHUNKS - 1) * i for i in range(CHUNKS)]
|
chunk_x = [W / (CHUNKS - 1) * i for i in range(CHUNKS)]
|
||||||
@@ -357,9 +388,11 @@ class LunarLander(gym.Env, EzPickle):
|
|||||||
self.moon.color1 = (0.0, 0.0, 0.0)
|
self.moon.color1 = (0.0, 0.0, 0.0)
|
||||||
self.moon.color2 = (0.0, 0.0, 0.0)
|
self.moon.color2 = (0.0, 0.0, 0.0)
|
||||||
|
|
||||||
|
# Create Lander body
|
||||||
initial_y = VIEWPORT_H / SCALE
|
initial_y = VIEWPORT_H / SCALE
|
||||||
|
initial_x = VIEWPORT_W / SCALE / 2
|
||||||
self.lander: Box2D.b2Body = self.world.CreateDynamicBody(
|
self.lander: Box2D.b2Body = self.world.CreateDynamicBody(
|
||||||
position=(VIEWPORT_W / SCALE / 2, initial_y),
|
position=(initial_x, initial_y),
|
||||||
angle=0.0,
|
angle=0.0,
|
||||||
fixtures=fixtureDef(
|
fixtures=fixtureDef(
|
||||||
shape=polygonShape(
|
shape=polygonShape(
|
||||||
@@ -374,6 +407,8 @@ class LunarLander(gym.Env, EzPickle):
|
|||||||
)
|
)
|
||||||
self.lander.color1 = (128, 102, 230)
|
self.lander.color1 = (128, 102, 230)
|
||||||
self.lander.color2 = (77, 77, 128)
|
self.lander.color2 = (77, 77, 128)
|
||||||
|
|
||||||
|
# Apply the initial random impulse to the lander
|
||||||
self.lander.ApplyForceToCenter(
|
self.lander.ApplyForceToCenter(
|
||||||
(
|
(
|
||||||
self.np_random.uniform(-INITIAL_RANDOM, INITIAL_RANDOM),
|
self.np_random.uniform(-INITIAL_RANDOM, INITIAL_RANDOM),
|
||||||
@@ -382,10 +417,11 @@ class LunarLander(gym.Env, EzPickle):
|
|||||||
True,
|
True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Create Lander Legs
|
||||||
self.legs = []
|
self.legs = []
|
||||||
for i in [-1, +1]:
|
for i in [-1, +1]:
|
||||||
leg = self.world.CreateDynamicBody(
|
leg = self.world.CreateDynamicBody(
|
||||||
position=(VIEWPORT_W / SCALE / 2 - i * LEG_AWAY / SCALE, initial_y),
|
position=(initial_x - i * LEG_AWAY / SCALE, initial_y),
|
||||||
angle=(i * 0.05),
|
angle=(i * 0.05),
|
||||||
fixtures=fixtureDef(
|
fixtures=fixtureDef(
|
||||||
shape=polygonShape(box=(LEG_W / SCALE, LEG_H / SCALE)),
|
shape=polygonShape(box=(LEG_W / SCALE, LEG_H / SCALE)),
|
||||||
@@ -450,7 +486,7 @@ class LunarLander(gym.Env, EzPickle):
|
|||||||
def step(self, action):
|
def step(self, action):
|
||||||
assert self.lander is not None
|
assert self.lander is not None
|
||||||
|
|
||||||
# Update wind
|
# Update wind and apply to the lander
|
||||||
assert self.lander is not None, "You forgot to call reset()"
|
assert self.lander is not None, "You forgot to call reset()"
|
||||||
if self.enable_wind and not (
|
if self.enable_wind and not (
|
||||||
self.legs[0].ground_contact or self.legs[1].ground_contact
|
self.legs[0].ground_contact or self.legs[1].ground_contact
|
||||||
@@ -489,9 +525,15 @@ class LunarLander(gym.Env, EzPickle):
|
|||||||
action
|
action
|
||||||
), f"{action!r} ({type(action)}) invalid "
|
), f"{action!r} ({type(action)}) invalid "
|
||||||
|
|
||||||
# Engines
|
# Apply Engine Impulses
|
||||||
|
|
||||||
|
# Tip is a the (X and Y) components of the rotation of the lander.
|
||||||
tip = (math.sin(self.lander.angle), math.cos(self.lander.angle))
|
tip = (math.sin(self.lander.angle), math.cos(self.lander.angle))
|
||||||
|
|
||||||
|
# Side is the (-Y and X) components of the rotation of the lander.
|
||||||
side = (-tip[1], tip[0])
|
side = (-tip[1], tip[0])
|
||||||
|
|
||||||
|
# Generate two random numbers between -1/SCALE and 1/SCALE.
|
||||||
dispersion = [self.np_random.uniform(-1.0, +1.0) / SCALE for _ in range(2)]
|
dispersion = [self.np_random.uniform(-1.0, +1.0) / SCALE for _ in range(2)]
|
||||||
|
|
||||||
m_power = 0.0
|
m_power = 0.0
|
||||||
@@ -504,12 +546,21 @@ class LunarLander(gym.Env, EzPickle):
|
|||||||
assert m_power >= 0.5 and m_power <= 1.0
|
assert m_power >= 0.5 and m_power <= 1.0
|
||||||
else:
|
else:
|
||||||
m_power = 1.0
|
m_power = 1.0
|
||||||
|
|
||||||
# 4 is move a bit downwards, +-2 for randomness
|
# 4 is move a bit downwards, +-2 for randomness
|
||||||
ox = tip[0] * (4 / SCALE + 2 * dispersion[0]) + side[0] * dispersion[1]
|
# The components of the impulse to be applied by the main engine.
|
||||||
oy = -tip[1] * (4 / SCALE + 2 * dispersion[0]) - side[1] * dispersion[1]
|
ox = (
|
||||||
|
tip[0] * (MAIN_ENGINE_Y_LOCATION / SCALE + 2 * dispersion[0])
|
||||||
|
+ side[0] * dispersion[1]
|
||||||
|
)
|
||||||
|
oy = (
|
||||||
|
-tip[1] * (MAIN_ENGINE_Y_LOCATION / SCALE + 2 * dispersion[0])
|
||||||
|
- side[1] * dispersion[1]
|
||||||
|
)
|
||||||
|
|
||||||
impulse_pos = (self.lander.position[0] + ox, self.lander.position[1] + oy)
|
impulse_pos = (self.lander.position[0] + ox, self.lander.position[1] + oy)
|
||||||
if self.render_mode is not None:
|
if self.render_mode is not None:
|
||||||
# particles are just a decoration, so don't add them when not rendering
|
# particles are just a decoration, with no impact on the physics, so don't add them when not rendering
|
||||||
p = self._create_particle(
|
p = self._create_particle(
|
||||||
3.5, # 3.5 is here to make particle speed adequate
|
3.5, # 3.5 is here to make particle speed adequate
|
||||||
impulse_pos[0],
|
impulse_pos[0],
|
||||||
@@ -534,27 +585,40 @@ class LunarLander(gym.Env, EzPickle):
|
|||||||
if (self.continuous and np.abs(action[1]) > 0.5) or (
|
if (self.continuous and np.abs(action[1]) > 0.5) or (
|
||||||
not self.continuous and action in [1, 3]
|
not self.continuous and action in [1, 3]
|
||||||
):
|
):
|
||||||
# Orientation engines
|
# Orientation/Side engines
|
||||||
if self.continuous:
|
if self.continuous:
|
||||||
direction = np.sign(action[1])
|
direction = np.sign(action[1])
|
||||||
s_power = np.clip(np.abs(action[1]), 0.5, 1.0)
|
s_power = np.clip(np.abs(action[1]), 0.5, 1.0)
|
||||||
assert s_power >= 0.5 and s_power <= 1.0
|
assert s_power >= 0.5 and s_power <= 1.0
|
||||||
else:
|
else:
|
||||||
|
# action = 1 is left, action = 3 is right
|
||||||
direction = action - 2
|
direction = action - 2
|
||||||
s_power = 1.0
|
s_power = 1.0
|
||||||
|
|
||||||
|
# The components of the impulse to be applied by the side engines.
|
||||||
ox = tip[0] * dispersion[0] + side[0] * (
|
ox = tip[0] * dispersion[0] + side[0] * (
|
||||||
3 * dispersion[1] + direction * SIDE_ENGINE_AWAY / SCALE
|
3 * dispersion[1] + direction * SIDE_ENGINE_AWAY / SCALE
|
||||||
)
|
)
|
||||||
oy = -tip[1] * dispersion[0] - side[1] * (
|
oy = -tip[1] * dispersion[0] - side[1] * (
|
||||||
3 * dispersion[1] + direction * SIDE_ENGINE_AWAY / SCALE
|
3 * dispersion[1] + direction * SIDE_ENGINE_AWAY / SCALE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# The constant 17 is a constant, that is presumably meant to be SIDE_ENGINE_HEIGHT.
|
||||||
|
# However, SIDE_ENGINE_HEIGHT is defined as 14
|
||||||
|
# This casuses the position of the thurst on the body of the lander to change, depending on the orientation of the lander.
|
||||||
|
# This in turn results in an orientation depentant torque being applied to the lander.
|
||||||
impulse_pos = (
|
impulse_pos = (
|
||||||
self.lander.position[0] + ox - tip[0] * 17 / SCALE,
|
self.lander.position[0] + ox - tip[0] * 17 / SCALE,
|
||||||
self.lander.position[1] + oy + tip[1] * SIDE_ENGINE_HEIGHT / SCALE,
|
self.lander.position[1] + oy + tip[1] * SIDE_ENGINE_HEIGHT / SCALE,
|
||||||
)
|
)
|
||||||
|
if self.render_mode is not None:
|
||||||
|
# particles are just a decoration, with no impact on the physics, so don't add them when not rendering
|
||||||
p = self._create_particle(0.7, impulse_pos[0], impulse_pos[1], s_power)
|
p = self._create_particle(0.7, impulse_pos[0], impulse_pos[1], s_power)
|
||||||
p.ApplyLinearImpulse(
|
p.ApplyLinearImpulse(
|
||||||
(ox * SIDE_ENGINE_POWER * s_power, oy * SIDE_ENGINE_POWER * s_power),
|
(
|
||||||
|
ox * SIDE_ENGINE_POWER * s_power,
|
||||||
|
oy * SIDE_ENGINE_POWER * s_power,
|
||||||
|
),
|
||||||
impulse_pos,
|
impulse_pos,
|
||||||
True,
|
True,
|
||||||
)
|
)
|
||||||
@@ -568,6 +632,7 @@ class LunarLander(gym.Env, EzPickle):
|
|||||||
|
|
||||||
pos = self.lander.position
|
pos = self.lander.position
|
||||||
vel = self.lander.linearVelocity
|
vel = self.lander.linearVelocity
|
||||||
|
|
||||||
state = [
|
state = [
|
||||||
(pos.x - VIEWPORT_W / SCALE / 2) / (VIEWPORT_W / SCALE / 2),
|
(pos.x - VIEWPORT_W / SCALE / 2) / (VIEWPORT_W / SCALE / 2),
|
||||||
(pos.y - (self.helipad_y + LEG_DOWN / SCALE)) / (VIEWPORT_H / SCALE / 2),
|
(pos.y - (self.helipad_y + LEG_DOWN / SCALE)) / (VIEWPORT_H / SCALE / 2),
|
||||||
|
Reference in New Issue
Block a user