Last Updated: 2/13/2026
jrn: feature-detail-mlk601sfa8ei attention:
- op: file path: src/main.ts
- op: file path: index.html
Snake Game - Major Features
A modern, browser-based implementation of the classic Snake game built with TypeScript and Vite. This project showcases a progressive difficulty system with dynamic grid expansion, adjustable starting grid size, smooth gameplay mechanics, and persistent high score tracking.
Table of Contents
- Adjustable Starting Grid Size
- Dynamic Grid Expansion System
- Fixed Canvas with Dynamic Cell Sizing
- Progressive Difficulty Levels
- Responsive Game Controls
- Pause and Resume Functionality
- Persistent High Score Tracking
- Real-time Score and Level Display
- Collision Detection System
- Smart Food Spawning
- Modern UI/UX Design
- TypeScript Type Safety
Adjustable Starting Grid Size
A unique feature that allows players to customize the difficulty before starting the game by adjusting the initial grid size.
How It Works
Before the game starts, players can use the + and - keys to adjust the grid resolution:
if ((e.key === '+' || e.key === '=' || e.key === '-' || e.key === '_') &&
!this.gameStarted && !this.isGameOver &&
!document.getElementById('game')!.classList.contains('hidden')) {
if (e.key === '+' || e.key === '=') {
this.initialGridSize = Math.min(this.initialGridSize + 5, 50)
} else {
this.initialGridSize = Math.max(this.initialGridSize - 5, 5)
}
this.gridSize = this.initialGridSize
this.updateCanvasSize()
const center = Math.floor(this.gridSize / 2)
this.snake = [{ x: center, y: center }]
this.spawnFood()
this.updateUI()
this.draw()
}Grid Size Range
- Minimum: 5x5 grid (25 cells)
- Maximum: 50x50 grid (2,500 cells)
- Step Size: Adjusts in increments of 5
- Default: 10x10 grid (100 cells)
Benefits
- Customizable difficulty: Beginners can start with larger grids, experts with smaller ones
- Instant visual feedback: The grid updates immediately as you adjust the size
- Preserved between adjustments: Snake repositions to center, food respawns
- Pre-game only: Can only be adjusted before the game starts, preventing mid-game changes
This feature gives players control over their initial challenge level while maintaining game balance.
Dynamic Grid Expansion System
One of the most innovative features of this Snake game is its dynamic grid expansion system. Unlike traditional Snake games that maintain a fixed grid size, this implementation automatically expands the playing field as the snake grows.
How It Works
The game monitors the snake’s length relative to the total grid size. When the snake occupies 25% or more of the available cells, the grid automatically doubles in size:
private checkGridExpansion() {
const totalCells = this.gridSize * this.gridSize
const snakeLength = this.snake.length
const fillPercentage = snakeLength / totalCells
if (fillPercentage >= 0.25) {
this.expandGrid()
}
}Benefits
- Extended gameplay: Players can continue playing indefinitely without running out of space
- Progressive challenge: Larger grids require better spatial awareness and planning
- Automatic canvas resizing: The game canvas maintains a fixed size while cell sizes adjust
- Seamless transitions: Grid expansion happens instantly without interrupting gameplay
The game starts with a configurable grid (default 10×10, adjustable 5×5 to 50×50) and can expand to 20×20, 40×40, 80×80, and beyond, providing virtually unlimited growth potential.
Fixed Canvas with Dynamic Cell Sizing
The game uses a fixed 300x300 pixel canvas with dynamically calculated cell sizes, ensuring consistent visual presentation across all grid sizes.
Implementation
const CANVAS_SIZE = 300
private updateCanvasSize() {
this.canvas.width = CANVAS_SIZE
this.canvas.height = CANVAS_SIZE
this.cellSize = CANVAS_SIZE / this.gridSize
}Cell Size Calculation
The cell size is automatically derived from the canvas size divided by the grid size:
- 5×5 grid: 60px cells (300 ÷ 5)
- 10×10 grid: 30px cells (300 ÷ 10)
- 20×20 grid: 15px cells (300 ÷ 20)
- 40×40 grid: 7.5px cells (300 ÷ 40)
- 50×50 grid: 6px cells (300 ÷ 50)
Benefits
- Consistent viewport: The game always occupies the same screen space
- Scalable rendering: Works seamlessly from 5x5 to very large grids
- Responsive design: Easier to integrate into different layouts
- Performance: Fixed canvas size reduces rendering overhead
This approach ensures that whether you start with a 5x5 or 50x50 grid, the game always looks clean and professional.
Progressive Difficulty Levels
The difficulty system is tightly integrated with the grid expansion feature, creating a natural progression that challenges players as they improve.
Speed Acceleration
Each time the grid expands, the game speed doubles, making the snake move faster and requiring quicker reflexes:
private expandGrid() {
this.gridSize *= 2
this.level++
this.gameSpeed = this.gameSpeed / 2 // Halves the interval, doubling the speed
this.updateCanvasSize()
if (this.gameLoop) clearInterval(this.gameLoop)
this.gameLoop = window.setInterval(() => this.update(), this.gameSpeed)
}Level Progression
- Level 1: Configurable starting grid (default 10×10), base speed (200ms per move)
- Level 2: Grid doubles (20×20), 2× speed (100ms per move)
- Level 3: 40×40 grid, 4× speed (50ms per move)
- Level 4+: Continues doubling grid size and speed
This exponential difficulty curve ensures that the game remains challenging for both casual and experienced players.
Responsive Game Controls
The game features a flexible control system that supports multiple input methods, making it accessible to different player preferences.
Supported Control Schemes
- Arrow Keys: Traditional directional controls (↑ ↓ ← →)
- WASD Keys: Popular gaming alternative (W/A/S/D)
- Case-insensitive: Both uppercase and lowercase WASD keys work
- Grid Adjustment: +/- keys to change starting grid size
- Reset: Shift+R to return to menu
Key Mapping Implementation
const keyMap: Record<string, Direction> = {
'ArrowUp': 'UP',
'ArrowDown': 'DOWN',
'ArrowLeft': 'LEFT',
'ArrowRight': 'RIGHT',
'w': 'UP',
'W': 'UP',
'a': 'LEFT',
'A': 'LEFT',
's': 'DOWN',
'S': 'DOWN',
'd': 'RIGHT',
'D': 'RIGHT'
}Direction Validation
The game prevents invalid moves that would cause the snake to reverse into itself:
private isValidDirection(newDirection: Direction): boolean {
const opposites: Record<Direction, Direction> = {
'UP': 'DOWN',
'DOWN': 'UP',
'LEFT': 'RIGHT',
'RIGHT': 'LEFT'
}
return opposites[this.direction] !== newDirection
}This ensures smooth gameplay by ignoring input that would result in immediate collision.
Press-to-Start Mechanic
The game implements a “press to start” feature that waits for player input before beginning:
if (newDirection) {
if (this.isValidDirection(newDirection)) {
this.nextDirection = newDirection
// Start the game on first directional input
if (!this.gameStarted) {
this.gameStarted = true
this.gameLoop = window.setInterval(() => this.update(), this.gameSpeed)
}
}
}Benefits:
- Players can prepare themselves before the snake starts moving
- Prevents accidental early deaths from not being ready
- The game draws the initial state immediately, showing the snake at starting position
- Game loop only begins after the first valid directional input
This design choice gives players full control over when gameplay actually begins, improving the overall user experience.
Pause and Resume Functionality
Players can pause the game at any time using the P key, allowing for breaks without losing progress.
Pause Mechanics
- Overlay display: A semi-transparent pause screen appears over the game
- Input blocking: All directional inputs are ignored while paused
- Game state preservation: The snake position, score, and level are maintained
- Visual feedback: Clear “Paused” message with resume instructions
private togglePause() {
this.isPaused = !this.isPaused
const pauseScreen = document.getElementById('pause')!
if (this.isPaused) {
pauseScreen.classList.remove('hidden')
} else {
pauseScreen.classList.add('hidden')
}
}The pause feature is particularly useful during higher levels when the game speed increases significantly.
Persistent High Score Tracking
The game automatically tracks and saves the highest score achieved across all play sessions using browser localStorage.
Features
- Automatic saving: High scores are saved immediately when a game ends
- Persistent storage: Scores survive browser restarts and page refreshes
- Multiple displays: High score is shown on the main menu and game over screen
- Score comparison: The game automatically compares the current score with the saved high score
Implementation
private getHighScore(): number {
const stored = localStorage.getItem('snake-high-score')
return stored ? parseInt(stored, 10) : 0
}
private saveHighScore(score: number) {
localStorage.setItem('snake-high-score', score.toString())
this.loadHighScore()
}This creates a sense of progression and encourages players to beat their previous best performance.
Real-time Score and Level Display
The game provides continuous feedback about the player’s progress through a comprehensive information display.
Displayed Information
- Current Score: Increments with each food item consumed
- Current Level: Shows the current difficulty level (tied to grid size)
- Grid Size: Displays the current playing field dimensions (e.g., “10×10”)
Dynamic Updates
The UI updates in real-time after every game tick:
private updateUI() {
document.getElementById('score')!.textContent = this.score.toString()
document.getElementById('level')!.textContent = this.level.toString()
document.getElementById('grid-size')!.textContent = `${this.gridSize}x${this.gridSize}`
}This information helps players understand their progress and anticipate when the next grid expansion will occur.
Collision Detection System
The game implements a robust collision detection system that checks for two types of collisions on every frame.
Wall Collision
The snake dies if it moves outside the grid boundaries:
if (head.x < 0 || head.x >= this.gridSize || head.y < 0 || head.y >= this.gridSize) {
return true
}Self-Collision
The snake dies if its head touches any part of its body:
return this.snake.some(segment => segment.x === head.x && segment.y === head.y)Collision Handling
When a collision is detected, the game immediately:
- Stops the game loop
- Checks and updates the high score if necessary
- Displays the game over screen with final statistics
This system ensures fair gameplay and clear win/loss conditions.
Smart Food Spawning
The food spawning algorithm ensures that food always appears in a valid, reachable location on the grid.
Spawn Logic
The game uses a do-while loop to generate random positions until it finds one that doesn’t overlap with the snake:
private spawnFood() {
let newFood: Position
do {
newFood = {
x: Math.floor(Math.random() * this.gridSize),
y: Math.floor(Math.random() * this.gridSize)
}
} while (this.snake.some(segment => segment.x === newFood.x && segment.y === newFood.y))
this.food = newFood
}Key Features
- Guaranteed valid placement: Food never spawns inside the snake
- Uniform distribution: All empty cells have equal probability
- Immediate respawn: New food appears instantly after consumption
- Scales with grid size: Works efficiently even on large grids
This ensures that the game always remains playable and fair.
Modern UI/UX Design
The game features a polished, modern interface with attention to visual appeal and user experience.
Design Highlights
Color Scheme:
- Dark theme with
#1a1a1abackground - Two-tone snake design: bright green (
#4ade80) for the head, darker green (#22c55e) for the body segments - Red (
#ef4444) for food - Golden yellow (
#fbbf24) for high scores
Visual Effects:
- Grid lines with subtle
#333color - Glowing shadow around the canvas with
rgba(74, 222, 128, 0.2)green tint - Smooth hover effects on buttons with scale transformation
- Semi-transparent pause overlay with
rgba(0, 0, 0, 0.85)background
Responsive Design:
@media (max-width: 768px) {
h1 {
font-size: 2em;
}
.game-info {
flex-direction: column;
gap: 0.5rem;
}
}Snake Rendering
The game implements a visual distinction between the snake’s head and body for better gameplay clarity:
this.snake.forEach((segment, index) => {
const brightness = index === 0 ? '#4ade80' : '#22c55e'
this.ctx.fillStyle = brightness
this.ctx.fillRect(
segment.x * this.cellSize + 1,
segment.y * this.cellSize + 1,
this.cellSize - 2,
this.cellSize - 2
)
})This two-tone design helps players quickly identify the snake’s head, which is crucial for avoiding collisions at higher speeds.
Screen Management
The game includes four distinct screens:
- Main Menu: Welcome screen with high score display
- Game Screen: Active gameplay with information panel
- Pause Screen: Overlay that appears during pause
- Game Over Screen: Final score and replay option
Each screen is cleanly separated and transitions smoothly between states.
TypeScript Type Safety
The entire game is built with TypeScript, providing strong type safety and better code maintainability.
Type Definitions
type Position = { x: number; y: number }
type Direction = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'Benefits
- Compile-time error checking: Catches bugs before runtime
- Better IDE support: Autocomplete and inline documentation
- Refactoring safety: Changes propagate correctly throughout the codebase
- Self-documenting code: Types serve as inline documentation
Class-Based Architecture
The game uses a single SnakeGame class that encapsulates all game logic, making the code organized and easy to understand:
- Private methods for internal logic
- Clear separation of concerns (rendering, input, game state)
- Consistent naming conventions
- Well-defined interfaces between components
This architecture makes the codebase maintainable and extensible for future enhancements.
Technical Stack
The project is built with modern web technologies:
- TypeScript 5.9.3: For type-safe JavaScript
- Vite 7.1.7: Fast build tool and development server
- HTML5 Canvas: For rendering the game graphics
- LocalStorage API: For persistent data storage
- CSS3: For modern styling and responsive design
This stack provides excellent developer experience, fast build times, and optimal runtime performance.