elelex boutique law firm

AI vs. Space Invaders: which LLM codes it best? 🎮 Ever wondered how different LLMs tackle classic games? 🤔 I put Grok 3, Gemini March, and Gemini May 🤖 to the test with Space Invaders. Spoiler: Some were fun, some were… well, let’s just say they need a patch or two. 😅 Which AI would you trust to save the galaxy? 🚀

The Invaders Strike Back

AI generated image, any similarity to actual persons, living or dead, is purely coincidental
AI generated image, any similarity to actual persons, living or dead, is purely coincidental

In my previous article “If a tree falls in a forest” I mentioned several new large language models (LLM), including Gemini 2.5 Pro Experimental released on 25 March 2025. I neglected to mention that the full LLM name was “Gemini 2.5 Pro Experimental 03-25”. The last numbers represent the month and day of its release. At the time, I didn’t pay much attention to these numbers.

Why are these numbers important? Earlier this week, Google released a new version of Gemini 2.5 Pro. Yes! “Same, same, but different”. This time, LLM is called “Preview” instead of “Experimental” and its name ends with “05-06”. If you’re in Europe, you might misread this as 5 June, especially if you’re reading this article after that date. However, it actually means LLM was released on 6 May. I wonder why the year isn’t included? When you put a copyright notice, you put at least a year. When you provide your date of birth, you include the day, month, and year. My best guess is that we’ll either see a 2.5 release (not experimental or preview) soon or that 2.5 will be replaced with the next version number.

This article is about coding and a fun experiment with the game Space Invaders, as generated by Grok 3, Gemini 2.5 Pro Experimental 03-25 (“Gemini March”) and Gemini 2.5 Pro Preview 05-06 (“Gemini May”). I gave each LLM the same prompt: “Help me write a computer classical arcade game Space Invaders in Python using pygame without the use of images, please use only text blocks or simple graphics”.

Let the results speak for themselves. Here are a few observations:

  • Grok 3 produced the shortest code. Was it lazy or simply effective? When the game ends, you can’t restart or exit; you can only close the game and start it again. If you lose, there is no message; the game just stops. If you win, the game shows “YOU WIN!” and stops. You can fire several laser shots in quick succession. The aliens don’t fire at you, and there are no defense bunkers. The aliens are red blocks with antennas, and your ship is green triangle. Verdict: The game is fun and playable.
  • Gemini March produced more sophisticated code, but it was unplayable at first. Despite additional details like the number of lives, defense bunkers, aliens firing at you, and the ability to restart by pressing Enter, the number of alien bullets made the game nearly impossible to play. After reducing BULLET_SPEED_ALIEN from 4 to 1 and ALIEN_FIRE_CHANCE from 0.001 to 0.00001, the game became playable. The aliens are white, and your ship is blue. You can fire several laser shots in quick succession. The aliens fire at you, and you can hide behind the defense bunkers. When an alien bullet hits you, the number of lives decreases until it reaches 0, and you lose. If you clear a level, you win. You can start again by pressing Enter. Verdict: The game is playable, but not fun.
  • Gemini May produced a shorter code as compared to Gemini March but with more game features like a start screen, levels, a new level screen, hit effect, and the ability to quit the game. The game starts with a welcome screen with instructions on how to play, start, and quit the game. After clearing one level, the game proceeds to the next, showing the new level number in between gameplay. Each row of aliens has its own unique color, making the game more colorful. Aliens move faster on subsequent level. After clearing 5 levels, you win (I won after 1 level since I changed win/loss conditions for the screenshot below). Unlike the other implementations, you can fire only one laser shot at a time and must wait until it hits the target or reaches the screen’s end, making the game feel harder and more challenging. If an alien bullet hits you, there’s a nice effect where the entire screen turns red for a moment. The game is playable, but I personally find Grok 3 visuals of the aliens and the ship, and the ability to fire several laser shots, more fun. Verdict: The game is playable but less fun than Grok 3 implementation.

Do you agree or disagree with my verdicts? Which game implementation do you like? Are you vibecoding, and if so, which LLM(s) do you use?

This article was written for fun, please do not judge. Instead, please share your comments in a constructive and respectful manner. The author and AI remain innocent until proven guilty.



Space Invaders: A Side-by-Side comparison

Image 1
A few numbers

Enjoy the game play screenshots

Image 1
Start

Image 1
Play

Image 1
Next Level

Image 1
Game Over

Image 1
Victory

Grok 3 code

Please see “Space Invaders are Here”.

Gemini March code

The following code contains manual edits of BULLET_SPEED_ALIEN from 4 to 1 and ALIEN_FIRE_CHANCE from 0.001 to 0.00001.

