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
- SecurityLogger (first - needed by all)
- GameStore (central state)
- ErrorBoundary (global handlers)
- ObjectPool (pre-allocate objects)
- StorageManager (load saved state)
- InputValidator (validation rules)
- TouchController (mobile support)
- InputManager (event listeners)
- CanvasScaler (canvas setup)
- RenderEngine (rendering context)
- PerformanceMonitor (monitoring)
- GameEngine (game loop)
Cleanup Order
- GameEngine (stop game loop)
- InputManager (remove event listeners)
- PerformanceMonitor (stop monitoring)
- RenderEngine (cleanup context)
- StorageManager (final save)
- ObjectPool (release all)
- GameStore (clear state)
- 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.