hashir-Ali commited on
Commit
f386f57
·
verified ·
1 Parent(s): dcb025d

Upload folder using huggingface_hub

Browse files
.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