Logical Components - Nokia Snake Game

Logical component structure

Logical Components - Nokia Snake Game

Overview

Unit Name: Nokia Snake Game (Monolithic)
Purpose: Define logical component architecture for implementing NFR requirements
Communication: Centralized state management (all components communicate through GameStore)

Component Architecture

┌─────────────────────────────────────────────────────────────┐
│                        Application                           │
│  ┌───────────────────────────────────────────────────────┐  │
│  │                     GameStore                          │  │
│  │              (Centralized State)                       │  │
│  └───────────────────────────────────────────────────────┘  │
│         ▲         ▲         ▲         ▲         ▲           │
│         │         │         │         │         │           │
│    ┌────┴───┐ ┌──┴───┐ ┌───┴────┐ ┌──┴────┐ ┌─┴──────┐    │
│    │ Game   │ │Render│ │ Input  │ │Storage│ │Security│    │
│    │ Engine │ │Engine│ │Manager │ │Manager│ │ Logger │    │
│    └────────┘ └──────┘ └────────┘ └───────┘ └────────┘    │
│         │                    │                              │
│    ┌────┴────┐          ┌───┴────┐                         │
│    │  Perf   │          │ Touch  │                         │
│    │ Monitor │          │Control │                         │
│    └─────────┘          └────────┘                         │
└─────────────────────────────────────────────────────────────┘

Core Components

Component 1: GameStore (Centralized State Manager)

Purpose: Central state management with unidirectional data flow
Pattern: Redux-like store
Communication: All components communicate through this store

Responsibilities:

  • Maintain single source of truth for game state
  • Dispatch actions to update state
  • Notify subscribers of state changes
  • Provide state access to all components

Interface:

class GameStore {
  // State management
  getState(): GameState
  dispatch(action: Action): void
  subscribe(callback: Function): UnsubscribeFunction
  
  // State queries
  getSnake(): Snake
  getFood(): Food
  getScore(): number
  getStatus(): GameStatus
}

Implementation:

class GameStore {
  constructor(initialState) {
    this.state = initialState;
    this.subscribers = [];
    this.actionHistory = []; // For debugging
  }
  
  getState() {
    // Return immutable copy
    return Object.freeze({ ...this.state });
  }
  
  dispatch(action) {
    // Log action for audit
    this.actionHistory.push({
      action,
      timestamp: Date.now(),
      previousState: { ...this.state }
    });
    
    // Update state through reducer
    const newState = this.reducer(this.state, action);
    
    // Only update if state changed
    if (newState !== this.state) {
      this.state = newState;
      this.notifySubscribers();
    }
  }
  
  reducer(state, action) {
    switch (action.type) {
      case 'MOVE_SNAKE':
        return { ...state, snake: this.moveSnake(state.snake, action.direction) };
      case 'COLLECT_FOOD':
        return this.handleFoodCollection(state);
      case 'GAME_OVER':
        return { ...state, status: 'GAME_OVER' };
      // ... other actions
      default:
        return state;
    }
  }
  
  subscribe(callback) {
    this.subscribers.push(callback);
    return () => {
      this.subscribers = this.subscribers.filter(cb => cb !== callback);
    };
  }
  
  notifySubscribers() {
    this.subscribers.forEach(callback => callback(this.state));
  }
}

Dependencies: None (central hub)
Used By: All other components

Component 2: GameEngine

Purpose: Core game logic and mechanics
Pattern: Fixed time step with frame skipping
Communication: Reads from and writes to GameStore

Responsibilities:

  • Manage game loop timing
  • Update game logic with fixed time step
  • Handle collision detection
  • Apply game rules
  • Trigger state updates

Interface:

class GameEngine {
  start(): void
  stop(): void
  pause(): void
  resume(): void
  update(deltaTime: number): void
}

Implementation: See nfr-design-patterns.md Pattern 1

Dependencies: GameStore, ObjectPool
Used By: Application (main entry point)

Component 3: RenderEngine

Purpose: Visual rendering of game state
Pattern: WebGL with Canvas 2D fallback
Communication: Reads from GameStore

Responsibilities:

  • Initialize rendering context (WebGL or Canvas 2D)
  • Render game state to canvas
  • Handle canvas resizing
  • Optimize rendering performance

