In this technical blog, I'll walk you through creating "Echoes of Code," a 2D top-down puzzle game where you solve puzzles by collaborating with echoes of your past actions. I'll focus on the key components and concepts to keep this a quick 5-minute read.
Setting Up the Development Environment
Installing WSL on Windows
- Enable WSL: Open PowerShell as Administrator and run:([DEV Community][1])
wsl --install
- Restart your computer and complete the Linux distribution setup.
Setting Up Python and PyGame
# Install Python and dependencies sudo apt install python3 python3-pip python3-dev -y sudo apt install python3-pygame -y # Install PyGame pip3 install pygame
Installing Amazon Q CLI
# Install AWS CLI first curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip awscliv2.zip sudo ./aws/install # Configure AWS aws configure # Install Amazon Q CLI curl -Lo "/tmp/q-cli.deb" "https://d3l58w4xvhud7h.cloudfront.net/linux/amd64/latest/q-cli.deb" sudo dpkg -i /tmp/q-cli.deb # Login q login
Game Design and Concept
"Echoes of Code" features a software engineer trapped in a glitched digital world. Every 10 seconds, time resets, and your past actions are replayed by ghost clones called "echoes." You must use these echoes strategically to solve puzzles.
Implementation Highlights
Time Loop System
The core of the game is the TimerManager
class that handles the time loop mechanics:
class TimerManager: def __init__(self, loop_duration: float, max_loops: int): self.loop_duration = loop_duration self.max_loops = max_loops self.start_time = time.time() self.current_loop = 1 def get_loop_time(self) -> float: """Get time elapsed in the current loop""" return self.get_elapsed_time() % self.loop_duration def should_reset_loop(self) -> bool: return self.get_loop_time() >= self.loop_duration def reset_loop(self): self.current_loop += 1
Action Recording and Replay
The Player
class records actions, while the Echo
class replays them:
# In Player class def record_action(self, action_type: str, time_stamp: float): self.actions.append(Action(action_type, (self.x, self.y), time_stamp, self.current_direction)) # In Echo class def update(self, current_time: float, game_objects: List): if self.current_action_index < len(self.actions): next_action = self.actions[self.current_action_index] if current_time >= next_action.time_stamp: if next_action.action_type == "move": self.x, self.y = next_action.position self.current_direction = next_action.direction elif next_action.action_type == "interact": self.interact_with_objects(game_objects) self.current_action_index += 1
Interactive Game Objects
The game includes various interactive elements:
# Example of the PressurePlate class class PressurePlate: def update(self, players: List[Player]): self.is_active = False plate_rect = pygame.Rect(self.x, self.y, self.width, self.height) for player in players: player_rect = pygame.Rect(player.x, player.y, player.width, player.height) if plate_rect.colliderect(player_rect): self.is_active = True break
Loop Reset Logic
When a loop resets, we create a new echo and reset the player:
def reset_loop(self): # Create a new echo from the current player echo_colors = [PURPLE, YELLOW, CYAN, RED, GREEN] echo_color = echo_colors[min(len(self.echoes), len(echo_colors) - 1)] self.echoes.append(Echo(self.player, echo_color, self.timer_manager.current_loop)) # Reset player position current_level = self.level_manager.get_current_level() self.player.x, self.player.y = current_level.player_start # Clear player actions for the new loop self.player.actions = [] # Increment loop counter self.timer_manager.reset_loop()
Level Design
Levels are created using the Level
class, which manages all game objects:
def create_levels(self): # Level 1: Simple pressure plate and gate level1 = Level(1, (100, 300), 3) # Player starts at (100, 300), 3 loops max # Add walls (border) level1.add_wall(0, 0, SCREEN_WIDTH, 20) # Top level1.add_wall(0, SCREEN_HEIGHT - 20, SCREEN_WIDTH, 20) # Bottom # Add pressure plate level1.add_pressure_plate(300, 200, 1) # Controls gate with ID 1 # Add gate level1.add_gate(400, 400, 20, 100, 1) # Gate with ID 1 # Add exit level1.set_exit(700, 300)
Main Game Loop
The Game
class ties everything together:
def run(self): running = True while running: running = self.handle_events() self.update() self.draw() self.clock.tick(FPS)
Development Challenges
Echo Replay Timing: Ensuring echoes replayed actions at the exact same time they were recorded.
Collision Detection: Making sure players and echoes interacted correctly with game objects.
Loop Reset Logic: Managing the state transition when a loop resets, especially ensuring all objects reset properly.
Future Enhancements
The game can be expanded with:
Additional Levels: More complex puzzles requiring multiple echoes to solve.
Mini Terminal: Implementing a system where players can write simple logic commands.
Enemies: Adding entities that track and interact with echoes.
Visual Timeline: Creating a replay system to show all actions at the end of each level.
Running the Game
To run the game:
cd ~/echoes_of_code python3 echoes_of_code.py
Conclusion
Building "Echoes of Code" demonstrates how a unique game mechanic like time loops can create engaging puzzle gameplay. The modular code structure makes it easy to expand with new levels and features. By leveraging WSL for development and Amazon Q CLI for assistance, I was able to streamline the development process and focus on implementing the core game mechanics.
If you're interested in time-based puzzle games or want to explore PyGame development, I encourage you to try out "Echoes of Code" and even extend it with your own ideas!
Top comments (0)