The Invaders Strike Back
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? 🚀

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

Enjoy the game play screenshots





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