Interface:

class RenderEngine {
  initialize(canvas: HTMLCanvasElement): void
  render(gameState: GameState): void
  resize(width: number, height: number): void
  clear(): void
}

Implementation: See nfr-design-patterns.md Pattern 3

Dependencies: GameStore
Used By: GameEngine (for rendering)

Component 4: InputManager

Purpose: Handle keyboard and touch input
Pattern: Whitelist validation with event delegation
Communication: Writes to GameStore via actions

Responsibilities:

  • Capture keyboard and touch events
  • Validate all input (SECURITY-05)
  • Convert input to game actions
  • Dispatch actions to GameStore

Interface:

class InputManager {
  initialize(): void
  destroy(): void
  validateInput(event: Event): boolean
  handleKeyboard(event: KeyboardEvent): void
  handleTouch(event: TouchEvent): void
}

Implementation: See nfr-design-patterns.md Pattern 4

Dependencies: GameStore, InputValidator, TouchController
Used By: Application (event listeners)

Component 5: TouchController

Purpose: Touch gesture recognition for mobile
Pattern: Swipe detection with 30px threshold
Communication: Used by InputManager

Responsibilities:

  • Detect swipe gestures
  • Calculate swipe direction
  • Provide haptic feedback
  • Handle touch sensitivity

Interface:

class TouchController {
  handleTouchStart(event: TouchEvent): void
  handleTouchMove(event: TouchEvent): void
  handleTouchEnd(event: TouchEvent): Direction | null
  getSwipeDirection(): Direction | null
}

Implementation:

class TouchController {
  constructor() {
    this.touchStartX = 0;
    this.touchStartY = 0;
    this.minSwipeDistance = 30; // Medium sensitivity
    this.currentSwipe = null;
  }
  
  handleTouchStart(event) {
    const touch = event.touches[0];
    this.touchStartX = touch.clientX;
    this.touchStartY = touch.clientY;
    this.currentSwipe = null;
  }
  
  handleTouchEnd(event) {
    const touch = event.changedTouches[0];
    const deltaX = touch.clientX - this.touchStartX;
    const deltaY = touch.clientY - this.touchStartY;
    
    // Determine primary direction
    if (Math.abs(deltaX) > Math.abs(deltaY)) {
      // Horizontal swipe
      if (Math.abs(deltaX) >= this.minSwipeDistance) {
        this.currentSwipe = deltaX > 0 ? 'RIGHT' : 'LEFT';
        this.triggerHapticFeedback();
      }
    } else {
      // Vertical swipe
      if (Math.abs(deltaY) >= this.minSwipeDistance) {
        this.currentSwipe = deltaY > 0 ? 'DOWN' : 'UP';
        this.triggerHapticFeedback();
      }
    }
    
    return this.currentSwipe;
  }
  
  triggerHapticFeedback() {
    if ('vibrate' in navigator) {
      navigator.vibrate(10); // 10ms vibration
    }
  }
}

Dependencies: None
Used By: InputManager

Component 6: StorageManager

Purpose: Data persistence to localStorage
Pattern: Persist on pause and game over only
Communication: Reads from GameStore

Responsibilities:

  • Save game state to localStorage
  • Load game state from localStorage
  • Validate loaded data
  • Handle storage errors

Interface:

class StorageManager {
  saveGameState(state: GameState): boolean
  loadGameState(): GameState | null
  saveHighScores(scores: Score[]): boolean
  loadHighScores(): Score[]
  clearAll(): void
}

Implementation:

class StorageManager {
  constructor() {
    this.keys = {
      GAME_STATE: 'nokiaSnake_gameState',
      HIGH_SCORES: 'nokiaSnake_highScores',
      SETTINGS: 'nokiaSnake_settings',
      VERSION: 'nokiaSnake_version'
    };
    this.currentVersion = '1.0.0';
  }
  
