DEV Community

John Vincent Augusto
John Vincent Augusto

Posted on

How I Built a Retro Python Game with Amazon Q CLI

I recently jumped on the "Build Games with Amazon Q CLI and score a T shirt 🏆👕" challenge. As a developer who loves a good retro arcade game and is curious about AI-driven development, this was the perfect excuse to dive in. The mission was simple: build a game using Amazon Q's command-line interface, document the journey, and share the results.

The result? A fully-functional, nostalgic side-scrolling shooter called Space Conquer, and a ton of insights into pairing AI with a classic coding project. Here’s how it went down.

My Game: "Space Conquer" - A Modern-Classic Shooter

For my project, I chose to build Space Conquer, a side-scrolling space shooter inspired by the classic Space Impact from old Nokia phones.

Why a Retro Shooter?

  1. Nostalgia Factor: Like many, I have fond memories of playing Space Impact. I wanted to capture that simple, addictive fun but with a modern coat of paint—better graphics, dynamic sound, and smoother controls.
  2. A Great Test for AI: A 2D shooter involves a fantastic mix of programming challenges that are perfect for an AI assistant: managing game states, handling real-time user input, collision detection, and creating varied enemy behaviors.
  3. Extensibility: I didn't just want to build a game; I wanted to build a framework. My vision was a modular design where new enemies, power-ups, or levels could be added easily. This is where an AI's ability to generate structured, boilerplate code would really shine.

Space Conquer features diverse enemies, collectible power-ups, dynamic audio that changes with the game state, and even a hidden developer panel for testing.

Main Menu sneak peek

Unlocking AI's Potential: Effective Prompting Techniques

Working with Amazon Q CLI is a conversation. The better your questions, the better the answers. I quickly learned that vague prompts like "make a game" were less effective than breaking down the problem into specific, well-defined tasks.

Here are a few prompting techniques I discovered.

Technique 1: Requesting a Modular Architecture

Instead of asking for a single, monolithic script, I prompted for a clean, organized structure from the start.

My Prompt: "Create a project structure for a PyGame-based space shooter. I need separate modules for asset management, sprites (player, enemies, bullets), UI components, and the main game loop. The asset manager should load images and sounds from manifest files."

The Result: Amazon Q generated a directory structure (src/, assets/, tools/) and starter Python files for each module (asset_loader.py, sprites.py, ui.py, main.py). The generated asset_loader.py included a function to read a JSON manifest, which was a huge head start.

Technique 2: Defining Behavior with Roles and Rules

When creating enemies, I defined their characteristics and constraints clearly.

My Prompt: "Generate a Python class Enemy that inherits from pygame.sprite.Sprite. It needs attributes for health, speed, and score value. Then, create a subclass EliteEnemy that moves in a sine wave pattern down the screen and fires a bullet every 2 seconds."

The Result: Q provided a base Enemy class and a well-defined EliteEnemy subclass with its update() method already implementing the sine wave movement using math.sin(). This saved me from figuring out the trigonometry and timing loops myself.

How AI Handled Classic Programming Challenges

Game development is full of recurring problems. Here's how Amazon Q helped tackle some of the classics:

  • State Management: A game needs distinct states like 'main_menu', 'gameplay', 'settings', and 'game_over'. I prompted the AI to implement a simple state machine. It generated a GameManager class that held the current state and handled transitions, ensuring that the main menu logic didn't run during gameplay and vice-versa.

  • Collision Detection: A core mechanic of any shooter. I asked Q for an efficient way to check for collisions between player bullets and enemies, and between the player and enemy ships or bullets. It suggested using PyGame's built-in pygame.sprite.groupcollide() function, providing a concise and performant solution that I could drop right into my main game loop.

  • Power-Up Spawning: I wanted power-ups to drop randomly from destroyed asteroids. I prompted: "When an asteroid is destroyed, there should be a 15% chance of it dropping a power-up. The power-up type (health, speed, rapid-fire) should be chosen randomly." The AI generated a clean if random.random() < 0.15: check and a random.choice() call to select from a list of power-up types.

Time-Saving Automation: More Than Just Code

One of the biggest wins was using AI for automation around the code. The project summary mentions developer tools, and Q was instrumental here.

The Asset Manifest Generator

My game uses JSON files to manage all assets (images, sounds, maps). Manually keeping these in sync is tedious.

