Landing on the Moon with Large Language Models

Implementation


From the application design part, we have a high-level view of the Lunar Lander game. In this chapter, we will prompt the large language model to generate the actual implementation of the game based on the design. Although due to the size of the game, it would be possible to generate the entire implementation in one go, we will break it down into smaller parts for better readability and understanding.

TL;DR — We start by asking the model to display the window, the lunar lander, and the terrain. We then ask the model to add the functionality for thrusting the lander upwards. Then, we prompt the model to create a heads-up display (HUD) showing current altitude, velocity, and fuel level. Then, we ask for the model to adjust the way how the lander is shown, first adding legs and dome, and then a thrusting indicator. Finally, we add conditions for winning and losing the game.

All of the codes shown on this page are generated by the large language model and they have not been modified in any way. They can be run locally when PyGame has been installed — PyGame is a set of Python modules designed for writing games. To install PyGame, run pip install pygame.

Showing content

Let’s start by asking the large language model to display the window, the lunar lander, and the terrain. We’ll create the lunar lander as a 20 pixels times 20 pixels square, and the terrain as a rectangle line almost at the bottom of the screen. We’ll use 800 times 600 pixels as the screen dimensions.

The concrete prompt will involve our earlier design and the specific requirements for this part.

The following code outlines a design for the Lunar Lander game. Modify the code so that when the program is run, the program displays a 800 times 600 pixels black screen, shows the lunar lander as a 20 times 20 pixels white square, and shows the terrain as a green rectangle filling the bottom 50 pixels of the screen.

###

(the code from the last part)

To modify the given code so that it displays an 800x600 pixel black screen, shows the lunar lander as a 20x20 pixel white square, and shows the terrain as a green rectangle filling the bottom 50 pixels of the screen, you need to make changes primarily in the `GameUI` class. Additionally, you need to ensure that the initial position of the lunar lander is set appropriately.

Here's the modified code:

The rest of the output from the model is outlined below.

Code from model

import pygame

# 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

    def start_game(self):
        # Main game loop
        while self.is_running:
            self.handle_events()
            self.update_game_state()
            self.render()
            pygame.time.delay(30)  # Control frame rate

    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
            self.player_controller.handle_event(event)

    def update_game_state(self):
        # Update lander and check for collisions
        self.lander.update()
        if self.surface.check_collision(self.lander):
            self.is_running = False
            self.ui.display_game_over(self.lander)

    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.thrust = 0

    def update(self):
        # Update position and velocity based on thrust and gravity
        self.apply_gravity()
        self.apply_thrust()
        self.consume_fuel()

    def apply_gravity(self):
        # Simulate gravity affecting the lander
        self.velocity[1] += 0.1  # Simple gravity effect
        self.position[1] += self.velocity[1]

    def apply_thrust(self):
        # Apply thrust to counteract gravity
        pass

    def consume_fuel(self):
        # Consume fuel based on thrust usage
        pass

# 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
        lander_bottom = lander.position[1] + 20
        if lander_bottom >= 600 - self.height:
            return True
        return False