  saveGameState(state) {
    try {
      const saveData = {
        version: this.currentVersion,
        timestamp: new Date().toISOString(),
        snake: state.snake,
        food: state.food,
        score: state.score,
        consecutiveFoods: state.consecutiveFoods,
        gameTime: state.gameTime
      };
      
      localStorage.setItem(this.keys.GAME_STATE, JSON.stringify(saveData));
      
      // Log save event
      securityLogger.log({
        type: 'DATA_PERSISTENCE',
        subtype: 'GAME_STATE_SAVED',
        severity: 'INFO'
      });
      
      return true;
    } catch (error) {
      console.error('[STORAGE_SAVE_FAILED]', error);
      securityLogger.log({
        type: 'DATA_PERSISTENCE',
        subtype: 'SAVE_FAILED',
        severity: 'HIGH',
        details: { error: error.message }
      });
      return false;
    }
  }
  
  loadGameState() {
    try {
      const data = localStorage.getItem(this.keys.GAME_STATE);
      if (!data) return null;
      
      const parsed = JSON.parse(data);
      
      // Validate version
      if (parsed.version !== this.currentVersion) {
        console.warn('[VERSION_MISMATCH]', parsed.version, this.currentVersion);
        // Could implement migration here
      }
      
      // Validate data structure
      if (!this.validateGameState(parsed)) {
        console.error('[INVALID_GAME_STATE]');
        return null;
      }
      
      // Log load event
      securityLogger.log({
        type: 'DATA_PERSISTENCE',
        subtype: 'GAME_STATE_LOADED',
        severity: 'INFO'
      });
      
      return parsed;
    } catch (error) {
      console.error('[STORAGE_LOAD_FAILED]', error);
      securityLogger.log({
        type: 'DATA_PERSISTENCE',
        subtype: 'LOAD_FAILED',
        severity: 'HIGH',
        details: { error: error.message }
      });
      return null;
    }
  }
  
  validateGameState(state) {
    // Validate required properties
    if (!state.snake || !state.food || typeof state.score !== 'number') {
      return false;
    }
    
    // Validate snake structure
    if (!Array.isArray(state.snake.body) || state.snake.body.length < 1) {
      return false;
    }
    
    // Validate score range
    if (state.score < 0 || state.score > 1000000) {
      return false;
    }
    
    return true;
  }
}

Dependencies: GameStore, SecurityLogger
Used By: GameEngine (on pause/game over events)

Component 7: SecurityLogger

Purpose: Audit logging with rotation
Pattern: Structured logging with 1000 entry limit
Communication: Used by all components for logging

Responsibilities:

  • Log security events
  • Log user actions
  • Log system events
  • Rotate logs automatically
  • Provide log query methods

Interface:

class SecurityLogger {
  log(event: LogEvent): void
  getLogs(): LogEntry[]
  getLogsByType(type: string): LogEntry[]
  getLogsBySeverity(severity: string): LogEntry[]
  clearLogs(): void
}

Implementation: See nfr-design-patterns.md Pattern 6

Dependencies: None
Used By: All components

Component 8: ErrorBoundary

Purpose: Global error handling and recovery
Pattern: Silent recovery to main menu
Communication: Resets GameStore on error

Responsibilities:

  • Catch all unhandled errors
  • Log errors securely (SECURITY-15)
  • Attempt error recovery
  • Reset to safe state

Interface:

class ErrorBoundary {
  setupGlobalHandlers(): void
  handleError(error: Error, type: string, context: any): void
  recoverFromError(error: Error): void
  resetToSafeState(): void
}

Implementation: See nfr-design-patterns.md Pattern 5

Dependencies: GameStore, SecurityLogger, StorageManager
Used By: Application (global handlers)

Component 9: PerformanceMonitor

Purpose: Track FPS and memory usage
Pattern: Lightweight monitoring with minimal overhead
Communication: Reads from GameEngine

Responsibilities:

  • Track frame rate
  • Monitor memory usage
  • Detect performance issues
  • Log performance metrics

Interface:

class PerformanceMonitor {
  start(): void
  stop(): void
  recordFrame(): void
  getFPS(): number
  getMemoryUsage(): number
  getMetrics(): PerformanceMetrics
}

Implementation:

class PerformanceMonitor {
  constructor() {
    this.frameCount = 0;
    this.lastTime = performance.now();
    this.fps = 60;
    this.fpsHistory = [];
    this.maxHistorySize = 60; // 1 second at 60 FPS
  }
  
