DEV Community

Cover image for Creating a Plants Vs Zombies with Redis
robot254
robot254 Subscriber

Posted on

Creating a Plants Vs Zombies with Redis

Redis AI Challenge: Beyond the Cache

This is a submission for the Redis AI Challenge: Beyond the Cache.

What I Built

I developed a Plants vs. Zombies-inspired tower defense game that redefines what’s possible with Redis. This project uses Redis as the entire backend infrastructure. All game logic, state management, and real-time features are handled without a traditional SQL or NoSQL database.

Core Game Features

  • Tower Defense: Engage in real-time battles against waves of zombies.
  • Live Leaderboards: Compete for the top spot with five different real-time leaderboards.
  • Persistent Game State: Players can reconnect and resume their games exactly where they left off. -** Dynamic Player Stats:** Track comprehensive, live statistics and player progress.
  • Smart Username Suggestions: Get creative, plant-themed username ideas automatically.

How Redis Powers the Backend

  • Redis is the engine behind the game. I utilized its diverse data structures to build a highly performant and scalable backend.
  • Real-time Communication: Redis Pub/Sub handles all real-time events for multiplayer gameplay.
  • Leaderboards: Redis sorted sets manage and update the live leaderboards, making it easy to rank players.
  • Player Data: Game state and player statistics are stored using Redis hashes and other data structures for efficient access and updates.
  • Analytics: Redis data structures track game metrics and analytics on the fly.
  • Persistence: Redis's persistence features ensure that all game data is saved across sessions.

Demo

Here is the full Github Code you can test for yourself Github Code

How I Used Redis 8

This project demonstrates Redis using different Redis data structures to replace traditional database architectures:

1. Primary Database (Hashes)

Redis completely replaces SQL databases
Redis stores all persistent data including complex nested game states, player profiles, and session information.

// Store complete game states as JSON
await redis.hset(`game:${gameId}:state`, 'data', JSON.stringify({
  players: {...},
  plants: [...],
  zombies: [...],
  currentWave: 5,
  gameStatus: 'playing'
}));

// Player profiles with statistics and achievements  
await redis.hset(`player:${playerId}:data`, {
  score: 15420,
  zombiesKilled: 89,
  plantsPlanted: 156,
  gamesWon: 12,
  achievements: JSON.stringify(['first_win', 'zombie_slayer'])
});
Enter fullscreen mode Exit fullscreen mode

2. Real-Time Pub/Sub

Live multiplayer without polling
All real-time updates use Redis Pub/Sub for sub-millisecond message delivery to unlimited concurrent players.

// Broadcast plant placements to all players instantly
await redis.publish(`game:${gameId}:updates`, JSON.stringify({
  type: 'plant_placed',
  plant: {type: 'sunflower', row: 2, col: 3},
  playerId: 'GardenMaster',
  timestamp: Date.now()
}));

// Wave completion notifications
await redis.publish(`game:${gameId}:events`, JSON.stringify({
  type: 'wave_completed', 
  wave: 5,
  nextWaveIn: 30000,
  message: 'Wave 5 completed! Prepare for boss wave!'
}));
Enter fullscreen mode Exit fullscreen mode

3. Event Sourcing (Streams)

Complete game replay and analytics pipeline
Provides complete event sourcing capabilities - every game action is recorded for replay, analytics, and debugging without external event streaming systems

// Record every game action for complete audit trail
await redis.xadd(`game:${gameId}:events`, '*', {
  action: 'plant_placed',
  playerId: 'ZombieSlayer', 
  plantType: 'peashooter',
  position: JSON.stringify({row: 1, col: 4}),
  cost: 100,
  timestamp: Date.now()
});

// Analytics pipeline processes all events
const gameEvents = await redis.xrange(`game:${gameId}:events`, '-', '+');
// Can reconstruct any game state from event history
Enter fullscreen mode Exit fullscreen mode

4. Leaderboards (Sorted Sets)

Real-time competitive rankings
Automatic ranking system with O(log N) performance - no manual sorting or separate ranking calculations needed.

// Update multiple leaderboards simultaneously during gameplay
await redis.zadd('leaderboard:high_scores', player.score, playerId);
await redis.zadd('leaderboard:zombies_killed', player.zombiesKilled, playerId);
await redis.zadd('leaderboard:plants_planted', player.plantsPlanted, playerId);

// Get top 10 players with scores instantly
const topPlayers = await redis.zrevrange('leaderboard:high_scores', 0, 9, 'WITHSCORES');
// Returns: [['EpicSunflower', '25840'], ['GardenMaster', '23150'], ...]
Enter fullscreen mode Exit fullscreen mode

5. Lane Management (Lists)

FIFO zombie queues for game mechanics
Perfect queue management for game mechanics - zombies spawn and move in correct order with atomic operations.

// Each game lane has its own zombie queue
await redis.lpush(`game:${gameId}:lane:0:zombies`, JSON.stringify({
  type: 'basic_zombie',
  health: 100,
  speed: 1.0,
  spawnTime: Date.now()
}));

