RogueAsteroid
The Hook
Most games have dumb enemies. They follow pre-programmed patterns, repeat the same mistakes, and never adapt.
RogueAsteroid is different. The AI opponent watches how you play and evolves against you.
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 the surface. Underneath, a neural network is learning your playstyle and evolving to counter it.
The AI Evolution
The opponent uses NEAT (NeuroEvolution of Augmenting Topologies) to evolve its neural network:
- Generation 0: Random movement, no strategy, easy to beat
- Generation 10: Basic avoidance, starts predicting your patterns
- Generation 50: Adaptive strategy, counters your specific playstyle
- Generation 100: Specialized opponent tuned to how you play
The AI doesn’t just get harder — it gets smarter about you.
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. 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.
Technical Depth
NEAT Implementation: Evolves both the weights and the topology of neural networks, starting from minimal complexity and complexifying only as needed to achieve higher fitness.
Fitness Function: The AI is rewarded for: survival time, accuracy, damage dealt, and adaptability to player patterns.
Population Management: Maintains diverse population to prevent convergence to single strategy. Novelty search rewards unique behaviors.
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.
GitHub: RogueAsteroid
Built with Python, Pygame, and NEAT. The AI opponent evolves against you.