  recordFrame() {
    this.frameCount++;
    const currentTime = performance.now();
    const deltaTime = currentTime - this.lastTime;
    
    // Update FPS every second
    if (deltaTime >= 1000) {
      this.fps = Math.round((this.frameCount * 1000) / deltaTime);
      this.fpsHistory.push(this.fps);
      
      // Keep history limited
      if (this.fpsHistory.length > this.maxHistorySize) {
        this.fpsHistory.shift();
      }
      
      this.frameCount = 0;
      this.lastTime = currentTime;
      
      // Log if FPS drops below threshold
      if (this.fps < 25) {
        securityLogger.log({
          type: 'PERFORMANCE',
          subtype: 'LOW_FPS',
          severity: 'MEDIUM',
          details: { fps: this.fps }
        });
      }
    }
  }
  
  getFPS() {
    return this.fps;
  }
  
  getMemoryUsage() {
    if (performance.memory) {
      return Math.round(performance.memory.usedJSHeapSize / 1048576); // MB
    }
    return 0;
  }
  
  getMetrics() {
    return {
      fps: this.fps,
      avgFPS: this.getAverageFPS(),
      minFPS: Math.min(...this.fpsHistory),
      maxFPS: Math.max(...this.fpsHistory),
      memoryUsage: this.getMemoryUsage()
    };
  }
  
  getAverageFPS() {
    if (this.fpsHistory.length === 0) return this.fps;
    const sum = this.fpsHistory.reduce((a, b) => a + b, 0);
    return Math.round(sum / this.fpsHistory.length);
  }
}

Dependencies: SecurityLogger
Used By: GameEngine (for monitoring)

Component 10: ObjectPool

Purpose: Reusable object pool for game entities
Pattern: Pre-allocation with acquire/release
Communication: Used by GameEngine

Responsibilities:

  • Pre-allocate game objects
  • Provide object acquisition
  • Handle object release
  • Minimize garbage collection

Interface:

class ObjectPool {
  acquire(): T
  release(obj: T): void
  releaseAll(): void
  getAvailableCount(): number
  getInUseCount(): number
}

Implementation: See nfr-design-patterns.md Pattern 2

Dependencies: None
Used By: GameEngine (for snake segments and food)

Support Components

Component 11: InputValidator

Purpose: Validate all user input
Pattern: Whitelist-based validation
Communication: Used by InputManager

Responsibilities:

  • Validate keyboard input
  • Validate touch input
  • Validate game moves
  • Log validation failures

Interface:

class InputValidator {
  validateKeyboardInput(event: KeyboardEvent): boolean
  validateTouchInput(event: TouchEvent): boolean
  validateGameMove(key: string, state: GameState): boolean
}

Implementation: See nfr-design-patterns.md Pattern 4

Dependencies: SecurityLogger
Used By: InputManager

Component 12: CanvasScaler

Purpose: Responsive canvas scaling
Pattern: Fit to container, maintain aspect ratio
Communication: Used by RenderEngine

Responsibilities:

  • Calculate canvas dimensions
  • Maintain aspect ratio
  • Handle window resize
  • Update canvas size

Interface:

class CanvasScaler {
  scaleCanvas(canvas: HTMLCanvasElement, container: HTMLElement): void
  handleResize(): void
  getScaleFactor(): number
}

Implementation:

class CanvasScaler {
  constructor(canvas, container) {
    this.canvas = canvas;
    this.container = container;
    this.aspectRatio = 1; // Square canvas
    this.setupResizeListener();
  }
  
  setupResizeListener() {
    window.addEventListener('resize', () => this.handleResize());
  }
  
  handleResize() {
    this.scaleCanvas();
  }
  
  scaleCanvas() {
    const containerWidth = this.container.clientWidth;
    const containerHeight = this.container.clientHeight;
    
    // Fit to container while maintaining aspect ratio
    if (containerWidth / containerHeight > this.aspectRatio) {
      // Container is wider - fit to height
      this.canvas.height = containerHeight;
      this.canvas.width = containerHeight * this.aspectRatio;
    } else {
      // Container is taller - fit to width
      this.canvas.width = containerWidth;
      this.canvas.height = containerWidth / this.aspectRatio;
    }
    
    // Center canvas in container
    this.canvas.style.margin = 'auto';
  }
  
