Tech Stack Decisions - Nokia Snake Game
Technology choices
Tech Stack Decisions - Nokia Snake Game
Overview
Decision Date: 2026-03-18
Project: Nokia Snake Game
Architecture: Single-page web application, fully client-side
Deployment: Static hosting, no server required
Frontend Framework Decision
Decision: Vanilla JavaScript (No Framework)
Status: ✅ Approved
Rationale:
- Simplicity: Game logic is straightforward, no need for framework complexity
- Performance: Zero framework overhead, minimal bundle size
- Learning Curve: No framework-specific knowledge required
- Control: Full control over rendering and game loop
- Bundle Size: Minimal JavaScript footprint (< 500KB target)
Alternatives Considered:
- React: Rejected - Overkill for simple game, adds unnecessary complexity
- Vue: Rejected - Not needed for game development
- Svelte: Rejected - Compilation step adds complexity without benefit
Trade-offs:
- ✅ Pros: Minimal bundle size, maximum performance, full control
- ❌ Cons: No component reusability framework, manual DOM management
Build Tool Decision
Decision: Vite
Status: ✅ Approved
Rationale:
- Fast Development: Lightning-fast HMR and dev server
- Modern: Built for ES modules, modern JavaScript
- Simple Configuration: Minimal config needed
- Build Performance: Fast production builds
- Plugin Ecosystem: Rich plugin ecosystem if needed
Alternatives Considered:
- Webpack: Rejected - More complex configuration, slower builds
- Parcel: Rejected - Less control, smaller ecosystem
- Rollup: Rejected - More manual configuration needed
- No Build Tool: Rejected - Need bundling, minification, and optimization
Configuration:
// vite.config.js
export default {
build: {
target: 'es2020',
minify: 'terser',
sourcemap: false,
rollupOptions: {
output: {
manualChunks: undefined // Single bundle for simplicity
}
}
},
server: {
port: 3000,
open: true
}
}
Trade-offs:
- ✅ Pros: Fast, modern, simple, great DX
- ❌ Cons: Requires Node.js for development
Rendering Technology Decision
Decision: WebGL 2D Context with Canvas 2D Fallback
Status: ✅ Approved
Rationale:
- Performance: WebGL provides hardware acceleration
- Compatibility: Canvas 2D fallback for older browsers
- Simplicity: 2D context sufficient for snake game
- Mobile Support: Good mobile device support
- Future-Proof: WebGL 2.0 widely supported
Alternatives Considered:
- Canvas 2D Only: Rejected - Less performant, no hardware acceleration
- SVG: Rejected - Not suitable for game rendering
- DOM Manipulation: Rejected - Too slow for 30+ FPS
- WebGL 3D: Rejected - Overkill for 2D game
Implementation Approach:
// Feature detection and fallback
function initializeRenderer(canvas) {
// Try WebGL first
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
if (gl) {
return new WebGLRenderer(gl);
}
// Fallback to Canvas 2D
const ctx = canvas.getContext('2d');
return new Canvas2DRenderer(ctx);
}
Trade-offs:
- ✅ Pros: Hardware accelerated, performant, widely supported
- ❌ Cons: More complex than Canvas 2D, requires fallback logic
State Management Decision
Decision: Custom Redux-like Store
Status: ✅ Approved
Rationale:
- Simplicity: Lightweight custom implementation
- Predictability: Unidirectional data flow
- Debugging: Easy to track state changes
- No Dependencies: No external library needed
- Game-Specific: Tailored to game requirements
Alternatives Considered:
- Redux: Rejected - Too heavy for simple game
- MobX: Rejected - Reactive programming not needed
- Zustand: Rejected - External dependency not needed
- Global Object: Rejected - No structure, hard to debug
Implementation:
class GameStore {
constructor(initialState) {
this.state = initialState;
this.subscribers = [];
}
getState() {
return this.state;
}
dispatch(action) {
this.state = reducer(this.state, action);
this.subscribers.forEach(callback => callback(this.state));
}
subscribe(callback) {
this.subscribers.push(callback);
return () => {
this.subscribers = this.subscribers.filter(cb => cb !== callback);
};
}
}
Trade-offs:
- ✅ Pros: Lightweight, tailored, no dependencies
- ❌ Cons: Custom code to maintain, no ecosystem
Data Persistence Decision
Decision: localStorage API
Status: ✅ Approved
Rationale:
- Simplicity: Built-in browser API, no setup needed
- Offline: Works completely offline
- Sufficient Capacity: 5-10MB sufficient for game data
- Synchronous: Simple synchronous API
- Wide Support: Supported in all modern browsers
Alternatives Considered:
- IndexedDB: Rejected - Overkill for simple key-value storage
- Cookies: Rejected - Too small (4KB limit)
- sessionStorage: Rejected - Doesn't persist across sessions
- Server Storage: Rejected - Not needed per requirements
Data Structure:
// Storage keys (namespaced)
const STORAGE_KEYS = {
GAME_STATE: 'nokiaSnake_gameState',
HIGH_SCORES: 'nokiaSnake_highScores',
SETTINGS: 'nokiaSnake_settings',
VERSION: 'nokiaSnake_version'
};
// Data format
{
version: '1.0.0',
gameState: { /* snake, food, score, etc. */ },
highScores: [ /* top 10 scores */ ],
settings: { /* controls, volume, etc. */ }
}
Trade-offs:
- ✅ Pros: Simple, synchronous, widely supported, offline
- ❌ Cons: Limited capacity (5-10MB), synchronous blocks main thread
Input Handling Decision
Decision: Native Event Listeners with Custom Input Manager
Status: ✅ Approved
Rationale:
- Simplicity: Native browser APIs
- Performance: Direct event handling, no overhead
- Flexibility: Easy to add touch, keyboard, gamepad support
- Control: Full control over input processing
- Validation: Custom validation logic
Alternatives Considered:
- Input Library: Rejected - No suitable library found
- Framework Input: Rejected - Not using framework
- Polling: Rejected - Less efficient than events
Implementation:
class InputManager {
constructor() {
this.keyState = {};
this.touchState = {};
this.setupListeners();
}
setupListeners() {
// Keyboard
window.addEventListener('keydown', this.handleKeyDown.bind(this));
window.addEventListener('keyup', this.handleKeyUp.bind(this));
// Touch
window.addEventListener('touchstart', this.handleTouchStart.bind(this));
window.addEventListener('touchmove', this.handleTouchMove.bind(this));
window.addEventListener('touchend', this.handleTouchEnd.bind(this));
}
handleKeyDown(event) {
if (this.validateInput(event)) {
this.keyState[event.key] = true;
this.dispatchGameAction(event.key);
}
}
validateInput(event) {
// SECURITY-05: Input validation
const allowedKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' ', 'Escape'];
return allowedKeys.includes(event.key);
}
}
Trade-offs:
- ✅ Pros: Native, performant, flexible, full control
- ❌ Cons: Manual event management, no abstraction layer
Testing Strategy Decision
Decision: Manual Testing Only
Status: ✅ Approved
Rationale:
- User Requirement: Manual testing only per NFR requirements
- Simplicity: No test framework setup needed
- One-Time Development: No ongoing maintenance, tests not needed
- Developer Testing: Developer tests before release
Alternatives Considered:
- Jest: Rejected - Not needed per user requirements
- Vitest: Rejected - Not needed per user requirements
- Playwright: Rejected - Not needed per user requirements
Testing Approach:
- Manual Test Checklist: Document test scenarios
- Browser Testing: Test on all supported browsers
- Device Testing: Test on desktop and mobile devices
- Performance Testing: Manual performance profiling
Test Checklist:
- Game starts correctly
- Snake moves in all directions
- Food collection works
- Collision detection works
- Scoring works correctly
- High scores persist
- Settings persist
- Touch controls work on mobile
- Keyboard controls work on desktop
- Game performs at 30+ FPS
Trade-offs:
- ✅ Pros: No test framework overhead, simple, fast development
- ❌ Cons: No automated regression testing, manual effort required
Code Quality Tools Decision
Decision: ESLint + Prettier
Status: ✅ Approved
Rationale:
- Code Consistency: Automated formatting
- Error Prevention: Catch common mistakes
- Best Practices: Enforce JavaScript best practices
- IDE Integration: Works with all major IDEs
Configuration:
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2020: true
},
extends: ['eslint:recommended'],
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module'
},
rules: {
'no-console': 'off', // Allow console for logging
'no-unused-vars': 'warn'
}
};
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5"
}
Trade-offs:
- ✅ Pros: Consistent code, catch errors, IDE integration
- ❌ Cons: Requires Node.js, adds dev dependencies
Deployment Strategy Decision
Decision: Static Hosting with Automated Deployment
Status: ✅ Approved
Rationale:
- Simplicity: Static files only, no server needed
- Cost: Free or very low cost hosting
- Performance: CDN distribution, fast loading
- Automation: Automated deployment script
Hosting Options (Choose one):
- GitHub Pages: Free, easy setup, GitHub integration
- Netlify: Free tier, automatic deployments, CDN
- Vercel: Free tier, fast deployments, edge network
- AWS S3 + CloudFront: Scalable, enterprise-grade
Deployment Script:
#!/bin/bash
# deploy.sh
# Build production bundle
npm run build
# Deploy to hosting (example: GitHub Pages)
# Customize based on chosen hosting platform
npm run deploy
Trade-offs:
- ✅ Pros: Simple, cheap, fast, scalable
- ❌ Cons: No server-side logic (not needed)
Security Implementation Decision
Decision: Enterprise-Grade Security with Audit Logging
Status: ✅ Approved
Rationale:
- User Requirement: Maximum security per NFR requirements
- Compliance: Full security baseline compliance
- Audit Trail: Comprehensive logging for security events
- Best Practices: Industry-standard security measures
Security Measures:
Content Security Policy (CSP):
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'none';">Input Validation (SECURITY-05):
function validateInput(input) { // Type checking if (typeof input !== 'object') return false; // Property validation if (!input.hasOwnProperty('key')) return false; // Whitelist validation const allowedKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' ', 'Escape']; return allowedKeys.includes(input.key); }Exception Handling (SECURITY-15):
window.addEventListener('error', (event) => { // Log error securely securityLogger.logError({ type: 'UNHANDLED_ERROR', message: event.error.message, timestamp: new Date().toISOString(), severity: 'HIGH' }); // Prevent default error handling event.preventDefault(); // Recover gracefully recoverFromError(); });Audit Logging:
class SecurityLogger { log(event) { const logEntry = { timestamp: new Date().toISOString(), type: event.type, severity: event.severity, details: event.details, userAgent: navigator.userAgent }; // Log to console (production: send to logging service) console.log('[SECURITY]', JSON.stringify(logEntry)); // Store in localStorage for audit trail this.storeAuditLog(logEntry); } storeAuditLog(entry) { const logs = JSON.parse(localStorage.getItem('nokiaSnake_auditLog') || '[]'); logs.push(entry); // Keep last 1000 entries if (logs.length > 1000) { logs.shift(); } localStorage.setItem('nokiaSnake_auditLog', JSON.stringify(logs)); } }
Trade-offs:
- ✅ Pros: Comprehensive security, audit trail, compliance
- ❌ Cons: Additional code complexity, performance overhead
Mobile Support Implementation Decision
Decision: Responsive Design + Touch Controls
Status: ✅ Approved
Rationale:
- User Requirement: Mobile optimized per NFR requirements
- Touch Controls: Swipe gestures for snake direction
- Responsive: Adapts to all screen sizes
- Performance: Optimized for mobile devices
Implementation:
// Touch gesture detection
class TouchController {
constructor() {
this.touchStartX = 0;
this.touchStartY = 0;
this.minSwipeDistance = 30; // pixels
}
handleTouchStart(event) {
this.touchStartX = event.touches[0].clientX;
this.touchStartY = event.touches[0].clientY;
}
handleTouchEnd(event) {
const touchEndX = event.changedTouches[0].clientX;
const touchEndY = event.changedTouches[0].clientY;
const deltaX = touchEndX - this.touchStartX;
const deltaY = touchEndY - this.touchStartY;
// Determine swipe direction
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal swipe
if (Math.abs(deltaX) > this.minSwipeDistance) {
return deltaX > 0 ? 'RIGHT' : 'LEFT';
}
} else {
// Vertical swipe
if (Math.abs(deltaY) > this.minSwipeDistance) {
return deltaY > 0 ? 'DOWN' : 'UP';
}
}
}
}
// Responsive canvas sizing
function resizeCanvas() {
const canvas = document.getElementById('gameCanvas');
const container = canvas.parentElement;
// Maintain aspect ratio
const aspectRatio = 1; // Square canvas
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
if (containerWidth / containerHeight > aspectRatio) {
canvas.height = containerHeight;
canvas.width = containerHeight * aspectRatio;
} else {
canvas.width = containerWidth;
canvas.height = containerWidth / aspectRatio;
}
}
Trade-offs:
- ✅ Pros: Works on all devices, intuitive touch controls
- ❌ Cons: Additional code for touch handling
Technology Stack Summary
| Category | Technology | Rationale |
|---|---|---|
| Language | JavaScript (ES2020) | Modern features, wide support |
| Framework | Vanilla JS (No framework) | Simplicity, performance, control |
| Build Tool | Vite | Fast, modern, simple |
| Rendering | WebGL 2D + Canvas 2D fallback | Performance, compatibility |
| State Management | Custom Redux-like store | Lightweight, tailored |
| Data Persistence | localStorage API | Simple, offline, sufficient |
| Input Handling | Native event listeners | Performance, flexibility |
| Testing | Manual testing only | Per user requirements |
| Code Quality | ESLint + Prettier | Consistency, error prevention |
| Deployment | Static hosting | Simple, cheap, fast |
| Security | CSP + Input validation + Audit logging | Enterprise-grade security |
| Mobile | Responsive + Touch controls | Mobile optimized |
Dependencies
Production Dependencies
{
"dependencies": {}
}
Note: Zero production dependencies - all vanilla JavaScript
Development Dependencies
{
"devDependencies": {
"vite": "^5.0.0",
"eslint": "^8.0.0",
"prettier": "^3.0.0"
}
}
Browser API Usage
| API | Purpose | Fallback |
|---|---|---|
| WebGL 2.0 | Hardware-accelerated rendering | Canvas 2D |
| localStorage | Data persistence | None (required) |
| requestAnimationFrame | Game loop timing | setTimeout |
| Touch Events | Mobile input | Mouse events |
| Vibration API | Haptic feedback | Silent fail |
| Screen Wake Lock | Prevent sleep | None (optional) |
Performance Optimization Strategies
- Object Pooling: Reuse game objects to minimize GC
- RequestAnimationFrame: Smooth 60 FPS rendering
- Debouncing: Debounce resize events
- Lazy Loading: Load assets on demand
- Code Splitting: Single bundle for simplicity
- Minification: Terser for production builds
- Compression: Gzip/Brotli compression on server
Future Considerations
Potential Enhancements (Not in Scope)
- TypeScript: Type safety for larger codebase
- Web Workers: Offload game logic to worker thread
- Service Worker: PWA capabilities, offline caching
- WebAssembly: Performance-critical code in WASM
- Multiplayer: WebRTC or WebSocket for multiplayer
- Cloud Sync: Firebase or similar for cloud storage
Note: These are not in scope for current one-time development but could be considered for future versions.
Decision Log
| Date | Decision | Rationale | Status |
|---|---|---|---|
| 2026-03-18 | Vanilla JavaScript | Simplicity, performance | ✅ Approved |
| 2026-03-18 | Vite build tool | Fast, modern, simple | ✅ Approved |
| 2026-03-18 | WebGL + Canvas 2D | Performance, compatibility | ✅ Approved |
| 2026-03-18 | Custom state management | Lightweight, tailored | ✅ Approved |
| 2026-03-18 | localStorage | Simple, offline, sufficient | ✅ Approved |
| 2026-03-18 | Manual testing only | Per user requirements | ✅ Approved |
| 2026-03-18 | Static hosting | Simple, cheap, fast | ✅ Approved |
| 2026-03-18 | Enterprise security | Per user requirements | ✅ Approved |
Conclusion
The technology stack for the Nokia Snake game is designed for:
- Simplicity: Minimal dependencies, vanilla JavaScript
- Performance: 30+ FPS on low-end devices
- Security: Enterprise-grade security with audit logging
- Mobile: Optimized for mobile with touch controls
- Offline: Full offline functionality
- Maintainability: Clean code, standard tools
All technology decisions align with the NFR requirements and support the functional design.