The Invaders Strike Back
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
A few numbers
Enjoy the game play screenshots
Start
Play
Next Level
Game Over
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()