Business Logic Model - Nokia Snake Game
Game logic model
Business Logic Model - Nokia Snake Game
Overview
Game Type: Classic Nokia Snake with modern enhancements
Core Loop: Move snake → Collect food → Grow snake → Avoid collisions → Increase score
Difficulty: Gradual speed increase as snake grows
Scoring: Progressive scoring with consecutive food bonuses
Core Game Logic
Game State Transitions
stateDiagram-v2
[*] --> Initialized
Initialized --> MainMenu
MainMenu --> GamePlaying : Start Game
GamePlaying --> GamePaused : Pause
GamePaused --> GamePlaying : Resume
GamePlaying --> GameOver : Collision
GameOver --> MainMenu : Return to Menu
GameOver --> GamePlaying : Restart
GamePlaying --> MainMenu : Quit to Menu
Game Loop Algorithm
1. Initialize game state
2. Start game loop (60 FPS target):
a. Process input (keyboard events)
b. Update game state:
- Move snake based on current direction
- Check for collisions
- Check for food collection
- Update score
- Apply game rules
c. Render game state
d. Repeat until game over
Game Mechanics
Snake Movement Logic
// Snake movement algorithm
function moveSnake(snake, direction) {
// 1. Create new head based on current direction
const newHead = calculateNewHead(snake.head, direction);
// 2. Add new head to snake body
snake.body.unshift(newHead);
// 3. If no food collected, remove tail
if (!foodCollected) {
snake.body.pop();
}
return snake;
}
// Direction calculation
function calculateNewHead(currentHead, direction) {
switch(direction) {
case 'UP': return { x: currentHead.x, y: currentHead.y - 1 };
case 'DOWN': return { x: currentHead.x, y: currentHead.y + 1 };
case 'LEFT': return { x: currentHead.x - 1, y: currentHead.y };
case 'RIGHT': return { x: currentHead.x + 1, y: currentHead.y };
}
}
Food Generation Logic
// Food generation algorithm
function generateFood(gameState) {
// 1. Generate random position within game bounds
const position = {
x: Math.floor(Math.random() * gridWidth),
y: Math.floor(Math.random() * gridHeight)
};
// 2. Ensure position doesn't overlap with snake
while (positionOverlapsSnake(position, gameState.snake)) {
position.x = Math.floor(Math.random() * gridWidth);
position.y = Math.floor(Math.random() * gridHeight);
}
// 3. Determine food type (regular or power-up)
const foodType = determineFoodType(gameState);
return {
position,
type: foodType,
value: calculateFoodValue(foodType, gameState)
};
}
// Food type determination (10% chance for power-up)
function determineFoodType(gameState) {
const powerUpChance = 0.1; // 10% chance
return Math.random() < powerUpChance ? 'POWER_UP' : 'REGULAR';
}
Power-up Mechanics (Speed Boost)
// Power-up activation
function activatePowerUp(gameState, powerUpType) {
if (powerUpType === 'SPEED_BOOST') {
// Increase game speed by 25%
gameState.gameSpeed *= 1.25;
// Set power-up active flag
gameState.activePowerUp = {
type: 'SPEED_BOOST',
duration: 'UNTIL_NEXT_FOOD', // Lasts until next food collection
originalSpeed: gameState.gameSpeed / 1.25 // Store for reset
};
}
return gameState;
}
// Power-up deactivation (when next food is collected)
function deactivatePowerUp(gameState) {
if (gameState.activePowerUp?.type === 'SPEED_BOOST') {
// Reset to original speed
gameState.gameSpeed = gameState.activePowerUp.originalSpeed;
gameState.activePowerUp = null;
}
return gameState;
}
Difficulty Progression
// Gradual speed increase as snake grows
function updateGameSpeed(gameState) {
const baseSpeed = 10; // Initial speed (grid cells per second)
const speedIncreasePerSegment = 0.5; // Speed increase per snake segment
// Calculate new speed based on snake length
const newSpeed = baseSpeed + (gameState.snake.length * speedIncreasePerSegment);
// Apply power-up multiplier if active
if (gameState.activePowerUp?.type === 'SPEED_BOOST') {
gameState.gameSpeed = newSpeed * 1.25;
} else {
gameState.gameSpeed = newSpeed;
}
return gameState;
}
Scoring Algorithms
Progressive Scoring System
// Score calculation for food collection
function calculateFoodScore(gameState, foodType) {
let baseScore = 10; // Base points per food
// Apply consecutive food bonus
if (gameState.consecutiveFoods > 0) {
const bonusMultiplier = 1 + (gameState.consecutiveFoods * 0.1); // 10% increase per consecutive food
baseScore = Math.floor(baseScore * bonusMultiplier);
}
// Apply food type multiplier
switch(foodType) {
case 'REGULAR':
return baseScore;
case 'POWER_UP':
return baseScore * 2; // Double points for power-up food
default:
return baseScore;
}
}
// Update consecutive food counter
function updateConsecutiveFoods(gameState, foodCollected) {
if (foodCollected) {
gameState.consecutiveFoods++;
} else {
gameState.consecutiveFoods = 0; // Reset on game over or missed food
}
return gameState;
}
High Score Tracking
// High score management
function updateHighScore(currentScore, highScores) {
// Add new score
const newScoreEntry = {
score: currentScore,
date: new Date().toISOString(),
snakeLength: gameState.snake.length
};
// Add to scores array
highScores.push(newScoreEntry);
// Sort descending by score
highScores.sort((a, b) => b.score - a.score);
// Keep only top 10 scores
return highScores.slice(0, 10);
}
State Management Logic
Game State Structure
const initialGameState = {
// Game status
status: 'MAIN_MENU', // MAIN_MENU, PLAYING, PAUSED, GAME_OVER
gameSpeed: 10, // Grid cells per second
// Snake data
snake: {
body: [{x: 10, y: 10}], // Array of positions
direction: 'RIGHT', // Current direction
length: 1
},
// Game objects
food: {x: 5, y: 5, type: 'REGULAR', value: 10},
powerUps: [], // Active power-ups
// Game metrics
score: 0,
consecutiveFoods: 0, // For progressive scoring
gameTime: 0, // Time in seconds
// Settings
gridSize: {width: 20, height: 20},
controls: {up: 'ArrowUp', down: 'ArrowDown', left: 'ArrowLeft', right: 'ArrowRight'},
// Power-up state
activePowerUp: null
};
State Persistence Logic
// Save game state
function saveGameState(gameState) {
const saveData = {
// Basic game state (snake, food, score)
snake: gameState.snake,
food: gameState.food,
score: gameState.score,
consecutiveFoods: gameState.consecutiveFoods,
gameTime: gameState.gameTime,
// Game settings
gameSpeed: gameState.gameSpeed,
gridSize: gameState.gridSize,
// Timestamp for reference
savedAt: new Date().toISOString()
};
// Serialize and save to localStorage
localStorage.setItem('nokiaSnake_saveGame', JSON.stringify(saveData));
}
// Load game state
function loadGameState() {
const savedData = localStorage.getItem('nokiaSnake_saveGame');
if (!savedData) return null;
try {
const parsedData = JSON.parse(savedData);
// Merge with initial state
return {
...initialGameState,
...parsedData,
status: 'PLAYING' // Set to playing when loaded
};
} catch (error) {
console.error('Failed to load game state:', error);
return null;
}
}
Data Flow Design
Game Loop Data Flow
Input Processing:
1. Keyboard event → Input Handler → Validated Input → Game State Update
Game State Update:
1. Current State + Validated Input → Game Engine → Updated State
2. Updated State → Collision Detection → Collision Results
3. Collision Results → Game Rules → Final State
4. Final State → Score Calculation → Updated Score
Rendering:
1. Final State → Rendering Engine → Visual Representation
2. Visual Representation → WebGL Context → Screen Update
Score Update:
1. Game Events → Score Service → Score Calculation
2. Score Calculation → High Score Check → High Score Update
3. High Score Update → Persistence Service → Local Storage
State Persistence Flow
Save Game:
1. User requests save → Game State Serialization → JSON Data
2. JSON Data → Local Storage API → Browser Storage
3. Success/Failure → User Feedback
Load Game:
1. User requests load → Local Storage API → JSON Data
2. JSON Data → Game State Deserialization → Game State
3. Game State → Validation → Validated State
4. Validated State → Game Initialization → Continue Game
High Score Persistence:
1. Game Over → Score Calculation → Final Score
2. Final Score → High Score Array Update → Sorted Array
3. Sorted Array → Local Storage API → Persistent Storage
Error Handling Logic
Silent Fail Strategy
// Global error handler
function handleGameError(error, context) {
// Log error for debugging
console.error(`Game error in ${context}:`, error);
// Attempt to continue game if possible
try {
// Fallback to safe state
const safeState = getSafeState();
// Continue with safe state
return safeState;
} catch (fallbackError) {
// If fallback fails, restart game
console.error('Fallback failed, restarting game:', fallbackError);
return restartGame();
}
}
// Safe state fallback
function getSafeState() {
return {
...initialGameState,
status: 'PLAYING',
errorRecovery: true // Flag to indicate recovery state
};
}
Input Validation Flow
1. Raw Input → Input Validator → Validation Rules
2. Validation Rules → State Check → Valid/Invalid
3. Valid Input → Game State Update
4. Invalid Input → Error Logging → Safe Fallback
5. Safe Fallback → Continue Game
Performance Considerations
Game Loop Optimization
- Fixed Time Step: Use fixed time step for consistent gameplay
- Frame Skipping: Allow frame skipping if behind schedule
- State Batching: Batch state updates for efficiency
- Rendering Optimization: Use WebGL best practices
Memory Management
- Object Pooling: Reuse game objects where possible
- Garbage Collection: Minimize object creation in game loop
- State Serialization: Efficient serialization for persistence
Security Considerations
Input Validation (SECURITY-05 Compliance)
// Comprehensive input validation
function validateInput(input, gameState) {
// 1. Type checking
if (typeof input !== 'object') return false;
// 2. Property validation
if (!input.hasOwnProperty('key') || !input.hasOwnProperty('action')) return false;
// 3. Key validation (allowed keys only)
const allowedKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' ', 'Escape'];
if (!allowedKeys.includes(input.key)) return false;
// 4. Action validation
const allowedActions = ['KEY_DOWN', 'KEY_UP'];
if (!allowedActions.includes(input.action)) return false;
// 5. Game state validation (prevent invalid moves)
if (input.action === 'KEY_DOWN') {
return validateGameMove(input.key, gameState);
}
return true;
}
// Game move validation
function validateGameMove(key, gameState) {
const currentDirection = gameState.snake.direction;
// Prevent 180-degree turns (can't go right when moving left)
switch(key) {
case 'ArrowUp': return currentDirection !== 'DOWN';
case 'ArrowDown': return currentDirection !== 'UP';
case 'ArrowLeft': return currentDirection !== 'RIGHT';
case 'ArrowRight': return currentDirection !== 'LEFT';
default: return true;
}
}
Exception Handling (SECURITY-15 Compliance)
// Global exception handler
window.addEventListener('error', (event) => {
// Log error
console.error('Unhandled error:', event.error);
// Attempt to recover
try {
// Save current state if possible
if (window.gameState) {
localStorage.setItem('nokiaSnake_errorRecovery', JSON.stringify(window.gameState));
}
// Show recovery message
showRecoveryMessage();
// Restart game
setTimeout(() => {
window.location.reload();
}, 3000);
} catch (recoveryError) {
console.error('Recovery failed:', recoveryError);
}
// Prevent default error handling
event.preventDefault();
});