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.0speed: 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.