RogueAsteroid
Physics-based arcade game with procedural difficulty and modular architecture
The Problem
Recreate the classic 1979 Asteroids arcade game with modern code architecture and progressive difficulty. The challenge was proving that clean architecture works even in performance-critical game code—most games are giant main loops with spaghetti logic.
My Approach
- → Entity-Component System (ECS) architecture: entities = data, systems = behavior
- → Modular design: game mechanics defined declaratively in config files
- → Particle system for explosions, bullet trails, visual polish
- → Procedural difficulty scaling (asteroid count, speed, spawn patterns)
- → Clean separation: game logic ≠ rendering ≠ physics
Key Highlights
- 221 commits showing iterative refinement
- Fully moddable via YAML config files (adjust game balance without code)
- Entity-Component architecture enables easy feature addition
- Particle system with efficient batch rendering
- Progressive difficulty: survive 10 waves, it gets harder
How It Works
RogueAsteroid is the project I built to prove a point: Game code doesn’t have to be a mess.
Most game projects devolve into unmaintainable spaghetti because they optimize for “fast feature iteration” at the cost of structure. You end up with a 5,000-line main.py where everything depends on everything.
RogueAsteroid shows an alternative.
The Game
You’re a spaceship. Asteroids come toward you. Shoot them. They break into smaller asteroids. Stay alive long enough and you win the round. Survive 10 rounds and you beat the game.
That’s it. The simplicity lets me focus on architecture instead of fighting complexity.
Architecture
Entities: Represent game objects (player ship, asteroids, bullets, particles).
@dataclass
class Entity:
position: Vector2
velocity: Vector2
components: Dict[str, Component]
Components: Encapsulate behaviors (physics, rendering, health).
@dataclass
class PhysicsComponent(Component):
velocity: Vector2
acceleration: Vector2
Systems: Operate on all entities with matching components.
def physics_system(entities):
for entity in entities:
if entity.has(PhysicsComponent):
physics = entity.get(PhysicsComponent)
entity.position += physics.velocity
Why ECS?
Traditional OOP game code uses inheritance:
class GameObject:
pass
class MovableGameObject(GameObject):
velocity: Vector2
class PhysicsGameObject(MovableGameObject):
acceleration: Vector2
This breaks. As you add behaviors, the inheritance tree becomes incomprehensible.
ECS inverts it: entities are just bags of components. Behavior emerges from which components are present.
Want a static asteroid? Add Position, Sprite. Want it to move? Add Velocity. Want physics? Add Acceleration. No inheritance nonsense.
Modularity
Game balance lives in config.yaml:
asteroid:
large:
health: 3
speed: 2.0
split_count: 2
medium:
health: 2
speed: 3.0
Want harder difficulty? Change speed: 2.0 → speed: 3.5. No code changes, no recompile.
This matters. Game designers (and players through mods) should be able to tune without touching code.
Particle System
Explosions use a particle emitter:
- Burst of 20-50 particles on impact
- Each particle has position, velocity, lifetime, color
- Rendered as a single batched sprite (efficient)
- Looks satisfying without performance cost
This kind of polish is easy with clean architecture. Add 3 lines to spawn particles, done.
Lessons Learned
Premature optimization is worse in games than anywhere else. Classic Asteroids runs at 60 FPS on modern hardware with a clean architecture. Spaghetti code wouldn’t be faster.
Config-driven design scales. If designers can’t tweak numbers without coding, they’ll stop using your engine. Make everything configurable.
ECS is not overkill. Even a simple game benefits from separating data from behavior. Especially as you add features.
221 commits proves iterative refinement. Not a one-time sprint. Sustained development, improving incrementally.
Future Work
The foundation is solid enough for Pygbag (Python → WebAssembly) conversion. Running Asteroids in a web browser would be the obvious next step—demonstrate that tight, clean code ports across platforms cleanly.