  getScaleFactor() {
    return this.canvas.width / 800; // Assuming 800px base width
  }
}

Dependencies: None
Used By: RenderEngine

Component Interaction Flow

Game Start Flow

User Action → InputManager → GameStore.dispatch('START_GAME')
                           → GameEngine.start()
                           → RenderEngine.render()

Game Loop Flow

GameEngine.update() → GameStore.dispatch('MOVE_SNAKE')
                   → GameStore.notifySubscribers()
                   → RenderEngine.render()
                   → PerformanceMonitor.recordFrame()

Input Flow

Keyboard/Touch Event → InputManager.validateInput()
                    → InputValidator.validate()
                    → GameStore.dispatch(action)
                    → SecurityLogger.log()

Error Flow

Error Occurs → ErrorBoundary.handleError()
            → SecurityLogger.log()
            → StorageManager.saveErrorRecoveryState()
            → GameStore.reset()
            → RenderEngine.render(MAIN_MENU)

Save Flow

User Pauses/Game Over → GameEngine.pause()
                      → StorageManager.saveGameState()
                      → SecurityLogger.log()

Component Dependencies Matrix

Component Depends On Used By
GameStore None All components
GameEngine GameStore, ObjectPool Application
RenderEngine GameStore GameEngine
InputManager GameStore, InputValidator, TouchController Application
TouchController None InputManager
StorageManager GameStore, SecurityLogger GameEngine
SecurityLogger None All components
ErrorBoundary GameStore, SecurityLogger, StorageManager Application
PerformanceMonitor SecurityLogger GameEngine
ObjectPool None GameEngine
InputValidator SecurityLogger InputManager
CanvasScaler None RenderEngine

Component Lifecycle

Initialization Order

  1. SecurityLogger (first - needed by all)
  2. GameStore (central state)
  3. ErrorBoundary (global handlers)
  4. ObjectPool (pre-allocate objects)
  5. StorageManager (load saved state)
  6. InputValidator (validation rules)
  7. TouchController (mobile support)
  8. InputManager (event listeners)
  9. CanvasScaler (canvas setup)
  10. RenderEngine (rendering context)
  11. PerformanceMonitor (monitoring)
  12. GameEngine (game loop)

Cleanup Order

  1. GameEngine (stop game loop)
  2. InputManager (remove event listeners)
  3. PerformanceMonitor (stop monitoring)
  4. RenderEngine (cleanup context)
  5. StorageManager (final save)
  6. ObjectPool (release all)
  7. GameStore (clear state)
  8. SecurityLogger (final log)

Memory Management

Object Pools

  • Snake Segment Pool: 200 pre-allocated segments
  • Food Pool: 10 pre-allocated food items

Event Listeners

  • All event listeners properly removed on cleanup
  • No memory leaks from event handlers

State Management

  • Immutable state updates prevent memory leaks
  • Old state references garbage collected

Performance Characteristics

Component CPU Impact Memory Impact I/O Impact
GameStore Low Low None
GameEngine Medium Low None
RenderEngine High Medium None
InputManager Low Low None
StorageManager Low Low High (localStorage)
SecurityLogger Low Medium High (localStorage)
PerformanceMonitor Low Low None

Security Considerations

Input Validation (SECURITY-05)

  • InputValidator validates all input
  • Whitelist-based validation
  • Game state validation

Exception Handling (SECURITY-15)

  • ErrorBoundary catches all errors
  • Secure error logging
  • No sensitive data exposure

Audit Logging

  • SecurityLogger logs all security events
  • Structured logging format
  • Automatic log rotation

Conclusion

This component architecture provides:

  • Centralized State Management: All components communicate through GameStore
  • Clear Separation of Concerns: Each component has a single responsibility
  • Security Compliance: Input validation and error handling built-in
  • Performance Optimization: Object pooling, efficient rendering, minimal overhead
  • Mobile Support: Touch controls, responsive scaling
  • Offline Capability: localStorage persistence, no network dependencies

All components work together to implement the NFR requirements while maintaining code quality and maintainability.