Upload folder using huggingface_hub
Browse files- .vscode/settings.json +3 -0
- Game2.py +219 -0
- Main2.py +177 -0
- RL.ipynb +373 -0
- __pycache__/Game.cpython-311.pyc +0 -0
- __pycache__/Game2.cpython-311.pyc +0 -0
- __pycache__/Game2.cpython-312.pyc +0 -0
- __pycache__/Main.cpython-311.pyc +0 -0
- __pycache__/Main2.cpython-311.pyc +0 -0
- model.keras +0 -0
- rocketsimulation.h5 +3 -0
- tempCodeRunnerFile.py +27 -0
.vscode/settings.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"python.REPL.enableREPLSmartSend": false
|
| 3 |
+
}
|
Game2.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pygame
|
| 2 |
+
import numpy as np
|
| 3 |
+
import Main2
|
| 4 |
+
import importlib
|
| 5 |
+
import time
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
importlib.reload(Main2)
|
| 9 |
+
class RocketLandingGameRunner:
|
| 10 |
+
def __init__(self, width=800, height=600, render=False):
|
| 11 |
+
"""Initialize the game runner with screen dimensions and rendering option."""
|
| 12 |
+
self.width = width
|
| 13 |
+
self.height = height
|
| 14 |
+
self.step_duration = 0.1 # Time step for each action
|
| 15 |
+
self.frame_time = 1.0 / 60 # Frame time for rendering (60 FPS)
|
| 16 |
+
self.white = (255, 255, 255)
|
| 17 |
+
self.render_enabled = render
|
| 18 |
+
self.game = Main2.RocketLandingGame(self.render_enabled) # Initialize the RocketLandingGame instance
|
| 19 |
+
|
| 20 |
+
# Define state and action spaces
|
| 21 |
+
self.state_space = {
|
| 22 |
+
'x': (0, self.width), # Position x
|
| 23 |
+
'y': (0, self.height), # Position y
|
| 24 |
+
'velocity_x': (-50, 50), # Velocity bounds (adjust as needed)
|
| 25 |
+
'velocity_y': (-50, 50),
|
| 26 |
+
'angle': (-180, 180) # Angle in degrees
|
| 27 |
+
}
|
| 28 |
+
self.action_space = [0, 1, 2, 3] # 0: Nothing, 1: Thrust, 2: Rotate left, 3: Rotate right
|
| 29 |
+
|
| 30 |
+
def reset(self):
|
| 31 |
+
"""Reset the game to initial state and return initial state."""
|
| 32 |
+
self.game.rocket = Main2.Rocket()
|
| 33 |
+
self.game.game_over = False
|
| 34 |
+
self.game.win = False
|
| 35 |
+
self.game.running = True
|
| 36 |
+
self.game.action_log = []
|
| 37 |
+
return self._get_state()
|
| 38 |
+
|
| 39 |
+
def _get_state(self):
|
| 40 |
+
"""Return the current state as a numpy array without exponent format."""
|
| 41 |
+
state = np.array([
|
| 42 |
+
self.game.rocket.x,
|
| 43 |
+
self.game.rocket.y,
|
| 44 |
+
self.game.rocket.velocity_x,
|
| 45 |
+
self.game.rocket.velocity_y,
|
| 46 |
+
self.game.rocket.angle
|
| 47 |
+
], dtype=np.float32)
|
| 48 |
+
|
| 49 |
+
return np.round(state, 1)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def step(self, action):
|
| 53 |
+
"""Take an action, update the game, and return (next_state, reward, done, info)."""
|
| 54 |
+
# Map action to game inputs
|
| 55 |
+
thrusting = (action == 1)
|
| 56 |
+
rotate_left = (action == 2)
|
| 57 |
+
rotate_right = (action == 3)
|
| 58 |
+
|
| 59 |
+
# Log the action
|
| 60 |
+
if action == 1:
|
| 61 |
+
self.game.add_to_action_log("Thrust activated.")
|
| 62 |
+
elif action == 2:
|
| 63 |
+
self.game.add_to_action_log("Rotation left.")
|
| 64 |
+
elif action == 3:
|
| 65 |
+
self.game.add_to_action_log("Rotation right.")
|
| 66 |
+
else:
|
| 67 |
+
self.game.add_to_action_log("Doing nothing.")
|
| 68 |
+
|
| 69 |
+
# Update game state for one step
|
| 70 |
+
reward, game_over, win = self.game.game_step(thrusting, rotate_left, rotate_right, time_step=self.step_duration)
|
| 71 |
+
|
| 72 |
+
# Get next state
|
| 73 |
+
next_state = self._get_state()
|
| 74 |
+
|
| 75 |
+
# Determine done flag
|
| 76 |
+
done=game_over
|
| 77 |
+
# Info dictionary for additional data
|
| 78 |
+
info = {'win': win, 'action_log': self.game.action_log[-1]}
|
| 79 |
+
|
| 80 |
+
# Render if enabled
|
| 81 |
+
if self.render_enabled:
|
| 82 |
+
self._render()
|
| 83 |
+
|
| 84 |
+
return next_state, reward, done
|
| 85 |
+
|
| 86 |
+
def _render(self):
|
| 87 |
+
"""Render the game state."""
|
| 88 |
+
self.game.screen.fill(self.white)
|
| 89 |
+
|
| 90 |
+
self.game.draw_terrain()
|
| 91 |
+
self.game.rocket.draw(self.game.screen)
|
| 92 |
+
|
| 93 |
+
self.game.draw_info()
|
| 94 |
+
|
| 95 |
+
if self.game.game_over:
|
| 96 |
+
if self.game.win:
|
| 97 |
+
win_text = pygame.font.SysFont('Arial', 40).render(
|
| 98 |
+
"You Landed Safely!", True, (0, 128, 0)
|
| 99 |
+
)
|
| 100 |
+
self.game.screen.blit(
|
| 101 |
+
win_text, (self.width // 2 - win_text.get_width() // 2, self.height // 2 - 100)
|
| 102 |
+
)
|
| 103 |
+
else:
|
| 104 |
+
crash_text = pygame.font.SysFont('Arial', 40).render(
|
| 105 |
+
"Crash! Try Again.", True, (255, 0, 0)
|
| 106 |
+
)
|
| 107 |
+
self.game.screen.blit(
|
| 108 |
+
crash_text, (self.width // 2 - crash_text.get_width() // 2, self.height // 2 - 100)
|
| 109 |
+
)
|
| 110 |
+
self.game.draw_restart_button()
|
| 111 |
+
if self.render_enabled:
|
| 112 |
+
pygame.display.update()
|
| 113 |
+
self.game.clock.tick(30)
|
| 114 |
+
pygame.event.pump()
|
| 115 |
+
|
| 116 |
+
def run(self, agent=None):
|
| 117 |
+
if agent is None:
|
| 118 |
+
# Original human-controlled game loop
|
| 119 |
+
while self.game.running:
|
| 120 |
+
step_start_time = time.time()
|
| 121 |
+
thrusting = False
|
| 122 |
+
rotate_left = False
|
| 123 |
+
rotate_right = False
|
| 124 |
+
reset_game = False
|
| 125 |
+
action_logged = False
|
| 126 |
+
|
| 127 |
+
for event in pygame.event.get():
|
| 128 |
+
if event.type == pygame.QUIT:
|
| 129 |
+
self.game.running = False
|
| 130 |
+
if event.type == pygame.MOUSEBUTTONDOWN and self.game.game_over:
|
| 131 |
+
mouse_pos = pygame.mouse.get_pos()
|
| 132 |
+
if self.game.draw_restart_button().collidepoint(mouse_pos):
|
| 133 |
+
reset_game = True
|
| 134 |
+
self.game.add_to_action_log("Game reset by user.")
|
| 135 |
+
action_logged = True
|
| 136 |
+
|
| 137 |
+
if not self.game.game_over and not reset_game:
|
| 138 |
+
keys = pygame.key.get_pressed()
|
| 139 |
+
if keys[pygame.K_w] and not action_logged:
|
| 140 |
+
thrusting = True
|
| 141 |
+
self.game.add_to_action_log("Thrust activated.")
|
| 142 |
+
action_logged = True
|
| 143 |
+
elif keys[pygame.K_a] and not action_logged:
|
| 144 |
+
rotate_left = True
|
| 145 |
+
self.game.add_to_action_log("Rotation left.")
|
| 146 |
+
action_logged = True
|
| 147 |
+
elif keys[pygame.K_d] and not action_logged:
|
| 148 |
+
rotate_right = True
|
| 149 |
+
self.game.add_to_action_log("Rotation right.")
|
| 150 |
+
action_logged = True
|
| 151 |
+
elif keys[pygame.K_r] and not action_logged:
|
| 152 |
+
reset_game = True
|
| 153 |
+
self.game.add_to_action_log("Game reset by 'R' key.")
|
| 154 |
+
action_logged = True
|
| 155 |
+
|
| 156 |
+
if not action_logged:
|
| 157 |
+
self.game.add_to_action_log("Doing nothing.")
|
| 158 |
+
|
| 159 |
+
if reset_game:
|
| 160 |
+
self.reset()
|
| 161 |
+
|
| 162 |
+
while time.time() - step_start_time < self.step_duration:
|
| 163 |
+
if not reset_game and not self.game.game_over:
|
| 164 |
+
reward, game_over, win = self.game.game_step(
|
| 165 |
+
thrusting, rotate_left, rotate_right, time_step=self.frame_time
|
| 166 |
+
)
|
| 167 |
+
print(reward)
|
| 168 |
+
else:
|
| 169 |
+
reward, game_over, win = 0, self.game.game_over, self.game.win
|
| 170 |
+
if self.render_enabled:
|
| 171 |
+
self._render()
|
| 172 |
+
|
| 173 |
+
print("\nAction Log:")
|
| 174 |
+
for entry in self.game.action_log:
|
| 175 |
+
print(entry)
|
| 176 |
+
pygame.quit()
|
| 177 |
+
|
| 178 |
+
else:
|
| 179 |
+
# RL agent-controlled loop
|
| 180 |
+
state = self.reset()
|
| 181 |
+
total_reward = 0
|
| 182 |
+
np.set_printoptions(suppress=True)
|
| 183 |
+
while self.game.running:
|
| 184 |
+
# if self.render_enabled:
|
| 185 |
+
state_input = np.expand_dims(state, axis=0) # make it shape (1,5)
|
| 186 |
+
action_values = agent(state_input) # get q_values
|
| 187 |
+
action = np.argmax(action_values.numpy()[0]) # pick best action
|
| 188 |
+
# action = np.random.choice(self.action_space)
|
| 189 |
+
next_state, reward, done = self.step(action)
|
| 190 |
+
total_reward += reward
|
| 191 |
+
# agent.update(state, action, reward, next_state, done) # Update agent's policy
|
| 192 |
+
state = next_state
|
| 193 |
+
if done:
|
| 194 |
+
break
|
| 195 |
+
start_time = time.time()
|
| 196 |
+
button_rect = self.game.draw_restart_button()
|
| 197 |
+
|
| 198 |
+
# Keep checking for mouse events during the 5-second window
|
| 199 |
+
while time.time() - start_time < 5:
|
| 200 |
+
for event in pygame.event.get():
|
| 201 |
+
if event.type == pygame.MOUSEBUTTONDOWN:
|
| 202 |
+
mouse_pos = event.pos
|
| 203 |
+
if button_rect.collidepoint(mouse_pos):
|
| 204 |
+
# If the restart button is clicked, restart the game
|
| 205 |
+
game_runner2 = RocketLandingGameRunner(width=800, height=600, render=True)
|
| 206 |
+
game_runner2.run(agent)
|
| 207 |
+
return # Exit to avoid pygame.quit() being called twice
|
| 208 |
+
elif event.type == pygame.QUIT:
|
| 209 |
+
pygame.quit()
|
| 210 |
+
return
|
| 211 |
+
|
| 212 |
+
pygame.display.update() # Make sure the display is updated
|
| 213 |
+
|
| 214 |
+
# If no click within 5 seconds, quit the game
|
| 215 |
+
pygame.quit()
|
| 216 |
+
|
| 217 |
+
def get_action_log(self):
|
| 218 |
+
"""Return the current action log."""
|
| 219 |
+
return self.game.action_log
|
Main2.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pygame
|
| 2 |
+
import math
|
| 3 |
+
import random
|
| 4 |
+
import numpy as np
|
| 5 |
+
import time
|
| 6 |
+
|
| 7 |
+
# Game constants
|
| 8 |
+
WIDTH, HEIGHT = 800, 600
|
| 9 |
+
WHITE = (255, 255, 255)
|
| 10 |
+
RED = (255, 0, 0)
|
| 11 |
+
BROWN = (139, 69, 19)
|
| 12 |
+
|
| 13 |
+
# Terrain generation
|
| 14 |
+
terrain_height = 50
|
| 15 |
+
terrain_points = [terrain_height + random.randint(5, 15) for _ in range(WIDTH // 50)]
|
| 16 |
+
terrain = np.interp(range(WIDTH), np.linspace(0, WIDTH, len(terrain_points)), terrain_points)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class Rocket:
|
| 20 |
+
def __init__(self):
|
| 21 |
+
# Rocket properties
|
| 22 |
+
self.width = 40
|
| 23 |
+
self.height = 80
|
| 24 |
+
self.x = WIDTH // 2
|
| 25 |
+
self.y = HEIGHT // 2 # Start from middle height
|
| 26 |
+
self.angle = 0
|
| 27 |
+
self.velocity_x = 0
|
| 28 |
+
self.velocity_y = 0
|
| 29 |
+
self.gravity = 10 # Gravity pulls downward, adjusted for faster game
|
| 30 |
+
self.thrust_power = 30 # Thrust power, adjusted for faster game
|
| 31 |
+
self.angular_velocity = 0
|
| 32 |
+
self.thrusting = False # Flag for thrusting
|
| 33 |
+
self.rotation_speed = 1 # Rotation speed, adjusted for faster game
|
| 34 |
+
|
| 35 |
+
def update(self, thrusting, rotate_left, rotate_right, time_step=0.1):
|
| 36 |
+
"""Update the rocket state based on user inputs."""
|
| 37 |
+
self.thrusting = thrusting # Update thrusting flag
|
| 38 |
+
|
| 39 |
+
# Apply gravity to velocity_y (pulls downward)
|
| 40 |
+
self.velocity_y -= self.gravity * time_step
|
| 41 |
+
|
| 42 |
+
# Apply thrust only when thrusting is active
|
| 43 |
+
if self.thrusting:
|
| 44 |
+
# Thrust aligns with rocket's nose direction
|
| 45 |
+
self.velocity_y += self.thrust_power * math.cos(math.radians(self.angle)) * time_step
|
| 46 |
+
self.velocity_x += self.thrust_power * math.sin(math.radians(self.angle)) * time_step
|
| 47 |
+
|
| 48 |
+
# Update position based on velocity
|
| 49 |
+
self.x += self.velocity_x * time_step
|
| 50 |
+
self.y += self.velocity_y * time_step
|
| 51 |
+
|
| 52 |
+
# self.x = max(0, min(self.x, WIDTH))
|
| 53 |
+
# self.y = max(0, min(self.y, HEIGHT))
|
| 54 |
+
# Apply rotation, scaled by time_step
|
| 55 |
+
if rotate_left:
|
| 56 |
+
self.angle += self.rotation_speed * time_step
|
| 57 |
+
if rotate_right:
|
| 58 |
+
self.angle -= self.rotation_speed * time_step
|
| 59 |
+
|
| 60 |
+
def draw(self, screen):
|
| 61 |
+
"""Draw the rocket on the screen."""
|
| 62 |
+
rocket_surface = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
|
| 63 |
+
pygame.draw.polygon(rocket_surface, RED, [
|
| 64 |
+
(self.width // 2, 0), # Nose
|
| 65 |
+
(0, self.height - 20), # Bottom left
|
| 66 |
+
(self.width, self.height - 20) # Bottom right
|
| 67 |
+
])
|
| 68 |
+
pygame.draw.rect(rocket_surface, (0, 0, 0), (self.width // 4, self.height - 20, self.width // 2, 20)) # Body
|
| 69 |
+
|
| 70 |
+
# Draw thrust flames if thrusting
|
| 71 |
+
if self.thrusting: # Only draw flames when thrusting
|
| 72 |
+
pygame.draw.polygon(rocket_surface, (255, 165, 0), [
|
| 73 |
+
(self.width // 4, self.height - 20),
|
| 74 |
+
(self.width // 2, self.height),
|
| 75 |
+
(3 * self.width // 4, self.height - 20)
|
| 76 |
+
])
|
| 77 |
+
|
| 78 |
+
rotated_rocket = pygame.transform.rotate(rocket_surface, self.angle)
|
| 79 |
+
rect = rotated_rocket.get_rect(center=(self.x, HEIGHT - self.y)) # Invert y for drawing
|
| 80 |
+
screen.blit(rotated_rocket, rect.topleft)
|
| 81 |
+
def state(self):
|
| 82 |
+
return [self.x, self.y, self.angle, self.velocity_x, self.velocity_y]
|
| 83 |
+
|
| 84 |
+
class RocketLandingGame:
|
| 85 |
+
def __init__(self,render):
|
| 86 |
+
self.render=render
|
| 87 |
+
if render:
|
| 88 |
+
pygame.init()
|
| 89 |
+
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
|
| 90 |
+
pygame.display.set_caption("Rocket Landing Simulation")
|
| 91 |
+
self.clock = pygame.time.Clock()
|
| 92 |
+
self.running = True
|
| 93 |
+
self.game_over = False
|
| 94 |
+
self.win = False
|
| 95 |
+
self.rocket = Rocket() # Create the rocket instance
|
| 96 |
+
self.action_log = [] # Action log to track the game's state
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def draw_terrain(self):
|
| 100 |
+
"""Draw the terrain at the bottom of the screen."""
|
| 101 |
+
for i in range(len(terrain) - 1):
|
| 102 |
+
pygame.draw.line(self.screen, BROWN, (i, HEIGHT - terrain[i]), (i + 1, HEIGHT - terrain[i + 1]), 3)
|
| 103 |
+
def returnState(self):
|
| 104 |
+
return self.rocket.state()
|
| 105 |
+
|
| 106 |
+
def game_step(self, thrusting, rotate_left, rotate_right, time_step=0.5):
|
| 107 |
+
"""Perform one step in the game (update physics, game state)."""
|
| 108 |
+
if not self.game_over:
|
| 109 |
+
reward = 0 # Initialize reward
|
| 110 |
+
self.rocket.update(thrusting, rotate_left, rotate_right, time_step)
|
| 111 |
+
|
| 112 |
+
# Check for landing or crash
|
| 113 |
+
rocket_bottom_y = self.rocket.y - self.rocket.height // 2
|
| 114 |
+
x_index = int(self.rocket.x)
|
| 115 |
+
terrain_y_at_x = terrain[x_index] if 0 <= x_index < WIDTH else float('inf')
|
| 116 |
+
|
| 117 |
+
if rocket_bottom_y <= terrain_y_at_x:
|
| 118 |
+
self.rocket.y = terrain_y_at_x + self.rocket.height // 2
|
| 119 |
+
|
| 120 |
+
if abs(self.rocket.velocity_y) <= 5.0 and abs(self.rocket.velocity_x) <= 5 and abs(self.rocket.angle) <= 10:
|
| 121 |
+
self.game_over = True
|
| 122 |
+
self.win = True # Successful landing
|
| 123 |
+
|
| 124 |
+
reward = 5 # Positive reward for successful landing
|
| 125 |
+
else:
|
| 126 |
+
self.game_over = True # Rocket destroyed
|
| 127 |
+
reward = -2 # Negative reward for crash
|
| 128 |
+
else:
|
| 129 |
+
if thrusting:
|
| 130 |
+
reward -= 0.002
|
| 131 |
+
else:
|
| 132 |
+
reward=0
|
| 133 |
+
reward += (5 - abs(self.rocket.velocity_y)) * 0.01 # Reward for reducing vertical speed
|
| 134 |
+
reward += (5 - abs(self.rocket.velocity_x)) * 0.01 # Reward for reducing horizontal speed
|
| 135 |
+
reward+=1/(self.rocket.y-99)
|
| 136 |
+
if abs(self.rocket.angle) <= 5: # Small angle threshold
|
| 137 |
+
reward += 0.02 # Reward for improving angle control
|
| 138 |
+
else:
|
| 139 |
+
reward -= 0.02 # Small penalty for poor angle control
|
| 140 |
+
if self.rocket.y>400:
|
| 141 |
+
self.game_over = True
|
| 142 |
+
reward = -2
|
| 143 |
+
if self.rocket.x>500 or self.rocket.x<300:
|
| 144 |
+
self.game_over = True
|
| 145 |
+
reward = -2
|
| 146 |
+
|
| 147 |
+
return reward, self.game_over, self.win
|
| 148 |
+
|
| 149 |
+
def draw_info(self):
|
| 150 |
+
"""Draw the game info (coordinates, angle, velocity) on the screen."""
|
| 151 |
+
font = pygame.font.SysFont('Arial', 18)
|
| 152 |
+
info_text = [
|
| 153 |
+
f"X: {self.rocket.x:.2f} Y: {self.rocket.y:.2f}",
|
| 154 |
+
f"Angle: {self.rocket.angle:.2f}",
|
| 155 |
+
f"Velocity X: {self.rocket.velocity_x:.2f} Velocity Y: {self.rocket.velocity_y:.2f}",
|
| 156 |
+
f"Thrusting: {'Yes' if self.rocket.thrusting else 'No'}",
|
| 157 |
+
]
|
| 158 |
+
for i, text in enumerate(info_text):
|
| 159 |
+
label = font.render(text, True, (0, 0, 0))
|
| 160 |
+
self.screen.blit(label, (10, 10 + i * 20)) # Display info
|
| 161 |
+
|
| 162 |
+
def draw_restart_button(self):
|
| 163 |
+
"""Draw the restart button if the game is over."""
|
| 164 |
+
font = pygame.font.SysFont('Arial', 30)
|
| 165 |
+
restart_text = font.render("Restart", True, (255, 255, 255))
|
| 166 |
+
button_rect = pygame.Rect(WIDTH // 2 - 75, HEIGHT // 2 - 50, 150, 50)
|
| 167 |
+
pygame.draw.rect(self.screen, (0, 128, 0), button_rect) # Green button
|
| 168 |
+
self.screen.blit(restart_text, button_rect.move(25, 10)) # Center the text on button
|
| 169 |
+
|
| 170 |
+
return button_rect
|
| 171 |
+
|
| 172 |
+
def add_to_action_log(self, action):
|
| 173 |
+
"""Add actions to the action log."""
|
| 174 |
+
timestamp = time.strftime("%H:%M:%S", time.gmtime(time.time()))
|
| 175 |
+
self.action_log.append(f"{action}")
|
| 176 |
+
|
| 177 |
+
|
RL.ipynb
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": null,
|
| 6 |
+
"id": "52a17104",
|
| 7 |
+
"metadata": {},
|
| 8 |
+
"outputs": [],
|
| 9 |
+
"source": [
|
| 10 |
+
"import importlib\n",
|
| 11 |
+
"import Game2 \n",
|
| 12 |
+
"import numpy as np\n",
|
| 13 |
+
"\n",
|
| 14 |
+
"importlib.reload(Game2) \n",
|
| 15 |
+
"game_runner = Game2.RocketLandingGameRunner(width=800, height=600, render=False)\n",
|
| 16 |
+
"\n"
|
| 17 |
+
]
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"cell_type": "code",
|
| 21 |
+
"execution_count": null,
|
| 22 |
+
"id": "f0ad93a0",
|
| 23 |
+
"metadata": {},
|
| 24 |
+
"outputs": [],
|
| 25 |
+
"source": [
|
| 26 |
+
"MEMORY_SIZE = 100_000 \n",
|
| 27 |
+
"GAMMA = 0.995 \n",
|
| 28 |
+
"ALPHA = 1e-3 \n",
|
| 29 |
+
"NUM_STEPS_FOR_UPDATE = 4 \n",
|
| 30 |
+
"MINIBATCH_SIZE = 64 \n",
|
| 31 |
+
"E_DECAY = 0.995 \n",
|
| 32 |
+
"E_MIN = 0.001 "
|
| 33 |
+
]
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"cell_type": "code",
|
| 37 |
+
"execution_count": 9,
|
| 38 |
+
"id": "663d87d0",
|
| 39 |
+
"metadata": {},
|
| 40 |
+
"outputs": [
|
| 41 |
+
{
|
| 42 |
+
"name": "stdout",
|
| 43 |
+
"output_type": "stream",
|
| 44 |
+
"text": [
|
| 45 |
+
"5 4\n"
|
| 46 |
+
]
|
| 47 |
+
}
|
| 48 |
+
],
|
| 49 |
+
"source": [
|
| 50 |
+
"state_space_size=len(game_runner.state_space)\n",
|
| 51 |
+
"action_space_size=len(game_runner.action_space)\n",
|
| 52 |
+
"print(state_space_size,action_space_size)"
|
| 53 |
+
]
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"cell_type": "code",
|
| 57 |
+
"execution_count": 10,
|
| 58 |
+
"id": "a8102d63",
|
| 59 |
+
"metadata": {},
|
| 60 |
+
"outputs": [
|
| 61 |
+
{
|
| 62 |
+
"name": "stdout",
|
| 63 |
+
"output_type": "stream",
|
| 64 |
+
"text": [
|
| 65 |
+
"[400. 300. 0. 0. 0.]\n"
|
| 66 |
+
]
|
| 67 |
+
}
|
| 68 |
+
],
|
| 69 |
+
"source": [
|
| 70 |
+
"inital_state = game_runner.reset()\n",
|
| 71 |
+
"print(inital_state)"
|
| 72 |
+
]
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"cell_type": "code",
|
| 76 |
+
"execution_count": 12,
|
| 77 |
+
"id": "c1032039",
|
| 78 |
+
"metadata": {},
|
| 79 |
+
"outputs": [],
|
| 80 |
+
"source": [
|
| 81 |
+
"from collections import deque,namedtuple\n",
|
| 82 |
+
"import tensorflow as tf\n",
|
| 83 |
+
"q_network = tf.keras.Sequential([\n",
|
| 84 |
+
" tf.keras.layers.Input(shape=(state_space_size,)),\n",
|
| 85 |
+
" tf.keras.layers.Dense(64, activation='relu'),\n",
|
| 86 |
+
" tf.keras.layers.Dense(64, activation='relu'),\n",
|
| 87 |
+
" tf.keras.layers.Dense(64, activation='relu'),\n",
|
| 88 |
+
" tf.keras.layers.Dense(action_space_size, activation='linear')\n",
|
| 89 |
+
"])\n",
|
| 90 |
+
"target_network = tf.keras.Sequential([\n",
|
| 91 |
+
" tf.keras.layers.Input(shape=(state_space_size,)),\n",
|
| 92 |
+
" tf.keras.layers.Dense(64, activation='relu'),\n",
|
| 93 |
+
" tf.keras.layers.Dense(64, activation='relu'),\n",
|
| 94 |
+
" tf.keras.layers.Dense(64, activation='relu'),\n",
|
| 95 |
+
" tf.keras.layers.Dense(action_space_size, activation='linear')\n",
|
| 96 |
+
"])\n",
|
| 97 |
+
"optimizer = tf.keras.optimizers.Adam(learning_rate=ALPHA)"
|
| 98 |
+
]
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"cell_type": "code",
|
| 102 |
+
"execution_count": 13,
|
| 103 |
+
"id": "d52372ee",
|
| 104 |
+
"metadata": {},
|
| 105 |
+
"outputs": [],
|
| 106 |
+
"source": [
|
| 107 |
+
"Experience = namedtuple(\"Experience\", field_names=[\"state\", \"action\", \"reward\", \"next_state\", \"done\"])"
|
| 108 |
+
]
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"cell_type": "code",
|
| 112 |
+
"execution_count": 14,
|
| 113 |
+
"id": "b4aa9676",
|
| 114 |
+
"metadata": {},
|
| 115 |
+
"outputs": [],
|
| 116 |
+
"source": [
|
| 117 |
+
"def compute_loss(experience,GAMMA,q_network,target_network):\n",
|
| 118 |
+
" states, actions, rewards, next_states, done_vals = experience\n",
|
| 119 |
+
" max_q=tf.reduce_max(target_network(next_states), axis=1)\n",
|
| 120 |
+
" target_q = rewards + (1 - done_vals) * GAMMA * max_q\n",
|
| 121 |
+
" q_values = q_network(states)\n",
|
| 122 |
+
" q_values = tf.gather_nd(q_values, tf.stack([tf.range(q_values.shape[0]),\n",
|
| 123 |
+
" tf.cast(actions, tf.int32)], axis=1))\n",
|
| 124 |
+
" loss=tf.keras.losses.MSE(target_q, q_values)\n",
|
| 125 |
+
" return loss"
|
| 126 |
+
]
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"cell_type": "code",
|
| 130 |
+
"execution_count": 15,
|
| 131 |
+
"id": "2131a447",
|
| 132 |
+
"metadata": {},
|
| 133 |
+
"outputs": [],
|
| 134 |
+
"source": [
|
| 135 |
+
"@tf.function\n",
|
| 136 |
+
"def Learning(experience,GAMMA):\n",
|
| 137 |
+
" with tf.GradientTape() as tape:\n",
|
| 138 |
+
" loss = compute_loss(experience, GAMMA, q_network, target_network)\n",
|
| 139 |
+
" gradients = tape.gradient(loss, q_network.trainable_variables)\n",
|
| 140 |
+
" optimizer.apply_gradients(zip(gradients, q_network.trainable_variables))\n",
|
| 141 |
+
" tau = 0.1\n",
|
| 142 |
+
" for target_param, q_param in zip(target_network.trainable_variables, q_network.trainable_variables):\n",
|
| 143 |
+
" target_param.assign(tau * q_param + (1.0 - tau) * target_param)\n",
|
| 144 |
+
"\n",
|
| 145 |
+
"\n"
|
| 146 |
+
]
|
| 147 |
+
},
|
| 148 |
+
{
|
| 149 |
+
"cell_type": "code",
|
| 150 |
+
"execution_count": 16,
|
| 151 |
+
"id": "01f4f57a",
|
| 152 |
+
"metadata": {},
|
| 153 |
+
"outputs": [],
|
| 154 |
+
"source": [
|
| 155 |
+
"import random\n",
|
| 156 |
+
"def get_action(q_values, epsilon=0.01): \n",
|
| 157 |
+
" if random.random() > epsilon:\n",
|
| 158 |
+
" return np.argmax(q_values.numpy()[0])\n",
|
| 159 |
+
" else:\n",
|
| 160 |
+
" return random.choice(np.arange(4))"
|
| 161 |
+
]
|
| 162 |
+
},
|
| 163 |
+
{
|
| 164 |
+
"cell_type": "code",
|
| 165 |
+
"execution_count": 17,
|
| 166 |
+
"id": "133eaed1",
|
| 167 |
+
"metadata": {},
|
| 168 |
+
"outputs": [],
|
| 169 |
+
"source": [
|
| 170 |
+
"def check_update_conditions(t, num_steps_upd, memory_buffer):\n",
|
| 171 |
+
" if (t + 1) % num_steps_upd == 0 and len(memory_buffer) > MINIBATCH_SIZE:\n",
|
| 172 |
+
" return True\n",
|
| 173 |
+
" else:\n",
|
| 174 |
+
" return False"
|
| 175 |
+
]
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
"cell_type": "code",
|
| 179 |
+
"execution_count": 18,
|
| 180 |
+
"id": "c98ea751",
|
| 181 |
+
"metadata": {},
|
| 182 |
+
"outputs": [],
|
| 183 |
+
"source": [
|
| 184 |
+
"def get_experiences(memory_buffer):\n",
|
| 185 |
+
" experiences = random.sample(memory_buffer, k=MINIBATCH_SIZE)\n",
|
| 186 |
+
" states = tf.convert_to_tensor(np.array([e.state for e in experiences if e is not None]),dtype=tf.float32)\n",
|
| 187 |
+
" actions = tf.convert_to_tensor(np.array([e.action for e in experiences if e is not None]), dtype=tf.float32)\n",
|
| 188 |
+
" rewards = tf.convert_to_tensor(np.array([e.reward for e in experiences if e is not None]), dtype=tf.float32)\n",
|
| 189 |
+
" next_states = tf.convert_to_tensor(np.array([e.next_state for e in experiences if e is not None]),dtype=tf.float32)\n",
|
| 190 |
+
" done_vals = tf.convert_to_tensor(np.array([e.done for e in experiences if e is not None]).astype(np.uint8),\n",
|
| 191 |
+
" dtype=tf.float32)\n",
|
| 192 |
+
" return (states, actions, rewards, next_states, done_vals)"
|
| 193 |
+
]
|
| 194 |
+
},
|
| 195 |
+
{
|
| 196 |
+
"cell_type": "code",
|
| 197 |
+
"execution_count": 19,
|
| 198 |
+
"id": "bd6921de",
|
| 199 |
+
"metadata": {},
|
| 200 |
+
"outputs": [],
|
| 201 |
+
"source": [
|
| 202 |
+
"def get_new_eps(epsilon):\n",
|
| 203 |
+
" return max(E_MIN, E_DECAY*epsilon)"
|
| 204 |
+
]
|
| 205 |
+
},
|
| 206 |
+
{
|
| 207 |
+
"cell_type": "code",
|
| 208 |
+
"execution_count": null,
|
| 209 |
+
"id": "b1085588",
|
| 210 |
+
"metadata": {},
|
| 211 |
+
"outputs": [
|
| 212 |
+
{
|
| 213 |
+
"name": "stdout",
|
| 214 |
+
"output_type": "stream",
|
| 215 |
+
"text": [
|
| 216 |
+
"Episode 100 | Total point average of the last 100 episodes: 11.81\n",
|
| 217 |
+
"Episode 136 | Total point average of the last 100 episodes: 29.85"
|
| 218 |
+
]
|
| 219 |
+
},
|
| 220 |
+
{
|
| 221 |
+
"name": "stderr",
|
| 222 |
+
"output_type": "stream",
|
| 223 |
+
"text": [
|
| 224 |
+
"WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')` or `keras.saving.save_model(model, 'my_model.keras')`. \n"
|
| 225 |
+
]
|
| 226 |
+
},
|
| 227 |
+
{
|
| 228 |
+
"name": "stdout",
|
| 229 |
+
"output_type": "stream",
|
| 230 |
+
"text": [
|
| 231 |
+
"Episode 137 | Total point average of the last 100 episodes: 30.15\n",
|
| 232 |
+
"\n",
|
| 233 |
+
"Environment solved in 137 episodes!\n",
|
| 234 |
+
"\n",
|
| 235 |
+
"Total Runtime: 138.98 s (2.32 min)\n"
|
| 236 |
+
]
|
| 237 |
+
}
|
| 238 |
+
],
|
| 239 |
+
"source": [
|
| 240 |
+
"import time\n",
|
| 241 |
+
"start = time.time()\n",
|
| 242 |
+
"num_episodes = 2000\n",
|
| 243 |
+
"max_num_timesteps = 1000\n",
|
| 244 |
+
"\n",
|
| 245 |
+
"total_point_history = []\n",
|
| 246 |
+
"\n",
|
| 247 |
+
"num_p_av = 100 \n",
|
| 248 |
+
"epsilon = 1.0 \n",
|
| 249 |
+
"\n",
|
| 250 |
+
"memory_buffer = deque(maxlen=MEMORY_SIZE)\n",
|
| 251 |
+
"target_network.set_weights(q_network.get_weights())\n",
|
| 252 |
+
"for i in range(num_episodes):\n",
|
| 253 |
+
" state=game_runner.reset()\n",
|
| 254 |
+
" total_points=0\n",
|
| 255 |
+
" for t in range(max_num_timesteps):\n",
|
| 256 |
+
" state_qn = np.expand_dims(state, axis=0) \n",
|
| 257 |
+
" q_values = q_network(state_qn)\n",
|
| 258 |
+
" action = get_action(q_values, epsilon)\n",
|
| 259 |
+
" next_state, reward, done = game_runner.step(action)\n",
|
| 260 |
+
" memory_buffer.append(Experience(state, action, reward, next_state, done))\n",
|
| 261 |
+
" update=check_update_conditions(t, NUM_STEPS_FOR_UPDATE, memory_buffer)\n",
|
| 262 |
+
" if update:\n",
|
| 263 |
+
" experience = get_experiences(memory_buffer)\n",
|
| 264 |
+
" Learning(experience, GAMMA)\n",
|
| 265 |
+
"\n",
|
| 266 |
+
" state = next_state.copy()\n",
|
| 267 |
+
" total_points += reward\n",
|
| 268 |
+
" if done:\n",
|
| 269 |
+
" break\n",
|
| 270 |
+
" total_point_history.append(total_points)\n",
|
| 271 |
+
" av_latest_points = np.mean(total_point_history[-num_p_av:])\n",
|
| 272 |
+
" epsilon = get_new_eps(epsilon)\n",
|
| 273 |
+
" \n",
|
| 274 |
+
"\n",
|
| 275 |
+
" print(f\"\\rEpisode {i+1} | Total point average of the last {num_p_av} episodes: {av_latest_points:.2f}\", end=\"\")\n",
|
| 276 |
+
"\n",
|
| 277 |
+
" if (i+1) % num_p_av == 0:\n",
|
| 278 |
+
" print(f\"\\rEpisode {i+1} | Total point average of the last {num_p_av} episodes: {av_latest_points:.2f}\")\n",
|
| 279 |
+
"\n",
|
| 280 |
+
" if av_latest_points >= 30.0:\n",
|
| 281 |
+
" print(f\"\\n\\nEnvironment solved in {i+1} episodes!\")\n",
|
| 282 |
+
" q_network.save('rocket_simulation.h5')\n",
|
| 283 |
+
" break\n",
|
| 284 |
+
" \n",
|
| 285 |
+
"tot_time = time.time() - start\n",
|
| 286 |
+
"\n",
|
| 287 |
+
"print(f\"\\nTotal Runtime: {tot_time:.2f} s ({(tot_time/60):.2f} min)\")\n",
|
| 288 |
+
" "
|
| 289 |
+
]
|
| 290 |
+
},
|
| 291 |
+
{
|
| 292 |
+
"cell_type": "code",
|
| 293 |
+
"execution_count": 35,
|
| 294 |
+
"id": "4e110ec2",
|
| 295 |
+
"metadata": {},
|
| 296 |
+
"outputs": [
|
| 297 |
+
{
|
| 298 |
+
"name": "stderr",
|
| 299 |
+
"output_type": "stream",
|
| 300 |
+
"text": [
|
| 301 |
+
"WARNING:absl:No training configuration found in the save file, so the model was *not* compiled. Compile it manually.\n"
|
| 302 |
+
]
|
| 303 |
+
}
|
| 304 |
+
],
|
| 305 |
+
"source": [
|
| 306 |
+
"from tensorflow.keras.models import load_model\n",
|
| 307 |
+
"\n",
|
| 308 |
+
"importlib.reload(Game2) \n",
|
| 309 |
+
"game_runner = Game2.RocketLandingGameRunner(width=800, height=600, render=False)\n",
|
| 310 |
+
"# Load the saved model\n",
|
| 311 |
+
"q_network = load_model('rocketsimulation.h5')\n",
|
| 312 |
+
"\n",
|
| 313 |
+
"game_runner2=Game2.RocketLandingGameRunner(width=800, height=600, render=True)\n",
|
| 314 |
+
"game_runner2.run(q_network)"
|
| 315 |
+
]
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"cell_type": "code",
|
| 319 |
+
"execution_count": 26,
|
| 320 |
+
"id": "bb53647c",
|
| 321 |
+
"metadata": {},
|
| 322 |
+
"outputs": [
|
| 323 |
+
{
|
| 324 |
+
"data": {
|
| 325 |
+
"image/png": "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EREREZEJgyMiIiIiEwZHRERERCYMjoiIiIhMGBwRkU9Mnz7d5ezjaa1MmTLy/vvvSyA6cOCAZMqUyThtAhFlHAZHROQTjzzyiOzevdvXs0FE5CKr611EROkve/bs6kIZC2d9x0mviSh5zBwRUarOnj1u3Dh1VnUEOLVq1ZIffvjBeHzZsmWqSeiPP/6QmjVrSlhYmNx1112yffv2ZJvV/v77b7n33nslV65c6szaOEP5hg0bjMd//PFHqVatmoSGhqrmsnfffddpnnB27vvvv1/ND+brm2++cZnvCxcuyJNPPikFCxZU79GiRQv1vrdq2vrpp5/UvIWHh6vPGhkZaUwzatQoqV27ttPz0JSHedR69+4tnTp1krfeeksKFy6sPveYMWMkNjZWhg4dKvny5ZMSJUrItGnTXOZh165d0rhxY7UMq1evLsuXL3d6HMu0Xbt2kjNnTvXaPXv2lDNnzhiPN2/eXJ577jkZOHCgFChQQNq0aZPs5yWiBAyOiMhrCIy+/PJLmTp1quzYsUMGDRokjz32mMuOGzt+BDHr169XAQmCl5s3b7p9zUcffVQFCJh248aNMmzYMMmWLZt6DLcffvhh6datm2zbtk0FJK+//roKsMwByOHDh2Xp0qUqUJsyZYoKmMweeughdd+8efPUa9atW1datmwp586dS/Hzvvrqq/LSSy+p+p877rhDunfvrgIbbyxZskSOHTsmK1askIkTJ8rIkSOlQ4cOkjdvXlm7dq30799fnn76aTly5IjLMhwyZIhs3rxZGjVqpJbh2bNnjWAPAV6dOnVUIDl//nw5efKkWlZmM2bMUNmiv/76S31nRHQLaXL6WiKyjevXr6uzha9evdrp/r59+zq6d++u/l+6dKk62/rMmTONx8+ePavOnv3999+r2zgTe0REhPF4rly5HNOnT3f7nj169HDcd999TvfhLN1Vq1ZV/0dFRan3W7dunfH4zp071X3vvfeeur1y5UpH7ty51fyblS9f3vHJJ5+4fd/9+/er1/j888+N+3bs2KHuw+vDyJEjHbVq1XJ6Ht4TZxzXevXqpW7HxcUZ91WqVMnRpEkT43ZsbKwjR44cju+++87pvcePH29MgzOOlyhRwvHf//5X3R47dqyjdevWTu99+PBh9TwsE2jWrJmjTp06bj8fEbnHmiMi8sqePXvk6tWrct9997nUsiCDYYZMh4amo0qVKsnOnTvdvu7gwYNVk9dXX30lrVq1Ulme8uXLq8fwnI4dOzpNf/fdd6vmq7i4OPV41qxZVVOcVrlyZZdmu8uXL0v+/PmdXufatWuyd+/eFD8zmga1okWLqmtkoPAenkKTYObMScl6NIGhmUzLkiWLmjdrtsu8DPEZ69evbyxDfCZkytCkZoXPhCwXmJcLEd0agyMi8goCDEA9UfHixZ0eQz1QaqGprEePHup10eyFZqeZM2dK586dJa3mG4EN6qGsbjWkgG7eA9Qg6borQMDjcCBZk8Rd06H5NfTruLtPv66nnwnNbP/9739dHtNBHOTIkcPj1yQiBkdE5KWqVauqIOjQoUPSrFmzFKdds2aNlCpVSv1//vx51XW/SpUqyU6PTAcuqGFCXQ8KlBEc4TmolzHDbUyLjAsyOKgBQh3RnXfeqR6PiopSNTka6otOnDihsi/mYunbhVoqvC4CJB04peXYRFiGTZs2Vf/rz4gCa/2ZUKiOz4PPRURpgwXZROQV9CZDcTICGBT6ovlm06ZN8tFHH6nbZuiRtXjxYtWjCgXT6C2FXltWaNrCDh9ZnYMHD6rAB4XZOpBCQTJeZ+zYsSrAwvtMmjRJzQegua5t27aqoBnFzQgg0ERnHioATXVoosL7L1iwQPVEW716tSq2NveK8xZ6g50+fVomTJiglsXkyZNV5iut4PXmzJmjeq0NGDBABZlPPPGEegy3UUyOQBLLC+//559/Sp8+fVRzIxGlDoMjIvIaghT0FkOvNQQwCEzQHIYu9Gbjx4+XF198UdW8ILvy22+/uR1jB9kf9MB6/PHHVTYIva3QPX306NFGhmTWrFmqmQ11OiNGjFCBFwIuDVmmYsWKqWxWly5dpF+/flKoUCHjcWR15s6dq7IwCB7wPuj9hmAM9T+phc+PnnEIYtDNf926dUbQlhawDHHBa69atUp+/fVXFWQCPi8CSQRCrVu3lho1aqgu+2gmNNc3EZF3MqEq28vnEBGlCBkgjAuELEd6nyKEiCit8dCCiIiIyITBEREREZEJm9WIiIiITJg5IiIiIjJhcERERERkwuCIiIiIyITBEREREZEJgyMiIiIiEwZHRERERCYMjoiIiIhMGBwRERERmTA4IiIiIpIk/w83A+V+x/+FAAAAAABJRU5ErkJggg==",
|
| 326 |
+
"text/plain": [
|
| 327 |
+
"<Figure size 640x480 with 1 Axes>"
|
| 328 |
+
]
|
| 329 |
+
},
|
| 330 |
+
"metadata": {},
|
| 331 |
+
"output_type": "display_data"
|
| 332 |
+
}
|
| 333 |
+
],
|
| 334 |
+
"source": [
|
| 335 |
+
"import matplotlib as plt\n",
|
| 336 |
+
"plt.pyplot.plot(list(range(1,len(total_point_history)+1)),total_point_history)\n",
|
| 337 |
+
"plt.pyplot.title('Total Points per Episode')\n",
|
| 338 |
+
"plt.pyplot.xlabel('episode number')\n",
|
| 339 |
+
"plt.pyplot.ylabel('total points')\n",
|
| 340 |
+
"plt.pyplot.show()\n"
|
| 341 |
+
]
|
| 342 |
+
},
|
| 343 |
+
{
|
| 344 |
+
"cell_type": "code",
|
| 345 |
+
"execution_count": null,
|
| 346 |
+
"id": "93a47de7",
|
| 347 |
+
"metadata": {},
|
| 348 |
+
"outputs": [],
|
| 349 |
+
"source": []
|
| 350 |
+
}
|
| 351 |
+
],
|
| 352 |
+
"metadata": {
|
| 353 |
+
"kernelspec": {
|
| 354 |
+
"display_name": "Python 3",
|
| 355 |
+
"language": "python",
|
| 356 |
+
"name": "python3"
|
| 357 |
+
},
|
| 358 |
+
"language_info": {
|
| 359 |
+
"codemirror_mode": {
|
| 360 |
+
"name": "ipython",
|
| 361 |
+
"version": 3
|
| 362 |
+
},
|
| 363 |
+
"file_extension": ".py",
|
| 364 |
+
"mimetype": "text/x-python",
|
| 365 |
+
"name": "python",
|
| 366 |
+
"nbconvert_exporter": "python",
|
| 367 |
+
"pygments_lexer": "ipython3",
|
| 368 |
+
"version": "3.11.3"
|
| 369 |
+
}
|
| 370 |
+
},
|
| 371 |
+
"nbformat": 4,
|
| 372 |
+
"nbformat_minor": 5
|
| 373 |
+
}
|
__pycache__/Game.cpython-311.pyc
ADDED
|
Binary file (6.56 kB). View file
|
|
|
__pycache__/Game2.cpython-311.pyc
ADDED
|
Binary file (10.9 kB). View file
|
|
|
__pycache__/Game2.cpython-312.pyc
ADDED
|
Binary file (10.4 kB). View file
|
|
|
__pycache__/Main.cpython-311.pyc
ADDED
|
Binary file (7.42 kB). View file
|
|
|
__pycache__/Main2.cpython-311.pyc
ADDED
|
Binary file (11.1 kB). View file
|
|
|
model.keras
ADDED
|
Binary file (57.6 kB). View file
|
|
|
rocketsimulation.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3c6a6bc9b7b5cf82e62ad2dc2536bc62a7a85c394a93d506d7e82d8cbed649b9
|
| 3 |
+
size 59816
|
tempCodeRunnerFile.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
f rocket_bottom_y <= terrain_y_at_x:
|
| 2 |
+
self.rocket.y = terrain_y_at_x + self.rocket.height // 2
|
| 3 |
+
|
| 4 |
+
if abs(self.rocket.velocity_y) <= 5.0 and abs(self.rocket.velocity_x) <= 5 and abs(self.rocket.angle) <= 10:
|
| 5 |
+
self.game_over = True
|
| 6 |
+
self.win = True # Successful landing
|
| 7 |
+
reward = 1 # Positive reward for successful landing
|
| 8 |
+
else:
|
| 9 |
+
self.game_over = True # Rocket destroyed
|
| 10 |
+
reward = -0.6 # Negative reward for crash
|
| 11 |
+
else:
|
| 12 |
+
if thrusting:
|
| 13 |
+
reward = 0.002
|
| 14 |
+
else:
|
| 15 |
+
reward=0
|
| 16 |
+
if abs(self.rocket.angle) >5:
|
| 17 |
+
reward = -0.02
|
| 18 |
+
if abs(self.rocket.velocity_y) > 5 or abs(self.rocket.velocity_x) > 5:
|
| 19 |
+
reward = -0.02
|
| 20 |
+
if self.rocket.y>700:
|
| 21 |
+
self.game_over = True
|
| 22 |
+
reward = -0.6
|
| 23 |
+
if self.rocket.x>800 or self.rocket.x<0:
|
| 24 |
+
self.game_over = True
|
| 25 |
+
reward = -0.6
|
| 26 |
+
|
| 27 |
+
return reward, self.game_over, self.win
|