import time import random # --- Game Constants --- WIDTH = 20 HEIGHT = 15 DOT = '.' WALL = '#' PACMAN = '@' GHOST = 'G' EMPTY = ' ' POWER_PILL = 'P' # --- Initial Game State --- def initialize_game(): """Sets up the initial maze, player, and ghost positions.""" # Create a simple maze structure (border walls) maze = [[EMPTY for _ in range(WIDTH)] for _ in range(HEIGHT)] # Draw outer walls for i in range(WIDTH): maze[0][i] = WALL maze[HEIGHT - 1][i] = WALL for i in range(HEIGHT): maze[i][0] = WALL maze[i][WIDTH - 1] = WALL # Add internal walls and dots for y in range(2, HEIGHT - 2): for x in range(2, WIDTH - 2): if random.random() < 0.2: # 20% chance of a wall block maze[y][x] = WALL else: maze[y][x] = DOT # Place Pac-Man in a clear spot pacman_pos = {'x': 1, 'y': 1, 'dir': (0, 0), 'lives': 3, 'score': 0, 'vulnerable_timer': 0} # Ensure starting area is clear maze[pacman_pos['y']][pacman_pos['x']] = EMPTY maze[pacman_pos['y']][pacman_pos['x'] + 1] = DOT maze[pacman_pos['y'] + 1][pacman_pos['x']] = DOT # Place ghosts ghosts = [ {'x': WIDTH - 2, 'y': HEIGHT - 2, 'id': 1, 'vulnerable': False}, {'x': 1, 'y': HEIGHT - 2, 'id': 2, 'vulnerable': False} ] # Place power pills maze[1][WIDTH - 2] = POWER_PILL maze[HEIGHT - 2][1] = POWER_PILL # Count total dots for win condition dot_count = sum(row.count(DOT) for row in maze) + 2 # +2 for the power pills return maze, pacman_pos, ghosts, dot_count # --- Drawing Functions --- def draw_board(maze, pacman_pos, ghosts): """Renders the game state to the console.""" print("\n" * 50) # Clear the console (basic attempt) # Header status = "VULNERABLE" if pacman_pos['vulnerable_timer'] > 0 else "NORMAL" print(f"--- PAC-MAN: Terminal Edition ---") print(f"Score: {pacman_pos['score']} | Lives: {pacman_pos['lives']} | Status: {status}") print("-" * (WIDTH + 2)) # Create a temporary board copy for drawing characters display_board = [list(row) for row in maze] # Draw Pac-Man display_board[pacman_pos['y']][pacman_pos['x']] = PACMAN # Draw Ghosts for ghost in ghosts: if ghost['vulnerable']: display_board[ghost['y']][ghost['x']] = 'v' # Vulnerable ghost symbol elif (ghost['x'], ghost['y']) != (pacman_pos['x'], pacman_pos['y']): display_board[ghost['y']][ghost['x']] = GHOST # Print the board for row in display_board: print(" " + "".join(row) + " ") print("-" * (WIDTH + 2)) # --- Movement and Logic --- def get_input(pacman_pos): """Gets non-blocking input from the user (simulation in this single-file context).""" # In a real terminal, you'd use a non-blocking input library. # For a simple script, we'll prompt for input, but make it optional # to keep the movement going. try: user_input = input("Move (w/a/s/d, or Enter to continue): ").lower() if user_input == 'w': return (0, -1) elif user_input == 's': return (0, 1) elif user_input == 'a': return (-1, 0) elif user_input == 'd': return (1, 0) except EOFError: # Handles potential Ctrl+D exit pass except Exception: pass return pacman_pos['dir'] # Keep current direction if no valid input def move_pacman(maze, pacman_pos, total_dots): """Applies movement and handles wall and dot collisions.""" dx, dy = pacman_pos['dir'] new_x, new_y = pacman_pos['x'] + dx, pacman_pos['y'] + dy if 0 <= new_y < HEIGHT and 0 <= new_x < WIDTH and maze[new_y][new_x] != WALL: pacman_pos['x'], pacman_pos['y'] = new_x, new_y # Check for dot or power pill if maze[new_y][new_x] == DOT: pacman_pos['score'] += 10 total_dots -= 1 elif maze[new_y][new_x] == POWER_PILL: pacman_pos['score'] += 50 pacman_pos['vulnerable_timer'] = 10 # 10 ghost move ticks total_dots -= 1 maze[new_y][new_x] = EMPTY # Consume the dot/pill return total_dots def move_ghosts(maze, pacman_pos, ghosts): """Moves ghosts randomly and handles collision.""" for ghost in ghosts: # Ghost AI: Attempt to move one step towards Pac-Man 50% of the time, # otherwise move randomly. possible_moves = [] if pacman_pos['vulnerable_timer'] > 0: # Fleeing AI (move away from pacman) target_x = ghost['x'] - pacman_pos['x'] target_y = ghost['y'] - pacman_pos['y'] else: # Chasing AI (move towards pacman) target_x = pacman_pos['x'] - ghost['x'] target_y = pacman_pos['y'] - ghost['y'] # Prioritize movement in the direction of the largest displacement if abs(target_x) > abs(target_y): # Try X-direction if target_x > 0: possible_moves.append((1, 0)) # Right else: possible_moves.append((-1, 0)) # Left else: # Try Y-direction if target_y > 0: possible_moves.append((0, 1)) # Down else: possible_moves.append((0, -1)) # Up # Add random moves as backup possible_moves.extend([(1, 0), (-1, 0), (0, 1), (0, -1)]) # Select first valid move new_x, new_y = ghost['x'], ghost['y'] for dx, dy in possible_moves: nx, ny = ghost['x'] + dx, ghost['y'] + dy if 0 <= ny < HEIGHT and 0 <= nx < WIDTH and maze[ny][nx] != WALL: new_x, new_y = nx, ny break ghost['x'], ghost['y'] = new_x, new_y # Check for collisions after all moves check_collisions(pacman_pos, ghosts) # Decrease vulnerability timer if pacman_pos['vulnerable_timer'] > 0: pacman_pos['vulnerable_timer'] -= 1 # Update ghost vulnerability state is_vulnerable = pacman_pos['vulnerable_timer'] > 0 for ghost in ghosts: ghost['vulnerable'] = is_vulnerable def check_collisions(pacman_pos, ghosts): """Handles interaction when Pac-Man and a Ghost share a tile.""" i = 0 while i < len(ghosts): ghost = ghosts[i] if pacman_pos['x'] == ghost['x'] and pacman_pos['y'] == ghost['y']: if ghost['vulnerable']: # Eat Ghost pacman_pos['score'] += 200 print("EAT GHOST!") # Remove ghost for simplicity (or reset its position in a complex version) ghosts.pop(i) continue # Skip incrementing i since we popped an element else: # Pac-Man dies pacman_pos['lives'] -= 1 print("PACMAN HIT!") if pacman_pos['lives'] > 0: reset_pacman_position(pacman_pos, ghosts) return # Exit collision check to pause game flow i += 1 def reset_pacman_position(pacman_pos, ghosts): """Resets Pac-Man and ghosts to starting positions after a death.""" pacman_pos['x'], pacman_pos['y'] = 1, 1 pacman_pos['dir'] = (0, 0) pacman_pos['vulnerable_timer'] = 0 # Simple ghost reset ghosts[0]['x'], ghosts[0]['y'] = WIDTH - 2, HEIGHT - 2 ghosts[1]['x'], ghosts[1]['y'] = 1, HEIGHT - 2 for ghost in ghosts: ghost['vulnerable'] = False # --- Main Game Loop --- def run_game(): """The main entry point for the text-based Pac-Man game.""" try: maze, pacman_pos, ghosts, total_dots = initialize_game() except Exception as e: print(f"Error initializing game: {e}") return running = True # In a simple terminal script, the game loop pauses for input, # making the speed easier to control than time.sleep. print("Welcome to Pac-Man: Terminal Edition!") print("Use w/a/s/d or arrow keys (if your terminal supports it) to move.") time.sleep(1) # Small pause while running: draw_board(maze, pacman_pos, ghosts) # 1. Check for Win/Loss Conditions if pacman_pos['lives'] <= 0: running = False print("\n!!! GAME OVER !!!") print(f"Final Score: {pacman_pos['score']}") break if total_dots == 0: running = False print("\n!!! YOU WON !!!") print(f"Final Score: {pacman_pos['score']}") break # 2. Get User Input new_dir = get_input(pacman_pos) pacman_pos['dir'] = new_dir # 3. Update Pac-Man total_dots = move_pacman(maze, pacman_pos, total_dots) # 4. Update Ghosts move_ghosts(maze, pacman_pos, ghosts) # If the game uses a fixed time step (like a real game loop), # you would use time.sleep(0.2) here. # Since we rely on user input, we skip the sleep. if __name__ == "__main__": run_game()