# generated by Gemini 2.5 Pro Experimental 03-25 on 28 March 2025

import pygame
import random
import sys
import time # For pausing briefly on game over/win

# --- Constants ---
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)

# Player settings
PLAYER_CHAR = "^"
PLAYER_SIZE = 20 # Approx size for collision and rendering font
PLAYER_SPEED = 5
PLAYER_START_X = SCREEN_WIDTH // 2
PLAYER_START_Y = SCREEN_HEIGHT - 50

# Bullet settings
BULLET_CHAR_PLAYER = "|"
BULLET_CHAR_ALIEN = "*"
BULLET_SIZE = 15 # Approx size for collision and rendering font
BULLET_SPEED_PLAYER = 7
BULLET_SPEED_ALIEN = 1
PLAYER_FIRE_COOLDOWN = 300 # milliseconds

# Alien settings
ALIEN_CHARS = ['X', 'O', 'M'] # Different rows can have different chars
ALIEN_ROWS = 5
ALIEN_COLS = 10
ALIEN_SPACING_X = 50
ALIEN_SPACING_Y = 40
ALIEN_START_X = 50
ALIEN_START_Y = 50
ALIEN_SIZE = 20 # Approx size for collision and rendering font
ALIEN_MOVE_SPEED_X = 1 # Initial horizontal speed
ALIEN_MOVE_DROP_Y = 15 # How much aliens drop when hitting edge
ALIEN_FIRE_CHANCE = 0.00001 # Chance per alien per frame to fire

# Barrier settings
BARRIER_CHAR = "#"
BARRIER_COLOR = GREEN
BARRIER_ROWS = 3
BARRIER_COLS = 7
BARRIER_BLOCK_SIZE = 8 # Size of each small rectangle making up the barrier
NUM_BARRIERS = 4
BARRIER_Y_POS = SCREEN_HEIGHT - 120
BARRIER_SPACING = SCREEN_WIDTH // (NUM_BARRIERS + 1)

# Game settings
FPS = 60
INITIAL_LIVES = 3