# Class to manage the graphical user interface
class GameUI:
    def __init__(self, screen, lander, surface):
        self.screen = screen
        self.lander = lander
        self.surface = surface

    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 as a white square
        pygame.draw.rect(self.screen, (255, 255, 255), (self.lander.position[0], self.lander.position[1], 20, 20))

        # Draw the terrain as a green rectangle
        pygame.draw.rect(self.screen, (0, 255, 0), (0, 600 - self.surface.height, 800, self.surface.height))

        pygame.display.flip()  # Update the screen

    def display_game_over(self, lander):
        # Display the game over screen
        font = pygame.font.Font(None, 74)
        text = font.render("Game Over", 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
        pass

# 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
        pass

# 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()

In this updated code:

  1. The LunarLanderGame class now initializes the Pygame screen with an 800x600 resolution.
  2. The LunarLander class initializes the lander’s position to be centered horizontally at the top of the screen.
  3. The LunarSurface class defines the height of the terrain.
  4. The GameUI class handles rendering the black background, the white square representing the lunar lander, and the green rectangle representing the terrain.
  5. The render method in GameUI updates the screen with these elements.

Reflection

Although we prompted for the visuals, the model also updated the positions of the lander and created the basic functionality for gravity. This is a good example of how a model can provide also something else than what was asked for.

Now, when we start the program, the application looks as follows.

Fig 1. -- Initial view of the Lunar Lander game.

Fig 1. — Initial view of the Lunar Lander game.

Thrusting

Let’s next continue by asking the model to provide the functionality for thrusting the lander upwards. We’ll prompt the model to add the ability to apply thrust to the lander to counteract gravity, basing the thrust on an educated guess on what the changes should be. The thrust will be controlled by the player pressing the up key. When the up key is released, the thrusting should stop.

The concrete prompt will involve our earlier design and the specific requirements for this part.

The following outlines the design of a Lunar Lander game. Modify the game so that the player can use the thrust of the Lunar Lander by pressing the upwards arrow. Thrusting should continue as long as the upwards arrow is down. Thrusting should decrease the downwards velocity by 0.5 and reduce the fuel by 0.1.

###

(the code produced by the large language model from above)

To implement the thrust functionality for the Lunar Lander when the upwards arrow key is pressed, we need to make modifications in several parts of the existing code. Specifically, we need to handle the key press events, apply the thrust effect, and manage fuel consumption. Below are the changes required:

...

The output of the model continues as follows.

Model output

  1. Update the PlayerController class to handle the upwards arrow key press and release.
  2. Modify the LunarLander class to apply thrust and consume fuel.

Here is the updated code:

import pygame

# 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

    def start_game(self):
        # Main game loop
        while self.is_running:
            self.handle_events()
            self.update_game_state()
            self.render()
            pygame.time.delay(30)  # Control frame rate

    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
            self.player_controller.handle_event(event)

    def update_game_state(self):
        # Update lander and check for collisions
        self.lander.update()
        if self.surface.check_collision(self.lander):
            self.is_running = False
            self.ui.display_game_over(self.lander)

    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
        lander_bottom = lander.position[1] + 20
        if lander_bottom >= 600 - self.height:
            return True
        return False

# Class to manage the graphical user interface
class GameUI:
    def __init__(self, screen, lander, surface):
        self.screen = screen
        self.lander = lander
        self.surface = surface

    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 as a white square
        pygame.draw.rect(self.screen, (255, 255, 255), (self.lander.position[0], self.lander.position[1], 20, 20))

        # Draw the terrain as a green rectangle
        pygame.draw.rect(self.screen, (0, 255, 0), (0, 600 - self.surface.height, 800, self.surface.height))

        pygame.display.flip()  # Update the screen

    def display_game_over(self, lander):
        # Display the game over screen
        font = pygame.font.Font(None, 74)
        text = font.render("Game Over", 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
        pass

# 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()

Explanation of changes:

  1. PlayerController Class:

    • Added logic to handle the KEYDOWN and KEYUP events for the upwards arrow key (pygame.K_UP). When the key is pressed, self.lander.thrusting is set to True, and when it is released, it is set to False.
  2. LunarLander Class:

    • Added a thrusting attribute to track whether the lander is currently thrusting.
    • Modified the update method to apply thrust if thrusting is True and there is enough fuel.
    • The apply_thrust method now decreases the downward velocity by 0.5 and reduces the fuel by 0.1 units.

This should give the player control over the lander’s thrust using the upwards arrow key, allowing them to counteract gravity and manage their fuel.

Reflection

The model was successful in modifying the code as prompted. Now, the game has the possibility to thrust upwards. However, it is worth noting that the prompt was quite explicit in asking for details on how the thrust should work; this stems from the material author having prior experience from creating games.

As an example, a prompt that might not work as well would not explicitly ask to keep thrusting as long as the key is pressed, which could lead to the thrust functionality not working well. Similarly, the changes to fuel and the velocity were quite explicitly prompted, in part informed by the model output from the previous part that tries to apply the updates 30 times per second.

The game uses PyGame’s command pygame.time.delay(30) to control the delay between updates. Without this delay, the game would run as fast as the computer could handle, which might be too fast for the player to control the lander effectively.

Heads-up display

At the moment, the game does not display any information about the amount fuel or the velocity to the player. Let’s ask the model to create a heads-up display (HUD) that shows the player’s current altitude, velocity, and fuel level. The HUD should be displayed at the top left corner of the screen.

The following outlines the design of a Lunar Lander game. Modify the game so that the game has a heads-up display that shows the player's current altitude, velocity, and fuel level. The heads-up display should be displayed at the top left corner of the screen.

###

(the code produced by the large language model from above)

To add a heads-up display (HUD) that shows the player's current altitude, velocity, and fuel level, we need to modify the `GameUI` class to include this information. We'll render this HUD at the top left corner of the screen.

...

The output of the model continues as follows.

Model output

Here’s the modified code:

import pygame

# 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

    def start_game(self):
        # Main game loop
        while self.is_running:
            self.handle_events()
            self.update_game_state()
            self.render()
            pygame.time.delay(30)  # Control frame rate

    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
            self.player_controller.handle_event(event)

    def update_game_state(self):
        # Update lander and check for collisions
        self.lander.update()
        if self.surface.check_collision(self.lander):
            self.is_running = False
            self.ui.display_game_over(self.lander)

    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
        lander_bottom = lander.position[1] + 20
        if lander_bottom >= 600 - self.height:
            return True
        return False

# 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 as a white square
        pygame.draw.rect(self.screen, (255, 255, 255), (self.lander.position[0], self.lander.position[1], 20, 20))

        # 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 render_hud(self):
        # Render the HUD with altitude, velocity, and fuel level
        altitude = 600 - self.surface.height - (self.lander.position[1] + 20)
        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, lander):
        # Display the game over screen
        font = pygame.font.Font(None, 74)
        text = font.render("Game Over", 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
        pass

# 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()

In this modified version, the GameUI class now includes a render_hud method, which renders the altitude, velocity, and fuel level at the top left corner of the screen. The render method calls render_hud to display this information along with the lander and the surface. The altitude is calculated as the distance from the bottom of the lander to the top of the terrain.

Reflection

The model again correctly creates the code as prompted. The HUD is a crucial part of the game, as it provides essential information to the player about the lander’s status. The model output is concise and clear, making it easy to understand the changes required to implement the HUD.

At this point, the game looks as follows.

Fig 2. -- Lunar Lander game with a simple heads-up display.

Fig 2. — Lunar Lander game with a simple heads-up display.

When trying out the game, we also see that the fuel consumption is a bit too slow, as the fuel level does not decrease much when thrusting. This is something that could be adjusted also directly the code, but we’ll ignore that for now.

Legs and dome

The lunar lander of the game is currently a square. Let’s ask the model to next modify the lunar lander a bit, and to add legs and a dome to the lander. Further, we want that the model adjusts the collision logic to account for the legs.

The following outlines the design of a Lunar Lander game. Modify the lunar lander of the game so that the lunar lander has legs (drawn as a line) and a dome (drawn as a circle overlapping the existing square). Adjust the collision detection with the surface to take into account the legs.

###

(the code produced by the large language model from above)

To modify the Lunar Lander game to include legs and a dome, and to adjust the collision detection accordingly, we need to make several changes. Specifically, we need to update the rendering of the lander to include legs and a dome, and adjust the collision detection logic to account for the legs of the lander.

...

The output of the model continues as follows.

Model output

Here is the modified code:

import pygame

# 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

    def start_game(self):
        # Main game loop
        while self.is_running:
            self.handle_events()
            self.update_game_state()
            self.render()
            pygame.time.delay(30)  # Control frame rate

    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
            self.player_controller.handle_event(event)

    def update_game_state(self):
        # Update lander and check for collisions
        self.lander.update()
        if self.surface.check_collision(self.lander):
            self.is_running = False
            self.ui.display_game_over(self.lander)

    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
        lander_bottom = lander.position[1] + 20  # Bottom of the lander body
        leg_bottom = lander.position[1] + 30  # Bottom of the legs
        if leg_bottom >= 600 - self.height:
            return True
        return False

# 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))

    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, lander):
        # Display the game over screen
        font = pygame.font.Font(None, 74)
        text = font.render("Game Over", 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
        pass

# 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:

  1. Drawing the Dome and Legs:

    • Modified the draw_lander method in the GameUI class to draw a dome (circle) and legs (lines) for the lunar lander.
  2. Collision Detection Adjustment:

    • Updated the check_collision method in the LunarSurface class to account for the legs of the lander. The legs extend 10 pixels below the body of the lander, so the collision check now considers this.
  3. Altitude Calculation:

    • Adjusted the altitude calculation in the render_hud method to account for the legs of the lander.

These changes ensure that the lunar lander now has a dome and legs, and the collision detection logic correctly takes the legs into account.

Reflection

The model correctly adjusted the code to include legs and a dome for the lunar lander, as well as updated the collision detection logic to account for the legs.

At this point, the game looks as follows.

Fig 3. -- Lunar Lander with legs and dome.

Fig 3. — Lunar Lander with legs and dome.

Thrust indication

As the almost final change, let’s add a thrust indication to the game. The thrust indication should show a triangle (depicting a flame) that points downwards when the lander is thrusting.

The following outlines the design of a Lunar Lander game. Modify the game so that when the lunar lander is thrusting, an upside down triangle is shown below the lunar lander to visualize thrusting to the user.

###

(the code produced by the large language model from above)

To modify the Lunar Lander game so that an upside-down triangle is shown below the lander to visualize thrusting, you need to update the `draw_lander` method in the `GameUI` class to draw the triangle when the lander is thrusting.

...

The output of the model continues as follows.

Model output

Here is the modified code:

import pygame

# 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

    def start_game(self):
        # Main game loop
        while self.is_running:
            self.handle_events()
            self.update_game_state()
            self.render()
            pygame.time.delay(30)  # Control frame rate

    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
            self.player_controller.handle_event(event)

    def update_game_state(self):
        # Update lander and check for collisions
        self.lander.update()
        if self.surface.check_collision(self.lander):
            self.is_running = False
            self.ui.display_game_over(self.lander)

    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
        lander_bottom = lander.position[1] + 20  # Bottom of the lander body
        leg_bottom = lander.position[1] + 30  # Bottom of the legs
        if leg_bottom >= 600 - self.height:
            return True
        return False

# 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, lander):
        # Display the game over screen
        font = pygame.font.Font(None, 74)
        text = font.render("Game Over", 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
        pass

# 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()

In the draw_lander method, the thrust flame is drawn as an upside-down triangle below the lander when the thrusting attribute is True. The triangle is drawn with red color to represent the flame. The points of the triangle are calculated to position it correctly below the lander.

Reflection

Again, the model correctly adjusted to add a thrusting indicator. At this point, the game looks as follows.

Fig 4. -- Lunar Lander with thrusting indicator.

Fig 4. — Lunar Lander with thrusting indicator.

Winning and losing

Finally, let’s add a win and lose condition to the game. The player wins if the lander lands softly on the lunar surface with a velocity of less than 3 units. Otherwise, if the lander lands with a velocity greater than or equal to 3 units, the player loses.

The following outlines the design of a Lunar Lander game. Modify the game so that it has win and lose conditions. The player wins if the lander lands softly on the lunar surface with a velocity of less than 3 units. Otherwise, if the lander lands with a velocity greater than or equal to 3 units, the player loses.

###

(the code produced by the large language model from above)

To add win and lose conditions based on the velocity when touching the ground, we need to modify the `LunarLanderGame` class and the `GameUI` class. Specifically, we will update the `check_collision` method in the `LunarSurface` class to return different values based on the velocity, and then handle these values in the `update_game_state` method. Additionally, we will modify the `display_game_over` method in the `GameUI` class to show different messages based on the outcome.

...

The output of the model continues as follows.

Model output

Here is the updated code:

import pygame

# 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

    def start_game(self):
        # Main game loop
        while self.is_running:
            self.handle_events()
            self.update_game_state()
            self.render()
            pygame.time.delay(30)  # Control frame rate

    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
            self.player_controller.handle_event(event)

    def update_game_state(self):
        # Update lander and check for collisions
        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
        pass

# 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()

In this modified code:

  1. The check_collision method in the LunarSurface class now returns “win” if the lander’s velocity is less than 3 and “lose” otherwise.
  2. The update_game_state method in the LunarLanderGame class checks the collision status and passes it to the display_game_over method.
  3. The display_game_over method in the GameUI class displays “Landed safely” if the status is “win” and “Crash” if the status is “lose”.

Reflection

The model correctly added the win and lose conditions to the game. At this point, the game looks as follows.

Fig 5. -- Lunar Lander with an indicator of successful landing.

Fig 5. — Lunar Lander with an indicator of successful landing.
Loading Exercise...