My Prompt: "Write a Python script for the tools/ directory that scans the assets/images/enemies and assets/sounds/sfx directories and automatically generates a manifest.json file with all the file paths."

This single prompt created a utility script that saved me countless minutes of error-prone manual editing every time I added a new enemy sprite or sound effect.

The Cross-Platform Launcher

I wanted a simple way for anyone to run the game, regardless of their OS.

My Prompt: "Create a Python script named run_game.py that checks the user's operating system. It should ensure all dependencies from requirements.txt are installed using pip and then launch the main.py script."

Q generated a script using the subprocess and sys modules that provided a one-click experience—a small but professional touch that I might have skipped otherwise.

AI-Generated Code That Impressed Me

It's one thing to generate boilerplate, but another to produce elegant solutions. Here are a couple of snippets that stood out.

1. Manifest-Driven Asset Loader

This function, generated early on, set the foundation for the game's modularity. It loads all assets listed in a JSON file into a dictionary, making them easily accessible throughout the game.

# Part of src/asset_loader.py
import pygame
import json

def load_assets_from_manifest(manifest_path):
    """Loads images and sounds based on a JSON manifest file."""
    assets = {'images': {}, 'sounds': {}}
    with open(manifest_path, 'r') as f:
        manifest = json.load(f)

    for category, files in manifest.items():
        if category == 'images':
            for key, path in files.items():
                try:
                    assets['images'][key] = pygame.image.load(path).convert_alpha()
                except pygame.error as e:
                    print(f"Error loading image {key} at {path}: {e}")
        elif category == 'sounds':
            for key, path in files.items():
                try:
                    assets['sounds'][key] = pygame.mixer.Sound(path)
                except pygame.error as e:
                    print(f"Error loading sound {key} at {path}: {e}")
    return assets

# Example Usage in main.py
# ASSETS = load_assets_from_manifest('assets/manifest.json')
# player_img = ASSETS['images']['player_ship']
Enter fullscreen mode Exit fullscreen mode

This design is clean, error-handled, and makes adding 50 new assets as easy as adding one.

  1. A Base Class for Animated UI Panels I wanted the UI to have a modern, "glowing" feel. I asked Q to create a reusable class for this.
# Part of src/ui.py
import pygame

class GlowingPanel(pygame.sprite.Sprite):
    """
    A UI panel that has a subtle pulsing glow effect by alpha blending.
    """
    def __init__(self, rect, color, glow_color):
        super().__init__()
        self.rect = rect
        self.color = color
        self.glow_color = glow_color
        self.image = pygame.Surface(self.rect.size, pygame.SRCALPHA)

        self.glow_alpha = 100
        self.glow_direction = 2 # Rate of change for alpha

    def update(self):
        """Update the pulsing glow effect."""
        self.glow_alpha += self.glow_direction
        if self.glow_alpha >= 180 or self.glow_alpha <= 80:
            self.glow_direction *= -1

        self.image.fill((0, 0, 0, 0)) # Clear with transparency

        # Draw base panel
        pygame.draw.rect(self.image, self.color, (0, 0, self.rect.width, self.rect.height), border_radius=8)

        # Draw glow effect (a slightly larger rect with changing alpha)
        glow_surface = pygame.Surface(self.rect.size, pygame.SRCALPHA)
        glow_rect = pygame.Rect(0, 0, self.rect.width, self.rect.height)
        glow_color_with_alpha = (*self.glow_color, self.glow_alpha)
        pygame.draw.rect(glow_surface, glow_color_with_alpha, glow_rect, border_radius=10)

        # Blit the glow onto the main surface
        self.image.blit(glow_surface, (0,0), special_flags=pygame.BLEND_RGBA_ADD)
Enter fullscreen mode Exit fullscreen mode

This self-contained class for a UI element with its own animation logic is a great example of the object-oriented code Q can produce. It's reusable for scoreboards, health bars, or any other panel in the game.

Final Thoughts
Using Amazon Q CLI for the "Build Games" challenge was a fantastic experience. It didn't just write code for me; it acted as a partner that handled the tedious, boilerplate, and sometimes complex parts of development, freeing me up to focus on the creative vision for "Space Conquer."

If you're a developer who hasn't tried integrating an AI assistant into your workflow, I highly recommend it. Pick a fun project, break it down into small pieces, and start prompting. You'll be surprised at how much you can build.

And hey, I might even get a t-shirt out of it.

Happy coding!

Top comments (0)