# --- Game Class ---
class Game:
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
        pygame.display.set_caption("Text Invaders")
        self.clock = pygame.time.Clock()

        # Fonts - Choose a monospaced font if available for better alignment
        try:
            # Try a common monospaced font
            self.font_char = pygame.font.SysFont("Consolas", PLAYER_SIZE * 2) # Larger font for chars
            self.font_ui = pygame.font.SysFont("Consolas", 24)
            self.font_msg = pygame.font.SysFont("Consolas", 48)
        except:
             # Fallback to default Pygame font
            print("Monospaced font not found, using default.")
            self.font_char = pygame.font.Font(None, PLAYER_SIZE * 2)
            self.font_ui = pygame.font.Font(None, 30)
            self.font_msg = pygame.font.Font(None, 60)

        self.reset_game()

    def reset_game(self):
        """Resets the game to the initial state."""
        self.score = 0
        self.lives = INITIAL_LIVES
        self.game_over = False
        self.game_won = False
        self.paused = False

        # Player setup
        self.player_rect = pygame.Rect(PLAYER_START_X - PLAYER_SIZE // 2, PLAYER_START_Y - PLAYER_SIZE // 2, PLAYER_SIZE, PLAYER_SIZE)
        self.last_player_shot_time = 0

        # Bullet lists
        self.player_bullets = []
        self.alien_bullets = []

        # Alien setup
        self.aliens = []
        self.alien_move_direction = 1 # 1 for right, -1 for left
        self.alien_speed_x = ALIEN_MOVE_SPEED_X
        self.create_aliens()

        # Barrier setup
        self.barriers = []
        self.create_barriers()

    def create_aliens(self):
        """Creates the grid of aliens."""
        self.aliens = []
        for row in range(ALIEN_ROWS):
            char = ALIEN_CHARS[row % len(ALIEN_CHARS)] # Cycle through chars for rows
            for col in range(ALIEN_COLS):
                x = ALIEN_START_X + col * ALIEN_SPACING_X
                y = ALIEN_START_Y + row * ALIEN_SPACING_Y
                # Store rect and the character representation
                alien = {'rect': pygame.Rect(x, y, ALIEN_SIZE, ALIEN_SIZE), 'char': char}
                self.aliens.append(alien)

    def create_barriers(self):
        """Creates the destructible barriers."""
        self.barriers = []
        for i in range(NUM_BARRIERS):
            barrier_base_x = (i + 1) * BARRIER_SPACING - (BARRIER_COLS * BARRIER_BLOCK_SIZE) // 2
            for row in range(BARRIER_ROWS):
                for col in range(BARRIER_COLS):
                    # Simple rectangular shape
                    if (row == 0 and (col == 0 or col == BARRIER_COLS -1)) or \
                       (row == 1 and (col == 0 or col == BARRIER_COLS -1)):
                       continue # Skip corners for a basic shape

                    block_x = barrier_base_x + col * BARRIER_BLOCK_SIZE
                    block_y = BARRIER_Y_POS + row * BARRIER_BLOCK_SIZE
                    barrier_block = pygame.Rect(block_x, block_y, BARRIER_BLOCK_SIZE, BARRIER_BLOCK_SIZE)
                    self.barriers.append(barrier_block)

    def handle_input(self):
        """Handles player input."""
        keys = pygame.key.get_pressed()
        if not self.game_over and not self.game_won and not self.paused:
            if keys[pygame.K_LEFT] and self.player_rect.left > 0:
                self.player_rect.x -= PLAYER_SPEED
            if keys[pygame.K_RIGHT] and self.player_rect.right < SCREEN_WIDTH:
                self.player_rect.x += PLAYER_SPEED
            if keys[pygame.K_SPACE]:
                self.fire_player_bullet()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return False # Signal to exit main loop
            if event.type == pygame.KEYDOWN:
                if (self.game_over or self.game_won) and event.key == pygame.K_RETURN:
                    self.reset_game() # Restart on Enter after game end
                if event.key == pygame.K_p: # Pause toggle
                     self.paused = not self.paused
                     if self.paused:
                         print("Game Paused") # Console message
                     else:
                         print("Game Resumed")

        return True # Signal to continue

    def fire_player_bullet(self):
        """Fires a bullet from the player if cooldown allows."""
        current_time = pygame.time.get_ticks()
        if current_time - self.last_player_shot_time > PLAYER_FIRE_COOLDOWN:
            bullet_rect = pygame.Rect(self.player_rect.centerx - BULLET_SIZE // 4, # Make bullet thinner
                                      self.player_rect.top,
                                      BULLET_SIZE // 2, BULLET_SIZE)
            self.player_bullets.append(bullet_rect)
            self.last_player_shot_time = current_time

    def move_bullets(self):
        """Moves player and alien bullets, removing off-screen ones."""
        # Player bullets move up
        self.player_bullets = [b for b in self.player_bullets if b.bottom > 0]
        for bullet in self.player_bullets:
            bullet.y -= BULLET_SPEED_PLAYER

        # Alien bullets move down
        self.alien_bullets = [b for b in self.alien_bullets if b.top < SCREEN_HEIGHT]
        for bullet in self.alien_bullets:
            bullet.y += BULLET_SPEED_ALIEN

    def move_aliens(self):
        """Moves the alien grid horizontally and drops them down."""
        move_down = False
        fastest_speed_factor = 1 + (ALIEN_ROWS * ALIEN_COLS - len(self.aliens)) / (ALIEN_ROWS * ALIEN_COLS) * 2 # Speed increases as fewer aliens remain
        current_speed_x = self.alien_speed_x * self.alien_move_direction * fastest_speed_factor

        # Check boundaries
        for alien in self.aliens:
            alien['rect'].x += current_speed_x
            if (alien['rect'].right > SCREEN_WIDTH or alien['rect'].left < 0):
                 move_down = True

        # If an edge was hit, reverse direction and move all down
        if move_down:
            self.alien_move_direction *= -1 # Reverse horizontal direction
            for alien in self.aliens:
                alien['rect'].y += ALIEN_MOVE_DROP_Y
                # Move back into screen bounds immediately after direction change
                alien['rect'].x += self.alien_speed_x * self.alien_move_direction * fastest_speed_factor * 2 # Adjust position after flip

    def alien_shoot(self):
        """Randomly selects an alien to fire a bullet."""
        # Only allow aliens at the bottom of their column to shoot
        potential_shooters = []
        cols_with_aliens = set(a['rect'].centerx // ALIEN_SPACING_X for a in self.aliens) # Use rough column index

        for col_idx in cols_with_aliens:
             col_aliens = [a for a in self.aliens if a['rect'].centerx // ALIEN_SPACING_X == col_idx]
             if col_aliens:
                 bottom_alien = max(col_aliens, key=lambda a: a['rect'].bottom)
                 potential_shooters.append(bottom_alien)

        if potential_shooters and random.random() < ALIEN_FIRE_CHANCE * len(self.aliens) * FPS: # Adjust chance based on alien count and frame rate
            shooter = random.choice(potential_shooters)
            bullet_rect = pygame.Rect(shooter['rect'].centerx - BULLET_SIZE // 4, # Thinner bullet
                                      shooter['rect'].bottom,
                                      BULLET_SIZE // 2, BULLET_SIZE)
            self.alien_bullets.append(bullet_rect)

    def check_collisions(self):
        """Checks for collisions between bullets, aliens, player, and barriers."""
        # Player bullets vs Aliens
        aliens_hit_indices = []
        bullets_to_remove_player = []
        for i, bullet in enumerate(self.player_bullets):
            for j, alien in enumerate(self.aliens):
                if bullet.colliderect(alien['rect']):
                    aliens_hit_indices.append(j)
                    bullets_to_remove_player.append(i)
                    self.score += 10 # Increase score
                    break # Bullet hits only one alien

        # Remove hit aliens and bullets (in reverse index order to avoid messing up list)
        aliens_hit_indices = sorted(list(set(aliens_hit_indices)), reverse=True)
        for index in aliens_hit_indices:
            del self.aliens[index]

        bullets_to_remove_player = sorted(list(set(bullets_to_remove_player)), reverse=True)
        for index in bullets_to_remove_player:
            del self.player_bullets[index]

        # Alien bullets vs Player
        for i, bullet in enumerate(self.alien_bullets):
            if self.player_rect.colliderect(bullet):
                self.lives -= 1
                del self.alien_bullets[i]
                if self.lives <= 0:
                    self.game_over = True
                else:
                    # Brief invincibility/visual feedback could be added here
                    print(f"Player hit! Lives left: {self.lives}")
                    # Reset player position briefly? Or just continue.
                break # Player hit, stop checking this bullet

        # Bullets vs Barriers
        bullets_to_remove_player_barrier = []
        bullets_to_remove_alien_barrier = []
        barriers_to_remove = []

        for i, bullet in enumerate(self.player_bullets):
            for j, block in enumerate(self.barriers):
                if bullet.colliderect(block):
                    bullets_to_remove_player_barrier.append(i)
                    barriers_to_remove.append(j)
                    break # Bullet hits one block max

        for i, bullet in enumerate(self.alien_bullets):
            for j, block in enumerate(self.barriers):
                 if bullet.colliderect(block):
                    bullets_to_remove_alien_barrier.append(i)
                    barriers_to_remove.append(j)
                    break # Bullet hits one block max

        # Remove hit barrier blocks and bullets that hit them
        barriers_to_remove = sorted(list(set(barriers_to_remove)), reverse=True)
        for index in barriers_to_remove:
            del self.barriers[index]

        bullets_to_remove_player_barrier = sorted(list(set(bullets_to_remove_player_barrier)), reverse=True)
        for index in bullets_to_remove_player_barrier:
             # Avoid index error if already removed by alien collision check
            if index < len(self.player_bullets):
                 del self.player_bullets[index]

        bullets_to_remove_alien_barrier = sorted(list(set(bullets_to_remove_alien_barrier)), reverse=True)
        for index in bullets_to_remove_alien_barrier:
             # Avoid index error if already removed by player collision check
             if index < len(self.alien_bullets):
                 del self.alien_bullets[index]

        # Aliens vs Barriers (destroy barrier on contact)
        aliens_vs_barriers_to_remove = []
        for j, block in enumerate(self.barriers):
            for alien in self.aliens:
                if alien['rect'].colliderect(block):
                    aliens_vs_barriers_to_remove.append(j)
                    # Don't break here, one alien might touch multiple blocks

        aliens_vs_barriers_to_remove = sorted(list(set(aliens_vs_barriers_to_remove)), reverse=True)
        for index in aliens_vs_barriers_to_remove:
             # Check index validity before deleting
             if index < len(self.barriers):
                del self.barriers[index]

        # Aliens vs Player (Game Over condition)
        for alien in self.aliens:
            if alien['rect'].colliderect(self.player_rect):
                self.game_over = True
                self.lives = 0 # Instant game over
                return # No need to check further

            # Check if aliens reached bottom
            if alien['rect'].bottom >= PLAYER_START_Y - 10: # Give a little buffer
                 self.game_over = True
                 return

    def check_win_condition(self):
        """Checks if all aliens are defeated."""
        if not self.game_over and len(self.aliens) == 0:
            self.game_won = True

    def draw_text(self, text, font, color, surface, x, y, center=False):
        """Helper function to draw text."""
        textobj = font.render(text, True, color)
        textrect = textobj.get_rect()
        if center:
            textrect.center = (x, y)
        else:
            textrect.topleft = (x, y)
        surface.blit(textobj, textrect)

    def draw(self):
        """Draws all game elements."""
        self.screen.fill(BLACK)

        # Draw Player
        self.draw_text(PLAYER_CHAR, self.font_char, BLUE, self.screen, self.player_rect.centerx, self.player_rect.centery, center=True)
        # Optional: Draw player rect for debugging collision box
        # pygame.draw.rect(self.screen, BLUE, self.player_rect, 1)

        # Draw Aliens
        for alien in self.aliens:
            self.draw_text(alien['char'], self.font_char, WHITE, self.screen, alien['rect'].centerx, alien['rect'].centery, center=True)
            # Optional: Draw alien rects for debugging
            # pygame.draw.rect(self.screen, WHITE, alien['rect'], 1)

        # Draw Bullets
        for bullet in self.player_bullets:
            self.draw_text(BULLET_CHAR_PLAYER, self.font_char, GREEN, self.screen, bullet.centerx, bullet.centery, center=True)
            # Optional: Draw bullet rects
            # pygame.draw.rect(self.screen, GREEN, bullet, 1)

        for bullet in self.alien_bullets:
             self.draw_text(BULLET_CHAR_ALIEN, self.font_char, RED, self.screen, bullet.centerx, bullet.centery, center=True)
             # Optional: Draw bullet rects
             # pygame.draw.rect(self.screen, RED, bullet, 1)

        # Draw Barriers (using simple rectangles)
        for block in self.barriers:
            pygame.draw.rect(self.screen, BARRIER_COLOR, block)

        # Draw UI (Score and Lives)
        self.draw_text(f"Score: {self.score}", self.font_ui, WHITE, self.screen, 10, 10)
        self.draw_text(f"Lives: {self.lives}", self.font_ui, WHITE, self.screen, SCREEN_WIDTH - 120, 10)

        # Draw Game Over / Win / Pause Message
        if self.game_over:
            self.draw_text("GAME OVER", self.font_msg, RED, self.screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 50, center=True)
            self.draw_text("Press ENTER to Restart", self.font_ui, WHITE, self.screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 20, center=True)
        elif self.game_won:
            self.draw_text("YOU WIN!", self.font_msg, GREEN, self.screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 50, center=True)
            self.draw_text(f"Final Score: {self.score}", self.font_ui, WHITE, self.screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 20, center=True)
            self.draw_text("Press ENTER to Restart", self.font_ui, WHITE, self.screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 60, center=True)
        elif self.paused:
             self.draw_text("PAUSED", self.font_msg, BLUE, self.screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 50, center=True)
             self.draw_text("Press P to Resume", self.font_ui, WHITE, self.screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 20, center=True)

        pygame.display.flip() # Update the full screen

    def run(self):
        """Main game loop."""
        running = True
        while running:
            running = self.handle_input()
            if not running:
                break

            if not self.game_over and not self.game_won and not self.paused:
                # --- Update ---
                self.move_bullets()
                self.move_aliens()
                self.alien_shoot()
                self.check_collisions()
                self.check_win_condition()

            # --- Draw ---
            self.draw()

            # --- Timing ---
            self.clock.tick(FPS)

            # Brief pause on game end before accepting restart
            if self.game_over or self.game_won:
                # Ensure display is updated before pausing
                pygame.display.flip()
                time.sleep(0.5) # Small delay

        pygame.quit()
        sys.exit()

# --- Main Execution ---
if __name__ == "__main__":
    game = Game()
    game.run()

Gemini May code

You win after clearing 5 levels. Change the number in “if level > 5” to whatever you want to win faster or slower.

# generated by Gemini 2.5 Pro Preview 05-06 on 7 May 2025

import pygame
import random
import sys

# --- Constants ---
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)

PLAYER_WIDTH = 40
PLAYER_HEIGHT = 20
PLAYER_SPEED = 7
PLAYER_START_X = SCREEN_WIDTH // 2 - PLAYER_WIDTH // 2
PLAYER_START_Y = SCREEN_HEIGHT - PLAYER_HEIGHT - 20
PLAYER_CHAR = "^" # Or use None to draw a rect

BULLET_WIDTH = 4
BULLET_HEIGHT = 10
PLAYER_BULLET_SPEED = 10
ALIEN_BULLET_SPEED = 5
PLAYER_BULLET_CHAR = "|" # Or use None to draw a rect
ALIEN_BULLET_CHAR = "*"  # Or use None to draw a rect

ALIEN_ROWS = 5
ALIENS_PER_ROW = 10
ALIEN_SPACING_X = 50
ALIEN_SPACING_Y = 30
ALIEN_START_X = 50
ALIEN_START_Y = 50
ALIEN_WIDTH = 30 # For collision, text will be smaller
ALIEN_HEIGHT = 20 # For collision, text will be smaller
ALIEN_MOVE_SPEED_X = 10 # Pixels per move step
ALIEN_MOVE_SPEED_Y = 15 # Pixels down on edge hit
ALIEN_CHARS = ['W', 'V', 'O', 'X', 'M'] # Different types for rows
ALIEN_COLORS = [YELLOW, (255,165,0), (255,105,180), (173,216,230), (144,238,144)]
ALIEN_SHOOT_CHANCE = 0.002 # Chance per alien per frame to shoot (adjust for difficulty)
ALIEN_MOVE_INTERVAL_INITIAL = 1000 # milliseconds
ALIEN_MOVE_INTERVAL_MIN = 150 # Minimum interval as aliens are destroyed

BARRIER_BLOCK_SIZE = 10
BARRIER_ROWS = 3
BARRIER_COLS = 6
NUM_BARRIERS = 4
BARRIER_Y_POS = SCREEN_HEIGHT - 120
BARRIER_SPACING = (SCREEN_WIDTH - (NUM_BARRIERS * BARRIER_COLS * BARRIER_BLOCK_SIZE)) // (NUM_BARRIERS + 1)

# --- Initialize Pygame ---
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Text Invaders")
clock = pygame.time.Clock()
font_game = pygame.font.SysFont(None, 36) # General game font
font_small = pygame.font.SysFont(None, 24) # For alien characters
font_title = pygame.font.SysFont(None, 72) # For titles

# --- Game Variables ---
player_lives = 3
score = 0
game_state = "start_menu" # "start_menu", "playing", "game_over", "victory"
level = 1

# --- Helper Functions ---
def draw_text(text, font, color, surface, x, y, center=False):
    text_obj = font.render(text, True, color)
    text_rect = text_obj.get_rect()
    if center:
        text_rect.center = (x, y)
    else:
        text_rect.topleft = (x, y)
    surface.blit(text_obj, text_rect)

def create_aliens():
    aliens_list = []
    for row_idx in range(ALIEN_ROWS):
        for col_idx in range(ALIENS_PER_ROW):
            x = ALIEN_START_X + col_idx * ALIEN_SPACING_X
            y = ALIEN_START_Y + row_idx * ALIEN_SPACING_Y
            # Rect is slightly larger than text for easier collision and visual consistency
            alien_rect = pygame.Rect(x, y, ALIEN_WIDTH, ALIEN_HEIGHT)
            char_index = row_idx % len(ALIEN_CHARS)
            alien_char = ALIEN_CHARS[char_index]
            alien_color = ALIEN_COLORS[char_index]
            points = (ALIEN_ROWS - row_idx) * 10 # More points for higher rows
            aliens_list.append({'rect': alien_rect, 'char': alien_char, 'color': alien_color, 'points': points})
    return aliens_list

def create_barriers():
    barriers_list = []
    for i in range(NUM_BARRIERS):
        barrier_start_x = BARRIER_SPACING + i * (BARRIER_COLS * BARRIER_BLOCK_SIZE + BARRIER_SPACING)
        for r in range(BARRIER_ROWS):
            for c in range(BARRIER_COLS):
                # Simple rectangular barrier shape
                if not ((r == 0 and (c == 0 or c == BARRIER_COLS - 1)) or \
                        (r == BARRIER_ROWS -1 and (c > 0 and c < BARRIER_COLS -1 and c%2 == 0 ))): # Simple arch
                    block_x = barrier_start_x + c * BARRIER_BLOCK_SIZE
                    block_y = BARRIER_Y_POS + r * BARRIER_BLOCK_SIZE
                    barriers_list.append(pygame.Rect(block_x, block_y, BARRIER_BLOCK_SIZE, BARRIER_BLOCK_SIZE))
    return barriers_list

# --- Game Object Initialization ---
player_rect = pygame.Rect(PLAYER_START_X, PLAYER_START_Y, PLAYER_WIDTH, PLAYER_HEIGHT)
player_bullets = []
aliens = []
alien_bullets = []
barriers = []

alien_move_direction = 1 # 1 for right, -1 for left
alien_last_move_time = 0
current_alien_move_interval = ALIEN_MOVE_INTERVAL_INITIAL

def reset_level():
    global aliens, alien_bullets, player_bullets, barriers
    global alien_move_direction, alien_last_move_time, current_alien_move_interval
    global player_rect

    aliens = create_aliens()
    alien_bullets = []
    player_bullets = [] # Keep player bullets or clear? Clearing seems fair.
    barriers = create_barriers()

    alien_move_direction = 1
    alien_last_move_time = pygame.time.get_ticks()
    current_alien_move_interval = ALIEN_MOVE_INTERVAL_INITIAL - (level - 1) * 50 # Faster for higher levels
    current_alien_move_interval = max(current_alien_move_interval, ALIEN_MOVE_INTERVAL_MIN)

    player_rect.x = PLAYER_START_X
    player_rect.y = PLAYER_START_Y

def start_new_game():
    global player_lives, score, game_state, level
    player_lives = 3
    score = 0
    level = 1
    reset_level()
    game_state = "playing"

# --- Game Loop ---
running = True
while running:
    current_time = pygame.time.get_ticks()

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        
        if game_state == "playing":
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE and len(player_bullets) < 1: # Limit to 1 bullet
                    bullet_x = player_rect.centerx - BULLET_WIDTH // 2
                    bullet_y = player_rect.top - BULLET_HEIGHT
                    player_bullets.append(pygame.Rect(bullet_x, bullet_y, BULLET_WIDTH, BULLET_HEIGHT))
        elif game_state == "start_menu" or game_state == "game_over" or game_state == "victory":
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RETURN:
                    start_new_game()
                if event.key == pygame.K_ESCAPE:
                    running = False

    # --- Game Logic ---
    if game_state == "playing":
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] and player_rect.left > 0:
            player_rect.x -= PLAYER_SPEED
        if keys[pygame.K_RIGHT] and player_rect.right < SCREEN_WIDTH:
            player_rect.x += PLAYER_SPEED

        # Player Bullets
        for bullet in player_bullets[:]: # Iterate a copy for safe removal
            bullet.y -= PLAYER_BULLET_SPEED
            if bullet.bottom < 0:
                player_bullets.remove(bullet)
            else:
                # Check collision with aliens
                for alien in aliens[:]:
                    if bullet.colliderect(alien['rect']):
                        aliens.remove(alien)
                        player_bullets.remove(bullet)
                        score += alien['points']
                        # Speed up aliens as they are destroyed
                        if aliens: # Avoid division by zero
                            num_initial_aliens = ALIEN_ROWS * ALIENS_PER_ROW
                            ratio_remaining = len(aliens) / num_initial_aliens
                            current_alien_move_interval = ALIEN_MOVE_INTERVAL_MIN + \
                                                          (ALIEN_MOVE_INTERVAL_INITIAL - ALIEN_MOVE_INTERVAL_MIN) * ratio_remaining
                            current_alien_move_interval = max(current_alien_move_interval, ALIEN_MOVE_INTERVAL_MIN)
                        break # Bullet can only hit one alien
                else: # If bullet didn't hit an alien, check barriers
                    for block in barriers[:]:
                        if bullet.colliderect(block):
                            barriers.remove(block)
                            player_bullets.remove(bullet)
                            break

        # Alien Movement
        move_aliens_now = False
        if current_time - alien_last_move_time > current_alien_move_interval:
            move_aliens_now = True
            alien_last_move_time = current_time
        
        if move_aliens_now:
            move_down = False
            for alien in aliens:
                if (alien['rect'].right + ALIEN_MOVE_SPEED_X * alien_move_direction > SCREEN_WIDTH and alien_move_direction == 1) or \
                   (alien['rect'].left + ALIEN_MOVE_SPEED_X * alien_move_direction < 0 and alien_move_direction == -1):
                    move_down = True
                    alien_move_direction *= -1 # Reverse direction for next horizontal move
                    break 
            
            for alien in aliens:
                if move_down:
                    alien['rect'].y += ALIEN_MOVE_SPEED_Y
                    if alien['rect'].bottom >= BARRIER_Y_POS: # Aliens reached barrier level
                         game_state = "game_over" # Or player_rect.top if no barriers
                         break
                else:
                    alien['rect'].x += ALIEN_MOVE_SPEED_X * alien_move_direction
            if game_state == "game_over": # Check again if aliens moved down and caused game over
                continue

        # Alien Shooting
        for alien in aliens:
            if random.random() < ALIEN_SHOOT_CHANCE and len(alien_bullets) < (5 + level): # Limit total alien bullets
                # Check if this alien is at the bottom of its column (optional, makes it more strategic)
                is_bottom_alien = True
                for other_alien in aliens:
                    if other_alien['rect'].left < alien['rect'].centerx < other_alien['rect'].right and \
                       other_alien['rect'].top > alien['rect'].top:
                        is_bottom_alien = False
                        break
                if is_bottom_alien:
                    bullet_x = alien['rect'].centerx - BULLET_WIDTH // 2
                    bullet_y = alien['rect'].bottom
                    alien_bullets.append(pygame.Rect(bullet_x, bullet_y, BULLET_WIDTH, BULLET_HEIGHT))

        # Alien Bullets
        for bullet in alien_bullets[:]:
            bullet.y += ALIEN_BULLET_SPEED
            if bullet.top > SCREEN_HEIGHT:
                alien_bullets.remove(bullet)
            else:
                # Check collision with player
                if bullet.colliderect(player_rect):
                    alien_bullets.remove(bullet)
                    player_lives -= 1
                    # Simple screen flash or player color change on hit
                    screen.fill(RED) 
                    pygame.display.flip()
                    pygame.time.wait(100)

                    if player_lives <= 0:
                        game_state = "game_over"
                    else: # Reset player position, maybe some invincibility
                        player_rect.x = PLAYER_START_X
                    break 
                # Check collision with barriers
                for block in barriers[:]:
                    if bullet.colliderect(block):
                        barriers.remove(block)
                        alien_bullets.remove(bullet)
                        break
        
        # Win/Loss Conditions
        if not aliens and game_state == "playing": # All aliens destroyed
            level += 1
            if level > 5: # Example: win after 5 levels
                game_state = "victory"
            else:
                reset_level() # Go to next level
                # Brief "Next Level" message
                screen.fill(BLACK)
                draw_text(f"Level {level}", font_title, GREEN, screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 50, center=True)
                pygame.display.flip()
                pygame.time.wait(1500)

    # --- Drawing ---
    screen.fill(BLACK)

    if game_state == "start_menu":
        draw_text("TEXT INVADERS", font_title, GREEN, screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 100, center=True)
        draw_text("Press ENTER to Start", font_game, WHITE, screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 50, center=True)
        draw_text("Use Arrow Keys to Move, SPACE to Shoot", font_small, WHITE, screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 100, center=True)
        draw_text("Press ESCAPE to Quit", font_small, WHITE, screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 150, center=True)

    elif game_state == "playing":
        # Draw Player
        if PLAYER_CHAR:
            draw_text(PLAYER_CHAR, font_game, GREEN, screen, player_rect.centerx, player_rect.top, center=True)
        else:
            pygame.draw.rect(screen, GREEN, player_rect)

        # Draw Player Bullets
        for bullet in player_bullets:
            if PLAYER_BULLET_CHAR:
                 draw_text(PLAYER_BULLET_CHAR, font_game, WHITE, screen, bullet.centerx, bullet.top, center=True)
            else:
                pygame.draw.rect(screen, WHITE, bullet)

        # Draw Aliens
        for alien in aliens:
            # Center the text within the alien's rect
            text_surf = font_small.render(alien['char'], True, alien['color'])
            text_rect = text_surf.get_rect(center=alien['rect'].center)
            screen.blit(text_surf, text_rect)
            # pygame.draw.rect(screen, (50,50,50), alien['rect'], 1) # For debugging alien rects

        # Draw Alien Bullets
        for bullet in alien_bullets:
            if ALIEN_BULLET_CHAR:
                draw_text(ALIEN_BULLET_CHAR, font_game, RED, screen, bullet.centerx, bullet.top, center=True)
            else:
                pygame.draw.rect(screen, RED, bullet)
        
        # Draw Barriers
        for block in barriers:
            pygame.draw.rect(screen, BLUE, block)

        # Draw Score and Lives
        draw_text(f"Score: {score}", font_game, WHITE, screen, 10, 10)
        lives_text = "Lives: "
        for i in range(player_lives):
            if PLAYER_CHAR:
                lives_text += PLAYER_CHAR + " "
            else: # Draw small rects for lives if no char
                pygame.draw.rect(screen, GREEN, (SCREEN_WIDTH - 100 + i * (PLAYER_WIDTH//2 + 5), 10, PLAYER_WIDTH//2, PLAYER_HEIGHT//2))
        if PLAYER_CHAR: # Draw text lives if char is used
             draw_text(lives_text, font_game, WHITE, screen, SCREEN_WIDTH - 150, 10)
        draw_text(f"Level: {level}", font_game, WHITE, screen, SCREEN_WIDTH // 2 - 50, 10)

    elif game_state == "game_over":
        draw_text("GAME OVER", font_title, RED, screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 50, center=True)
        draw_text(f"Final Score: {score}", font_game, WHITE, screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 20, center=True)
        draw_text("Press ENTER to Play Again", font_game, WHITE, screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 70, center=True)
        draw_text("Press ESCAPE to Quit", font_small, WHITE, screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 120, center=True)

    elif game_state == "victory":
        draw_text("YOU WIN!", font_title, YELLOW, screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 50, center=True)
        draw_text(f"Final Score: {score}", font_game, WHITE, screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 20, center=True)
        draw_text("Press ENTER to Play Again", font_game, WHITE, screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 70, center=True)
        draw_text("Press ESCAPE to Quit", font_small, WHITE, screen, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 120, center=True)

    pygame.display.flip()
    clock.tick(30) # FPS

pygame.quit()
sys.exit()

to publications

to home