NFR Design Patterns - Nokia Snake Game
Design patterns used
NFR Design Patterns - Nokia Snake Game
Overview
Unit Name: Nokia Snake Game (Monolithic)
Purpose: Define design patterns for implementing non-functional requirements
Scope: Performance, security, mobile, offline, resilience, and accessibility patterns
Performance Design Patterns
Pattern 1: Fixed Time Step with Frame Skipping
Category: Performance - Game Loop
Problem: Need consistent gameplay across devices with varying performance
Solution: Fixed time step for game logic, skip rendering frames if behind schedule
Implementation:
class GameLoop {
constructor() {
this.targetFPS = 60;
this.fixedTimeStep = 1000 / this.targetFPS; // 16.67ms
this.maxFrameSkip = 5; // Maximum frames to skip
this.accumulator = 0;
this.lastTime = performance.now();
}
start() {
this.running = true;
this.loop();
}
loop() {
if (!this.running) return;
const currentTime = performance.now();
const deltaTime = currentTime - this.lastTime;
this.lastTime = currentTime;
// Add to accumulator
this.accumulator += deltaTime;
// Update game logic with fixed time step
let framesSkipped = 0;
while (this.accumulator >= this.fixedTimeStep && framesSkipped < this.maxFrameSkip) {
this.updateGameLogic(this.fixedTimeStep);
this.accumulator -= this.fixedTimeStep;
framesSkipped++;
}
// Render current state
this.render();
// Continue loop
requestAnimationFrame(() => this.loop());
}
updateGameLogic(deltaTime) {
// Update snake position, check collisions, etc.
gameState.update(deltaTime);
}
render() {
// Render current game state
renderer.render(gameState);
}
}
Benefits:
- Consistent gameplay speed across devices
- Smooth rendering on capable devices
- Graceful degradation on slow devices
Trade-offs:
- May skip rendering frames on slow devices
- Slightly more complex than simple loop
Pattern 2: Object Pooling for Game Entities
Category: Performance - Memory Management
Problem: Frequent allocation/deallocation of snake segments and food causes GC pauses
Solution: Pre-allocate object pools and reuse objects
Implementation:
class ObjectPool {
constructor(createFn, resetFn, initialSize = 100) {
this.createFn = createFn;
this.resetFn = resetFn;
this.available = [];
this.inUse = new Set();
// Pre-allocate objects
for (let i = 0; i < initialSize; i++) {
this.available.push(this.createFn());
}
}
acquire() {
let obj;
if (this.available.length > 0) {
obj = this.available.pop();
} else {
obj = this.createFn();
}
this.inUse.add(obj);
return obj;
}
release(obj) {
if (this.inUse.has(obj)) {
this.inUse.delete(obj);
this.resetFn(obj);
this.available.push(obj);
}
}
releaseAll() {
this.inUse.forEach(obj => {
this.resetFn(obj);
this.available.push(obj);
});
this.inUse.clear();
}
}
// Usage
const snakeSegmentPool = new ObjectPool(
() => ({ x: 0, y: 0, color: '#00FF00' }),
(segment) => { segment.x = 0; segment.y = 0; },
200 // Pre-allocate 200 segments
);
const foodPool = new ObjectPool(
() => ({ x: 0, y: 0, type: 'REGULAR', value: 10 }),
(food) => { food.x = 0; food.y = 0; food.type = 'REGULAR'; },
10 // Pre-allocate 10 food items
);
Benefits:
- Eliminates GC pauses during gameplay
- Predictable memory usage
- Improved performance
Trade-offs:
- Slightly more complex object management
- Initial memory allocation
Pattern 3: WebGL Rendering with Canvas 2D Fallback
Category: Performance - Rendering
Problem: Need hardware acceleration but must support devices without WebGL
Solution: Feature detection with graceful fallback
Implementation:
class RendererFactory {
static createRenderer(canvas) {
// Feature detection before initialization
if (this.isWebGLAvailable(canvas)) {
console.log('Using WebGL renderer');
return new WebGLRenderer(canvas);
} else {
console.log('WebGL not available, using Canvas 2D renderer');
return new Canvas2DRenderer(canvas);
}
}
static isWebGLAvailable(canvas) {
try {
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
return !!gl;
} catch (e) {
return false;
}
}
}
class WebGLRenderer {
constructor(canvas) {
this.canvas = canvas;
this.gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
this.initializeWebGL();
}
initializeWebGL() {
// Set up shaders, buffers, etc.
}
render(gameState) {
// WebGL rendering implementation
}
}
class Canvas2DRenderer {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
}
render(gameState) {
// Canvas 2D rendering implementation
}
}
Benefits:
- Hardware acceleration when available
- Broad device compatibility
- Transparent fallback
Trade-offs:
- Two rendering implementations to maintain
- Feature detection overhead
Security Design Patterns
Pattern 4: Whitelist-Based Input Validation
Category: Security - Input Validation (SECURITY-05)
Problem: Need to validate all user input to prevent injection attacks
Solution: Whitelist validation with comprehensive checks
Implementation:
class InputValidator {
constructor() {
this.allowedKeys = new Set([
'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight',
' ', 'Escape', 'Enter', 'Tab'
]);
this.allowedEventTypes = new Set(['keydown', 'keyup']);
}
validateKeyboardInput(event) {
// Type checking
if (!event || typeof event !== 'object') {
this.logSecurityEvent('INVALID_INPUT_TYPE', { type: typeof event });
return false;
}
// Property validation
if (!event.hasOwnProperty('key') || !event.hasOwnProperty('type')) {
this.logSecurityEvent('MISSING_INPUT_PROPERTIES', { event });
return false;
}
// Whitelist validation - key
if (!this.allowedKeys.has(event.key)) {
this.logSecurityEvent('INVALID_KEY', { key: event.key });
return false;
}
// Whitelist validation - event type
if (!this.allowedEventTypes.has(event.type)) {
this.logSecurityEvent('INVALID_EVENT_TYPE', { type: event.type });
return false;
}
// Game state validation
if (!this.validateGameMove(event.key, gameState)) {
this.logSecurityEvent('INVALID_GAME_MOVE', { key: event.key, state: gameState.status });
return false;
}
return true;
}
validateGameMove(key, gameState) {
// Only validate direction keys during gameplay
if (gameState.status !== 'PLAYING') return true;
const currentDirection = gameState.snake.direction;
const oppositeDirections = {
'UP': 'DOWN', 'DOWN': 'UP',
'LEFT': 'RIGHT', 'RIGHT': 'LEFT'
};
// Prevent 180-degree turns
const newDirection = this.keyToDirection(key);
if (newDirection && oppositeDirections[currentDirection] === newDirection) {
return false;
}
return true;
}
keyToDirection(key) {
const mapping = {
'ArrowUp': 'UP', 'ArrowDown': 'DOWN',
'ArrowLeft': 'LEFT', 'ArrowRight': 'RIGHT'
};
return mapping[key] || null;
}
logSecurityEvent(type, details) {
securityLogger.log({
type: 'INPUT_VALIDATION_FAILURE',
subtype: type,
details,
timestamp: new Date().toISOString(),
severity: 'MEDIUM'
});
}
}
Benefits:
- Comprehensive input validation
- Security event logging
- Prevents invalid game states
Trade-offs:
- Additional validation overhead
- More complex input handling
Pattern 5: Global Error Boundary with Silent Recovery
Category: Security - Exception Handling (SECURITY-15)
Problem: Need to handle all errors gracefully without exposing sensitive information
Solution: Global error boundary with safe state fallback
Implementation:
class ErrorBoundary {
constructor() {
this.setupGlobalHandlers();
}
setupGlobalHandlers() {
// Unhandled errors
window.addEventListener('error', (event) => {
this.handleError(event.error, 'UNHANDLED_ERROR', event);
event.preventDefault();
});
// Unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
this.handleError(event.reason, 'UNHANDLED_REJECTION', event);
event.preventDefault();
});
}
handleError(error, type, context) {
// Log error securely (no sensitive data)
this.logError(error, type, context);
// Attempt recovery
this.recoverFromError(error, type);
}
logError(error, type, context) {
const errorLog = {
type,
message: error?.message || 'Unknown error',
timestamp: new Date().toISOString(),
severity: 'HIGH',
userAgent: navigator.userAgent,
// DO NOT include stack trace or sensitive data
};
securityLogger.log(errorLog);
console.error('[ERROR]', errorLog);
}
recoverFromError(error, type) {
try {
// Attempt to save current state
if (window.gameState && gameState.status === 'PLAYING') {
storageManager.saveErrorRecoveryState(gameState);
}
// Reset to safe state (main menu)
this.resetToSafeState();
} catch (recoveryError) {
// If recovery fails, force reload
console.error('[RECOVERY_FAILED]', recoveryError);
this.forceReload();
}
}
resetToSafeState() {
// Silent recovery - no user notification
gameState.reset();
gameState.status = 'MAIN_MENU';
renderer.render(gameState);
}
forceReload() {
// Last resort - reload page
setTimeout(() => {
window.location.reload();
}, 1000);
}
}
Benefits:
- Graceful error handling
- No sensitive data exposure
- Automatic recovery
Trade-offs:
- Silent failures may confuse users
- Loss of current game state
Pattern 6: Audit Logging with Rotation
Category: Security - Audit Logging
Problem: Need comprehensive audit trail without filling localStorage
Solution: Structured logging with automatic rotation
Implementation:
class SecurityLogger {
constructor() {
this.storageKey = 'nokiaSnake_auditLog';
this.maxEntries = 1000;
}
log(event) {
const logEntry = {
id: this.generateId(),
timestamp: new Date().toISOString(),
type: event.type,
subtype: event.subtype || null,
severity: event.severity || 'INFO',
details: this.sanitizeDetails(event.details),
userAgent: navigator.userAgent,
sessionId: this.getSessionId()
};
// Log to console
console.log(`[AUDIT:${logEntry.severity}]`, logEntry);
// Persist to localStorage
this.persistLog(logEntry);
}
persistLog(entry) {
try {
const logs = this.getLogs();
logs.push(entry);
// Rotate if exceeds max entries
if (logs.length > this.maxEntries) {
logs.shift(); // Remove oldest entry
}
localStorage.setItem(this.storageKey, JSON.stringify(logs));
} catch (error) {
console.error('[AUDIT_LOG_FAILED]', error);
}
}
getLogs() {
try {
const data = localStorage.getItem(this.storageKey);
return data ? JSON.parse(data) : [];
} catch (error) {
console.error('[AUDIT_LOG_READ_FAILED]', error);
return [];
}
}
sanitizeDetails(details) {
// Remove any sensitive data
if (!details) return null;
const sanitized = { ...details };
// Remove potential PII
delete sanitized.email;
delete sanitized.username;
delete sanitized.password;
return sanitized;
}
generateId() {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
getSessionId() {
let sessionId = sessionStorage.getItem('nokiaSnake_sessionId');
if (!sessionId) {
sessionId = this.generateId();
sessionStorage.setItem('nokiaSnake_sessionId', sessionId);
}
return sessionId;
}
// Query methods
getLogsByType(type) {
return this.getLogs().filter(log => log.type === type);
}
getLogsBySeverity(severity) {
return this.getLogs().filter(log => log.severity === severity);
}
getLogsInTimeRange(startTime, endTime) {
return this.getLogs().filter(log => {
const timestamp = new Date(log.timestamp).getTime();
return timestamp >= startTime && timestamp <= endTime;
});
}
}
Benefits:
- Comprehensive audit trail
- Automatic rotation prevents storage overflow
- Queryable log data
Trade-offs:
- localStorage space usage
- Potential performance impact on writes
Mobile Optimization Patterns
Pattern 7: Touch Gesture Recognition (Medium Sensitivity)
Category: Mobile - Touch Input
Problem: Need intuitive touch controls for mobile devices
Solution: Swipe gesture detection with 30px minimum distance
Implementation: See logical-components.md for TouchController implementation
Benefits:
- Intuitive mobile controls
- Prevents accidental inputs
- Responsive touch feedback
Pattern 8: Responsive Canvas Scaling
Category: Mobile - Responsive Design
Problem: Need to adapt to various screen sizes
Solution: Fit to container while maintaining aspect ratio
Implementation: See logical-components.md for canvas scaling implementation
Benefits:
- Works on all screen sizes
- Maintains visual consistency
- No distortion
Offline Functionality Patterns
Pattern 9: State Persistence on User Actions
Category: Offline - Data Persistence
Problem: Need to save game state without impacting performance
Solution: Persist only on pause and game over events
Implementation: See logical-components.md for StorageManager implementation
Benefits:
- Minimal performance impact
- User-initiated saves
- Data safety on critical events
Pattern 10: Centralized State Management
Category: Offline - State Management
Problem: Need predictable state updates and component communication
Solution: Redux-like store with unidirectional data flow
Implementation: See logical-components.md for GameStore implementation
Benefits:
- Predictable state updates
- Easy debugging
- Centralized communication
Performance Monitoring Pattern
Pattern 11: FPS and Memory Tracking
Category: Performance - Monitoring
Problem: Need to monitor performance during gameplay
Solution: Track FPS and memory usage with minimal overhead
Implementation: See logical-components.md for PerformanceMonitor implementation
Benefits:
- Real-time performance insights
- Identify performance issues
- Minimal overhead
Pattern Summary
| Pattern | Category | Priority | Complexity |
|---|---|---|---|
| Fixed Time Step with Frame Skipping | Performance | High | Medium |
| Object Pooling | Performance | High | Medium |
| WebGL with Canvas 2D Fallback | Performance | High | High |
| Whitelist Input Validation | Security | Critical | Medium |
| Global Error Boundary | Security | Critical | Medium |
| Audit Logging with Rotation | Security | High | Low |
| Touch Gesture Recognition | Mobile | High | Medium |
| Responsive Canvas Scaling | Mobile | High | Low |
| State Persistence on User Actions | Offline | Medium | Low |
| Centralized State Management | Offline | High | Medium |
| FPS and Memory Tracking | Performance | Medium | Low |
Pattern Dependencies
Fixed Time Step ──> Object Pooling
└──> WebGL Rendering
Whitelist Validation ──> Audit Logging
└──> Error Boundary
Touch Gestures ──> Centralized State
└──> Input Validation
State Persistence ──> Centralized State
└──> Audit Logging
Implementation Priority
Phase 1 - Core Patterns (Must have):
- Centralized State Management
- Fixed Time Step Game Loop
- WebGL with Fallback
- Whitelist Input Validation
- Global Error Boundary
Phase 2 - Optimization Patterns (Should have):
- Object Pooling
- Audit Logging
- Touch Gesture Recognition
- Responsive Canvas Scaling
Phase 3 - Enhancement Patterns (Nice to have):
- State Persistence
- Performance Monitoring
Security Compliance
All patterns comply with security baseline requirements:
- SECURITY-05 (Input Validation): Pattern 4 - Whitelist-Based Input Validation
- SECURITY-15 (Exception Handling): Pattern 5 - Global Error Boundary
Conclusion
These design patterns provide a comprehensive framework for implementing all NFR requirements while maintaining code quality, performance, and security.