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:

  1. Game starts correctly
  2. Snake moves in all directions
  3. Food collection works
  4. Collision detection works
  5. Scoring works correctly
  6. High scores persist
  7. Settings persist
  8. Touch controls work on mobile
  9. Keyboard controls work on desktop
  10. 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):

  1. GitHub Pages: Free, easy setup, GitHub integration
  2. Netlify: Free tier, automatic deployments, CDN
  3. Vercel: Free tier, fast deployments, edge network
  4. 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:

  1. 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';">
    
  2. 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);
    }
    
  3. 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();
    });
    
  4. 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

  1. Object Pooling: Reuse game objects to minimize GC
  2. RequestAnimationFrame: Smooth 60 FPS rendering
  3. Debouncing: Debounce resize events
  4. Lazy Loading: Load assets on demand
  5. Code Splitting: Single bundle for simplicity
  6. Minification: Terser for production builds
  7. 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.