// Process zombies in spawn order (FIFO)
const nextZombie = await redis.rpop(`game:${gameId}:lane:0:zombies`);
Enter fullscreen mode Exit fullscreen mode

👥 6. Session Management (Sets)

Active games and player tracking
Efficient session management with set operations - instant membership checks and lobby management without complex queries.

// Track all active games
await redis.sadd('active_games', gameId);

// Track players in each game
await redis.sadd(`game:${gameId}:players`, playerId);

// Efficient membership checks
const isPlayerInGame = await redis.sismember(`game:${gameId}:players`, playerId);
const activeGameCount = await redis.scard('active_games');
Enter fullscreen mode Exit fullscreen mode

7. Analytics & Metrics (HyperLogLog + Counters)

Memory-efficient statistics
Memory-efficient unique counting, global statistics, and rate limiting without external analytics systems.

// HyperLogLog for unique player counting (uses only ~12KB for millions of players)
await redis.pfadd('hll:unique_players', playerId);
const uniquePlayerCount = await redis.pfcount('hll:unique_players');

// Global game statistics
await redis.incr('counter:total_games');
await redis.incrby('counter:zombies_killed', zombiesKilledThisGame);
await redis.incrby('counter:plants_planted', plantsPlantedThisGame);

// Rate limiting with expiring counters
await redis.incr(`rate_limit:${playerId}:${currentHour}`);
await redis.expire(`rate_limit:${playerId}:${currentHour}`, 3600);
Enter fullscreen mode Exit fullscreen mode

Traditional Redis Usage

// What most applications do - Redis as cache layer
const cachedUser = await redis.get(`cache:user:${userId}`);
if (!cachedUser) {
  const user = await database.query('SELECT * FROM users WHERE id = ?', userId);
  await redis.setex(`cache:user:${userId}`, 3600, JSON.stringify(user));
  return user;
}
return JSON.parse(cachedUser);
Enter fullscreen mode Exit fullscreen mode

Redis as Complete Platform

// What this project demonstrates - Redis as the entire backend
class GameEngine {
  constructor(redis) {
    this.redis = redis; // Redis IS the database, not a cache
  }

  async placePlant(gameId, playerId, plantType, row, col) {
    // 1. Primary Database: Update game state
    const gameState = await this.redis.hget(`game:${gameId}:state`, 'data');
    // ... modify game state ...
    await this.redis.hset(`game:${gameId}:state`, 'data', JSON.stringify(gameState));

    // 2. Real-time: Notify all players instantly
    await this.redis.publish(`game:${gameId}:updates`, JSON.stringify({
      type: 'plant_placed', plant: newPlant, playerId
    }));

    // 3. Event Sourcing: Record for replay/analytics
    await this.redis.xadd(`game:${gameId}:events`, '*', {
      action: 'plant_placed', playerId, plantType, row, col
    });

    // 4. Leaderboards: Update rankings
    await this.redis.zadd('leaderboard:plants_planted', ++player.plantsPlanted, playerId);

    // 5. Analytics: Update global statistics
    await this.redis.incr('counter:plants_planted');
    await this.redis.pfadd('hll:unique_players', playerId);

    // 6 different Redis capabilities in one operation!
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Technical Achievements

Architecture Innovation

  • Zero SQL databases - Redis handles all data persistence
  • No cache invalidation - No cache layer because Redis IS the primary database
  • Event-driven design - Pub/Sub eliminates all polling
  • Complete audit trails - Streams provide event sourcing out of the box ### Performance Benefits
  • Sub-millisecond operations - All data operations in memory
  • Zero cache misses - No cache layer means no cache invalidation complexity
  • Horizontal scalability - Architecture ready for Redis Cluster deployment
  • Memory efficiency - HyperLogLog and optimized data structures

roduction Game Features

  • Persistent progression - Player statistics and achievements survive server restarts
  • Live competitive rankings - 5 different leaderboard categories updating in real-time
  • Smart user experience - Auto-generated usernames, responsive design, health monitoring

** Deployment Ready**

  • Docker support - Single container and multi-container configurations
  • Production logging - Structured logging with multiple log levels
  • Health monitoring - Comprehensive health checks and metrics endpoints
  • Complete documentation - Deployment guides, API documentation, development workflows

Conclusion

This Plants vs Zombies game demonstrates Redis as a complete, powerful, multi-model platform that can:

Replace entire database stacks - No PostgreSQL, MongoDB, or MySQL needed

Provide real-time capabilities - Built-in Pub/Sub eliminates message queues

Handle complex analytics - HyperLogLog, counters, and sorted sets for insights

Scale to millions of users - Ready for Redis Cluster horizontal scaling

Deliver consistent sub-millisecond performance - All operations in memory

The game is fully functional, production-ready, and demonstrates Redis as the complete backend infrastructure for modern real-time applications.

Top comments (0)