What started as a small classroom exercise—just moving a single player around the console to eat food—quickly grew into something bigger. The more I coded, the more fun it became, and before I knew it, I had expanded the idea into a full Snake game.
This blog documents the actual development process, focusing on concrete implementation details, entity structures, and how different components interact with each other.
Key Features
Dual-Player Support: Two players can play simultaneously with different control schemes
Dynamic Difficulty System: Four difficulty levels (Easy, Medium, Hard, Hell) with different speeds
Food Expiration Mechanics: Foods have limited lifetimes and disappear if not consumed
Obstacle Generation: Configurable random obstacles to increase challenge
ConsoleApp2/ ├── PythonGame.cs # Application entry point └── PythonGame/ ├── SnakeGame.cs # Menu system and game state management ├── SnakeGameBoard.cs # Main game loop and board logic ├── Snake.cs # Snake entity with movement and collision ├── Food.cs # Food entity with expiration mechanics ├── Direction.cs # Direction enumeration ├── DirctionExtension.cs # Direction utility methods ├── Level.cs # Difficulty level enumeration ├── GameRule.cs # Game configuration structure ├── GameRules.cs # Predefined rule configurations ├── CellType.cs # Board cell type enumeration └── Ultils.cs # Utility functions
This architecture keeps the board as the central coordinator while entities handle their own internal logic and rendering.
Core Entity Design and Implementation
1. Snake Entity - The Heart of the Game
Let me start with the most complex entity - the Snake class. Here’s how I structured it:
classSnake { publicstring Name { set; get; } private LinkedList<(int x, int y)> body; public IEnumerable<(int x, int y)> Body => body; private ConsoleColor colorHead; private ConsoleColor colorBody; publicbool IsDead { get; set; } privateint width, height; private Direction dir; public Direction Dir { set { if (value != dir.Opposite()) dir = value; } } }
Key Design Decisions:
LinkedList for Body: I chose LinkedList<(int x, int y)> because:
O(1) insertion at head (AddFirst)
O(1) removal at tail (RemoveLast)
Perfect for snake movement where you add head and remove tail
Immutable Body Access: The Body property exposes IEnumerable to prevent external modification while allowing collision detection.
Direction Validation: The Dir setter prevents 180-degree turns that would cause instant death.
publicvoidMoveAndDraw(bool eat) { (int nx, int ny) = GetNextHead(); if (!eat) { // Remove tail visually and from data structure (int tx, int ty) = body.Last.Value; Console.SetCursorPosition(tx, ty); Console.Write(" "); body.RemoveLast(); // Redraw new tail (int ntx, int nty) = body.Last.Value; Console.SetCursorPosition(ntx, nty); PrintTail(); } // Convert old head to body segment (int hx, int hy) = body.First.Value; Console.SetCursorPosition(hx, hy); PrintBody(); // Draw new head Console.SetCursorPosition(nx, ny); PrintHead(); body.AddFirst((nx, ny)); }
This method handles both movement logic and rendering in one atomic operation, ensuring the visual state always matches the data state.
privatevoidNewGame() { GameRule rule = GameRules.Rules[(player, level)]; // Apply user customizations if (obstacle == false) rule.ObstacleRange = (0, 0); rule.FoodCount = foodOnField; rule.Player = player; if (!foodTimer) rule.FoodLifetimeRange = ((int)1e8, (int)1e8 + 1); game = new SnakeGameBoard(rule, "Player1", "Player2"); game.Run(); }
Key Technical Implementation Details
Console Rendering Optimization
Problem: Console rendering is slow and causes flickering.
My Solution: Strategic cursor positioning and selective updates:
1 2 3 4 5 6
// Only clear specific positions, never full screen during gameplay Console.SetCursorPosition(tx, ty); Console.Write(" "); // Clear only the tail position
Console.SetCursorPosition(nx, ny); PrintHead(); // Draw only the new head
Dual-Player Input Handling
Problem: Need to handle simultaneous inputs from both players.
My Solution: Frame-based input collection:
1 2 3 4 5 6
// Collect ALL available inputs in current frame while (Console.KeyAvailable) { var key = Console.ReadKey(true).Key; // Process immediately to avoid input lag }
Boundary Wrapping Implementation
Problem: Handle snake movement across screen edges seamlessly.
My Solution: Modulo arithmetic with proper negative handling:
1 2 3 4 5 6
public (int x, int y) GetNextHead() { (int nx, int ny) = dir.ToOffset(); return ((body.First.Value.x + nx + width) % width, (body.First.Value.y + ny + height) % height); }
The key is adding width and height before modulo to handle negative results correctly.
Food System Implementation
The food system manages lifecycle, expiration, and regeneration:
privateboolcheckDeath(CellType current) { return current == CellType.Snake1 || current == CellType.Snake2 || current == CellType.Obstacle; }
Head-on Collision Detection:
1 2 3 4 5 6 7 8 9 10 11
if (snake2 != null) { (int nx2, int ny2) = snake2.GetNextHead(); // Check if both snakes move to same position if (nx2 == nx && ny2 == ny) { snake1.IsDead = true; snake2.IsDead = true; } }
Collision Types:
Self-Collision: Snake hits its own body (CellType.Snake1/Snake2)
Cross-Collision: Snake hits other snake’s body
Obstacle Collision: Snake hits static obstacle
Head-on Collision: Both snakes move to same position simultaneously
Win/Loss Determination Logic
The game determines winners through multiple scenarios:
This ensures all three layers stay synchronized throughout the game.
Conclusion
Building this Snake game taught me valuable lessons about:
Entity-Component interaction: How to keep entities focused while enabling coordination
Console game optimization: Minimizing rendering calls for smooth gameplay
Configuration-driven design: Making systems flexible through external configuration
Data structure selection: Choosing the right structure (LinkedList) for the job
State synchronization: Keeping visual state and data state consistent
Real-time systems: Frame-based processing for responsive multiplayer gameplay
Collision detection: Using unified state arrays for efficient collision checking
The key insight was treating the console as a graphics buffer and the vis array as the authoritative game state, with entities responsible for their own behavior but coordinated through the central board.
Core Components Deep Dive
1. Menu System (SnakeGame.cs)
The menu system demonstrates sophisticated state management and user interface design:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
publicclassSnakeGame { int player; int currentMenu; Level level; bool obstacle; bool foodTimer; int foodOnField; privatestaticreadonlystring[] menu = newstring[]{ "Start New Game", " Players: ", " Difficulty: ", " Obstacles: ", " Food Timer: ", " Food on Field: ", "View History / Rankings" }; }
Key Implementation Details:
State Management: The menu maintains current selection (currentMenu) and game configuration state
Circular Navigation: Uses modulo arithmetic for wrap-around menu navigation
Dynamic Options: Settings change in real-time with visual feedback
Input Handling: Comprehensive keyboard input processing with arrow keys and confirmation