Landing with AI
At the moment, we have a playable version of the lunar lander game. However, it would be nice to relax and let an AI play the game instead of us. In this chapter, we will prompt the model to generate an AI controller that plays the Lunar Lander game. We start by asking for a version where we can choose who is controlling the lunar lander (with a randomly thrusting AI), and then we will ask for an AI controller that can safely land the lunar lander.
Choosing controller
First, we prompt the large language model to modify the Lunar Lander game so that it can be controlled by an AI. The first version of the AI controller should randomly thrust to control the lunar lander.
The following outlines the design of a Lunar Lander game. Modify the game so that the lunar lander can be controlled with the AI controller. At the start of the game, the game should ask whether to play as a human, or whether the game should be controlled by the AI. As the first version of the AI, implement an AI that simply randomly thrusts.
###
(the code from the last part)
To modify the Lunar Lander game so that it can be controlled by an AI, we need to make several changes. Specifically, we need to add the option for the player to choose between human control and AI control at the start of the game. We'll also implement a simple AI that randomly decides whether to thrust.
...
The model output continued as follows.
Model output
Here’s the updated code:
import pygame
import random
# Class to manage the overall game state
class LunarLanderGame:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Lunar Lander")
self.lander = LunarLander()
self.surface = LunarSurface()
self.ui = GameUI(self.screen, self.lander, self.surface)
self.player_controller = PlayerController(self.lander)
self.ai_controller = AIController(self.lander)
self.score_manager = ScoreManager()
self.is_running = True
self.controller = None
def start_game(self):
# Ask the player whether to use human or AI control
self.choose_controller()
# Main game loop
while self.is_running:
self.handle_events()
self.update_game_state()
self.render()
pygame.time.delay(30) # Control frame rate
def choose_controller(self):
# Display options for the player to choose between human and AI control
font = pygame.font.Font(None, 74)
human_text = font.render("Press H for Human Control", True, (255, 255, 255))
ai_text = font.render("Press A for AI Control", True, (255, 255, 255))
while True:
self.screen.fill((0, 0, 0))
self.screen.blit(human_text, (100, 200))
self.screen.blit(ai_text, (100, 300))
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.is_running = False
return
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_h:
self.controller = self.player_controller
return
elif event.key == pygame.K_a:
self.controller = self.ai_controller
return
def handle_events(self):
# Handle player input and other events
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.is_running = False
if self.controller == self.player_controller:
self.player_controller.handle_event(event)
def update_game_state(self):
# Update lander and check for collisions
if self.controller == self.ai_controller:
self.ai_controller.control_lander()
self.lander.update()
collision_status = self.surface.check_collision(self.lander)
if collision_status is not None:
self.is_running = False
self.ui.display_game_over(collision_status)
def render(self):
# Render the game UI
self.ui.render()
# Class to represent the Lunar Lander
class LunarLander:
def __init__(self):
self.position = [390, 0] # Center the lander horizontally at the top
self.velocity = [0, 0]
self.fuel = 100
self.thrusting = False
def update(self):
# Update position and velocity based on thrust and gravity
self.apply_gravity()
if self.thrusting and self.fuel > 0:
self.apply_thrust()
self.position[1] += self.velocity[1]
def apply_gravity(self):
# Simulate gravity affecting the lander
self.velocity[1] += 0.1 # Simple gravity effect
def apply_thrust(self):
# Apply thrust to counteract gravity
self.velocity[1] -= 0.5
self.fuel -= 0.1
# Class to represent the lunar surface
class LunarSurface:
def __init__(self):
# Initialize lunar surface (e.g., terrain)
self.height = 50 # Height of the terrain
def check_collision(self, lander):
# Check for collision with the lunar surface
leg_bottom = lander.position[1] + 30 # Bottom of the legs
if leg_bottom >= 600 - self.height:
if lander.velocity[1] < 3:
return "win"
else:
return "lose"
return None
# Class to manage the graphical user interface
class GameUI:
def __init__(self, screen, lander, surface):
self.screen = screen
self.lander = lander
self.surface = surface
self.font = pygame.font.Font(None, 36)
def render(self):
# Render the lander, surface, and status information
self.screen.fill((0, 0, 0)) # Fill the screen with black
# Draw the lunar lander
self.draw_lander()
# Draw the terrain as a green rectangle
pygame.draw.rect(self.screen, (0, 255, 0), (0, 600 - self.surface.height, 800, self.surface.height))
# Draw the HUD
self.render_hud()
pygame.display.flip() # Update the screen
def draw_lander(self):
# Draw the lander body as a white square
pygame.draw.rect(self.screen, (255, 255, 255), (self.lander.position[0], self.lander.position[1], 20, 20))
# Draw the dome as a white circle
pygame.draw.circle(self.screen, (255, 255, 255), (self.lander.position[0] + 10, self.lander.position[1]), 10)
# Draw the legs as white lines
pygame.draw.line(self.screen, (255, 255, 255), (self.lander.position[0], self.lander.position[1] + 20), (self.lander.position[0] - 10, self.lander.position[1] + 30))
pygame.draw.line(self.screen, (255, 255, 255), (self.lander.position[0] + 20, self.lander.position[1] + 20), (self.lander.position[0] + 30, self.lander.position[1] + 30))
# Draw the thrust flame as an upside-down triangle if thrusting
if self.lander.thrusting:
flame_color = (255, 0, 0) # Red color for the flame
flame_points = [
(self.lander.position[0] + 10, self.lander.position[1] + 40), # Bottom point
(self.lander.position[0], self.lander.position[1] + 20), # Top-left point
(self.lander.position[0] + 20, self.lander.position[1] + 20) # Top-right point
]
pygame.draw.polygon(self.screen, flame_color, flame_points)
def render_hud(self):
# Render the HUD with altitude, velocity, and fuel level
altitude = 600 - self.surface.height - (self.lander.position[1] + 30) # Adjusted for leg length
velocity = self.lander.velocity[1]
fuel = self.lander.fuel
altitude_text = self.font.render(f"Altitude: {altitude:.2f}", True, (255, 255, 255))
velocity_text = self.font.render(f"Velocity: {velocity:.2f}", True, (255, 255, 255))
fuel_text = self.font.render(f"Fuel: {fuel:.2f}", True, (255, 255, 255))
self.screen.blit(altitude_text, (10, 10))
self.screen.blit(velocity_text, (10, 40))
self.screen.blit(fuel_text, (10, 70))
def display_game_over(self, status):
# Display the game over screen with win/lose message
font = pygame.font.Font(None, 74)
if status == "win":
text = font.render("Landed safely", True, (0, 255, 0))
else:
text = font.render("Crash", True, (255, 0, 0))
self.screen.blit(text, (200, 250))
pygame.display.flip()
pygame.time.delay(2000) # Display for 2 seconds
# Class to handle player input
class PlayerController:
def __init__(self, lander):
self.lander = lander
def handle_event(self, event):
# Handle player input events to control the lander
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
self.lander.thrusting = True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_UP:
self.lander.thrusting = False
# Class to provide AI control for the lander
class AIController:
def __init__(self, lander):
self.lander = lander
def control_lander(self):
# AI logic to control the lander
if random.random() < 0.1: # 10% chance to thrust
self.lander.thrusting = True
else:
self.lander.thrusting = False
# Class to manage scoring and feedback
class ScoreManager:
def __init__(self):
self.score = 0
def calculate_score(self, lander):
# Calculate score based on landing success, fuel efficiency, and time taken
pass
def display_score(self):
# Display the score to the player
pass
# Initialize and start the game
if __name__ == "__main__":
game = LunarLanderGame()
game.start_game()
pygame.quit()
Changes Made:
-
Added a
choose_controller
Method:- Displays options for the player to choose between human and AI control.
- Sets the
self.controller
to eitherself.player_controller
orself.ai_controller
based on the player’s input.
-
Modified
handle_events
Method:- Only handles player input if the player controller is chosen.
-
Modified
update_game_state
Method:- Calls the AI controller’s
control_lander
method if the AI controller is chosen.
- Calls the AI controller’s
-
Implemented
AIController
Class:- Contains a
control_lander
method that randomly decides whether to thrust.
- Contains a
This setup allows the player to choose between human and AI control at the start of the game, and the AI will randomly decide whether to thrust during its control.
Reflection
At this point, the game allows choosing whether to play as a human or let the AI control the lunar lander. The AI controller randomly decides whether to thrust, which leads to the lunar lander crashing eventually. The initial screen of the game now looks as follows.
Landing safely
The next step is to implement an AI controller that can safely land the lunar lander. While there are advanced approaches like proportional-integral-derivative controller for controlling systems, we use a simpler approach. In our case, we wish that the AI adjusts the thrust based on the velocity and the distance from the surface.
The following outlines the design of a Lunar Lander game. Modify the AI in the game so that it lands the lunar lander safely. Implement the AI so that it controls the velocity based on two thresholds that relate to the distance from the surface.
###
(the code from the last part)
To improve the AI controller so that it lands the lunar lander safely, we will modify the `AIController` class to adjust the thrust based on two distance thresholds from the surface. The AI will use these thresholds to control the lander's descent velocity more precisely.
...
The output from the model continued as follows.
Model output
Here’s the updated AIController
class with the logic to control the lander based on the distance from the surface:
class AIController:
def __init__(self, lander):
self.lander = lander
self.threshold1 = 200 # First threshold distance from the surface
self.threshold2 = 100 # Second threshold distance from the surface
self.safe_velocity = 2 # Safe descent velocity
def control_lander(self):
altitude = 600 - self.lander.position[1] - 30 # Calculate altitude (adjusted for leg length)
if altitude > self.threshold1:
# High altitude: minimal thrust to reduce speed
if self.lander.velocity[1] > self.safe_velocity:
self.lander.thrusting = True
else:
self.lander.thrusting = False
elif altitude > self.threshold2:
# Medium altitude: moderate thrust to control descent
if self.lander.velocity[1] > self.safe_velocity / 2:
self.lander.thrusting = True
else:
self.lander.thrusting = False
else:
# Low altitude: strong thrust to ensure safe landing
if self.lander.velocity[1] > self.safe_velocity / 4:
self.lander.thrusting = True
else:
self.lander.thrusting = False
Here’s the complete game code with the updated AIController
:
import pygame
import random
# Class to manage the overall game state
class LunarLanderGame:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Lunar Lander")
self.lander = LunarLander()
self.surface = LunarSurface()
self.ui = GameUI(self.screen, self.lander, self.surface)
self.player_controller = PlayerController(self.lander)
self.ai_controller = AIController(self.lander)
self.score_manager = ScoreManager()
self.is_running = True
self.controller = None
def start_game(self):
# Ask the player whether to use human or AI control
self.choose_controller()
# Main game loop
while self.is_running:
self.handle_events()
self.update_game_state()
self.render()
pygame.time.delay(30) # Control frame rate
def choose_controller(self):
# Display options for the player to choose between human and AI control
font = pygame.font.Font(None, 74)
human_text = font.render("Press H for Human Control", True, (255, 255, 255))
ai_text = font.render("Press A for AI Control", True, (255, 255, 255))
while True:
self.screen.fill((0, 0, 0))
self.screen.blit(human_text, (100, 200))
self.screen.blit(ai_text, (100, 300))
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.is_running = False
return
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_h:
self.controller = self.player_controller
return
elif event.key == pygame.K_a:
self.controller = self.ai_controller
return
def handle_events(self):
# Handle player input and other events
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.is_running = False
if self.controller == self.player_controller:
self.player_controller.handle_event(event)
def update_game_state(self):
# Update lander and check for collisions
if self.controller == self.ai_controller:
self.ai_controller.control_lander()
self.lander.update()
collision_status = self.surface.check_collision(self.lander)
if collision_status is not None:
self.is_running = False
self.ui.display_game_over(collision_status)
def render(self):
# Render the game UI
self.ui.render()
# Class to represent the Lunar Lander
class LunarLander:
def __init__(self):
self.position = [390, 0] # Center the lander horizontally at the top
self.velocity = [0, 0]
self.fuel = 100
self.thrusting = False
def update(self):
# Update position and velocity based on thrust and gravity
self.apply_gravity()
if self.thrusting and self.fuel > 0:
self.apply_thrust()
self.position[1] += self.velocity[1]
def apply_gravity(self):
# Simulate gravity affecting the lander
self.velocity[1] += 0.1 # Simple gravity effect
def apply_thrust(self):
# Apply thrust to counteract gravity
self.velocity[1] -= 0.5
self.fuel -= 0.1
# Class to represent the lunar surface
class LunarSurface:
def __init__(self):
# Initialize lunar surface (e.g., terrain)
self.height = 50 # Height of the terrain
def check_collision(self, lander):
# Check for collision with the lunar surface
leg_bottom = lander.position[1] + 30 # Bottom of the legs
if leg_bottom >= 600 - self.height:
if lander.velocity[1] < 3:
return "win"
else:
return "lose"
return None
# Class to manage the graphical user interface
class GameUI:
def __init__(self, screen, lander, surface):
self.screen = screen
self.lander = lander
self.surface = surface
self.font = pygame.font.Font(None, 36)
def render(self):
# Render the lander, surface, and status information
self.screen.fill((0, 0, 0)) # Fill the screen with black
# Draw the lunar lander
self.draw_lander()
# Draw the terrain as a green rectangle
pygame.draw.rect(self.screen, (0, 255, 0), (0, 600 - self.surface.height, 800, self.surface.height))
# Draw the HUD
self.render_hud()
pygame.display.flip() # Update the screen
def draw_lander(self):
# Draw the lander body as a white square
pygame.draw.rect(self.screen, (255, 255, 255), (self.lander.position[0], self.lander.position[1], 20, 20))
# Draw the dome as a white circle
pygame.draw.circle(self.screen, (255, 255, 255), (self.lander.position[0] + 10, self.lander.position[1]), 10)
# Draw the legs as white lines
pygame.draw.line(self.screen, (255, 255, 255), (self.lander.position[0], self.lander.position[1] + 20), (self.lander.position[0] - 10, self.lander.position[1] + 30))
pygame.draw.line(self.screen, (255, 255, 255), (self.lander.position[0] + 20, self.lander.position[1] + 20), (self.lander.position[0] + 30, self.lander.position[1] + 30))
# Draw the thrust flame as an upside-down triangle if thrusting
if self.lander.thrusting:
flame_color = (255, 0, 0) # Red color for the flame
flame_points = [
(self.lander.position[0] + 10, self.lander.position[1] + 40), # Bottom point
(self.lander.position[0], self.lander.position[1] + 20), # Top-left point
(self.lander.position[0] + 20, self.lander.position[1] + 20) # Top-right point
]
pygame.draw.polygon(self.screen, flame_color, flame_points)
def render_hud(self):
# Render the HUD with altitude, velocity, and fuel level
altitude = 600 - self.surface.height - (self.lander.position[1] + 30) # Adjusted for leg length
velocity = self.lander.velocity[1]
fuel = self.lander.fuel
altitude_text = self.font.render(f"Altitude: {altitude:.2f}", True, (255, 255, 255))
velocity_text = self.font.render(f"Velocity: {velocity:.2f}", True, (255, 255, 255))
fuel_text = self.font.render(f"Fuel: {fuel:.2f}", True, (255, 255, 255))
self.screen.blit(altitude_text, (10, 10))
self.screen.blit(velocity_text, (10, 40))
self.screen.blit(fuel_text, (10, 70))
def display_game_over(self, status):
# Display the game over screen with win/lose message
font = pygame.font.Font(None, 74)
if status == "win":
text = font.render("Landed safely", True, (0, 255, 0))
else:
text = font.render("Crash", True, (255, 0, 0))
self.screen.blit(text, (200, 250))
pygame.display.flip()
pygame.time.delay(2000) # Display for 2 seconds
# Class to handle player input
class PlayerController:
def __init__(self, lander):
self.lander = lander
def handle_event(self, event):
# Handle player input events to control the lander
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
self.lander.thrusting = True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_UP:
self.lander.thrusting = False
# Class to provide AI control for the lander
class AIController:
def __init__(self, lander):
self.lander = lander
self.threshold1 = 200 # First threshold distance from the surface
self.threshold2 = 100 # Second threshold distance from the surface
self.safe_velocity = 2 # Safe descent velocity
def control_lander(self):
altitude = 600 - self.lander.position[1] - 30 # Calculate altitude (adjusted for leg length)
if altitude > self.threshold1:
# High altitude: minimal thrust to reduce speed
if self.lander.velocity[1] > self.safe_velocity:
self.lander.thrusting = True
else:
self.lander.thrusting = False
elif altitude > self.threshold2:
# Medium altitude: moderate thrust to control descent
if self.lander.velocity[1] > self.safe_velocity / 2:
self.lander.thrusting = True
else:
self.lander.thrusting = False
else:
# Low altitude: strong thrust to ensure safe landing
if self.lander.velocity[1] > self.safe_velocity / 4:
self.lander.thrusting = True
else:
self.lander.thrusting = False
# Class to manage scoring and feedback
class ScoreManager:
def __init__(self):
self.score = 0
def calculate_score(self, lander):
# Calculate score based on landing success, fuel efficiency, and time taken
pass
def display_score(self):
# Display the score to the player
pass
# Initialize and start the game
if __name__ == "__main__":
game = LunarLanderGame()
game.start_game()
pygame.quit()
This AI controller uses two thresholds to adjust the lander’s thrust based on its altitude, aiming to safely land the lunar lander by controlling its descent velocity.
Reflection
The Lunar Lander now also has an AI that safely lands the lunar lander. The AI controller uses altitude thresholds to decide on target velocity, which is then used to adjust thrusting. The lander can still land safely.
The above threshold-based AI is an example of a rule-based system. Rule-based systems that decide on what to do based on a set of rules that relate to inputs. As an example, in home automation, a rule-based system might consist of a set of rules that decide on what to do based on the time of day, the day of the week, and the temperature.
A very simple rule-based system for heating a house might look like this:
- If the temperature is below 18 degrees, turn on the heater.
- If the temperature is over 20 degrees, turn off the heater.
One could also introduce the time of day into the system, or even consider weekdays, where each day of the week could have different rules.